From 58b905e636853b687a80fb1d297e355d22faf5b4 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 16 Nov 2016 17:50:08 +0700 Subject: [PATCH] validate server config signature, for RSA certificates --- handshake/crypto_setup_client.go | 38 +++++++++ handshake/crypto_setup_client_test.go | 116 ++++++++++++++++++++++++-- 2 files changed, 149 insertions(+), 5 deletions(-) diff --git a/handshake/crypto_setup_client.go b/handshake/crypto_setup_client.go index dd1546552..3cff7ecaf 100644 --- a/handshake/crypto_setup_client.go +++ b/handshake/crypto_setup_client.go @@ -2,7 +2,11 @@ package handshake import ( "bytes" + gocrypto "crypto" "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" "encoding/binary" "errors" "io" @@ -27,6 +31,7 @@ type cryptoSetupClient struct { nonc []byte proof []byte diversificationNonce []byte + chloForSignature []byte lastSentCHLO []byte certManager crypto.CertManager @@ -135,6 +140,7 @@ func (h *cryptoSetupClient) handleREJMessage(cryptoData map[Tag][]byte) error { if proof, ok := cryptoData[TagPROF]; ok { h.proof = proof + h.chloForSignature = h.lastSentCHLO } if crt, ok := cryptoData[TagCERT]; ok { @@ -144,6 +150,38 @@ func (h *cryptoSetupClient) handleREJMessage(cryptoData map[Tag][]byte) error { } } + if h.serverConfig != nil && len(h.proof) != 0 && h.certManager.GetLeafCert() != nil { + return h.verifyServerConfigSignature() + } + + return nil +} + +func (h *cryptoSetupClient) verifyServerConfigSignature() error { + leafCert := h.certManager.GetLeafCert() + cert, err := x509.ParseCertificate(leafCert) + if err != nil { + return qerr.Error(qerr.InvalidCryptoMessageParameter, "Certificate data invalid") + } + + hash := sha256.New() + hash.Write([]byte("QUIC CHLO and server config signature\x00")) + chloHash := sha256.Sum256(h.chloForSignature) + hash.Write([]byte{32, 0, 0, 0}) + hash.Write(chloHash[:]) + hash.Write(h.serverConfig.Get()) + + if cert.PublicKeyAlgorithm == x509.RSA { + opts := &rsa.PSSOptions{SaltLength: 32, Hash: gocrypto.SHA256} + err = rsa.VerifyPSS(cert.PublicKey.(*rsa.PublicKey), gocrypto.SHA256, hash.Sum(nil), h.proof, opts) + + if err != nil { + return qerr.ProofInvalid + } + } else { + panic("Not a RSA.") + } + return nil } diff --git a/handshake/crypto_setup_client_test.go b/handshake/crypto_setup_client_test.go index b7212d5b9..a732dfb08 100644 --- a/handshake/crypto_setup_client_test.go +++ b/handshake/crypto_setup_client_test.go @@ -2,7 +2,16 @@ package handshake import ( "bytes" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" "encoding/binary" + "encoding/pem" + "fmt" + "math/big" + "os" "time" "github.com/lucas-clemente/quic-go/crypto" @@ -12,6 +21,23 @@ import ( . "github.com/onsi/gomega" ) +// taken from https://golang.org/src/crypto/tls/generate_cert.go +func pemBlockForKey(priv interface{}) *pem.Block { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + case *ecdsa.PrivateKey: + b, err := x509.MarshalECPrivateKey(k) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err) + os.Exit(2) + } + return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + default: + return nil + } +} + type mockCertManager struct { setDataCalledWith []byte leafCert []byte @@ -81,6 +107,18 @@ var _ = Describe("Crypto setup", func() { Expect(cs.proof).To(Equal(proof)) }) + It("saves the last sent CHLO for signature validation, when receiving the proof", func() { + chlo := []byte("last sent CHLO") + cs.lastSentCHLO = chlo + err := cs.handleREJMessage(tagMap) + Expect(err).ToNot(HaveOccurred()) + Expect(cs.chloForSignature).To(BeEmpty()) + tagMap[TagPROF] = []byte("signature") + err = cs.handleREJMessage(tagMap) + Expect(err).ToNot(HaveOccurred()) + Expect(cs.chloForSignature).To(Equal(chlo)) + }) + It("saves the server nonce", func() { nonc := []byte("servernonce") tagMap[TagSNO] = nonc @@ -89,11 +127,79 @@ var _ = Describe("Crypto setup", func() { Expect(cs.sno).To(Equal(nonc)) }) - It("passes the certificates to the CertManager", func() { - tagMap[TagCERT] = []byte("cert") - err := cs.handleREJMessage(tagMap) - Expect(err).ToNot(HaveOccurred()) - Expect(certManager.setDataCalledWith).To(Equal(tagMap[TagCERT])) + Context("Certificates", func() { + var leafCert *x509.Certificate + var tlsConfig *tls.Config + + BeforeEach(func() { + // generate a RSA key pair and a certificate + key, err := rsa.GenerateKey(rand.Reader, 1024) + Expect(err).ToNot(HaveOccurred()) + template := x509.Certificate{SerialNumber: big.NewInt(1)} + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) + Expect(err).ToNot(HaveOccurred()) + leafCert, err = x509.ParseCertificate(certDER) + Expect(err).ToNot(HaveOccurred()) + + // export certificate and key in PEM format + b := &pem.Block{Type: "CERTIFICATE", Bytes: certDER} + certPEM := pem.EncodeToMemory(b) + b = pemBlockForKey(key) + keyPEM := pem.EncodeToMemory(b) + + // create a tls.Config + tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + Expect(err).ToNot(HaveOccurred()) + tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + } + }) + + It("passes the certificates to the CertManager", func() { + tagMap[TagCERT] = []byte("cert") + err := cs.handleREJMessage(tagMap) + Expect(err).ToNot(HaveOccurred()) + Expect(certManager.setDataCalledWith).To(Equal(tagMap[TagCERT])) + }) + + It("verifies the signature, once it has read all required data", func() { + cs.serverConfig = &serverConfigClient{} + certManager.leafCert = []byte("leaf cert") // this certificate can't be parsed by x509 + cs.proof = []byte("proof") + err := cs.handleREJMessage(tagMap) + Expect(err).To(MatchError(qerr.Error(qerr.InvalidCryptoMessageParameter, "Certificate data invalid"))) + }) + + It("verifies the signature of the server config", func() { + cs.chloForSignature = []byte("CHLO for signature") + serverConfigData := []byte("Server Config Data") + cs.serverConfig = &serverConfigClient{raw: serverConfigData} + certManager.leafCert = leafCert.Raw + + ps, err := crypto.NewProofSource(tlsConfig) + Expect(err).ToNot(HaveOccurred()) + signature, err := ps.SignServerProof("", cs.chloForSignature, serverConfigData) + Expect(err).ToNot(HaveOccurred()) + cs.proof = signature + + err = cs.verifyServerConfigSignature() + Expect(err).ToNot(HaveOccurred()) + }) + + It("errors if it can't read the leaf certificate", func() { + certManager.leafCert = []byte("invalid leaf cert") + err := cs.verifyServerConfigSignature() + Expect(err).To(MatchError(qerr.Error(qerr.InvalidCryptoMessageParameter, "Certificate data invalid"))) + }) + + It("rejects invalid signatures of the server config", func() { + cs.serverConfig = &serverConfigClient{raw: []byte("Server Config Data")} + cs.proof = []byte("invalid signature") + certManager.leafCert = leafCert.Raw + + err := cs.verifyServerConfigSignature() + Expect(err).To(MatchError(qerr.ProofInvalid)) + }) }) Context("Reading server configs", func() {