490 lines
17 KiB
Go
490 lines
17 KiB
Go
package publisher
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/asn1"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"math/big"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
ct "github.com/google/certificate-transparency-go"
|
|
ctTLS "github.com/google/certificate-transparency-go/tls"
|
|
"github.com/jmhodges/clock"
|
|
"golang.org/x/net/context"
|
|
|
|
blog "github.com/letsencrypt/boulder/log"
|
|
"github.com/letsencrypt/boulder/metrics"
|
|
"github.com/letsencrypt/boulder/metrics/mock_metrics"
|
|
"github.com/letsencrypt/boulder/mocks"
|
|
"github.com/letsencrypt/boulder/test"
|
|
)
|
|
|
|
var testLeaf = `-----BEGIN CERTIFICATE-----
|
|
MIIHAjCCBeqgAwIBAgIQfwAAAQAAAUtRVNy9a8fMcDANBgkqhkiG9w0BAQsFADBa
|
|
MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MRcwFQYDVQQLEw5UcnVz
|
|
dElEIFNlcnZlcjEeMBwGA1UEAxMVVHJ1c3RJRCBTZXJ2ZXIgQ0EgQTUyMB4XDTE1
|
|
MDIwMzIxMjQ1MVoXDTE4MDIwMjIxMjQ1MVowfzEYMBYGA1UEAxMPbGV0c2VuY3J5
|
|
cHQub3JnMSkwJwYDVQQKEyBJTlRFUk5FVCBTRUNVUklUWSBSRVNFQVJDSCBHUk9V
|
|
UDEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzETMBEGA1UECBMKQ2FsaWZvcm5pYTEL
|
|
MAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGE6T8
|
|
LcmS6g8lH/1Y5orXeZOva4gthrS+VmJUWlz3K4Er5q8CmVFTmD/rYL6tA31JYCAi
|
|
p2bVQ8z/PgWYGosuMzox2OO9MqnLwTTG074sCHTZi4foFb6KacS8xVu25u8RRBd8
|
|
1WJNlw736FO0pJUkkE3gDSPz1QTpw3gc6n7SyppaFr40D5PpK3PPoNCPfoz2bFtH
|
|
m2KRsUH924LRfitUZdI68kxJP7QG1SAbdZxA/qDcfvDSgCYW5WNmMKS4v+GHuMkJ
|
|
gBe20tML+hItmF5S9mYm/GbkFLG8YwWZrytUZrSjxmuL9nj3MaBrAPQw3/T582ry
|
|
KM8+z188kbnA7A+BAgMBAAGjggOdMIIDmTAOBgNVHQ8BAf8EBAMCBaAwggInBgNV
|
|
HSAEggIeMIICGjCCAQsGCmCGSAGG+S8ABgMwgfwwQAYIKwYBBQUHAgEWNGh0dHBz
|
|
Oi8vc2VjdXJlLmlkZW50cnVzdC5jb20vY2VydGlmaWNhdGVzL3BvbGljeS90cy8w
|
|
gbcGCCsGAQUFBwICMIGqGoGnVGhpcyBUcnVzdElEIFNlcnZlciBDZXJ0aWZpY2F0
|
|
ZSBoYXMgYmVlbiBpc3N1ZWQgaW4gYWNjb3JkYW5jZSB3aXRoIElkZW5UcnVzdCdz
|
|
IFRydXN0SUQgQ2VydGlmaWNhdGUgUG9saWN5IGZvdW5kIGF0IGh0dHBzOi8vc2Vj
|
|
dXJlLmlkZW50cnVzdC5jb20vY2VydGlmaWNhdGVzL3BvbGljeS90cy8wggEHBgZn
|
|
gQwBAgIwgfwwQAYIKwYBBQUHAgEWNGh0dHBzOi8vc2VjdXJlLmlkZW50cnVzdC5j
|
|
b20vY2VydGlmaWNhdGVzL3BvbGljeS90cy8wgbcGCCsGAQUFBwICMIGqGoGnVGhp
|
|
cyBUcnVzdElEIFNlcnZlciBDZXJ0aWZpY2F0ZSBoYXMgYmVlbiBpc3N1ZWQgaW4g
|
|
YWNjb3JkYW5jZSB3aXRoIElkZW5UcnVzdCdzIFRydXN0SUQgQ2VydGlmaWNhdGUg
|
|
UG9saWN5IGZvdW5kIGF0IGh0dHBzOi8vc2VjdXJlLmlkZW50cnVzdC5jb20vY2Vy
|
|
dGlmaWNhdGVzL3BvbGljeS90cy8wHQYDVR0OBBYEFNLAuFI2ugD0U24OgEPtX6+p
|
|
/xJQMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly92YWxpZGF0aW9uLmlkZW50cnVz
|
|
dC5jb20vY3JsL3RydXN0aWRjYWE1Mi5jcmwwgYQGCCsGAQUFBwEBBHgwdjAwBggr
|
|
BgEFBQcwAYYkaHR0cDovL2NvbW1lcmNpYWwub2NzcC5pZGVudHJ1c3QuY29tMEIG
|
|
CCsGAQUFBzAChjZodHRwOi8vdmFsaWRhdGlvbi5pZGVudHJ1c3QuY29tL2NlcnRz
|
|
L3RydXN0aWRjYWE1Mi5wN2MwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC
|
|
MB8GA1UdIwQYMBaAFKJWJDzQ1BW56L94oxMQWEguFlThMC8GA1UdEQQoMCaCD2xl
|
|
dHNlbmNyeXB0Lm9yZ4ITd3d3LmxldHNlbmNyeXB0Lm9yZzANBgkqhkiG9w0BAQsF
|
|
AAOCAQEAgEmnzpYncB/E5SCHa5cnGorvNNE6Xsp3YXK9fJBT2++chQTkyFYpE12T
|
|
TR+cb7CTdRiYErNHXV8Hl/XTK8mxGxK8KXM9zUDlfrl7yBnyGTl2Sk8qJwA2kGuu
|
|
X9KA1o3MFkKMD809ITAlvPoQpml1Ke0aFo4NLO/LJKnJpkyF8L+JQrkfLNHpKYn3
|
|
PvnyJnurVTXDOIwQw8HVXbw6UKAad87e1hKGLYOpsaaKCLaNw1vg8uI+O9mv1MC6
|
|
FTfP1pSlr11s+Ih4YancuJud41rT8lXCUbDs1Uws9pPdVzLt8zk5M0vbHmTCljbg
|
|
UC5XkUmEvadMfgWslIQD0r6+BRRS+A==
|
|
-----END CERTIFICATE-----`
|
|
|
|
var testIntermediate = `-----BEGIN CERTIFICATE-----
|
|
MIIG3zCCBMegAwIBAgIQAJv84kD9Vb7ZJp4MASwbdzANBgkqhkiG9w0BAQsFADBK
|
|
MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu
|
|
VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMzIwMTgwNTM4WhcNMjIw
|
|
MzIwMTgwNTM4WjBaMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MRcw
|
|
FQYDVQQLEw5UcnVzdElEIFNlcnZlcjEeMBwGA1UEAxMVVHJ1c3RJRCBTZXJ2ZXIg
|
|
Q0EgQTUyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl2nXmZiFAj/p
|
|
JkJ26PRzP6kyRCaQeC54V5EZoF12K0n5k1pdWs6C88LY5Uw2eisdDdump/6REnzt
|
|
cgG3jKHF2syd/gn7V+IURw/onpGPlC2AMpOTA/UoeGi6fg9CtDF6BRQiUzPko61s
|
|
j6++Y2uyMp/ZF7nJ4GB8mdYx4eSgtz+vsjKsfoyc3ALr4bwfFJy8kfey+0Lz4SAr
|
|
y7+P87NwY/r3dSgCq8XUsO3qJX+HzTcUloM8QAIboJ4ZR3/zsMzFJWC4NRLxUesX
|
|
3Pxbpdmb70BM13dx6ftFi37y42mwQmYXRpA6zUY98bAJb9z/7jNhyvzHLjztXgrR
|
|
vyISaYBLIwIDAQABo4ICrzCCAqswgYkGCCsGAQUFBwEBBH0wezAwBggrBgEFBQcw
|
|
AYYkaHR0cDovL2NvbW1lcmNpYWwub2NzcC5pZGVudHJ1c3QuY29tMEcGCCsGAQUF
|
|
BzAChjtodHRwOi8vdmFsaWRhdGlvbi5pZGVudHJ1c3QuY29tL3Jvb3RzL2NvbW1l
|
|
cmNpYWxyb290Y2ExLnA3YzAfBgNVHSMEGDAWgBTtRBnA0/AGi+6ke75C5yZUyI42
|
|
djAPBgNVHRMBAf8EBTADAQH/MIIBMQYDVR0gBIIBKDCCASQwggEgBgRVHSAAMIIB
|
|
FjBQBggrBgEFBQcCAjBEMEIWPmh0dHBzOi8vc2VjdXJlLmlkZW50cnVzdC5jb20v
|
|
Y2VydGlmaWNhdGVzL3BvbGljeS90cy9pbmRleC5odG1sMAAwgcEGCCsGAQUFBwIC
|
|
MIG0GoGxVGhpcyBUcnVzdElEIFNlcnZlciBDZXJ0aWZpY2F0ZSBoYXMgYmVlbiBp
|
|
c3N1ZWQgaW4gYWNjb3JkYW5jZSB3aXRoIElkZW5UcnVzdCdzIFRydXN0SUQgQ2Vy
|
|
dGlmaWNhdGUgUG9saWN5IGZvdW5kIGF0IGh0dHBzOi8vc2VjdXJlLmlkZW50cnVz
|
|
dC5jb20vY2VydGlmaWNhdGVzL3BvbGljeS90cy9pbmRleC5odG1sMEoGA1UdHwRD
|
|
MEEwP6A9oDuGOWh0dHA6Ly92YWxpZGF0aW9uLmlkZW50cnVzdC5jb20vY3JsL2Nv
|
|
bW1lcmNpYWxyb290Y2ExLmNybDA7BgNVHSUENDAyBggrBgEFBQcDAQYIKwYBBQUH
|
|
AwIGCCsGAQUFBwMFBggrBgEFBQcDBgYIKwYBBQUHAwcwDgYDVR0PAQH/BAQDAgGG
|
|
MB0GA1UdDgQWBBSiViQ80NQVuei/eKMTEFhILhZU4TANBgkqhkiG9w0BAQsFAAOC
|
|
AgEAm4oWcizMGDsjzYFKfWUKferHD1Vusclu4/dra0PCx3HctXJMnuXc4Ngvn6Ab
|
|
BcanG0Uht+bkuC4TaaS3QMCl0LwcsIzlfRzDJdxIpREWHH8yoNoPafVN3u2iGiyT
|
|
5qda4Ej4WQgOmmNiluZPk8a4d4MkAxyQdVF/AVVx6Or+9d+bkQenjPSxWVmi/bfW
|
|
RBXq2AcD8Ej7AIU15dRnLEkESmJm4xtV2aqmCd0SSBGhJHYLcInUPzWVg1zcB5EQ
|
|
78GOTue8UrZvbcYhOufHG0k5JX5HVoVZ6GSXKqn5kqbcHXT6adVoWT/BxZruZiKQ
|
|
qkryoZoSywt7dDdDhpC2+oAOC+XwX2HJp2mrPaAea1+E4LM9C9iEDtjsn5FfsBz0
|
|
VRbMRdaoayXzOlTRhF3pGU2LLCmrXy/pqpqAGYPxyHr3auRn9fjv77UMEqVFdfOc
|
|
CspkK71IGqM9UwwMtCZBp0fK/Xv9o1d85paXcJ/aH8zg6EK4UkuXDFnLsg1LrIru
|
|
+YHeHOeSaXJlcjzwWVY/Exe5HymtqGH8klMhy65bjtapNt76+j2CJgxOdPEiTy/l
|
|
9LH5ujlo5qgemXE3ePwYZ9D3iiJThTf3tWkvdbz2wCPJAy2EHS0FxHMfx5sXsFsa
|
|
OY8B7wwvZTLzU6WWs781TJXx2CE04PneeeArLpVLkiGIWjk=
|
|
-----END CERTIFICATE-----`
|
|
|
|
const issuerPath = "../test/test-ca.pem"
|
|
|
|
var log = blog.UseMock()
|
|
var ctx = context.Background()
|
|
|
|
func getPort(hs *httptest.Server) (int, error) {
|
|
url, err := url.Parse(hs.URL)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
_, portString, err := net.SplitHostPort(url.Host)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
port, err := strconv.ParseInt(portString, 10, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return int(port), nil
|
|
}
|
|
|
|
func createSignedSCT(leaf []byte, k *ecdsa.PrivateKey) string {
|
|
rawKey, _ := x509.MarshalPKIXPublicKey(&k.PublicKey)
|
|
pkHash := sha256.Sum256(rawKey)
|
|
sct := ct.SignedCertificateTimestamp{
|
|
SCTVersion: ct.V1,
|
|
LogID: ct.LogID{KeyID: pkHash},
|
|
Timestamp: 1337,
|
|
}
|
|
serialized, _ := ct.SerializeSCTSignatureInput(sct, ct.LogEntry{
|
|
Leaf: ct.MerkleTreeLeaf{
|
|
LeafType: ct.TimestampedEntryLeafType,
|
|
TimestampedEntry: &ct.TimestampedEntry{
|
|
X509Entry: &ct.ASN1Cert{Data: leaf},
|
|
EntryType: ct.X509LogEntryType,
|
|
},
|
|
},
|
|
})
|
|
hashed := sha256.Sum256(serialized)
|
|
var ecdsaSig struct {
|
|
R, S *big.Int
|
|
}
|
|
ecdsaSig.R, ecdsaSig.S, _ = ecdsa.Sign(rand.Reader, k, hashed[:])
|
|
sig, _ := asn1.Marshal(ecdsaSig)
|
|
|
|
ds := ct.DigitallySigned{
|
|
Algorithm: ctTLS.SignatureAndHashAlgorithm{
|
|
Hash: ctTLS.SHA256,
|
|
Signature: ctTLS.ECDSA,
|
|
},
|
|
Signature: sig,
|
|
}
|
|
|
|
var jsonSCTObj struct {
|
|
SCTVersion ct.Version `json:"sct_version"`
|
|
ID string `json:"id"`
|
|
Timestamp uint64 `json:"timestamp"`
|
|
Extensions string `json:"extensions"`
|
|
Signature string `json:"signature"`
|
|
}
|
|
jsonSCTObj.SCTVersion = ct.V1
|
|
jsonSCTObj.ID = base64.StdEncoding.EncodeToString(pkHash[:])
|
|
jsonSCTObj.Timestamp = 1337
|
|
jsonSCTObj.Signature, _ = ds.Base64String()
|
|
|
|
jsonSCT, _ := json.Marshal(jsonSCTObj)
|
|
return string(jsonSCT)
|
|
}
|
|
|
|
func logSrv(leaf []byte, k *ecdsa.PrivateKey) *httptest.Server {
|
|
sct := createSignedSCT(leaf, k)
|
|
m := http.NewServeMux()
|
|
m.HandleFunc("/ct/", func(w http.ResponseWriter, r *http.Request) {
|
|
decoder := json.NewDecoder(r.Body)
|
|
var jsonReq ctSubmissionRequest
|
|
err := decoder.Decode(&jsonReq)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// Submissions should always contain at least one cert
|
|
if len(jsonReq.Chain) >= 1 {
|
|
fmt.Fprint(w, sct)
|
|
}
|
|
})
|
|
|
|
server := httptest.NewUnstartedServer(m)
|
|
server.Start()
|
|
return server
|
|
}
|
|
|
|
func errorLogSrv() *httptest.Server {
|
|
m := http.NewServeMux()
|
|
m.HandleFunc("/ct/", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
})
|
|
|
|
server := httptest.NewUnstartedServer(m)
|
|
server.Start()
|
|
return server
|
|
}
|
|
|
|
func retryableLogSrv(leaf []byte, k *ecdsa.PrivateKey, retries int, after *int) *httptest.Server {
|
|
hits := 0
|
|
sct := createSignedSCT(leaf, k)
|
|
m := http.NewServeMux()
|
|
m.HandleFunc("/ct/", func(w http.ResponseWriter, r *http.Request) {
|
|
if hits >= retries {
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprint(w, sct)
|
|
} else {
|
|
hits++
|
|
if after != nil {
|
|
w.Header().Add("Retry-After", fmt.Sprintf("%d", *after))
|
|
w.WriteHeader(503)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusRequestTimeout)
|
|
}
|
|
})
|
|
|
|
server := httptest.NewUnstartedServer(m)
|
|
server.Start()
|
|
return server
|
|
}
|
|
|
|
func badLogSrv() *httptest.Server {
|
|
m := http.NewServeMux()
|
|
m.HandleFunc("/ct/", func(w http.ResponseWriter, r *http.Request) {
|
|
decoder := json.NewDecoder(r.Body)
|
|
var jsonReq ctSubmissionRequest
|
|
err := decoder.Decode(&jsonReq)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// Submissions should always contain at least one cert
|
|
if len(jsonReq.Chain) >= 1 {
|
|
fmt.Fprint(w, `{"signature":"BAMASDBGAiEAknaySJVdB3FqG9bUKHgyu7V9AdEabpTc71BELUp6/iECIQDObrkwlQq6Azfj5XOA5E12G/qy/WuRn97z7qMSXXc82Q=="}`)
|
|
}
|
|
})
|
|
|
|
server := httptest.NewUnstartedServer(m)
|
|
server.Start()
|
|
return server
|
|
}
|
|
|
|
func setup(t *testing.T) (*Impl, *x509.Certificate, *ecdsa.PrivateKey) {
|
|
intermediatePEM, _ := pem.Decode([]byte(testIntermediate))
|
|
|
|
pub := New(nil,
|
|
nil,
|
|
0,
|
|
log,
|
|
metrics.NewNoopScope(),
|
|
mocks.NewStorageAuthority(clock.NewFake()))
|
|
pub.issuerBundle = append(pub.issuerBundle, ct.ASN1Cert{Data: intermediatePEM.Bytes})
|
|
|
|
leafPEM, _ := pem.Decode([]byte(testLeaf))
|
|
leaf, err := x509.ParseCertificate(leafPEM.Bytes)
|
|
test.AssertNotError(t, err, "Couldn't parse leafPEM.Bytes")
|
|
|
|
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
test.AssertNotError(t, err, "Couldn't generate test key")
|
|
|
|
return pub, leaf, k
|
|
}
|
|
|
|
func addLog(t *testing.T, pub *Impl, port int, pubKey *ecdsa.PublicKey) {
|
|
uri := fmt.Sprintf("http://localhost:%d/ct", port)
|
|
der, err := x509.MarshalPKIXPublicKey(pubKey)
|
|
test.AssertNotError(t, err, "Failed to marshal key")
|
|
newLog, err := NewLog(uri, base64.StdEncoding.EncodeToString(der), log)
|
|
test.AssertNotError(t, err, "Couldn't create log")
|
|
test.AssertEquals(t, newLog.uri, fmt.Sprintf("http://localhost:%d/ct", port))
|
|
pub.ctLogs = append(pub.ctLogs, newLog)
|
|
}
|
|
|
|
func TestBasicSuccessful(t *testing.T) {
|
|
pub, leaf, k := setup(t)
|
|
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
scope := mock_metrics.NewMockScope(ctrl)
|
|
pub.stats = scope
|
|
|
|
server := logSrv(leaf.Raw, k)
|
|
defer server.Close()
|
|
port, err := getPort(server)
|
|
test.AssertNotError(t, err, "Failed to get test server port")
|
|
addLog(t, pub, port, &k.PublicKey)
|
|
|
|
statName := pub.ctLogs[0].statName
|
|
log.Clear()
|
|
scope.EXPECT().NewScope(statName).Return(scope)
|
|
scope.EXPECT().Inc("Submits", int64(1))
|
|
scope.EXPECT().TimingDuration("SubmitLatency", gomock.Any())
|
|
err = pub.SubmitToCT(ctx, leaf.Raw)
|
|
test.AssertNotError(t, err, "Certificate submission failed")
|
|
test.AssertEquals(t, len(log.GetAllMatching("Failed to.*")), 0)
|
|
|
|
// No Intermediate
|
|
pub.issuerBundle = []ct.ASN1Cert{}
|
|
log.Clear()
|
|
scope.EXPECT().NewScope(statName).Return(scope)
|
|
scope.EXPECT().Inc("Submits", int64(1))
|
|
scope.EXPECT().TimingDuration("SubmitLatency", gomock.Any())
|
|
err = pub.SubmitToCT(ctx, leaf.Raw)
|
|
test.AssertNotError(t, err, "Certificate submission failed")
|
|
test.AssertEquals(t, len(log.GetAllMatching("Failed to.*")), 0)
|
|
}
|
|
|
|
func TestGoodRetry(t *testing.T) {
|
|
pub, leaf, k := setup(t)
|
|
|
|
server := retryableLogSrv(leaf.Raw, k, 1, nil)
|
|
defer server.Close()
|
|
port, err := getPort(server)
|
|
test.AssertNotError(t, err, "Failed to get test server port")
|
|
addLog(t, pub, port, &k.PublicKey)
|
|
|
|
log.Clear()
|
|
err = pub.SubmitToCT(ctx, leaf.Raw)
|
|
test.AssertNotError(t, err, "Certificate submission failed")
|
|
fmt.Println(strings.Join(log.GetAllMatching(".*"), "\n"))
|
|
test.AssertEquals(t, len(log.GetAllMatching("Failed to.*")), 0)
|
|
}
|
|
|
|
func TestUnexpectedError(t *testing.T) {
|
|
pub, leaf, k := setup(t)
|
|
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
scope := mock_metrics.NewMockScope(ctrl)
|
|
pub.stats = scope
|
|
|
|
srv := errorLogSrv()
|
|
defer srv.Close()
|
|
port, err := getPort(srv)
|
|
test.AssertNotError(t, err, "Failed to get test server port")
|
|
addLog(t, pub, port, &k.PublicKey)
|
|
statName := pub.ctLogs[0].statName
|
|
|
|
log.Clear()
|
|
scope.EXPECT().NewScope(statName).Return(scope)
|
|
scope.EXPECT().Inc("Submits", int64(1))
|
|
scope.EXPECT().Inc("Errors", int64(1))
|
|
scope.EXPECT().TimingDuration("SubmitLatency", gomock.Any())
|
|
err = pub.SubmitToCT(ctx, leaf.Raw)
|
|
test.AssertNotError(t, err, "Certificate submission failed")
|
|
test.AssertEquals(t, len(log.GetAllMatching("Failed .*http://localhost:"+strconv.Itoa(port))), 1)
|
|
}
|
|
|
|
func TestRetryAfter(t *testing.T) {
|
|
pub, leaf, k := setup(t)
|
|
|
|
retryAfter := 2
|
|
server := retryableLogSrv(leaf.Raw, k, 2, &retryAfter)
|
|
defer server.Close()
|
|
port, err := getPort(server)
|
|
test.AssertNotError(t, err, "Failed to get test server port")
|
|
addLog(t, pub, port, &k.PublicKey)
|
|
|
|
log.Clear()
|
|
startedWaiting := time.Now()
|
|
err = pub.SubmitToCT(ctx, leaf.Raw)
|
|
test.AssertNotError(t, err, "Certificate submission failed")
|
|
test.AssertEquals(t, len(log.GetAllMatching("Failed to.*")), 0)
|
|
test.Assert(t, time.Since(startedWaiting) > time.Duration(retryAfter*2)*time.Second, fmt.Sprintf("Submitter retried submission too fast: %s", time.Since(startedWaiting)))
|
|
}
|
|
|
|
func TestRetryAfterContext(t *testing.T) {
|
|
pub, leaf, k := setup(t)
|
|
|
|
retryAfter := 2
|
|
server := retryableLogSrv(leaf.Raw, k, 2, &retryAfter)
|
|
defer server.Close()
|
|
port, err := getPort(server)
|
|
test.AssertNotError(t, err, "Failed to get test server port")
|
|
addLog(t, pub, port, &k.PublicKey)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
s := time.Now()
|
|
err = pub.SubmitToCT(ctx, leaf.Raw)
|
|
test.AssertNotError(t, err, "Failed to submit to CT")
|
|
took := time.Since(s)
|
|
test.Assert(t, len(log.GetAllMatching(".*Failed to submit certificate to CT log at .*: context deadline exceeded.*")) == 1, "Submission didn't timeout")
|
|
test.Assert(t, took >= time.Second, fmt.Sprintf("Submission took too long to timeout: %s", took))
|
|
}
|
|
|
|
func TestMultiLog(t *testing.T) {
|
|
pub, leaf, k := setup(t)
|
|
|
|
srvA := logSrv(leaf.Raw, k)
|
|
defer srvA.Close()
|
|
srvB := logSrv(leaf.Raw, k)
|
|
defer srvB.Close()
|
|
portA, err := getPort(srvA)
|
|
test.AssertNotError(t, err, "Failed to get test server port")
|
|
portB, err := getPort(srvB)
|
|
test.AssertNotError(t, err, "Failed to get test server port")
|
|
addLog(t, pub, portA, &k.PublicKey)
|
|
addLog(t, pub, portB, &k.PublicKey)
|
|
|
|
log.Clear()
|
|
err = pub.SubmitToCT(ctx, leaf.Raw)
|
|
test.AssertNotError(t, err, "Certificate submission failed")
|
|
test.AssertEquals(t, len(log.GetAllMatching("Failed to.*")), 0)
|
|
}
|
|
|
|
func TestBadServer(t *testing.T) {
|
|
pub, leaf, k := setup(t)
|
|
|
|
srv := badLogSrv()
|
|
defer srv.Close()
|
|
port, err := getPort(srv)
|
|
test.AssertNotError(t, err, "Failed to get test server port")
|
|
addLog(t, pub, port, &k.PublicKey)
|
|
|
|
log.Clear()
|
|
err = pub.SubmitToCT(ctx, leaf.Raw)
|
|
test.AssertNotError(t, err, "Certificate submission failed")
|
|
test.AssertEquals(t, len(log.GetAllMatching("failed to verify ECDSA signature")), 1)
|
|
}
|
|
|
|
func TestLogCache(t *testing.T) {
|
|
cache := logCache{
|
|
logs: make(map[string]*Log),
|
|
}
|
|
|
|
// Adding a log with an invalid base64 public key should error
|
|
_, err := cache.AddLog("www.test.com", "1234", log)
|
|
test.AssertError(t, err, "AddLog() with invalid base64 pk didn't error")
|
|
|
|
// Adding a log with an invalid URI should error
|
|
_, err = cache.AddLog(":", "", log)
|
|
test.AssertError(t, err, "AddLog() with an invalid log URI didn't error")
|
|
|
|
// Create one keypair & base 64 public key
|
|
k1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
test.AssertNotError(t, err, "ecdsa.GenerateKey() failed for k1")
|
|
der1, err := x509.MarshalPKIXPublicKey(&k1.PublicKey)
|
|
test.AssertNotError(t, err, "x509.MarshalPKIXPublicKey(der1) failed")
|
|
k1b64 := base64.StdEncoding.EncodeToString(der1)
|
|
|
|
// Create a second keypair & base64 public key
|
|
k2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
test.AssertNotError(t, err, "ecdsa.GenerateKey() failed for k2")
|
|
der2, err := x509.MarshalPKIXPublicKey(&k2.PublicKey)
|
|
test.AssertNotError(t, err, "x509.MarshalPKIXPublicKey(der2) failed")
|
|
k2b64 := base64.StdEncoding.EncodeToString(der2)
|
|
|
|
// Adding the first log should not produce an error
|
|
l1, err := cache.AddLog("http://log.one.example.com", k1b64, log)
|
|
test.AssertNotError(t, err, "cache.AddLog() failed for log 1")
|
|
test.AssertEquals(t, cache.Len(), 1)
|
|
test.AssertEquals(t, l1.uri, "http://log.one.example.com")
|
|
test.AssertEquals(t, l1.logID, k1b64)
|
|
|
|
// Adding it again should not produce any errors, or increase the Len()
|
|
l1, err = cache.AddLog("http://log.one.example.com", k1b64, log)
|
|
test.AssertNotError(t, err, "cache.AddLog() failed for second add of log 1")
|
|
test.AssertEquals(t, cache.Len(), 1)
|
|
test.AssertEquals(t, l1.uri, "http://log.one.example.com")
|
|
test.AssertEquals(t, l1.logID, k1b64)
|
|
|
|
// Adding a second log should not error and should increase the Len()
|
|
l2, err := cache.AddLog("http://log.two.example.com", k2b64, log)
|
|
test.AssertNotError(t, err, "cache.AddLog() failed for log 2")
|
|
test.AssertEquals(t, cache.Len(), 2)
|
|
test.AssertEquals(t, l2.uri, "http://log.two.example.com")
|
|
test.AssertEquals(t, l2.logID, k2b64)
|
|
}
|