...

Source file src/github.com/letsencrypt/boulder/publisher/publisher_test.go

Documentation: github.com/letsencrypt/boulder/publisher

     1  package publisher
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"crypto/elliptic"
     7  	"crypto/rand"
     8  	"crypto/x509"
     9  	"crypto/x509/pkix"
    10  	"encoding/asn1"
    11  	"encoding/base64"
    12  	"encoding/json"
    13  	"fmt"
    14  	"math/big"
    15  	"net"
    16  	"net/http"
    17  	"net/http/httptest"
    18  	"net/url"
    19  	"strconv"
    20  	"strings"
    21  	"sync/atomic"
    22  	"testing"
    23  	"time"
    24  
    25  	ct "github.com/google/certificate-transparency-go"
    26  	"github.com/prometheus/client_golang/prometheus"
    27  
    28  	"github.com/letsencrypt/boulder/core"
    29  	"github.com/letsencrypt/boulder/issuance"
    30  	blog "github.com/letsencrypt/boulder/log"
    31  	"github.com/letsencrypt/boulder/metrics"
    32  	pubpb "github.com/letsencrypt/boulder/publisher/proto"
    33  	"github.com/letsencrypt/boulder/test"
    34  )
    35  
    36  func TestImplementation(t *testing.T) {
    37  	test.AssertImplementsGRPCServer(t, &Impl{}, pubpb.UnimplementedPublisherServer{})
    38  }
    39  
    40  var log = blog.UseMock()
    41  var ctx = context.Background()
    42  
    43  func getPort(srvURL string) (int, error) {
    44  	url, err := url.Parse(srvURL)
    45  	if err != nil {
    46  		return 0, err
    47  	}
    48  	_, portString, err := net.SplitHostPort(url.Host)
    49  	if err != nil {
    50  		return 0, err
    51  	}
    52  	port, err := strconv.ParseInt(portString, 10, 64)
    53  	if err != nil {
    54  		return 0, err
    55  	}
    56  	return int(port), nil
    57  }
    58  
    59  type testLogSrv struct {
    60  	*httptest.Server
    61  	submissions int64
    62  }
    63  
    64  func logSrv(k *ecdsa.PrivateKey) *testLogSrv {
    65  	testLog := &testLogSrv{}
    66  	m := http.NewServeMux()
    67  	m.HandleFunc("/ct/", func(w http.ResponseWriter, r *http.Request) {
    68  		decoder := json.NewDecoder(r.Body)
    69  		var jsonReq ctSubmissionRequest
    70  		err := decoder.Decode(&jsonReq)
    71  		if err != nil {
    72  			return
    73  		}
    74  		precert := false
    75  		if r.URL.Path == "/ct/v1/add-pre-chain" {
    76  			precert = true
    77  		}
    78  		sct := CreateTestingSignedSCT(jsonReq.Chain, k, precert, time.Now())
    79  		fmt.Fprint(w, string(sct))
    80  		atomic.AddInt64(&testLog.submissions, 1)
    81  	})
    82  
    83  	testLog.Server = httptest.NewUnstartedServer(m)
    84  	testLog.Server.Start()
    85  	return testLog
    86  }
    87  
    88  // lyingLogSrv always signs SCTs with the timestamp it was given.
    89  func lyingLogSrv(k *ecdsa.PrivateKey, timestamp time.Time) *testLogSrv {
    90  	testLog := &testLogSrv{}
    91  	m := http.NewServeMux()
    92  	m.HandleFunc("/ct/", func(w http.ResponseWriter, r *http.Request) {
    93  		decoder := json.NewDecoder(r.Body)
    94  		var jsonReq ctSubmissionRequest
    95  		err := decoder.Decode(&jsonReq)
    96  		if err != nil {
    97  			return
    98  		}
    99  		precert := false
   100  		if r.URL.Path == "/ct/v1/add-pre-chain" {
   101  			precert = true
   102  		}
   103  		sct := CreateTestingSignedSCT(jsonReq.Chain, k, precert, timestamp)
   104  		fmt.Fprint(w, string(sct))
   105  		atomic.AddInt64(&testLog.submissions, 1)
   106  	})
   107  
   108  	testLog.Server = httptest.NewUnstartedServer(m)
   109  	testLog.Server.Start()
   110  	return testLog
   111  }
   112  
   113  func errorBodyLogSrv() *httptest.Server {
   114  	m := http.NewServeMux()
   115  	m.HandleFunc("/ct/", func(w http.ResponseWriter, r *http.Request) {
   116  		w.WriteHeader(http.StatusBadRequest)
   117  		w.Write([]byte("well this isn't good now is it."))
   118  	})
   119  
   120  	server := httptest.NewUnstartedServer(m)
   121  	server.Start()
   122  	return server
   123  }
   124  
   125  func setup(t *testing.T) (*Impl, *x509.Certificate, *ecdsa.PrivateKey) {
   126  	// Load chain: R3 <- Root DST
   127  	chain1, err := issuance.LoadChain([]string{
   128  		"../test/hierarchy/int-r3-cross.cert.pem",
   129  		"../test/hierarchy/root-dst.cert.pem",
   130  	})
   131  	test.AssertNotError(t, err, "failed to load chain1.")
   132  
   133  	// Load chain: R3 <- Root X1
   134  	chain2, err := issuance.LoadChain([]string{
   135  		"../test/hierarchy/int-r3.cert.pem",
   136  		"../test/hierarchy/root-x1.cert.pem",
   137  	})
   138  	test.AssertNotError(t, err, "failed to load chain2.")
   139  
   140  	// Load chain: E1 <- Root X2
   141  	chain3, err := issuance.LoadChain([]string{
   142  		"../test/hierarchy/int-e1.cert.pem",
   143  		"../test/hierarchy/root-x2.cert.pem",
   144  	})
   145  	test.AssertNotError(t, err, "failed to load chain3.")
   146  
   147  	// Create an example issuerNameID to CT bundle mapping
   148  	issuerBundles := map[issuance.IssuerNameID][]ct.ASN1Cert{
   149  		chain1[0].NameID(): GetCTBundleForChain(chain1),
   150  		chain2[0].NameID(): GetCTBundleForChain(chain2),
   151  		chain3[0].NameID(): GetCTBundleForChain(chain3),
   152  	}
   153  	pub := New(
   154  		issuerBundles,
   155  		"test-user-agent/1.0",
   156  		log,
   157  		metrics.NoopRegisterer)
   158  
   159  	// Load leaf certificate
   160  	leaf, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
   161  	test.AssertNotError(t, err, "unable to load leaf certificate.")
   162  
   163  	k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   164  	test.AssertNotError(t, err, "Couldn't generate test key")
   165  
   166  	return pub, leaf, k
   167  }
   168  
   169  func addLog(t *testing.T, port int, pubKey *ecdsa.PublicKey) *Log {
   170  	uri := fmt.Sprintf("http://localhost:%d", port)
   171  	der, err := x509.MarshalPKIXPublicKey(pubKey)
   172  	test.AssertNotError(t, err, "Failed to marshal key")
   173  	newLog, err := NewLog(uri, base64.StdEncoding.EncodeToString(der), "test-user-agent/1.0", log)
   174  	test.AssertNotError(t, err, "Couldn't create log")
   175  	test.AssertEquals(t, newLog.uri, fmt.Sprintf("http://localhost:%d", port))
   176  	return newLog
   177  }
   178  
   179  func makePrecert(k *ecdsa.PrivateKey) (map[issuance.IssuerNameID][]ct.ASN1Cert, []byte, error) {
   180  	rootTmpl := x509.Certificate{
   181  		SerialNumber:          big.NewInt(0),
   182  		Subject:               pkix.Name{CommonName: "root"},
   183  		BasicConstraintsValid: true,
   184  		IsCA:                  true,
   185  	}
   186  	rootBytes, err := x509.CreateCertificate(rand.Reader, &rootTmpl, &rootTmpl, k.Public(), k)
   187  	if err != nil {
   188  		return nil, nil, err
   189  	}
   190  	root, err := x509.ParseCertificate(rootBytes)
   191  	if err != nil {
   192  		return nil, nil, err
   193  	}
   194  	precertTmpl := x509.Certificate{
   195  		SerialNumber: big.NewInt(0),
   196  		ExtraExtensions: []pkix.Extension{
   197  			{Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}, Critical: true, Value: []byte{0x05, 0x00}},
   198  		},
   199  	}
   200  	precert, err := x509.CreateCertificate(rand.Reader, &precertTmpl, root, k.Public(), k)
   201  	if err != nil {
   202  		return nil, nil, err
   203  	}
   204  	precertX509, err := x509.ParseCertificate(precert)
   205  	if err != nil {
   206  		return nil, nil, err
   207  	}
   208  	precertIssuerNameID := issuance.GetIssuerNameID(precertX509)
   209  	bundles := map[issuance.IssuerNameID][]ct.ASN1Cert{
   210  		precertIssuerNameID: {
   211  			ct.ASN1Cert{Data: rootBytes},
   212  		},
   213  	}
   214  	return bundles, precert, err
   215  }
   216  
   217  func TestTimestampVerificationFuture(t *testing.T) {
   218  	pub, _, k := setup(t)
   219  
   220  	server := lyingLogSrv(k, time.Now().Add(time.Hour))
   221  	defer server.Close()
   222  	port, err := getPort(server.URL)
   223  	test.AssertNotError(t, err, "Failed to get test server port")
   224  	testLog := addLog(t, port, &k.PublicKey)
   225  
   226  	// Precert
   227  	issuerBundles, precert, err := makePrecert(k)
   228  	test.AssertNotError(t, err, "Failed to create test leaf")
   229  	pub.issuerBundles = issuerBundles
   230  
   231  	_, err = pub.SubmitToSingleCTWithResult(ctx, &pubpb.Request{LogURL: testLog.uri, LogPublicKey: testLog.logID, Der: precert, Precert: true})
   232  	if err == nil {
   233  		t.Fatal("Expected error for lying log server, got none")
   234  	}
   235  	if !strings.HasPrefix(err.Error(), "SCT Timestamp was too far in the future") {
   236  		t.Fatalf("Got wrong error: %s", err)
   237  	}
   238  }
   239  
   240  func TestTimestampVerificationPast(t *testing.T) {
   241  	pub, _, k := setup(t)
   242  
   243  	server := lyingLogSrv(k, time.Now().Add(-time.Hour))
   244  	defer server.Close()
   245  	port, err := getPort(server.URL)
   246  	test.AssertNotError(t, err, "Failed to get test server port")
   247  	testLog := addLog(t, port, &k.PublicKey)
   248  
   249  	// Precert
   250  	issuerBundles, precert, err := makePrecert(k)
   251  	test.AssertNotError(t, err, "Failed to create test leaf")
   252  
   253  	pub.issuerBundles = issuerBundles
   254  
   255  	_, err = pub.SubmitToSingleCTWithResult(ctx, &pubpb.Request{LogURL: testLog.uri, LogPublicKey: testLog.logID, Der: precert, Precert: true})
   256  	if err == nil {
   257  		t.Fatal("Expected error for lying log server, got none")
   258  	}
   259  	if !strings.HasPrefix(err.Error(), "SCT Timestamp was too far in the past") {
   260  		t.Fatalf("Got wrong error: %s", err)
   261  	}
   262  }
   263  
   264  func TestLogCache(t *testing.T) {
   265  	cache := logCache{
   266  		logs: make(map[string]*Log),
   267  	}
   268  
   269  	// Adding a log with an invalid base64 public key should error
   270  	_, err := cache.AddLog("www.test.com", "1234", "test-user-agent/1.0", log)
   271  	test.AssertError(t, err, "AddLog() with invalid base64 pk didn't error")
   272  
   273  	// Adding a log with an invalid URI should error
   274  	_, err = cache.AddLog(":", "", "test-user-agent/1.0", log)
   275  	test.AssertError(t, err, "AddLog() with an invalid log URI didn't error")
   276  
   277  	// Create one keypair & base 64 public key
   278  	k1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   279  	test.AssertNotError(t, err, "ecdsa.GenerateKey() failed for k1")
   280  	der1, err := x509.MarshalPKIXPublicKey(&k1.PublicKey)
   281  	test.AssertNotError(t, err, "x509.MarshalPKIXPublicKey(der1) failed")
   282  	k1b64 := base64.StdEncoding.EncodeToString(der1)
   283  
   284  	// Create a second keypair & base64 public key
   285  	k2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   286  	test.AssertNotError(t, err, "ecdsa.GenerateKey() failed for k2")
   287  	der2, err := x509.MarshalPKIXPublicKey(&k2.PublicKey)
   288  	test.AssertNotError(t, err, "x509.MarshalPKIXPublicKey(der2) failed")
   289  	k2b64 := base64.StdEncoding.EncodeToString(der2)
   290  
   291  	// Adding the first log should not produce an error
   292  	l1, err := cache.AddLog("http://log.one.example.com", k1b64, "test-user-agent/1.0", log)
   293  	test.AssertNotError(t, err, "cache.AddLog() failed for log 1")
   294  	test.AssertEquals(t, cache.Len(), 1)
   295  	test.AssertEquals(t, l1.uri, "http://log.one.example.com")
   296  	test.AssertEquals(t, l1.logID, k1b64)
   297  
   298  	// Adding it again should not produce any errors, or increase the Len()
   299  	l1, err = cache.AddLog("http://log.one.example.com", k1b64, "test-user-agent/1.0", log)
   300  	test.AssertNotError(t, err, "cache.AddLog() failed for second add of log 1")
   301  	test.AssertEquals(t, cache.Len(), 1)
   302  	test.AssertEquals(t, l1.uri, "http://log.one.example.com")
   303  	test.AssertEquals(t, l1.logID, k1b64)
   304  
   305  	// Adding a second log should not error and should increase the Len()
   306  	l2, err := cache.AddLog("http://log.two.example.com", k2b64, "test-user-agent/1.0", log)
   307  	test.AssertNotError(t, err, "cache.AddLog() failed for log 2")
   308  	test.AssertEquals(t, cache.Len(), 2)
   309  	test.AssertEquals(t, l2.uri, "http://log.two.example.com")
   310  	test.AssertEquals(t, l2.logID, k2b64)
   311  }
   312  
   313  func TestLogErrorBody(t *testing.T) {
   314  	pub, leaf, k := setup(t)
   315  
   316  	srv := errorBodyLogSrv()
   317  	defer srv.Close()
   318  	port, err := getPort(srv.URL)
   319  	test.AssertNotError(t, err, "Failed to get test server port")
   320  
   321  	log.Clear()
   322  	logURI := fmt.Sprintf("http://localhost:%d", port)
   323  	pkDER, err := x509.MarshalPKIXPublicKey(&k.PublicKey)
   324  	test.AssertNotError(t, err, "Failed to marshal key")
   325  	pkB64 := base64.StdEncoding.EncodeToString(pkDER)
   326  	_, err = pub.SubmitToSingleCTWithResult(context.Background(), &pubpb.Request{
   327  		LogURL:       logURI,
   328  		LogPublicKey: pkB64,
   329  		Der:          leaf.Raw,
   330  	})
   331  	test.AssertError(t, err, "SubmitToSingleCTWithResult didn't fail")
   332  	test.AssertEquals(t, len(log.GetAllMatching("well this isn't good now is it")), 1)
   333  }
   334  
   335  func TestHTTPStatusMetric(t *testing.T) {
   336  	pub, leaf, k := setup(t)
   337  
   338  	badSrv := errorBodyLogSrv()
   339  	defer badSrv.Close()
   340  	port, err := getPort(badSrv.URL)
   341  	test.AssertNotError(t, err, "Failed to get test server port")
   342  	logURI := fmt.Sprintf("http://localhost:%d", port)
   343  
   344  	pkDER, err := x509.MarshalPKIXPublicKey(&k.PublicKey)
   345  	test.AssertNotError(t, err, "Failed to marshal key")
   346  	pkB64 := base64.StdEncoding.EncodeToString(pkDER)
   347  	_, err = pub.SubmitToSingleCTWithResult(context.Background(), &pubpb.Request{
   348  		LogURL:       logURI,
   349  		LogPublicKey: pkB64,
   350  		Der:          leaf.Raw,
   351  	})
   352  	test.AssertError(t, err, "SubmitToSingleCTWithResult didn't fail")
   353  	test.AssertMetricWithLabelsEquals(t, pub.metrics.submissionLatency, prometheus.Labels{
   354  		"log":         logURI,
   355  		"status":      "error",
   356  		"http_status": "400",
   357  	}, 1)
   358  
   359  	pub, leaf, k = setup(t)
   360  	pkDER, err = x509.MarshalPKIXPublicKey(&k.PublicKey)
   361  	test.AssertNotError(t, err, "Failed to marshal key")
   362  	pkB64 = base64.StdEncoding.EncodeToString(pkDER)
   363  	workingSrv := logSrv(k)
   364  	defer workingSrv.Close()
   365  	port, err = getPort(workingSrv.URL)
   366  	test.AssertNotError(t, err, "Failed to get test server port")
   367  	logURI = fmt.Sprintf("http://localhost:%d", port)
   368  
   369  	_, err = pub.SubmitToSingleCTWithResult(context.Background(), &pubpb.Request{
   370  		LogURL:       logURI,
   371  		LogPublicKey: pkB64,
   372  		Der:          leaf.Raw,
   373  	})
   374  	test.AssertNotError(t, err, "SubmitToSingleCTWithResult failed")
   375  	test.AssertMetricWithLabelsEquals(t, pub.metrics.submissionLatency, prometheus.Labels{
   376  		"log":         logURI,
   377  		"status":      "success",
   378  		"http_status": "",
   379  	}, 1)
   380  }
   381  func Test_GetCTBundleForChain(t *testing.T) {
   382  	chain, err := issuance.LoadChain([]string{
   383  		"../test/hierarchy/int-r3.cert.pem",
   384  		"../test/hierarchy/root-x1.cert.pem",
   385  	})
   386  	test.AssertNotError(t, err, "Failed to load chain.")
   387  	expect := []ct.ASN1Cert{{Data: chain[0].Raw}}
   388  	type args struct {
   389  		chain []*issuance.Certificate
   390  	}
   391  	tests := []struct {
   392  		name string
   393  		args args
   394  		want []ct.ASN1Cert
   395  	}{
   396  		{"Create a ct bundle with a single intermediate", args{chain}, expect},
   397  	}
   398  	for _, tt := range tests {
   399  		t.Run(tt.name, func(t *testing.T) {
   400  			bundle := GetCTBundleForChain(tt.args.chain)
   401  			test.AssertDeepEquals(t, bundle, tt.want)
   402  		})
   403  	}
   404  }
   405  

View as plain text