Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support raw public keys (RFC 7250) #208

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 110 additions & 33 deletions client-state-machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ func (state clientStateStart) Next(hr handshakeMessageReader) (HandshakeState, [
alpn = &ALPNExtension{Protocols: state.Opts.NextProtos}
}

// Raw public keys
var cct *ClientCertTypeExtension
var sct *ClientCertTypeExtension
bifurcation marked this conversation as resolved.
Show resolved Hide resolved
if state.Config.AllowRawPublicKeys || !state.Config.ForbidCertificates {
bifurcation marked this conversation as resolved.
Show resolved Hide resolved
cct := &ClientCertTypeExtension{HandshakeType: HandshakeTypeClientHello}
sct := &ServerCertTypeExtension{HandshakeType: HandshakeTypeClientHello}
bifurcation marked this conversation as resolved.
Show resolved Hide resolved

if state.Config.AllowRawPublicKeys {
cct.CertificateTypes = append(cct.CertificateTypes, CertificateTypeRawPublicKey)
sct.CertificateTypes = append(sct.CertificateTypes, CertificateTypeRawPublicKey)
}

if !state.Config.ForbidCertificates {
cct.CertificateTypes = append(cct.CertificateTypes, CertificateTypeX509)
sct.CertificateTypes = append(sct.CertificateTypes, CertificateTypeX509)
}
}

// Construct base ClientHello
ch := &ClientHelloBody{
LegacyVersion: wireVersion(state.hsCtx.hIn),
Expand All @@ -113,7 +131,7 @@ func (state clientStateStart) Next(hr handshakeMessageReader) (HandshakeState, [
return nil, nil, AlertInternalError
}
for _, ext := range []ExtensionBody{&sv, &sni, &ks, &sg, &sa} {
err := ch.Extensions.Add(ext)
err = ch.Extensions.Add(ext)
bifurcation marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
logf(logTypeHandshake, "[ClientStateStart] Error adding extension type=[%v] [%v]", ext.Type(), err)
return nil, nil, AlertInternalError
Expand All @@ -128,6 +146,18 @@ func (state clientStateStart) Next(hr handshakeMessageReader) (HandshakeState, [
return nil, nil, AlertInternalError
}
}
if cct != nil {
bifurcation marked this conversation as resolved.
Show resolved Hide resolved
err = ch.Extensions.Add(cct)
if err != nil {
logf(logTypeHandshake, "[ClientStateStart] Error adding ClientCertType extension [%v]", err)
return nil, nil, AlertInternalError
}
err = ch.Extensions.Add(sct)
if err != nil {
logf(logTypeHandshake, "[ClientStateStart] Error adding ServerCertType extension [%v]", err)
return nil, nil, AlertInternalError
}
}
if state.cookie != nil {
err := ch.Extensions.Add(&CookieExtension{Cookie: state.cookie})
if err != nil {
Expand Down Expand Up @@ -590,11 +620,15 @@ func (state clientStateWaitEE) Next(hr handshakeMessageReader) (HandshakeState,

serverALPN := &ALPNExtension{}
serverEarlyData := &EarlyDataExtension{}
serverClientCertType := &ClientCertTypeExtension{HandshakeType: HandshakeTypeEncryptedExtensions}
serverServerCertType := &ServerCertTypeExtension{HandshakeType: HandshakeTypeEncryptedExtensions}
bifurcation marked this conversation as resolved.
Show resolved Hide resolved

foundExts, err := ee.Extensions.Parse(
[]ExtensionBody{
serverALPN,
serverEarlyData,
serverClientCertType,
serverServerCertType,
})
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitEE] Error decoding extensions: %v", err)
Expand All @@ -607,6 +641,14 @@ func (state clientStateWaitEE) Next(hr handshakeMessageReader) (HandshakeState,
state.Params.NextProto = serverALPN.Protocols[0]
}

if foundExts[ExtensionTypeClientCertType] &&
foundExts[ExtensionTypeServerCertType] &&
state.Config.AllowRawPublicKeys {
foundClient := serverClientCertType.CertificateTypes[0] == CertificateTypeRawPublicKey
foundServer := serverServerCertType.CertificateTypes[0] == CertificateTypeRawPublicKey
state.Params.UsingRawPublicKeys = foundClient && foundServer
bifurcation marked this conversation as resolved.
Show resolved Hide resolved
}

state.handshakeHash.Write(hm.Marshal())

toSend := []HandshakeAction{}
Expand Down Expand Up @@ -818,44 +860,67 @@ func (state clientStateWaitCV) Next(hr handshakeMessageReader) (HandshakeState,
hcv := state.handshakeHash.Sum(nil)
logf(logTypeHandshake, "Handshake Hash to be verified: [%d] %x", len(hcv), hcv)

serverPublicKey := state.serverCertificate.CertificateList[0].CertData.PublicKey
certs := make([][]byte, len(state.serverCertificate.CertificateList))
for i, certEntry := range state.serverCertificate.CertificateList {
certs[i] = certEntry.CertData
}

var err error
var serverPublicKey crypto.PublicKey
var eeCert *x509.Certificate
if !state.Params.UsingRawPublicKeys {
eeCert, err = x509.ParseCertificate(certs[0])
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitCV] Unable to parse client cert: %v", err)
}

serverPublicKey = eeCert.PublicKey
} else {
serverPublicKey, err = unmarshalSigningKey(certs[0])
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitCV] Unable to parse raw public key: %v", err)
}
}
bifurcation marked this conversation as resolved.
Show resolved Hide resolved

if err := certVerify.Verify(serverPublicKey, hcv); err != nil {
logf(logTypeHandshake, "[ClientStateWaitCV] Server signature failed to verify")
return nil, nil, AlertHandshakeFailure
}

certs := make([]*x509.Certificate, len(state.serverCertificate.CertificateList))
rawCerts := make([][]byte, len(state.serverCertificate.CertificateList))
for i, certEntry := range state.serverCertificate.CertificateList {
certs[i] = certEntry.CertData
rawCerts[i] = certEntry.CertData.Raw
}

var verifiedChains [][]*x509.Certificate
if !state.Config.InsecureSkipVerify {
opts := x509.VerifyOptions{
Roots: state.Config.RootCAs,
CurrentTime: state.Config.time(),
DNSName: state.Config.ServerName,
Intermediates: x509.NewCertPool(),
}
if !state.Params.UsingRawPublicKeys {
if !state.Config.InsecureSkipVerify {
opts := x509.VerifyOptions{
Roots: state.Config.RootCAs,
CurrentTime: state.Config.time(),
DNSName: state.Config.ServerName,
Intermediates: x509.NewCertPool(),
}

for i, cert := range certs {
if i == 0 {
continue
}

caCert, err := x509.ParseCertificate(cert)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitCV] Error parsing server chain: %v", err)
return nil, nil, AlertDecodeError
}

for i, cert := range certs {
if i == 0 {
continue
opts.Intermediates.AddCert(caCert)
}
var err error
verifiedChains, err = eeCert.Verify(opts)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitCV] Certificate verification failed: %s", err)
return nil, nil, AlertBadCertificate
}
opts.Intermediates.AddCert(cert)
}
var err error
verifiedChains, err = certs[0].Verify(opts)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitCV] Certificate verification failed: %s", err)
return nil, nil, AlertBadCertificate
}
}

if state.Config.VerifyPeerCertificate != nil {
if err := state.Config.VerifyPeerCertificate(rawCerts, verifiedChains); err != nil {
if err := state.Config.VerifyPeerCertificate(certs, verifiedChains); err != nil {
logf(logTypeHandshake, "[ClientStateWaitCV] Application rejected server certificate: %s", err)
return nil, nil, AlertBadCertificate
}
Expand Down Expand Up @@ -888,7 +953,7 @@ type clientStateWaitFinished struct {

certificates []*Certificate
serverCertificateRequest *CertificateRequestBody
peerCertificates []*x509.Certificate
peerCertificates [][]byte
verifiedChains [][]*x509.Certificate

masterSecret []byte
Expand Down Expand Up @@ -1000,12 +1065,23 @@ func (state clientStateWaitFinished) Next(hr handshakeMessageReader) (HandshakeS
state.handshakeHash.Write(certm.Marshal())
} else {
// Create and send Certificate, CertificateVerify
certificate := &CertificateBody{
CertificateList: make([]CertificateEntry, len(cert.Chain)),
}
for i, entry := range cert.Chain {
certificate.CertificateList[i] = CertificateEntry{CertData: entry}
var certList []CertificateEntry
if !state.Params.UsingRawPublicKeys {
certList = make([]CertificateEntry, len(cert.Chain))
for i, entry := range cert.Chain {
certList[i] = CertificateEntry{CertData: entry.Raw}
}
} else {
certData, err := marshalSigningKey(cert.PrivateKey.Public())
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitFinished] Unable to marshal raw public key [%v]", err)
return nil, nil, AlertInternalError
}

certList = []CertificateEntry{{CertData: certData}}
}

certificate := &CertificateBody{CertificateList: certList}
certm, err := state.hsCtx.hOut.HandshakeMessageFromBody(certificate)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitFinished] Error marshaling Certificate [%v]", err)
Expand All @@ -1015,6 +1091,7 @@ func (state clientStateWaitFinished) Next(hr handshakeMessageReader) (HandshakeS
toSend = append(toSend, QueueHandshakeMessage{certm})
state.handshakeHash.Write(certm.Marshal())

// Create and send CertificateVerify
hcv := state.handshakeHash.Sum(nil)
logf(logTypeHandshake, "Handshake Hash to be verified: [%d] %x", len(hcv), hcv)

Expand Down
15 changes: 15 additions & 0 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ const (
ExtensionTypeCookie ExtensionType = 44
ExtensionTypePSKKeyExchangeModes ExtensionType = 45
ExtensionTypeTicketEarlyDataInfo ExtensionType = 46
ExtensionTypeClientCertType ExtensionType = 19
ExtensionTypeServerCertType ExtensionType = 20
)

// enum {...} NamedGroup
Expand Down Expand Up @@ -164,6 +166,19 @@ const (
KeyUpdateRequested KeyUpdateRequest = 1
)

/*
0 X.509 Y [RFC6091]
1 OpenPGP_RESERVED N [RFC6091][RFC8446] Used in TLS versions prior to 1.3.
2 Raw Public Key Y [RFC7250]
3 1609Dot2 N
*/
type CertificateType uint8

const (
CertificateTypeX509 CertificateType = 0
CertificateTypeRawPublicKey CertificateType = 2
)

type State uint8

const (
Expand Down
8 changes: 7 additions & 1 deletion conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
type Certificate struct {
Chain []*x509.Certificate
PrivateKey crypto.Signer
PublicKey crypto.PublicKey
}

type PreSharedKey struct {
Expand Down Expand Up @@ -129,6 +130,11 @@ type Config struct {
NonBlocking bool
UseDTLS bool

// These bools are arranged in opposite directions so that their default
// values reflect the correct default semantics (certs yes, raw keys no)
AllowRawPublicKeys bool
ForbidCertificates bool

RecordLayer RecordLayerFactory

// The same config object can be shared among different connections, so it
Expand Down Expand Up @@ -250,7 +256,7 @@ var (
type ConnectionState struct {
HandshakeState State
CipherSuite CipherSuiteParams // cipher suite in use (TLS_RSA_WITH_RC4_128_SHA, ...)
PeerCertificates []*x509.Certificate // certificate chain presented by remote peer
PeerCertificates [][]byte // certificate chain presented by remote peer
VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates
NextProto string // Selected ALPN proto
UsingPSK bool // Are we using PSK.
Expand Down
26 changes: 18 additions & 8 deletions conn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ var (
psk PreSharedKey
psks *PSKMapCache

basicConfig, dtlsConfig, nbConfig, nbDTLSConfig, hrrConfig, alpnConfig, pskConfig, pskDTLSConfig, pskECDHEConfig, pskDHEConfig, resumptionConfig, ffdhConfig, x25519Config *Config
basicConfig, dtlsConfig, nbConfig, nbDTLSConfig, hrrConfig, alpnConfig, rawConfig, pskConfig, pskDTLSConfig, pskECDHEConfig, pskDHEConfig, resumptionConfig, ffdhConfig, x25519Config *Config
)

func init() {
Expand Down Expand Up @@ -262,6 +262,13 @@ func init() {
InsecureSkipVerify: true,
}

rawConfig = &Config{
ServerName: serverName,
Certificates: certificates,
AllowRawPublicKeys: true,
InsecureSkipVerify: true,
}

pskConfig = &Config{
ServerName: serverName,
CipherSuites: []CipherSuite{TLS_AES_128_GCM_SHA256},
Expand Down Expand Up @@ -357,11 +364,13 @@ func checkConsistency(t *testing.T, client *Conn, server *Conn) {

func testConnInner(t *testing.T, name string, p testInstanceState) {
// Configs array:
configs := map[string]*Config{"basic config": basicConfig,
"HRR": hrrConfig,
"ALPN": alpnConfig,
"FFDH": ffdhConfig,
"x25519": x25519Config,
configs := map[string]*Config{
"basic config": basicConfig,
"HRR": hrrConfig,
"ALPN": alpnConfig,
"RawPK": rawConfig,
"FFDH": ffdhConfig,
"x25519": x25519Config,
}

c := configs[p["config"]]
Expand Down Expand Up @@ -400,6 +409,7 @@ func TestBasicFlows(t *testing.T) {
"basic config",
"HRR",
"ALPN",
"RawPK",
"FFDH",
"x25519",
},
Expand Down Expand Up @@ -1232,9 +1242,9 @@ func TestConnectionState(t *testing.T) {
serverCS := server.ConnectionState()
assertEquals(t, clientCS.CipherSuite.Suite, configClient.CipherSuites[0])
assertDeepEquals(t, clientCS.VerifiedChains, [][]*x509.Certificate{{serverCert}})
assertDeepEquals(t, clientCS.PeerCertificates, []*x509.Certificate{serverCert})
assertDeepEquals(t, clientCS.PeerCertificates, [][]byte{serverCert.Raw})
assertEquals(t, serverCS.CipherSuite.Suite, serverConfig.CipherSuites[0])
assertDeepEquals(t, serverCS.PeerCertificates, []*x509.Certificate{clientCert})
assertDeepEquals(t, serverCS.PeerCertificates, [][]byte{clientCert.Raw})
}

func TestDTLS(t *testing.T) {
Expand Down
25 changes: 25 additions & 0 deletions crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,31 @@ func newSigningKey(sig SignatureScheme) (crypto.Signer, error) {
}
}

func marshalSigningKey(pub crypto.PublicKey) ([]byte, error) {
switch pub.(type) {
case *rsa.PublicKey:
return x509.MarshalPKIXPublicKey(pub)

case *ecdsa.PublicKey:
return x509.MarshalPKIXPublicKey(pub)

// TODO support Ed25519

default:
return nil, fmt.Errorf("tls.marshalsigningkey: Unsupported public key type")
}
}

func unmarshalSigningKey(data []byte) (interface{}, error) {
pub, err := x509.ParsePKIXPublicKey(data)
if err == nil {
return pub, err
}

// TODO attempt to parse Ed25519
return nil, err
}

// XXX(rlb): Copied from crypto/x509
type ecdsaSignature struct {
R, S *big.Int
Expand Down
Loading