GetCertificate has all the information it needs to know if a client supports ECDSA in ClientHelloInfo. Deprecate and ignore ForceRSA, and just obtain a RSA certificate on the fly when a client that doesn't support ECDSA connects. This changes the cache key format to have a "+rsa" suffix for RSA certificates. The default (ForceRSA = false) cache key is unchanged, so most DirCache instances will still be valid. Caches created with ForceRSA set will be silently ignored and certificates reissued. The cache keys for HTTP tokens and the account key are changed to be guaranteed not to overlap with valid domain names as well. Note that ECDSA support detection is more strict in following RFC 5246 than crypto/tls, which ignores signature_algorithms. Fixes golang/go#22066 Change-Id: I70227747b563d6849cb693f83a950d57040b3f39 Reviewed-on: https://go-review.googlesource.com/114501 Reviewed-by: Adam Langley <agl@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
131 lines
3.3 KiB
Go
131 lines
3.3 KiB
Go
// Copyright 2016 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 autocert
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// ErrCacheMiss is returned when a certificate is not found in cache.
|
|
var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
|
|
|
|
// Cache is used by Manager to store and retrieve previously obtained certificates
|
|
// and other account data as opaque blobs.
|
|
//
|
|
// 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.
|
|
Get(ctx context.Context, key string) ([]byte, error)
|
|
|
|
// Put stores the data in the cache under the specified key.
|
|
// Underlying implementations may use any data storage format,
|
|
// as long as the reverse operation, Get, results in the original data.
|
|
Put(ctx context.Context, key string, data []byte) error
|
|
|
|
// Delete removes a certificate data from the cache under the specified key.
|
|
// If there's no such key in the cache, Delete returns nil.
|
|
Delete(ctx context.Context, key string) error
|
|
}
|
|
|
|
// DirCache implements Cache using a directory on the local filesystem.
|
|
// If the directory does not exist, it will be created with 0700 permissions.
|
|
type DirCache string
|
|
|
|
// Get reads a certificate data from the specified file name.
|
|
func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
|
|
name = filepath.Join(string(d), name)
|
|
var (
|
|
data []byte
|
|
err error
|
|
done = make(chan struct{})
|
|
)
|
|
go func() {
|
|
data, err = ioutil.ReadFile(name)
|
|
close(done)
|
|
}()
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
case <-done:
|
|
}
|
|
if os.IsNotExist(err) {
|
|
return nil, ErrCacheMiss
|
|
}
|
|
return data, err
|
|
}
|
|
|
|
// Put writes the certificate data to the specified file name.
|
|
// The file will be created with 0600 permissions.
|
|
func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
|
|
if err := os.MkdirAll(string(d), 0700); err != nil {
|
|
return err
|
|
}
|
|
|
|
done := make(chan struct{})
|
|
var err error
|
|
go func() {
|
|
defer close(done)
|
|
var tmp string
|
|
if tmp, err = d.writeTempFile(name, data); err != nil {
|
|
return
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
// Don't overwrite the file if the context was canceled.
|
|
default:
|
|
newName := filepath.Join(string(d), name)
|
|
err = os.Rename(tmp, newName)
|
|
}
|
|
}()
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-done:
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Delete removes the specified file name.
|
|
func (d DirCache) Delete(ctx context.Context, name string) error {
|
|
name = filepath.Join(string(d), name)
|
|
var (
|
|
err error
|
|
done = make(chan struct{})
|
|
)
|
|
go func() {
|
|
err = os.Remove(name)
|
|
close(done)
|
|
}()
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-done:
|
|
}
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// writeTempFile writes b to a temporary file, closes the file and returns its path.
|
|
func (d DirCache) writeTempFile(prefix string, b []byte) (string, error) {
|
|
// TempFile uses 0600 permissions
|
|
f, err := ioutil.TempFile(string(d), prefix)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if _, err := f.Write(b); err != nil {
|
|
f.Close()
|
|
return "", err
|
|
}
|
|
return f.Name(), f.Close()
|
|
}
|