commit fe142d4ee313772a7713dbe1fc994880d6a1fdf6
Author: David Fifield <david(a)bamsoftware.com>
Date: Tue Mar 21 23:48:31 2017 -0700
Tests for certContext.
---
meek-server/certificate_test.go | 312 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 312 insertions(+)
diff --git a/meek-server/certificate_test.go b/meek-server/certificate_test.go
new file mode 100644
index 0000000..94e18f0
--- /dev/null
+++ b/meek-server/certificate_test.go
@@ -0,0 +1,312 @@
+package main
+
+import (
+ "bytes"
+ "crypto/rand"
+ "crypto/tls"
+ "encoding/hex"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+)
+
+// openssl genpkey -out key1.pem -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve
+const key1PEM = `-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgwBuadiHWuhiCwUHK
+pvzM0MYVySH8FE5T/76qoDBozzChRANCAAQao88iJDj284z5YsSOwynblbNfhRHL
+dhInn3bl8dYUr0s88q7yaOHW0riAYAX8Q/G5zoP/P1MgNPFMYV76eSY4
+-----END PRIVATE KEY-----`
+
+// openssl req -out cert1.pem -x509 -new -nodes -key key1.pem -days 1 -subj "/CN=meek-server.example.com"
+const cert1PEM = `-----BEGIN CERTIFICATE-----
+MIIBjjCCATSgAwIBAgIJAKpvM1Hu/AeyMAoGCCqGSM49BAMCMCIxIDAeBgNVBAMM
+F21lZWstc2VydmVyLmV4YW1wbGUuY29tMB4XDTE3MDMyMTIyNTM0N1oXDTE3MDMy
+MjIyNTM0N1owIjEgMB4GA1UEAwwXbWVlay1zZXJ2ZXIuZXhhbXBsZS5jb20wWTAT
+BgcqhkjOPQIBBggqhkjOPQMBBwNCAAQao88iJDj284z5YsSOwynblbNfhRHLdhIn
+n3bl8dYUr0s88q7yaOHW0riAYAX8Q/G5zoP/P1MgNPFMYV76eSY4o1MwUTAdBgNV
+HQ4EFgQU/FIFX5DX58BFhNBSWV0ulWmS+XIwHwYDVR0jBBgwFoAU/FIFX5DX58BF
+hNBSWV0ulWmS+XIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEA
+mylhVbnAd0KEQoaIEH1whj9oUxlk2kWU5G8daG5uUjMCIBW6fwv0cbYmyzspCMqJ
+eib1vgFnhGTI44K05cunpXJ+
+-----END CERTIFICATE-----`
+
+// openssl genpkey -out key2.pem -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve
+const key2PEM = `-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgUbNm7exkEPKMv/DT
+B3UMv6XevnD2E99W3Rp8L/a1v12hRANCAATqnza9H9uNa8EvoKm2GOLvw25kR7OA
+oVHZiyaXdBeB480FBUtRmWUukLZFxp/QStd4OCwaOwWGtXGlspM2LEum
+-----END PRIVATE KEY-----`
+
+// openssl req -out cert2.pem -x509 -new -nodes -key key2.pem -days 1 -subj "/CN=meek-server.example.com"
+const cert2PEM = `-----BEGIN CERTIFICATE-----
+MIIBjzCCATSgAwIBAgIJAKvKZ4vqwpySMAoGCCqGSM49BAMCMCIxIDAeBgNVBAMM
+F21lZWstc2VydmVyLmV4YW1wbGUuY29tMB4XDTE3MDMyMTIyNTQzN1oXDTE3MDMy
+MjIyNTQzN1owIjEgMB4GA1UEAwwXbWVlay1zZXJ2ZXIuZXhhbXBsZS5jb20wWTAT
+BgcqhkjOPQIBBggqhkjOPQMBBwNCAATqnza9H9uNa8EvoKm2GOLvw25kR7OAoVHZ
+iyaXdBeB480FBUtRmWUukLZFxp/QStd4OCwaOwWGtXGlspM2LEumo1MwUTAdBgNV
+HQ4EFgQUdqTMumWa7f965k/SLgWJT0tIlcswHwYDVR0jBBgwFoAUdqTMumWa7f96
+5k/SLgWJT0tIlcswDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNJADBGAiEA
+yOhMqBPZLpqnjTGD1OgOc1N1SkS53bdWAazAZgLgwvICIQC7hFOK1j74Frh+1l3h
+kLn8Jbjpp6jzNBhQhVHF/nj3CQ==
+-----END CERTIFICATE-----`
+
+const badSyntax = "hello world\n"
+
+// Backdate file times so any modifications will make them appear different;
+// otherwise immediately overwriting the files may happen quickly enough that
+// the times don't change.
+func backdateFile(filename string) error {
+ info, err := os.Stat(filename)
+ if err != nil {
+ return err
+ }
+ timestamp := info.ModTime().Add(-10 * time.Second)
+ return os.Chtimes(filename, timestamp, timestamp)
+}
+
+func mustBackdateFile(filename string) {
+ err := backdateFile(filename)
+ if err != nil {
+ panic(err)
+ }
+}
+
+// Return the path to a newly created temporary file with the given contents.
+func makeTempFileFromContents(contents []byte) (string, error) {
+ f, err := ioutil.TempFile("", "meek-server-certificate-test-")
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ _, err = f.Write(contents)
+ if err != nil {
+ return f.Name(), err
+ }
+
+ err = backdateFile(f.Name())
+
+ return f.Name(), err
+}
+
+func mustMakeTempFileFromContents(contents []byte) string {
+ f, err := makeTempFileFromContents(contents)
+ if err != nil {
+ panic(err)
+ }
+ return f
+}
+
+// Return a random filename that is unlikely to exist.
+func makeNonexistentFilename() string {
+ bytes := make([]byte, 8)
+ _, err := rand.Read(bytes)
+ if err != nil {
+ panic(err)
+ }
+ return filepath.Join(os.TempDir(), hex.EncodeToString(bytes))
+}
+
+// Call tls.X509KeyPair and panic if it fails.
+func mustLoadCertificate(certPEM, keyPEM []byte) *tls.Certificate {
+ cert, err := tls.X509KeyPair(certPEM, keyPEM)
+ if err != nil {
+ panic(err)
+ }
+ return &cert
+}
+
+func mustWriteFile(filename string, data []byte) {
+ err := ioutil.WriteFile(filename, data, 0600)
+ if err != nil {
+ panic(err)
+ }
+}
+
+// A bunch of temporary certificate filenames and the like.
+type testFiles struct {
+ cert1 *tls.Certificate
+ cert2 *tls.Certificate
+
+ key1Filename string
+ key2Filename string
+ cert1Filename string
+ cert2Filename string
+ badSyntaxFilename string
+ nonexistentFilename string
+}
+
+func loadTestFiles() *testFiles {
+ var files testFiles
+
+ files.cert1 = mustLoadCertificate([]byte(cert1PEM), []byte(key1PEM))
+ files.cert2 = mustLoadCertificate([]byte(cert2PEM), []byte(key2PEM))
+
+ files.key1Filename = mustMakeTempFileFromContents([]byte(key1PEM))
+ files.key2Filename = mustMakeTempFileFromContents([]byte(key2PEM))
+ files.cert1Filename = mustMakeTempFileFromContents([]byte(cert1PEM))
+ files.cert2Filename = mustMakeTempFileFromContents([]byte(cert2PEM))
+ files.badSyntaxFilename = mustMakeTempFileFromContents([]byte(badSyntax))
+ files.nonexistentFilename = makeNonexistentFilename()
+
+ return &files
+}
+
+// Delete temporary files created by loadTestFiles (to be called in a defer
+// handler).
+func (files *testFiles) Cleanup() {
+ os.Remove(files.key1Filename)
+ os.Remove(files.key2Filename)
+ os.Remove(files.cert1Filename)
+ os.Remove(files.cert2Filename)
+ os.Remove(files.badSyntaxFilename)
+}
+
+// Check if two certificate chains are equal.
+func certificatesEqual(cert1, cert2 *tls.Certificate) bool {
+ if len(cert1.Certificate) != len(cert2.Certificate) {
+ return false
+ }
+ for i := range cert1.Certificate {
+ if !bytes.Equal(cert1.Certificate[i], cert2.Certificate[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// Call ctx.reloadCertificate and check if the certificate and error status are
+// as expected.
+func checkCertificate(t *testing.T, ctx *certContext, expectedCert *tls.Certificate, expectedError bool) {
+ cert, err := ctx.reloadCertificate()
+ if expectedError && err == nil {
+ t.Errorf("expected error, got %v\n", err)
+ } else if !expectedError && err != nil {
+ t.Errorf("expected no error, got %v\n", err)
+ }
+ if !certificatesEqual(cert, expectedCert) {
+ t.Errorf("certificate was other than expected")
+ }
+}
+
+func TestNewCertContext(t *testing.T) {
+ files := loadTestFiles()
+ defer files.Cleanup()
+
+ var ctx *certContext
+ var err error
+
+ // Test with one or both files nonexistent.
+ ctx, err = newCertContext(files.nonexistentFilename, files.nonexistentFilename)
+ if err == nil {
+ t.Errorf("did not raise error on nonexistent cert and key")
+ }
+ ctx, err = newCertContext(files.cert1Filename, files.nonexistentFilename)
+ if err == nil {
+ t.Errorf("did not raise error on nonexistent cert")
+ }
+ ctx, err = newCertContext(files.nonexistentFilename, files.key1Filename)
+ if err == nil {
+ t.Errorf("did not raise error on nonexistent key")
+ }
+
+ // Test with bad syntax.
+ ctx, err = newCertContext(files.badSyntaxFilename, files.badSyntaxFilename)
+ if err == nil {
+ t.Errorf("did not raise error on bad-syntax cert and key")
+ }
+ ctx, err = newCertContext(files.cert1Filename, files.badSyntaxFilename)
+ if err == nil {
+ t.Errorf("did not raise error on bad-syntax cert")
+ }
+ ctx, err = newCertContext(files.badSyntaxFilename, files.key1Filename)
+ if err == nil {
+ t.Errorf("did not raise error on bad-syntax key")
+ }
+
+ // Test with certificate and key that don't match.
+ ctx, err = newCertContext(files.cert1Filename, files.key2Filename)
+ if err == nil {
+ t.Errorf("did not raise error on mismatched cert and key")
+ }
+ ctx, err = newCertContext(files.cert2Filename, files.key1Filename)
+ if err == nil {
+ t.Errorf("did not raise error on mismatched cert and key")
+ }
+
+ // Test with everything good.
+ ctx, err = newCertContext(files.cert1Filename, files.key1Filename)
+ if ctx == nil || err != nil {
+ t.Fatalf("raised an error: %s", err)
+ }
+}
+
+// Test that reloadCertificate continues returning the old certificate if files
+// are deleted.
+func TestDelete(t *testing.T) {
+ files := loadTestFiles()
+ defer files.Cleanup()
+
+ var ctx *certContext
+ var err error
+
+ ctx, err = newCertContext(files.cert1Filename, files.key1Filename)
+ if ctx == nil || err != nil {
+ t.Fatalf("raised an error: %s", err)
+ }
+ checkCertificate(t, ctx, files.cert1, false)
+ // Try removing the cert file; cert should be the same but now raise an error.
+ os.Remove(files.cert1Filename)
+ checkCertificate(t, ctx, files.cert1, true)
+
+ ctx, err = newCertContext(files.cert2Filename, files.key2Filename)
+ if ctx == nil || err != nil {
+ t.Fatalf("raised an error: %s", err)
+ }
+ checkCertificate(t, ctx, files.cert2, false)
+ // Try removing the key file; cert should be the same but now raise an error.
+ os.Remove(files.key2Filename)
+ checkCertificate(t, ctx, files.cert2, true)
+}
+
+// Test replacing the contents of cert and key files.
+func TestReplace(t *testing.T) {
+ files := loadTestFiles()
+ defer files.Cleanup()
+
+ var ctx *certContext
+ var err error
+
+ ctx, err = newCertContext(files.cert1Filename, files.key1Filename)
+ if ctx == nil || err != nil {
+ t.Fatalf("raised an error: %s", err)
+ }
+ checkCertificate(t, ctx, files.cert1, false)
+
+ // Replace cert file with junk.
+ mustWriteFile(files.cert1Filename, []byte(badSyntax))
+ checkCertificate(t, ctx, files.cert1, true)
+ // Put it back to normal.
+ mustWriteFile(files.cert1Filename, []byte(cert1PEM))
+ checkCertificate(t, ctx, files.cert1, false)
+
+ // Replace key file with junk.
+ mustWriteFile(files.key1Filename, []byte(badSyntax))
+ checkCertificate(t, ctx, files.cert1, true)
+ // Put it back to normal.
+ mustWriteFile(files.key1Filename, []byte(key1PEM))
+ checkCertificate(t, ctx, files.cert1, false)
+
+ mustBackdateFile(files.cert1Filename)
+ mustBackdateFile(files.key1Filename)
+ checkCertificate(t, ctx, files.cert1, false)
+
+ // Replace cert1 with cert2 contents; expect to still get cert1, with an error.
+ mustWriteFile(files.cert1Filename, []byte(cert2PEM))
+ checkCertificate(t, ctx, files.cert1, true)
+ // Replace key1 with key2 contents; now we expect to be using cert2.
+ mustWriteFile(files.key1Filename, []byte(key2PEM))
+ checkCertificate(t, ctx, files.cert2, false)
+}