crypto/tls: client certificate support.
This changeset implements client certificate support in crypto/tls for both handshake_server.go and handshake_client.go The updated server implementation sends an empty CertificateAuthorities field in the CertificateRequest, thus allowing clients to send any certificates they wish. Likewise, the client code will only respond with its certificate when the server requests a certificate with this field empty. R=agl, rsc, agl1 CC=golang-dev https://golang.org/cl/1975042
This commit is contained in:
parent
10589a7c24
commit
e85c1c3393
37
common.go
37
common.go
@ -35,14 +35,16 @@ const (
|
||||
|
||||
// TLS handshake message types.
|
||||
const (
|
||||
typeClientHello uint8 = 1
|
||||
typeServerHello uint8 = 2
|
||||
typeCertificate uint8 = 11
|
||||
typeCertificateStatus uint8 = 22
|
||||
typeServerHelloDone uint8 = 14
|
||||
typeClientKeyExchange uint8 = 16
|
||||
typeFinished uint8 = 20
|
||||
typeNextProtocol uint8 = 67 // Not IANA assigned
|
||||
typeClientHello uint8 = 1
|
||||
typeServerHello uint8 = 2
|
||||
typeCertificate uint8 = 11
|
||||
typeCertificateRequest uint8 = 13
|
||||
typeServerHelloDone uint8 = 14
|
||||
typeCertificateVerify uint8 = 15
|
||||
typeClientKeyExchange uint8 = 16
|
||||
typeFinished uint8 = 20
|
||||
typeCertificateStatus uint8 = 22
|
||||
typeNextProtocol uint8 = 67 // Not IANA assigned
|
||||
)
|
||||
|
||||
// TLS cipher suites.
|
||||
@ -67,6 +69,15 @@ const (
|
||||
statusTypeOCSP uint8 = 1
|
||||
)
|
||||
|
||||
// Certificate types (for certificateRequestMsg)
|
||||
const (
|
||||
certTypeRSASign = 1 // A certificate containing an RSA key
|
||||
certTypeDSSSign = 2 // A certificate containing a DSA key
|
||||
certTypeRSAFixedDH = 3 // A certificate containing a static DH key
|
||||
certTypeDSSFixedDH = 4 // A certficiate containing a static DH key
|
||||
// Rest of these are reserved by the TLS spec
|
||||
)
|
||||
|
||||
type ConnectionState struct {
|
||||
HandshakeComplete bool
|
||||
CipherSuite uint16
|
||||
@ -79,7 +90,8 @@ type Config struct {
|
||||
// Rand provides the source of entropy for nonces and RSA blinding.
|
||||
Rand io.Reader
|
||||
// Time returns the current time as the number of seconds since the epoch.
|
||||
Time func() int64
|
||||
Time func() int64
|
||||
// Certificates contains one or more certificate chains.
|
||||
Certificates []Certificate
|
||||
RootCAs *CASet
|
||||
// NextProtos is a list of supported, application level protocols.
|
||||
@ -88,9 +100,16 @@ type Config struct {
|
||||
// ServerName is included in the client's handshake to support virtual
|
||||
// hosting.
|
||||
ServerName string
|
||||
// AuthenticateClient determines if a server will request a certificate
|
||||
// from the client. It does not require that the client send a
|
||||
// certificate nor, if it does, that the certificate is anything more
|
||||
// than self-signed.
|
||||
AuthenticateClient bool
|
||||
}
|
||||
|
||||
type Certificate struct {
|
||||
// Certificate contains a chain of one or more certificates. Leaf
|
||||
// certificate first.
|
||||
Certificate [][]byte
|
||||
PrivateKey *rsa.PrivateKey
|
||||
}
|
||||
|
4
conn.go
4
conn.go
@ -534,12 +534,16 @@ func (c *Conn) readHandshake() (interface{}, os.Error) {
|
||||
m = new(serverHelloMsg)
|
||||
case typeCertificate:
|
||||
m = new(certificateMsg)
|
||||
case typeCertificateRequest:
|
||||
m = new(certificateRequestMsg)
|
||||
case typeCertificateStatus:
|
||||
m = new(certificateStatusMsg)
|
||||
case typeServerHelloDone:
|
||||
m = new(serverHelloDoneMsg)
|
||||
case typeClientKeyExchange:
|
||||
m = new(clientKeyExchangeMsg)
|
||||
case typeCertificateVerify:
|
||||
m = new(certificateVerifyMsg)
|
||||
case typeNextProtocol:
|
||||
m = new(nextProtoMsg)
|
||||
case typeFinished:
|
||||
|
@ -130,12 +130,64 @@ func (c *Conn) clientHandshake() os.Error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
transmitCert := false
|
||||
certReq, ok := msg.(*certificateRequestMsg)
|
||||
if ok {
|
||||
// We only accept certificates with RSA keys.
|
||||
rsaAvail := false
|
||||
for _, certType := range certReq.certificateTypes {
|
||||
if certType == certTypeRSASign {
|
||||
rsaAvail = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// For now, only send a certificate back if the server gives us an
|
||||
// empty list of certificateAuthorities.
|
||||
//
|
||||
// RFC 4346 on the certificateAuthorities field:
|
||||
// A list of the distinguished names of acceptable certificate
|
||||
// authorities. These distinguished names may specify a desired
|
||||
// distinguished name for a root CA or for a subordinate CA; thus,
|
||||
// this message can be used to describe both known roots and a
|
||||
// desired authorization space. If the certificate_authorities
|
||||
// list is empty then the client MAY send any certificate of the
|
||||
// appropriate ClientCertificateType, unless there is some
|
||||
// external arrangement to the contrary.
|
||||
if rsaAvail && len(certReq.certificateAuthorities) == 0 {
|
||||
transmitCert = true
|
||||
}
|
||||
|
||||
finishedHash.Write(certReq.marshal())
|
||||
|
||||
msg, err = c.readHandshake()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
shd, ok := msg.(*serverHelloDoneMsg)
|
||||
if !ok {
|
||||
return c.sendAlert(alertUnexpectedMessage)
|
||||
}
|
||||
finishedHash.Write(shd.marshal())
|
||||
|
||||
var cert *x509.Certificate
|
||||
if transmitCert {
|
||||
certMsg = new(certificateMsg)
|
||||
if len(c.config.Certificates) > 0 {
|
||||
cert, err = x509.ParseCertificate(c.config.Certificates[0].Certificate[0])
|
||||
if err == nil && cert.PublicKeyAlgorithm == x509.RSA {
|
||||
certMsg.certificates = c.config.Certificates[0].Certificate
|
||||
} else {
|
||||
cert = nil
|
||||
}
|
||||
}
|
||||
finishedHash.Write(certMsg.marshal())
|
||||
c.writeRecord(recordTypeHandshake, certMsg.marshal())
|
||||
}
|
||||
|
||||
ckx := new(clientKeyExchangeMsg)
|
||||
preMasterSecret := make([]byte, 48)
|
||||
preMasterSecret[0] = byte(hello.vers >> 8)
|
||||
@ -153,6 +205,21 @@ func (c *Conn) clientHandshake() os.Error {
|
||||
finishedHash.Write(ckx.marshal())
|
||||
c.writeRecord(recordTypeHandshake, ckx.marshal())
|
||||
|
||||
if cert != nil {
|
||||
certVerify := new(certificateVerifyMsg)
|
||||
var digest [36]byte
|
||||
copy(digest[0:16], finishedHash.serverMD5.Sum())
|
||||
copy(digest[16:36], finishedHash.serverSHA1.Sum())
|
||||
signed, err := rsa.SignPKCS1v15(c.config.Rand, c.config.Certificates[0].PrivateKey, rsa.HashMD5SHA1, digest[0:])
|
||||
if err != nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
certVerify.signature = signed
|
||||
|
||||
finishedHash.Write(certVerify.marshal())
|
||||
c.writeRecord(recordTypeHandshake, certVerify.marshal())
|
||||
}
|
||||
|
||||
suite := cipherSuites[0]
|
||||
masterSecret, clientMAC, serverMAC, clientKey, serverKey :=
|
||||
keysFromPreMasterSecret11(preMasterSecret, hello.random, serverHello.random, suite.hashLength, suite.cipherKeyLength)
|
||||
|
@ -668,3 +668,153 @@ func (m *nextProtoMsg) unmarshal(data []byte) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type certificateRequestMsg struct {
|
||||
raw []byte
|
||||
certificateTypes []byte
|
||||
certificateAuthorities [][]byte
|
||||
}
|
||||
|
||||
func (m *certificateRequestMsg) marshal() (x []byte) {
|
||||
if m.raw != nil {
|
||||
return m.raw
|
||||
}
|
||||
|
||||
// See http://tools.ietf.org/html/rfc4346#section-7.4.4
|
||||
length := 1 + len(m.certificateTypes) + 2
|
||||
for _, ca := range m.certificateAuthorities {
|
||||
length += 2 + len(ca)
|
||||
}
|
||||
|
||||
x = make([]byte, 4+length)
|
||||
x[0] = typeCertificateRequest
|
||||
x[1] = uint8(length >> 16)
|
||||
x[2] = uint8(length >> 8)
|
||||
x[3] = uint8(length)
|
||||
|
||||
x[4] = uint8(len(m.certificateTypes))
|
||||
|
||||
copy(x[5:], m.certificateTypes)
|
||||
y := x[5+len(m.certificateTypes):]
|
||||
|
||||
numCA := len(m.certificateAuthorities)
|
||||
y[0] = uint8(numCA >> 8)
|
||||
y[1] = uint8(numCA)
|
||||
y = y[2:]
|
||||
for _, ca := range m.certificateAuthorities {
|
||||
y[0] = uint8(len(ca) >> 8)
|
||||
y[1] = uint8(len(ca))
|
||||
y = y[2:]
|
||||
copy(y, ca)
|
||||
y = y[len(ca):]
|
||||
}
|
||||
|
||||
m.raw = x
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *certificateRequestMsg) unmarshal(data []byte) bool {
|
||||
m.raw = data
|
||||
|
||||
if len(data) < 5 {
|
||||
return false
|
||||
}
|
||||
|
||||
length := uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3])
|
||||
if uint32(len(data))-4 != length {
|
||||
return false
|
||||
}
|
||||
|
||||
numCertTypes := int(data[4])
|
||||
data = data[5:]
|
||||
if numCertTypes == 0 || len(data) <= numCertTypes {
|
||||
return false
|
||||
}
|
||||
|
||||
m.certificateTypes = make([]byte, numCertTypes)
|
||||
if copy(m.certificateTypes, data) != numCertTypes {
|
||||
return false
|
||||
}
|
||||
|
||||
data = data[numCertTypes:]
|
||||
if len(data) < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
numCAs := uint16(data[0])<<16 | uint16(data[1])
|
||||
data = data[2:]
|
||||
|
||||
m.certificateAuthorities = make([][]byte, numCAs)
|
||||
for i := uint16(0); i < numCAs; i++ {
|
||||
if len(data) < 2 {
|
||||
return false
|
||||
}
|
||||
caLen := uint16(data[0])<<16 | uint16(data[1])
|
||||
|
||||
data = data[2:]
|
||||
if len(data) < int(caLen) {
|
||||
return false
|
||||
}
|
||||
|
||||
ca := make([]byte, caLen)
|
||||
copy(ca, data)
|
||||
m.certificateAuthorities[i] = ca
|
||||
data = data[caLen:]
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type certificateVerifyMsg struct {
|
||||
raw []byte
|
||||
signature []byte
|
||||
}
|
||||
|
||||
func (m *certificateVerifyMsg) marshal() (x []byte) {
|
||||
if m.raw != nil {
|
||||
return m.raw
|
||||
}
|
||||
|
||||
// See http://tools.ietf.org/html/rfc4346#section-7.4.8
|
||||
siglength := len(m.signature)
|
||||
length := 2 + siglength
|
||||
x = make([]byte, 4+length)
|
||||
x[0] = typeCertificateVerify
|
||||
x[1] = uint8(length >> 16)
|
||||
x[2] = uint8(length >> 8)
|
||||
x[3] = uint8(length)
|
||||
x[4] = uint8(siglength >> 8)
|
||||
x[5] = uint8(siglength)
|
||||
copy(x[6:], m.signature)
|
||||
|
||||
m.raw = x
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *certificateVerifyMsg) unmarshal(data []byte) bool {
|
||||
m.raw = data
|
||||
|
||||
if len(data) < 6 {
|
||||
return false
|
||||
}
|
||||
|
||||
length := uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3])
|
||||
if uint32(len(data))-4 != length {
|
||||
return false
|
||||
}
|
||||
|
||||
siglength := int(data[4])<<8 + int(data[5])
|
||||
if len(data)-6 != siglength {
|
||||
return false
|
||||
}
|
||||
|
||||
m.signature = data[6:]
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ var tests = []interface{}{
|
||||
&serverHelloMsg{},
|
||||
|
||||
&certificateMsg{},
|
||||
&certificateRequestMsg{},
|
||||
&certificateVerifyMsg{},
|
||||
&certificateStatusMsg{},
|
||||
&clientKeyExchangeMsg{},
|
||||
&finishedMsg{},
|
||||
@ -148,6 +150,23 @@ func (*certificateMsg) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
return reflect.NewValue(m)
|
||||
}
|
||||
|
||||
func (*certificateRequestMsg) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
m := &certificateRequestMsg{}
|
||||
m.certificateTypes = randomBytes(rand.Intn(5)+1, rand)
|
||||
numCAs := rand.Intn(100)
|
||||
m.certificateAuthorities = make([][]byte, numCAs)
|
||||
for i := 0; i < numCAs; i++ {
|
||||
m.certificateAuthorities[i] = randomBytes(rand.Intn(15)+1, rand)
|
||||
}
|
||||
return reflect.NewValue(m)
|
||||
}
|
||||
|
||||
func (*certificateVerifyMsg) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
m := &certificateVerifyMsg{}
|
||||
m.signature = randomBytes(rand.Intn(15)+1, rand)
|
||||
return reflect.NewValue(m)
|
||||
}
|
||||
|
||||
func (*certificateStatusMsg) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
m := &certificateStatusMsg{}
|
||||
if rand.Intn(10) > 5 {
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/subtle"
|
||||
"crypto/x509"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
@ -112,10 +113,62 @@ func (c *Conn) serverHandshake() os.Error {
|
||||
finishedHash.Write(certMsg.marshal())
|
||||
c.writeRecord(recordTypeHandshake, certMsg.marshal())
|
||||
|
||||
if config.AuthenticateClient {
|
||||
// Request a client certificate
|
||||
certReq := new(certificateRequestMsg)
|
||||
certReq.certificateTypes = []byte{certTypeRSASign}
|
||||
// An empty list of certificateAuthorities signals to
|
||||
// the client that it may send any certificate in response
|
||||
// to our request.
|
||||
|
||||
finishedHash.Write(certReq.marshal())
|
||||
c.writeRecord(recordTypeHandshake, certReq.marshal())
|
||||
}
|
||||
|
||||
helloDone := new(serverHelloDoneMsg)
|
||||
finishedHash.Write(helloDone.marshal())
|
||||
c.writeRecord(recordTypeHandshake, helloDone.marshal())
|
||||
|
||||
var pub *rsa.PublicKey
|
||||
if config.AuthenticateClient {
|
||||
// Get client certificate
|
||||
msg, err = c.readHandshake()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certMsg, ok = msg.(*certificateMsg)
|
||||
if !ok {
|
||||
return c.sendAlert(alertUnexpectedMessage)
|
||||
}
|
||||
finishedHash.Write(certMsg.marshal())
|
||||
|
||||
certs := make([]*x509.Certificate, len(certMsg.certificates))
|
||||
for i, asn1Data := range certMsg.certificates {
|
||||
cert, err := x509.ParseCertificate(asn1Data)
|
||||
if err != nil {
|
||||
return c.sendAlert(alertBadCertificate)
|
||||
}
|
||||
certs[i] = cert
|
||||
}
|
||||
|
||||
// TODO(agl): do better validation of certs: max path length, name restrictions etc.
|
||||
for i := 1; i < len(certs); i++ {
|
||||
if err := certs[i-1].CheckSignatureFrom(certs[i]); err != nil {
|
||||
return c.sendAlert(alertBadCertificate)
|
||||
}
|
||||
}
|
||||
|
||||
if len(certs) > 0 {
|
||||
key, ok := certs[0].PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return c.sendAlert(alertUnsupportedCertificate)
|
||||
}
|
||||
pub = key
|
||||
c.peerCertificates = certs
|
||||
}
|
||||
}
|
||||
|
||||
// Get client key exchange
|
||||
msg, err = c.readHandshake()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -126,6 +179,33 @@ func (c *Conn) serverHandshake() os.Error {
|
||||
}
|
||||
finishedHash.Write(ckx.marshal())
|
||||
|
||||
// If we received a client cert in response to our certificate request message,
|
||||
// the client will send us a certificateVerifyMsg immediately after the
|
||||
// clientKeyExchangeMsg. This message is a MD5SHA1 digest of all preceeding
|
||||
// handshake-layer messages that is signed using the private key corresponding
|
||||
// to the client's certificate. This allows us to verify that the client is in
|
||||
// posession of the private key of the certificate.
|
||||
if len(c.peerCertificates) > 0 {
|
||||
msg, err = c.readHandshake()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certVerify, ok := msg.(*certificateVerifyMsg)
|
||||
if !ok {
|
||||
return c.sendAlert(alertUnexpectedMessage)
|
||||
}
|
||||
|
||||
digest := make([]byte, 36)
|
||||
copy(digest[0:16], finishedHash.serverMD5.Sum())
|
||||
copy(digest[16:36], finishedHash.serverSHA1.Sum())
|
||||
err = rsa.VerifyPKCS1v15(pub, rsa.HashMD5SHA1, digest, certVerify.signature)
|
||||
if err != nil {
|
||||
return c.sendAlert(alertBadCertificate)
|
||||
}
|
||||
|
||||
finishedHash.Write(certVerify.marshal())
|
||||
}
|
||||
|
||||
preMasterSecret := make([]byte, 48)
|
||||
_, err = io.ReadFull(config.Rand, preMasterSecret[2:])
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user