520 lines
14 KiB
Go
520 lines
14 KiB
Go
// 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"
|
|
CertAlgoED25519v01 = "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
|
|
}
|
|
|
|
// 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.
|
|
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
|
|
}
|
|
|
|
// 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")
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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 sets c.SignatureKey to the authority's public key and stores a
|
|
// Signature, by authority, in the certificate.
|
|
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,
|
|
KeyAlgoED25519: CertAlgoED25519v01,
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
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
|
|
}
|