mirror of
https://github.com/miniflux/v2.git
synced 2025-08-01 17:38:37 +00:00
Update vendor dependencies
This commit is contained in:
parent
34a3fe426b
commit
459bb4531f
747 changed files with 89857 additions and 39711 deletions
289
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
289
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
|
@ -27,7 +27,6 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -99,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
|
||||
|
@ -128,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
|
||||
|
@ -141,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
|
||||
|
@ -175,6 +184,23 @@ type Manager struct {
|
|||
certTokens map[string]*tls.Certificate
|
||||
}
|
||||
|
||||
// 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-sni 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -195,7 +221,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")
|
||||
}
|
||||
|
||||
|
@ -211,7 +237,7 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
|||
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?
|
||||
|
@ -219,8 +245,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
|
||||
}
|
||||
|
@ -232,14 +261,60 @@ 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
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -305,16 +380,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
|
||||
}
|
||||
|
@ -323,25 +398,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
|
||||
}
|
||||
|
@ -372,7 +447,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)
|
||||
if err != nil {
|
||||
return nil, ErrCacheMiss
|
||||
}
|
||||
|
@ -384,7 +459,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
|
||||
}
|
||||
|
@ -416,7 +491,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 {
|
||||
|
@ -433,9 +508,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
|
||||
}
|
||||
|
@ -453,44 +528,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); 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
|
||||
}
|
||||
|
||||
|
@ -499,7 +574,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)
|
||||
|
@ -513,22 +588,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
|
||||
}
|
||||
|
@ -536,13 +611,25 @@ 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)
|
||||
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 {
|
||||
|
@ -555,6 +642,24 @@ func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string
|
|||
}
|
||||
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.
|
||||
|
@ -571,6 +676,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) {
|
||||
|
@ -578,21 +685,30 @@ 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)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -635,8 +751,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()
|
||||
|
@ -644,17 +760,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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -705,7 +822,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.
|
||||
|
@ -716,18 +833,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)
|
||||
}
|
||||
|
||||
|
@ -743,7 +860,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)
|
||||
|
@ -754,6 +874,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 {
|
||||
|
@ -850,12 +973,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)
|
||||
}
|
||||
|
@ -886,12 +1009,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) (leaf *x509.Certificate, err error) {
|
||||
// parse public part(s)
|
||||
var n int
|
||||
for _, b := range der {
|
||||
|
@ -903,7 +1026,7 @@ 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
|
||||
|
@ -915,10 +1038,10 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi
|
|||
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)
|
||||
|
@ -928,6 +1051,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 {
|
||||
|
@ -936,22 +1062,15 @@ 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")
|
||||
}
|
||||
return leaf, nil
|
||||
}
|
||||
|
||||
func retryAfter(v string) time.Duration {
|
||||
if i, err := strconv.Atoi(v); err == nil {
|
||||
return time.Duration(i) * time.Second
|
||||
}
|
||||
if t, err := http.ParseTime(v); err == nil {
|
||||
return t.Sub(timeNow())
|
||||
}
|
||||
return time.Second
|
||||
}
|
||||
|
||||
type lockedMathRand struct {
|
||||
sync.Mutex
|
||||
rnd *mathrand.Rand
|
||||
|
@ -969,5 +1088,5 @@ var (
|
|||
timeNow = time.Now
|
||||
|
||||
// Called when a state is removed.
|
||||
testDidRemoveState = func(domain string) {}
|
||||
testDidRemoveState = func(certKey) {}
|
||||
)
|
||||
|
|
563
vendor/golang.org/x/crypto/acme/autocert/autocert_test.go
generated
vendored
563
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,6 +15,7 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -31,6 +33,12 @@ import (
|
|||
"golang.org/x/crypto/acme"
|
||||
)
|
||||
|
||||
var (
|
||||
exampleDomain = "example.org"
|
||||
exampleCertKey = certKey{domain: exampleDomain}
|
||||
exampleCertKeyRSA = certKey{domain: exampleDomain, isRSA: true}
|
||||
)
|
||||
|
||||
var discoTmpl = template.Must(template.New("disco").Parse(`{
|
||||
"new-reg": "{{.}}/new-reg",
|
||||
"new-authz": "{{.}}/new-authz",
|
||||
|
@ -64,6 +72,7 @@ var authzTmpl = template.Must(template.New("authz").Parse(`{
|
|||
}`))
|
||||
|
||||
type memCache struct {
|
||||
t *testing.T
|
||||
mu sync.Mutex
|
||||
keyData map[string][]byte
|
||||
}
|
||||
|
@ -79,7 +88,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 +123,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 +182,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 +247,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 +259,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 +277,6 @@ func TestGetCertificate_failedAttempt(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
const example = "example.org"
|
||||
d := createCertRetryAfter
|
||||
f := testDidRemoveState
|
||||
defer func() {
|
||||
|
@ -236,51 +285,167 @@ 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()) {
|
||||
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 +527,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 +551,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,7 +601,7 @@ 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)
|
||||
}
|
||||
}
|
||||
|
@ -505,18 +661,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 +685,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: Merge this with startACMEServerStub, making it a configurable CA for testing.
|
||||
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 +832,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 +848,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 +962,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)
|
||||
if err != nil && test.ok {
|
||||
t.Errorf("%d: err = %v", i, err)
|
||||
}
|
||||
|
@ -748,10 +1032,99 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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.
|
||||
|
|
43
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
43
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,11 +120,10 @@ 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
|
||||
}
|
||||
|
||||
|
|
170
vendor/golang.org/x/crypto/acme/autocert/renewal_test.go
generated
vendored
170
vendor/golang.org/x/crypto/acme/autocert/renewal_test.go
generated
vendored
|
@ -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)
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue