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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

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
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:]

View File

@ -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 {

View File

@ -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,13 +68,43 @@ 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
// 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
// (as defined in the X.509 standard) for the credential.
func (cred *credential) marshalSubjectPublicKeyInfo() ([]byte, error) {
@ -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
}

View File

@ -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)
}
}
}