Allow publisher/ct-test-serv to work with precerts (#3537)
`boulder-publisher` and `ct-test-serv` make a number of assumptions about _what_ is being submitted to a CT log (mainly that it's always a plain certificate) which prevents proper submission of precertificates and verification/usage of the returned SCTs. This change adds the ability for the publisher to submit both normal certificates and precertificates and verify the returned SCTs. This change also adds the ability for `ct-test-serv` to accept `/ct/v1/add-pre-chain` submissions and create correct SCTs for precert entry types.
This commit is contained in:
commit
687ab5722b
|
|
@ -39,6 +39,7 @@ type Request struct {
|
|||
Der []byte `protobuf:"bytes,1,opt,name=der" json:"der,omitempty"`
|
||||
LogURL *string `protobuf:"bytes,2,opt,name=LogURL,json=logURL" json:"LogURL,omitempty"`
|
||||
LogPublicKey *string `protobuf:"bytes,3,opt,name=LogPublicKey,json=logPublicKey" json:"LogPublicKey,omitempty"`
|
||||
Precert *bool `protobuf:"varint,4,opt,name=precert" json:"precert,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +69,13 @@ func (m *Request) GetLogPublicKey() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *Request) GetPrecert() bool {
|
||||
if m != nil && m.Precert != nil {
|
||||
return *m.Precert
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Sct []byte `protobuf:"bytes,1,opt,name=sct" json:"sct,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
|
|
@ -241,18 +249,19 @@ var _Publisher_serviceDesc = grpc.ServiceDesc{
|
|||
func init() { proto.RegisterFile("publisher.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 207 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2f, 0x28, 0x4d, 0xca,
|
||||
0xc9, 0x2c, 0xce, 0x48, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x0a, 0xe7, 0x62, 0x0f,
|
||||
0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe0, 0x62, 0x4e, 0x49, 0x2d, 0x92, 0x60, 0x54,
|
||||
0x60, 0xd4, 0xe0, 0x09, 0x02, 0x31, 0x85, 0xc4, 0xb8, 0xd8, 0x7c, 0xf2, 0xd3, 0x43, 0x83, 0x7c,
|
||||
0x24, 0x98, 0x14, 0x18, 0x35, 0x38, 0x83, 0xd8, 0x72, 0xc0, 0x3c, 0x21, 0x25, 0x2e, 0x1e, 0x9f,
|
||||
0xfc, 0xf4, 0x00, 0x90, 0x51, 0xc9, 0xde, 0xa9, 0x95, 0x12, 0xcc, 0x60, 0x59, 0x9e, 0x1c, 0x24,
|
||||
0x31, 0x25, 0x29, 0x2e, 0xb6, 0xa0, 0xd4, 0xe2, 0xd2, 0x1c, 0xb0, 0xb9, 0xc5, 0xc9, 0x25, 0x30,
|
||||
0x73, 0x8b, 0x93, 0x4b, 0x94, 0xd8, 0xb9, 0x58, 0x5d, 0x73, 0x0b, 0x4a, 0x2a, 0x8d, 0x3a, 0x18,
|
||||
0xb9, 0x38, 0x03, 0x60, 0x2e, 0x12, 0x52, 0xe0, 0xe2, 0x0a, 0x2e, 0x4d, 0xca, 0xcd, 0x2c, 0x09,
|
||||
0xc9, 0x77, 0x0e, 0x11, 0xe2, 0xd0, 0x83, 0x3a, 0x4c, 0x8a, 0x4d, 0x0f, 0xac, 0x5a, 0x89, 0x41,
|
||||
0x48, 0x8d, 0x4b, 0x00, 0xa6, 0x22, 0x38, 0x33, 0x2f, 0x3d, 0x27, 0x15, 0x87, 0x3a, 0x43, 0x2e,
|
||||
0x29, 0x74, 0x75, 0xe1, 0x99, 0x25, 0x19, 0x50, 0x07, 0x21, 0x74, 0xb0, 0xeb, 0x41, 0x84, 0x94,
|
||||
0x18, 0x00, 0x01, 0x00, 0x00, 0xff, 0xff, 0x8b, 0x2a, 0x83, 0x37, 0x1a, 0x01, 0x00, 0x00,
|
||||
// 224 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x8e, 0x41, 0x4b, 0x03, 0x31,
|
||||
0x10, 0x85, 0x1b, 0xab, 0xbb, 0xed, 0xb0, 0x60, 0xc9, 0x41, 0xc2, 0x9e, 0x96, 0x1c, 0x64, 0x4f,
|
||||
0x0b, 0xfa, 0x17, 0xc4, 0x93, 0x7b, 0x28, 0x69, 0xc5, 0x7b, 0xe3, 0xb0, 0x0d, 0xa4, 0x26, 0x4d,
|
||||
0x26, 0x87, 0xfe, 0x03, 0x7f, 0xb6, 0x34, 0x36, 0x28, 0x82, 0xb7, 0x79, 0x6f, 0xbe, 0x99, 0xf7,
|
||||
0xe0, 0xd6, 0xa7, 0x9d, 0x35, 0x71, 0x8f, 0x61, 0xf0, 0xc1, 0x91, 0x93, 0x47, 0xa8, 0x15, 0x1e,
|
||||
0x13, 0x46, 0xe2, 0x2b, 0x98, 0xbf, 0x63, 0x10, 0xac, 0x63, 0x7d, 0xa3, 0xce, 0x23, 0xbf, 0x83,
|
||||
0x6a, 0x74, 0xd3, 0xab, 0x1a, 0xc5, 0x55, 0xc7, 0xfa, 0xa5, 0xaa, 0x6c, 0x56, 0x5c, 0x42, 0x33,
|
||||
0xba, 0x69, 0x7d, 0x7e, 0xa5, 0x5f, 0xf0, 0x24, 0xe6, 0x79, 0xdb, 0xd8, 0x5f, 0x1e, 0x17, 0x50,
|
||||
0xfb, 0x80, 0x1a, 0x03, 0x89, 0xeb, 0x8e, 0xf5, 0x0b, 0x55, 0xa4, 0x6c, 0xa1, 0x52, 0x18, 0x93,
|
||||
0xcd, 0x89, 0x51, 0x53, 0x49, 0x8c, 0x9a, 0x64, 0x0d, 0x37, 0xcf, 0x07, 0x4f, 0xa7, 0xc7, 0x4f,
|
||||
0x06, 0xcb, 0x75, 0xe9, 0xca, 0x3b, 0x80, 0x4d, 0xda, 0x1d, 0x0c, 0x6d, 0xdd, 0xd3, 0x96, 0x2f,
|
||||
0x86, 0x4b, 0xe5, 0xb6, 0x1a, 0x32, 0x2d, 0x67, 0xfc, 0x1e, 0x56, 0x85, 0xd8, 0x98, 0x8f, 0xc9,
|
||||
0xe2, 0x3f, 0xdc, 0x03, 0xb4, 0x7f, 0xb9, 0x37, 0x43, 0xfb, 0x4b, 0xa1, 0x9f, 0x8b, 0x7a, 0xf8,
|
||||
0xb6, 0xe4, 0xec, 0x2b, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x6e, 0xb0, 0x3d, 0x34, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ message Request {
|
|||
optional bytes der = 1;
|
||||
optional string LogURL = 2;
|
||||
optional string LogPublicKey = 3;
|
||||
optional bool precert = 4;
|
||||
}
|
||||
|
||||
message Result {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,26 @@
|
|||
package publisher
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
ct "github.com/google/certificate-transparency-go"
|
||||
"github.com/google/certificate-transparency-go"
|
||||
ctClient "github.com/google/certificate-transparency-go/client"
|
||||
"github.com/google/certificate-transparency-go/jsonclient"
|
||||
"github.com/google/certificate-transparency-go/tls"
|
||||
cttls "github.com/google/certificate-transparency-go/tls"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
|
|
@ -216,9 +223,15 @@ func (pub *Impl) SubmitToSingleCTWithResult(ctx context.Context, req *pubpb.Requ
|
|||
return nil, err
|
||||
}
|
||||
|
||||
isPrecert := false
|
||||
if req.Precert != nil {
|
||||
isPrecert = *req.Precert
|
||||
}
|
||||
|
||||
sct, err := pub.singleLogSubmit(
|
||||
ctx,
|
||||
chain,
|
||||
isPrecert,
|
||||
core.SerialToString(cert.SerialNumber),
|
||||
ctLog)
|
||||
if err != nil {
|
||||
|
|
@ -259,11 +272,18 @@ func (pub *Impl) SubmitToCT(ctx context.Context, der []byte) error {
|
|||
func (pub *Impl) singleLogSubmit(
|
||||
ctx context.Context,
|
||||
chain []ct.ASN1Cert,
|
||||
isPrecert bool,
|
||||
serial string,
|
||||
ctLog *Log) (*ct.SignedCertificateTimestamp, error) {
|
||||
ctLog *Log,
|
||||
) (*ct.SignedCertificateTimestamp, error) {
|
||||
var submissionMethod func(context.Context, []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error)
|
||||
submissionMethod = ctLog.client.AddChain
|
||||
if isPrecert {
|
||||
submissionMethod = ctLog.client.AddPreChain
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
sct, err := ctLog.client.AddChain(ctx, chain)
|
||||
sct, err := submissionMethod(ctx, chain)
|
||||
took := time.Since(start).Seconds()
|
||||
if err != nil {
|
||||
status := "error"
|
||||
|
|
@ -281,22 +301,29 @@ func (pub *Impl) singleLogSubmit(
|
|||
"status": "success",
|
||||
}).Observe(took)
|
||||
|
||||
err = ctLog.verifier.VerifySCTSignature(*sct, ct.LogEntry{
|
||||
Leaf: ct.MerkleTreeLeaf{
|
||||
LeafType: ct.TimestampedEntryLeafType,
|
||||
TimestampedEntry: &ct.TimestampedEntry{
|
||||
X509Entry: &chain[0],
|
||||
EntryType: ct.X509LogEntryType,
|
||||
},
|
||||
},
|
||||
})
|
||||
// Generate log entry so we can verify the signature in the returned SCT
|
||||
eType := ct.X509LogEntryType
|
||||
if isPrecert {
|
||||
eType = ct.PrecertLogEntryType
|
||||
}
|
||||
// Note: The timestamp on the merkle tree leaf is not actually used in
|
||||
// the SCT signature validation so it is left as 0 here
|
||||
leaf, err := ct.MerkleTreeLeafFromRawChain(chain, eType, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = ctLog.verifier.VerifySCTSignature(*sct, ct.LogEntry{Leaf: *leaf})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = pub.sa.AddSCTReceipt(ctx, sctToInternal(sct, serial))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Only store the SCT if it was for a certificate, we have no need for
|
||||
// the precert once it is embedded in a certificate
|
||||
if !isPrecert {
|
||||
err = pub.sa.AddSCTReceipt(ctx, sctToInternal(sct, serial))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return sct, nil
|
||||
}
|
||||
|
|
@ -311,3 +338,67 @@ func sctToInternal(sct *ct.SignedCertificateTimestamp, serial string) core.Signe
|
|||
Signature: sct.Signature.Signature,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateTestingSignedSCT is used by both the publisher tests and ct-test-serv, which is
|
||||
// why it is exported. It creates a signed SCT based on the provided chain.
|
||||
func CreateTestingSignedSCT(req []string, k *ecdsa.PrivateKey, precert bool) []byte {
|
||||
chain := make([]ct.ASN1Cert, len(req))
|
||||
for i, str := range req {
|
||||
b, err := base64.StdEncoding.DecodeString(str)
|
||||
if err != nil {
|
||||
panic("cannot decode chain")
|
||||
}
|
||||
chain[i] = ct.ASN1Cert{Data: b}
|
||||
}
|
||||
|
||||
// Generate the internal leaf entry for the SCT
|
||||
etype := ct.X509LogEntryType
|
||||
if precert {
|
||||
etype = ct.PrecertLogEntryType
|
||||
}
|
||||
leaf, err := ct.MerkleTreeLeafFromRawChain(chain, etype, 0)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create leaf: %s", err))
|
||||
}
|
||||
|
||||
// Sign the SCT
|
||||
rawKey, _ := x509.MarshalPKIXPublicKey(&k.PublicKey)
|
||||
logID := sha256.Sum256(rawKey)
|
||||
timestamp := uint64(time.Now().Unix())
|
||||
serialized, _ := ct.SerializeSCTSignatureInput(ct.SignedCertificateTimestamp{
|
||||
SCTVersion: ct.V1,
|
||||
LogID: ct.LogID{KeyID: logID},
|
||||
Timestamp: timestamp,
|
||||
}, ct.LogEntry{Leaf: *leaf})
|
||||
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)
|
||||
|
||||
// The ct.SignedCertificateTimestamp object doesn't have the needed
|
||||
// `json` tags to properly marshal so we need to transform in into
|
||||
// a struct that does before we can send it off
|
||||
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(logID[:])
|
||||
jsonSCTObj.Timestamp = timestamp
|
||||
ds := ct.DigitallySigned{
|
||||
Algorithm: cttls.SignatureAndHashAlgorithm{
|
||||
Hash: cttls.SHA256,
|
||||
Signature: cttls.ECDSA,
|
||||
},
|
||||
Signature: sig,
|
||||
}
|
||||
jsonSCTObj.Signature, _ = ds.Base64String()
|
||||
|
||||
jsonSCT, _ := json.Marshal(jsonSCTObj)
|
||||
return jsonSCT
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
|
|
@ -23,7 +23,6 @@ import (
|
|||
"time"
|
||||
|
||||
ct "github.com/google/certificate-transparency-go"
|
||||
ctTLS "github.com/google/certificate-transparency-go/tls"
|
||||
"github.com/jmhodges/clock"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/net/context"
|
||||
|
|
@ -31,6 +30,7 @@ import (
|
|||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
"github.com/letsencrypt/boulder/mocks"
|
||||
pubpb "github.com/letsencrypt/boulder/publisher/proto"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
|
|
@ -134,61 +134,12 @@ func getPort(srvURL string) (int, error) {
|
|||
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)
|
||||
}
|
||||
|
||||
type testLogSrv struct {
|
||||
*httptest.Server
|
||||
submissions int64
|
||||
}
|
||||
|
||||
func logSrv(leaf []byte, k *ecdsa.PrivateKey) *testLogSrv {
|
||||
sct := createSignedSCT(leaf, k)
|
||||
func logSrv(k *ecdsa.PrivateKey) *testLogSrv {
|
||||
testLog := &testLogSrv{}
|
||||
m := http.NewServeMux()
|
||||
m.HandleFunc("/ct/", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -198,11 +149,13 @@ func logSrv(leaf []byte, k *ecdsa.PrivateKey) *testLogSrv {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Submissions should always contain at least one cert
|
||||
if len(jsonReq.Chain) >= 1 {
|
||||
fmt.Fprint(w, sct)
|
||||
atomic.AddInt64(&testLog.submissions, 1)
|
||||
precert := false
|
||||
if r.URL.Path == "/ct/v1/add-pre-chain" {
|
||||
precert = true
|
||||
}
|
||||
sct := CreateTestingSignedSCT(jsonReq.Chain, k, precert)
|
||||
fmt.Fprint(w, string(sct))
|
||||
atomic.AddInt64(&testLog.submissions, 1)
|
||||
})
|
||||
|
||||
testLog.Server = httptest.NewUnstartedServer(m)
|
||||
|
|
@ -221,14 +174,20 @@ func errorLogSrv() *httptest.Server {
|
|||
return server
|
||||
}
|
||||
|
||||
func retryableLogSrv(leaf []byte, k *ecdsa.PrivateKey, retries int, after *int) *httptest.Server {
|
||||
func retryableLogSrv(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 {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
var jsonReq ctSubmissionRequest
|
||||
err := decoder.Decode(&jsonReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sct := CreateTestingSignedSCT(jsonReq.Chain, k, false)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, sct)
|
||||
fmt.Fprint(w, string(sct))
|
||||
} else {
|
||||
hits++
|
||||
if after != nil {
|
||||
|
|
@ -286,19 +245,19 @@ func setup(t *testing.T) (*Impl, *x509.Certificate, *ecdsa.PrivateKey) {
|
|||
}
|
||||
|
||||
func addLog(t *testing.T, pub *Impl, port int, pubKey *ecdsa.PublicKey) {
|
||||
uri := fmt.Sprintf("http://localhost:%d/ct", port)
|
||||
uri := fmt.Sprintf("http://localhost:%d", 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))
|
||||
test.AssertEquals(t, newLog.uri, fmt.Sprintf("http://localhost:%d", port))
|
||||
pub.ctLogs = append(pub.ctLogs, newLog)
|
||||
}
|
||||
|
||||
func TestBasicSuccessful(t *testing.T) {
|
||||
pub, leaf, k := setup(t)
|
||||
|
||||
server := logSrv(leaf.Raw, k)
|
||||
server := logSrv(k)
|
||||
defer server.Close()
|
||||
port, err := getPort(server.URL)
|
||||
test.AssertNotError(t, err, "Failed to get test server port")
|
||||
|
|
@ -319,12 +278,37 @@ func TestBasicSuccessful(t *testing.T) {
|
|||
err = pub.SubmitToCT(ctx, leaf.Raw)
|
||||
test.AssertNotError(t, err, "Certificate submission failed")
|
||||
test.AssertEquals(t, len(log.GetAllMatching("Failed to.*")), 0)
|
||||
|
||||
// Precert
|
||||
trueBool := true
|
||||
rootTmpl := x509.Certificate{
|
||||
SerialNumber: big.NewInt(0),
|
||||
Subject: pkix.Name{CommonName: "root"},
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
rootBytes, err := x509.CreateCertificate(rand.Reader, &rootTmpl, &rootTmpl, k.Public(), k)
|
||||
test.AssertNotError(t, err, "Failed to create test root")
|
||||
pub.issuerBundle = []ct.ASN1Cert{ct.ASN1Cert{Data: rootBytes}}
|
||||
root, err := x509.ParseCertificate(rootBytes)
|
||||
test.AssertNotError(t, err, "Failed to parse test root")
|
||||
precertTmpl := x509.Certificate{
|
||||
SerialNumber: big.NewInt(0),
|
||||
ExtraExtensions: []pkix.Extension{
|
||||
{Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}, Critical: true, Value: []byte{0x05, 0x00}},
|
||||
},
|
||||
}
|
||||
precert, err := x509.CreateCertificate(rand.Reader, &precertTmpl, root, k.Public(), k)
|
||||
test.AssertNotError(t, err, "Failed to create test leaf")
|
||||
_, err = pub.SubmitToSingleCTWithResult(ctx, &pubpb.Request{LogURL: &pub.ctLogs[0].uri, LogPublicKey: &pub.ctLogs[0].logID, Der: precert, Precert: &trueBool})
|
||||
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)
|
||||
server := retryableLogSrv(k, 1, nil)
|
||||
defer server.Close()
|
||||
port, err := getPort(server.URL)
|
||||
test.AssertNotError(t, err, "Failed to get test server port")
|
||||
|
|
@ -360,7 +344,7 @@ func TestRetryAfter(t *testing.T) {
|
|||
pub, leaf, k := setup(t)
|
||||
|
||||
retryAfter := 2
|
||||
server := retryableLogSrv(leaf.Raw, k, 2, &retryAfter)
|
||||
server := retryableLogSrv(k, 2, &retryAfter)
|
||||
defer server.Close()
|
||||
port, err := getPort(server.URL)
|
||||
test.AssertNotError(t, err, "Failed to get test server port")
|
||||
|
|
@ -378,7 +362,7 @@ func TestRetryAfterContext(t *testing.T) {
|
|||
pub, leaf, k := setup(t)
|
||||
|
||||
retryAfter := 2
|
||||
server := retryableLogSrv(leaf.Raw, k, 2, &retryAfter)
|
||||
server := retryableLogSrv(k, 2, &retryAfter)
|
||||
defer server.Close()
|
||||
port, err := getPort(server.URL)
|
||||
test.AssertNotError(t, err, "Failed to get test server port")
|
||||
|
|
@ -397,9 +381,9 @@ func TestRetryAfterContext(t *testing.T) {
|
|||
func TestMultiLog(t *testing.T) {
|
||||
pub, leaf, k := setup(t)
|
||||
|
||||
srvA := logSrv(leaf.Raw, k)
|
||||
srvA := logSrv(k)
|
||||
defer srvA.Close()
|
||||
srvB := logSrv(leaf.Raw, k)
|
||||
srvB := logSrv(k)
|
||||
defer srvB.Close()
|
||||
portA, err := getPort(srvA.URL)
|
||||
test.AssertNotError(t, err, "Failed to get test server port")
|
||||
|
|
@ -483,13 +467,13 @@ func TestSubmitToCTParallel(t *testing.T) {
|
|||
|
||||
// Create a server that will timeout on submission
|
||||
retryAfter := 2
|
||||
srvA := retryableLogSrv(leaf.Raw, k, 2, &retryAfter)
|
||||
srvA := retryableLogSrv(k, 2, &retryAfter)
|
||||
defer srvA.Close()
|
||||
|
||||
// Create a server that will instantly accept a submission
|
||||
k2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
test.AssertNotError(t, err, "Couldn't generate test key")
|
||||
srvB := logSrv(leaf.Raw, k2)
|
||||
srvB := logSrv(k2)
|
||||
defer srvB.Close()
|
||||
|
||||
portA, err := getPort(srvA.URL)
|
||||
|
|
|
|||
|
|
@ -5,75 +5,22 @@ package main
|
|||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
ct "github.com/google/certificate-transparency-go"
|
||||
ctTLS "github.com/google/certificate-transparency-go/tls"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/publisher"
|
||||
)
|
||||
|
||||
func createSignedSCT(leaf []byte, k *ecdsa.PrivateKey) []byte {
|
||||
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 jsonSCT
|
||||
}
|
||||
|
||||
type ctSubmissionRequest struct {
|
||||
Chain []string `json:"chain"`
|
||||
}
|
||||
|
|
@ -88,6 +35,8 @@ type integrationSrv struct {
|
|||
|
||||
func (is *integrationSrv) handler(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/ct/v1/add-pre-chain":
|
||||
fallthrough
|
||||
case "/ct/v1/add-chain":
|
||||
if r.Method != "POST" {
|
||||
http.NotFound(w, r)
|
||||
|
|
@ -117,14 +66,13 @@ func (is *integrationSrv) handler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
leaf, err := base64.StdEncoding.DecodeString(addChainReq.Chain[0])
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
precert := false
|
||||
if r.URL.Path == "/ct/v1/add-pre-chain" {
|
||||
precert = true
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(createSignedSCT(leaf, is.key))
|
||||
w.Write(publisher.CreateTestingSignedSCT(addChainReq.Chain, is.key, precert))
|
||||
case "/submissions":
|
||||
if r.Method != "GET" {
|
||||
http.NotFound(w, r)
|
||||
|
|
|
|||
Loading…
Reference in New Issue