| // Copyright 2012 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package ssh |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| "io" |
| "net" |
| "sort" |
| "time" |
| ) |
| |
| // These constants from [PROTOCOL.certkeys] represent the algorithm names |
| // for certificate types supported by this package. |
| const ( |
| CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com" |
| CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com" |
| CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com" |
| CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com" |
| CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com" |
| CertAlgoSKECDSA256v01 = "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com" |
| CertAlgoED25519v01 = "ssh-ed25519-cert-v01@openssh.com" |
| CertAlgoSKED25519v01 = "sk-ssh-ed25519-cert-v01@openssh.com" |
| ) |
| |
| // Certificate types distinguish between host and user |
| // certificates. The values can be set in the CertType field of |
| // Certificate. |
| const ( |
| UserCert = 1 |
| HostCert = 2 |
| ) |
| |
| // Signature represents a cryptographic signature. |
| type Signature struct { |
| Format string |
| Blob []byte |
| Rest []byte `ssh:"rest"` |
| } |
| |
| // CertTimeInfinity can be used for OpenSSHCertV01.ValidBefore to indicate that |
| // a certificate does not expire. |
| const CertTimeInfinity = 1<<64 - 1 |
| |
| // An Certificate represents an OpenSSH certificate as defined in |
| // [PROTOCOL.certkeys]?rev=1.8. The Certificate type implements the |
| // PublicKey interface, so it can be unmarshaled using |
| // ParsePublicKey. |
| type Certificate struct { |
| Nonce []byte |
| Key PublicKey |
| Serial uint64 |
| CertType uint32 |
| KeyId string |
| ValidPrincipals []string |
| ValidAfter uint64 |
| ValidBefore uint64 |
| Permissions |
| Reserved []byte |
| SignatureKey PublicKey |
| Signature *Signature |
| } |
| |
| // genericCertData holds the key-independent part of the certificate data. |
| // Overall, certificates contain an nonce, public key fields and |
| // key-independent fields. |
| type genericCertData struct { |
| Serial uint64 |
| CertType uint32 |
| KeyId string |
| ValidPrincipals []byte |
| ValidAfter uint64 |
| ValidBefore uint64 |
| CriticalOptions []byte |
| Extensions []byte |
| Reserved []byte |
| SignatureKey []byte |
| Signature []byte |
| } |
| |
| func marshalStringList(namelist []string) []byte { |
| var to []byte |
| for _, name := range namelist { |
| s := struct{ N string }{name} |
| to = append(to, Marshal(&s)...) |
| } |
| return to |
| } |
| |
| type optionsTuple struct { |
| Key string |
| Value []byte |
| } |
| |
| type optionsTupleValue struct { |
| Value string |
| } |
| |
| // serialize a map of critical options or extensions |
| // issue #10569 - per [PROTOCOL.certkeys] and SSH implementation, |
| // we need two length prefixes for a non-empty string value |
| func marshalTuples(tups map[string]string) []byte { |
| keys := make([]string, 0, len(tups)) |
| for key := range tups { |
| keys = append(keys, key) |
| } |
| sort.Strings(keys) |
| |
| var ret []byte |
| for _, key := range keys { |
| s := optionsTuple{Key: key} |
| if value := tups[key]; len(value) > 0 { |
| s.Value = Marshal(&optionsTupleValue{value}) |
| } |
| ret = append(ret, Marshal(&s)...) |
| } |
| return ret |
| } |
| |
| // issue #10569 - per [PROTOCOL.certkeys] and SSH implementation, |
| // we need two length prefixes for a non-empty option value |
| func parseTuples(in []byte) (map[string]string, error) { |
| tups := map[string]string{} |
| var lastKey string |
| var haveLastKey bool |
| |
| for len(in) > 0 { |
| var key, val, extra []byte |
| var ok bool |
| |
| if key, in, ok = parseString(in); !ok { |
| return nil, errShortRead |
| } |
| keyStr := string(key) |
| // according to [PROTOCOL.certkeys], the names must be in |
| // lexical order. |
| if haveLastKey && keyStr <= lastKey { |
| return nil, fmt.Errorf("ssh: certificate options are not in lexical order") |
| } |
| lastKey, haveLastKey = keyStr, true |
| // the next field is a data field, which if non-empty has a string embedded |
| if val, in, ok = parseString(in); !ok { |
| return nil, errShortRead |
| } |
| if len(val) > 0 { |
| val, extra, ok = parseString(val) |
| if !ok { |
| return nil, errShortRead |
| } |
| if len(extra) > 0 { |
| return nil, fmt.Errorf("ssh: unexpected trailing data after certificate option value") |
| } |
| tups[keyStr] = string(val) |
| } else { |
| tups[keyStr] = "" |
| } |
| } |
| return tups, nil |
| } |
| |
| func parseCert(in []byte, privAlgo string) (*Certificate, error) { |
| nonce, rest, ok := parseString(in) |
| if !ok { |
| return nil, errShortRead |
| } |
| |
| key, rest, err := parsePubKey(rest, privAlgo) |
| if err != nil { |
| return nil, err |
| } |
| |
| var g genericCertData |
| if err := Unmarshal(rest, &g); err != nil { |
| return nil, err |
| } |
| |
| c := &Certificate{ |
| Nonce: nonce, |
| Key: key, |
| Serial: g.Serial, |
| CertType: g.CertType, |
| KeyId: g.KeyId, |
| ValidAfter: g.ValidAfter, |
| ValidBefore: g.ValidBefore, |
| } |
| |
| for principals := g.ValidPrincipals; len(principals) > 0; { |
| principal, rest, ok := parseString(principals) |
| if !ok { |
| return nil, errShortRead |
| } |
| c.ValidPrincipals = append(c.ValidPrincipals, string(principal)) |
| principals = rest |
| } |
| |
| c.CriticalOptions, err = parseTuples(g.CriticalOptions) |
| if err != nil { |
| return nil, err |
| } |
| c.Extensions, err = parseTuples(g.Extensions) |
| if err != nil { |
| return nil, err |
| } |
| c.Reserved = g.Reserved |
| k, err := ParsePublicKey(g.SignatureKey) |
| if err != nil { |
| return nil, err |
| } |
| |
| c.SignatureKey = k |
| c.Signature, rest, ok = parseSignatureBody(g.Signature) |
| if !ok || len(rest) > 0 { |
| return nil, errors.New("ssh: signature parse error") |
| } |
| |
| return c, nil |
| } |
| |
| type openSSHCertSigner struct { |
| pub *Certificate |
| signer Signer |
| } |
| |
| type algorithmOpenSSHCertSigner struct { |
| *openSSHCertSigner |
| algorithmSigner AlgorithmSigner |
| } |
| |
| // NewCertSigner returns a Signer that signs with the given Certificate, whose |
| // private key is held by signer. It returns an error if the public key in cert |
| // doesn't match the key used by signer. |
| func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) { |
| if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 { |
| return nil, errors.New("ssh: signer and cert have different public key") |
| } |
| |
| if algorithmSigner, ok := signer.(AlgorithmSigner); ok { |
| return &algorithmOpenSSHCertSigner{ |
| &openSSHCertSigner{cert, signer}, algorithmSigner}, nil |
| } else { |
| return &openSSHCertSigner{cert, signer}, nil |
| } |
| } |
| |
| func (s *openSSHCertSigner) Sign(rand io.Reader, data []byte) (*Signature, error) { |
| return s.signer.Sign(rand, data) |
| } |
| |
| func (s *openSSHCertSigner) PublicKey() PublicKey { |
| return s.pub |
| } |
| |
| func (s *algorithmOpenSSHCertSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) { |
| return s.algorithmSigner.SignWithAlgorithm(rand, data, algorithm) |
| } |
| |
| const sourceAddressCriticalOption = "source-address" |
| |
| // CertChecker does the work of verifying a certificate. Its methods |
| // can be plugged into ClientConfig.HostKeyCallback and |
| // ServerConfig.PublicKeyCallback. For the CertChecker to work, |
| // minimally, the IsAuthority callback should be set. |
| type CertChecker struct { |
| // SupportedCriticalOptions lists the CriticalOptions that the |
| // server application layer understands. These are only used |
| // for user certificates. |
| SupportedCriticalOptions []string |
| |
| // IsUserAuthority should return true if the key is recognized as an |
| // authority for the given user certificate. This allows for |
| // certificates to be signed by other certificates. This must be set |
| // if this CertChecker will be checking user certificates. |
| IsUserAuthority func(auth PublicKey) bool |
| |
| // IsHostAuthority should report whether the key is recognized as |
| // an authority for this host. This allows for certificates to be |
| // signed by other keys, and for those other keys to only be valid |
| // signers for particular hostnames. This must be set if this |
| // CertChecker will be checking host certificates. |
| IsHostAuthority func(auth PublicKey, address string) bool |
| |
| // Clock is used for verifying time stamps. If nil, time.Now |
| // is used. |
| Clock func() time.Time |
| |
| // UserKeyFallback is called when CertChecker.Authenticate encounters a |
| // public key that is not a certificate. It must implement validation |
| // of user keys or else, if nil, all such keys are rejected. |
| UserKeyFallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) |
| |
| // HostKeyFallback is called when CertChecker.CheckHostKey encounters a |
| // public key that is not a certificate. It must implement host key |
| // validation or else, if nil, all such keys are rejected. |
| HostKeyFallback HostKeyCallback |
| |
| // IsRevoked is called for each certificate so that revocation checking |
| // can be implemented. It should return true if the given certificate |
| // is revoked and false otherwise. If nil, no certificates are |
| // considered to have been revoked. |
| IsRevoked func(cert *Certificate) bool |
| } |
| |
| // CheckHostKey checks a host key certificate. This method can be |
| // plugged into ClientConfig.HostKeyCallback. |
| func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey) error { |
| cert, ok := key.(*Certificate) |
| if !ok { |
| if c.HostKeyFallback != nil { |
| return c.HostKeyFallback(addr, remote, key) |
| } |
| return errors.New("ssh: non-certificate host key") |
| } |
| if cert.CertType != HostCert { |
| return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType) |
| } |
| if !c.IsHostAuthority(cert.SignatureKey, addr) { |
| return fmt.Errorf("ssh: no authorities for hostname: %v", addr) |
| } |
| |
| hostname, _, err := net.SplitHostPort(addr) |
| if err != nil { |
| return err |
| } |
| |
| // Pass hostname only as principal for host certificates (consistent with OpenSSH) |
| return c.CheckCert(hostname, cert) |
| } |
| |
| // Authenticate checks a user certificate. Authenticate can be used as |
| // a value for ServerConfig.PublicKeyCallback. |
| func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) { |
| cert, ok := pubKey.(*Certificate) |
| if !ok { |
| if c.UserKeyFallback != nil { |
| return c.UserKeyFallback(conn, pubKey) |
| } |
| return nil, errors.New("ssh: normal key pairs not accepted") |
| } |
| |
| if cert.CertType != UserCert { |
| return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType) |
| } |
| if !c.IsUserAuthority(cert.SignatureKey) { |
| return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority") |
| } |
| |
| if err := c.CheckCert(conn.User(), cert); err != nil { |
| return nil, err |
| } |
| |
| return &cert.Permissions, nil |
| } |
| |
| // CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and |
| // the signature of the certificate. |
| func (c *CertChecker) CheckCert(principal string, cert *Certificate) error { |
| if c.IsRevoked != nil && c.IsRevoked(cert) { |
| return fmt.Errorf("ssh: certificate serial %d revoked", cert.Serial) |
| } |
| |
| for opt := range cert.CriticalOptions { |
| // sourceAddressCriticalOption will be enforced by |
| // serverAuthenticate |
| if opt == sourceAddressCriticalOption { |
| continue |
| } |
| |
| found := false |
| for _, supp := range c.SupportedCriticalOptions { |
| if supp == opt { |
| found = true |
| break |
| } |
| } |
| if !found { |
| return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt) |
| } |
| } |
| |
| if len(cert.ValidPrincipals) > 0 { |
| // By default, certs are valid for all users/hosts. |
| found := false |
| for _, p := range cert.ValidPrincipals { |
| if p == principal { |
| found = true |
| break |
| } |
| } |
| if !found { |
| return fmt.Errorf("ssh: principal %q not in the set of valid principals for given certificate: %q", principal, cert.ValidPrincipals) |
| } |
| } |
| |
| clock := c.Clock |
| if clock == nil { |
| clock = time.Now |
| } |
| |
| unixNow := clock().Unix() |
| if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) { |
| return fmt.Errorf("ssh: cert is not yet valid") |
| } |
| if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) { |
| return fmt.Errorf("ssh: cert has expired") |
| } |
| if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil { |
| return fmt.Errorf("ssh: certificate signature does not verify") |
| } |
| |
| return nil |
| } |
| |
| // SignCert signs the certificate with an authority, setting the Nonce, |
| // SignatureKey, and Signature fields. |
| func (c *Certificate) SignCert(rand io.Reader, authority Signer) error { |
| c.Nonce = make([]byte, 32) |
| if _, err := io.ReadFull(rand, c.Nonce); err != nil { |
| return err |
| } |
| c.SignatureKey = authority.PublicKey() |
| |
| sig, err := authority.Sign(rand, c.bytesForSigning()) |
| if err != nil { |
| return err |
| } |
| c.Signature = sig |
| return nil |
| } |
| |
| var certAlgoNames = map[string]string{ |
| KeyAlgoRSA: CertAlgoRSAv01, |
| KeyAlgoDSA: CertAlgoDSAv01, |
| KeyAlgoECDSA256: CertAlgoECDSA256v01, |
| KeyAlgoECDSA384: CertAlgoECDSA384v01, |
| KeyAlgoECDSA521: CertAlgoECDSA521v01, |
| KeyAlgoSKECDSA256: CertAlgoSKECDSA256v01, |
| KeyAlgoED25519: CertAlgoED25519v01, |
| KeyAlgoSKED25519: CertAlgoSKED25519v01, |
| } |
| |
| // certToPrivAlgo returns the underlying algorithm for a certificate algorithm. |
| // Panics if a non-certificate algorithm is passed. |
| func certToPrivAlgo(algo string) string { |
| for privAlgo, pubAlgo := range certAlgoNames { |
| if pubAlgo == algo { |
| return privAlgo |
| } |
| } |
| panic("unknown cert algorithm") |
| } |
| |
| func (cert *Certificate) bytesForSigning() []byte { |
| c2 := *cert |
| c2.Signature = nil |
| out := c2.Marshal() |
| // Drop trailing signature length. |
| return out[:len(out)-4] |
| } |
| |
| // Marshal serializes c into OpenSSH's wire format. It is part of the |
| // PublicKey interface. |
| func (c *Certificate) Marshal() []byte { |
| generic := genericCertData{ |
| Serial: c.Serial, |
| CertType: c.CertType, |
| KeyId: c.KeyId, |
| ValidPrincipals: marshalStringList(c.ValidPrincipals), |
| ValidAfter: uint64(c.ValidAfter), |
| ValidBefore: uint64(c.ValidBefore), |
| CriticalOptions: marshalTuples(c.CriticalOptions), |
| Extensions: marshalTuples(c.Extensions), |
| Reserved: c.Reserved, |
| SignatureKey: c.SignatureKey.Marshal(), |
| } |
| if c.Signature != nil { |
| generic.Signature = Marshal(c.Signature) |
| } |
| genericBytes := Marshal(&generic) |
| keyBytes := c.Key.Marshal() |
| _, keyBytes, _ = parseString(keyBytes) |
| prefix := Marshal(&struct { |
| Name string |
| Nonce []byte |
| Key []byte `ssh:"rest"` |
| }{c.Type(), c.Nonce, keyBytes}) |
| |
| result := make([]byte, 0, len(prefix)+len(genericBytes)) |
| result = append(result, prefix...) |
| result = append(result, genericBytes...) |
| return result |
| } |
| |
| // Type returns the key name. It is part of the PublicKey interface. |
| func (c *Certificate) Type() string { |
| algo, ok := certAlgoNames[c.Key.Type()] |
| if !ok { |
| panic("unknown cert key type " + c.Key.Type()) |
| } |
| return algo |
| } |
| |
| // Verify verifies a signature against the certificate's public |
| // key. It is part of the PublicKey interface. |
| func (c *Certificate) Verify(data []byte, sig *Signature) error { |
| return c.Key.Verify(data, sig) |
| } |
| |
| func parseSignatureBody(in []byte) (out *Signature, rest []byte, ok bool) { |
| format, in, ok := parseString(in) |
| if !ok { |
| return |
| } |
| |
| out = &Signature{ |
| Format: string(format), |
| } |
| |
| if out.Blob, in, ok = parseString(in); !ok { |
| return |
| } |
| |
| switch out.Format { |
| case KeyAlgoSKECDSA256, CertAlgoSKECDSA256v01, KeyAlgoSKED25519, CertAlgoSKED25519v01: |
| out.Rest = in |
| return out, nil, ok |
| } |
| |
| return out, in, ok |
| } |
| |
| func parseSignature(in []byte) (out *Signature, rest []byte, ok bool) { |
| sigBytes, rest, ok := parseString(in) |
| if !ok { |
| return |
| } |
| |
| out, trailing, ok := parseSignatureBody(sigBytes) |
| if !ok || len(trailing) > 0 { |
| return nil, nil, false |
| } |
| return |
| } |