| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- // Copyright 2020-2021 Cloudflare, Inc. 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 or server supports this
- // extension, then the server or client 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 peer by an entity trusted by the
- // corresponding peer. This allows a reverse proxy to terminate a TLS connection
- // on behalf of the entity. Credentials can't be revoked; in order to
- // mitigate risk in case the reverse proxy is compromised, the credential is only
- // valid for a short time (days, hours, or even minutes).
- import (
- "bytes"
- "crypto"
- "crypto/ecdsa"
- "crypto/ed25519"
- "crypto/elliptic"
- "crypto/rand"
- "crypto/rsa"
- "crypto/x509"
- "encoding/binary"
- "errors"
- "fmt"
- "io"
- "time"
- "golang.org/x/crypto/cryptobyte"
- )
- const (
- // In the absence of an application profile standard specifying otherwise,
- // the maximum validity period is set to 7 days.
- dcMaxTTLSeconds = 60 * 60 * 24 * 7
- dcMaxTTL = time.Duration(dcMaxTTLSeconds * time.Second)
- dcMaxPubLen = (1 << 24) - 1 // Bytes
- dcMaxSignatureLen = (1 << 16) - 1 // Bytes
- )
- const (
- undefinedSignatureScheme SignatureScheme = 0x0000
- )
- var extensionDelegatedCredential = []int{1, 3, 6, 1, 4, 1, 44363, 44}
- // isValidForDelegation returns true if a certificate can be used for Delegated
- // Credentials.
- func isValidForDelegation(cert *x509.Certificate) bool {
- // Check that the digitalSignature key usage is set.
- // The certificate must contains the digitalSignature KeyUsage.
- if (cert.KeyUsage & x509.KeyUsageDigitalSignature) == 0 {
- return false
- }
- // Check that the certificate has the DelegationUsage extension and that
- // it's marked as non-critical (See Section 4.2 of RFC5280).
- for _, extension := range cert.Extensions {
- if extension.Id.Equal(extensionDelegatedCredential) {
- if extension.Critical {
- return false
- }
- return true
- }
- }
- return false
- }
- // 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 dc.cred.validTime seconds. This function simply checks that the current time
- // ('now') is before the end of the validity interval.
- func (dc *DelegatedCredential) isExpired(start, now time.Time) bool {
- end := start.Add(dc.cred.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 dc.validTime, minus the current time ('now').
- func (dc *DelegatedCredential) invalidTTL(start, now time.Time) bool {
- return dc.cred.validTime > (now.Sub(start) + dcMaxTTL).Round(time.Second)
- }
- // credential stores the public components of a Delegated Credential.
- type credential struct {
- // 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 signature scheme associated with the credential public key.
- // This is expected to be the same as the CertificateVerify.algorithm
- // sent by the client or server.
- expCertVerfAlgo SignatureScheme
- // The credential's public key.
- publicKey crypto.PublicKey
- }
- // DelegatedCredential stores a Delegated Credential with the credential and its
- // signature.
- type DelegatedCredential struct {
- // The serialized form of the Delegated Credential.
- raw []byte
- // Cred stores the public components of a Delegated Credential.
- cred *credential
- // The signature scheme used to sign the Delegated Credential.
- algorithm SignatureScheme
- // The Credential's delegation: a signature that binds the credential to
- // the end-entity certificate's public key.
- signature []byte
- }
- // marshalPublicKeyInfo returns a DER encoded PublicKeyInfo
- // from a Delegated Credential (as defined in the X.509 standard).
- // The following key types are currently supported: *ecdsa.PublicKey
- // and ed25519.PublicKey. Unsupported key types result in an error.
- // rsa.PublicKey is not supported as defined by the draft.
- func (cred *credential) marshalPublicKeyInfo() ([]byte, error) {
- switch cred.expCertVerfAlgo {
- case ECDSAWithP256AndSHA256,
- ECDSAWithP384AndSHA384,
- ECDSAWithP521AndSHA512,
- Ed25519:
- rawPub, err := x509.MarshalPKIXPublicKey(cred.publicKey)
- if err != nil {
- return nil, err
- }
- return rawPub, nil
- default:
- return nil, fmt.Errorf("tls: unsupported signature scheme: 0x%04x", cred.expCertVerfAlgo)
- }
- }
- // marshal encodes the credential struct of the Delegated Credential.
- func (cred *credential) marshal() ([]byte, error) {
- var b cryptobyte.Builder
- b.AddUint32(uint32(cred.validTime / time.Second))
- b.AddUint16(uint16(cred.expCertVerfAlgo))
- // Encode the public key
- rawPub, err := cred.marshalPublicKeyInfo()
- if err != nil {
- return nil, err
- }
- // Assert that the public key encoding is no longer than 2^24-1 bytes.
- if len(rawPub) > dcMaxPubLen {
- return nil, errors.New("tls: public key length exceeds 2^24-1 limit")
- }
- b.AddUint24(uint32(len(rawPub)))
- b.AddBytes(rawPub)
- raw := b.BytesOrPanic()
- return raw, nil
- }
- // unmarshalCredential decodes serialized bytes and returns a credential, if possible.
- func unmarshalCredential(raw []byte) (*credential, error) {
- if len(raw) < 10 {
- return nil, errors.New("tls: Delegated Credential is not valid: invalid length")
- }
- s := cryptobyte.String(raw)
- var t uint32
- if !s.ReadUint32(&t) {
- return nil, errors.New("tls: Delegated Credential is not valid")
- }
- validTime := time.Duration(t) * time.Second
- var pubAlgo uint16
- if !s.ReadUint16(&pubAlgo) {
- return nil, errors.New("tls: Delegated Credential is not valid")
- }
- algo := SignatureScheme(pubAlgo)
- var pubLen uint32
- s.ReadUint24(&pubLen)
- pubKey, err := x509.ParsePKIXPublicKey(s)
- if err != nil {
- return nil, err
- }
- return &credential{validTime, algo, pubKey}, nil
- }
- // getCredentialLen returns the number of bytes comprising the serialized
- // credential struct inside the Delegated Credential.
- func getCredentialLen(raw []byte) (int, error) {
- if len(raw) < 10 {
- return 0, errors.New("tls: Delegated Credential is not valid")
- }
- var read []byte
- s := cryptobyte.String(raw)
- s.ReadBytes(&read, 6)
- var pubLen uint32
- s.ReadUint24(&pubLen)
- if !(pubLen > 0) {
- return 0, errors.New("tls: Delegated Credential is not valid")
- }
- raw = raw[6:]
- if len(raw) < int(pubLen) {
- return 0, errors.New("tls: Delegated Credential is not valid")
- }
- return 9 + int(pubLen), nil
- }
- // getHash maps the SignatureScheme to its corresponding hash function.
- func getHash(scheme SignatureScheme) crypto.Hash {
- switch scheme {
- case ECDSAWithP256AndSHA256:
- return crypto.SHA256
- case ECDSAWithP384AndSHA384:
- return crypto.SHA384
- case ECDSAWithP521AndSHA512:
- return crypto.SHA512
- case Ed25519:
- return directSigning
- case PKCS1WithSHA256, PSSWithSHA256:
- return crypto.SHA256
- case PSSWithSHA384:
- return crypto.SHA384
- case PSSWithSHA512:
- return crypto.SHA512
- default:
- return 0 // Unknown hash function
- }
- }
- // getECDSACurve maps the SignatureScheme to its corresponding ecdsa elliptic.Curve.
- func getECDSACurve(scheme SignatureScheme) elliptic.Curve {
- switch scheme {
- case ECDSAWithP256AndSHA256:
- return elliptic.P256()
- case ECDSAWithP384AndSHA384:
- return elliptic.P384()
- case ECDSAWithP521AndSHA512:
- return elliptic.P521()
- default:
- return nil
- }
- }
- // prepareDelegationSignatureInput returns the message that the delegator is going to sign.
- func prepareDelegationSignatureInput(hash crypto.Hash, cred *credential, dCert []byte, algo SignatureScheme, isClient bool) ([]byte, error) {
- header := make([]byte, 64)
- for i := range header {
- header[i] = 0x20
- }
- var context string
- if !isClient {
- context = "TLS, server delegated credentials\x00"
- } else {
- context = "TLS, client delegated credentials\x00"
- }
- rawCred, err := cred.marshal()
- if err != nil {
- return nil, err
- }
- var rawAlgo [2]byte
- binary.BigEndian.PutUint16(rawAlgo[:], uint16(algo))
- if hash == directSigning {
- b := &bytes.Buffer{}
- b.Write(header)
- io.WriteString(b, context)
- b.Write(dCert)
- b.Write(rawCred)
- b.Write(rawAlgo[:])
- return b.Bytes(), nil
- }
- h := hash.New()
- h.Write(header)
- io.WriteString(h, context)
- h.Write(dCert)
- h.Write(rawCred)
- h.Write(rawAlgo[:])
- return h.Sum(nil), nil
- }
- // Extract the algorithm used to sign the Delegated Credential from the
- // end-entity (leaf) certificate.
- func getSignatureAlgorithm(cert *Certificate) (SignatureScheme, error) {
- switch sk := cert.PrivateKey.(type) {
- case *ecdsa.PrivateKey:
- pk := sk.Public().(*ecdsa.PublicKey)
- curveName := pk.Curve.Params().Name
- certAlg := cert.Leaf.PublicKeyAlgorithm
- if certAlg == x509.ECDSA && curveName == "P-256" {
- return ECDSAWithP256AndSHA256, nil
- } else if certAlg == x509.ECDSA && curveName == "P-384" {
- return ECDSAWithP384AndSHA384, nil
- } else if certAlg == x509.ECDSA && curveName == "P-521" {
- return ECDSAWithP521AndSHA512, nil
- } else {
- return undefinedSignatureScheme, fmt.Errorf("using curve %s for %s is not supported", curveName, cert.Leaf.SignatureAlgorithm)
- }
- case ed25519.PrivateKey:
- return Ed25519, nil
- case *rsa.PrivateKey:
- // If the certificate has the RSAEncryption OID there are a number of valid signature schemes that may sign the DC.
- // In the absence of better information, we make a reasonable choice.
- return PSSWithSHA256, nil
- default:
- return undefinedSignatureScheme, fmt.Errorf("tls: unsupported algorithm for signing Delegated Credential")
- }
- }
- // NewDelegatedCredential creates a new Delegated Credential using 'cert' for
- // delegation, depending if the caller is the client or the server (defined by
- // 'isClient'). It generates a public/private key pair for the provided signature
- // algorithm ('pubAlgo') and it defines a validity interval (defined
- // by 'cert.Leaf.notBefore' and 'validTime'). It signs the Delegated Credential
- // using 'cert.PrivateKey'.
- func NewDelegatedCredential(cert *Certificate, pubAlgo SignatureScheme, validTime time.Duration, isClient bool) (*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("tls: missing leaf certificate for Delegated Credential")
- }
- 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 !isValidForDelegation(cert.Leaf) {
- return nil, nil, errors.New("tls: certificate not authorized for delegation")
- }
- sigAlgo, err := getSignatureAlgorithm(cert)
- if err != nil {
- return nil, nil, err
- }
- // Generate the Delegated Credential key pair based on the provided scheme
- var privK crypto.PrivateKey
- var pubK crypto.PublicKey
- switch pubAlgo {
- case ECDSAWithP256AndSHA256,
- ECDSAWithP384AndSHA384,
- ECDSAWithP521AndSHA512:
- privK, err = ecdsa.GenerateKey(getECDSACurve(pubAlgo), rand.Reader)
- if err != nil {
- return nil, nil, err
- }
- pubK = privK.(*ecdsa.PrivateKey).Public()
- case Ed25519:
- pubK, privK, err = ed25519.GenerateKey(rand.Reader)
- if err != nil {
- return nil, nil, err
- }
- default:
- return nil, nil, fmt.Errorf("tls: unsupported algorithm for Delegated Credential: %s", pubAlgo)
- }
- // Prepare the credential for signing
- hash := getHash(sigAlgo)
- credential := &credential{validTime, pubAlgo, pubK}
- values, err := prepareDelegationSignatureInput(hash, credential, cert.Leaf.Raw, sigAlgo, isClient)
- if err != nil {
- return nil, nil, err
- }
- var sig []byte
- switch sk := cert.PrivateKey.(type) {
- case *ecdsa.PrivateKey:
- opts := crypto.SignerOpts(hash)
- sig, err = sk.Sign(rand.Reader, values, opts)
- if err != nil {
- return nil, nil, err
- }
- case ed25519.PrivateKey:
- opts := crypto.SignerOpts(hash)
- sig, err = sk.Sign(rand.Reader, values, opts)
- if err != nil {
- return nil, nil, err
- }
- case *rsa.PrivateKey:
- opts := &rsa.PSSOptions{
- SaltLength: rsa.PSSSaltLengthEqualsHash,
- Hash: hash,
- }
- sig, err = rsa.SignPSS(rand.Reader, sk, hash, values, opts)
- if err != nil {
- return nil, nil, err
- }
- default:
- return nil, nil, fmt.Errorf("tls: unsupported key type for Delegated Credential")
- }
- if len(sig) > dcMaxSignatureLen {
- return nil, nil, errors.New("tls: unable to create a Delegated Credential")
- }
- return &DelegatedCredential{
- cred: credential,
- algorithm: sigAlgo,
- signature: sig,
- }, privK, nil
- }
- // Validate validates the Delegated Credential by checking that the signature is
- // valid, that it 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, isClient bool, now time.Time, certVerifyMsg *certificateVerifyMsg) bool {
- if dc.isExpired(cert.NotBefore, now) {
- return false
- }
- if dc.invalidTTL(cert.NotBefore, now) {
- return false
- }
- if dc.cred.expCertVerfAlgo != certVerifyMsg.signatureAlgorithm {
- return false
- }
- if !isValidForDelegation(cert) {
- return false
- }
- hash := getHash(dc.algorithm)
- in, err := prepareDelegationSignatureInput(hash, dc.cred, cert.Raw, dc.algorithm, isClient)
- if err != nil {
- return false
- }
- switch dc.algorithm {
- case ECDSAWithP256AndSHA256,
- ECDSAWithP384AndSHA384,
- ECDSAWithP521AndSHA512:
- pk, ok := cert.PublicKey.(*ecdsa.PublicKey)
- if !ok {
- return false
- }
- return ecdsa.VerifyASN1(pk, in, dc.signature)
- case Ed25519:
- pk, ok := cert.PublicKey.(ed25519.PublicKey)
- if !ok {
- return false
- }
- return ed25519.Verify(pk, in, dc.signature)
- case PSSWithSHA256,
- PSSWithSHA384,
- PSSWithSHA512:
- pk, ok := cert.PublicKey.(*rsa.PublicKey)
- if !ok {
- return false
- }
- hash := getHash(dc.algorithm)
- return rsa.VerifyPSS(pk, hash, in, dc.signature, nil) == nil
- default:
- return false
- }
- }
- // Marshal encodes a DelegatedCredential structure. It also sets dc.Raw to that
- // encoding.
- func (dc *DelegatedCredential) Marshal() ([]byte, error) {
- if len(dc.signature) > dcMaxSignatureLen {
- return nil, errors.New("tls: delegated credential is not valid")
- }
- if len(dc.signature) == 0 {
- return nil, errors.New("tls: delegated credential has no signature")
- }
- raw, err := dc.cred.marshal()
- if err != nil {
- return nil, err
- }
- var b cryptobyte.Builder
- b.AddBytes(raw)
- b.AddUint16(uint16(dc.algorithm))
- b.AddUint16(uint16(len(dc.signature)))
- b.AddBytes(dc.signature)
- dc.raw = b.BytesOrPanic()
- return dc.raw, nil
- }
- // UnmarshalDelegatedCredential decodes a DelegatedCredential structure.
- func UnmarshalDelegatedCredential(raw []byte) (*DelegatedCredential, error) {
- rawCredentialLen, err := getCredentialLen(raw)
- if err != nil {
- return nil, err
- }
- credential, err := unmarshalCredential(raw[:rawCredentialLen])
- if err != nil {
- return nil, err
- }
- raw = raw[rawCredentialLen:]
- if len(raw) < 4 {
- return nil, errors.New("tls: Delegated Credential is not valid")
- }
- s := cryptobyte.String(raw)
- var algo uint16
- if !s.ReadUint16(&algo) {
- return nil, errors.New("tls: Delegated Credential is not valid")
- }
- var rawSignatureLen uint16
- if !s.ReadUint16(&rawSignatureLen) {
- return nil, errors.New("tls: Delegated Credential is not valid")
- }
- var sig []byte
- if !s.ReadBytes(&sig, int(rawSignatureLen)) {
- return nil, errors.New("tls: Delegated Credential is not valid")
- }
- return &DelegatedCredential{
- cred: credential,
- algorithm: SignatureScheme(algo),
- signature: sig,
- }, nil
- }
|