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:
Jacob Hoffman-Andrews 2018-03-09 10:03:47 -08:00 committed by GitHub
commit 687ab5722b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 190 additions and 157 deletions

View File

@ -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,
}

View File

@ -10,6 +10,7 @@ message Request {
optional bytes der = 1;
optional string LogURL = 2;
optional string LogPublicKey = 3;
optional bool precert = 4;
}
message Result {

View File

@ -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
}

View File

@ -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)

View File

@ -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)