Merge pull request #95 from cjpatton/subcerts
Delegated credentials for TLS
This commit is contained in:
commit
22d6deb0e7
40
13.go
40
13.go
@ -403,6 +403,16 @@ func (hs *serverHandshakeState) sendCertificate13() error {
|
|||||||
if len(certEntries) > 0 && hs.clientHello.scts {
|
if len(certEntries) > 0 && hs.clientHello.scts {
|
||||||
certEntries[0].sctList = hs.cert.SignedCertificateTimestamps
|
certEntries[0].sctList = hs.cert.SignedCertificateTimestamps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If hs.delegatedCredential is set (see hs.readClientHello()) then the
|
||||||
|
// 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).
|
||||||
|
if len(certEntries) > 0 && hs.clientHello.delegatedCredential && hs.delegatedCredential != nil {
|
||||||
|
certEntries[0].delegatedCredential = hs.delegatedCredential
|
||||||
|
}
|
||||||
|
|
||||||
certMsg := &certificateMsg13{certificates: certEntries}
|
certMsg := &certificateMsg13{certificates: certEntries}
|
||||||
|
|
||||||
hs.keySchedule.write(certMsg.marshal())
|
hs.keySchedule.write(certMsg.marshal())
|
||||||
@ -423,7 +433,7 @@ func (hs *serverHandshakeState) sendCertificate13() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toSign := prepareDigitallySigned(sigHash, "TLS 1.3, server CertificateVerify", hs.keySchedule.transcriptHash.Sum(nil))
|
toSign := prepareDigitallySigned(sigHash, "TLS 1.3, server CertificateVerify", hs.keySchedule.transcriptHash.Sum(nil))
|
||||||
signature, err := hs.cert.PrivateKey.(crypto.Signer).Sign(c.config.rand(), toSign[:], opts)
|
signature, err := hs.privateKey.(crypto.Signer).Sign(c.config.rand(), toSign[:], opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.sendAlert(alertInternalError)
|
c.sendAlert(alertInternalError)
|
||||||
return err
|
return err
|
||||||
@ -468,9 +478,9 @@ func (c *Conn) handleEndOfEarlyData() error {
|
|||||||
// See https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.4.1.2
|
// See https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.4.1.2
|
||||||
func (hs *serverHandshakeState) selectTLS13SignatureScheme() (sigScheme SignatureScheme, err error) {
|
func (hs *serverHandshakeState) selectTLS13SignatureScheme() (sigScheme SignatureScheme, err error) {
|
||||||
var supportedSchemes []SignatureScheme
|
var supportedSchemes []SignatureScheme
|
||||||
signer, ok := hs.cert.PrivateKey.(crypto.Signer)
|
signer, ok := hs.privateKey.(crypto.Signer)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, errors.New("tls: certificate private key does not implement crypto.Signer")
|
return 0, errors.New("tls: private key does not implement crypto.Signer")
|
||||||
}
|
}
|
||||||
pk := signer.Public()
|
pk := signer.Public()
|
||||||
if _, ok := pk.(*rsa.PublicKey); ok {
|
if _, ok := pk.(*rsa.PublicKey); ok {
|
||||||
@ -1034,12 +1044,34 @@ func (hs *clientHandshakeState) doTLS13Handshake() error {
|
|||||||
return unexpectedMessageError(certMsg, msg)
|
return unexpectedMessageError(certMsg, msg)
|
||||||
}
|
}
|
||||||
hs.keySchedule.write(certMsg.marshal())
|
hs.keySchedule.write(certMsg.marshal())
|
||||||
|
|
||||||
// Validate certificates.
|
// Validate certificates.
|
||||||
certs := getCertsFromEntries(certMsg.certificates)
|
certs := getCertsFromEntries(certMsg.certificates)
|
||||||
if err := hs.processCertsFromServer(certs); err != nil {
|
if err := hs.processCertsFromServer(certs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the DC if present. The DC is only processed if the extension was
|
||||||
|
// indicated by the ClientHello; otherwise this call will result in an
|
||||||
|
// "illegal_parameter" alert. The call also asserts that the DC extension
|
||||||
|
// did not appear in the ServerHello.
|
||||||
|
if len(certMsg.certificates) > 0 {
|
||||||
|
if err := hs.processDelegatedCredentialFromServer(
|
||||||
|
certMsg.certificates[0].delegatedCredential); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the public key used to verify the handshake.
|
||||||
|
pk := hs.c.peerCertificates[0].PublicKey
|
||||||
|
|
||||||
|
// If the delegated credential extension has successfully been negotiated,
|
||||||
|
// then the CertificateVerify signature will have been produced with the
|
||||||
|
// DelegatedCredential's private key.
|
||||||
|
if hs.c.verifiedDc != nil {
|
||||||
|
pk = hs.c.verifiedDc.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
// Receive CertificateVerify message.
|
// Receive CertificateVerify message.
|
||||||
msg, err = c.readHandshake()
|
msg, err = c.readHandshake()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1052,7 +1084,7 @@ func (hs *clientHandshakeState) doTLS13Handshake() error {
|
|||||||
}
|
}
|
||||||
err, alertCode := verifyPeerHandshakeSignature(
|
err, alertCode := verifyPeerHandshakeSignature(
|
||||||
certVerifyMsg,
|
certVerifyMsg,
|
||||||
hs.c.peerCertificates[0].PublicKey,
|
pk,
|
||||||
hs.hello.supportedSignatureAlgorithms,
|
hs.hello.supportedSignatureAlgorithms,
|
||||||
hs.keySchedule.transcriptHash.Sum(nil),
|
hs.keySchedule.transcriptHash.Sum(nil),
|
||||||
"TLS 1.3, server CertificateVerify")
|
"TLS 1.3, server CertificateVerify")
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/des"
|
"crypto/des"
|
||||||
@ -12,7 +13,6 @@ import (
|
|||||||
"crypto/rc4"
|
"crypto/rc4"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
|
||||||
"hash"
|
"hash"
|
||||||
|
|
||||||
"golang_org/x/crypto/chacha20poly1305"
|
"golang_org/x/crypto/chacha20poly1305"
|
||||||
@ -26,15 +26,15 @@ type keyAgreement interface {
|
|||||||
// In the case that the key agreement protocol doesn't use a
|
// In the case that the key agreement protocol doesn't use a
|
||||||
// ServerKeyExchange message, generateServerKeyExchange can return nil,
|
// ServerKeyExchange message, generateServerKeyExchange can return nil,
|
||||||
// nil.
|
// nil.
|
||||||
generateServerKeyExchange(*Config, *Certificate, *clientHelloMsg, *serverHelloMsg) (*serverKeyExchangeMsg, error)
|
generateServerKeyExchange(*Config, crypto.PrivateKey, *clientHelloMsg, *serverHelloMsg) (*serverKeyExchangeMsg, error)
|
||||||
processClientKeyExchange(*Config, *Certificate, *clientKeyExchangeMsg, uint16) ([]byte, error)
|
processClientKeyExchange(*Config, crypto.PrivateKey, *clientKeyExchangeMsg, uint16) ([]byte, error)
|
||||||
|
|
||||||
// On the client side, the next two methods are called in order.
|
// On the client side, the next two methods are called in order.
|
||||||
|
|
||||||
// This method may not be called if the server doesn't send a
|
// This method may not be called if the server doesn't send a
|
||||||
// ServerKeyExchange message.
|
// ServerKeyExchange message.
|
||||||
processServerKeyExchange(*Config, *clientHelloMsg, *serverHelloMsg, *x509.Certificate, *serverKeyExchangeMsg) error
|
processServerKeyExchange(*Config, *clientHelloMsg, *serverHelloMsg, crypto.PublicKey, *serverKeyExchangeMsg) error
|
||||||
generateClientKeyExchange(*Config, *clientHelloMsg, *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error)
|
generateClientKeyExchange(*Config, *clientHelloMsg, crypto.PublicKey) ([]byte, *clientKeyExchangeMsg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
23
common.go
23
common.go
@ -97,6 +97,7 @@ const (
|
|||||||
extensionKeyShare uint16 = 51
|
extensionKeyShare uint16 = 51
|
||||||
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
|
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
|
||||||
extensionRenegotiationInfo uint16 = 0xff01
|
extensionRenegotiationInfo uint16 = 0xff01
|
||||||
|
extensionDelegatedCredential uint16 = 0xff02 // TODO(any) Get IANA assignment
|
||||||
)
|
)
|
||||||
|
|
||||||
// TLS signaling cipher suite values
|
// TLS signaling cipher suite values
|
||||||
@ -356,6 +357,10 @@ type ClientHelloInfo struct {
|
|||||||
// immediately available for Read.
|
// immediately available for Read.
|
||||||
Offered0RTTData bool
|
Offered0RTTData bool
|
||||||
|
|
||||||
|
// AcceptsDelegatedCredential is true if the client indicated willingness
|
||||||
|
// to negotiate the delegated credential extension.
|
||||||
|
AcceptsDelegatedCredential bool
|
||||||
|
|
||||||
// The Fingerprint is an sequence of bytes unique to this Client Hello.
|
// The Fingerprint is an sequence of bytes unique to this Client Hello.
|
||||||
// It can be used to prevent or mitigate 0-RTT data replays as it's
|
// It can be used to prevent or mitigate 0-RTT data replays as it's
|
||||||
// guaranteed that a replayed connection will have the same Fingerprint.
|
// guaranteed that a replayed connection will have the same Fingerprint.
|
||||||
@ -609,6 +614,22 @@ type Config struct {
|
|||||||
// session tickets, instead of SessionTicketKey.
|
// session tickets, instead of SessionTicketKey.
|
||||||
SessionTicketSealer SessionTicketSealer
|
SessionTicketSealer SessionTicketSealer
|
||||||
|
|
||||||
|
// AcceptDelegatedCredential is true if the client is willing to negotiate
|
||||||
|
// the delegated credential extension.
|
||||||
|
//
|
||||||
|
// This value has no meaning for the server.
|
||||||
|
//
|
||||||
|
// See https://tools.ietf.org/html/draft-ietf-tls-subcerts.
|
||||||
|
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.
|
||||||
|
//
|
||||||
|
// This value has no meaning for the client.
|
||||||
|
GetDelegatedCredential func(*ClientHelloInfo, uint16) (*DelegatedCredential, crypto.PrivateKey, error)
|
||||||
|
|
||||||
serverInitOnce sync.Once // guards calling (*Config).serverInit
|
serverInitOnce sync.Once // guards calling (*Config).serverInit
|
||||||
|
|
||||||
// mutex protects sessionTicketKeys.
|
// mutex protects sessionTicketKeys.
|
||||||
@ -685,6 +706,8 @@ func (c *Config) Clone() *Config {
|
|||||||
Accept0RTTData: c.Accept0RTTData,
|
Accept0RTTData: c.Accept0RTTData,
|
||||||
Max0RTTDataSize: c.Max0RTTDataSize,
|
Max0RTTDataSize: c.Max0RTTDataSize,
|
||||||
SessionTicketSealer: c.SessionTicketSealer,
|
SessionTicketSealer: c.SessionTicketSealer,
|
||||||
|
AcceptDelegatedCredential: c.AcceptDelegatedCredential,
|
||||||
|
GetDelegatedCredential: c.GetDelegatedCredential,
|
||||||
sessionTicketKeys: sessionTicketKeys,
|
sessionTicketKeys: sessionTicketKeys,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
conn.go
5
conn.go
@ -51,11 +51,14 @@ type Conn struct {
|
|||||||
didResume bool // whether this connection was a session resumption
|
didResume bool // whether this connection was a session resumption
|
||||||
cipherSuite uint16
|
cipherSuite uint16
|
||||||
ocspResponse []byte // stapled OCSP response
|
ocspResponse []byte // stapled OCSP response
|
||||||
scts [][]byte // signed certificate timestamps from server
|
scts [][]byte // Signed certificate timestamps from server
|
||||||
peerCertificates []*x509.Certificate
|
peerCertificates []*x509.Certificate
|
||||||
// verifiedChains contains the certificate chains that we built, as
|
// verifiedChains contains the certificate chains that we built, as
|
||||||
// opposed to the ones presented by the server.
|
// opposed to the ones presented by the server.
|
||||||
verifiedChains [][]*x509.Certificate
|
verifiedChains [][]*x509.Certificate
|
||||||
|
// verifiedDc is set by a client who negotiates the use of a valid delegated
|
||||||
|
// credential.
|
||||||
|
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
|
||||||
|
@ -14,6 +14,7 @@ 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"
|
||||||
@ -34,6 +35,7 @@ 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{} {
|
||||||
@ -137,6 +139,11 @@ 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)
|
||||||
|
@ -65,6 +65,7 @@ func makeClientHello(config *Config) (*clientHelloMsg, error) {
|
|||||||
supportedPoints: []uint8{pointFormatUncompressed},
|
supportedPoints: []uint8{pointFormatUncompressed},
|
||||||
nextProtoNeg: len(config.NextProtos) > 0,
|
nextProtoNeg: len(config.NextProtos) > 0,
|
||||||
secureRenegotiationSupported: true,
|
secureRenegotiationSupported: true,
|
||||||
|
delegatedCredential: config.AcceptDelegatedCredential,
|
||||||
alpnProtocols: config.NextProtos,
|
alpnProtocols: config.NextProtos,
|
||||||
}
|
}
|
||||||
possibleCipherSuites := config.cipherSuites()
|
possibleCipherSuites := config.cipherSuites()
|
||||||
@ -412,6 +413,50 @@ func (hs *clientHandshakeState) processCertsFromServer(certificates [][]byte) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processDelegatedCredentialFromServer unmarshals the DelegatedCredential
|
||||||
|
// offered by the server (if present) and validates it using the peer
|
||||||
|
// certificate.
|
||||||
|
func (hs *clientHandshakeState) processDelegatedCredentialFromServer(dc []byte) error {
|
||||||
|
c := hs.c
|
||||||
|
|
||||||
|
var cred *DelegatedCredential
|
||||||
|
var err error
|
||||||
|
if dc != nil {
|
||||||
|
|
||||||
|
// Assert that the DC extension was indicated by the client.
|
||||||
|
if !hs.hello.delegatedCredential {
|
||||||
|
c.sendAlert(alertUnexpectedMessage)
|
||||||
|
return errors.New("tls: got delegated credential extension without indication")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the DC was sent in the ServerHello in (and only in)
|
||||||
|
// version 1.2.
|
||||||
|
if hs.serverHello.delegatedCredential != nil && hs.serverHello.vers != VersionTLS12 {
|
||||||
|
c.sendAlert(alertIllegalParameter)
|
||||||
|
return errors.New("tls: ServerHello with delegated credential extension in TLS != 1.2")
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, err = UnmarshalDelegatedCredential(dc)
|
||||||
|
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 {
|
||||||
|
c.sendAlert(alertIllegalParameter)
|
||||||
|
return fmt.Errorf("delegated credential: %s", err)
|
||||||
|
} else if !v {
|
||||||
|
c.sendAlert(alertIllegalParameter)
|
||||||
|
return errors.New("delegated credential: signature invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.verifiedDc = cred
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (hs *clientHandshakeState) doFullHandshake() error {
|
func (hs *clientHandshakeState) doFullHandshake() error {
|
||||||
c := hs.c
|
c := hs.c
|
||||||
|
|
||||||
@ -432,6 +477,13 @@ func (hs *clientHandshakeState) doFullHandshake() error {
|
|||||||
if err := hs.processCertsFromServer(certMsg.certificates); err != nil {
|
if err := hs.processCertsFromServer(certMsg.certificates); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Validate the DC if present. The DC is only processed if the
|
||||||
|
// extension was indicated by the ClientHello; otherwise this call will
|
||||||
|
// result in an "illegal_parameter" alert. It also asserts that the DC
|
||||||
|
// was sent in the ServerHello if and only if TLS 1.2 is in use.
|
||||||
|
if err := hs.processDelegatedCredentialFromServer(hs.serverHello.delegatedCredential); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// This is a renegotiation handshake. We require that the
|
// This is a renegotiation handshake. We require that the
|
||||||
// server's identity (i.e. leaf certificate) is unchanged and
|
// server's identity (i.e. leaf certificate) is unchanged and
|
||||||
@ -477,10 +529,20 @@ func (hs *clientHandshakeState) doFullHandshake() error {
|
|||||||
|
|
||||||
keyAgreement := hs.suite.ka(c.vers)
|
keyAgreement := hs.suite.ka(c.vers)
|
||||||
|
|
||||||
|
// Set the public key used to verify the handshake.
|
||||||
|
pk := c.peerCertificates[0].PublicKey
|
||||||
|
|
||||||
|
// If the delegated credential extension has successfully been negotiated,
|
||||||
|
// then the ServerKeyExchange DelegatedCredential's private key.
|
||||||
|
if c.verifiedDc != nil {
|
||||||
|
pk = c.verifiedDc.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
skx, ok := msg.(*serverKeyExchangeMsg)
|
skx, ok := msg.(*serverKeyExchangeMsg)
|
||||||
if ok {
|
if ok {
|
||||||
hs.finishedHash.Write(skx.marshal())
|
hs.finishedHash.Write(skx.marshal())
|
||||||
err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0], skx)
|
|
||||||
|
err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, pk, skx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.sendAlert(alertUnexpectedMessage)
|
c.sendAlert(alertUnexpectedMessage)
|
||||||
return err
|
return err
|
||||||
@ -529,7 +591,7 @@ func (hs *clientHandshakeState) doFullHandshake() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preMasterSecret, ckx, err := keyAgreement.generateClientKeyExchange(c.config, hs.hello, c.peerCertificates[0])
|
preMasterSecret, ckx, err := keyAgreement.generateClientKeyExchange(c.config, hs.hello, pk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.sendAlert(alertInternalError)
|
c.sendAlert(alertInternalError)
|
||||||
return err
|
return err
|
||||||
|
@ -49,12 +49,14 @@ type clientHelloMsg struct {
|
|||||||
psks []psk
|
psks []psk
|
||||||
pskKeyExchangeModes []uint8
|
pskKeyExchangeModes []uint8
|
||||||
earlyData bool
|
earlyData bool
|
||||||
|
delegatedCredential bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers
|
// Function used for signature_algorithms and signature_algorithrms_cert
|
||||||
// Function used for signature_algorithms and signature_algorithrms_cert extensions only
|
// extensions only (for more details, see TLS 1.3 draft 28, 4.2.3).
|
||||||
// (for more details, see TLS 1.3 draft 28, 4.2.3)
|
//
|
||||||
// Function advances data slice and returns it, so that it can be used for further processing
|
// It advances data slice and returns it, so that it can be used for further
|
||||||
|
// processing
|
||||||
func marshalExtensionSignatureAlgorithms(extension uint16, data []byte, schemes []SignatureScheme) []byte {
|
func marshalExtensionSignatureAlgorithms(extension uint16, data []byte, schemes []SignatureScheme) []byte {
|
||||||
algNum := uint16(len(schemes))
|
algNum := uint16(len(schemes))
|
||||||
if algNum == 0 {
|
if algNum == 0 {
|
||||||
@ -126,7 +128,8 @@ func (m *clientHelloMsg) equal(i interface{}) bool {
|
|||||||
eqStrings(m.alpnProtocols, m1.alpnProtocols) &&
|
eqStrings(m.alpnProtocols, m1.alpnProtocols) &&
|
||||||
eqKeyShares(m.keyShares, m1.keyShares) &&
|
eqKeyShares(m.keyShares, m1.keyShares) &&
|
||||||
eqUint16s(m.supportedVersions, m1.supportedVersions) &&
|
eqUint16s(m.supportedVersions, m1.supportedVersions) &&
|
||||||
m.earlyData == m1.earlyData
|
m.earlyData == m1.earlyData &&
|
||||||
|
m.delegatedCredential == m1.delegatedCredential
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *clientHelloMsg) marshal() []byte {
|
func (m *clientHelloMsg) marshal() []byte {
|
||||||
@ -202,6 +205,9 @@ func (m *clientHelloMsg) marshal() []byte {
|
|||||||
if m.earlyData {
|
if m.earlyData {
|
||||||
numExtensions++
|
numExtensions++
|
||||||
}
|
}
|
||||||
|
if m.delegatedCredential {
|
||||||
|
numExtensions++
|
||||||
|
}
|
||||||
if numExtensions > 0 {
|
if numExtensions > 0 {
|
||||||
extensionsLength += 4 * numExtensions
|
extensionsLength += 4 * numExtensions
|
||||||
length += 2 + extensionsLength
|
length += 2 + extensionsLength
|
||||||
@ -419,6 +425,10 @@ func (m *clientHelloMsg) marshal() []byte {
|
|||||||
z[1] = byte(extensionEarlyData)
|
z[1] = byte(extensionEarlyData)
|
||||||
z = z[4:]
|
z = z[4:]
|
||||||
}
|
}
|
||||||
|
if m.delegatedCredential {
|
||||||
|
binary.BigEndian.PutUint16(z, extensionDelegatedCredential)
|
||||||
|
z = z[4:]
|
||||||
|
}
|
||||||
|
|
||||||
m.raw = x
|
m.raw = x
|
||||||
|
|
||||||
@ -483,6 +493,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) alert {
|
|||||||
m.psks = nil
|
m.psks = nil
|
||||||
m.pskKeyExchangeModes = nil
|
m.pskKeyExchangeModes = nil
|
||||||
m.earlyData = false
|
m.earlyData = false
|
||||||
|
m.delegatedCredential = false
|
||||||
|
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
// ClientHello is optionally followed by extension data
|
// ClientHello is optionally followed by extension data
|
||||||
@ -747,6 +758,9 @@ func (m *clientHelloMsg) unmarshal(data []byte) alert {
|
|||||||
case extensionEarlyData:
|
case extensionEarlyData:
|
||||||
// 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:
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-tls-subcerts
|
||||||
|
m.delegatedCredential = true
|
||||||
}
|
}
|
||||||
data = data[length:]
|
data = data[length:]
|
||||||
bindersOffset += length
|
bindersOffset += length
|
||||||
@ -775,6 +789,10 @@ type serverHelloMsg struct {
|
|||||||
secureRenegotiationSupported bool
|
secureRenegotiationSupported bool
|
||||||
alpnProtocol string
|
alpnProtocol string
|
||||||
|
|
||||||
|
// TLS 1.2. In TLS 1.3, the DC extension is included in of the end-entity
|
||||||
|
// certificate in the Certificate message.
|
||||||
|
delegatedCredential []byte
|
||||||
|
|
||||||
// TLS 1.3
|
// TLS 1.3
|
||||||
keyShare keyShare
|
keyShare keyShare
|
||||||
psk bool
|
psk bool
|
||||||
@ -810,6 +828,7 @@ func (m *serverHelloMsg) equal(i interface{}) bool {
|
|||||||
bytes.Equal(m.secureRenegotiation, m1.secureRenegotiation) &&
|
bytes.Equal(m.secureRenegotiation, m1.secureRenegotiation) &&
|
||||||
m.alpnProtocol == m1.alpnProtocol &&
|
m.alpnProtocol == m1.alpnProtocol &&
|
||||||
m.keyShare.group == m1.keyShare.group &&
|
m.keyShare.group == m1.keyShare.group &&
|
||||||
|
bytes.Equal(m.delegatedCredential, m1.delegatedCredential) &&
|
||||||
bytes.Equal(m.keyShare.data, m1.keyShare.data) &&
|
bytes.Equal(m.keyShare.data, m1.keyShare.data) &&
|
||||||
m.psk == m1.psk &&
|
m.psk == m1.psk &&
|
||||||
m.pskIdentity == m1.pskIdentity
|
m.pskIdentity == m1.pskIdentity
|
||||||
@ -863,6 +882,10 @@ func (m *serverHelloMsg) marshal() []byte {
|
|||||||
extensionsLength += 2 + sctLen
|
extensionsLength += 2 + sctLen
|
||||||
numExtensions++
|
numExtensions++
|
||||||
}
|
}
|
||||||
|
if dcLen := len(m.delegatedCredential); dcLen > 0 && m.vers == VersionTLS12 {
|
||||||
|
extensionsLength += 4 + dcLen
|
||||||
|
numExtensions++
|
||||||
|
}
|
||||||
if m.keyShare.group != 0 {
|
if m.keyShare.group != 0 {
|
||||||
extensionsLength += 4 + len(m.keyShare.data)
|
extensionsLength += 4 + len(m.keyShare.data)
|
||||||
numExtensions++
|
numExtensions++
|
||||||
@ -992,7 +1015,13 @@ func (m *serverHelloMsg) marshal() []byte {
|
|||||||
z = z[len(sct)+2:]
|
z = z[len(sct)+2:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if dcLen := len(m.delegatedCredential); dcLen > 0 && m.vers == VersionTLS12 {
|
||||||
|
binary.BigEndian.PutUint16(z, extensionDelegatedCredential)
|
||||||
|
binary.BigEndian.PutUint16(z[2:], uint16(dcLen))
|
||||||
|
z = z[4:]
|
||||||
|
copy(z, m.delegatedCredential)
|
||||||
|
z = z[dcLen:]
|
||||||
|
}
|
||||||
if m.keyShare.group != 0 {
|
if m.keyShare.group != 0 {
|
||||||
z[0] = uint8(extensionKeyShare >> 8)
|
z[0] = uint8(extensionKeyShare >> 8)
|
||||||
z[1] = uint8(extensionKeyShare)
|
z[1] = uint8(extensionKeyShare)
|
||||||
@ -1180,6 +1209,11 @@ func (m *serverHelloMsg) unmarshal(data []byte) alert {
|
|||||||
m.scts = append(m.scts, d[:sctLen])
|
m.scts = append(m.scts, d[:sctLen])
|
||||||
d = d[sctLen:]
|
d = d[sctLen:]
|
||||||
}
|
}
|
||||||
|
case extensionDelegatedCredential:
|
||||||
|
if m.vers != VersionTLS12 {
|
||||||
|
return alertUnexpectedMessage
|
||||||
|
}
|
||||||
|
m.delegatedCredential = data[:length]
|
||||||
case extensionKeyShare:
|
case extensionKeyShare:
|
||||||
d := data[:length]
|
d := data[:length]
|
||||||
|
|
||||||
@ -1425,6 +1459,7 @@ type certificateEntry struct {
|
|||||||
data []byte
|
data []byte
|
||||||
ocspStaple []byte
|
ocspStaple []byte
|
||||||
sctList [][]byte
|
sctList [][]byte
|
||||||
|
delegatedCredential []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type certificateMsg13 struct {
|
type certificateMsg13 struct {
|
||||||
@ -1446,6 +1481,7 @@ func (m *certificateMsg13) equal(i interface{}) bool {
|
|||||||
ok := bytes.Equal(m.certificates[i].data, m1.certificates[i].data)
|
ok := bytes.Equal(m.certificates[i].data, m1.certificates[i].data)
|
||||||
ok = ok && bytes.Equal(m.certificates[i].ocspStaple, m1.certificates[i].ocspStaple)
|
ok = ok && bytes.Equal(m.certificates[i].ocspStaple, m1.certificates[i].ocspStaple)
|
||||||
ok = ok && eqByteSlices(m.certificates[i].sctList, m1.certificates[i].sctList)
|
ok = ok && eqByteSlices(m.certificates[i].sctList, m1.certificates[i].sctList)
|
||||||
|
ok = ok && bytes.Equal(m.certificates[i].delegatedCredential, m1.certificates[i].delegatedCredential)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -1472,6 +1508,9 @@ func (m *certificateMsg13) marshal() (x []byte) {
|
|||||||
i += 2 + len(sct)
|
i += 2 + len(sct)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(cert.delegatedCredential) != 0 {
|
||||||
|
i += 4 + len(cert.delegatedCredential)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
length := 3 + 3*len(m.certificates) + i
|
length := 3 + 3*len(m.certificates) + i
|
||||||
@ -1546,6 +1585,15 @@ func (m *certificateMsg13) marshal() (x []byte) {
|
|||||||
sctLenPos[2] = uint8(sctLen >> 8)
|
sctLenPos[2] = uint8(sctLen >> 8)
|
||||||
sctLenPos[3] = uint8(sctLen)
|
sctLenPos[3] = uint8(sctLen)
|
||||||
}
|
}
|
||||||
|
if len(cert.delegatedCredential) != 0 {
|
||||||
|
binary.BigEndian.PutUint16(z, extensionDelegatedCredential)
|
||||||
|
binary.BigEndian.PutUint16(z[2:], uint16(len(cert.delegatedCredential)))
|
||||||
|
z = z[4:]
|
||||||
|
copy(z, cert.delegatedCredential)
|
||||||
|
z = z[len(cert.delegatedCredential):]
|
||||||
|
extensionLen += 4 + len(cert.delegatedCredential)
|
||||||
|
}
|
||||||
|
|
||||||
extLenPos[0] = uint8(extensionLen >> 8)
|
extLenPos[0] = uint8(extensionLen >> 8)
|
||||||
extLenPos[1] = uint8(extensionLen)
|
extLenPos[1] = uint8(extensionLen)
|
||||||
}
|
}
|
||||||
@ -1651,6 +1699,8 @@ func (m *certificateMsg13) unmarshal(data []byte) alert {
|
|||||||
m.certificates[i].sctList = append(m.certificates[i].sctList, body[2:2+sctLen])
|
m.certificates[i].sctList = append(m.certificates[i].sctList, body[2:2+sctLen])
|
||||||
body = body[2+sctLen:]
|
body = body[2+sctLen:]
|
||||||
}
|
}
|
||||||
|
case extensionDelegatedCredential:
|
||||||
|
m.certificates[i].delegatedCredential = body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,11 @@ type serverHandshakeState struct {
|
|||||||
clientHello *clientHelloMsg
|
clientHello *clientHelloMsg
|
||||||
hello *serverHelloMsg
|
hello *serverHelloMsg
|
||||||
cert *Certificate
|
cert *Certificate
|
||||||
|
privateKey crypto.PrivateKey
|
||||||
|
|
||||||
|
// A marshalled DelegatedCredential to be sent to the client in the
|
||||||
|
// handshake.
|
||||||
|
delegatedCredential []byte
|
||||||
|
|
||||||
// TLS 1.0-1.2 fields
|
// TLS 1.0-1.2 fields
|
||||||
ellipticOk bool
|
ellipticOk bool
|
||||||
@ -299,11 +304,48 @@ Curves:
|
|||||||
c.sendAlert(alertInternalError)
|
c.sendAlert(alertInternalError)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if hs.clientHello.scts && hs.hello != nil {
|
|
||||||
|
// Set the private key for this handshake to the certificate's secret key.
|
||||||
|
hs.privateKey = hs.cert.PrivateKey
|
||||||
|
|
||||||
|
if hs.clientHello.scts {
|
||||||
hs.hello.scts = hs.cert.SignedCertificateTimestamps
|
hs.hello.scts = hs.cert.SignedCertificateTimestamps
|
||||||
}
|
}
|
||||||
|
|
||||||
if priv, ok := hs.cert.PrivateKey.(crypto.Signer); ok {
|
// Set the private key to the DC private key if the client and server are
|
||||||
|
// willing to negotiate the delegated credential extension.
|
||||||
|
//
|
||||||
|
// Check to see if a DelegatedCredential is available and should be used.
|
||||||
|
// If one is available, the session is using TLS >= 1.2, and the client
|
||||||
|
// accepts the delegated credential extension, then set the handshake
|
||||||
|
// private key to the DC private key.
|
||||||
|
if c.config.GetDelegatedCredential != nil && hs.clientHello.delegatedCredential && c.vers >= VersionTLS12 {
|
||||||
|
dc, sk, err := c.config.GetDelegatedCredential(hs.clientHelloInfo(), c.vers)
|
||||||
|
if err != nil {
|
||||||
|
c.sendAlert(alertInternalError)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// For TLS 1.2, the DC is an extension to the ServerHello.
|
||||||
|
if c.vers == VersionTLS12 {
|
||||||
|
hs.hello.delegatedCredential = hs.delegatedCredential
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if priv, ok := hs.privateKey.(crypto.Signer); ok {
|
||||||
switch priv.Public().(type) {
|
switch priv.Public().(type) {
|
||||||
case *ecdsa.PublicKey:
|
case *ecdsa.PublicKey:
|
||||||
hs.ecdsaOk = true
|
hs.ecdsaOk = true
|
||||||
@ -314,7 +356,7 @@ Curves:
|
|||||||
return false, fmt.Errorf("tls: unsupported signing key type (%T)", priv.Public())
|
return false, fmt.Errorf("tls: unsupported signing key type (%T)", priv.Public())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if priv, ok := hs.cert.PrivateKey.(crypto.Decrypter); ok {
|
if priv, ok := hs.privateKey.(crypto.Decrypter); ok {
|
||||||
switch priv.Public().(type) {
|
switch priv.Public().(type) {
|
||||||
case *rsa.PublicKey:
|
case *rsa.PublicKey:
|
||||||
hs.rsaDecryptOk = true
|
hs.rsaDecryptOk = true
|
||||||
@ -479,7 +521,7 @@ func (hs *serverHandshakeState) doFullHandshake() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keyAgreement := hs.suite.ka(c.vers)
|
keyAgreement := hs.suite.ka(c.vers)
|
||||||
skx, err := keyAgreement.generateServerKeyExchange(c.config, hs.cert, hs.clientHello, hs.hello)
|
skx, err := keyAgreement.generateServerKeyExchange(c.config, hs.privateKey, hs.clientHello, hs.hello)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.sendAlert(alertHandshakeFailure)
|
c.sendAlert(alertHandshakeFailure)
|
||||||
return err
|
return err
|
||||||
@ -572,7 +614,7 @@ func (hs *serverHandshakeState) doFullHandshake() error {
|
|||||||
}
|
}
|
||||||
hs.finishedHash.Write(ckx.marshal())
|
hs.finishedHash.Write(ckx.marshal())
|
||||||
|
|
||||||
preMasterSecret, err := keyAgreement.processClientKeyExchange(c.config, hs.cert, ckx, c.vers)
|
preMasterSecret, err := keyAgreement.processClientKeyExchange(c.config, hs.privateKey, ckx, c.vers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == errClientKeyExchange {
|
if err == errClientKeyExchange {
|
||||||
c.sendAlert(alertDecodeError)
|
c.sendAlert(alertDecodeError)
|
||||||
@ -889,6 +931,7 @@ func (hs *serverHandshakeState) clientHelloInfo() *ClientHelloInfo {
|
|||||||
SupportedVersions: supportedVersions,
|
SupportedVersions: supportedVersions,
|
||||||
Conn: hs.c.conn,
|
Conn: hs.c.conn,
|
||||||
Offered0RTTData: hs.clientHello.earlyData,
|
Offered0RTTData: hs.clientHello.earlyData,
|
||||||
|
AcceptsDelegatedCredential: hs.clientHello.delegatedCredential,
|
||||||
Fingerprint: pskBinder,
|
Fingerprint: pskBinder,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
@ -25,11 +24,11 @@ var errServerKeyExchange = errors.New("tls: invalid ServerKeyExchange message")
|
|||||||
// encrypts the pre-master secret to the server's public key.
|
// encrypts the pre-master secret to the server's public key.
|
||||||
type rsaKeyAgreement struct{}
|
type rsaKeyAgreement struct{}
|
||||||
|
|
||||||
func (ka rsaKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
|
func (ka rsaKeyAgreement) generateServerKeyExchange(config *Config, sk crypto.PrivateKey, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ka rsaKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) {
|
func (ka rsaKeyAgreement) processClientKeyExchange(config *Config, sk crypto.PrivateKey, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) {
|
||||||
if len(ckx.ciphertext) < 2 {
|
if len(ckx.ciphertext) < 2 {
|
||||||
return nil, errClientKeyExchange
|
return nil, errClientKeyExchange
|
||||||
}
|
}
|
||||||
@ -42,7 +41,7 @@ func (ka rsaKeyAgreement) processClientKeyExchange(config *Config, cert *Certifi
|
|||||||
}
|
}
|
||||||
ciphertext = ckx.ciphertext[2:]
|
ciphertext = ckx.ciphertext[2:]
|
||||||
}
|
}
|
||||||
priv, ok := cert.PrivateKey.(crypto.Decrypter)
|
priv, ok := sk.(crypto.Decrypter)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("tls: certificate private key does not implement crypto.Decrypter")
|
return nil, errors.New("tls: certificate private key does not implement crypto.Decrypter")
|
||||||
}
|
}
|
||||||
@ -60,11 +59,11 @@ func (ka rsaKeyAgreement) processClientKeyExchange(config *Config, cert *Certifi
|
|||||||
return preMasterSecret, nil
|
return preMasterSecret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ka rsaKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
|
func (ka rsaKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, pk crypto.PublicKey, skx *serverKeyExchangeMsg) error {
|
||||||
return errors.New("tls: unexpected ServerKeyExchange")
|
return errors.New("tls: unexpected ServerKeyExchange")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ka rsaKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) {
|
func (ka rsaKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, pk crypto.PublicKey) ([]byte, *clientKeyExchangeMsg, error) {
|
||||||
preMasterSecret := make([]byte, 48)
|
preMasterSecret := make([]byte, 48)
|
||||||
preMasterSecret[0] = byte(clientHello.vers >> 8)
|
preMasterSecret[0] = byte(clientHello.vers >> 8)
|
||||||
preMasterSecret[1] = byte(clientHello.vers)
|
preMasterSecret[1] = byte(clientHello.vers)
|
||||||
@ -73,7 +72,7 @@ func (ka rsaKeyAgreement) generateClientKeyExchange(config *Config, clientHello
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypted, err := rsa.EncryptPKCS1v15(config.rand(), cert.PublicKey.(*rsa.PublicKey), preMasterSecret)
|
encrypted, err := rsa.EncryptPKCS1v15(config.rand(), pk.(*rsa.PublicKey), preMasterSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -156,7 +155,7 @@ type ecdheKeyAgreement struct {
|
|||||||
x, y *big.Int
|
x, y *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
|
func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, sk crypto.PrivateKey, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
|
||||||
preferredCurves := config.curvePreferences()
|
preferredCurves := config.curvePreferences()
|
||||||
|
|
||||||
NextCandidate:
|
NextCandidate:
|
||||||
@ -207,7 +206,7 @@ NextCandidate:
|
|||||||
serverECDHParams[3] = byte(len(ecdhePublic))
|
serverECDHParams[3] = byte(len(ecdhePublic))
|
||||||
copy(serverECDHParams[4:], ecdhePublic)
|
copy(serverECDHParams[4:], ecdhePublic)
|
||||||
|
|
||||||
priv, ok := cert.PrivateKey.(crypto.Signer)
|
priv, ok := sk.(crypto.Signer)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("tls: certificate private key does not implement crypto.Signer")
|
return nil, errors.New("tls: certificate private key does not implement crypto.Signer")
|
||||||
}
|
}
|
||||||
@ -255,7 +254,7 @@ NextCandidate:
|
|||||||
return skx, nil
|
return skx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ka *ecdheKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) {
|
func (ka *ecdheKeyAgreement) processClientKeyExchange(config *Config, sk crypto.PrivateKey, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) {
|
||||||
if len(ckx.ciphertext) == 0 || int(ckx.ciphertext[0]) != len(ckx.ciphertext)-1 {
|
if len(ckx.ciphertext) == 0 || int(ckx.ciphertext[0]) != len(ckx.ciphertext)-1 {
|
||||||
return nil, errClientKeyExchange
|
return nil, errClientKeyExchange
|
||||||
}
|
}
|
||||||
@ -291,7 +290,7 @@ func (ka *ecdheKeyAgreement) processClientKeyExchange(config *Config, cert *Cert
|
|||||||
return preMasterSecret, nil
|
return preMasterSecret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
|
func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, pk crypto.PublicKey, skx *serverKeyExchangeMsg) error {
|
||||||
if len(skx.key) < 4 {
|
if len(skx.key) < 4 {
|
||||||
return errServerKeyExchange
|
return errServerKeyExchange
|
||||||
}
|
}
|
||||||
@ -337,7 +336,7 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell
|
|||||||
return errServerKeyExchange
|
return errServerKeyExchange
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, sigType, hashFunc, err := pickSignatureAlgorithm(cert.PublicKey, []SignatureScheme{signatureAlgorithm}, clientHello.supportedSignatureAlgorithms, ka.version)
|
_, sigType, hashFunc, err := pickSignatureAlgorithm(pk, []SignatureScheme{signatureAlgorithm}, clientHello.supportedSignatureAlgorithms, ka.version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -355,10 +354,10 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return verifyHandshakeSignature(sigType, cert.PublicKey, hashFunc, digest, sig)
|
return verifyHandshakeSignature(sigType, pk, hashFunc, digest, sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ka *ecdheKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) {
|
func (ka *ecdheKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, pk crypto.PublicKey) ([]byte, *clientKeyExchangeMsg, error) {
|
||||||
if ka.curveid == 0 {
|
if ka.curveid == 0 {
|
||||||
return nil, nil, errors.New("tls: missing ServerKeyExchange message")
|
return nil, nil, errors.New("tls: missing ServerKeyExchange message")
|
||||||
}
|
}
|
||||||
|
521
subcerts.go
Normal file
521
subcerts.go
Normal file
@ -0,0 +1,521 @@
|
|||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
// Delegated credentials for TLS
|
||||||
|
// (https://tools.ietf.org/html/draft-ietf-tls-subcerts) is an IETF Internet
|
||||||
|
// draft and proposed TLS extension. If the client 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).
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dcMaxTTLSeconds = 60 * 60 * 24 * 7 // 7 days
|
||||||
|
dcMaxTTL = time.Duration(dcMaxTTLSeconds * time.Second)
|
||||||
|
dcMaxPublicKeyLen = 1 << 16 // Bytes
|
||||||
|
dcMaxSignatureLen = 1 << 16 // Bytes
|
||||||
|
)
|
||||||
|
|
||||||
|
var errNoDelegationUsage = errors.New("certificate not authorized for delegation")
|
||||||
|
|
||||||
|
// delegationUsageId is the DelegationUsage X.509 extension 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}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// Check that the digitalSignature key usage is set.
|
||||||
|
if (cert.KeyUsage & x509.KeyUsageDigitalSignature) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the certificate has the DelegationUsage extension and that
|
||||||
|
// it's non-critical (per the spec).
|
||||||
|
for _, extension := range cert.Extensions {
|
||||||
|
if extension.Id.Equal(delegationUsageId) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// This structure stores the public components of a credential.
|
||||||
|
type credential struct {
|
||||||
|
validTime time.Duration
|
||||||
|
publicKey crypto.PublicKey
|
||||||
|
scheme SignatureScheme
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalSubjectPublicKeyInfo returns a DER encoded SubjectPublicKeyInfo structure
|
||||||
|
// (as defined in the X.509 standard) for the credential.
|
||||||
|
func (cred *credential) marshalSubjectPublicKeyInfo() ([]byte, error) {
|
||||||
|
switch cred.scheme {
|
||||||
|
case ECDSAWithP256AndSHA256,
|
||||||
|
ECDSAWithP384AndSHA384,
|
||||||
|
ECDSAWithP521AndSHA512:
|
||||||
|
serializedPublicKey, err := x509.MarshalPKIXPublicKey(cred.publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return serializedPublicKey, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported signature scheme: 0x%04x", cred.scheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal encodes a credential as per the spec.
|
||||||
|
func (cred *credential) marshal() ([]byte, error) {
|
||||||
|
// Write the valid_time field.
|
||||||
|
serialized := make([]byte, 6)
|
||||||
|
binary.BigEndian.PutUint32(serialized, uint32(cred.validTime/time.Second))
|
||||||
|
|
||||||
|
// Encode the public key and assert that the encoding is no longer than 2^16
|
||||||
|
// bytes (per the spect).
|
||||||
|
serializedPublicKey, err := cred.marshalSubjectPublicKeyInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(serializedPublicKey) > dcMaxPublicKeyLen {
|
||||||
|
return nil, errors.New("public key is too long")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the length of the public_key field.
|
||||||
|
binary.BigEndian.PutUint16(serialized[4:], uint16(len(serializedPublicKey)))
|
||||||
|
|
||||||
|
// Write the public key.
|
||||||
|
return append(serialized, serializedPublicKey...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshalCredential decodes a credential and returns it.
|
||||||
|
func unmarshalCredential(serialized []byte) (*credential, error) {
|
||||||
|
// Bytes 0-3 are the validity time field; bytes 4-6 are the length of the
|
||||||
|
// serialized SubjectPublicKeyInfo.
|
||||||
|
if len(serialized) < 6 {
|
||||||
|
return nil, errors.New("credential is too short")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the validity time.
|
||||||
|
validTime := time.Duration(binary.BigEndian.Uint32(serialized)) * time.Second
|
||||||
|
|
||||||
|
// Parse the SubjectPublicKeyInfo.
|
||||||
|
pk, scheme, err := unmarshalSubjectPublicKeyInfo(serialized[6:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &credential{validTime, pk, scheme}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshalSubjectPublicKeyInfo parses a DER encoded SubjectPublicKeyInfo
|
||||||
|
// structure into a public key and its corresponding algorithm.
|
||||||
|
func unmarshalSubjectPublicKeyInfo(serialized []byte) (crypto.PublicKey, SignatureScheme, error) {
|
||||||
|
publicKey, err := x509.ParsePKIXPublicKey(serialized)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch pk := publicKey.(type) {
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
curveName := pk.Curve.Params().Name
|
||||||
|
if curveName == "P-256" {
|
||||||
|
return pk, ECDSAWithP256AndSHA256, nil
|
||||||
|
} else if curveName == "P-384" {
|
||||||
|
return pk, ECDSAWithP384AndSHA384, nil
|
||||||
|
} else if curveName == "P-521" {
|
||||||
|
return pk, ECDSAWithP521AndSHA512, nil
|
||||||
|
} else {
|
||||||
|
return nil, 0, fmt.Errorf("curve %s s not supported", curveName)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, 0, fmt.Errorf("unsupported delgation key type: %T", pk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCredentialLen returns the number of bytes comprising the serialized
|
||||||
|
// credential that starts at the beginning of the input slice. It returns an
|
||||||
|
// error if the input is too short to contain a credential.
|
||||||
|
func getCredentialLen(serialized []byte) (int, error) {
|
||||||
|
if len(serialized) < 6 {
|
||||||
|
return 0, errors.New("credential is too short")
|
||||||
|
}
|
||||||
|
// First 4 bytes is the validity time.
|
||||||
|
serialized = serialized[4:]
|
||||||
|
|
||||||
|
// The next 2 bytes are the length of the serialized public key.
|
||||||
|
serializedPublicKeyLen := int(binary.BigEndian.Uint16(serialized))
|
||||||
|
serialized = serialized[2:]
|
||||||
|
|
||||||
|
if len(serialized) < serializedPublicKeyLen {
|
||||||
|
return 0, errors.New("public key of credential is too short")
|
||||||
|
}
|
||||||
|
|
||||||
|
return 6 + serializedPublicKeyLen, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelegatedCredential stores a credential and its delegation.
|
||||||
|
type DelegatedCredential struct {
|
||||||
|
// The serialized form of the credential.
|
||||||
|
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 signature scheme used to sign the credential.
|
||||||
|
Scheme SignatureScheme
|
||||||
|
|
||||||
|
// The credential's delegation.
|
||||||
|
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.
|
||||||
|
var err error
|
||||||
|
if cert.Leaf == nil {
|
||||||
|
if len(cert.Certificate[0]) == 0 {
|
||||||
|
return nil, nil, errors.New("missing leaf certificate")
|
||||||
|
}
|
||||||
|
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the leaf certificate can be used for delegation.
|
||||||
|
if !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
|
||||||
|
// 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
|
||||||
|
// used for delegation.
|
||||||
|
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) {
|
||||||
|
return false, errors.New("credential has expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
if dc.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)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(any) This code overlaps signficantly with verifyHandshakeSignature()
|
||||||
|
// in ../auth.go. This should be refactored.
|
||||||
|
switch dc.Scheme {
|
||||||
|
case ECDSAWithP256AndSHA256,
|
||||||
|
ECDSAWithP384AndSHA384,
|
||||||
|
ECDSAWithP521AndSHA512:
|
||||||
|
pk, ok := cert.PublicKey.(*ecdsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
return false, errors.New("expected ECDSA public key")
|
||||||
|
}
|
||||||
|
sig := new(ecdsaSignature)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// Get the length of the serialized credential that begins at the start of
|
||||||
|
// the input slice.
|
||||||
|
serializedCredentialLen, err := getCredentialLen(serialized)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the credential.
|
||||||
|
cred, err := unmarshalCredential(serialized[:serializedCredentialLen])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the signature scheme.
|
||||||
|
serialized = serialized[serializedCredentialLen:]
|
||||||
|
if len(serialized) < 4 {
|
||||||
|
return nil, errors.New("delegated credential is too short")
|
||||||
|
}
|
||||||
|
scheme := SignatureScheme(binary.BigEndian.Uint16(serialized))
|
||||||
|
|
||||||
|
// Parse the signature length.
|
||||||
|
serialized = serialized[2:]
|
||||||
|
serializedSignatureLen := binary.BigEndian.Uint16(serialized)
|
||||||
|
|
||||||
|
// Prase the signature.
|
||||||
|
serialized = serialized[2:]
|
||||||
|
if len(serialized) < int(serializedSignatureLen) {
|
||||||
|
return nil, errors.New("signature of delegated credential is too short")
|
||||||
|
}
|
||||||
|
sig := serialized[:serializedSignatureLen]
|
||||||
|
|
||||||
|
return &DelegatedCredential{
|
||||||
|
ValidTime: cred.validTime,
|
||||||
|
PublicKey: cred.publicKey,
|
||||||
|
publicKeyScheme: cred.scheme,
|
||||||
|
Scheme: scheme,
|
||||||
|
Signature: sig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCurve maps the SignatureScheme to its corresponding elliptic.Curve.
|
||||||
|
func getCurve(scheme SignatureScheme) elliptic.Curve {
|
||||||
|
switch scheme {
|
||||||
|
case ECDSAWithP256AndSHA256:
|
||||||
|
return elliptic.P256()
|
||||||
|
case ECDSAWithP384AndSHA384:
|
||||||
|
return elliptic.P384()
|
||||||
|
case ECDSAWithP521AndSHA512:
|
||||||
|
return elliptic.P521()
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHash maps the SignatureScheme to its corresponding hash function.
|
||||||
|
//
|
||||||
|
// TODO(any) This function overlaps with hashForSignatureScheme in 13.go.
|
||||||
|
func getHash(scheme SignatureScheme) crypto.Hash {
|
||||||
|
switch scheme {
|
||||||
|
case ECDSAWithP256AndSHA256:
|
||||||
|
return crypto.SHA256
|
||||||
|
case ECDSAWithP384AndSHA384:
|
||||||
|
return crypto.SHA384
|
||||||
|
case ECDSAWithP521AndSHA512:
|
||||||
|
return crypto.SHA512
|
||||||
|
default:
|
||||||
|
return 0 // Unknown hash function
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareDelegation returns a hash of the message that the delegator is to
|
||||||
|
// sign. The inputs are the credential (cred), the DER-encoded delegator
|
||||||
|
// certificate (`delegatorCert`), the signature scheme of the delegator
|
||||||
|
// (`delegatorScheme`), and the protocol version (`vers`) in which the credential
|
||||||
|
// is to be used.
|
||||||
|
func prepareDelegation(hash crypto.Hash, cred *credential, delegatorCert []byte, delegatorScheme SignatureScheme, vers uint16) ([]byte, error) {
|
||||||
|
h := hash.New()
|
||||||
|
|
||||||
|
// The header.
|
||||||
|
h.Write(bytes.Repeat([]byte{0x20}, 64))
|
||||||
|
h.Write([]byte("TLS, server delegated credentials"))
|
||||||
|
h.Write([]byte{0x00})
|
||||||
|
|
||||||
|
// The protocol version.
|
||||||
|
var serializedVers [2]byte
|
||||||
|
binary.BigEndian.PutUint16(serializedVers[:], uint16(vers))
|
||||||
|
h.Write(serializedVers[:])
|
||||||
|
|
||||||
|
// The delegation certificate.
|
||||||
|
h.Write(delegatorCert)
|
||||||
|
|
||||||
|
// The delegator signature scheme.
|
||||||
|
var serializedScheme [2]byte
|
||||||
|
binary.BigEndian.PutUint16(serializedScheme[:], uint16(delegatorScheme))
|
||||||
|
h.Write(serializedScheme[:])
|
||||||
|
|
||||||
|
// The credential.
|
||||||
|
serializedCred, err := cred.marshal()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h.Write(serializedCred)
|
||||||
|
|
||||||
|
return h.Sum(nil), nil
|
||||||
|
}
|
500
subcerts_test.go
Normal file
500
subcerts_test.go
Normal file
@ -0,0 +1,500 @@
|
|||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dcWithPrivateKey stores a delegated credential and its corresponding private
|
||||||
|
// key.
|
||||||
|
type dcWithPrivateKey struct {
|
||||||
|
*DelegatedCredential
|
||||||
|
privateKey crypto.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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-----
|
||||||
|
MIIBdzCCAR2gAwIBAgIQLVIvEpo0/0TzRja4ImvB1TAKBggqhkjOPQQDAjASMRAw
|
||||||
|
DgYDVQQKEwdBY21lIENvMB4XDTE4MDcwMzE2NTE1M1oXDTE5MDcwMzE2NTE1M1ow
|
||||||
|
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOhB
|
||||||
|
U6adaAgliLaFc1PAo9HBO4Wish1G4df3IK5EXLy+ooYfmkfzT1FxqbNLZufNYzve
|
||||||
|
25fmpal/1VJAjpVyKq2jVTBTMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
|
||||||
|
BgEFBQcDATAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwDQYJKwYBBAGC
|
||||||
|
2kssBAAwCgYIKoZIzj0EAwIDSAAwRQIhAPNwRk6cygm6zO5rjOzohKYWS+1KuWCM
|
||||||
|
OetDIvU4mdyoAiAGN97y3GJccYn9ZOJS4UOqhr9oO8PuZMLgdq4OrMRiiA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`
|
||||||
|
|
||||||
|
var delegatorKeyPEM = `-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MHcCAQEEIJDVlo+sJolMcNjMkfCGDUjMJcE4UgclcXGCrOtbJAi2oAoGCCqGSM49
|
||||||
|
AwEHoUQDQgAE6EFTpp1oCCWItoVzU8Cj0cE7haKyHUbh1/cgrkRcvL6ihh+aR/NP
|
||||||
|
UXGps0tm581jO97bl+alqX/VUkCOlXIqrQ==
|
||||||
|
-----END EC PRIVATE KEY-----
|
||||||
|
`
|
||||||
|
|
||||||
|
var nonDelegatorCertPEM = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBaTCCAQ6gAwIBAgIQSUo+9uaip3qCW+1EPeHZgDAKBggqhkjOPQQDAjASMRAw
|
||||||
|
DgYDVQQKEwdBY21lIENvMB4XDTE4MDYxMjIzNDAyNloXDTE5MDYxMjIzNDAyNlow
|
||||||
|
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLf7
|
||||||
|
fiznPVdc3V5mM3ymswU2/IoJaq/deA6dgdj50ozdYyRiAPjxzcz9zRsZw1apTF/h
|
||||||
|
yNfiLhV4EE1VrwXcT5OjRjBEMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
|
||||||
|
BgEFBQcDATAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZIzj0E
|
||||||
|
AwIDSQAwRgIhANXG0zmrVtQBK0TNZZoEGMOtSwxmiZzXNe+IjdpxO3TiAiEA5VYx
|
||||||
|
0CWJq5zqpVXbJMeKVMASo2nrXZoA6NhJvFQ97hw=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`
|
||||||
|
|
||||||
|
var nonDelegatorKeyPEM = `-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MHcCAQEEIMw9DiOfGI1E/XZrrW2huZSjYi0EKwvVjAe+dYtyFsSloAoGCCqGSM49
|
||||||
|
AwEHoUQDQgAEt/t+LOc9V1zdXmYzfKazBTb8iglqr914Dp2B2PnSjN1jJGIA+PHN
|
||||||
|
zP3NGxnDVqlMX+HI1+IuFXgQTVWvBdxPkw==
|
||||||
|
-----END EC PRIVATE KEY-----
|
||||||
|
`
|
||||||
|
|
||||||
|
// Invalid TLS versions used for testing purposes.
|
||||||
|
const (
|
||||||
|
versionInvalidDC uint16 = 0xff00
|
||||||
|
versionMalformedDC12 uint16 = 0xff12
|
||||||
|
versionMalformedDC13 uint16 = 0xff13
|
||||||
|
)
|
||||||
|
|
||||||
|
var dcTestConfig *Config
|
||||||
|
var dcTestCerts map[string]*Certificate
|
||||||
|
var dcTestDCs map[uint16]dcWithPrivateKey
|
||||||
|
var dcNow time.Time
|
||||||
|
var dcTestDCScheme = ECDSAWithP521AndSHA512
|
||||||
|
var dcTestDCVersions = []uint16{
|
||||||
|
VersionTLS12,
|
||||||
|
VersionTLS13,
|
||||||
|
VersionTLS13Draft23,
|
||||||
|
versionInvalidDC,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
// Use a static time for testing at whcih time the test certificates are
|
||||||
|
// valid.
|
||||||
|
dcNow = time.Date(2018, 07, 03, 18, 0, 0, 234234, time.UTC)
|
||||||
|
|
||||||
|
dcTestConfig = &Config{
|
||||||
|
Time: func() time.Time {
|
||||||
|
return dcNow
|
||||||
|
},
|
||||||
|
Rand: zeroSource{},
|
||||||
|
Certificates: nil,
|
||||||
|
MinVersion: VersionTLS10,
|
||||||
|
MaxVersion: VersionTLS13Draft22,
|
||||||
|
CipherSuites: allCipherSuites(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// The certificates of the server.
|
||||||
|
dcTestCerts = make(map[string]*Certificate)
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// The delegation certificate.
|
||||||
|
dcCert := new(Certificate)
|
||||||
|
*dcCert, err = X509KeyPair([]byte(delegatorCertPEM), []byte(delegatorKeyPEM))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dcCert.Leaf, err = x509.ParseCertificate(dcCert.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dcTestCerts["dc"] = dcCert
|
||||||
|
|
||||||
|
// The standard certificate.
|
||||||
|
ndcCert := new(Certificate)
|
||||||
|
*ndcCert, err = X509KeyPair([]byte(nonDelegatorCertPEM), []byte(nonDelegatorKeyPEM))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ndcCert.Leaf, err = x509.ParseCertificate(ndcCert.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dcTestCerts["no dc"] = ndcCert
|
||||||
|
|
||||||
|
// The root certificates for the client.
|
||||||
|
dcTestConfig.RootCAs = x509.NewCertPool()
|
||||||
|
|
||||||
|
dcRoot, err := x509.ParseCertificate(dcCert.Certificate[len(dcCert.Certificate)-1])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dcTestConfig.RootCAs.AddCert(dcRoot)
|
||||||
|
|
||||||
|
ndcRoot, err := x509.ParseCertificate(ndcCert.Certificate[len(ndcCert.Certificate)-1])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dcTestConfig.RootCAs.AddCert(ndcRoot)
|
||||||
|
|
||||||
|
// A pool of DCs.
|
||||||
|
dcTestDCs = make(map[uint16]dcWithPrivateKey)
|
||||||
|
for _, vers := range dcTestDCVersions {
|
||||||
|
dc, sk, err := NewDelegatedCredential(dcCert, dcTestDCScheme, dcNow.Sub(dcCert.Leaf.NotBefore)+dcMaxTTL, vers)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dcTestDCs[vers] = dcWithPrivateKey{dc, sk}
|
||||||
|
}
|
||||||
|
// Add two DCs with invalid private keys, one for TLS 1.2 and another for
|
||||||
|
// 1.3.
|
||||||
|
malformedDC12 := new(DelegatedCredential)
|
||||||
|
*malformedDC12 = *dcTestDCs[VersionTLS12].DelegatedCredential
|
||||||
|
dcTestDCs[versionMalformedDC12] = dcWithPrivateKey{
|
||||||
|
malformedDC12,
|
||||||
|
dcTestDCs[versionInvalidDC].privateKey,
|
||||||
|
}
|
||||||
|
malformedDC13 := new(DelegatedCredential)
|
||||||
|
*malformedDC13 = *dcTestDCs[VersionTLS13].DelegatedCredential
|
||||||
|
dcTestDCs[versionMalformedDC13] = dcWithPrivateKey{
|
||||||
|
malformedDC13,
|
||||||
|
dcTestDCs[versionInvalidDC].privateKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkECDSAPublicKeysEqual(
|
||||||
|
publicKey, publicKey2 crypto.PublicKey, scheme SignatureScheme) error {
|
||||||
|
|
||||||
|
curve := getCurve(scheme)
|
||||||
|
pk := publicKey.(*ecdsa.PublicKey)
|
||||||
|
pk2 := publicKey2.(*ecdsa.PublicKey)
|
||||||
|
serializedPublicKey := elliptic.Marshal(curve, pk.X, pk.Y)
|
||||||
|
serializedPublicKey2 := elliptic.Marshal(curve, pk2.X, pk2.Y)
|
||||||
|
if !bytes.Equal(serializedPublicKey2, serializedPublicKey) {
|
||||||
|
return errors.New("PublicKey mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that cred and cred2 are equal.
|
||||||
|
func checkCredentialsEqual(dc, dc2 *DelegatedCredential) error {
|
||||||
|
if dc2.ValidTime != dc.ValidTime {
|
||||||
|
return fmt.Errorf("ValidTime mismatch: got %d; want %d", dc2.ValidTime, dc.ValidTime)
|
||||||
|
}
|
||||||
|
if dc2.publicKeyScheme != dc.publicKeyScheme {
|
||||||
|
return fmt.Errorf("scheme mismatch: got %04x; want %04x", dc2.publicKeyScheme, dc.publicKeyScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkECDSAPublicKeysEqual(dc.PublicKey, dc2.PublicKey, dc.publicKeyScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test delegation and validation of credentials.
|
||||||
|
func TestDelegateValidate(t *testing.T) {
|
||||||
|
ver := uint16(VersionTLS12)
|
||||||
|
cert := dcTestCerts["dc"]
|
||||||
|
|
||||||
|
validTime := dcNow.Sub(cert.Leaf.NotBefore) + dcMaxTTL
|
||||||
|
shortValidTime := dcNow.Sub(cert.Leaf.NotBefore) + time.Second
|
||||||
|
|
||||||
|
delegatedCred, _, err := NewDelegatedCredential(cert, ECDSAWithP256AndSHA256, validTime, ver)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test validation of good DC.
|
||||||
|
if v, err := delegatedCred.Validate(cert.Leaf, ver, dcNow); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if !v {
|
||||||
|
t.Error("good DC is invalid; want valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test validation of expired DC.
|
||||||
|
tooLate := dcNow.Add(dcMaxTTL).Add(time.Nanosecond)
|
||||||
|
if v, err := delegatedCred.Validate(cert.Leaf, ver, tooLate); err == nil {
|
||||||
|
t.Error("expired DC validation succeeded; want failure")
|
||||||
|
} else if v {
|
||||||
|
t.Error("expired DC is valid; want invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test protocol binding.
|
||||||
|
if v, err := delegatedCred.Validate(cert.Leaf, VersionSSL30, dcNow); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if v {
|
||||||
|
t.Error("DC with wrong version is valid; want invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test signature algorithm binding.
|
||||||
|
delegatedCred.Scheme = ECDSAWithP521AndSHA512
|
||||||
|
if v, err := delegatedCred.Validate(cert.Leaf, ver, dcNow); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if v {
|
||||||
|
t.Error("DC with wrong scheme is valid; want invalid")
|
||||||
|
}
|
||||||
|
delegatedCred.Scheme = ECDSAWithP256AndSHA256
|
||||||
|
|
||||||
|
// Test delegation cedrtificate binding.
|
||||||
|
cert.Leaf.Raw[0] ^= byte(42)
|
||||||
|
if v, err := delegatedCred.Validate(cert.Leaf, ver, dcNow); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if v {
|
||||||
|
t.Error("DC with wrong cert is valid; want invalid")
|
||||||
|
}
|
||||||
|
cert.Leaf.Raw[0] ^= byte(42)
|
||||||
|
|
||||||
|
// Test validation of DC who's TTL is too long.
|
||||||
|
delegatedCred2, _, err := NewDelegatedCredential(cert, ECDSAWithP256AndSHA256, validTime+time.Second, ver)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if v, err := delegatedCred2.Validate(cert.Leaf, ver, dcNow); err == nil {
|
||||||
|
t.Error("DC validation with long TTL succeeded; want failure")
|
||||||
|
} else if v {
|
||||||
|
t.Error("DC with long TTL is valid; want invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test validation of DC who's TTL is short.
|
||||||
|
delegatedCred3, _, err := NewDelegatedCredential(cert, ECDSAWithP256AndSHA256, shortValidTime, ver)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if v, err := delegatedCred3.Validate(cert.Leaf, ver, dcNow); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if !v {
|
||||||
|
t.Error("good DC is invalid; want valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test validation of DC using a certificate that can't delegate.
|
||||||
|
if v, err := delegatedCred.Validate(
|
||||||
|
dcTestCerts["no dc"].Leaf, ver, dcNow); err != errNoDelegationUsage {
|
||||||
|
t.Error("DC validation with non-delegation cert succeeded; want failure")
|
||||||
|
} else if v {
|
||||||
|
t.Error("DC with non-delegation cert is valid; want invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encoding/decoding of delegated credentials.
|
||||||
|
func TestDelegatedCredentialMarshalUnmarshal(t *testing.T) {
|
||||||
|
cert := dcTestCerts["dc"]
|
||||||
|
delegatedCred, _, err := NewDelegatedCredential(cert,
|
||||||
|
ECDSAWithP256AndSHA256,
|
||||||
|
dcNow.Sub(cert.Leaf.NotBefore)+dcMaxTTL,
|
||||||
|
VersionTLS12)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serialized, err := delegatedCred.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
delegatedCred2, err := UnmarshalDelegatedCredential(serialized)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkCredentialsEqual(delegatedCred, delegatedCred2)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if delegatedCred.Scheme != delegatedCred2.Scheme {
|
||||||
|
t.Errorf("scheme mismatch: got %04x; want %04x",
|
||||||
|
delegatedCred2.Scheme, delegatedCred.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(delegatedCred2.Signature, delegatedCred.Signature) {
|
||||||
|
t.Error("Signature mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
|
||||||
|
ln := newLocalListener(t)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
srvCh := make(chan *Conn, 1)
|
||||||
|
var serr error
|
||||||
|
go func() {
|
||||||
|
sconn, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
serr = err
|
||||||
|
srvCh <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srv := Server(sconn, serverConfig)
|
||||||
|
if err := srv.Handshake(); err != nil {
|
||||||
|
serr = fmt.Errorf("handshake: %v", err)
|
||||||
|
srvCh <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srvCh <- srv
|
||||||
|
}()
|
||||||
|
|
||||||
|
cli, err := Dial("tcp", ln.Addr().String(), clientConfig)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
|
||||||
|
srv := <-srvCh
|
||||||
|
if srv == nil {
|
||||||
|
return false, serr
|
||||||
|
}
|
||||||
|
|
||||||
|
bufLen := len(clientMsg)
|
||||||
|
if len(serverMsg) > len(clientMsg) {
|
||||||
|
bufLen = len(serverMsg)
|
||||||
|
}
|
||||||
|
buf := make([]byte, bufLen)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks that the client suppports a version >= 1.2 and accepts delegated
|
||||||
|
// credentials. If so, it returns the delegation certificate; otherwise it
|
||||||
|
// returns a plain certificate.
|
||||||
|
func testServerGetCertificate(ch *ClientHelloInfo) (*Certificate, error) {
|
||||||
|
versOk := false
|
||||||
|
for _, vers := range ch.SupportedVersions {
|
||||||
|
versOk = versOk || (vers >= uint16(VersionTLS12))
|
||||||
|
}
|
||||||
|
|
||||||
|
if versOk && ch.AcceptsDelegatedCredential {
|
||||||
|
return dcTestCerts["dc"], nil
|
||||||
|
}
|
||||||
|
return dcTestCerts["no dc"], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks that the ciient supports the signature algorithm supported by the test
|
||||||
|
// server, and that the server has a DC for the selected protocol version.
|
||||||
|
func testServerGetDC(ch *ClientHelloInfo, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) {
|
||||||
|
schemeOk := false
|
||||||
|
for _, scheme := range ch.SignatureSchemes {
|
||||||
|
schemeOk = schemeOk || (scheme == dcTestDCScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
versOk := false
|
||||||
|
for _, testVers := range dcTestDCVersions {
|
||||||
|
versOk = versOk || (vers == testVers)
|
||||||
|
}
|
||||||
|
|
||||||
|
if schemeOk && versOk && ch.AcceptsDelegatedCredential {
|
||||||
|
d := dcTestDCs[vers]
|
||||||
|
return d.DelegatedCredential, d.privateKey, nil
|
||||||
|
}
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a DC signed with a bad version number.
|
||||||
|
func testServerGetInvalidDC(ch *ClientHelloInfo, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) {
|
||||||
|
d := dcTestDCs[versionInvalidDC]
|
||||||
|
return d.DelegatedCredential, d.privateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a DC with the wrong private key.
|
||||||
|
func testServerGetMalformedDC(ch *ClientHelloInfo, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) {
|
||||||
|
if vers == VersionTLS12 {
|
||||||
|
d := dcTestDCs[versionMalformedDC12]
|
||||||
|
return d.DelegatedCredential, d.privateKey, nil
|
||||||
|
} else if vers == VersionTLS13 {
|
||||||
|
d := dcTestDCs[versionMalformedDC13]
|
||||||
|
return d.DelegatedCredential, d.privateKey, nil
|
||||||
|
} else {
|
||||||
|
return nil, nil, fmt.Errorf("testServerGetMalformedDC: unsupported version %x", vers)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var dcTests = []struct {
|
||||||
|
clientDC bool
|
||||||
|
serverDC bool
|
||||||
|
clientSkipVerify bool
|
||||||
|
clientMaxVers uint16
|
||||||
|
serverMaxVers uint16
|
||||||
|
useMalformedDC bool
|
||||||
|
useInvalidDC bool
|
||||||
|
expectSuccess bool
|
||||||
|
expectDC bool
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{true, true, false, VersionTLS12, VersionTLS12, false, false, true, true, "tls12"},
|
||||||
|
{true, true, false, VersionTLS13, VersionTLS13, false, false, true, true, "tls13"},
|
||||||
|
{true, true, false, VersionTLS12, VersionTLS12, true, false, false, false, "tls12, malformed dc"},
|
||||||
|
{true, true, false, VersionTLS13, VersionTLS13, true, false, false, false, "tls13, malformed dc"},
|
||||||
|
{true, true, true, VersionTLS12, VersionTLS12, false, true, true, true, "tls12, invalid dc, skip verify"},
|
||||||
|
{true, true, true, VersionTLS13, VersionTLS13, false, true, true, true, "tls13, invalid dc, skip verify"},
|
||||||
|
{false, true, false, VersionTLS12, VersionTLS12, false, false, true, false, "client no dc"},
|
||||||
|
{true, false, false, VersionTLS12, VersionTLS12, false, false, true, false, "server no dc"},
|
||||||
|
{true, true, false, VersionTLS11, VersionTLS12, false, false, true, false, "client old"},
|
||||||
|
{true, true, false, VersionTLS12, VersionTLS11, false, false, true, false, "server old"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the handshake with the delegated credential extension.
|
||||||
|
func TestDCHandshake(t *testing.T) {
|
||||||
|
serverMsg := "hello"
|
||||||
|
clientMsg := "world"
|
||||||
|
|
||||||
|
clientConfig := dcTestConfig.Clone()
|
||||||
|
serverConfig := dcTestConfig.Clone()
|
||||||
|
serverConfig.GetCertificate = testServerGetCertificate
|
||||||
|
|
||||||
|
for i, test := range dcTests {
|
||||||
|
clientConfig.AcceptDelegatedCredential = test.clientDC
|
||||||
|
clientConfig.InsecureSkipVerify = test.clientSkipVerify
|
||||||
|
|
||||||
|
if test.serverDC {
|
||||||
|
if test.useInvalidDC {
|
||||||
|
serverConfig.GetDelegatedCredential = testServerGetInvalidDC
|
||||||
|
} else if test.useMalformedDC {
|
||||||
|
serverConfig.GetDelegatedCredential = testServerGetMalformedDC
|
||||||
|
} else {
|
||||||
|
serverConfig.GetDelegatedCredential = testServerGetDC
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
serverConfig.GetDelegatedCredential = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
clientConfig.MaxVersion = test.clientMaxVers
|
||||||
|
serverConfig.MaxVersion = test.serverMaxVers
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if usedDC != test.expectDC {
|
||||||
|
t.Errorf("test #%d (%s) usedDC = %v; expected %v", i+1, test.name, usedDC, test.expectDC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -674,7 +674,7 @@ func TestCloneNonFuncFields(t *testing.T) {
|
|||||||
switch fn := typ.Field(i).Name; fn {
|
switch fn := typ.Field(i).Name; fn {
|
||||||
case "Rand":
|
case "Rand":
|
||||||
f.Set(reflect.ValueOf(io.Reader(os.Stdin)))
|
f.Set(reflect.ValueOf(io.Reader(os.Stdin)))
|
||||||
case "Time", "GetCertificate", "GetConfigForClient", "VerifyPeerCertificate", "GetClientCertificate":
|
case "Time", "GetCertificate", "GetConfigForClient", "VerifyPeerCertificate", "GetClientCertificate", "GetDelegatedCredential":
|
||||||
// DeepEqual can't compare functions. If you add a
|
// DeepEqual can't compare functions. If you add a
|
||||||
// function field to this list, you must also change
|
// function field to this list, you must also change
|
||||||
// TestCloneFuncFields to ensure that the func field is
|
// TestCloneFuncFields to ensure that the func field is
|
||||||
@ -713,6 +713,8 @@ func TestCloneNonFuncFields(t *testing.T) {
|
|||||||
f.Set(reflect.ValueOf(uint32(0)))
|
f.Set(reflect.ValueOf(uint32(0)))
|
||||||
case "SessionTicketSealer":
|
case "SessionTicketSealer":
|
||||||
// TODO
|
// TODO
|
||||||
|
case "AcceptDelegatedCredential":
|
||||||
|
f.Set(reflect.ValueOf(false))
|
||||||
default:
|
default:
|
||||||
t.Errorf("all fields must be accounted for, but saw unknown field %q", fn)
|
t.Errorf("all fields must be accounted for, but saw unknown field %q", fn)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user