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:
Christopher Patton 2018-07-20 09:47:24 -07:00 committed by Kris Kwiatkowski
parent 1ea9624098
commit c5280001a4
9 changed files with 159 additions and 312 deletions

4
13.go
View File

@ -408,7 +408,7 @@ func (hs *serverHandshakeState) sendCertificate13() error {
// server is using the delegated credential extension. In TLS 1.3, the DC is // 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 // added as an extension to the end-entity certificate, i.e., the last
// CertificateEntry of Certificate.certficate_list (see // 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 { if len(certEntries) > 0 && hs.clientHello.delegatedCredential && hs.delegatedCredential != nil {
certEntries[0].delegatedCredential = hs.delegatedCredential certEntries[0].delegatedCredential = hs.delegatedCredential
} }
@ -1069,7 +1069,7 @@ func (hs *clientHandshakeState) doTLS13Handshake() error {
// then the CertificateVerify signature will have been produced with the // then the CertificateVerify signature will have been produced with the
// DelegatedCredential's private key. // DelegatedCredential's private key.
if hs.c.verifiedDc != nil { if hs.c.verifiedDc != nil {
pk = hs.c.verifiedDc.PublicKey pk = hs.c.verifiedDc.cred.publicKey
} }
// Receive CertificateVerify message. // Receive CertificateVerify message.

View File

@ -219,6 +219,7 @@ type ConnectionState struct {
VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates
SignedCertificateTimestamps [][]byte // SCTs from the server, if any SignedCertificateTimestamps [][]byte // SCTs from the server, if any
OCSPResponse []byte // stapled OCSP response from 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 // TLSUnique contains the "tls-unique" channel binding value (see RFC
// 5929, section 3). For resumed sessions this value will be nil // 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. // 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 AcceptDelegatedCredential bool
// GetDelegatedCredential returns a DelegatedCredential for use with the // GetDelegatedCredential returns a DC and its private key for use in the
// delegated credential extension based on the ClientHello and TLS version // delegated credential extension. The inputs to the callback are some
// selected for the session. If this is nil, then the server will not offer // information parsed from the ClientHello, as well as the protocol version
// a DelegatedCredential. // 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. // 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 serverInitOnce sync.Once // guards calling (*Config).serverInit

View File

@ -58,7 +58,7 @@ type Conn struct {
verifiedChains [][]*x509.Certificate verifiedChains [][]*x509.Certificate
// verifiedDc is set by a client who negotiates the use of a valid delegated // verifiedDc is set by a client who negotiates the use of a valid delegated
// credential. // credential.
verifiedDc *DelegatedCredential verifiedDc *delegatedCredential
// serverName contains the server name indicated by the client, if any. // serverName contains the server name indicated by the client, if any.
serverName string serverName string
// secureRenegotiation is true if the server echoed the secure // secureRenegotiation is true if the server echoed the secure
@ -1666,6 +1666,9 @@ func (c *Conn) ConnectionState() ConnectionState {
state.VerifiedChains = c.verifiedChains state.VerifiedChains = c.verifiedChains
state.SignedCertificateTimestamps = c.scts state.SignedCertificateTimestamps = c.scts
state.OCSPResponse = c.ocspResponse state.OCSPResponse = c.ocspResponse
if c.verifiedDc != nil {
state.DelegatedCredential = c.verifiedDc.raw
}
state.HandshakeConfirmed = atomic.LoadInt32(&c.handshakeConfirmed) == 1 state.HandshakeConfirmed = atomic.LoadInt32(&c.handshakeConfirmed) == 1
if !state.HandshakeConfirmed { if !state.HandshakeConfirmed {
state.Unique0RTTToken = c.binder state.Unique0RTTToken = c.binder

View File

@ -14,7 +14,6 @@ import (
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/tls"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
@ -35,7 +34,6 @@ var (
isCA = flag.Bool("ca", false, "whether this cert should be its own Certificate Authority") 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") 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") 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{} { func publicKey(priv interface{}) interface{} {
@ -139,11 +137,6 @@ func main() {
template.KeyUsage |= x509.KeyUsageCertSign template.KeyUsage |= x509.KeyUsageCertSign
} }
if *isDC {
template.ExtraExtensions = append(
template.ExtraExtensions, *tls.CreateDelegationUsagePKIXExtension())
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil { if err != nil {
log.Fatalf("Failed to create certificate: %s", err) log.Fatalf("Failed to create certificate: %s", err)

View File

@ -413,15 +413,15 @@ func (hs *clientHandshakeState) processCertsFromServer(certificates [][]byte) er
return nil return nil
} }
// processDelegatedCredentialFromServer unmarshals the DelegatedCredential // processDelegatedCredentialFromServer unmarshals the delegated credential
// offered by the server (if present) and validates it using the peer // offered by the server (if present) and validates it using the peer
// certificate. // certificate.
func (hs *clientHandshakeState) processDelegatedCredentialFromServer(dc []byte) error { func (hs *clientHandshakeState) processDelegatedCredentialFromServer(serialized []byte) error {
c := hs.c c := hs.c
var cred *DelegatedCredential var dc *delegatedCredential
var err error var err error
if dc != nil { if serialized != nil {
// Assert that the DC extension was indicated by the client. // Assert that the DC extension was indicated by the client.
if !hs.hello.delegatedCredential { 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") return errors.New("tls: ServerHello with delegated credential extension in TLS != 1.2")
} }
cred, err = UnmarshalDelegatedCredential(dc) dc, err = unmarshalDelegatedCredential(serialized)
if err != nil { if err != nil {
c.sendAlert(alertDecodeError) c.sendAlert(alertDecodeError)
return fmt.Errorf("tls: delegated credential: %s", err) return fmt.Errorf("tls: delegated credential: %s", err)
} }
} }
if cred != nil && !c.config.InsecureSkipVerify { if dc != nil && !c.config.InsecureSkipVerify {
if v, err := cred.Validate(c.peerCertificates[0], hs.c.vers, c.config.time()); err != nil { if v, err := dc.validate(c.peerCertificates[0], hs.c.vers, c.config.time()); err != nil {
c.sendAlert(alertIllegalParameter) c.sendAlert(alertIllegalParameter)
return fmt.Errorf("delegated credential: %s", err) return fmt.Errorf("delegated credential: %s", err)
} else if !v { } else if !v {
@ -453,7 +453,7 @@ func (hs *clientHandshakeState) processDelegatedCredentialFromServer(dc []byte)
} }
} }
c.verifiedDc = cred c.verifiedDc = dc
return nil return nil
} }
@ -533,9 +533,10 @@ func (hs *clientHandshakeState) doFullHandshake() error {
pk := c.peerCertificates[0].PublicKey pk := c.peerCertificates[0].PublicKey
// If the delegated credential extension has successfully been negotiated, // 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 { if c.verifiedDc != nil {
pk = c.verifiedDc.PublicKey pk = c.verifiedDc.cred.publicKey
} }
skx, ok := msg.(*serverKeyExchangeMsg) skx, ok := msg.(*serverKeyExchangeMsg)

View File

@ -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 // https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.2.8
m.earlyData = true m.earlyData = true
case extensionDelegatedCredential: case extensionDelegatedCredential:
// https://tools.ietf.org/html/draft-ietf-tls-subcerts // https://tools.ietf.org/html/draft-ietf-tls-subcerts-01
m.delegatedCredential = true m.delegatedCredential = true
} }
data = data[length:] data = data[length:]

View File

@ -329,14 +329,7 @@ Curves:
// Set the handshake private key. // Set the handshake private key.
if dc != nil { if dc != nil {
hs.privateKey = sk hs.privateKey = sk
if dc.Raw == nil { hs.delegatedCredential = dc
dc.Raw, err = dc.Marshal()
if err != nil {
c.sendAlert(alertInternalError)
return false, err
}
}
hs.delegatedCredential = dc.Raw
// For TLS 1.2, the DC is an extension to the ServerHello. // For TLS 1.2, the DC is an extension to the ServerHello.
if c.vers == VersionTLS12 { if c.vers == VersionTLS12 {

View File

@ -5,29 +5,30 @@
package tls package tls
// Delegated credentials for TLS // Delegated credentials for TLS
// (https://tools.ietf.org/html/draft-ietf-tls-subcerts) is an IETF Internet // (https://tools.ietf.org/html/draft-ietf-tls-subcerts-01) is an IETF Internet
// draft and proposed TLS extension. If the client supports this extension, then // draft and proposed TLS extension. This allows a backend server to delegate
// the server may use a "delegated credential" as the signing key in the // TLS termination to a trusted frontend. If the client supports this extension,
// handshake. A delegated credential is a short lived public/secret key pair // then the frontend may use a "delegated credential" as the signing key in the
// delegated to the server by an entity trusted by the client. This allows a // handshake. A delegated credential is a short lived key pair delegated to the
// middlebox to terminate a TLS connection on behalf of the entity; for example, // server by an entity trusted by the client. Once issued, credentials can't be
// this can be used to delegate TLS termination to a reverse proxy. Credentials // revoked; in order to mitigate risk in case the frontend is compromised, the
// can't be revoked; in order to mitigate risk in case the middlebox is // credential is only valid for a short time (days, hours, or even minutes).
// 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. // This implements draft 01. This draft doesn't specify an object identifier for
// Currently delegated credentials only support ECDSA. The delegator must also // the X.509 extension; we use one assigned by Cloudflare. In addition, IANA has
// use an ECDSA key. // 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 ( import (
"bytes" "bytes"
"crypto" "crypto"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
"encoding/binary" "encoding/binary"
"errors" "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. // 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} 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 // canDelegate returns true if a certificate can be used for delegated
// credentials. // credentials.
func canDelegate(cert *x509.Certificate) bool { func canDelegate(cert *x509.Certificate) bool {
@ -83,11 +68,41 @@ func canDelegate(cert *x509.Certificate) bool {
return false return false
} }
// This structure stores the public components of a credential. // credential stores the public components of a credential.
type credential struct { 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 validTime time.Duration
// The credential public key.
publicKey crypto.PublicKey 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 // 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) { func (cred *credential) marshal() ([]byte, error) {
// Write the valid_time field. // Write the valid_time field.
serialized := make([]byte, 6) serialized := make([]byte, 6)
@ -198,173 +214,62 @@ func getCredentialLen(serialized []byte) (int, error) {
return 6 + serializedPublicKeyLen, nil return 6 + serializedPublicKeyLen, nil
} }
// DelegatedCredential stores a credential and its delegation. // delegatedCredential stores a credential and its delegation.
type DelegatedCredential struct { type delegatedCredential struct {
// The serialized form of the credential. raw []byte
Raw []byte
// The amount of time for which the credential is valid. Specifically, the // The credential, which contains a public and its validity time.
// the credential expires `ValidTime` seconds after the `notBefore` of the cred *credential
// 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. // The signature scheme used to sign the credential.
Scheme SignatureScheme scheme SignatureScheme
// The credential's delegation. // The credential's delegation.
Signature []byte signature []byte
} }
// NewDelegatedCredential creates a new delegated credential using `cert` for // ensureCertificateHasLeaf parses the leaf certificate if needed.
// delegation. It generates a public/private key pair for the provided signature func ensureCertificateHasLeaf(cert *Certificate) error {
// 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 var err error
if cert.Leaf == nil { if cert.Leaf == nil {
if len(cert.Certificate[0]) == 0 { 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]) cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil { if err != nil {
return nil, nil, err return err
} }
} }
return nil
// 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 // validate checks that that the signature is valid, that the credential hasn't
// 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 // expired, and that the TTL is valid. It also checks that certificate can be
// used for delegation. // 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. // Check that the cert can delegate.
if !canDelegate(cert) { if !canDelegate(cert) {
return false, errNoDelegationUsage return false, errNoDelegationUsage
} }
if dc.IsExpired(cert.NotBefore, now) { if dc.cred.isExpired(cert.NotBefore, now) {
return false, errors.New("credential has expired") 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") return false, errors.New("credential TTL is invalid")
} }
// Prepare the credential for verification. // Prepare the credential for verification.
hash := getHash(dc.Scheme) hash := getHash(dc.scheme)
cred := &credential{dc.ValidTime, dc.PublicKey, dc.publicKeyScheme} in, err := prepareDelegation(hash, dc.cred, cert.Raw, dc.scheme, vers)
in, err := prepareDelegation(hash, cred, cert.Raw, dc.Scheme, vers)
if err != nil { if err != nil {
return false, err return false, err
} }
// TODO(any) This code overlaps signficantly with verifyHandshakeSignature() // TODO(any) This code overlaps signficantly with verifyHandshakeSignature()
// in ../auth.go. This should be refactored. // in ../auth.go. This should be refactored.
switch dc.Scheme { switch dc.scheme {
case ECDSAWithP256AndSHA256, case ECDSAWithP256AndSHA256,
ECDSAWithP384AndSHA384, ECDSAWithP384AndSHA384,
ECDSAWithP521AndSHA512: ECDSAWithP521AndSHA512:
@ -373,47 +278,19 @@ func (dc *DelegatedCredential) Validate(cert *x509.Certificate, vers uint16, now
return false, errors.New("expected ECDSA public key") return false, errors.New("expected ECDSA public key")
} }
sig := new(ecdsaSignature) 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 false, err
} }
return ecdsa.Verify(pk, in, sig.R, sig.S), nil return ecdsa.Verify(pk, in, sig.R, sig.S), nil
default: default:
return false, fmt.Errorf( 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 // unmarshalDelegatedCredential decodes a DelegatedCredential structure.
// dc.Raw to the output as a side effect. func unmarshalDelegatedCredential(serialized []byte) (*delegatedCredential, error) {
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 // Get the length of the serialized credential that begins at the start of
// the input slice. // the input slice.
serializedCredentialLen, err := getCredentialLen(serialized) serializedCredentialLen, err := getCredentialLen(serialized)
@ -445,12 +322,11 @@ func UnmarshalDelegatedCredential(serialized []byte) (*DelegatedCredential, erro
} }
sig := serialized[:serializedSignatureLen] sig := serialized[:serializedSignatureLen]
return &DelegatedCredential{ return &delegatedCredential{
ValidTime: cred.validTime, raw: serialized,
PublicKey: cred.publicKey, cred: cred,
publicKeyScheme: cred.scheme, scheme: scheme,
Scheme: scheme, signature: sig,
Signature: sig,
}, nil }, nil
} }

View File

@ -14,14 +14,10 @@ import (
"time" "time"
) )
// These test keys were generated with the following program, available in the // A PEM-encoded "delegation certificate", an X.509 certificate with the
// crypto/tls directory: // DelegationUsage extension. The extension is defined in
// // specified in https://tools.ietf.org/html/draft-ietf-tls-subcerts-01.
// go run generate_cert.go -ecdsa-curve P256 -host 127.0.0.1 -dc var dcDelegationCertPEM = `-----BEGIN CERTIFICATE-----
//
// To get a certificate without the DelegationUsage extension, remove the `-dc`
// parameter.
var delegatorCertPEM = `-----BEGIN CERTIFICATE-----
MIIBdzCCAR2gAwIBAgIQLVIvEpo0/0TzRja4ImvB1TAKBggqhkjOPQQDAjASMRAw MIIBdzCCAR2gAwIBAgIQLVIvEpo0/0TzRja4ImvB1TAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE4MDcwMzE2NTE1M1oXDTE5MDcwMzE2NTE1M1ow DgYDVQQKEwdBY21lIENvMB4XDTE4MDcwMzE2NTE1M1oXDTE5MDcwMzE2NTE1M1ow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOhB EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOhB
@ -33,14 +29,17 @@ OetDIvU4mdyoAiAGN97y3GJccYn9ZOJS4UOqhr9oO8PuZMLgdq4OrMRiiA==
-----END CERTIFICATE----- -----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 MHcCAQEEIJDVlo+sJolMcNjMkfCGDUjMJcE4UgclcXGCrOtbJAi2oAoGCCqGSM49
AwEHoUQDQgAE6EFTpp1oCCWItoVzU8Cj0cE7haKyHUbh1/cgrkRcvL6ihh+aR/NP AwEHoUQDQgAE6EFTpp1oCCWItoVzU8Cj0cE7haKyHUbh1/cgrkRcvL6ihh+aR/NP
UXGps0tm581jO97bl+alqX/VUkCOlXIqrQ== UXGps0tm581jO97bl+alqX/VUkCOlXIqrQ==
-----END EC PRIVATE KEY----- -----END EC PRIVATE KEY-----
` `
var nonDelegatorCertPEM = `-----BEGIN CERTIFICATE----- // A certificate without the DelegationUsage extension.
var dcCertPEM = `-----BEGIN CERTIFICATE-----
MIIBaTCCAQ6gAwIBAgIQSUo+9uaip3qCW+1EPeHZgDAKBggqhkjOPQQDAjASMRAw MIIBaTCCAQ6gAwIBAgIQSUo+9uaip3qCW+1EPeHZgDAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE4MDYxMjIzNDAyNloXDTE5MDYxMjIzNDAyNlow DgYDVQQKEwdBY21lIENvMB4XDTE4MDYxMjIzNDAyNloXDTE5MDYxMjIzNDAyNlow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLf7 EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLf7
@ -52,13 +51,25 @@ AwIDSQAwRgIhANXG0zmrVtQBK0TNZZoEGMOtSwxmiZzXNe+IjdpxO3TiAiEA5VYx
-----END CERTIFICATE----- -----END CERTIFICATE-----
` `
var nonDelegatorKeyPEM = `-----BEGIN EC PRIVATE KEY----- // The secret key associatted with dcCertPEM.
var dcKeyPEM = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMw9DiOfGI1E/XZrrW2huZSjYi0EKwvVjAe+dYtyFsSloAoGCCqGSM49 MHcCAQEEIMw9DiOfGI1E/XZrrW2huZSjYi0EKwvVjAe+dYtyFsSloAoGCCqGSM49
AwEHoUQDQgAEt/t+LOc9V1zdXmYzfKazBTb8iglqr914Dp2B2PnSjN1jJGIA+PHN AwEHoUQDQgAEt/t+LOc9V1zdXmYzfKazBTb8iglqr914Dp2B2PnSjN1jJGIA+PHN
zP3NGxnDVqlMX+HI1+IuFXgQTVWvBdxPkw== zP3NGxnDVqlMX+HI1+IuFXgQTVWvBdxPkw==
-----END EC PRIVATE KEY----- -----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----- var dcTestDCsPEM = `-----BEGIN DC TEST DATA-----
MIIGQzCCAToTBXRsczEyAgIDAwICBAMEga0ACUp3AFswWTATBgcqhkjOPQIBBggq MIIGQzCCAToTBXRsczEyAgIDAwICBAMEga0ACUp3AFswWTATBgcqhkjOPQIBBggq
hkjOPQMBBwNCAAQ9z9RDrMvyRzPOkw9SK2S/O5DiwfRNjAwYcq7e/sKdN0ZcSP1K hkjOPQMBBwNCAAQ9z9RDrMvyRzPOkw9SK2S/O5DiwfRNjAwYcq7e/sKdN0ZcSP1K
@ -97,14 +108,6 @@ HJbn52hmB027JEIMPWsxKxPkr7udk7Q=
-----END DC TEST DATA----- -----END DC TEST DATA-----
` `
type dcTestDC struct {
Name string
Version int
Scheme int
DC []byte
PrivateKey []byte
}
var dcTestDCs []dcTestDC var dcTestDCs []dcTestDC
var dcTestConfig *Config var dcTestConfig *Config
var dcTestDelegationCert Certificate var dcTestDelegationCert Certificate
@ -112,8 +115,7 @@ var dcTestCert Certificate
var dcTestNow time.Time var dcTestNow time.Time
func init() { func init() {
// Parse the PEM block containing the test DCs.
// Parse the PEM-encoded DER block containing the test DCs.
block, _ := pem.Decode([]byte(dcTestDCsPEM)) block, _ := pem.Decode([]byte(dcTestDCsPEM))
if block == nil { if block == nil {
panic("failed to decode DC tests PEM block") panic("failed to decode DC tests PEM block")
@ -142,7 +144,7 @@ func init() {
} }
// The delegation certificate. // The delegation certificate.
dcTestDelegationCert, err = X509KeyPair([]byte(delegatorCertPEM), []byte(delegatorKeyPEM)) dcTestDelegationCert, err = X509KeyPair([]byte(dcDelegationCertPEM), []byte(dcDelegationKeyPEM))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -152,7 +154,7 @@ func init() {
} }
// A certificate without the the DelegationUsage extension for X.509. // 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 { if err != nil {
panic(err) panic(err)
} }
@ -179,9 +181,9 @@ func init() {
dcTestConfig.RootCAs.AddCert(root) dcTestConfig.RootCAs.AddCert(root)
} }
// Tests the handshake and one round of application data. Returns true if the // Executes the handshake with the given configuration and returns true if the
// connection used a DC. // delegated credential extension was successfully negotiated.
func testConnWithDC(t *testing.T, clientMsg, serverMsg string, clientConfig, serverConfig *Config) (bool, error) { func testConnWithDC(t *testing.T, clientConfig, serverConfig *Config) (bool, error) {
ln := newLocalListener(t) ln := newLocalListener(t)
defer ln.Close() defer ln.Close()
@ -216,28 +218,9 @@ func testConnWithDC(t *testing.T, clientMsg, serverMsg string, clientConfig, ser
return false, serr 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 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 // 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. // Various test cases for handshakes involving DCs.
var dcTests = []struct { var dcTesters = []struct {
clientDC bool clientDC bool
serverDC bool serverDC bool
clientSkipVerify bool clientSkipVerify bool
@ -284,53 +267,46 @@ var dcTests = []struct {
// Tests the handshake with the delegated credential extension for each test // Tests the handshake with the delegated credential extension for each test
// case in dcTests. // case in dcTests.
func TestDCHandshake(t *testing.T) { func TestDCHandshake(t *testing.T) {
serverMsg := "hello"
clientMsg := "world"
clientConfig := dcTestConfig.Clone() clientConfig := dcTestConfig.Clone()
serverConfig := dcTestConfig.Clone() serverConfig := dcTestConfig.Clone()
serverConfig.GetCertificate = testServerGetCertificate serverConfig.GetCertificate = testServerGetCertificate
for i, test := range dcTests { for i, tester := range dcTesters {
clientConfig.MaxVersion = test.clientMaxVers clientConfig.MaxVersion = tester.clientMaxVers
serverConfig.MaxVersion = test.serverMaxVers serverConfig.MaxVersion = tester.serverMaxVers
clientConfig.InsecureSkipVerify = test.clientSkipVerify clientConfig.InsecureSkipVerify = tester.clientSkipVerify
clientConfig.AcceptDelegatedCredential = test.clientDC clientConfig.AcceptDelegatedCredential = tester.clientDC
clientConfig.Time = func() time.Time { 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( serverConfig.GetDelegatedCredential = func(
ch *ClientHelloInfo, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) { ch *ClientHelloInfo, vers uint16) ([]byte, crypto.PrivateKey, error) {
for _, t := range dcTestDCs { for _, test := range dcTestDCs {
if t.Name == test.dcTestName { if test.Name == tester.dcTestName {
dc, err := UnmarshalDelegatedCredential(t.DC) sk, err := x509.ParseECPrivateKey(test.PrivateKey)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
sk, err := x509.ParseECPrivateKey(t.PrivateKey) return test.DC, sk, nil
if err != nil {
return nil, nil, err
}
return 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 { } else {
serverConfig.GetDelegatedCredential = nil serverConfig.GetDelegatedCredential = nil
} }
usedDC, err := testConnWithDC(t, clientMsg, serverMsg, clientConfig, serverConfig) usedDC, err := testConnWithDC(t, clientConfig, serverConfig)
if err != nil && test.expectSuccess { if err != nil && tester.expectSuccess {
t.Errorf("test #%d (%s) fails: %s", i+1, test.name, err) t.Errorf("test #%d (%s) fails: %s", i+1, tester.name, err)
} else if err == nil && !test.expectSuccess { } else if err == nil && !tester.expectSuccess {
t.Errorf("test #%d (%s) succeeds; expected failure", i+1, test.name) t.Errorf("test #%d (%s) succeeds; expected failure", i+1, tester.name)
} }
if usedDC != test.expectDC { if usedDC != tester.expectDC {
t.Errorf("test #%d (%s) usedDC = %v; expected %v", i+1, test.name, usedDC, test.expectDC) t.Errorf("test #%d (%s) usedDC = %v; expected %v", i+1, tester.name, usedDC, tester.expectDC)
} }
} }
} }