forked from TrueCloudLab/certificates
Add initial implementation of a multilog.
This commit is contained in:
parent
b766f49995
commit
963fe0fa91
3 changed files with 119 additions and 22 deletions
|
@ -188,7 +188,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts SignOptions, ext
|
|||
|
||||
if a.ctClient != nil {
|
||||
// Submit final certificate chain
|
||||
if err := a.ctClient.SubmitToLogs(serverCert.Raw, caCert.Raw); err != nil {
|
||||
if _, err := a.ctClient.SubmitToLogs(serverCert.Raw, caCert.Raw); err != nil {
|
||||
return nil, nil, &apiError{errors.Wrap(err, "sign: error submitting final certificate to ct logs"),
|
||||
http.StatusBadGateway, errContext}
|
||||
}
|
||||
|
@ -306,7 +306,7 @@ func (a *Authority) Renew(ocx *x509.Certificate) (*x509.Certificate, *x509.Certi
|
|||
|
||||
if a.ctClient != nil {
|
||||
// Submit final certificate chain
|
||||
if err := a.ctClient.SubmitToLogs(serverCert.Raw, caCert.Raw); err != nil {
|
||||
if _, err := a.ctClient.SubmitToLogs(serverCert.Raw, caCert.Raw); err != nil {
|
||||
return nil, nil, &apiError{errors.Wrap(err, "renew: error submitting final certificate to ct logs"),
|
||||
http.StatusBadGateway, context{}}
|
||||
}
|
||||
|
@ -373,7 +373,7 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
|
|||
|
||||
if a.ctClient != nil {
|
||||
// Submit final certificate chain
|
||||
if err := a.ctClient.SubmitToLogs(crtBytes, intermediatePEM.Bytes); err != nil {
|
||||
if _, err := a.ctClient.SubmitToLogs(crtBytes, intermediatePEM.Bytes); err != nil {
|
||||
return nil, errors.Wrap(err, "error submitting final certificate to ct logs")
|
||||
}
|
||||
}
|
||||
|
|
51
ct/ct.go
51
ct/ct.go
|
@ -26,8 +26,10 @@ var (
|
|||
|
||||
// Config represents the configuration for the certificate authority client.
|
||||
type Config struct {
|
||||
URI string `json:"uri"`
|
||||
Key string `json:"key"`
|
||||
URI string `json:"uri"`
|
||||
Key string `json:"key"`
|
||||
NotAfterStart time.Time `json:"notAfterStart,omitempty"`
|
||||
NotAfterLimit time.Time `json:"notAfterLimit,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates the ct configuration.
|
||||
|
@ -42,10 +44,10 @@ func (c *Config) Validate() error {
|
|||
}
|
||||
}
|
||||
|
||||
// Client is the interfaced used to communicate with the certificate transparency logs.
|
||||
// Client is the interface used to communicate with the certificate transparency logs.
|
||||
type Client interface {
|
||||
GetSCTs(asn1Data ...[]byte) (*SCT, error)
|
||||
SubmitToLogs(asn1Data ...[]byte) error
|
||||
SubmitToLogs(asn1Data ...[]byte) (*SCT, error)
|
||||
}
|
||||
|
||||
type logClient interface {
|
||||
|
@ -102,19 +104,14 @@ type ClientImpl struct {
|
|||
|
||||
// New creates a new Client
|
||||
func New(c Config) (*ClientImpl, error) {
|
||||
// Extract DER from key
|
||||
data, err := ioutil.ReadFile(c.Key)
|
||||
der, err := readPublicKey(c.Key)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading %s", c.Key)
|
||||
}
|
||||
block, rest := pem.Decode(data)
|
||||
if block == nil || len(rest) > 0 {
|
||||
return nil, errors.Wrapf(err, "invalid public key %s", c.Key)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize ct client
|
||||
logClient, err := client.New(c.URI, &http.Client{}, jsonclient.Options{
|
||||
PublicKeyDER: block.Bytes,
|
||||
PublicKeyDER: der,
|
||||
UserAgent: "smallstep certificates",
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -146,7 +143,7 @@ func (c *ClientImpl) GetSCTs(asn1Data ...[]byte) (*SCT, error) {
|
|||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get SCT from %s", c.config.URI)
|
||||
}
|
||||
logLeafHash(asn1Data, sct, true)
|
||||
logLeafHash("AddPreChain", asn1Data, sct, true)
|
||||
return &SCT{
|
||||
LogURL: c.config.URI,
|
||||
SCT: sct,
|
||||
|
@ -154,16 +151,32 @@ func (c *ClientImpl) GetSCTs(asn1Data ...[]byte) (*SCT, error) {
|
|||
}
|
||||
|
||||
// SubmitToLogs submits the certificate to the certificate transparency logs.
|
||||
func (c *ClientImpl) SubmitToLogs(asn1Data ...[]byte) error {
|
||||
func (c *ClientImpl) SubmitToLogs(asn1Data ...[]byte) (*SCT, error) {
|
||||
chain := chainFromDERs(asn1Data)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
|
||||
defer cancel()
|
||||
sct, err := c.logClient.AddChain(ctx, chain)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed submit certificate to %s", c.config.URI)
|
||||
return nil, errors.Wrapf(err, "failed submit certificate to %s", c.config.URI)
|
||||
}
|
||||
logLeafHash(asn1Data, sct, false)
|
||||
return nil
|
||||
logLeafHash("AddChain", asn1Data, sct, false)
|
||||
return &SCT{
|
||||
LogURL: c.config.URI,
|
||||
SCT: sct,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// readPublicKey extracts the DER from the given file.
|
||||
func readPublicKey(filename string) ([]byte, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading %s", filename)
|
||||
}
|
||||
block, rest := pem.Decode(data)
|
||||
if block == nil || len(rest) > 0 {
|
||||
return nil, errors.Wrapf(err, "invalid public key %s", filename)
|
||||
}
|
||||
return block.Bytes, nil
|
||||
}
|
||||
|
||||
func chainFromDERs(asn1Data [][]byte) []ct.ASN1Cert {
|
||||
|
@ -174,7 +187,7 @@ func chainFromDERs(asn1Data [][]byte) []ct.ASN1Cert {
|
|||
return chain
|
||||
}
|
||||
|
||||
func logLeafHash(asn1Data [][]byte, sct *ct.SignedCertificateTimestamp, isPrecert bool) {
|
||||
func logLeafHash(op string, asn1Data [][]byte, sct *ct.SignedCertificateTimestamp, isPrecert bool) {
|
||||
var etype ct.LogEntryType
|
||||
if isPrecert {
|
||||
etype = ct.PrecertLogEntryType
|
||||
|
@ -204,5 +217,5 @@ func logLeafHash(asn1Data [][]byte, sct *ct.SignedCertificateTimestamp, isPrecer
|
|||
return
|
||||
}
|
||||
|
||||
log.Printf("LogID: %x, LeafHash: %x, Timestamp: %d\n", sct.LogID.KeyID[:], leafHash, sct.Timestamp)
|
||||
log.Printf("Op: %s, LogID: %x, LeafHash: %x, Timestamp: %d\n", op, sct.LogID.KeyID[:], leafHash, sct.Timestamp)
|
||||
}
|
||||
|
|
84
ct/multilog.go
Normal file
84
ct/multilog.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package ct
|
||||
|
||||
// MultiLog is the interface used to send certificates to multiple logs.
|
||||
type MultiLog interface {
|
||||
GetSCTs(asn1Data ...[]byte) ([]*SCT, error)
|
||||
SubmitToLogs(asn1Data ...[]byte) ([]*SCT, error)
|
||||
}
|
||||
|
||||
// MultiLogImpl is the implementation used to send certificates to multiple
|
||||
// logs.
|
||||
type MultiLogImpl struct {
|
||||
clients []Client
|
||||
configs []Config
|
||||
}
|
||||
|
||||
type result struct {
|
||||
sct *SCT
|
||||
err error
|
||||
uri string
|
||||
}
|
||||
|
||||
// NewMultiLog returns a MultiLog with the given configuration.
|
||||
func NewMultiLog(config []Config) (MultiLog, error) {
|
||||
ml := new(MultiLogImpl)
|
||||
for _, cfg := range config {
|
||||
client, err := New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ml.clients = append(ml.clients, client)
|
||||
}
|
||||
return ml, nil
|
||||
}
|
||||
|
||||
// GetSCTs submit the precertificate to the logs and returns the list of SCTs to
|
||||
// embed into the certificate.
|
||||
func (c *MultiLogImpl) GetSCTs(asn1Data ...[]byte) (scts []*SCT, err error) {
|
||||
results := make(chan result, len(c.clients))
|
||||
for i := range c.clients {
|
||||
client := c.clients[i]
|
||||
config := c.configs[i]
|
||||
go func() {
|
||||
sct, err := client.GetSCTs(asn1Data...)
|
||||
results <- result{sct: sct, err: err, uri: config.URI}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < len(c.clients); i++ {
|
||||
res := <-results
|
||||
switch {
|
||||
case res.sct != nil:
|
||||
scts = append(scts, res.sct)
|
||||
case res.err != nil && err != nil:
|
||||
err = res.err
|
||||
}
|
||||
}
|
||||
|
||||
return scts, err
|
||||
}
|
||||
|
||||
// SubmitToLogs submits the certificate to the certificate transparency logs.
|
||||
func (c *MultiLogImpl) SubmitToLogs(asn1Data ...[]byte) (scts []*SCT, err error) {
|
||||
results := make(chan result, len(c.clients))
|
||||
for i := range c.clients {
|
||||
client := c.clients[i]
|
||||
config := c.configs[i]
|
||||
go func() {
|
||||
sct, err := client.SubmitToLogs(asn1Data...)
|
||||
results <- result{sct: sct, err: err, uri: config.URI}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < len(c.clients); i++ {
|
||||
res := <-results
|
||||
switch {
|
||||
case res.sct != nil:
|
||||
scts = append(scts, res.sct)
|
||||
case res.err != nil && err != nil:
|
||||
err = res.err
|
||||
}
|
||||
}
|
||||
|
||||
return scts, err
|
||||
}
|
Loading…
Reference in a new issue