Compare commits
15 commits
tcl/master
...
seb/ct-loc
Author | SHA1 | Date | |
---|---|---|---|
|
50a88d3265 | ||
|
600b1db302 | ||
|
3939e85526 | ||
|
4fef188a3a | ||
|
f47e356ba3 | ||
|
4b4b0d6202 | ||
|
c2908ef888 | ||
|
16c88c6bf1 | ||
|
b9104a92f9 | ||
|
dff3f6f270 | ||
|
963fe0fa91 | ||
|
b766f49995 | ||
|
7012500aac | ||
|
7b175004cb | ||
|
19c4842cdf |
21 changed files with 602 additions and 28 deletions
37
Gopkg.lock
generated
37
Gopkg.lock
generated
|
@ -74,7 +74,8 @@
|
||||||
revision = "883fe33ffc4344bad1ecd881f61afd5ec5d80e0a"
|
revision = "883fe33ffc4344bad1ecd881f61afd5ec5d80e0a"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:4c0989ca0bcd10799064318923b9bc2db6b4d6338dd75f3f2d86c3511aaaf5cf"
|
branch = "master"
|
||||||
|
digest = "1:239c4c7fd2159585454003d9be7207167970194216193a8a210b8d29576f19c9"
|
||||||
name = "github.com/golang/protobuf"
|
name = "github.com/golang/protobuf"
|
||||||
packages = [
|
packages = [
|
||||||
"proto",
|
"proto",
|
||||||
|
@ -84,8 +85,24 @@
|
||||||
"ptypes/timestamp",
|
"ptypes/timestamp",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
|
revision = "c823c79ea1570fb5ff454033735a8e68575d1d0f"
|
||||||
version = "v1.2.0"
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:78ae27573e8adaf14269f9146230153a61c4b29bedc8742e464e053280dfa3d0"
|
||||||
|
name = "github.com/google/certificate-transparency-go"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"asn1",
|
||||||
|
"client",
|
||||||
|
"client/configpb",
|
||||||
|
"jsonclient",
|
||||||
|
"tls",
|
||||||
|
"x509",
|
||||||
|
"x509/pkix",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "b5e3a70217c2a317c27b3c852126d0f8f29fef2b"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -276,8 +293,8 @@
|
||||||
revision = "de77670473b5492f5d0bce155b5c01534c2d13f7"
|
revision = "de77670473b5492f5d0bce155b5c01534c2d13f7"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "certificate-transparency"
|
||||||
digest = "1:17d4424defbc718315d61e296841867ff76b3e03a941b41fdddbae11a7d47746"
|
digest = "1:ed4ae9e597c66929a567de027512ee77a632ed8c4471e53c82c82c6658c4eb90"
|
||||||
name = "github.com/smallstep/cli"
|
name = "github.com/smallstep/cli"
|
||||||
packages = [
|
packages = [
|
||||||
"command",
|
"command",
|
||||||
|
@ -298,7 +315,7 @@
|
||||||
"utils",
|
"utils",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "fe87ac01926afb1a518a98d4768fded646bddce1"
|
revision = "6cb4285dba37a5d03e1f4be41fc897b071181a70"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -333,10 +350,11 @@
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:2f7468b0b3fd7d926072f0dcbb6ec81e337278b4e5de639d017e54f785f0b475"
|
digest = "1:50bed722f4f0bbb3d64b0ca49d41911f57a0ddb63e03666656f6621af3b70f9e"
|
||||||
name = "golang.org/x/net"
|
name = "golang.org/x/net"
|
||||||
packages = [
|
packages = [
|
||||||
"context",
|
"context",
|
||||||
|
"context/ctxhttp",
|
||||||
"html",
|
"html",
|
||||||
"html/atom",
|
"html/atom",
|
||||||
"http/httpguts",
|
"http/httpguts",
|
||||||
|
@ -556,6 +574,11 @@
|
||||||
"github.com/go-chi/chi",
|
"github.com/go-chi/chi",
|
||||||
"github.com/golang/lint/golint",
|
"github.com/golang/lint/golint",
|
||||||
"github.com/golang/protobuf/proto",
|
"github.com/golang/protobuf/proto",
|
||||||
|
"github.com/google/certificate-transparency-go",
|
||||||
|
"github.com/google/certificate-transparency-go/client",
|
||||||
|
"github.com/google/certificate-transparency-go/jsonclient",
|
||||||
|
"github.com/google/certificate-transparency-go/tls",
|
||||||
|
"github.com/google/certificate-transparency-go/x509",
|
||||||
"github.com/gordonklaus/ineffassign",
|
"github.com/gordonklaus/ineffassign",
|
||||||
"github.com/newrelic/go-agent",
|
"github.com/newrelic/go-agent",
|
||||||
"github.com/pkg/errors",
|
"github.com/pkg/errors",
|
||||||
|
|
10
Gopkg.toml
10
Gopkg.toml
|
@ -45,7 +45,7 @@ required = [
|
||||||
name = "github.com/go-chi/chi"
|
name = "github.com/go-chi/chi"
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
branch = "master"
|
branch = "certificate-transparency"
|
||||||
name = "github.com/smallstep/cli"
|
name = "github.com/smallstep/cli"
|
||||||
|
|
||||||
[prune]
|
[prune]
|
||||||
|
@ -63,3 +63,11 @@ required = [
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "gopkg.in/square/go-jose.v2"
|
name = "gopkg.in/square/go-jose.v2"
|
||||||
version = "2.1.9"
|
version = "2.1.9"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/google/certificate-transparency-go"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/golang/protobuf"
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/smallstep/certificates/ct"
|
||||||
"github.com/smallstep/cli/crypto/pemutil"
|
"github.com/smallstep/cli/crypto/pemutil"
|
||||||
"github.com/smallstep/cli/crypto/x509util"
|
"github.com/smallstep/cli/crypto/x509util"
|
||||||
)
|
)
|
||||||
|
@ -28,6 +29,7 @@ type Authority struct {
|
||||||
provisionerKeySetIndex *sync.Map
|
provisionerKeySetIndex *sync.Map
|
||||||
sortedProvisioners provisionerSlice
|
sortedProvisioners provisionerSlice
|
||||||
audiences []string
|
audiences []string
|
||||||
|
ctClient ct.Client
|
||||||
// Do not re-initialize
|
// Do not re-initialize
|
||||||
initOnce bool
|
initOnce bool
|
||||||
}
|
}
|
||||||
|
@ -55,6 +57,14 @@ func New(config *Config) (*Authority, error) {
|
||||||
audiences = append(audiences, fmt.Sprintf("https://%s/sign", name), fmt.Sprintf("https://%s/1.0/sign", name))
|
audiences = append(audiences, fmt.Sprintf("https://%s/sign", name), fmt.Sprintf("https://%s/1.0/sign", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ctClient ct.Client
|
||||||
|
// only first one is supported at the moment.
|
||||||
|
if len(config.CTs) > 0 {
|
||||||
|
if ctClient, err = ct.New(config.CTs[0]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var a = &Authority{
|
var a = &Authority{
|
||||||
config: config,
|
config: config,
|
||||||
certificates: new(sync.Map),
|
certificates: new(sync.Map),
|
||||||
|
@ -64,6 +74,7 @@ func New(config *Config) (*Authority, error) {
|
||||||
provisionerKeySetIndex: new(sync.Map),
|
provisionerKeySetIndex: new(sync.Map),
|
||||||
sortedProvisioners: sorted,
|
sortedProvisioners: sorted,
|
||||||
audiences: audiences,
|
audiences: audiences,
|
||||||
|
ctClient: ctClient,
|
||||||
}
|
}
|
||||||
if err := a.init(); err != nil {
|
if err := a.init(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/certificates/ct"
|
||||||
"github.com/smallstep/cli/crypto/tlsutil"
|
"github.com/smallstep/cli/crypto/tlsutil"
|
||||||
"github.com/smallstep/cli/crypto/x509util"
|
"github.com/smallstep/cli/crypto/x509util"
|
||||||
)
|
)
|
||||||
|
@ -46,6 +47,7 @@ type Config struct {
|
||||||
AuthorityConfig *AuthConfig `json:"authority,omitempty"`
|
AuthorityConfig *AuthConfig `json:"authority,omitempty"`
|
||||||
TLS *tlsutil.TLSOptions `json:"tls,omitempty"`
|
TLS *tlsutil.TLSOptions `json:"tls,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
|
CTs []ct.Config `json:"cts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthConfig represents the configuration options for the authority.
|
// AuthConfig represents the configuration options for the authority.
|
||||||
|
@ -153,5 +155,13 @@ func (c *Config) Validate() error {
|
||||||
c.TLS.Renegotiation = c.TLS.Renegotiation || DefaultTLSOptions.Renegotiation
|
c.TLS.Renegotiation = c.TLS.Renegotiation || DefaultTLSOptions.Renegotiation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(c.CTs) > 0 {
|
||||||
|
for _, ct := range c.CTs {
|
||||||
|
if err := ct.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return c.AuthorityConfig.Validate()
|
return c.AuthorityConfig.Validate()
|
||||||
}
|
}
|
||||||
|
|
115
authority/tls.go
115
authority/tls.go
|
@ -30,9 +30,13 @@ type SignOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// Step extensions OIDs
|
||||||
stepOIDRoot = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64}
|
stepOIDRoot = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64}
|
||||||
stepOIDProvisioner = append(asn1.ObjectIdentifier(nil), append(stepOIDRoot, 1)...)
|
stepOIDProvisioner = append(asn1.ObjectIdentifier(nil), append(stepOIDRoot, 1)...)
|
||||||
oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35}
|
oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35}
|
||||||
|
// Certificate transparency extensions OIDs
|
||||||
|
ctPoisonOID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}
|
||||||
|
ctSigendCertificateTimestampOID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2}
|
||||||
)
|
)
|
||||||
|
|
||||||
type stepProvisionerASN1 struct {
|
type stepProvisionerASN1 struct {
|
||||||
|
@ -124,6 +128,11 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts SignOptions, ext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add CT Poison extension
|
||||||
|
if a.ctClient != nil {
|
||||||
|
mods = append(mods, x509util.WithCTPoison())
|
||||||
|
}
|
||||||
|
|
||||||
stepCSR, err := stepx509.ParseCertificateRequest(csr.Raw)
|
stepCSR, err := stepx509.ParseCertificateRequest(csr.Raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, &apiError{errors.Wrap(err, "sign: error converting x509 csr to stepx509 csr"),
|
return nil, nil, &apiError{errors.Wrap(err, "sign: error converting x509 csr to stepx509 csr"),
|
||||||
|
@ -147,6 +156,25 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts SignOptions, ext
|
||||||
http.StatusInternalServerError, errContext}
|
http.StatusInternalServerError, errContext}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.ctClient != nil {
|
||||||
|
// Submit precertificate chain and get SCTs
|
||||||
|
scts, err := a.ctClient.GetSCTs(crtBytes, issIdentity.Crt.Raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, &apiError{errors.Wrap(err, "sign: error getting SCTs for certificate"),
|
||||||
|
http.StatusBadGateway, errContext}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove ct poison extension and add sct extension
|
||||||
|
leaf.RemoveExtension(ctPoisonOID)
|
||||||
|
leaf.AddExtension(scts.GetExtension())
|
||||||
|
|
||||||
|
// Recreate final certificate
|
||||||
|
if crtBytes, err = leaf.CreateCertificate(); err != nil {
|
||||||
|
return nil, nil, &apiError{errors.Wrap(err, "sign: error creating final leaf certificate"),
|
||||||
|
http.StatusInternalServerError, errContext}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serverCert, err := x509.ParseCertificate(crtBytes)
|
serverCert, err := x509.ParseCertificate(crtBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, &apiError{errors.Wrap(err, "sign: error parsing new leaf certificate"),
|
return nil, nil, &apiError{errors.Wrap(err, "sign: error parsing new leaf certificate"),
|
||||||
|
@ -159,6 +187,14 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts SignOptions, ext
|
||||||
http.StatusInternalServerError, errContext}
|
http.StatusInternalServerError, errContext}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.ctClient != nil {
|
||||||
|
// Submit final certificate chain
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return serverCert, caCert, nil
|
return serverCert, caCert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +209,7 @@ func (a *Authority) Renew(ocx *x509.Certificate) (*x509.Certificate, *x509.Certi
|
||||||
// Issuer
|
// Issuer
|
||||||
issIdentity := a.intermediateIdentity
|
issIdentity := a.intermediateIdentity
|
||||||
|
|
||||||
// Convert a realx509.Certificate to the step x509 Certificate.
|
// Convert a x509.Certificate to the step x509 Certificate.
|
||||||
oldCert, err := stepx509.ParseCertificate(ocx.Raw)
|
oldCert, err := stepx509.ParseCertificate(ocx.Raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, &apiError{
|
return nil, nil, &apiError{
|
||||||
|
@ -226,17 +262,45 @@ func (a *Authority) Renew(ocx *x509.Certificate) (*x509.Certificate, *x509.Certi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
leaf, err := x509util.NewLeafProfileWithTemplate(newCert,
|
opts := []x509util.WithOption{}
|
||||||
issIdentity.Crt, issIdentity.Key)
|
// Add CT Poison extension
|
||||||
|
if a.ctClient != nil {
|
||||||
|
opts = append(opts, x509util.WithCTPoison())
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf, err := x509util.NewLeafProfileWithTemplate(newCert, issIdentity.Crt, issIdentity.Key, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, &apiError{err, http.StatusInternalServerError, context{}}
|
return nil, nil, &apiError{err, http.StatusInternalServerError, context{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove previous SCTs if any
|
||||||
|
leaf.RemoveExtension(ctSigendCertificateTimestampOID)
|
||||||
|
|
||||||
crtBytes, err := leaf.CreateCertificate()
|
crtBytes, err := leaf.CreateCertificate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, &apiError{errors.Wrap(err, "error renewing certificate from existing server certificate"),
|
return nil, nil, &apiError{errors.Wrap(err, "error renewing certificate from existing server certificate"),
|
||||||
http.StatusInternalServerError, context{}}
|
http.StatusInternalServerError, context{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.ctClient != nil {
|
||||||
|
// Submit precertificate chain and get SCTs
|
||||||
|
scts, err := a.ctClient.GetSCTs(crtBytes, issIdentity.Crt.Raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, &apiError{errors.Wrap(err, "renew: error getting SCTs for certificate"),
|
||||||
|
http.StatusBadGateway, context{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove ct poison extension and add sct extension
|
||||||
|
leaf.RemoveExtension(ctPoisonOID)
|
||||||
|
leaf.AddExtension(scts.GetExtension())
|
||||||
|
|
||||||
|
// Recreate final certificate
|
||||||
|
if crtBytes, err = leaf.CreateCertificate(); err != nil {
|
||||||
|
return nil, nil, &apiError{errors.Wrap(err, "renew: error creating final leaf certificate"),
|
||||||
|
http.StatusInternalServerError, context{}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serverCert, err := x509.ParseCertificate(crtBytes)
|
serverCert, err := x509.ParseCertificate(crtBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, &apiError{errors.Wrap(err, "error parsing new server certificate"),
|
return nil, nil, &apiError{errors.Wrap(err, "error parsing new server certificate"),
|
||||||
|
@ -248,14 +312,30 @@ func (a *Authority) Renew(ocx *x509.Certificate) (*x509.Certificate, *x509.Certi
|
||||||
http.StatusInternalServerError, context{}}
|
http.StatusInternalServerError, context{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.ctClient != nil {
|
||||||
|
// Submit final certificate chain
|
||||||
|
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{}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return serverCert, caCert, nil
|
return serverCert, caCert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server.
|
// GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server.
|
||||||
func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
|
func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
|
||||||
|
opts := []x509util.WithOption{
|
||||||
|
x509util.WithHosts(strings.Join(a.config.DNSNames, ",")),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add CT Poison extension
|
||||||
|
if a.ctClient != nil {
|
||||||
|
opts = append(opts, x509util.WithCTPoison())
|
||||||
|
}
|
||||||
|
|
||||||
profile, err := x509util.NewLeafProfile("Step Online CA",
|
profile, err := x509util.NewLeafProfile("Step Online CA",
|
||||||
a.intermediateIdentity.Crt, a.intermediateIdentity.Key,
|
a.intermediateIdentity.Crt, a.intermediateIdentity.Key, opts...)
|
||||||
x509util.WithHosts(strings.Join(a.config.DNSNames, ",")))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -265,6 +345,23 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.ctClient != nil {
|
||||||
|
// Submit precertificate chain and get SCTs
|
||||||
|
scts, err := a.ctClient.GetSCTs(crtBytes, a.intermediateIdentity.Crt.Raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error getting SCTs for certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove ct poison extension and add sct extension
|
||||||
|
profile.RemoveExtension(ctPoisonOID)
|
||||||
|
profile.AddExtension(scts.GetExtension())
|
||||||
|
|
||||||
|
// Recreate final certificate
|
||||||
|
if crtBytes, err = profile.CreateCertificate(); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error creating final leaf certificate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
keyPEM, err := pemutil.Serialize(profile.SubjectPrivateKey())
|
keyPEM, err := pemutil.Serialize(profile.SubjectPrivateKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -281,6 +378,14 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.ctClient != nil {
|
||||||
|
// Submit final certificate chain
|
||||||
|
if _, err := a.ctClient.SubmitToLogs(crtBytes, intermediatePEM.Bytes); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error submitting final certificate to ct logs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tlsCrt, err := tls.X509KeyPair(append(crtPEM,
|
tlsCrt, err := tls.X509KeyPair(append(crtPEM,
|
||||||
pem.EncodeToMemory(intermediatePEM)...),
|
pem.EncodeToMemory(intermediatePEM)...),
|
||||||
pem.EncodeToMemory(keyPEM))
|
pem.EncodeToMemory(keyPEM))
|
||||||
|
|
|
@ -14,8 +14,8 @@ spec:
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: hello-mtls-client
|
- name: hello-mtls-client
|
||||||
image: hello-mtls-client-node:latest
|
image: sourishkrout/hello-mtls-client-node:latest
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: IfNotPresent
|
||||||
resources: {requests: {cpu: 10m, memory: 20Mi}}
|
resources: {requests: {cpu: 10m, memory: 20Mi}}
|
||||||
env:
|
env:
|
||||||
- name: HELLO_MTLS_URL
|
- name: HELLO_MTLS_URL
|
||||||
|
|
|
@ -28,6 +28,6 @@ spec:
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: hello-mtls
|
- name: hello-mtls
|
||||||
image: hello-mtls-server-node:latest
|
image: sourishkrout/hello-mtls-server-node:latest
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: IfNotPresent
|
||||||
resources: {requests: {cpu: 10m, memory: 20Mi}}
|
resources: {requests: {cpu: 10m, memory: 20Mi}}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM smallstep/step-cli:0.8.4-rc.1
|
FROM smallstep/step-cli:latest
|
||||||
|
|
||||||
ENV CA_NAME="Autocert"
|
ENV CA_NAME="Autocert"
|
||||||
ENV CA_DNS="ca.step.svc.cluster.local,127.0.0.1"
|
ENV CA_DNS="ca.step.svc.cluster.local,127.0.0.1"
|
||||||
|
@ -12,7 +12,10 @@ USER root
|
||||||
RUN curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \
|
RUN curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \
|
||||||
&& chmod +x /usr/local/bin/kubectl
|
&& chmod +x /usr/local/bin/kubectl
|
||||||
RUN apk --update add expect
|
RUN apk --update add expect
|
||||||
|
RUN apk --update add jq
|
||||||
|
|
||||||
COPY autocert.sh /home/step/
|
COPY autocert.sh /home/step/
|
||||||
|
COPY ct.json /home/step/
|
||||||
|
COPY ca /home/step/ca/
|
||||||
RUN chmod +x /home/step/autocert.sh
|
RUN chmod +x /home/step/autocert.sh
|
||||||
CMD ["/home/step/autocert.sh"]
|
CMD ["/home/step/autocert.sh"]
|
||||||
|
|
|
@ -8,8 +8,8 @@ read ANYKEY
|
||||||
|
|
||||||
STEPPATH=/home/step/.step
|
STEPPATH=/home/step/.step
|
||||||
|
|
||||||
CA_PASSWORD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32 ; echo '')
|
CA_PASSWORD=asdf
|
||||||
AUTOCERT_PASSWORD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32 ; echo '')
|
AUTOCERT_PASSWORD=asdf
|
||||||
|
|
||||||
echo -e "\e[1mChecking cluster permissions...\e[0m"
|
echo -e "\e[1mChecking cluster permissions...\e[0m"
|
||||||
|
|
||||||
|
@ -86,11 +86,18 @@ step ca init \
|
||||||
--with-ca-url "$CA_URL" \
|
--with-ca-url "$CA_URL" \
|
||||||
--password-file <(echo "$CA_PASSWORD")
|
--password-file <(echo "$CA_PASSWORD")
|
||||||
|
|
||||||
|
cp -f ./ca/ca.json $(step path)/config/ca.json
|
||||||
|
cp -f ./ca/root_ca.crt $(step path)/certs/root_ca.crt
|
||||||
|
cp -f ./ca/pubkey.pem $(step path)/certs/pubkey.pem
|
||||||
|
cp -f ./ca/intermediate_ca.crt $(step path)/certs/intermediate_ca.crt
|
||||||
|
cp -f ./ca/intermediate_ca_key $(step path)/certs/intermediate_ca_key
|
||||||
|
rm -f $(step path)/config/defaults.json
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo -e "\e[1mCreating autocert provisioner...\e[0m"
|
echo -e "\e[1mCreating autocert provisioner...\e[0m"
|
||||||
|
|
||||||
expect <<EOD
|
expect <<EOD
|
||||||
spawn step ca provisioner add autocert --create
|
spawn step ca provisioner add autocert --create --ca-config $(step path)/config/ca.json
|
||||||
expect "Please enter a password to encrypt the provisioner private key? \\\\\\[leave empty and we'll generate one\\\\\\]: "
|
expect "Please enter a password to encrypt the provisioner private key? \\\\\\[leave empty and we'll generate one\\\\\\]: "
|
||||||
send "${AUTOCERT_PASSWORD}\n"
|
send "${AUTOCERT_PASSWORD}\n"
|
||||||
expect eof
|
expect eof
|
||||||
|
@ -99,6 +106,10 @@ EOD
|
||||||
echo
|
echo
|
||||||
echo -e "\e[1mCreating step namespace and preparing environment...\e[0m"
|
echo -e "\e[1mCreating step namespace and preparing environment...\e[0m"
|
||||||
|
|
||||||
|
jq -s '.[0] * .[1]' $(step path)/config/ca.json ./ct.json > $(step path)/config/_ca.json
|
||||||
|
rm -f $(step path)/config/ca.json
|
||||||
|
mv -f $(step path)/config/_ca.json $(step path)/config/ca.json
|
||||||
|
|
||||||
kubectl create namespace step
|
kubectl create namespace step
|
||||||
|
|
||||||
kubectl -n step create configmap config --from-file $(step path)/config
|
kubectl -n step create configmap config --from-file $(step path)/config
|
||||||
|
@ -112,15 +123,15 @@ kubectl -n step create secret generic autocert-password --from-literal "password
|
||||||
echo
|
echo
|
||||||
echo -e "\e[1mDeploying certificate authority...\e[0m"
|
echo -e "\e[1mDeploying certificate authority...\e[0m"
|
||||||
|
|
||||||
kubectl apply -f https://raw.githubusercontent.com/smallstep/certificates/autocert/autocert/install/01-step-ca.yaml
|
kubectl apply -f https://raw.githubusercontent.com/smallstep/certificates/seb/ct-local/autocert/install/01-step-ca.yaml
|
||||||
kubectl -n step rollout status deployment/ca
|
kubectl -n step rollout status deployment/ca
|
||||||
|
|
||||||
# Deploy autocert, setup RBAC, and wait for rollout to complete
|
# Deploy autocert, setup RBAC, and wait for rollout to complete
|
||||||
echo
|
echo
|
||||||
echo -e "\e[1mDeploying autocert...\e[0m"
|
echo -e "\e[1mDeploying autocert...\e[0m"
|
||||||
|
|
||||||
kubectl apply -f https://raw.githubusercontent.com/smallstep/certificates/autocert/autocert/install/02-autocert.yaml
|
kubectl apply -f https://raw.githubusercontent.com/smallstep/certificates/seb/ct-local/autocert/install/02-autocert.yaml
|
||||||
kubectl apply -f https://raw.githubusercontent.com/smallstep/certificates/autocert/autocert/install/03-rbac.yaml
|
kubectl apply -f https://raw.githubusercontent.com/smallstep/certificates/seb/ct-local/autocert/install/03-rbac.yaml
|
||||||
kubectl -n step rollout status deployment/autocert
|
kubectl -n step rollout status deployment/autocert
|
||||||
|
|
||||||
# Some `base64`s wrap lines... no thanks!
|
# Some `base64`s wrap lines... no thanks!
|
||||||
|
|
62
autocert/init/ca/ca.json
Normal file
62
autocert/init/ca/ca.json
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"root": "/home/step/.step/certs/root_ca.crt",
|
||||||
|
"crt": "/home/step/.step/certs/intermediate_ca.crt",
|
||||||
|
"key": "/home/step/.step/certs/intermediate_ca_key",
|
||||||
|
"password": "asdf",
|
||||||
|
"address": ":4443",
|
||||||
|
"dnsNames": [
|
||||||
|
"ca.smallstep.com",
|
||||||
|
"ctca.step.toys",
|
||||||
|
"ca.step.svc.cluster.local"
|
||||||
|
],
|
||||||
|
"logger": {
|
||||||
|
"format": "text",
|
||||||
|
"level": "warn"
|
||||||
|
},
|
||||||
|
"authority": {
|
||||||
|
"claims": {
|
||||||
|
"minTLSCertDuration": "1m"
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"country": "US",
|
||||||
|
"organization": "Smallstep Labs Inc.",
|
||||||
|
"organizationalUnit": "",
|
||||||
|
"locality": "San Francisco",
|
||||||
|
"province": "CA",
|
||||||
|
"streetAddress": "",
|
||||||
|
"commonName": ""
|
||||||
|
},
|
||||||
|
"provisioners": [
|
||||||
|
{
|
||||||
|
"name": "mariano@smallstep.com",
|
||||||
|
"type": "jwk",
|
||||||
|
"key": {
|
||||||
|
"use": "sig",
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "jO37dtDbku-Qnabs5VR0Yw6YFFv9weA18dp3htvdEjs",
|
||||||
|
"crv": "P-256",
|
||||||
|
"alg": "ES256",
|
||||||
|
"x": "vo6GTwfXryV5WDI-_JL1FeK0k2AvWwUnSbtdSE3IQl0",
|
||||||
|
"y": "Z4j_nNmETqTsKq-6ZCjyCIIMNE_308Mx866z3pD6sJ0"
|
||||||
|
},
|
||||||
|
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiUUppQnNnN3VhY2MtQ1BiN3lCM2RhdyJ9.xIundEA1ZT3zk5qP9a9nH1n5pZK2bSTuYIAq6W1vMNJTkKZWIFjtmg.ztnvv4FBPExc2arS.OukgkTrlqpsWRMYM_l4-QHJqBMhfbeW164-qmULuzoNdo1umW8WLIX3Us8newUFh1zrJKDFJfrW_KT2C022_VKXOUO6LGX9WWN7RYiUC_aOY8O73xs1yq65whD7hMxPlq2fMd85AGvv0QQTwlG2lJ_Gw_bdbB3vDIBVJa5lywraG6tyVXT15yykVdoScc6fmxasi5tuoFW4VNjZzcgQ25cdpbwj0fvLACQWQjz49cGAjfpR6I8sys2pA55HobMdbyj7lKnDTD5TUMmoMB8WvGwleKyjLwZBPAhi_Wwrj1UXh3nrEWVFPJY9VLSCIIKdilugE62mW3reTNayqvN0.z-VKtvBPBATKsDtGppbj4w"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"cipherSuites": [
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||||
|
],
|
||||||
|
"minVersion": 1.2,
|
||||||
|
"maxVersion": 1.2,
|
||||||
|
"renegotiation": false
|
||||||
|
},
|
||||||
|
"cts": [
|
||||||
|
{
|
||||||
|
"uri": "http://trillian.step.toys:8080/smallstep",
|
||||||
|
"key": "/home/step/.step/certs/pubkey.pem"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
12
autocert/init/ca/intermediate_ca.crt
Normal file
12
autocert/init/ca/intermediate_ca.crt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBxTCCAWugAwIBAgIQDjIQy/8LZJZTC3tNwe6CfjAKBggqhkjOPQQDAjAcMRow
|
||||||
|
GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0xODExMjAyMDU5MjBaFw0yODEx
|
||||||
|
MTcyMDU5MjBaMCQxIjAgBgNVBAMTGVNtYWxsc3RlcCBJbnRlcm1lZGlhdGUgQ0Ew
|
||||||
|
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQGMnCwtwe7GD1XE/vibfav+Z63S0lx
|
||||||
|
H8AKvx/ek5AzVvbtih8rjtnYBBomzjHxSoIZmqdwljsEQAf1LAvgHlQwo4GGMIGD
|
||||||
|
MA4GA1UdDwEB/wQEAwIBpjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
|
||||||
|
EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUu97PaFQPfuyKOeew7Hg45WFI
|
||||||
|
AVMwHwYDVR0jBBgwFoAUF4980mRE8cQgMRTVBiUD2K+9SQswCgYIKoZIzj0EAwID
|
||||||
|
SAAwRQIgXiW7J3QLJDw6vzoI4PnI60jjO74O9dVyZFaQbLaxSHYCIQCmfsOmp7YS
|
||||||
|
rcJOsyNy8OjKyY9S9khfzgQdfaVfeq/QIQ==
|
||||||
|
-----END CERTIFICATE-----
|
8
autocert/init/ca/intermediate_ca_key
Normal file
8
autocert/init/ca/intermediate_ca_key
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
Proc-Type: 4,ENCRYPTED
|
||||||
|
DEK-Info: AES-256-CBC,b592733c8881da2a3235dc7a30a9c118
|
||||||
|
|
||||||
|
SXv4oaklKb6VXK+JTzRjHwroCRvzl3eXuCyHB4Rz9gAy82dCnUvIlFanhtC3nMp1
|
||||||
|
PQQgYaC3gbIo4mxQyChA7RLN6yfRSB67Z4U0GCZ4Eq5TFm5SAQJnUHEzt4XC0rAB
|
||||||
|
nUFQOKTyLmwEAsQd1LrAfmGplNNUHM4tZtr41FtnObQ=
|
||||||
|
-----END EC PRIVATE KEY-----
|
4
autocert/init/ca/pubkey.pem
Normal file
4
autocert/init/ca/pubkey.pem
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7lGU8z4HuEpzmOGPN7nPRs+H8THY
|
||||||
|
FKPgrsl2aN34CDlVIl8tyEBIIc8fFGmBGUR1WaIvHeOQcQgis3g+KFPD2Q==
|
||||||
|
-----END PUBLIC KEY-----
|
10
autocert/init/ca/root_ca.crt
Normal file
10
autocert/init/ca/root_ca.crt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBejCCASGgAwIBAgIQQ2IoFOF1vM1scQkJqsF3DzAKBggqhkjOPQQDAjAcMRow
|
||||||
|
GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0xODA0MTMxNzQxMjhaFw0yODA0
|
||||||
|
MTAxNzQxMjhaMBwxGjAYBgNVBAMTEVNtYWxsc3RlcCBSb290IENBMFkwEwYHKoZI
|
||||||
|
zj0CAQYIKoZIzj0DAQcDQgAEZcVMwFzo0l9bk09oz7NEiMnMFqzEAr2NpEBbx9jp
|
||||||
|
QmMtVx8LzGl8+LvBi8Xi4f3BJ3+1vWGPd75+LmnY5mPkKqNFMEMwDgYDVR0PAQH/
|
||||||
|
BAQDAgGmMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFBePfNJkRPHEIDEU
|
||||||
|
1QYlA9ivvUkLMAoGCCqGSM49BAMCA0cAMEQCICeD/FW/e2EVtRnjySqzeCpdbFLh
|
||||||
|
R7roQrKEwrzQtHKAAiAsYC6RssgTFCpV6Xn7FKKGaPSlQ94bxNIaYGlj3p4FlQ==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -34,7 +34,7 @@ spec:
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: ca
|
- name: ca
|
||||||
image: smallstep/step-ca:0.8.3
|
image: sourishkrout/step-ca:dev
|
||||||
env:
|
env:
|
||||||
- name: PWDPATH
|
- name: PWDPATH
|
||||||
value: /home/step/password/password
|
value: /home/step/password/password
|
||||||
|
|
|
@ -25,7 +25,7 @@ data:
|
||||||
certLifetime: 24h
|
certLifetime: 24h
|
||||||
renewer:
|
renewer:
|
||||||
name: autocert-renewer
|
name: autocert-renewer
|
||||||
image: smallstep/autocert-renewer:0.8.3
|
image: sourishkrout/autocert-renewer:dev
|
||||||
resources: {requests: {cpu: 10m, memory: 20Mi}}
|
resources: {requests: {cpu: 10m, memory: 20Mi}}
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
|
|
|
@ -5,4 +5,4 @@ ENV CRT="/var/run/autocert.step.sm/site.crt"
|
||||||
ENV KEY="/var/run/autocert.step.sm/site.key"
|
ENV KEY="/var/run/autocert.step.sm/site.key"
|
||||||
ENV STEP_ROOT="/var/run/autocert.step.sm/root.crt"
|
ENV STEP_ROOT="/var/run/autocert.step.sm/root.crt"
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/bash", "-c", "step ca renew --daemon $CRT $KEY"]
|
ENTRYPOINT ["/bin/bash", "-c", "step ca renew --daemon --renew-period 1m $CRT $KEY"]
|
||||||
|
|
|
@ -72,6 +72,8 @@ func NewTLSRenewer(cert *tls.Certificate, fn RenewFunc, opts ...tlsRenewerOption
|
||||||
if r.renewJitter == 0 {
|
if r.renewJitter == 0 {
|
||||||
r.renewJitter = period / 20
|
r.renewJitter = period / 20
|
||||||
}
|
}
|
||||||
|
// Initialize certNotAfter
|
||||||
|
r.certNotAfter = cert.Leaf.NotAfter.Add(-1 * time.Minute)
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
221
ct/ct.go
Normal file
221
ct/ct.go
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
package ct
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/pem"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ct "github.com/google/certificate-transparency-go"
|
||||||
|
"github.com/google/certificate-transparency-go/client"
|
||||||
|
"github.com/google/certificate-transparency-go/jsonclient"
|
||||||
|
cttls "github.com/google/certificate-transparency-go/tls"
|
||||||
|
ctx509 "github.com/google/certificate-transparency-go/x509"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
oidExtensionCTPoison = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}
|
||||||
|
oidSignedCertificateTimestampList = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config represents the configuration for the certificate authority client.
|
||||||
|
type Config struct {
|
||||||
|
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.
|
||||||
|
func (c *Config) Validate() error {
|
||||||
|
switch {
|
||||||
|
case c.URI == "":
|
||||||
|
return errors.New("ct uri cannot be empty")
|
||||||
|
case c.Key == "":
|
||||||
|
return errors.New("ct key cannot be empty")
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client is the interface used to communicate with the certificate transparency logs.
|
||||||
|
type Client interface {
|
||||||
|
GetSCTs(asn1Data ...[]byte) (*SCT, error)
|
||||||
|
SubmitToLogs(asn1Data ...[]byte) (*SCT, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type logClient interface {
|
||||||
|
AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error)
|
||||||
|
AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SCT represents a Signed Certificate Timestamp.
|
||||||
|
type SCT struct {
|
||||||
|
LogURL string
|
||||||
|
SCT *ct.SignedCertificateTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExtension returns the extension representing an SCT that will be added to
|
||||||
|
// a certificate.
|
||||||
|
func (t *SCT) GetExtension() pkix.Extension {
|
||||||
|
val, err := cttls.Marshal(*t.SCT)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
value, err := cttls.Marshal(ctx509.SignedCertificateTimestampList{
|
||||||
|
SCTList: []ctx509.SerializedSCT{
|
||||||
|
{Val: val},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
rawValue, err := asn1.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return pkix.Extension{
|
||||||
|
Id: oidSignedCertificateTimestampList,
|
||||||
|
Critical: false,
|
||||||
|
Value: rawValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPoisonExtension appends the ct poison extension to the given certificate.
|
||||||
|
func AddPoisonExtension(cert *x509.Certificate) {
|
||||||
|
cert.Extensions = append(cert.Extensions, pkix.Extension{
|
||||||
|
Id: oidExtensionCTPoison,
|
||||||
|
Critical: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientImpl is the implementation of a certificate transparency Client.
|
||||||
|
type ClientImpl struct {
|
||||||
|
config Config
|
||||||
|
logClient logClient
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Client
|
||||||
|
func New(c Config) (*ClientImpl, error) {
|
||||||
|
der, err := readPublicKey(c.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize ct client
|
||||||
|
logClient, err := client.New(c.URI, &http.Client{}, jsonclient.Options{
|
||||||
|
PublicKeyDER: der,
|
||||||
|
UserAgent: "smallstep certificates",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to create client to %s", c.URI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate connection
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if _, err := logClient.GetSTH(ctx); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to connect to %s", c.URI)
|
||||||
|
}
|
||||||
|
log.Printf("connecting to CT log %s", c.URI)
|
||||||
|
|
||||||
|
return &ClientImpl{
|
||||||
|
config: c,
|
||||||
|
logClient: logClient,
|
||||||
|
timeout: 30 * time.Second,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSCTs submit the precertificate to the logs and returns the list of SCTs to
|
||||||
|
// embed into the certificate.
|
||||||
|
func (c *ClientImpl) GetSCTs(asn1Data ...[]byte) (*SCT, error) {
|
||||||
|
chain := chainFromDERs(asn1Data)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
|
||||||
|
defer cancel()
|
||||||
|
sct, err := c.logClient.AddPreChain(ctx, chain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to get SCT from %s", c.config.URI)
|
||||||
|
}
|
||||||
|
logLeafHash("AddPreChain", asn1Data, sct, true)
|
||||||
|
return &SCT{
|
||||||
|
LogURL: c.config.URI,
|
||||||
|
SCT: sct,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitToLogs submits the certificate to the certificate transparency logs.
|
||||||
|
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 nil, errors.Wrapf(err, "failed submit certificate to %s", c.config.URI)
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
var chain []ct.ASN1Cert
|
||||||
|
for _, der := range asn1Data {
|
||||||
|
chain = append(chain, ct.ASN1Cert{Data: der})
|
||||||
|
}
|
||||||
|
return chain
|
||||||
|
}
|
||||||
|
|
||||||
|
func logLeafHash(op string, asn1Data [][]byte, sct *ct.SignedCertificateTimestamp, isPrecert bool) {
|
||||||
|
var etype ct.LogEntryType
|
||||||
|
if isPrecert {
|
||||||
|
etype = ct.PrecertLogEntryType
|
||||||
|
} else {
|
||||||
|
etype = ct.X509LogEntryType
|
||||||
|
}
|
||||||
|
|
||||||
|
chain := make([]*ctx509.Certificate, len(asn1Data))
|
||||||
|
for i := range asn1Data {
|
||||||
|
cert, err := ctx509.ParseCertificate(asn1Data[i])
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
chain[i] = cert
|
||||||
|
}
|
||||||
|
|
||||||
|
leafEntry, err := ct.MerkleTreeLeafFromChain(chain, etype, sct.Timestamp)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
leafHash, err := ct.LeafHashForLeaf(leafEntry)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
FROM smallstep/step-cli:0.8.3
|
FROM smallstep/step-cli:latest
|
||||||
|
|
||||||
ARG BINPATH="bin/step-ca"
|
ARG BINPATH="bin/step-ca"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue