Bumping k8s dependencies to 1.13
This commit is contained in:
361
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
361
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
@@ -44,7 +44,7 @@ var createCertRetryAfter = time.Minute
|
||||
var pseudoRand *lockedMathRand
|
||||
|
||||
func init() {
|
||||
src := mathrand.NewSource(timeNow().UnixNano())
|
||||
src := mathrand.NewSource(time.Now().UnixNano())
|
||||
pseudoRand = &lockedMathRand{rnd: mathrand.New(src)}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func HostWhitelist(hosts ...string) HostPolicy {
|
||||
}
|
||||
return func(_ context.Context, host string) error {
|
||||
if !whitelist[host] {
|
||||
return errors.New("acme/autocert: host not configured")
|
||||
return fmt.Errorf("acme/autocert: host %q not configured in HostWhitelist", host)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -81,9 +81,9 @@ func defaultHostPolicy(context.Context, string) error {
|
||||
}
|
||||
|
||||
// Manager is a stateful certificate manager built on top of acme.Client.
|
||||
// It obtains and refreshes certificates automatically using "tls-sni-01",
|
||||
// "tls-sni-02" and "http-01" challenge types, as well as providing them
|
||||
// to a TLS server via tls.Config.
|
||||
// It obtains and refreshes certificates automatically using "tls-alpn-01",
|
||||
// "tls-sni-01", "tls-sni-02" and "http-01" challenge types,
|
||||
// as well as providing them to a TLS server via tls.Config.
|
||||
//
|
||||
// You must specify a cache implementation, such as DirCache,
|
||||
// to reuse obtained certificates across program restarts.
|
||||
@@ -98,11 +98,11 @@ type Manager struct {
|
||||
// To always accept the terms, the callers can use AcceptTOS.
|
||||
Prompt func(tosURL string) bool
|
||||
|
||||
// Cache optionally stores and retrieves previously-obtained certificates.
|
||||
// If nil, certs will only be cached for the lifetime of the Manager.
|
||||
// Cache optionally stores and retrieves previously-obtained certificates
|
||||
// and other state. If nil, certs will only be cached for the lifetime of
|
||||
// the Manager. Multiple Managers can share the same Cache.
|
||||
//
|
||||
// Manager passes the Cache certificates data encoded in PEM, with private/public
|
||||
// parts combined in a single Cache.Put call, private key first.
|
||||
// Using a persistent Cache, such as DirCache, is strongly recommended.
|
||||
Cache Cache
|
||||
|
||||
// HostPolicy controls which domains the Manager will attempt
|
||||
@@ -127,8 +127,10 @@ type Manager struct {
|
||||
|
||||
// Client is used to perform low-level operations, such as account registration
|
||||
// and requesting new certificates.
|
||||
//
|
||||
// If Client is nil, a zero-value acme.Client is used with acme.LetsEncryptURL
|
||||
// directory endpoint and a newly-generated ECDSA P-256 key.
|
||||
// as directory endpoint. If the Client.Key is nil, a new ECDSA P-256 key is
|
||||
// generated and, if Cache is not nil, stored in cache.
|
||||
//
|
||||
// Mutating the field after the first call of GetCertificate method will have no effect.
|
||||
Client *acme.Client
|
||||
@@ -140,22 +142,30 @@ type Manager struct {
|
||||
// If the Client's account key is already registered, Email is not used.
|
||||
Email string
|
||||
|
||||
// ForceRSA makes the Manager generate certificates with 2048-bit RSA keys.
|
||||
// ForceRSA used to make the Manager generate RSA certificates. It is now ignored.
|
||||
//
|
||||
// If false, a default is used. Currently the default
|
||||
// is EC-based keys using the P-256 curve.
|
||||
// Deprecated: the Manager will request the correct type of certificate based
|
||||
// on what each client supports.
|
||||
ForceRSA bool
|
||||
|
||||
// ExtraExtensions are used when generating a new CSR (Certificate Request),
|
||||
// thus allowing customization of the resulting certificate.
|
||||
// For instance, TLS Feature Extension (RFC 7633) can be used
|
||||
// to prevent an OCSP downgrade attack.
|
||||
//
|
||||
// The field value is passed to crypto/x509.CreateCertificateRequest
|
||||
// in the template's ExtraExtensions field as is.
|
||||
ExtraExtensions []pkix.Extension
|
||||
|
||||
clientMu sync.Mutex
|
||||
client *acme.Client // initialized by acmeClient method
|
||||
|
||||
stateMu sync.Mutex
|
||||
state map[string]*certState // keyed by domain name
|
||||
state map[certKey]*certState
|
||||
|
||||
// renewal tracks the set of domains currently running renewal timers.
|
||||
// It is keyed by domain name.
|
||||
renewalMu sync.Mutex
|
||||
renewal map[string]*domainRenewal
|
||||
renewal map[certKey]*domainRenewal
|
||||
|
||||
// tokensMu guards the rest of the fields: tryHTTP01, certTokens and httpTokens.
|
||||
tokensMu sync.RWMutex
|
||||
@@ -167,21 +177,60 @@ type Manager struct {
|
||||
// to be provisioned.
|
||||
// The entries are stored for the duration of the authorization flow.
|
||||
httpTokens map[string][]byte
|
||||
// certTokens contains temporary certificates for tls-sni challenges
|
||||
// certTokens contains temporary certificates for tls-sni and tls-alpn challenges
|
||||
// and is keyed by token domain name, which matches server name of ClientHello.
|
||||
// Keys always have ".acme.invalid" suffix.
|
||||
// Keys always have ".acme.invalid" suffix for tls-sni. Otherwise, they are domain names
|
||||
// for tls-alpn.
|
||||
// The entries are stored for the duration of the authorization flow.
|
||||
certTokens map[string]*tls.Certificate
|
||||
// nowFunc, if not nil, returns the current time. This may be set for
|
||||
// testing purposes.
|
||||
nowFunc func() time.Time
|
||||
}
|
||||
|
||||
// certKey is the key by which certificates are tracked in state, renewal and cache.
|
||||
type certKey struct {
|
||||
domain string // without trailing dot
|
||||
isRSA bool // RSA cert for legacy clients (as opposed to default ECDSA)
|
||||
isToken bool // tls-based challenge token cert; key type is undefined regardless of isRSA
|
||||
}
|
||||
|
||||
func (c certKey) String() string {
|
||||
if c.isToken {
|
||||
return c.domain + "+token"
|
||||
}
|
||||
if c.isRSA {
|
||||
return c.domain + "+rsa"
|
||||
}
|
||||
return c.domain
|
||||
}
|
||||
|
||||
// TLSConfig creates a new TLS config suitable for net/http.Server servers,
|
||||
// supporting HTTP/2 and the tls-alpn-01 ACME challenge type.
|
||||
func (m *Manager) TLSConfig() *tls.Config {
|
||||
return &tls.Config{
|
||||
GetCertificate: m.GetCertificate,
|
||||
NextProtos: []string{
|
||||
"h2", "http/1.1", // enable HTTP/2
|
||||
acme.ALPNProto, // enable tls-alpn ACME challenges
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetCertificate implements the tls.Config.GetCertificate hook.
|
||||
// It provides a TLS certificate for hello.ServerName host, including answering
|
||||
// *.acme.invalid (TLS-SNI) challenges. All other fields of hello are ignored.
|
||||
// tls-alpn-01 and *.acme.invalid (tls-sni-01 and tls-sni-02) challenges.
|
||||
// All other fields of hello are ignored.
|
||||
//
|
||||
// If m.HostPolicy is non-nil, GetCertificate calls the policy before requesting
|
||||
// a new cert. A non-nil error returned from m.HostPolicy halts TLS negotiation.
|
||||
// The error is propagated back to the caller of GetCertificate and is user-visible.
|
||||
// This does not affect cached certs. See HostPolicy field description for more details.
|
||||
//
|
||||
// If GetCertificate is used directly, instead of via Manager.TLSConfig, package users will
|
||||
// also have to add acme.ALPNProto to NextProtos for tls-alpn-01, or use HTTPHandler
|
||||
// for http-01. (The tls-sni-* challenges have been deprecated by popular ACME providers
|
||||
// due to security issues in the ecosystem.)
|
||||
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if m.Prompt == nil {
|
||||
return nil, errors.New("acme/autocert: Manager.Prompt not set")
|
||||
@@ -194,7 +243,7 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
if !strings.Contains(strings.Trim(name, "."), ".") {
|
||||
return nil, errors.New("acme/autocert: server name component count invalid")
|
||||
}
|
||||
if strings.ContainsAny(name, `/\`) {
|
||||
if strings.ContainsAny(name, `+/\`) {
|
||||
return nil, errors.New("acme/autocert: server name contains invalid character")
|
||||
}
|
||||
|
||||
@@ -203,14 +252,17 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// check whether this is a token cert requested for TLS-SNI challenge
|
||||
if strings.HasSuffix(name, ".acme.invalid") {
|
||||
// Check whether this is a token cert requested for TLS-SNI or TLS-ALPN challenge.
|
||||
if wantsTokenCert(hello) {
|
||||
m.tokensMu.RLock()
|
||||
defer m.tokensMu.RUnlock()
|
||||
// It's ok to use the same token cert key for both tls-sni and tls-alpn
|
||||
// because there's always at most 1 token cert per on-going domain authorization.
|
||||
// See m.verify for details.
|
||||
if cert := m.certTokens[name]; cert != nil {
|
||||
return cert, nil
|
||||
}
|
||||
if cert, err := m.cacheGet(ctx, name); err == nil {
|
||||
if cert, err := m.cacheGet(ctx, certKey{domain: name, isToken: true}); err == nil {
|
||||
return cert, nil
|
||||
}
|
||||
// TODO: cache error results?
|
||||
@@ -218,8 +270,11 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
}
|
||||
|
||||
// regular domain
|
||||
name = strings.TrimSuffix(name, ".") // golang.org/issue/18114
|
||||
cert, err := m.cert(ctx, name)
|
||||
ck := certKey{
|
||||
domain: strings.TrimSuffix(name, "."), // golang.org/issue/18114
|
||||
isRSA: !supportsECDSA(hello),
|
||||
}
|
||||
cert, err := m.cert(ctx, ck)
|
||||
if err == nil {
|
||||
return cert, nil
|
||||
}
|
||||
@@ -231,14 +286,71 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
if err := m.hostPolicy()(ctx, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err = m.createCert(ctx, name)
|
||||
cert, err = m.createCert(ctx, ck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.cachePut(ctx, name, cert)
|
||||
m.cachePut(ctx, ck, cert)
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// wantsTokenCert reports whether a TLS request with SNI is made by a CA server
|
||||
// for a challenge verification.
|
||||
func wantsTokenCert(hello *tls.ClientHelloInfo) bool {
|
||||
// tls-alpn-01
|
||||
if len(hello.SupportedProtos) == 1 && hello.SupportedProtos[0] == acme.ALPNProto {
|
||||
return true
|
||||
}
|
||||
// tls-sni-xx
|
||||
return strings.HasSuffix(hello.ServerName, ".acme.invalid")
|
||||
}
|
||||
|
||||
func supportsECDSA(hello *tls.ClientHelloInfo) bool {
|
||||
// The "signature_algorithms" extension, if present, limits the key exchange
|
||||
// algorithms allowed by the cipher suites. See RFC 5246, section 7.4.1.4.1.
|
||||
if hello.SignatureSchemes != nil {
|
||||
ecdsaOK := false
|
||||
schemeLoop:
|
||||
for _, scheme := range hello.SignatureSchemes {
|
||||
const tlsECDSAWithSHA1 tls.SignatureScheme = 0x0203 // constant added in Go 1.10
|
||||
switch scheme {
|
||||
case tlsECDSAWithSHA1, tls.ECDSAWithP256AndSHA256,
|
||||
tls.ECDSAWithP384AndSHA384, tls.ECDSAWithP521AndSHA512:
|
||||
ecdsaOK = true
|
||||
break schemeLoop
|
||||
}
|
||||
}
|
||||
if !ecdsaOK {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if hello.SupportedCurves != nil {
|
||||
ecdsaOK := false
|
||||
for _, curve := range hello.SupportedCurves {
|
||||
if curve == tls.CurveP256 {
|
||||
ecdsaOK = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ecdsaOK {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, suite := range hello.CipherSuites {
|
||||
switch suite {
|
||||
case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HTTPHandler configures the Manager to provision ACME "http-01" challenge responses.
|
||||
// It returns an http.Handler that responds to the challenges and must be
|
||||
// running on port 80. If it receives a request that is not an ACME challenge,
|
||||
@@ -252,8 +364,8 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
// Because the fallback handler is run with unencrypted port 80 requests,
|
||||
// the fallback should not serve TLS-only requests.
|
||||
//
|
||||
// If HTTPHandler is never called, the Manager will only use TLS SNI
|
||||
// challenges for domain verification.
|
||||
// If HTTPHandler is never called, the Manager will only use the "tls-alpn-01"
|
||||
// challenge for domain verification.
|
||||
func (m *Manager) HTTPHandler(fallback http.Handler) http.Handler {
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
@@ -304,16 +416,16 @@ func stripPort(hostport string) string {
|
||||
// cert returns an existing certificate either from m.state or cache.
|
||||
// If a certificate is found in cache but not in m.state, the latter will be filled
|
||||
// with the cached value.
|
||||
func (m *Manager) cert(ctx context.Context, name string) (*tls.Certificate, error) {
|
||||
func (m *Manager) cert(ctx context.Context, ck certKey) (*tls.Certificate, error) {
|
||||
m.stateMu.Lock()
|
||||
if s, ok := m.state[name]; ok {
|
||||
if s, ok := m.state[ck]; ok {
|
||||
m.stateMu.Unlock()
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
return s.tlscert()
|
||||
}
|
||||
defer m.stateMu.Unlock()
|
||||
cert, err := m.cacheGet(ctx, name)
|
||||
cert, err := m.cacheGet(ctx, ck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -322,25 +434,25 @@ func (m *Manager) cert(ctx context.Context, name string) (*tls.Certificate, erro
|
||||
return nil, errors.New("acme/autocert: private key cannot sign")
|
||||
}
|
||||
if m.state == nil {
|
||||
m.state = make(map[string]*certState)
|
||||
m.state = make(map[certKey]*certState)
|
||||
}
|
||||
s := &certState{
|
||||
key: signer,
|
||||
cert: cert.Certificate,
|
||||
leaf: cert.Leaf,
|
||||
}
|
||||
m.state[name] = s
|
||||
go m.renew(name, s.key, s.leaf.NotAfter)
|
||||
m.state[ck] = s
|
||||
go m.renew(ck, s.key, s.leaf.NotAfter)
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// cacheGet always returns a valid certificate, or an error otherwise.
|
||||
// If a cached certficate exists but is not valid, ErrCacheMiss is returned.
|
||||
func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate, error) {
|
||||
// If a cached certificate exists but is not valid, ErrCacheMiss is returned.
|
||||
func (m *Manager) cacheGet(ctx context.Context, ck certKey) (*tls.Certificate, error) {
|
||||
if m.Cache == nil {
|
||||
return nil, ErrCacheMiss
|
||||
}
|
||||
data, err := m.Cache.Get(ctx, domain)
|
||||
data, err := m.Cache.Get(ctx, ck.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -371,7 +483,7 @@ func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate
|
||||
}
|
||||
|
||||
// verify and create TLS cert
|
||||
leaf, err := validCert(domain, pubDER, privKey)
|
||||
leaf, err := validCert(ck, pubDER, privKey, m.now())
|
||||
if err != nil {
|
||||
return nil, ErrCacheMiss
|
||||
}
|
||||
@@ -383,7 +495,7 @@ func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate
|
||||
return tlscert, nil
|
||||
}
|
||||
|
||||
func (m *Manager) cachePut(ctx context.Context, domain string, tlscert *tls.Certificate) error {
|
||||
func (m *Manager) cachePut(ctx context.Context, ck certKey, tlscert *tls.Certificate) error {
|
||||
if m.Cache == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -415,7 +527,7 @@ func (m *Manager) cachePut(ctx context.Context, domain string, tlscert *tls.Cert
|
||||
}
|
||||
}
|
||||
|
||||
return m.Cache.Put(ctx, domain, buf.Bytes())
|
||||
return m.Cache.Put(ctx, ck.String(), buf.Bytes())
|
||||
}
|
||||
|
||||
func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
|
||||
@@ -432,9 +544,9 @@ func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
|
||||
//
|
||||
// If the domain is already being verified, it waits for the existing verification to complete.
|
||||
// Either way, createCert blocks for the duration of the whole process.
|
||||
func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certificate, error) {
|
||||
func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate, error) {
|
||||
// TODO: maybe rewrite this whole piece using sync.Once
|
||||
state, err := m.certState(domain)
|
||||
state, err := m.certState(ck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -452,44 +564,44 @@ func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certifica
|
||||
defer state.Unlock()
|
||||
state.locked = false
|
||||
|
||||
der, leaf, err := m.authorizedCert(ctx, state.key, domain)
|
||||
der, leaf, err := m.authorizedCert(ctx, state.key, ck)
|
||||
if err != nil {
|
||||
// Remove the failed state after some time,
|
||||
// making the manager call createCert again on the following TLS hello.
|
||||
time.AfterFunc(createCertRetryAfter, func() {
|
||||
defer testDidRemoveState(domain)
|
||||
defer testDidRemoveState(ck)
|
||||
m.stateMu.Lock()
|
||||
defer m.stateMu.Unlock()
|
||||
// Verify the state hasn't changed and it's still invalid
|
||||
// before deleting.
|
||||
s, ok := m.state[domain]
|
||||
s, ok := m.state[ck]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if _, err := validCert(domain, s.cert, s.key); err == nil {
|
||||
if _, err := validCert(ck, s.cert, s.key, m.now()); err == nil {
|
||||
return
|
||||
}
|
||||
delete(m.state, domain)
|
||||
delete(m.state, ck)
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
state.cert = der
|
||||
state.leaf = leaf
|
||||
go m.renew(domain, state.key, state.leaf.NotAfter)
|
||||
go m.renew(ck, state.key, state.leaf.NotAfter)
|
||||
return state.tlscert()
|
||||
}
|
||||
|
||||
// certState returns a new or existing certState.
|
||||
// If a new certState is returned, state.exist is false and the state is locked.
|
||||
// The returned error is non-nil only in the case where a new state could not be created.
|
||||
func (m *Manager) certState(domain string) (*certState, error) {
|
||||
func (m *Manager) certState(ck certKey) (*certState, error) {
|
||||
m.stateMu.Lock()
|
||||
defer m.stateMu.Unlock()
|
||||
if m.state == nil {
|
||||
m.state = make(map[string]*certState)
|
||||
m.state = make(map[certKey]*certState)
|
||||
}
|
||||
// existing state
|
||||
if state, ok := m.state[domain]; ok {
|
||||
if state, ok := m.state[ck]; ok {
|
||||
return state, nil
|
||||
}
|
||||
|
||||
@@ -498,7 +610,7 @@ func (m *Manager) certState(domain string) (*certState, error) {
|
||||
err error
|
||||
key crypto.Signer
|
||||
)
|
||||
if m.ForceRSA {
|
||||
if ck.isRSA {
|
||||
key, err = rsa.GenerateKey(rand.Reader, 2048)
|
||||
} else {
|
||||
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
@@ -512,22 +624,22 @@ func (m *Manager) certState(domain string) (*certState, error) {
|
||||
locked: true,
|
||||
}
|
||||
state.Lock() // will be unlocked by m.certState caller
|
||||
m.state[domain] = state
|
||||
m.state[ck] = state
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// authorizedCert starts the domain ownership verification process and requests a new cert upon success.
|
||||
// The key argument is the certificate private key.
|
||||
func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain string) (der [][]byte, leaf *x509.Certificate, err error) {
|
||||
func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, ck certKey) (der [][]byte, leaf *x509.Certificate, err error) {
|
||||
client, err := m.acmeClient(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := m.verify(ctx, client, domain); err != nil {
|
||||
if err := m.verify(ctx, client, ck.domain); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
csr, err := certRequest(key, domain)
|
||||
csr, err := certRequest(key, ck.domain, m.ExtraExtensions)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -535,25 +647,55 @@ func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
leaf, err = validCert(domain, der, key)
|
||||
leaf, err = validCert(ck, der, key, m.now())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return der, leaf, nil
|
||||
}
|
||||
|
||||
// revokePendingAuthz revokes all authorizations idenfied by the elements of uri slice.
|
||||
// It ignores revocation errors.
|
||||
func (m *Manager) revokePendingAuthz(ctx context.Context, uri []string) {
|
||||
client, err := m.acmeClient(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, u := range uri {
|
||||
client.RevokeAuthorization(ctx, u)
|
||||
}
|
||||
}
|
||||
|
||||
// verify runs the identifier (domain) authorization flow
|
||||
// using each applicable ACME challenge type.
|
||||
func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string) error {
|
||||
// The list of challenge types we'll try to fulfill
|
||||
// in this specific order.
|
||||
challengeTypes := []string{"tls-sni-02", "tls-sni-01"}
|
||||
challengeTypes := []string{"tls-alpn-01", "tls-sni-02", "tls-sni-01"}
|
||||
m.tokensMu.RLock()
|
||||
if m.tryHTTP01 {
|
||||
challengeTypes = append(challengeTypes, "http-01")
|
||||
}
|
||||
m.tokensMu.RUnlock()
|
||||
|
||||
// Keep track of pending authzs and revoke the ones that did not validate.
|
||||
pendingAuthzs := make(map[string]bool)
|
||||
defer func() {
|
||||
var uri []string
|
||||
for k, pending := range pendingAuthzs {
|
||||
if pending {
|
||||
uri = append(uri, k)
|
||||
}
|
||||
}
|
||||
if len(uri) > 0 {
|
||||
// Use "detached" background context.
|
||||
// The revocations need not happen in the current verification flow.
|
||||
go m.revokePendingAuthz(context.Background(), uri)
|
||||
}
|
||||
}()
|
||||
|
||||
// errs accumulates challenge failure errors, printed if all fail
|
||||
errs := make(map[*acme.Challenge]error)
|
||||
var nextTyp int // challengeType index of the next challenge type to try
|
||||
for {
|
||||
// Start domain authorization and get the challenge.
|
||||
@@ -570,6 +712,8 @@ func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string
|
||||
return fmt.Errorf("acme/autocert: invalid authorization %q", authz.URI)
|
||||
}
|
||||
|
||||
pendingAuthzs[authz.URI] = true
|
||||
|
||||
// Pick the next preferred challenge.
|
||||
var chal *acme.Challenge
|
||||
for chal == nil && nextTyp < len(challengeTypes) {
|
||||
@@ -577,28 +721,44 @@ func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string
|
||||
nextTyp++
|
||||
}
|
||||
if chal == nil {
|
||||
return fmt.Errorf("acme/autocert: unable to authorize %q; tried %q", domain, challengeTypes)
|
||||
errorMsg := fmt.Sprintf("acme/autocert: unable to authorize %q", domain)
|
||||
for chal, err := range errs {
|
||||
errorMsg += fmt.Sprintf("; challenge %q failed with error: %v", chal.Type, err)
|
||||
}
|
||||
return errors.New(errorMsg)
|
||||
}
|
||||
cleanup, err := m.fulfill(ctx, client, chal)
|
||||
cleanup, err := m.fulfill(ctx, client, chal, domain)
|
||||
if err != nil {
|
||||
errs[chal] = err
|
||||
continue
|
||||
}
|
||||
defer cleanup()
|
||||
if _, err := client.Accept(ctx, chal); err != nil {
|
||||
errs[chal] = err
|
||||
continue
|
||||
}
|
||||
|
||||
// A challenge is fulfilled and accepted: wait for the CA to validate.
|
||||
if _, err := client.WaitAuthorization(ctx, authz.URI); err == nil {
|
||||
return nil
|
||||
if _, err := client.WaitAuthorization(ctx, authz.URI); err != nil {
|
||||
errs[chal] = err
|
||||
continue
|
||||
}
|
||||
delete(pendingAuthzs, authz.URI)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// fulfill provisions a response to the challenge chal.
|
||||
// The cleanup is non-nil only if provisioning succeeded.
|
||||
func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge) (cleanup func(), err error) {
|
||||
func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge, domain string) (cleanup func(), err error) {
|
||||
switch chal.Type {
|
||||
case "tls-alpn-01":
|
||||
cert, err := client.TLSALPN01ChallengeCert(chal.Token, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.putCertToken(ctx, domain, &cert)
|
||||
return func() { go m.deleteCertToken(domain) }, nil
|
||||
case "tls-sni-01":
|
||||
cert, name, err := client.TLSSNI01ChallengeCert(chal.Token)
|
||||
if err != nil {
|
||||
@@ -634,8 +794,8 @@ func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge {
|
||||
return nil
|
||||
}
|
||||
|
||||
// putCertToken stores the cert under the named key in both m.certTokens map
|
||||
// and m.Cache.
|
||||
// putCertToken stores the token certificate with the specified name
|
||||
// in both m.certTokens map and m.Cache.
|
||||
func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) {
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
@@ -643,17 +803,18 @@ func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certi
|
||||
m.certTokens = make(map[string]*tls.Certificate)
|
||||
}
|
||||
m.certTokens[name] = cert
|
||||
m.cachePut(ctx, name, cert)
|
||||
m.cachePut(ctx, certKey{domain: name, isToken: true}, cert)
|
||||
}
|
||||
|
||||
// deleteCertToken removes the token certificate for the specified domain name
|
||||
// deleteCertToken removes the token certificate with the specified name
|
||||
// from both m.certTokens map and m.Cache.
|
||||
func (m *Manager) deleteCertToken(name string) {
|
||||
m.tokensMu.Lock()
|
||||
defer m.tokensMu.Unlock()
|
||||
delete(m.certTokens, name)
|
||||
if m.Cache != nil {
|
||||
m.Cache.Delete(context.Background(), name)
|
||||
ck := certKey{domain: name, isToken: true}
|
||||
m.Cache.Delete(context.Background(), ck.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,7 +865,7 @@ func (m *Manager) deleteHTTPToken(tokenPath string) {
|
||||
// httpTokenCacheKey returns a key at which an http-01 token value may be stored
|
||||
// in the Manager's optional Cache.
|
||||
func httpTokenCacheKey(tokenPath string) string {
|
||||
return "http-01-" + path.Base(tokenPath)
|
||||
return path.Base(tokenPath) + "+http-01"
|
||||
}
|
||||
|
||||
// renew starts a cert renewal timer loop, one per domain.
|
||||
@@ -715,18 +876,18 @@ func httpTokenCacheKey(tokenPath string) string {
|
||||
//
|
||||
// The key argument is a certificate private key.
|
||||
// The exp argument is the cert expiration time (NotAfter).
|
||||
func (m *Manager) renew(domain string, key crypto.Signer, exp time.Time) {
|
||||
func (m *Manager) renew(ck certKey, key crypto.Signer, exp time.Time) {
|
||||
m.renewalMu.Lock()
|
||||
defer m.renewalMu.Unlock()
|
||||
if m.renewal[domain] != nil {
|
||||
if m.renewal[ck] != nil {
|
||||
// another goroutine is already on it
|
||||
return
|
||||
}
|
||||
if m.renewal == nil {
|
||||
m.renewal = make(map[string]*domainRenewal)
|
||||
m.renewal = make(map[certKey]*domainRenewal)
|
||||
}
|
||||
dr := &domainRenewal{m: m, domain: domain, key: key}
|
||||
m.renewal[domain] = dr
|
||||
dr := &domainRenewal{m: m, ck: ck, key: key}
|
||||
m.renewal[ck] = dr
|
||||
dr.start(exp)
|
||||
}
|
||||
|
||||
@@ -742,7 +903,10 @@ func (m *Manager) stopRenew() {
|
||||
}
|
||||
|
||||
func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) {
|
||||
const keyName = "acme_account.key"
|
||||
const keyName = "acme_account+key"
|
||||
|
||||
// Previous versions of autocert stored the value under a different key.
|
||||
const legacyKeyName = "acme_account.key"
|
||||
|
||||
genKey := func() (*ecdsa.PrivateKey, error) {
|
||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
@@ -753,6 +917,9 @@ func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) {
|
||||
}
|
||||
|
||||
data, err := m.Cache.Get(ctx, keyName)
|
||||
if err == ErrCacheMiss {
|
||||
data, err = m.Cache.Get(ctx, legacyKeyName)
|
||||
}
|
||||
if err == ErrCacheMiss {
|
||||
key, err := genKey()
|
||||
if err != nil {
|
||||
@@ -824,6 +991,13 @@ func (m *Manager) renewBefore() time.Duration {
|
||||
return 720 * time.Hour // 30 days
|
||||
}
|
||||
|
||||
func (m *Manager) now() time.Time {
|
||||
if m.nowFunc != nil {
|
||||
return m.nowFunc()
|
||||
}
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// certState is ready when its mutex is unlocked for reading.
|
||||
type certState struct {
|
||||
sync.RWMutex
|
||||
@@ -849,12 +1023,12 @@ func (s *certState) tlscert() (*tls.Certificate, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// certRequest creates a certificate request for the given common name cn
|
||||
// and optional SANs.
|
||||
func certRequest(key crypto.Signer, cn string, san ...string) ([]byte, error) {
|
||||
// certRequest generates a CSR for the given common name cn and optional SANs.
|
||||
func certRequest(key crypto.Signer, cn string, ext []pkix.Extension, san ...string) ([]byte, error) {
|
||||
req := &x509.CertificateRequest{
|
||||
Subject: pkix.Name{CommonName: cn},
|
||||
DNSNames: san,
|
||||
Subject: pkix.Name{CommonName: cn},
|
||||
DNSNames: san,
|
||||
ExtraExtensions: ext,
|
||||
}
|
||||
return x509.CreateCertificateRequest(rand.Reader, req, key)
|
||||
}
|
||||
@@ -885,12 +1059,12 @@ func parsePrivateKey(der []byte) (crypto.Signer, error) {
|
||||
return nil, errors.New("acme/autocert: failed to parse private key")
|
||||
}
|
||||
|
||||
// validCert parses a cert chain provided as der argument and verifies the leaf, der[0],
|
||||
// corresponds to the private key, as well as the domain match and expiration dates.
|
||||
// It doesn't do any revocation checking.
|
||||
// validCert parses a cert chain provided as der argument and verifies the leaf and der[0]
|
||||
// correspond to the private key, the domain and key type match, and expiration dates
|
||||
// are valid. It doesn't do any revocation checking.
|
||||
//
|
||||
// The returned value is the verified leaf cert.
|
||||
func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certificate, err error) {
|
||||
func validCert(ck certKey, der [][]byte, key crypto.Signer, now time.Time) (leaf *x509.Certificate, err error) {
|
||||
// parse public part(s)
|
||||
var n int
|
||||
for _, b := range der {
|
||||
@@ -902,22 +1076,21 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi
|
||||
n += copy(pub[n:], b)
|
||||
}
|
||||
x509Cert, err := x509.ParseCertificates(pub)
|
||||
if len(x509Cert) == 0 {
|
||||
if err != nil || len(x509Cert) == 0 {
|
||||
return nil, errors.New("acme/autocert: no public key found")
|
||||
}
|
||||
// verify the leaf is not expired and matches the domain name
|
||||
leaf = x509Cert[0]
|
||||
now := timeNow()
|
||||
if now.Before(leaf.NotBefore) {
|
||||
return nil, errors.New("acme/autocert: certificate is not valid yet")
|
||||
}
|
||||
if now.After(leaf.NotAfter) {
|
||||
return nil, errors.New("acme/autocert: expired certificate")
|
||||
}
|
||||
if err := leaf.VerifyHostname(domain); err != nil {
|
||||
if err := leaf.VerifyHostname(ck.domain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ensure the leaf corresponds to the private key
|
||||
// ensure the leaf corresponds to the private key and matches the certKey type
|
||||
switch pub := leaf.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
prv, ok := key.(*rsa.PrivateKey)
|
||||
@@ -927,6 +1100,9 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi
|
||||
if pub.N.Cmp(prv.N) != 0 {
|
||||
return nil, errors.New("acme/autocert: private key does not match public key")
|
||||
}
|
||||
if !ck.isRSA && !ck.isToken {
|
||||
return nil, errors.New("acme/autocert: key type does not match expected value")
|
||||
}
|
||||
case *ecdsa.PublicKey:
|
||||
prv, ok := key.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
@@ -935,6 +1111,9 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi
|
||||
if pub.X.Cmp(prv.X) != 0 || pub.Y.Cmp(prv.Y) != 0 {
|
||||
return nil, errors.New("acme/autocert: private key does not match public key")
|
||||
}
|
||||
if ck.isRSA && !ck.isToken {
|
||||
return nil, errors.New("acme/autocert: key type does not match expected value")
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("acme/autocert: unknown public key algorithm")
|
||||
}
|
||||
@@ -955,8 +1134,6 @@ func (r *lockedMathRand) int63n(max int64) int64 {
|
||||
|
||||
// For easier testing.
|
||||
var (
|
||||
timeNow = time.Now
|
||||
|
||||
// Called when a state is removed.
|
||||
testDidRemoveState = func(domain string) {}
|
||||
testDidRemoveState = func(certKey) {}
|
||||
)
|
||||
|
624
vendor/golang.org/x/crypto/acme/autocert/autocert_test.go
generated
vendored
624
vendor/golang.org/x/crypto/acme/autocert/autocert_test.go
generated
vendored
@@ -5,6 +5,7 @@
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
@@ -14,11 +15,13 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -29,6 +32,13 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
"golang.org/x/crypto/acme/autocert/internal/acmetest"
|
||||
)
|
||||
|
||||
var (
|
||||
exampleDomain = "example.org"
|
||||
exampleCertKey = certKey{domain: exampleDomain}
|
||||
exampleCertKeyRSA = certKey{domain: exampleDomain, isRSA: true}
|
||||
)
|
||||
|
||||
var discoTmpl = template.Must(template.New("disco").Parse(`{
|
||||
@@ -64,6 +74,7 @@ var authzTmpl = template.Must(template.New("authz").Parse(`{
|
||||
}`))
|
||||
|
||||
type memCache struct {
|
||||
t *testing.T
|
||||
mu sync.Mutex
|
||||
keyData map[string][]byte
|
||||
}
|
||||
@@ -79,7 +90,26 @@ func (m *memCache) Get(ctx context.Context, key string) ([]byte, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// filenameSafe returns whether all characters in s are printable ASCII
|
||||
// and safe to use in a filename on most filesystems.
|
||||
func filenameSafe(s string) bool {
|
||||
for _, c := range s {
|
||||
if c < 0x20 || c > 0x7E {
|
||||
return false
|
||||
}
|
||||
switch c {
|
||||
case '\\', '/', ':', '*', '?', '"', '<', '>', '|':
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *memCache) Put(ctx context.Context, key string, data []byte) error {
|
||||
if !filenameSafe(key) {
|
||||
m.t.Errorf("invalid characters in cache key %q", key)
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
@@ -95,12 +125,29 @@ func (m *memCache) Delete(ctx context.Context, key string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMemCache() *memCache {
|
||||
func newMemCache(t *testing.T) *memCache {
|
||||
return &memCache{
|
||||
t: t,
|
||||
keyData: make(map[string][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memCache) numCerts() int {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
res := 0
|
||||
for key := range m.keyData {
|
||||
if strings.HasSuffix(key, "+token") ||
|
||||
strings.HasSuffix(key, "+key") ||
|
||||
strings.HasSuffix(key, "+http-01") {
|
||||
continue
|
||||
}
|
||||
res++
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func dummyCert(pub interface{}, san ...string) ([]byte, error) {
|
||||
return dateDummyCert(pub, time.Now(), time.Now().Add(90*24*time.Hour), san...)
|
||||
}
|
||||
@@ -137,53 +184,58 @@ func decodePayload(v interface{}, r io.Reader) error {
|
||||
return json.Unmarshal(payload, v)
|
||||
}
|
||||
|
||||
func clientHelloInfo(sni string, ecdsaSupport bool) *tls.ClientHelloInfo {
|
||||
hello := &tls.ClientHelloInfo{
|
||||
ServerName: sni,
|
||||
CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
|
||||
}
|
||||
if ecdsaSupport {
|
||||
hello.CipherSuites = append(hello.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
|
||||
}
|
||||
return hello
|
||||
}
|
||||
|
||||
func TestGetCertificate(t *testing.T) {
|
||||
man := &Manager{Prompt: AcceptTOS}
|
||||
defer man.stopRenew()
|
||||
hello := &tls.ClientHelloInfo{ServerName: "example.org"}
|
||||
hello := clientHelloInfo("example.org", true)
|
||||
testGetCertificate(t, man, "example.org", hello)
|
||||
}
|
||||
|
||||
func TestGetCertificate_trailingDot(t *testing.T) {
|
||||
man := &Manager{Prompt: AcceptTOS}
|
||||
defer man.stopRenew()
|
||||
hello := &tls.ClientHelloInfo{ServerName: "example.org."}
|
||||
hello := clientHelloInfo("example.org.", true)
|
||||
testGetCertificate(t, man, "example.org", hello)
|
||||
}
|
||||
|
||||
func TestGetCertificate_ForceRSA(t *testing.T) {
|
||||
man := &Manager{
|
||||
Prompt: AcceptTOS,
|
||||
Cache: newMemCache(),
|
||||
Cache: newMemCache(t),
|
||||
ForceRSA: true,
|
||||
}
|
||||
defer man.stopRenew()
|
||||
hello := &tls.ClientHelloInfo{ServerName: "example.org"}
|
||||
testGetCertificate(t, man, "example.org", hello)
|
||||
hello := clientHelloInfo(exampleDomain, true)
|
||||
testGetCertificate(t, man, exampleDomain, hello)
|
||||
|
||||
cert, err := man.cacheGet(context.Background(), "example.org")
|
||||
// ForceRSA was deprecated and is now ignored.
|
||||
cert, err := man.cacheGet(context.Background(), exampleCertKey)
|
||||
if err != nil {
|
||||
t.Fatalf("man.cacheGet: %v", err)
|
||||
}
|
||||
if _, ok := cert.PrivateKey.(*rsa.PrivateKey); !ok {
|
||||
t.Errorf("cert.PrivateKey is %T; want *rsa.PrivateKey", cert.PrivateKey)
|
||||
if _, ok := cert.PrivateKey.(*ecdsa.PrivateKey); !ok {
|
||||
t.Errorf("cert.PrivateKey is %T; want *ecdsa.PrivateKey", cert.PrivateKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCertificate_nilPrompt(t *testing.T) {
|
||||
man := &Manager{}
|
||||
defer man.stopRenew()
|
||||
url, finish := startACMEServerStub(t, man, "example.org")
|
||||
url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), "example.org")
|
||||
defer finish()
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
man.Client = &acme.Client{
|
||||
Key: key,
|
||||
DirectoryURL: url,
|
||||
}
|
||||
hello := &tls.ClientHelloInfo{ServerName: "example.org"}
|
||||
man.Client = &acme.Client{DirectoryURL: url}
|
||||
hello := clientHelloInfo("example.org", true)
|
||||
if _, err := man.GetCertificate(hello); err == nil {
|
||||
t.Error("got certificate for example.org; wanted error")
|
||||
}
|
||||
@@ -197,7 +249,7 @@ func TestGetCertificate_expiredCache(t *testing.T) {
|
||||
}
|
||||
tmpl := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{CommonName: "example.org"},
|
||||
Subject: pkix.Name{CommonName: exampleDomain},
|
||||
NotAfter: time.Now(),
|
||||
}
|
||||
pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk)
|
||||
@@ -209,16 +261,16 @@ func TestGetCertificate_expiredCache(t *testing.T) {
|
||||
PrivateKey: pk,
|
||||
}
|
||||
|
||||
man := &Manager{Prompt: AcceptTOS, Cache: newMemCache()}
|
||||
man := &Manager{Prompt: AcceptTOS, Cache: newMemCache(t)}
|
||||
defer man.stopRenew()
|
||||
if err := man.cachePut(context.Background(), "example.org", tlscert); err != nil {
|
||||
if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil {
|
||||
t.Fatalf("man.cachePut: %v", err)
|
||||
}
|
||||
|
||||
// The expired cached cert should trigger a new cert issuance
|
||||
// and return without an error.
|
||||
hello := &tls.ClientHelloInfo{ServerName: "example.org"}
|
||||
testGetCertificate(t, man, "example.org", hello)
|
||||
hello := clientHelloInfo(exampleDomain, true)
|
||||
testGetCertificate(t, man, exampleDomain, hello)
|
||||
}
|
||||
|
||||
func TestGetCertificate_failedAttempt(t *testing.T) {
|
||||
@@ -227,7 +279,6 @@ func TestGetCertificate_failedAttempt(t *testing.T) {
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
const example = "example.org"
|
||||
d := createCertRetryAfter
|
||||
f := testDidRemoveState
|
||||
defer func() {
|
||||
@@ -236,51 +287,168 @@ func TestGetCertificate_failedAttempt(t *testing.T) {
|
||||
}()
|
||||
createCertRetryAfter = 0
|
||||
done := make(chan struct{})
|
||||
testDidRemoveState = func(domain string) {
|
||||
if domain != example {
|
||||
t.Errorf("testDidRemoveState: domain = %q; want %q", domain, example)
|
||||
testDidRemoveState = func(ck certKey) {
|
||||
if ck != exampleCertKey {
|
||||
t.Errorf("testDidRemoveState: domain = %v; want %v", ck, exampleCertKey)
|
||||
}
|
||||
close(done)
|
||||
}
|
||||
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
man := &Manager{
|
||||
Prompt: AcceptTOS,
|
||||
Client: &acme.Client{
|
||||
Key: key,
|
||||
DirectoryURL: ts.URL,
|
||||
},
|
||||
}
|
||||
defer man.stopRenew()
|
||||
hello := &tls.ClientHelloInfo{ServerName: example}
|
||||
hello := clientHelloInfo(exampleDomain, true)
|
||||
if _, err := man.GetCertificate(hello); err == nil {
|
||||
t.Error("GetCertificate: err is nil")
|
||||
}
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Errorf("took too long to remove the %q state", example)
|
||||
t.Errorf("took too long to remove the %q state", exampleCertKey)
|
||||
case <-done:
|
||||
man.stateMu.Lock()
|
||||
defer man.stateMu.Unlock()
|
||||
if v, exist := man.state[example]; exist {
|
||||
t.Errorf("state exists for %q: %+v", example, v)
|
||||
if v, exist := man.state[exampleCertKey]; exist {
|
||||
t.Errorf("state exists for %v: %+v", exampleCertKey, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testGetCertificate_tokenCache tests the fallback of token certificate fetches
|
||||
// to cache when Manager.certTokens misses. ecdsaSupport refers to the CA when
|
||||
// verifying the certificate token.
|
||||
func testGetCertificate_tokenCache(t *testing.T, ecdsaSupport bool) {
|
||||
man1 := &Manager{
|
||||
Cache: newMemCache(t),
|
||||
Prompt: AcceptTOS,
|
||||
}
|
||||
defer man1.stopRenew()
|
||||
man2 := &Manager{
|
||||
Cache: man1.Cache,
|
||||
Prompt: AcceptTOS,
|
||||
}
|
||||
defer man2.stopRenew()
|
||||
|
||||
// Send the verification request to a different Manager from the one that
|
||||
// initiated the authorization, when they share caches.
|
||||
url, finish := startACMEServerStub(t, getCertificateFromManager(man2, ecdsaSupport), "example.org")
|
||||
defer finish()
|
||||
man1.Client = &acme.Client{DirectoryURL: url}
|
||||
hello := clientHelloInfo("example.org", true)
|
||||
if _, err := man1.GetCertificate(hello); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := man2.GetCertificate(hello); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCertificate_tokenCache(t *testing.T) {
|
||||
t.Run("ecdsaSupport=true", func(t *testing.T) {
|
||||
testGetCertificate_tokenCache(t, true)
|
||||
})
|
||||
t.Run("ecdsaSupport=false", func(t *testing.T) {
|
||||
testGetCertificate_tokenCache(t, false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetCertificate_ecdsaVsRSA(t *testing.T) {
|
||||
cache := newMemCache(t)
|
||||
man := &Manager{Prompt: AcceptTOS, Cache: cache}
|
||||
defer man.stopRenew()
|
||||
url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), "example.org")
|
||||
defer finish()
|
||||
man.Client = &acme.Client{DirectoryURL: url}
|
||||
|
||||
cert, err := man.GetCertificate(clientHelloInfo("example.org", true))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, ok := cert.Leaf.PublicKey.(*ecdsa.PublicKey); !ok {
|
||||
t.Error("an ECDSA client was served a non-ECDSA certificate")
|
||||
}
|
||||
|
||||
cert, err = man.GetCertificate(clientHelloInfo("example.org", false))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, ok := cert.Leaf.PublicKey.(*rsa.PublicKey); !ok {
|
||||
t.Error("a RSA client was served a non-RSA certificate")
|
||||
}
|
||||
|
||||
if _, err := man.GetCertificate(clientHelloInfo("example.org", true)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := man.GetCertificate(clientHelloInfo("example.org", false)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if numCerts := cache.numCerts(); numCerts != 2 {
|
||||
t.Errorf("found %d certificates in cache; want %d", numCerts, 2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCertificate_wrongCacheKeyType(t *testing.T) {
|
||||
cache := newMemCache(t)
|
||||
man := &Manager{Prompt: AcceptTOS, Cache: cache}
|
||||
defer man.stopRenew()
|
||||
url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), exampleDomain)
|
||||
defer finish()
|
||||
man.Client = &acme.Client{DirectoryURL: url}
|
||||
|
||||
// Make an RSA cert and cache it without suffix.
|
||||
pk, err := rsa.GenerateKey(rand.Reader, 512)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpl := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{CommonName: exampleDomain},
|
||||
NotAfter: time.Now().Add(90 * 24 * time.Hour),
|
||||
}
|
||||
pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rsaCert := &tls.Certificate{
|
||||
Certificate: [][]byte{pub},
|
||||
PrivateKey: pk,
|
||||
}
|
||||
if err := man.cachePut(context.Background(), exampleCertKey, rsaCert); err != nil {
|
||||
t.Fatalf("man.cachePut: %v", err)
|
||||
}
|
||||
|
||||
// The RSA cached cert should be silently ignored and replaced.
|
||||
cert, err := man.GetCertificate(clientHelloInfo(exampleDomain, true))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, ok := cert.Leaf.PublicKey.(*ecdsa.PublicKey); !ok {
|
||||
t.Error("an ECDSA client was served a non-ECDSA certificate")
|
||||
}
|
||||
if numCerts := cache.numCerts(); numCerts != 1 {
|
||||
t.Errorf("found %d certificates in cache; want %d", numCerts, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func getCertificateFromManager(man *Manager, ecdsaSupport bool) func(string) error {
|
||||
return func(sni string) error {
|
||||
_, err := man.GetCertificate(clientHelloInfo(sni, ecdsaSupport))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// startACMEServerStub runs an ACME server
|
||||
// The domain argument is the expected domain name of a certificate request.
|
||||
func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string, finish func()) {
|
||||
// TODO: Drop this in favour of x/crypto/acme/autocert/internal/acmetest.
|
||||
func startACMEServerStub(t *testing.T, getCertificate func(string) error, domain string) (url string, finish func()) {
|
||||
// echo token-02 | shasum -a 256
|
||||
// then divide result in 2 parts separated by dot
|
||||
tokenCertName := "4e8eb87631187e9ff2153b56b13a4dec.13a35d002e485d60ff37354b32f665d9.token.acme.invalid"
|
||||
verifyTokenCert := func() {
|
||||
hello := &tls.ClientHelloInfo{ServerName: tokenCertName}
|
||||
_, err := man.GetCertificate(hello)
|
||||
if err != nil {
|
||||
if err := getCertificate(tokenCertName); err != nil {
|
||||
t.Errorf("verifyTokenCert: GetCertificate(%q): %v", tokenCertName, err)
|
||||
return
|
||||
}
|
||||
@@ -362,8 +530,7 @@ func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string,
|
||||
tick := time.NewTicker(100 * time.Millisecond)
|
||||
defer tick.Stop()
|
||||
for {
|
||||
hello := &tls.ClientHelloInfo{ServerName: tokenCertName}
|
||||
if _, err := man.GetCertificate(hello); err != nil {
|
||||
if err := getCertificate(tokenCertName); err != nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
@@ -387,21 +554,13 @@ func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string,
|
||||
// tests man.GetCertificate flow using the provided hello argument.
|
||||
// The domain argument is the expected domain name of a certificate request.
|
||||
func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.ClientHelloInfo) {
|
||||
url, finish := startACMEServerStub(t, man, domain)
|
||||
url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), domain)
|
||||
defer finish()
|
||||
|
||||
// use EC key to run faster on 386
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
man.Client = &acme.Client{
|
||||
Key: key,
|
||||
DirectoryURL: url,
|
||||
}
|
||||
man.Client = &acme.Client{DirectoryURL: url}
|
||||
|
||||
// simulate tls.Config.GetCertificate
|
||||
var tlscert *tls.Certificate
|
||||
var err error
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
tlscert, err = man.GetCertificate(hello)
|
||||
@@ -445,13 +604,13 @@ func TestVerifyHTTP01(t *testing.T) {
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("http token: w.Code = %d; want %d", w.Code, http.StatusOK)
|
||||
}
|
||||
if v := string(w.Body.Bytes()); !strings.HasPrefix(v, "token-http-01.") {
|
||||
if v := w.Body.String(); !strings.HasPrefix(v, "token-http-01.") {
|
||||
t.Errorf("http token value = %q; want 'token-http-01.' prefix", v)
|
||||
}
|
||||
}
|
||||
|
||||
// ACME CA server stub, only the needed bits.
|
||||
// TODO: Merge this with startACMEServerStub, making it a configurable CA for testing.
|
||||
// TODO: Replace this with x/crypto/acme/autocert/internal/acmetest.
|
||||
var ca *httptest.Server
|
||||
ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
@@ -505,18 +664,18 @@ func TestVerifyHTTP01(t *testing.T) {
|
||||
}))
|
||||
defer ca.Close()
|
||||
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := &Manager{
|
||||
Client: &acme.Client{
|
||||
Key: key,
|
||||
DirectoryURL: ca.URL,
|
||||
},
|
||||
}
|
||||
http01 = m.HTTPHandler(nil)
|
||||
if err := m.verify(context.Background(), m.Client, "example.org"); err != nil {
|
||||
ctx := context.Background()
|
||||
client, err := m.acmeClient(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("m.acmeClient: %v", err)
|
||||
}
|
||||
if err := m.verify(ctx, client, "example.org"); err != nil {
|
||||
t.Errorf("m.verify: %v", err)
|
||||
}
|
||||
// Only tls-sni-01, tls-sni-02 and http-01 must be accepted
|
||||
@@ -529,6 +688,111 @@ func TestVerifyHTTP01(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRevokeFailedAuthz(t *testing.T) {
|
||||
// Prefill authorization URIs expected to be revoked.
|
||||
// The challenges are selected in a specific order,
|
||||
// each tried within a newly created authorization.
|
||||
// This means each authorization URI corresponds to a different challenge type.
|
||||
revokedAuthz := map[string]bool{
|
||||
"/authz/0": false, // tls-sni-02
|
||||
"/authz/1": false, // tls-sni-01
|
||||
"/authz/2": false, // no viable challenge, but authz is created
|
||||
}
|
||||
|
||||
var authzCount int // num. of created authorizations
|
||||
var revokeCount int // num. of revoked authorizations
|
||||
done := make(chan struct{}) // closed when revokeCount is 3
|
||||
|
||||
// ACME CA server stub, only the needed bits.
|
||||
// TODO: Replace this with x/crypto/acme/autocert/internal/acmetest.
|
||||
var ca *httptest.Server
|
||||
ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
if r.Method == "HEAD" {
|
||||
// a nonce request
|
||||
return
|
||||
}
|
||||
|
||||
switch r.URL.Path {
|
||||
// Discovery.
|
||||
case "/":
|
||||
if err := discoTmpl.Execute(w, ca.URL); err != nil {
|
||||
t.Errorf("discoTmpl: %v", err)
|
||||
}
|
||||
// Client key registration.
|
||||
case "/new-reg":
|
||||
w.Write([]byte("{}"))
|
||||
// New domain authorization.
|
||||
case "/new-authz":
|
||||
w.Header().Set("Location", fmt.Sprintf("%s/authz/%d", ca.URL, authzCount))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := authzTmpl.Execute(w, ca.URL); err != nil {
|
||||
t.Errorf("authzTmpl: %v", err)
|
||||
}
|
||||
authzCount++
|
||||
// tls-sni-02 challenge "accept" request.
|
||||
case "/challenge/2":
|
||||
// Refuse.
|
||||
http.Error(w, "won't accept tls-sni-02 challenge", http.StatusBadRequest)
|
||||
// tls-sni-01 challenge "accept" request.
|
||||
case "/challenge/1":
|
||||
// Accept but the authorization will be "expired".
|
||||
w.Write([]byte("{}"))
|
||||
// Authorization requests.
|
||||
case "/authz/0", "/authz/1", "/authz/2":
|
||||
// Revocation requests.
|
||||
if r.Method == "POST" {
|
||||
var req struct{ Status string }
|
||||
if err := decodePayload(&req, r.Body); err != nil {
|
||||
t.Errorf("%s: decodePayload: %v", r.URL, err)
|
||||
}
|
||||
switch req.Status {
|
||||
case "deactivated":
|
||||
revokedAuthz[r.URL.Path] = true
|
||||
revokeCount++
|
||||
if revokeCount >= 3 {
|
||||
// Last authorization is revoked.
|
||||
defer close(done)
|
||||
}
|
||||
default:
|
||||
t.Errorf("%s: req.Status = %q; want 'deactivated'", r.URL, req.Status)
|
||||
}
|
||||
w.Write([]byte(`{"status": "invalid"}`))
|
||||
return
|
||||
}
|
||||
// Authorization status requests.
|
||||
// Simulate abandoned authorization, deleted by the CA.
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
t.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer ca.Close()
|
||||
|
||||
m := &Manager{
|
||||
Client: &acme.Client{DirectoryURL: ca.URL},
|
||||
}
|
||||
// Should fail and revoke 3 authorizations.
|
||||
// The first 2 are tsl-sni-02 and tls-sni-01 challenges.
|
||||
// The third time an authorization is created but no viable challenge is found.
|
||||
// See revokedAuthz above for more explanation.
|
||||
if _, err := m.createCert(context.Background(), exampleCertKey); err == nil {
|
||||
t.Errorf("m.createCert returned nil error")
|
||||
}
|
||||
select {
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Error("revocations took too long")
|
||||
case <-done:
|
||||
// revokeCount is at least 3.
|
||||
}
|
||||
for uri, ok := range revokedAuthz {
|
||||
if !ok {
|
||||
t.Errorf("%q authorization was not revoked", uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPHandlerDefaultFallback(t *testing.T) {
|
||||
tt := []struct {
|
||||
method, url string
|
||||
@@ -571,7 +835,7 @@ func TestHTTPHandlerDefaultFallback(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAccountKeyCache(t *testing.T) {
|
||||
m := Manager{Cache: newMemCache()}
|
||||
m := Manager{Cache: newMemCache(t)}
|
||||
ctx := context.Background()
|
||||
k1, err := m.accountKey(ctx)
|
||||
if err != nil {
|
||||
@@ -587,36 +851,57 @@ func TestAccountKeyCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpl := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{CommonName: "example.org"},
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
}
|
||||
pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &privKey.PublicKey, privKey)
|
||||
cert, err := dummyCert(ecdsaKey.Public(), exampleDomain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tlscert := &tls.Certificate{
|
||||
Certificate: [][]byte{pub},
|
||||
PrivateKey: privKey,
|
||||
ecdsaCert := &tls.Certificate{
|
||||
Certificate: [][]byte{cert},
|
||||
PrivateKey: ecdsaKey,
|
||||
}
|
||||
|
||||
man := &Manager{Cache: newMemCache()}
|
||||
rsaKey, err := rsa.GenerateKey(rand.Reader, 512)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cert, err = dummyCert(rsaKey.Public(), exampleDomain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rsaCert := &tls.Certificate{
|
||||
Certificate: [][]byte{cert},
|
||||
PrivateKey: rsaKey,
|
||||
}
|
||||
|
||||
man := &Manager{Cache: newMemCache(t)}
|
||||
defer man.stopRenew()
|
||||
ctx := context.Background()
|
||||
if err := man.cachePut(ctx, "example.org", tlscert); err != nil {
|
||||
|
||||
if err := man.cachePut(ctx, exampleCertKey, ecdsaCert); err != nil {
|
||||
t.Fatalf("man.cachePut: %v", err)
|
||||
}
|
||||
res, err := man.cacheGet(ctx, "example.org")
|
||||
if err := man.cachePut(ctx, exampleCertKeyRSA, rsaCert); err != nil {
|
||||
t.Fatalf("man.cachePut: %v", err)
|
||||
}
|
||||
|
||||
res, err := man.cacheGet(ctx, exampleCertKey)
|
||||
if err != nil {
|
||||
t.Fatalf("man.cacheGet: %v", err)
|
||||
}
|
||||
if res == nil {
|
||||
t.Fatal("res is nil")
|
||||
if res == nil || !bytes.Equal(res.Certificate[0], ecdsaCert.Certificate[0]) {
|
||||
t.Errorf("man.cacheGet = %+v; want %+v", res, ecdsaCert)
|
||||
}
|
||||
|
||||
res, err = man.cacheGet(ctx, exampleCertKeyRSA)
|
||||
if err != nil {
|
||||
t.Fatalf("man.cacheGet: %v", err)
|
||||
}
|
||||
if res == nil || !bytes.Equal(res.Certificate[0], rsaCert.Certificate[0]) {
|
||||
t.Errorf("man.cacheGet = %+v; want %+v", res, rsaCert)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -680,26 +965,28 @@ func TestValidCert(t *testing.T) {
|
||||
}
|
||||
|
||||
tt := []struct {
|
||||
domain string
|
||||
key crypto.Signer
|
||||
cert [][]byte
|
||||
ok bool
|
||||
ck certKey
|
||||
key crypto.Signer
|
||||
cert [][]byte
|
||||
ok bool
|
||||
}{
|
||||
{"example.org", key1, [][]byte{cert1}, true},
|
||||
{"example.org", key3, [][]byte{cert3}, true},
|
||||
{"example.org", key1, [][]byte{cert1, cert2, cert3}, true},
|
||||
{"example.org", key1, [][]byte{cert1, {1}}, false},
|
||||
{"example.org", key1, [][]byte{{1}}, false},
|
||||
{"example.org", key1, [][]byte{cert2}, false},
|
||||
{"example.org", key2, [][]byte{cert1}, false},
|
||||
{"example.org", key1, [][]byte{cert3}, false},
|
||||
{"example.org", key3, [][]byte{cert1}, false},
|
||||
{"example.net", key1, [][]byte{cert1}, false},
|
||||
{"example.org", key1, [][]byte{early}, false},
|
||||
{"example.org", key1, [][]byte{expired}, false},
|
||||
{certKey{domain: "example.org"}, key1, [][]byte{cert1}, true},
|
||||
{certKey{domain: "example.org", isRSA: true}, key3, [][]byte{cert3}, true},
|
||||
{certKey{domain: "example.org"}, key1, [][]byte{cert1, cert2, cert3}, true},
|
||||
{certKey{domain: "example.org"}, key1, [][]byte{cert1, {1}}, false},
|
||||
{certKey{domain: "example.org"}, key1, [][]byte{{1}}, false},
|
||||
{certKey{domain: "example.org"}, key1, [][]byte{cert2}, false},
|
||||
{certKey{domain: "example.org"}, key2, [][]byte{cert1}, false},
|
||||
{certKey{domain: "example.org"}, key1, [][]byte{cert3}, false},
|
||||
{certKey{domain: "example.org"}, key3, [][]byte{cert1}, false},
|
||||
{certKey{domain: "example.net"}, key1, [][]byte{cert1}, false},
|
||||
{certKey{domain: "example.org"}, key1, [][]byte{early}, false},
|
||||
{certKey{domain: "example.org"}, key1, [][]byte{expired}, false},
|
||||
{certKey{domain: "example.org", isRSA: true}, key1, [][]byte{cert1}, false},
|
||||
{certKey{domain: "example.org"}, key3, [][]byte{cert3}, false},
|
||||
}
|
||||
for i, test := range tt {
|
||||
leaf, err := validCert(test.domain, test.cert, test.key)
|
||||
leaf, err := validCert(test.ck, test.cert, test.key, now)
|
||||
if err != nil && test.ok {
|
||||
t.Errorf("%d: err = %v", i, err)
|
||||
}
|
||||
@@ -748,10 +1035,155 @@ func TestManagerGetCertificateBogusSNI(t *testing.T) {
|
||||
{"fo.o", "cache.Get of fo.o"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
_, err := m.GetCertificate(&tls.ClientHelloInfo{ServerName: tt.name})
|
||||
_, err := m.GetCertificate(clientHelloInfo(tt.name, true))
|
||||
got := fmt.Sprint(err)
|
||||
if got != tt.wantErr {
|
||||
t.Errorf("GetCertificate(SNI = %q) = %q; want %q", tt.name, got, tt.wantErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertRequest(t *testing.T) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// An extension from RFC7633. Any will do.
|
||||
ext := pkix.Extension{
|
||||
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1},
|
||||
Value: []byte("dummy"),
|
||||
}
|
||||
b, err := certRequest(key, "example.org", []pkix.Extension{ext}, "san.example.org")
|
||||
if err != nil {
|
||||
t.Fatalf("certRequest: %v", err)
|
||||
}
|
||||
r, err := x509.ParseCertificateRequest(b)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseCertificateRequest: %v", err)
|
||||
}
|
||||
var found bool
|
||||
for _, v := range r.Extensions {
|
||||
if v.Id.Equal(ext.Id) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("want %v in Extensions: %v", ext, r.Extensions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupportsECDSA(t *testing.T) {
|
||||
tests := []struct {
|
||||
CipherSuites []uint16
|
||||
SignatureSchemes []tls.SignatureScheme
|
||||
SupportedCurves []tls.CurveID
|
||||
ecdsaOk bool
|
||||
}{
|
||||
{[]uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
}, nil, nil, false},
|
||||
{[]uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
}, nil, nil, true},
|
||||
|
||||
// SignatureSchemes limits, not extends, CipherSuites
|
||||
{[]uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
}, []tls.SignatureScheme{
|
||||
tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
|
||||
}, nil, false},
|
||||
{[]uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
}, []tls.SignatureScheme{
|
||||
tls.PKCS1WithSHA256,
|
||||
}, nil, false},
|
||||
{[]uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
}, []tls.SignatureScheme{
|
||||
tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
|
||||
}, nil, true},
|
||||
|
||||
{[]uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
}, []tls.SignatureScheme{
|
||||
tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
|
||||
}, []tls.CurveID{
|
||||
tls.CurveP521,
|
||||
}, false},
|
||||
{[]uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
}, []tls.SignatureScheme{
|
||||
tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
|
||||
}, []tls.CurveID{
|
||||
tls.CurveP256,
|
||||
tls.CurveP521,
|
||||
}, true},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
result := supportsECDSA(&tls.ClientHelloInfo{
|
||||
CipherSuites: tt.CipherSuites,
|
||||
SignatureSchemes: tt.SignatureSchemes,
|
||||
SupportedCurves: tt.SupportedCurves,
|
||||
})
|
||||
if result != tt.ecdsaOk {
|
||||
t.Errorf("%d: supportsECDSA = %v; want %v", i, result, tt.ecdsaOk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add same end-to-end for http-01 challenge type.
|
||||
func TestEndToEnd(t *testing.T) {
|
||||
const domain = "example.org"
|
||||
|
||||
// ACME CA server
|
||||
ca := acmetest.NewCAServer([]string{"tls-alpn-01"}, []string{domain})
|
||||
defer ca.Close()
|
||||
|
||||
// User dummy server.
|
||||
m := &Manager{
|
||||
Prompt: AcceptTOS,
|
||||
Client: &acme.Client{DirectoryURL: ca.URL},
|
||||
}
|
||||
us := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("OK"))
|
||||
}))
|
||||
us.TLS = &tls.Config{
|
||||
NextProtos: []string{"http/1.1", acme.ALPNProto},
|
||||
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
cert, err := m.GetCertificate(hello)
|
||||
if err != nil {
|
||||
t.Errorf("m.GetCertificate: %v", err)
|
||||
}
|
||||
return cert, err
|
||||
},
|
||||
}
|
||||
us.StartTLS()
|
||||
defer us.Close()
|
||||
// In TLS-ALPN challenge verification, CA connects to the domain:443 in question.
|
||||
// Because the domain won't resolve in tests, we need to tell the CA
|
||||
// where to dial to instead.
|
||||
ca.Resolve(domain, strings.TrimPrefix(us.URL, "https://"))
|
||||
|
||||
// A client visiting user dummy server.
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: ca.Roots,
|
||||
ServerName: domain,
|
||||
},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
res, err := client.Get(us.URL)
|
||||
if err != nil {
|
||||
t.Logf("CA errors: %v", ca.Errors())
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
b, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v := string(b); v != "OK" {
|
||||
t.Errorf("user server response: %q; want 'OK'", v)
|
||||
}
|
||||
}
|
||||
|
6
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
6
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
@@ -16,10 +16,10 @@ import (
|
||||
var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
|
||||
|
||||
// Cache is used by Manager to store and retrieve previously obtained certificates
|
||||
// as opaque data.
|
||||
// and other account data as opaque blobs.
|
||||
//
|
||||
// The key argument of the methods refers to a domain name but need not be an FQDN.
|
||||
// Cache implementations should not rely on the key naming pattern.
|
||||
// Cache implementations should not rely on the key naming pattern. Keys can
|
||||
// include any printable ASCII characters, except the following: \/:*?"<>|
|
||||
type Cache interface {
|
||||
// Get returns a certificate data for the specified key.
|
||||
// If there's no such key, Get returns ErrCacheMiss.
|
||||
|
6
vendor/golang.org/x/crypto/acme/autocert/example_test.go
generated
vendored
6
vendor/golang.org/x/crypto/acme/autocert/example_test.go
generated
vendored
@@ -5,7 +5,6 @@
|
||||
package autocert_test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -25,12 +24,11 @@ func ExampleManager() {
|
||||
m := &autocert.Manager{
|
||||
Cache: autocert.DirCache("secret-dir"),
|
||||
Prompt: autocert.AcceptTOS,
|
||||
HostPolicy: autocert.HostWhitelist("example.org"),
|
||||
HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"),
|
||||
}
|
||||
go http.ListenAndServe(":http", m.HTTPHandler(nil))
|
||||
s := &http.Server{
|
||||
Addr: ":https",
|
||||
TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
|
||||
TLSConfig: m.TLSConfig(),
|
||||
}
|
||||
s.ListenAndServeTLS("", "")
|
||||
}
|
||||
|
416
vendor/golang.org/x/crypto/acme/autocert/internal/acmetest/ca.go
generated
vendored
Normal file
416
vendor/golang.org/x/crypto/acme/autocert/internal/acmetest/ca.go
generated
vendored
Normal file
@@ -0,0 +1,416 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package acmetest provides types for testing acme and autocert packages.
|
||||
//
|
||||
// TODO: Consider moving this to x/crypto/acme/internal/acmetest for acme tests as well.
|
||||
package acmetest
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CAServer is a simple test server which implements ACME spec bits needed for testing.
|
||||
type CAServer struct {
|
||||
URL string // server URL after it has been started
|
||||
Roots *x509.CertPool // CA root certificates; initialized in NewCAServer
|
||||
|
||||
rootKey crypto.Signer
|
||||
rootCert []byte // DER encoding
|
||||
rootTemplate *x509.Certificate
|
||||
|
||||
server *httptest.Server
|
||||
challengeTypes []string // supported challenge types
|
||||
domainsWhitelist []string // only these domains are valid for issuing, unless empty
|
||||
|
||||
mu sync.Mutex
|
||||
certCount int // number of issued certs
|
||||
domainAddr map[string]string // domain name to addr:port resolution
|
||||
authorizations map[string]*authorization // keyed by domain name
|
||||
errors []error // encountered client errors
|
||||
}
|
||||
|
||||
// NewCAServer creates a new ACME test server and starts serving requests.
|
||||
// The returned CAServer issues certs signed with the CA roots
|
||||
// available in the Roots field.
|
||||
//
|
||||
// The challengeTypes argument defines the supported ACME challenge types
|
||||
// sent to a client in a response for a domain authorization.
|
||||
// If domainsWhitelist is non-empty, the certs will be issued only for the specified
|
||||
// list of domains. Otherwise, any domain name is allowed.
|
||||
func NewCAServer(challengeTypes []string, domainsWhitelist []string) *CAServer {
|
||||
var whitelist []string
|
||||
for _, name := range domainsWhitelist {
|
||||
whitelist = append(whitelist, name)
|
||||
}
|
||||
sort.Strings(whitelist)
|
||||
ca := &CAServer{
|
||||
challengeTypes: challengeTypes,
|
||||
domainsWhitelist: whitelist,
|
||||
domainAddr: make(map[string]string),
|
||||
authorizations: make(map[string]*authorization),
|
||||
}
|
||||
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("ecdsa.GenerateKey: %v", err))
|
||||
}
|
||||
tmpl := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Test Acme Co"},
|
||||
CommonName: "Root CA",
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(365 * 24 * time.Hour),
|
||||
KeyUsage: x509.KeyUsageCertSign,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("x509.CreateCertificate: %v", err))
|
||||
}
|
||||
cert, err := x509.ParseCertificate(der)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("x509.ParseCertificate: %v", err))
|
||||
}
|
||||
ca.Roots = x509.NewCertPool()
|
||||
ca.Roots.AddCert(cert)
|
||||
ca.rootKey = key
|
||||
ca.rootCert = der
|
||||
ca.rootTemplate = tmpl
|
||||
|
||||
ca.server = httptest.NewServer(http.HandlerFunc(ca.handle))
|
||||
ca.URL = ca.server.URL
|
||||
return ca
|
||||
}
|
||||
|
||||
// Close shuts down the server and blocks until all outstanding
|
||||
// requests on this server have completed.
|
||||
func (ca *CAServer) Close() {
|
||||
ca.server.Close()
|
||||
}
|
||||
|
||||
// Errors returns all client errors.
|
||||
func (ca *CAServer) Errors() []error {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
return ca.errors
|
||||
}
|
||||
|
||||
// Resolve adds a domain to address resolution for the ca to dial to
|
||||
// when validating challenges for the domain authorization.
|
||||
func (ca *CAServer) Resolve(domain, addr string) {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
ca.domainAddr[domain] = addr
|
||||
}
|
||||
|
||||
type discovery struct {
|
||||
NewReg string `json:"new-reg"`
|
||||
NewAuthz string `json:"new-authz"`
|
||||
NewCert string `json:"new-cert"`
|
||||
}
|
||||
|
||||
type challenge struct {
|
||||
URI string `json:"uri"`
|
||||
Type string `json:"type"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type authorization struct {
|
||||
Status string `json:"status"`
|
||||
Challenges []challenge `json:"challenges"`
|
||||
|
||||
id int
|
||||
domain string
|
||||
}
|
||||
|
||||
func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Replay-Nonce", "nonce")
|
||||
if r.Method == "HEAD" {
|
||||
// a nonce request
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Verify nonce header for all POST requests.
|
||||
|
||||
switch {
|
||||
default:
|
||||
err := fmt.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
|
||||
// Discovery request.
|
||||
case r.URL.Path == "/":
|
||||
resp := &discovery{
|
||||
NewReg: ca.serverURL("/new-reg"),
|
||||
NewAuthz: ca.serverURL("/new-authz"),
|
||||
NewCert: ca.serverURL("/new-cert"),
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
panic(fmt.Sprintf("discovery response: %v", err))
|
||||
}
|
||||
|
||||
// Client key registration request.
|
||||
case r.URL.Path == "/new-reg":
|
||||
// TODO: Check the user account key against a ca.accountKeys?
|
||||
w.Write([]byte("{}"))
|
||||
|
||||
// Domain authorization request.
|
||||
case r.URL.Path == "/new-authz":
|
||||
var req struct {
|
||||
Identifier struct{ Value string }
|
||||
}
|
||||
if err := decodePayload(&req, r.Body); err != nil {
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
authz, ok := ca.authorizations[req.Identifier.Value]
|
||||
if !ok {
|
||||
authz = &authorization{
|
||||
domain: req.Identifier.Value,
|
||||
Status: "pending",
|
||||
}
|
||||
for _, typ := range ca.challengeTypes {
|
||||
authz.Challenges = append(authz.Challenges, challenge{
|
||||
Type: typ,
|
||||
URI: ca.serverURL("/challenge/%s/%s", typ, authz.domain),
|
||||
Token: challengeToken(authz.domain, typ),
|
||||
})
|
||||
}
|
||||
ca.authorizations[authz.domain] = authz
|
||||
}
|
||||
w.Header().Set("Location", ca.serverURL("/authz/%s", authz.domain))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := json.NewEncoder(w).Encode(authz); err != nil {
|
||||
panic(fmt.Sprintf("new authz response: %v", err))
|
||||
}
|
||||
|
||||
// Accept tls-alpn-01 challenge type requests.
|
||||
// TODO: Add http-01 and dns-01 handlers.
|
||||
case strings.HasPrefix(r.URL.Path, "/challenge/tls-alpn-01/"):
|
||||
domain := strings.TrimPrefix(r.URL.Path, "/challenge/tls-alpn-01/")
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
if _, ok := ca.authorizations[domain]; !ok {
|
||||
err := fmt.Errorf("challenge accept: no authz for %q", domain)
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
go func(domain string) {
|
||||
err := ca.verifyALPNChallenge(domain)
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
authz := ca.authorizations[domain]
|
||||
if err != nil {
|
||||
authz.Status = "invalid"
|
||||
return
|
||||
}
|
||||
authz.Status = "valid"
|
||||
|
||||
}(domain)
|
||||
w.Write([]byte("{}"))
|
||||
|
||||
// Get authorization status requests.
|
||||
case strings.HasPrefix(r.URL.Path, "/authz/"):
|
||||
domain := strings.TrimPrefix(r.URL.Path, "/authz/")
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
authz, ok := ca.authorizations[domain]
|
||||
if !ok {
|
||||
http.Error(w, fmt.Sprintf("no authz for %q", domain), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(authz); err != nil {
|
||||
panic(fmt.Sprintf("get authz for %q response: %v", domain, err))
|
||||
}
|
||||
|
||||
// Cert issuance request.
|
||||
case r.URL.Path == "/new-cert":
|
||||
var req struct {
|
||||
CSR string `json:"csr"`
|
||||
}
|
||||
decodePayload(&req, r.Body)
|
||||
b, _ := base64.RawURLEncoding.DecodeString(req.CSR)
|
||||
csr, err := x509.ParseCertificateRequest(b)
|
||||
if err != nil {
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
names := unique(append(csr.DNSNames, csr.Subject.CommonName))
|
||||
if err := ca.matchWhitelist(names); err != nil {
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if err := ca.authorized(names); err != nil {
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
der, err := ca.leafCert(csr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("new-cert response: ca.leafCert: %v", err)
|
||||
ca.addError(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
w.Header().Set("Link", fmt.Sprintf("<%s>; rel=up", ca.serverURL("/ca-cert")))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write(der)
|
||||
|
||||
// CA chain cert request.
|
||||
case r.URL.Path == "/ca-cert":
|
||||
w.Write(ca.rootCert)
|
||||
}
|
||||
}
|
||||
|
||||
func (ca *CAServer) addError(err error) {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
ca.errors = append(ca.errors, err)
|
||||
}
|
||||
|
||||
func (ca *CAServer) serverURL(format string, arg ...interface{}) string {
|
||||
return ca.server.URL + fmt.Sprintf(format, arg...)
|
||||
}
|
||||
|
||||
func (ca *CAServer) matchWhitelist(dnsNames []string) error {
|
||||
if len(ca.domainsWhitelist) == 0 {
|
||||
return nil
|
||||
}
|
||||
var nomatch []string
|
||||
for _, name := range dnsNames {
|
||||
i := sort.SearchStrings(ca.domainsWhitelist, name)
|
||||
if i == len(ca.domainsWhitelist) || ca.domainsWhitelist[i] != name {
|
||||
nomatch = append(nomatch, name)
|
||||
}
|
||||
}
|
||||
if len(nomatch) > 0 {
|
||||
return fmt.Errorf("matchWhitelist: some domains don't match: %q", nomatch)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ca *CAServer) authorized(dnsNames []string) error {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
var noauthz []string
|
||||
for _, name := range dnsNames {
|
||||
authz, ok := ca.authorizations[name]
|
||||
if !ok || authz.Status != "valid" {
|
||||
noauthz = append(noauthz, name)
|
||||
}
|
||||
}
|
||||
if len(noauthz) > 0 {
|
||||
return fmt.Errorf("CAServer: no authz for %q", noauthz)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ca *CAServer) leafCert(csr *x509.CertificateRequest) (der []byte, err error) {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
ca.certCount++ // next leaf cert serial number
|
||||
leaf := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(int64(ca.certCount)),
|
||||
Subject: pkix.Name{Organization: []string{"Test Acme Co"}},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(90 * 24 * time.Hour),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
DNSNames: csr.DNSNames,
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
if len(csr.DNSNames) == 0 {
|
||||
leaf.DNSNames = []string{csr.Subject.CommonName}
|
||||
}
|
||||
return x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, csr.PublicKey, ca.rootKey)
|
||||
}
|
||||
|
||||
func (ca *CAServer) addr(domain string) (string, error) {
|
||||
ca.mu.Lock()
|
||||
defer ca.mu.Unlock()
|
||||
addr, ok := ca.domainAddr[domain]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("CAServer: no addr resolution for %q", domain)
|
||||
}
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
func (ca *CAServer) verifyALPNChallenge(domain string) error {
|
||||
const acmeALPNProto = "acme-tls/1"
|
||||
|
||||
addr, err := ca.addr(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := tls.Dial("tcp", addr, &tls.Config{
|
||||
ServerName: domain,
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{acmeALPNProto},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v := conn.ConnectionState().NegotiatedProtocol; v != acmeALPNProto {
|
||||
return fmt.Errorf("CAServer: verifyALPNChallenge: negotiated proto is %q; want %q", v, acmeALPNProto)
|
||||
}
|
||||
if n := len(conn.ConnectionState().PeerCertificates); n != 1 {
|
||||
return fmt.Errorf("len(PeerCertificates) = %d; want 1", n)
|
||||
}
|
||||
// TODO: verify conn.ConnectionState().PeerCertificates[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodePayload(v interface{}, r io.Reader) error {
|
||||
var req struct{ Payload string }
|
||||
if err := json.NewDecoder(r).Decode(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
payload, err := base64.RawURLEncoding.DecodeString(req.Payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(payload, v)
|
||||
}
|
||||
|
||||
func challengeToken(domain, challType string) string {
|
||||
return fmt.Sprintf("token-%s-%s", domain, challType)
|
||||
}
|
||||
|
||||
func unique(a []string) []string {
|
||||
seen := make(map[string]bool)
|
||||
var res []string
|
||||
for _, s := range a {
|
||||
if s != "" && !seen[s] {
|
||||
seen[s] = true
|
||||
res = append(res, s)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
7
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
7
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
@@ -72,11 +72,8 @@ func NewListener(domains ...string) net.Listener {
|
||||
// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
|
||||
func (m *Manager) Listener() net.Listener {
|
||||
ln := &listener{
|
||||
m: m,
|
||||
conf: &tls.Config{
|
||||
GetCertificate: m.GetCertificate, // bonus: panic on nil m
|
||||
NextProtos: []string{"h2", "http/1.1"}, // Enable HTTP/2
|
||||
},
|
||||
m: m,
|
||||
conf: m.TLSConfig(),
|
||||
}
|
||||
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
|
||||
return ln
|
||||
|
45
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
45
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
@@ -17,9 +17,9 @@ const renewJitter = time.Hour
|
||||
// domainRenewal tracks the state used by the periodic timers
|
||||
// renewing a single domain's cert.
|
||||
type domainRenewal struct {
|
||||
m *Manager
|
||||
domain string
|
||||
key crypto.Signer
|
||||
m *Manager
|
||||
ck certKey
|
||||
key crypto.Signer
|
||||
|
||||
timerMu sync.Mutex
|
||||
timer *time.Timer
|
||||
@@ -71,25 +71,43 @@ func (dr *domainRenewal) renew() {
|
||||
testDidRenewLoop(next, err)
|
||||
}
|
||||
|
||||
// updateState locks and replaces the relevant Manager.state item with the given
|
||||
// state. It additionally updates dr.key with the given state's key.
|
||||
func (dr *domainRenewal) updateState(state *certState) {
|
||||
dr.m.stateMu.Lock()
|
||||
defer dr.m.stateMu.Unlock()
|
||||
dr.key = state.key
|
||||
dr.m.state[dr.ck] = state
|
||||
}
|
||||
|
||||
// do is similar to Manager.createCert but it doesn't lock a Manager.state item.
|
||||
// Instead, it requests a new certificate independently and, upon success,
|
||||
// replaces dr.m.state item with a new one and updates cache for the given domain.
|
||||
//
|
||||
// It may return immediately if the expiration date of the currently cached cert
|
||||
// is far enough in the future.
|
||||
// It may lock and update the Manager.state if the expiration date of the currently
|
||||
// cached cert is far enough in the future.
|
||||
//
|
||||
// The returned value is a time interval after which the renewal should occur again.
|
||||
func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
||||
// a race is likely unavoidable in a distributed environment
|
||||
// but we try nonetheless
|
||||
if tlscert, err := dr.m.cacheGet(ctx, dr.domain); err == nil {
|
||||
if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil {
|
||||
next := dr.next(tlscert.Leaf.NotAfter)
|
||||
if next > dr.m.renewBefore()+renewJitter {
|
||||
return next, nil
|
||||
signer, ok := tlscert.PrivateKey.(crypto.Signer)
|
||||
if ok {
|
||||
state := &certState{
|
||||
key: signer,
|
||||
cert: tlscert.Certificate,
|
||||
leaf: tlscert.Leaf,
|
||||
}
|
||||
dr.updateState(state)
|
||||
return next, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.domain)
|
||||
der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.ck)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -102,16 +120,15 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dr.m.cachePut(ctx, dr.domain, tlscert)
|
||||
dr.m.stateMu.Lock()
|
||||
defer dr.m.stateMu.Unlock()
|
||||
// m.state is guaranteed to be non-nil at this point
|
||||
dr.m.state[dr.domain] = state
|
||||
if err := dr.m.cachePut(ctx, dr.ck, tlscert); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dr.updateState(state)
|
||||
return dr.next(leaf.NotAfter), nil
|
||||
}
|
||||
|
||||
func (dr *domainRenewal) next(expiry time.Time) time.Duration {
|
||||
d := expiry.Sub(timeNow()) - dr.m.renewBefore()
|
||||
d := expiry.Sub(dr.m.now()) - dr.m.renewBefore()
|
||||
// add a bit of randomness to renew deadline
|
||||
n := pseudoRand.int63n(int64(renewJitter))
|
||||
d -= time.Duration(n)
|
||||
|
178
vendor/golang.org/x/crypto/acme/autocert/renewal_test.go
generated
vendored
178
vendor/golang.org/x/crypto/acme/autocert/renewal_test.go
generated
vendored
@@ -23,10 +23,10 @@ import (
|
||||
|
||||
func TestRenewalNext(t *testing.T) {
|
||||
now := time.Now()
|
||||
timeNow = func() time.Time { return now }
|
||||
defer func() { timeNow = time.Now }()
|
||||
|
||||
man := &Manager{RenewBefore: 7 * 24 * time.Hour}
|
||||
man := &Manager{
|
||||
RenewBefore: 7 * 24 * time.Hour,
|
||||
nowFunc: func() time.Time { return now },
|
||||
}
|
||||
defer man.stopRenew()
|
||||
tt := []struct {
|
||||
expiry time.Time
|
||||
@@ -48,8 +48,6 @@ func TestRenewalNext(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenewFromCache(t *testing.T) {
|
||||
const domain = "example.org"
|
||||
|
||||
// ACME CA server stub
|
||||
var ca *httptest.Server
|
||||
ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -84,7 +82,7 @@ func TestRenewFromCache(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("new-cert: CSR: %v", err)
|
||||
}
|
||||
der, err := dummyCert(csr.PublicKey, domain)
|
||||
der, err := dummyCert(csr.PublicKey, exampleDomain)
|
||||
if err != nil {
|
||||
t.Fatalf("new-cert: dummyCert: %v", err)
|
||||
}
|
||||
@@ -105,30 +103,28 @@ func TestRenewFromCache(t *testing.T) {
|
||||
}))
|
||||
defer ca.Close()
|
||||
|
||||
// use EC key to run faster on 386
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
man := &Manager{
|
||||
Prompt: AcceptTOS,
|
||||
Cache: newMemCache(),
|
||||
Cache: newMemCache(t),
|
||||
RenewBefore: 24 * time.Hour,
|
||||
Client: &acme.Client{
|
||||
Key: key,
|
||||
DirectoryURL: ca.URL,
|
||||
},
|
||||
}
|
||||
defer man.stopRenew()
|
||||
|
||||
// cache an almost expired cert
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Now()
|
||||
cert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), domain)
|
||||
cert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), exampleDomain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tlscert := &tls.Certificate{PrivateKey: key, Certificate: [][]byte{cert}}
|
||||
if err := man.cachePut(context.Background(), domain, tlscert); err != nil {
|
||||
if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -152,7 +148,7 @@ func TestRenewFromCache(t *testing.T) {
|
||||
|
||||
// ensure the new cert is cached
|
||||
after := time.Now().Add(future)
|
||||
tlscert, err := man.cacheGet(context.Background(), domain)
|
||||
tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
|
||||
if err != nil {
|
||||
t.Fatalf("man.cacheGet: %v", err)
|
||||
}
|
||||
@@ -163,9 +159,9 @@ func TestRenewFromCache(t *testing.T) {
|
||||
// verify the old cert is also replaced in memory
|
||||
man.stateMu.Lock()
|
||||
defer man.stateMu.Unlock()
|
||||
s := man.state[domain]
|
||||
s := man.state[exampleCertKey]
|
||||
if s == nil {
|
||||
t.Fatalf("m.state[%q] is nil", domain)
|
||||
t.Fatalf("m.state[%q] is nil", exampleCertKey)
|
||||
}
|
||||
tlscert, err = s.tlscert()
|
||||
if err != nil {
|
||||
@@ -177,7 +173,7 @@ func TestRenewFromCache(t *testing.T) {
|
||||
}
|
||||
|
||||
// trigger renew
|
||||
hello := &tls.ClientHelloInfo{ServerName: domain}
|
||||
hello := clientHelloInfo(exampleDomain, true)
|
||||
if _, err := man.GetCertificate(hello); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -189,3 +185,145 @@ func TestRenewFromCache(t *testing.T) {
|
||||
case <-done:
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
|
||||
man := &Manager{
|
||||
Prompt: AcceptTOS,
|
||||
Cache: newMemCache(t),
|
||||
RenewBefore: 24 * time.Hour,
|
||||
Client: &acme.Client{
|
||||
DirectoryURL: "invalid",
|
||||
},
|
||||
}
|
||||
defer man.stopRenew()
|
||||
|
||||
// cache a recently renewed cert with a different private key
|
||||
newKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Now()
|
||||
newCert, err := dateDummyCert(newKey.Public(), now.Add(-2*time.Hour), now.Add(time.Hour*24*90), exampleDomain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newLeaf, err := validCert(exampleCertKey, [][]byte{newCert}, newKey, now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newTLSCert := &tls.Certificate{PrivateKey: newKey, Certificate: [][]byte{newCert}, Leaf: newLeaf}
|
||||
if err := man.cachePut(context.Background(), exampleCertKey, newTLSCert); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// set internal state to an almost expired cert
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldCert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), exampleDomain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldLeaf, err := validCert(exampleCertKey, [][]byte{oldCert}, key, now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
man.stateMu.Lock()
|
||||
if man.state == nil {
|
||||
man.state = make(map[certKey]*certState)
|
||||
}
|
||||
s := &certState{
|
||||
key: key,
|
||||
cert: [][]byte{oldCert},
|
||||
leaf: oldLeaf,
|
||||
}
|
||||
man.state[exampleCertKey] = s
|
||||
man.stateMu.Unlock()
|
||||
|
||||
// veriy the renewal accepted the newer cached cert
|
||||
defer func() {
|
||||
testDidRenewLoop = func(next time.Duration, err error) {}
|
||||
}()
|
||||
done := make(chan struct{})
|
||||
testDidRenewLoop = func(next time.Duration, err error) {
|
||||
defer close(done)
|
||||
if err != nil {
|
||||
t.Errorf("testDidRenewLoop: %v", err)
|
||||
}
|
||||
// Next should be about 90 days
|
||||
// Previous expiration was within 1 min.
|
||||
future := 88 * 24 * time.Hour
|
||||
if next < future {
|
||||
t.Errorf("testDidRenewLoop: next = %v; want >= %v", next, future)
|
||||
}
|
||||
|
||||
// ensure the cached cert was not modified
|
||||
tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
|
||||
if err != nil {
|
||||
t.Fatalf("man.cacheGet: %v", err)
|
||||
}
|
||||
if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) {
|
||||
t.Errorf("cache leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
|
||||
}
|
||||
|
||||
// verify the old cert is also replaced in memory
|
||||
man.stateMu.Lock()
|
||||
defer man.stateMu.Unlock()
|
||||
s := man.state[exampleCertKey]
|
||||
if s == nil {
|
||||
t.Fatalf("m.state[%q] is nil", exampleCertKey)
|
||||
}
|
||||
stateKey := s.key.Public().(*ecdsa.PublicKey)
|
||||
if stateKey.X.Cmp(newKey.X) != 0 || stateKey.Y.Cmp(newKey.Y) != 0 {
|
||||
t.Fatalf("state key was not updated from cache x: %v y: %v; want x: %v y: %v", stateKey.X, stateKey.Y, newKey.X, newKey.Y)
|
||||
}
|
||||
tlscert, err = s.tlscert()
|
||||
if err != nil {
|
||||
t.Fatalf("s.tlscert: %v", err)
|
||||
}
|
||||
if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) {
|
||||
t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
|
||||
}
|
||||
|
||||
// verify the private key is replaced in the renewal state
|
||||
r := man.renewal[exampleCertKey]
|
||||
if r == nil {
|
||||
t.Fatalf("m.renewal[%q] is nil", exampleCertKey)
|
||||
}
|
||||
renewalKey := r.key.Public().(*ecdsa.PublicKey)
|
||||
if renewalKey.X.Cmp(newKey.X) != 0 || renewalKey.Y.Cmp(newKey.Y) != 0 {
|
||||
t.Fatalf("renewal private key was not updated from cache x: %v y: %v; want x: %v y: %v", renewalKey.X, renewalKey.Y, newKey.X, newKey.Y)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// assert the expiring cert is returned from state
|
||||
hello := clientHelloInfo(exampleDomain, true)
|
||||
tlscert, err := man.GetCertificate(hello)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !oldLeaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
|
||||
t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, oldLeaf.NotAfter)
|
||||
}
|
||||
|
||||
// trigger renew
|
||||
go man.renew(exampleCertKey, s.key, s.leaf.NotAfter)
|
||||
|
||||
// wait for renew loop
|
||||
select {
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("renew took too long to occur")
|
||||
case <-done:
|
||||
// assert the new cert is returned from state after renew
|
||||
hello := clientHelloInfo(exampleDomain, true)
|
||||
tlscert, err := man.GetCertificate(hello)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !newTLSCert.Leaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
|
||||
t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newTLSCert.Leaf.NotAfter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user