From d17d597ebee084c196fdb0ba73e4352879c1d0d8 Mon Sep 17 00:00:00 2001 From: Lucas Clemente Date: Wed, 11 May 2016 15:57:24 +0200 Subject: [PATCH] implement certificate compression using common certificate sets fixes #47 --- crypto/cert_compression.go | 30 +++++++++++++++++++++++--- crypto/cert_compression_test.go | 38 +++++++++++++++++++++++++++++++++ crypto/cert_sets.go | 24 +++++++++++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 crypto/cert_sets.go diff --git a/crypto/cert_compression.go b/crypto/cert_compression.go index a3dbc02d6..a6dc8f815 100644 --- a/crypto/cert_compression.go +++ b/crypto/cert_compression.go @@ -33,19 +33,27 @@ func compressChain(chain [][]byte, pCommonSetHashes, pCachedHashes []byte) ([]by return nil, err } + setHashes, err := splitHashes(pCommonSetHashes) + if err != nil { + return nil, err + } + chainHashes := make([]uint64, len(chain)) for i := range chain { chainHashes[i] = hashCert(chain[i]) } - entries := buildEntries(chain, chainHashes, cachedHashes) + entries := buildEntries(chain, chainHashes, cachedHashes, setHashes) totalUncompressedLen := 0 for i, e := range entries { res.WriteByte(uint8(e.t)) switch e.t { case entryCached: - utils.WriteUint64(res, chainHashes[i]) + utils.WriteUint64(res, e.h) + case entryCommon: + utils.WriteUint64(res, e.h) + utils.WriteUint32(res, e.i) case entryCompressed: totalUncompressedLen += 4 + len(chain[i]) } @@ -80,7 +88,7 @@ func compressChain(chain [][]byte, pCommonSetHashes, pCachedHashes []byte) ([]by return res.Bytes(), nil } -func buildEntries(chain [][]byte, chainHashes, cachedHashes []uint64) []entry { +func buildEntries(chain [][]byte, chainHashes, cachedHashes, setHashes []uint64) []entry { res := make([]entry, len(chain)) chainLoop: for i := range chain { @@ -92,6 +100,22 @@ chainLoop: } } + // Go through common sets and check if it's in there + for _, setHash := range setHashes { + set, ok := certSets[setHash] + if !ok { + // We don't have this set + continue + } + // We have this set, check if chain[i] is in the set + pos := set.findCertInSet(chain[i]) + if pos >= 0 { + // Found + res[i] = entry{t: entryCommon, h: setHash, i: uint32(pos)} + continue chainLoop + } + } + res[i] = entry{t: entryCompressed} } return res diff --git a/crypto/cert_compression_test.go b/crypto/cert_compression_test.go index 7a07ff192..5094375da 100644 --- a/crypto/cert_compression_test.go +++ b/crypto/cert_compression_test.go @@ -7,6 +7,7 @@ import ( "encoding/binary" "hash/fnv" + "github.com/lucas-clemente/quic-go-certificates" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -95,4 +96,41 @@ var _ = Describe("Cert compression", func() { expected = append(expected, certZlib.Bytes()...) Expect(compressed).To(Equal(expected)) }) + + It("uses common certificates", func() { + cert := certsets.CertSet1[42] + setHash := make([]byte, 8) + binary.LittleEndian.PutUint64(setHash, certsets.CertSet1Hash) + chain := [][]byte{cert} + compressed, err := compressChain(chain, setHash, nil) + Expect(err).ToNot(HaveOccurred()) + expected := []byte{0x03} + expected = append(expected, setHash...) + expected = append(expected, []byte{42, 0, 0, 0}...) + expected = append(expected, 0x00) + Expect(compressed).To(Equal(expected)) + }) + + It("uses common certificates and compressed combined", func() { + cert1 := []byte{0xde, 0xca, 0xfb, 0xad} + cert2 := certsets.CertSet1[42] + setHash := make([]byte, 8) + binary.LittleEndian.PutUint64(setHash, certsets.CertSet1Hash) + certZlib := &bytes.Buffer{} + z, err := zlib.NewWriterLevelDict(certZlib, flate.BestCompression, append(cert2, certDictZlib...)) + Expect(err).ToNot(HaveOccurred()) + z.Write([]byte{0x04, 0x00, 0x00, 0x00}) + z.Write(cert1) + z.Close() + chain := [][]byte{cert1, cert2} + compressed, err := compressChain(chain, setHash, nil) + Expect(err).ToNot(HaveOccurred()) + expected := []byte{0x01, 0x03} + expected = append(expected, setHash...) + expected = append(expected, []byte{42, 0, 0, 0}...) + expected = append(expected, 0x00) + expected = append(expected, []byte{0x08, 0, 0, 0}...) + expected = append(expected, certZlib.Bytes()...) + Expect(compressed).To(Equal(expected)) + }) }) diff --git a/crypto/cert_sets.go b/crypto/cert_sets.go new file mode 100644 index 000000000..45add0e4c --- /dev/null +++ b/crypto/cert_sets.go @@ -0,0 +1,24 @@ +package crypto + +import ( + "bytes" + + "github.com/lucas-clemente/quic-go-certificates" +) + +type certSet [][]byte + +var certSets = map[uint64]certSet{ + certsets.CertSet1Hash: certsets.CertSet1, + certsets.CertSet2Hash: certsets.CertSet2, +} + +// findCertInSet searches for the cert in the set. Negative return value means not found. +func (s *certSet) findCertInSet(cert []byte) int { + for i, c := range *s { + if bytes.Equal(c, cert) { + return i + } + } + return -1 +}