Remove delegated credential minting from the API
What's left are the minimal API changes required to use the delegated credential extension in the TLS handshake.
This commit is contained in:
rodzic
1ea9624098
commit
c5280001a4
4
13.go
4
13.go
@ -408,7 +408,7 @@ func (hs *serverHandshakeState) sendCertificate13() error {
|
||||
// server is using the delegated credential extension. In TLS 1.3, the DC is
|
||||
// added as an extension to the end-entity certificate, i.e., the last
|
||||
// CertificateEntry of Certificate.certficate_list (see
|
||||
// https://tools.ietf.org/html/draft-ietf-tls-subcerts).
|
||||
// https://tools.ietf.org/html/draft-ietf-tls-subcerts-01).
|
||||
if len(certEntries) > 0 && hs.clientHello.delegatedCredential && hs.delegatedCredential != nil {
|
||||
certEntries[0].delegatedCredential = hs.delegatedCredential
|
||||
}
|
||||
@ -1069,7 +1069,7 @@ func (hs *clientHandshakeState) doTLS13Handshake() error {
|
||||
// then the CertificateVerify signature will have been produced with the
|
||||
// DelegatedCredential's private key.
|
||||
if hs.c.verifiedDc != nil {
|
||||
pk = hs.c.verifiedDc.PublicKey
|
||||
pk = hs.c.verifiedDc.cred.publicKey
|
||||
}
|
||||
|
||||
// Receive CertificateVerify message.
|
||||
|
17
common.go
17
common.go
@ -219,6 +219,7 @@ type ConnectionState struct {
|
||||
VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates
|
||||
SignedCertificateTimestamps [][]byte // SCTs from the server, if any
|
||||
OCSPResponse []byte // stapled OCSP response from server, if any
|
||||
DelegatedCredential []byte // Delegated credential sent by the server, if any
|
||||
|
||||
// TLSUnique contains the "tls-unique" channel binding value (see RFC
|
||||
// 5929, section 3). For resumed sessions this value will be nil
|
||||
@ -619,16 +620,20 @@ type Config struct {
|
||||
//
|
||||
// This value has no meaning for the server.
|
||||
//
|
||||
// See https://tools.ietf.org/html/draft-ietf-tls-subcerts.
|
||||
// See https://tools.ietf.org/html/draft-ietf-tls-subcerts-01.
|
||||
AcceptDelegatedCredential bool
|
||||
|
||||
// GetDelegatedCredential returns a DelegatedCredential for use with the
|
||||
// delegated credential extension based on the ClientHello and TLS version
|
||||
// selected for the session. If this is nil, then the server will not offer
|
||||
// a DelegatedCredential.
|
||||
// GetDelegatedCredential returns a DC and its private key for use in the
|
||||
// delegated credential extension. The inputs to the callback are some
|
||||
// information parsed from the ClientHello, as well as the protocol version
|
||||
// selected by the server. This is necessary because the DC is bound to
|
||||
// protocol version in which it's used. The return value is the raw DC
|
||||
// encoded in the wire format specified in
|
||||
// https://tools.ietf.org/html/draft-ietf-tls-subcerts-01. If the return
|
||||
// value is nil, then the server will not offer negotiate the extension.
|
||||
//
|
||||
// This value has no meaning for the client.
|
||||
GetDelegatedCredential func(*ClientHelloInfo, uint16) (*DelegatedCredential, crypto.PrivateKey, error)
|
||||
GetDelegatedCredential func(*ClientHelloInfo, uint16) ([]byte, crypto.PrivateKey, error)
|
||||
|
||||
serverInitOnce sync.Once // guards calling (*Config).serverInit
|
||||
|
||||
|
5
conn.go
5
conn.go
@ -58,7 +58,7 @@ type Conn struct {
|
||||
verifiedChains [][]*x509.Certificate
|
||||
// verifiedDc is set by a client who negotiates the use of a valid delegated
|
||||
// credential.
|
||||
verifiedDc *DelegatedCredential
|
||||
verifiedDc *delegatedCredential
|
||||
// serverName contains the server name indicated by the client, if any.
|
||||
serverName string
|
||||
// secureRenegotiation is true if the server echoed the secure
|
||||
@ -1666,6 +1666,9 @@ func (c *Conn) ConnectionState() ConnectionState {
|
||||
state.VerifiedChains = c.verifiedChains
|
||||
state.SignedCertificateTimestamps = c.scts
|
||||
state.OCSPResponse = c.ocspResponse
|
||||
if c.verifiedDc != nil {
|
||||
state.DelegatedCredential = c.verifiedDc.raw
|
||||
}
|
||||
state.HandshakeConfirmed = atomic.LoadInt32(&c.handshakeConfirmed) == 1
|
||||
if !state.HandshakeConfirmed {
|
||||
state.Unique0RTTToken = c.binder
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
@ -35,7 +34,6 @@ var (
|
||||
isCA = flag.Bool("ca", false, "whether this cert should be its own Certificate Authority")
|
||||
rsaBits = flag.Int("rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set")
|
||||
ecdsaCurve = flag.String("ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256 (recommended), P384, P521")
|
||||
isDC = flag.Bool("dc", false, "whether this cert can be used with delegated credentials")
|
||||
)
|
||||
|
||||
func publicKey(priv interface{}) interface{} {
|
||||
@ -139,11 +137,6 @@ func main() {
|
||||
template.KeyUsage |= x509.KeyUsageCertSign
|
||||
}
|
||||
|
||||
if *isDC {
|
||||
template.ExtraExtensions = append(
|
||||
template.ExtraExtensions, *tls.CreateDelegationUsagePKIXExtension())
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create certificate: %s", err)
|
||||
|
@ -413,15 +413,15 @@ func (hs *clientHandshakeState) processCertsFromServer(certificates [][]byte) er
|
||||
return nil
|
||||
}
|
||||
|
||||
// processDelegatedCredentialFromServer unmarshals the DelegatedCredential
|
||||
// processDelegatedCredentialFromServer unmarshals the delegated credential
|
||||
// offered by the server (if present) and validates it using the peer
|
||||
// certificate.
|
||||
func (hs *clientHandshakeState) processDelegatedCredentialFromServer(dc []byte) error {
|
||||
func (hs *clientHandshakeState) processDelegatedCredentialFromServer(serialized []byte) error {
|
||||
c := hs.c
|
||||
|
||||
var cred *DelegatedCredential
|
||||
var dc *delegatedCredential
|
||||
var err error
|
||||
if dc != nil {
|
||||
if serialized != nil {
|
||||
|
||||
// Assert that the DC extension was indicated by the client.
|
||||
if !hs.hello.delegatedCredential {
|
||||
@ -436,15 +436,15 @@ func (hs *clientHandshakeState) processDelegatedCredentialFromServer(dc []byte)
|
||||
return errors.New("tls: ServerHello with delegated credential extension in TLS != 1.2")
|
||||
}
|
||||
|
||||
cred, err = UnmarshalDelegatedCredential(dc)
|
||||
dc, err = unmarshalDelegatedCredential(serialized)
|
||||
if err != nil {
|
||||
c.sendAlert(alertDecodeError)
|
||||
return fmt.Errorf("tls: delegated credential: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if cred != nil && !c.config.InsecureSkipVerify {
|
||||
if v, err := cred.Validate(c.peerCertificates[0], hs.c.vers, c.config.time()); err != nil {
|
||||
if dc != nil && !c.config.InsecureSkipVerify {
|
||||
if v, err := dc.validate(c.peerCertificates[0], hs.c.vers, c.config.time()); err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return fmt.Errorf("delegated credential: %s", err)
|
||||
} else if !v {
|
||||
@ -453,7 +453,7 @@ func (hs *clientHandshakeState) processDelegatedCredentialFromServer(dc []byte)
|
||||
}
|
||||
}
|
||||
|
||||
c.verifiedDc = cred
|
||||
c.verifiedDc = dc
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -533,9 +533,10 @@ func (hs *clientHandshakeState) doFullHandshake() error {
|
||||
pk := c.peerCertificates[0].PublicKey
|
||||
|
||||
// If the delegated credential extension has successfully been negotiated,
|
||||
// then the ServerKeyExchange DelegatedCredential's private key.
|
||||
// then the ServerKeyExchange is signed with delegated credential private
|
||||
// key.
|
||||
if c.verifiedDc != nil {
|
||||
pk = c.verifiedDc.PublicKey
|
||||
pk = c.verifiedDc.cred.publicKey
|
||||
}
|
||||
|
||||
skx, ok := msg.(*serverKeyExchangeMsg)
|
||||
|
@ -759,7 +759,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) alert {
|
||||
// https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.2.8
|
||||
m.earlyData = true
|
||||
case extensionDelegatedCredential:
|
||||
// https://tools.ietf.org/html/draft-ietf-tls-subcerts
|
||||
// https://tools.ietf.org/html/draft-ietf-tls-subcerts-01
|
||||
m.delegatedCredential = true
|
||||
}
|
||||
data = data[length:]
|
||||
|
@ -329,14 +329,7 @@ Curves:
|
||||
// Set the handshake private key.
|
||||
if dc != nil {
|
||||
hs.privateKey = sk
|
||||
if dc.Raw == nil {
|
||||
dc.Raw, err = dc.Marshal()
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
hs.delegatedCredential = dc.Raw
|
||||
hs.delegatedCredential = dc
|
||||
|
||||
// For TLS 1.2, the DC is an extension to the ServerHello.
|
||||
if c.vers == VersionTLS12 {
|
||||
|
280
subcerts.go
280
subcerts.go
@ -5,29 +5,30 @@
|
||||
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).
|
||||
// (https://tools.ietf.org/html/draft-ietf-tls-subcerts-01) is an IETF Internet
|
||||
// draft and proposed TLS extension. This allows a backend server to delegate
|
||||
// TLS termination to a trusted frontend. If the client supports this extension,
|
||||
// then the frontend may use a "delegated credential" as the signing key in the
|
||||
// handshake. A delegated credential is a short lived key pair delegated to the
|
||||
// server by an entity trusted by the client. Once issued, credentials can't be
|
||||
// revoked; in order to mitigate risk in case the frontend 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.
|
||||
// This implements draft 01. This draft doesn't specify an object identifier for
|
||||
// the X.509 extension; we use one assigned by Cloudflare. In addition, IANA has
|
||||
// not assigned an extension ID for this extension; we picked up one that's not
|
||||
// yet taken.
|
||||
//
|
||||
// TODO(cjpatton) Only ECDSA is supported with delegated credentials for now;
|
||||
// we'd like to suppoort for EcDSA signatures once these have better support
|
||||
// upstream.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
@ -49,22 +50,6 @@ var errNoDelegationUsage = errors.New("certificate not authorized for delegation
|
||||
// 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 {
|
||||
@ -83,11 +68,41 @@ func canDelegate(cert *x509.Certificate) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// This structure stores the public components of a credential.
|
||||
// credential stores the public components of a credential.
|
||||
type credential struct {
|
||||
// The amount of time for which the credential is valid. Specifically, 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
|
||||
scheme SignatureScheme
|
||||
|
||||
// The signature scheme associated with the credential public key.
|
||||
//
|
||||
// NOTE(cjpatton) This is used for bookkeeping and is not actually part of
|
||||
// the credential structure specified in the standard.
|
||||
scheme SignatureScheme
|
||||
}
|
||||
|
||||
// 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 (cred *credential) isExpired(start, now time.Time) bool {
|
||||
end := start.Add(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 ValidTime, minus the current time (`now`).
|
||||
func (cred *credential) invalidTTL(start, now time.Time) bool {
|
||||
return cred.validTime > (now.Sub(start) + dcMaxTTL).Round(time.Second)
|
||||
}
|
||||
|
||||
// marshalSubjectPublicKeyInfo returns a DER encoded SubjectPublicKeyInfo structure
|
||||
@ -108,7 +123,8 @@ func (cred *credential) marshalSubjectPublicKeyInfo() ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// marshal encodes a credential as per the spec.
|
||||
// marshal encodes a credential in the wire format specified in
|
||||
// https://tools.ietf.org/html/draft-ietf-tls-subcerts-01.
|
||||
func (cred *credential) marshal() ([]byte, error) {
|
||||
// Write the valid_time field.
|
||||
serialized := make([]byte, 6)
|
||||
@ -198,173 +214,62 @@ func getCredentialLen(serialized []byte) (int, error) {
|
||||
return 6 + serializedPublicKeyLen, nil
|
||||
}
|
||||
|
||||
// DelegatedCredential stores a credential and its delegation.
|
||||
type DelegatedCredential struct {
|
||||
// The serialized form of the credential.
|
||||
Raw []byte
|
||||
// delegatedCredential stores a credential and its delegation.
|
||||
type delegatedCredential struct {
|
||||
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 credential, which contains a public and its validity time.
|
||||
cred *credential
|
||||
|
||||
// The signature scheme used to sign the credential.
|
||||
Scheme SignatureScheme
|
||||
scheme SignatureScheme
|
||||
|
||||
// The credential's delegation.
|
||||
Signature []byte
|
||||
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.
|
||||
// ensureCertificateHasLeaf parses the leaf certificate if needed.
|
||||
func ensureCertificateHasLeaf(cert *Certificate) error {
|
||||
var err error
|
||||
if cert.Leaf == nil {
|
||||
if len(cert.Certificate[0]) == 0 {
|
||||
return nil, nil, errors.New("missing leaf certificate")
|
||||
return errors.New("missing leaf certificate")
|
||||
}
|
||||
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return 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
|
||||
return 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
|
||||
// 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) {
|
||||
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) {
|
||||
if dc.cred.isExpired(cert.NotBefore, now) {
|
||||
return false, errors.New("credential has expired")
|
||||
}
|
||||
|
||||
if dc.InvalidTTL(cert.NotBefore, now) {
|
||||
if dc.cred.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)
|
||||
hash := getHash(dc.scheme)
|
||||
in, err := prepareDelegation(hash, dc.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 {
|
||||
switch dc.scheme {
|
||||
case ECDSAWithP256AndSHA256,
|
||||
ECDSAWithP384AndSHA384,
|
||||
ECDSAWithP521AndSHA512:
|
||||
@ -373,47 +278,19 @@ func (dc *DelegatedCredential) Validate(cert *x509.Certificate, vers uint16, now
|
||||
return false, errors.New("expected ECDSA public key")
|
||||
}
|
||||
sig := new(ecdsaSignature)
|
||||
if _, err = asn1.Unmarshal(dc.Signature, sig); err != nil {
|
||||
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)
|
||||
"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) {
|
||||
// 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)
|
||||
@ -445,12 +322,11 @@ func UnmarshalDelegatedCredential(serialized []byte) (*DelegatedCredential, erro
|
||||
}
|
||||
sig := serialized[:serializedSignatureLen]
|
||||
|
||||
return &DelegatedCredential{
|
||||
ValidTime: cred.validTime,
|
||||
PublicKey: cred.publicKey,
|
||||
publicKeyScheme: cred.scheme,
|
||||
Scheme: scheme,
|
||||
Signature: sig,
|
||||
return &delegatedCredential{
|
||||
raw: serialized,
|
||||
cred: cred,
|
||||
scheme: scheme,
|
||||
signature: sig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
126
subcerts_test.go
126
subcerts_test.go
@ -14,14 +14,10 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// These test keys were generated with the following program, available in the
|
||||
// crypto/tls directory:
|
||||
//
|
||||
// go run generate_cert.go -ecdsa-curve P256 -host 127.0.0.1 -dc
|
||||
//
|
||||
// To get a certificate without the DelegationUsage extension, remove the `-dc`
|
||||
// parameter.
|
||||
var delegatorCertPEM = `-----BEGIN CERTIFICATE-----
|
||||
// A PEM-encoded "delegation certificate", an X.509 certificate with the
|
||||
// DelegationUsage extension. The extension is defined in
|
||||
// specified in https://tools.ietf.org/html/draft-ietf-tls-subcerts-01.
|
||||
var dcDelegationCertPEM = `-----BEGIN CERTIFICATE-----
|
||||
MIIBdzCCAR2gAwIBAgIQLVIvEpo0/0TzRja4ImvB1TAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMB4XDTE4MDcwMzE2NTE1M1oXDTE5MDcwMzE2NTE1M1ow
|
||||
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOhB
|
||||
@ -33,14 +29,17 @@ OetDIvU4mdyoAiAGN97y3GJccYn9ZOJS4UOqhr9oO8PuZMLgdq4OrMRiiA==
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
var delegatorKeyPEM = `-----BEGIN EC PRIVATE KEY-----
|
||||
// The PEM-encoded "delegation key", the secret key associated with the
|
||||
// delegation certificate.
|
||||
var dcDelegationKeyPEM = `-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIJDVlo+sJolMcNjMkfCGDUjMJcE4UgclcXGCrOtbJAi2oAoGCCqGSM49
|
||||
AwEHoUQDQgAE6EFTpp1oCCWItoVzU8Cj0cE7haKyHUbh1/cgrkRcvL6ihh+aR/NP
|
||||
UXGps0tm581jO97bl+alqX/VUkCOlXIqrQ==
|
||||
-----END EC PRIVATE KEY-----
|
||||
`
|
||||
|
||||
var nonDelegatorCertPEM = `-----BEGIN CERTIFICATE-----
|
||||
// A certificate without the DelegationUsage extension.
|
||||
var dcCertPEM = `-----BEGIN CERTIFICATE-----
|
||||
MIIBaTCCAQ6gAwIBAgIQSUo+9uaip3qCW+1EPeHZgDAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMB4XDTE4MDYxMjIzNDAyNloXDTE5MDYxMjIzNDAyNlow
|
||||
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLf7
|
||||
@ -52,13 +51,25 @@ AwIDSQAwRgIhANXG0zmrVtQBK0TNZZoEGMOtSwxmiZzXNe+IjdpxO3TiAiEA5VYx
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
var nonDelegatorKeyPEM = `-----BEGIN EC PRIVATE KEY-----
|
||||
// The secret key associatted with dcCertPEM.
|
||||
var dcKeyPEM = `-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIMw9DiOfGI1E/XZrrW2huZSjYi0EKwvVjAe+dYtyFsSloAoGCCqGSM49
|
||||
AwEHoUQDQgAEt/t+LOc9V1zdXmYzfKazBTb8iglqr914Dp2B2PnSjN1jJGIA+PHN
|
||||
zP3NGxnDVqlMX+HI1+IuFXgQTVWvBdxPkw==
|
||||
-----END EC PRIVATE KEY-----
|
||||
`
|
||||
|
||||
// dcTestDC stores delegated credentials and their secret keys.
|
||||
type dcTestDC struct {
|
||||
Name string
|
||||
Version int
|
||||
Scheme int
|
||||
DC []byte
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
// Test data used for testing the TLS handshake with the delegated creedential
|
||||
// extension. The PEM block encodes a DER encoded slice of dcTestDC's.
|
||||
var dcTestDCsPEM = `-----BEGIN DC TEST DATA-----
|
||||
MIIGQzCCAToTBXRsczEyAgIDAwICBAMEga0ACUp3AFswWTATBgcqhkjOPQIBBggq
|
||||
hkjOPQMBBwNCAAQ9z9RDrMvyRzPOkw9SK2S/O5DiwfRNjAwYcq7e/sKdN0ZcSP1K
|
||||
@ -97,14 +108,6 @@ HJbn52hmB027JEIMPWsxKxPkr7udk7Q=
|
||||
-----END DC TEST DATA-----
|
||||
`
|
||||
|
||||
type dcTestDC struct {
|
||||
Name string
|
||||
Version int
|
||||
Scheme int
|
||||
DC []byte
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
var dcTestDCs []dcTestDC
|
||||
var dcTestConfig *Config
|
||||
var dcTestDelegationCert Certificate
|
||||
@ -112,8 +115,7 @@ var dcTestCert Certificate
|
||||
var dcTestNow time.Time
|
||||
|
||||
func init() {
|
||||
|
||||
// Parse the PEM-encoded DER block containing the test DCs.
|
||||
// Parse the PEM block containing the test DCs.
|
||||
block, _ := pem.Decode([]byte(dcTestDCsPEM))
|
||||
if block == nil {
|
||||
panic("failed to decode DC tests PEM block")
|
||||
@ -142,7 +144,7 @@ func init() {
|
||||
}
|
||||
|
||||
// The delegation certificate.
|
||||
dcTestDelegationCert, err = X509KeyPair([]byte(delegatorCertPEM), []byte(delegatorKeyPEM))
|
||||
dcTestDelegationCert, err = X509KeyPair([]byte(dcDelegationCertPEM), []byte(dcDelegationKeyPEM))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -152,7 +154,7 @@ func init() {
|
||||
}
|
||||
|
||||
// A certificate without the the DelegationUsage extension for X.509.
|
||||
dcTestCert, err = X509KeyPair([]byte(nonDelegatorCertPEM), []byte(nonDelegatorKeyPEM))
|
||||
dcTestCert, err = X509KeyPair([]byte(dcCertPEM), []byte(dcKeyPEM))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -179,9 +181,9 @@ func init() {
|
||||
dcTestConfig.RootCAs.AddCert(root)
|
||||
}
|
||||
|
||||
// Tests the handshake and one round of application data. Returns true if the
|
||||
// connection used a DC.
|
||||
func testConnWithDC(t *testing.T, clientMsg, serverMsg string, clientConfig, serverConfig *Config) (bool, error) {
|
||||
// Executes the handshake with the given configuration and returns true if the
|
||||
// delegated credential extension was successfully negotiated.
|
||||
func testConnWithDC(t *testing.T, clientConfig, serverConfig *Config) (bool, error) {
|
||||
ln := newLocalListener(t)
|
||||
defer ln.Close()
|
||||
|
||||
@ -216,28 +218,9 @@ func testConnWithDC(t *testing.T, clientMsg, serverMsg string, clientConfig, ser
|
||||
return false, serr
|
||||
}
|
||||
|
||||
bufLen := len(clientMsg)
|
||||
if len(serverMsg) > len(clientMsg) {
|
||||
bufLen = len(serverMsg)
|
||||
}
|
||||
buf := make([]byte, bufLen)
|
||||
|
||||
// Client sends a message to the server, which is read by the server.
|
||||
cli.Write([]byte(clientMsg))
|
||||
n, err := srv.Read(buf)
|
||||
if n != len(clientMsg) || string(buf[:n]) != clientMsg {
|
||||
return false, fmt.Errorf("Server read = %d, buf= %q; want %d, %s", n, buf, len(clientMsg), clientMsg)
|
||||
}
|
||||
|
||||
// Server reads a message from the client, which is read by the client.
|
||||
srv.Write([]byte(serverMsg))
|
||||
n, err = cli.Read(buf)
|
||||
if n != len(serverMsg) || err != nil || string(buf[:n]) != serverMsg {
|
||||
return false, fmt.Errorf("Client read = %d, %v, data %q; want %d, nil, %s", n, err, buf, len(serverMsg), serverMsg)
|
||||
}
|
||||
|
||||
// Return true if the client's conn.dc structure was instantiated.
|
||||
return (cli.verifiedDc != nil), nil
|
||||
st := cli.ConnectionState()
|
||||
return (st.DelegatedCredential != nil), nil
|
||||
}
|
||||
|
||||
// Checks that the client suppports a version >= 1.2 and accepts delegated
|
||||
@ -256,7 +239,7 @@ func testServerGetCertificate(ch *ClientHelloInfo) (*Certificate, error) {
|
||||
}
|
||||
|
||||
// Various test cases for handshakes involving DCs.
|
||||
var dcTests = []struct {
|
||||
var dcTesters = []struct {
|
||||
clientDC bool
|
||||
serverDC bool
|
||||
clientSkipVerify bool
|
||||
@ -284,53 +267,46 @@ var dcTests = []struct {
|
||||
// Tests the handshake with the delegated credential extension for each test
|
||||
// case in dcTests.
|
||||
func TestDCHandshake(t *testing.T) {
|
||||
serverMsg := "hello"
|
||||
clientMsg := "world"
|
||||
|
||||
clientConfig := dcTestConfig.Clone()
|
||||
serverConfig := dcTestConfig.Clone()
|
||||
serverConfig.GetCertificate = testServerGetCertificate
|
||||
|
||||
for i, test := range dcTests {
|
||||
clientConfig.MaxVersion = test.clientMaxVers
|
||||
serverConfig.MaxVersion = test.serverMaxVers
|
||||
clientConfig.InsecureSkipVerify = test.clientSkipVerify
|
||||
clientConfig.AcceptDelegatedCredential = test.clientDC
|
||||
for i, tester := range dcTesters {
|
||||
clientConfig.MaxVersion = tester.clientMaxVers
|
||||
serverConfig.MaxVersion = tester.serverMaxVers
|
||||
clientConfig.InsecureSkipVerify = tester.clientSkipVerify
|
||||
clientConfig.AcceptDelegatedCredential = tester.clientDC
|
||||
clientConfig.Time = func() time.Time {
|
||||
return dcTestNow.Add(time.Duration(test.nowOffset))
|
||||
return dcTestNow.Add(time.Duration(tester.nowOffset))
|
||||
}
|
||||
|
||||
if test.serverDC {
|
||||
if tester.serverDC {
|
||||
serverConfig.GetDelegatedCredential = func(
|
||||
ch *ClientHelloInfo, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) {
|
||||
for _, t := range dcTestDCs {
|
||||
if t.Name == test.dcTestName {
|
||||
dc, err := UnmarshalDelegatedCredential(t.DC)
|
||||
ch *ClientHelloInfo, vers uint16) ([]byte, crypto.PrivateKey, error) {
|
||||
for _, test := range dcTestDCs {
|
||||
if test.Name == tester.dcTestName {
|
||||
sk, err := x509.ParseECPrivateKey(test.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sk, err := x509.ParseECPrivateKey(t.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return dc, sk, nil
|
||||
return test.DC, sk, nil
|
||||
}
|
||||
}
|
||||
return nil, nil, fmt.Errorf("Test DC with name '%s' not found", test.dcTestName)
|
||||
return nil, nil, fmt.Errorf("Test DC with name '%s' not found", tester.dcTestName)
|
||||
}
|
||||
} else {
|
||||
serverConfig.GetDelegatedCredential = nil
|
||||
}
|
||||
|
||||
usedDC, err := testConnWithDC(t, clientMsg, serverMsg, clientConfig, serverConfig)
|
||||
if err != nil && test.expectSuccess {
|
||||
t.Errorf("test #%d (%s) fails: %s", i+1, test.name, err)
|
||||
} else if err == nil && !test.expectSuccess {
|
||||
t.Errorf("test #%d (%s) succeeds; expected failure", i+1, test.name)
|
||||
usedDC, err := testConnWithDC(t, clientConfig, serverConfig)
|
||||
if err != nil && tester.expectSuccess {
|
||||
t.Errorf("test #%d (%s) fails: %s", i+1, tester.name, err)
|
||||
} else if err == nil && !tester.expectSuccess {
|
||||
t.Errorf("test #%d (%s) succeeds; expected failure", i+1, tester.name)
|
||||
}
|
||||
|
||||
if usedDC != test.expectDC {
|
||||
t.Errorf("test #%d (%s) usedDC = %v; expected %v", i+1, test.name, usedDC, test.expectDC)
|
||||
if usedDC != tester.expectDC {
|
||||
t.Errorf("test #%d (%s) usedDC = %v; expected %v", i+1, tester.name, usedDC, tester.expectDC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Ładowanie…
Reference in New Issue
Block a user