sing-box/transport/cloudflaretls/delegated_credentials.go
2022-09-10 22:10:45 +08:00

551 lines
16 KiB
Go

// 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
}