|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521 |
- // Copyright 2018 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 tls
-
- // Delegated credentials for TLS
- // (https://tools.ietf.org/html/draft-ietf-tls-subcerts) is an IETF Internet
- // draft and proposed TLS extension. If the client supports this extension, then
- // the server may use a "delegated credential" as the signing key in the
- // handshake. A delegated credential is a short lived public/secret key pair
- // delegated to the server by an entity trusted by the client. This allows a
- // middlebox to terminate a TLS connection on behalf of the entity; for example,
- // this can be used to delegate TLS termination to a reverse proxy. Credentials
- // can't be revoked; in order to mitigate risk in case the middlebox is
- // compromised, the credential is only valid for a short time (days, hours, or
- // even minutes).
- //
- // BUG(cjpatton) Subcerts: Need to add support for PKCS1, PSS, and EdDSA.
- // Currently delegated credentials only support ECDSA. The delegator must also
- // use an ECDSA key.
-
- import (
- "bytes"
- "crypto"
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/rand"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/asn1"
- "encoding/binary"
- "errors"
- "fmt"
- "time"
- )
-
- const (
- dcMaxTTLSeconds = 60 * 60 * 24 * 7 // 7 days
- dcMaxTTL = time.Duration(dcMaxTTLSeconds * time.Second)
- dcMaxPublicKeyLen = 1 << 16 // Bytes
- dcMaxSignatureLen = 1 << 16 // Bytes
- )
-
- var errNoDelegationUsage = errors.New("certificate not authorized for delegation")
-
- // delegationUsageId is the DelegationUsage X.509 extension OID
- //
- // NOTE(cjpatton) This OID is a child of Cloudflare's IANA-assigned OID.
- var delegationUsageId = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 44363, 44}
-
- // CreateDelegationUsagePKIXExtension returns a pkix.Extension that every delegation
- // certificate must have.
- //
- // NOTE(cjpatton) Brendan McMillion suggests adding the delegationUsage
- // extension as a flag `PermitsDelegationUsage` for the `x509.Certificate`
- // structure. But we can't make this change unless tris includes crypto/x509,
- // too. Once we upstream this code, we'll want to do modify x509.Certficate and
- // do away with this function.
- func CreateDelegationUsagePKIXExtension() *pkix.Extension {
- return &pkix.Extension{
- Id: delegationUsageId,
- Critical: false,
- Value: nil,
- }
- }
-
- // canDelegate returns true if a certificate can be used for delegated
- // credentials.
- func canDelegate(cert *x509.Certificate) bool {
- // Check that the digitalSignature key usage is set.
- if (cert.KeyUsage & x509.KeyUsageDigitalSignature) == 0 {
- return false
- }
-
- // Check that the certificate has the DelegationUsage extension and that
- // it's non-critical (per the spec).
- for _, extension := range cert.Extensions {
- if extension.Id.Equal(delegationUsageId) {
- return true
- }
- }
- return false
- }
-
- // This structure stores the public components of a credential.
- type credential struct {
- validTime time.Duration
- publicKey crypto.PublicKey
- scheme SignatureScheme
- }
-
- // marshalSubjectPublicKeyInfo returns a DER encoded SubjectPublicKeyInfo structure
- // (as defined in the X.509 standard) for the credential.
- func (cred *credential) marshalSubjectPublicKeyInfo() ([]byte, error) {
- switch cred.scheme {
- case ECDSAWithP256AndSHA256,
- ECDSAWithP384AndSHA384,
- ECDSAWithP521AndSHA512:
- serializedPublicKey, err := x509.MarshalPKIXPublicKey(cred.publicKey)
- if err != nil {
- return nil, err
- }
- return serializedPublicKey, nil
-
- default:
- return nil, fmt.Errorf("unsupported signature scheme: 0x%04x", cred.scheme)
- }
- }
-
- // marshal encodes a credential as per the spec.
- func (cred *credential) marshal() ([]byte, error) {
- // Write the valid_time field.
- serialized := make([]byte, 6)
- binary.BigEndian.PutUint32(serialized, uint32(cred.validTime/time.Second))
-
- // Encode the public key and assert that the encoding is no longer than 2^16
- // bytes (per the spect).
- serializedPublicKey, err := cred.marshalSubjectPublicKeyInfo()
- if err != nil {
- return nil, err
- }
- if len(serializedPublicKey) > dcMaxPublicKeyLen {
- return nil, errors.New("public key is too long")
- }
-
- // Write the length of the public_key field.
- binary.BigEndian.PutUint16(serialized[4:], uint16(len(serializedPublicKey)))
-
- // Write the public key.
- return append(serialized, serializedPublicKey...), nil
- }
-
- // unmarshalCredential decodes a credential and returns it.
- func unmarshalCredential(serialized []byte) (*credential, error) {
- // Bytes 0-3 are the validity time field; bytes 4-6 are the length of the
- // serialized SubjectPublicKeyInfo.
- if len(serialized) < 6 {
- return nil, errors.New("credential is too short")
- }
-
- // Parse the validity time.
- validTime := time.Duration(binary.BigEndian.Uint32(serialized)) * time.Second
-
- // Parse the SubjectPublicKeyInfo.
- pk, scheme, err := unmarshalSubjectPublicKeyInfo(serialized[6:])
- if err != nil {
- return nil, err
- }
-
- return &credential{validTime, pk, scheme}, nil
- }
-
- // unmarshalSubjectPublicKeyInfo parses a DER encoded SubjectPublicKeyInfo
- // structure into a public key and its corresponding algorithm.
- func unmarshalSubjectPublicKeyInfo(serialized []byte) (crypto.PublicKey, SignatureScheme, error) {
- publicKey, err := x509.ParsePKIXPublicKey(serialized)
- if err != nil {
- return nil, 0, err
- }
-
- switch pk := publicKey.(type) {
- case *ecdsa.PublicKey:
- curveName := pk.Curve.Params().Name
- if curveName == "P-256" {
- return pk, ECDSAWithP256AndSHA256, nil
- } else if curveName == "P-384" {
- return pk, ECDSAWithP384AndSHA384, nil
- } else if curveName == "P-521" {
- return pk, ECDSAWithP521AndSHA512, nil
- } else {
- return nil, 0, fmt.Errorf("curve %s s not supported", curveName)
- }
-
- default:
- return nil, 0, fmt.Errorf("unsupported delgation key type: %T", pk)
- }
- }
-
- // getCredentialLen returns the number of bytes comprising the serialized
- // credential that starts at the beginning of the input slice. It returns an
- // error if the input is too short to contain a credential.
- func getCredentialLen(serialized []byte) (int, error) {
- if len(serialized) < 6 {
- return 0, errors.New("credential is too short")
- }
- // First 4 bytes is the validity time.
- serialized = serialized[4:]
-
- // The next 2 bytes are the length of the serialized public key.
- serializedPublicKeyLen := int(binary.BigEndian.Uint16(serialized))
- serialized = serialized[2:]
-
- if len(serialized) < serializedPublicKeyLen {
- return 0, errors.New("public key of credential is too short")
- }
-
- return 6 + serializedPublicKeyLen, nil
- }
-
- // DelegatedCredential stores a credential and its delegation.
- type DelegatedCredential struct {
- // The serialized form of the credential.
- Raw []byte
-
- // The amount of time for which the credential is valid. Specifically, the
- // the credential expires `ValidTime` seconds after the `notBefore` of the
- // delegation certificate. The delegator shall not issue delegated
- // credentials that are valid for more than 7 days from the current time.
- //
- // When this data structure is serialized, this value is converted to a
- // uint32 representing the duration in seconds.
- ValidTime time.Duration
-
- // The credential public key.
- PublicKey crypto.PublicKey
-
- // The signature scheme associated with the credential public key.
- publicKeyScheme SignatureScheme
-
- // The signature scheme used to sign the credential.
- Scheme SignatureScheme
-
- // The credential's delegation.
- Signature []byte
- }
-
- // NewDelegatedCredential creates a new delegated credential using `cert` for
- // delegation. It generates a public/private key pair for the provided signature
- // algorithm (`scheme`), validity interval (defined by `cert.Leaf.notBefore` and
- // `validTime`), and TLS version (`vers`), and signs it using `cert.PrivateKey`.
- func NewDelegatedCredential(cert *Certificate, scheme SignatureScheme, validTime time.Duration, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) {
- // The granularity of DC validity is seconds.
- validTime = validTime.Round(time.Second)
-
- // Parse the leaf certificate if needed.
- var err error
- if cert.Leaf == nil {
- if len(cert.Certificate[0]) == 0 {
- return nil, nil, errors.New("missing leaf certificate")
- }
- cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
- if err != nil {
- return nil, nil, err
- }
- }
-
- // Check that the leaf certificate can be used for delegation.
- if !canDelegate(cert.Leaf) {
- return nil, nil, errNoDelegationUsage
- }
-
- // Extract the delegator signature scheme from the certificate.
- var delegatorScheme SignatureScheme
- switch sk := cert.PrivateKey.(type) {
- case *ecdsa.PrivateKey:
- // Set scheme.
- pk := sk.Public().(*ecdsa.PublicKey)
- curveName := pk.Curve.Params().Name
- certAlg := cert.Leaf.SignatureAlgorithm
- if certAlg == x509.ECDSAWithSHA256 && curveName == "P-256" {
- delegatorScheme = ECDSAWithP256AndSHA256
- } else if certAlg == x509.ECDSAWithSHA384 && curveName == "P-384" {
- delegatorScheme = ECDSAWithP384AndSHA384
- } else if certAlg == x509.ECDSAWithSHA512 && curveName == "P-521" {
- delegatorScheme = ECDSAWithP521AndSHA512
- } else {
- return nil, nil, fmt.Errorf(
- "using curve %s for %s is not supported",
- curveName, cert.Leaf.SignatureAlgorithm)
- }
-
- default:
- return nil, nil, fmt.Errorf("unsupported delgation key type: %T", sk)
- }
-
- // Generate a new key pair.
- var sk crypto.PrivateKey
- var pk crypto.PublicKey
- switch scheme {
- case ECDSAWithP256AndSHA256,
- ECDSAWithP384AndSHA384,
- ECDSAWithP521AndSHA512:
- sk, err = ecdsa.GenerateKey(getCurve(scheme), rand.Reader)
- if err != nil {
- return nil, nil, err
- }
- pk = sk.(*ecdsa.PrivateKey).Public()
-
- default:
- return nil, nil, fmt.Errorf("unsupported signature scheme: 0x%04x", scheme)
- }
-
- // Prepare the credential for digital signing.
- hash := getHash(delegatorScheme)
- cred := &credential{validTime, pk, scheme}
- in, err := prepareDelegation(hash, cred, cert.Leaf.Raw, delegatorScheme, vers)
- if err != nil {
- return nil, nil, err
- }
-
- // Sign the credential.
- var sig []byte
- switch sk := cert.PrivateKey.(type) {
- case *ecdsa.PrivateKey:
- opts := crypto.SignerOpts(hash)
- sig, err = sk.Sign(rand.Reader, in, opts)
- if err != nil {
- return nil, nil, err
- }
- default:
- return nil, nil, fmt.Errorf("unsupported delgation key type: %T", sk)
- }
-
- return &DelegatedCredential{
- ValidTime: validTime,
- PublicKey: pk,
- publicKeyScheme: scheme,
- Scheme: delegatorScheme,
- Signature: sig,
- }, sk, nil
- }
-
- // IsExpired returns true if the credential has expired. The end of the validity
- // interval is defined as the delegator certificate's notBefore field (`start`)
- // plus ValidTime seconds. This function simply checks that the current time
- // (`now`) is before the end of the valdity interval.
- func (dc *DelegatedCredential) IsExpired(start, now time.Time) bool {
- end := start.Add(dc.ValidTime)
- return !now.Before(end)
- }
-
- // InvalidTTL returns true if the credential's validity period is longer than the
- // maximum permitted. This is defined by the certificate's notBefore field
- // (`start`) plus the ValidTime, minus the current time (`now`).
- func (dc *DelegatedCredential) InvalidTTL(start, now time.Time) bool {
- return dc.ValidTime > (now.Sub(start) + dcMaxTTL).Round(time.Second)
- }
-
- // Validate checks that that the signature is valid, that the credential hasn't
- // expired, and that the TTL is valid. It also checks that certificate can be
- // used for delegation.
- func (dc *DelegatedCredential) Validate(cert *x509.Certificate, vers uint16, now time.Time) (bool, error) {
- // Check that the cert can delegate.
- if !canDelegate(cert) {
- return false, errNoDelegationUsage
- }
-
- if dc.IsExpired(cert.NotBefore, now) {
- return false, errors.New("credential has expired")
- }
-
- if dc.InvalidTTL(cert.NotBefore, now) {
- return false, errors.New("credential TTL is invalid")
- }
-
- // Prepare the credential for verification.
- hash := getHash(dc.Scheme)
- cred := &credential{dc.ValidTime, dc.PublicKey, dc.publicKeyScheme}
- in, err := prepareDelegation(hash, cred, cert.Raw, dc.Scheme, vers)
- if err != nil {
- return false, err
- }
-
- // TODO(any) This code overlaps signficantly with verifyHandshakeSignature()
- // in ../auth.go. This should be refactored.
- switch dc.Scheme {
- case ECDSAWithP256AndSHA256,
- ECDSAWithP384AndSHA384,
- ECDSAWithP521AndSHA512:
- pk, ok := cert.PublicKey.(*ecdsa.PublicKey)
- if !ok {
- return false, errors.New("expected ECDSA public key")
- }
- sig := new(ecdsaSignature)
- if _, err = asn1.Unmarshal(dc.Signature, sig); err != nil {
- return false, err
- }
- return ecdsa.Verify(pk, in, sig.R, sig.S), nil
-
- default:
- return false, fmt.Errorf(
- "unsupported signature scheme: 0x%04x", dc.Scheme)
- }
- }
-
- // Marshal encodes a DelegatedCredential structure per the spec. It also sets
- // dc.Raw to the output as a side effect.
- func (dc *DelegatedCredential) Marshal() ([]byte, error) {
- // The credential.
- cred := &credential{dc.ValidTime, dc.PublicKey, dc.publicKeyScheme}
- serialized, err := cred.marshal()
- if err != nil {
- return nil, err
- }
-
- // The scheme.
- serializedScheme := make([]byte, 2)
- binary.BigEndian.PutUint16(serializedScheme, uint16(dc.Scheme))
- serialized = append(serialized, serializedScheme...)
-
- // The signature.
- if len(dc.Signature) > dcMaxSignatureLen {
- return nil, errors.New("signature is too long")
- }
- serializedSignature := make([]byte, 2)
- binary.BigEndian.PutUint16(serializedSignature, uint16(len(dc.Signature)))
- serializedSignature = append(serializedSignature, dc.Signature...)
- serialized = append(serialized, serializedSignature...)
-
- dc.Raw = serialized
- return serialized, nil
- }
-
- // UnmarshalDelegatedCredential decodes a DelegatedCredential structure.
- func UnmarshalDelegatedCredential(serialized []byte) (*DelegatedCredential, error) {
- // Get the length of the serialized credential that begins at the start of
- // the input slice.
- serializedCredentialLen, err := getCredentialLen(serialized)
- if err != nil {
- return nil, err
- }
-
- // Parse the credential.
- cred, err := unmarshalCredential(serialized[:serializedCredentialLen])
- if err != nil {
- return nil, err
- }
-
- // Parse the signature scheme.
- serialized = serialized[serializedCredentialLen:]
- if len(serialized) < 4 {
- return nil, errors.New("delegated credential is too short")
- }
- scheme := SignatureScheme(binary.BigEndian.Uint16(serialized))
-
- // Parse the signature length.
- serialized = serialized[2:]
- serializedSignatureLen := binary.BigEndian.Uint16(serialized)
-
- // Prase the signature.
- serialized = serialized[2:]
- if len(serialized) < int(serializedSignatureLen) {
- return nil, errors.New("signature of delegated credential is too short")
- }
- sig := serialized[:serializedSignatureLen]
-
- return &DelegatedCredential{
- ValidTime: cred.validTime,
- PublicKey: cred.publicKey,
- publicKeyScheme: cred.scheme,
- Scheme: scheme,
- Signature: sig,
- }, nil
- }
-
- // getCurve maps the SignatureScheme to its corresponding elliptic.Curve.
- func getCurve(scheme SignatureScheme) elliptic.Curve {
- switch scheme {
- case ECDSAWithP256AndSHA256:
- return elliptic.P256()
- case ECDSAWithP384AndSHA384:
- return elliptic.P384()
- case ECDSAWithP521AndSHA512:
- return elliptic.P521()
- default:
- return nil
- }
- }
-
- // getHash maps the SignatureScheme to its corresponding hash function.
- //
- // TODO(any) This function overlaps with hashForSignatureScheme in 13.go.
- func getHash(scheme SignatureScheme) crypto.Hash {
- switch scheme {
- case ECDSAWithP256AndSHA256:
- return crypto.SHA256
- case ECDSAWithP384AndSHA384:
- return crypto.SHA384
- case ECDSAWithP521AndSHA512:
- return crypto.SHA512
- default:
- return 0 // Unknown hash function
- }
- }
-
- // prepareDelegation returns a hash of the message that the delegator is to
- // sign. The inputs are the credential (cred), the DER-encoded delegator
- // certificate (`delegatorCert`), the signature scheme of the delegator
- // (`delegatorScheme`), and the protocol version (`vers`) in which the credential
- // is to be used.
- func prepareDelegation(hash crypto.Hash, cred *credential, delegatorCert []byte, delegatorScheme SignatureScheme, vers uint16) ([]byte, error) {
- h := hash.New()
-
- // The header.
- h.Write(bytes.Repeat([]byte{0x20}, 64))
- h.Write([]byte("TLS, server delegated credentials"))
- h.Write([]byte{0x00})
-
- // The protocol version.
- var serializedVers [2]byte
- binary.BigEndian.PutUint16(serializedVers[:], uint16(vers))
- h.Write(serializedVers[:])
-
- // The delegation certificate.
- h.Write(delegatorCert)
-
- // The delegator signature scheme.
- var serializedScheme [2]byte
- binary.BigEndian.PutUint16(serializedScheme[:], uint16(delegatorScheme))
- h.Write(serializedScheme[:])
-
- // The credential.
- serializedCred, err := cred.marshal()
- if err != nil {
- return nil, err
- }
- h.Write(serializedCred)
-
- return h.Sum(nil), nil
- }
|