295 lines
9.5 KiB
Go
295 lines
9.5 KiB
Go
|
package crypto
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"compress/flate"
|
||
|
"compress/zlib"
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"hash/fnv"
|
||
|
|
||
|
"github.com/lucas-clemente/quic-go-certificates"
|
||
|
. "github.com/onsi/ginkgo"
|
||
|
. "github.com/onsi/gomega"
|
||
|
)
|
||
|
|
||
|
func byteHash(d []byte) []byte {
|
||
|
h := fnv.New64a()
|
||
|
h.Write(d)
|
||
|
s := h.Sum64()
|
||
|
res := make([]byte, 8)
|
||
|
binary.LittleEndian.PutUint64(res, s)
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
var _ = Describe("Cert compression and decompression", func() {
|
||
|
var certSetsOld map[uint64]certSet
|
||
|
|
||
|
BeforeEach(func() {
|
||
|
certSetsOld = make(map[uint64]certSet)
|
||
|
for s := range certSets {
|
||
|
certSetsOld[s] = certSets[s]
|
||
|
}
|
||
|
})
|
||
|
|
||
|
AfterEach(func() {
|
||
|
certSets = certSetsOld
|
||
|
})
|
||
|
|
||
|
It("compresses empty", func() {
|
||
|
compressed, err := compressChain(nil, nil, nil)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
Expect(compressed).To(Equal([]byte{0}))
|
||
|
})
|
||
|
|
||
|
It("decompresses empty", func() {
|
||
|
compressed, err := compressChain(nil, nil, nil)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
uncompressed, err := decompressChain(compressed)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
Expect(uncompressed).To(BeEmpty())
|
||
|
})
|
||
|
|
||
|
It("gives correct single cert", func() {
|
||
|
cert := []byte{0xde, 0xca, 0xfb, 0xad}
|
||
|
certZlib := &bytes.Buffer{}
|
||
|
z, err := zlib.NewWriterLevelDict(certZlib, flate.BestCompression, certDictZlib)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
z.Write([]byte{0x04, 0x00, 0x00, 0x00})
|
||
|
z.Write(cert)
|
||
|
z.Close()
|
||
|
chain := [][]byte{cert}
|
||
|
compressed, err := compressChain(chain, nil, nil)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
Expect(compressed).To(Equal(append([]byte{
|
||
|
0x01, 0x00,
|
||
|
0x08, 0x00, 0x00, 0x00,
|
||
|
}, certZlib.Bytes()...)))
|
||
|
})
|
||
|
|
||
|
It("decompresses a single cert", func() {
|
||
|
cert := []byte{0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe}
|
||
|
chain := [][]byte{cert}
|
||
|
compressed, err := compressChain(chain, nil, nil)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
uncompressed, err := decompressChain(compressed)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
Expect(uncompressed).To(Equal(chain))
|
||
|
})
|
||
|
|
||
|
It("gives correct cert and intermediate", func() {
|
||
|
cert1 := []byte{0xde, 0xca, 0xfb, 0xad}
|
||
|
cert2 := []byte{0xde, 0xad, 0xbe, 0xef}
|
||
|
certZlib := &bytes.Buffer{}
|
||
|
z, err := zlib.NewWriterLevelDict(certZlib, flate.BestCompression, certDictZlib)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
z.Write([]byte{0x04, 0x00, 0x00, 0x00})
|
||
|
z.Write(cert1)
|
||
|
z.Write([]byte{0x04, 0x00, 0x00, 0x00})
|
||
|
z.Write(cert2)
|
||
|
z.Close()
|
||
|
chain := [][]byte{cert1, cert2}
|
||
|
compressed, err := compressChain(chain, nil, nil)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
Expect(compressed).To(Equal(append([]byte{
|
||
|
0x01, 0x01, 0x00,
|
||
|
0x10, 0x00, 0x00, 0x00,
|
||
|
}, certZlib.Bytes()...)))
|
||
|
})
|
||
|
|
||
|
It("decompresses the chain with a cert and an intermediate", func() {
|
||
|
cert1 := []byte{0xde, 0xca, 0xfb, 0xad}
|
||
|
cert2 := []byte{0xde, 0xad, 0xbe, 0xef}
|
||
|
chain := [][]byte{cert1, cert2}
|
||
|
compressed, err := compressChain(chain, nil, nil)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
decompressed, err := decompressChain(compressed)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
Expect(decompressed).To(Equal(chain))
|
||
|
})
|
||
|
|
||
|
It("uses cached certificates", func() {
|
||
|
cert := []byte{0xde, 0xca, 0xfb, 0xad}
|
||
|
certHash := byteHash(cert)
|
||
|
chain := [][]byte{cert}
|
||
|
compressed, err := compressChain(chain, nil, certHash)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
expected := append([]byte{0x02}, certHash...)
|
||
|
expected = append(expected, 0x00)
|
||
|
Expect(compressed).To(Equal(expected))
|
||
|
})
|
||
|
|
||
|
It("uses cached certificates and compressed combined", func() {
|
||
|
cert1 := []byte{0xde, 0xca, 0xfb, 0xad}
|
||
|
cert2 := []byte{0xde, 0xad, 0xbe, 0xef}
|
||
|
cert2Hash := byteHash(cert2)
|
||
|
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, nil, cert2Hash)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
expected := []byte{0x01, 0x02}
|
||
|
expected = append(expected, cert2Hash...)
|
||
|
expected = append(expected, 0x00)
|
||
|
expected = append(expected, []byte{0x08, 0, 0, 0}...)
|
||
|
expected = append(expected, certZlib.Bytes()...)
|
||
|
Expect(compressed).To(Equal(expected))
|
||
|
})
|
||
|
|
||
|
It("uses common certificate sets", func() {
|
||
|
cert := certsets.CertSet3[42]
|
||
|
setHash := make([]byte, 8)
|
||
|
binary.LittleEndian.PutUint64(setHash, certsets.CertSet3Hash)
|
||
|
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("decompresses a single cert form a common certificate set", func() {
|
||
|
cert := certsets.CertSet3[42]
|
||
|
setHash := make([]byte, 8)
|
||
|
binary.LittleEndian.PutUint64(setHash, certsets.CertSet3Hash)
|
||
|
chain := [][]byte{cert}
|
||
|
compressed, err := compressChain(chain, setHash, nil)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
decompressed, err := decompressChain(compressed)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
Expect(decompressed).To(Equal(chain))
|
||
|
})
|
||
|
|
||
|
It("decompresses multiple certs form common certificate sets", func() {
|
||
|
cert1 := certsets.CertSet3[42]
|
||
|
cert2 := certsets.CertSet2[24]
|
||
|
setHash := make([]byte, 16)
|
||
|
binary.LittleEndian.PutUint64(setHash[0:8], certsets.CertSet3Hash)
|
||
|
binary.LittleEndian.PutUint64(setHash[8:16], certsets.CertSet2Hash)
|
||
|
chain := [][]byte{cert1, cert2}
|
||
|
compressed, err := compressChain(chain, setHash, nil)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
decompressed, err := decompressChain(compressed)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
Expect(decompressed).To(Equal(chain))
|
||
|
})
|
||
|
|
||
|
It("ignores uncommon certificate sets", func() {
|
||
|
cert := []byte{0xde, 0xca, 0xfb, 0xad}
|
||
|
setHash := make([]byte, 8)
|
||
|
binary.LittleEndian.PutUint64(setHash, 0xdeadbeef)
|
||
|
chain := [][]byte{cert}
|
||
|
compressed, err := compressChain(chain, setHash, nil)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
certZlib := &bytes.Buffer{}
|
||
|
z, err := zlib.NewWriterLevelDict(certZlib, flate.BestCompression, certDictZlib)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
z.Write([]byte{0x04, 0x00, 0x00, 0x00})
|
||
|
z.Write(cert)
|
||
|
z.Close()
|
||
|
Expect(compressed).To(Equal(append([]byte{
|
||
|
0x01, 0x00,
|
||
|
0x08, 0x00, 0x00, 0x00,
|
||
|
}, certZlib.Bytes()...)))
|
||
|
})
|
||
|
|
||
|
It("errors if a common set does not exist", func() {
|
||
|
cert := certsets.CertSet3[42]
|
||
|
setHash := make([]byte, 8)
|
||
|
binary.LittleEndian.PutUint64(setHash, certsets.CertSet3Hash)
|
||
|
chain := [][]byte{cert}
|
||
|
compressed, err := compressChain(chain, setHash, nil)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
delete(certSets, certsets.CertSet3Hash)
|
||
|
_, err = decompressChain(compressed)
|
||
|
Expect(err).To(MatchError(errors.New("unknown certSet")))
|
||
|
})
|
||
|
|
||
|
It("errors if a cert in a common set does not exist", func() {
|
||
|
certSet := [][]byte{
|
||
|
{0x1, 0x2, 0x3, 0x4},
|
||
|
{0x5, 0x6, 0x7, 0x8},
|
||
|
}
|
||
|
certSets[0x1337] = certSet
|
||
|
cert := certSet[1]
|
||
|
setHash := make([]byte, 8)
|
||
|
binary.LittleEndian.PutUint64(setHash, 0x1337)
|
||
|
chain := [][]byte{cert}
|
||
|
compressed, err := compressChain(chain, setHash, nil)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
certSets[0x1337] = certSet[:1] // delete the last certificate from the certSet
|
||
|
_, err = decompressChain(compressed)
|
||
|
Expect(err).To(MatchError(errors.New("certificate not found in certSet")))
|
||
|
})
|
||
|
|
||
|
It("uses common certificates and compressed combined", func() {
|
||
|
cert1 := []byte{0xde, 0xca, 0xfb, 0xad}
|
||
|
cert2 := certsets.CertSet3[42]
|
||
|
setHash := make([]byte, 8)
|
||
|
binary.LittleEndian.PutUint64(setHash, certsets.CertSet3Hash)
|
||
|
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))
|
||
|
})
|
||
|
|
||
|
It("decompresses a certficate from a common set and a compressed cert combined", func() {
|
||
|
cert1 := []byte{0xde, 0xca, 0xfb, 0xad}
|
||
|
cert2 := certsets.CertSet3[42]
|
||
|
setHash := make([]byte, 8)
|
||
|
binary.LittleEndian.PutUint64(setHash, certsets.CertSet3Hash)
|
||
|
chain := [][]byte{cert1, cert2}
|
||
|
compressed, err := compressChain(chain, setHash, nil)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
decompressed, err := decompressChain(compressed)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
Expect(decompressed).To(Equal(chain))
|
||
|
})
|
||
|
|
||
|
It("rejects invalid CCS / CCRT hashes", func() {
|
||
|
cert := []byte{0xde, 0xca, 0xfb, 0xad}
|
||
|
chain := [][]byte{cert}
|
||
|
_, err := compressChain(chain, []byte("foo"), nil)
|
||
|
Expect(err).To(MatchError("expected a multiple of 8 bytes for CCS / CCRT hashes"))
|
||
|
_, err = compressChain(chain, nil, []byte("foo"))
|
||
|
Expect(err).To(MatchError("expected a multiple of 8 bytes for CCS / CCRT hashes"))
|
||
|
})
|
||
|
|
||
|
Context("common certificate hashes", func() {
|
||
|
It("gets the hashes", func() {
|
||
|
ccs := getCommonCertificateHashes()
|
||
|
Expect(ccs).ToNot(BeEmpty())
|
||
|
hashes, err := splitHashes(ccs)
|
||
|
Expect(err).ToNot(HaveOccurred())
|
||
|
for _, hash := range hashes {
|
||
|
Expect(certSets).To(HaveKey(hash))
|
||
|
}
|
||
|
})
|
||
|
|
||
|
It("returns an empty slice if there are not common sets", func() {
|
||
|
certSets = make(map[uint64]certSet)
|
||
|
ccs := getCommonCertificateHashes()
|
||
|
Expect(ccs).ToNot(BeNil())
|
||
|
Expect(ccs).To(HaveLen(0))
|
||
|
})
|
||
|
})
|
||
|
})
|