Merge github.com:letsencrypt/boulder into longserial
This commit is contained in:
commit
668ccc2d97
|
|
@ -28,10 +28,16 @@ services:
|
|||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
# Only build pushes to the master branch (and PRs)
|
||||
# Only build pushes to the master branch, PRs, and branches beginning with
|
||||
# `test-`. You should not push branches beginning with `test-` to the
|
||||
# letsencrypt repository, but this is a convenient way to push branches to your
|
||||
# own fork of the repostiroy to ensure Travis passes before submitting a PR.
|
||||
# For instance, you might run:
|
||||
# git push myremote branchname:test-branchname
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^test-.*$/
|
||||
|
||||
before_install:
|
||||
# Travis does shallow clones, so there is no master branch present.
|
||||
|
|
|
|||
|
|
@ -12,51 +12,63 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/auth",
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
"Comment": "1.1.0-56-gfed4546",
|
||||
"Rev": "fed4546bc16a51b87ea8b18df7c26090c4c5b900"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/config",
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
"Comment": "1.1.0-56-gfed4546",
|
||||
"Rev": "fed4546bc16a51b87ea8b18df7c26090c4c5b900"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs11key",
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
"Comment": "1.1.0-56-gfed4546",
|
||||
"Rev": "fed4546bc16a51b87ea8b18df7c26090c4c5b900"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs12",
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
"Comment": "1.1.0-56-gfed4546",
|
||||
"Rev": "fed4546bc16a51b87ea8b18df7c26090c4c5b900"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs7",
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
"Comment": "1.1.0-56-gfed4546",
|
||||
"Rev": "fed4546bc16a51b87ea8b18df7c26090c4c5b900"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/csr",
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
"Comment": "1.1.0-56-gfed4546",
|
||||
"Rev": "fed4546bc16a51b87ea8b18df7c26090c4c5b900"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/errors",
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
"Comment": "1.1.0-56-gfed4546",
|
||||
"Rev": "fed4546bc16a51b87ea8b18df7c26090c4c5b900"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/helpers",
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
"Comment": "1.1.0-56-gfed4546",
|
||||
"Rev": "fed4546bc16a51b87ea8b18df7c26090c4c5b900"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/info",
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
"Comment": "1.1.0-56-gfed4546",
|
||||
"Rev": "fed4546bc16a51b87ea8b18df7c26090c4c5b900"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/log",
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
"Comment": "1.1.0-56-gfed4546",
|
||||
"Rev": "fed4546bc16a51b87ea8b18df7c26090c4c5b900"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/ocsp",
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
"Comment": "1.1.0-56-gfed4546",
|
||||
"Rev": "fed4546bc16a51b87ea8b18df7c26090c4c5b900"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/signer",
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
"Comment": "1.1.0-56-gfed4546",
|
||||
"Rev": "fed4546bc16a51b87ea8b18df7c26090c4c5b900"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codegangsta/cli",
|
||||
|
|
@ -67,6 +79,18 @@
|
|||
"ImportPath": "github.com/dgryski/go-rc2",
|
||||
"Rev": "fd90a5fcd260ebe709a689d0bdca2043afffabfa"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/facebookgo/clock",
|
||||
"Rev": "600d898af40aa09a7a93ecb9265d87b0504b6f03"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/facebookgo/httpdown",
|
||||
"Rev": "1fa03998d20119dfe4ef73f56a638d83048052e2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/facebookgo/stats",
|
||||
"Rev": "31fb71caf5a4f04c9f8bb3fa8e7c2597ba6eb50a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||
"Comment": "v1.2-88-ga197e5d",
|
||||
|
|
|
|||
10
Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key/config.go
generated
vendored
Normal file
10
Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key/config.go
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package pkcs11key
|
||||
|
||||
// Config contains configuration information required to use a PKCS
|
||||
// #11 key.
|
||||
type Config struct {
|
||||
Module string
|
||||
TokenLabel string
|
||||
PIN string
|
||||
PrivateKeyLabel string
|
||||
}
|
||||
353
Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key/key.go
generated
vendored
Normal file
353
Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key/key.go
generated
vendored
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
// +build !nopkcs11
|
||||
|
||||
// Package pkcs11key implements crypto.Signer for PKCS #11 private
|
||||
// keys. Currently, only RSA keys are supported.
|
||||
// See ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-11/v2-30/pkcs-11v2-30b-d6.pdf for
|
||||
// details of the Cryptoki PKCS#11 API.
|
||||
package pkcs11key
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/pkcs11"
|
||||
)
|
||||
|
||||
// from src/pkg/crypto/rsa/pkcs1v15.go
|
||||
var hashPrefixes = map[crypto.Hash][]byte{
|
||||
crypto.MD5: {0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10},
|
||||
crypto.SHA1: {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14},
|
||||
crypto.SHA224: {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c},
|
||||
crypto.SHA256: {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20},
|
||||
crypto.SHA384: {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30},
|
||||
crypto.SHA512: {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40},
|
||||
crypto.MD5SHA1: {}, // A special TLS case which doesn't use an ASN1 prefix.
|
||||
crypto.RIPEMD160: {0x30, 0x20, 0x30, 0x08, 0x06, 0x06, 0x28, 0xcf, 0x06, 0x03, 0x00, 0x31, 0x04, 0x14},
|
||||
}
|
||||
|
||||
// Key is an implementation of the crypto.Signer interface using a key stored
|
||||
// in a PKCS#11 hardware token. This enables the use of PKCS#11 tokens with
|
||||
// the Go x509 library's methods for signing certificates.
|
||||
//
|
||||
// Each Key represents one session. Its session handle is protected internally
|
||||
// by a mutex, so at most one Sign operation can be active at a time. For best
|
||||
// performance you may want to instantiate multiple Keys using pkcs11key.Pool.
|
||||
// Each one will have its own session and can be used concurrently. Note that
|
||||
// some smartcards like the Yubikey Neo do not support multiple simultaneous
|
||||
// sessions and will error out on creation of the second Key object.
|
||||
//
|
||||
// Note: If you instantiate multiple Keys without using Pool, it is *highly*
|
||||
// recommended that you create all your Key objects serially, on your main
|
||||
// thread, checking for errors each time, and then farm them out for use by
|
||||
// different goroutines. If you fail to do this, your application may attempt
|
||||
// to login repeatedly with an incorrect PIN, locking the PKCS#11 token.
|
||||
type Key struct {
|
||||
// The PKCS#11 library to use
|
||||
module *pkcs11.Ctx
|
||||
|
||||
// The path to the PKCS#11 library
|
||||
modulePath string
|
||||
|
||||
// The label of the token to be used (mandatory).
|
||||
// We will automatically search for this in the slot list.
|
||||
tokenLabel string
|
||||
|
||||
// The PIN to be used to log in to the device
|
||||
pin string
|
||||
|
||||
// The public key corresponding to the private key.
|
||||
publicKey rsa.PublicKey
|
||||
|
||||
// The an ObjectHandle pointing to the private key on the HSM.
|
||||
privateKeyHandle pkcs11.ObjectHandle
|
||||
|
||||
// A handle to the session used by this Key.
|
||||
session *pkcs11.SessionHandle
|
||||
sessionMu sync.Mutex
|
||||
|
||||
// True if the private key has the CKA_ALWAYS_AUTHENTICATE attribute set.
|
||||
alwaysAuthenticate bool
|
||||
}
|
||||
|
||||
var modules = make(map[string]*pkcs11.Ctx)
|
||||
var modulesMu sync.Mutex
|
||||
|
||||
// initialize loads the given PKCS#11 module (shared library) if it is not
|
||||
// already loaded. It's an error to load a PKCS#11 module multiple times, so we
|
||||
// maintain a map of loaded modules. Note that there is no facility yet to
|
||||
// unload a module ("finalize" in PKCS#11 parlance). In general, modules will
|
||||
// be unloaded at the end of the process. The only place where you are likely
|
||||
// to need to explicitly unload a module is if you fork your process after a
|
||||
// Key has already been created, and the child process also needs to use
|
||||
// that module.
|
||||
func initialize(modulePath string) (*pkcs11.Ctx, error) {
|
||||
modulesMu.Lock()
|
||||
defer modulesMu.Unlock()
|
||||
module, ok := modules[modulePath]
|
||||
if ok {
|
||||
return module, nil
|
||||
}
|
||||
|
||||
module = pkcs11.New(modulePath)
|
||||
|
||||
if module == nil {
|
||||
return nil, fmt.Errorf("unable to load PKCS#11 module")
|
||||
}
|
||||
|
||||
err := module.Initialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modules[modulePath] = module
|
||||
|
||||
return module, nil
|
||||
}
|
||||
|
||||
// New instantiates a new handle to a PKCS #11-backed key.
|
||||
func New(modulePath, tokenLabel, pin, privateKeyLabel string) (ps *Key, err error) {
|
||||
module, err := initialize(modulePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if module == nil {
|
||||
err = fmt.Errorf("nil module")
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize a partial key
|
||||
ps = &Key{
|
||||
module: module,
|
||||
modulePath: modulePath,
|
||||
tokenLabel: tokenLabel,
|
||||
pin: pin,
|
||||
}
|
||||
|
||||
// Open a session
|
||||
ps.sessionMu.Lock()
|
||||
defer ps.sessionMu.Unlock()
|
||||
session, err := ps.openSession()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ps.session = &session
|
||||
|
||||
// Fetch the private key by its label
|
||||
privateKeyHandle, err := ps.getPrivateKey(module, session, privateKeyLabel)
|
||||
if err != nil {
|
||||
ps.module.CloseSession(session)
|
||||
return
|
||||
}
|
||||
ps.privateKeyHandle = privateKeyHandle
|
||||
|
||||
publicKey, err := getPublicKey(module, session, privateKeyHandle)
|
||||
if err != nil {
|
||||
ps.module.CloseSession(session)
|
||||
return
|
||||
}
|
||||
ps.publicKey = publicKey
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ps *Key) getPrivateKey(module *pkcs11.Ctx, session pkcs11.SessionHandle, label string) (pkcs11.ObjectHandle, error) {
|
||||
var noHandle pkcs11.ObjectHandle
|
||||
template := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
|
||||
}
|
||||
if err := module.FindObjectsInit(session, template); err != nil {
|
||||
return noHandle, err
|
||||
}
|
||||
objs, _, err := module.FindObjects(session, 2)
|
||||
if err != nil {
|
||||
return noHandle, err
|
||||
}
|
||||
if err = module.FindObjectsFinal(session); err != nil {
|
||||
return noHandle, err
|
||||
}
|
||||
|
||||
if len(objs) == 0 {
|
||||
return noHandle, fmt.Errorf("private key not found")
|
||||
}
|
||||
privateKeyHandle := objs[0]
|
||||
|
||||
// Check whether the key has the CKA_ALWAYS_AUTHENTICATE attribute.
|
||||
// If so, fail: we don't want to have to re-authenticate for each sign
|
||||
// operation.
|
||||
attributes, err := module.GetAttributeValue(session, privateKeyHandle, []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ALWAYS_AUTHENTICATE, false),
|
||||
})
|
||||
if err != nil {
|
||||
return noHandle, err
|
||||
}
|
||||
for _, attribute := range attributes {
|
||||
if len(attribute.Value) > 0 && attribute.Value[0] == 1 {
|
||||
ps.alwaysAuthenticate = true
|
||||
}
|
||||
}
|
||||
|
||||
return privateKeyHandle, nil
|
||||
}
|
||||
|
||||
// Get the public key matching a private key
|
||||
// TODO: Add support for non-RSA keys, switching on CKA_KEY_TYPE
|
||||
func getPublicKey(module *pkcs11.Ctx, session pkcs11.SessionHandle, privateKeyHandle pkcs11.ObjectHandle) (rsa.PublicKey, error) {
|
||||
var noKey rsa.PublicKey
|
||||
template := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_MODULUS, nil),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, nil),
|
||||
}
|
||||
attr, err := module.GetAttributeValue(session, privateKeyHandle, template)
|
||||
if err != nil {
|
||||
return noKey, err
|
||||
}
|
||||
|
||||
n := big.NewInt(0)
|
||||
e := int(0)
|
||||
gotModulus, gotExponent := false, false
|
||||
for _, a := range attr {
|
||||
if a.Type == pkcs11.CKA_MODULUS {
|
||||
n.SetBytes(a.Value)
|
||||
gotModulus = true
|
||||
} else if a.Type == pkcs11.CKA_PUBLIC_EXPONENT {
|
||||
bigE := big.NewInt(0)
|
||||
bigE.SetBytes(a.Value)
|
||||
e = int(bigE.Int64())
|
||||
gotExponent = true
|
||||
}
|
||||
}
|
||||
if !gotModulus || !gotExponent {
|
||||
return noKey, errors.New("public key missing either modulus or exponent")
|
||||
}
|
||||
return rsa.PublicKey{
|
||||
N: n,
|
||||
E: e,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Destroy tears down a Key by closing the session. It should be
|
||||
// called before the key gets GC'ed, to avoid leaving dangling sessions.
|
||||
func (ps *Key) Destroy() error {
|
||||
if ps.session != nil {
|
||||
// NOTE: We do not want to call module.Logout here. module.Logout applies
|
||||
// application-wide. So if there are multiple sessions active, the other ones
|
||||
// would be logged out as well, causing CKR_OBJECT_HANDLE_INVALID next
|
||||
// time they try to sign something. It's also unnecessary to log out explicitly:
|
||||
// module.CloseSession will log out once the last session in the application is
|
||||
// closed.
|
||||
ps.sessionMu.Lock()
|
||||
defer ps.sessionMu.Unlock()
|
||||
err := ps.module.CloseSession(*ps.session)
|
||||
ps.session = nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *Key) openSession() (pkcs11.SessionHandle, error) {
|
||||
var noSession pkcs11.SessionHandle
|
||||
slots, err := ps.module.GetSlotList(true)
|
||||
if err != nil {
|
||||
return noSession, err
|
||||
}
|
||||
|
||||
for _, slot := range slots {
|
||||
// Check that token label matches.
|
||||
tokenInfo, err := ps.module.GetTokenInfo(slot)
|
||||
if err != nil {
|
||||
return noSession, err
|
||||
}
|
||||
if tokenInfo.Label != ps.tokenLabel {
|
||||
continue
|
||||
}
|
||||
|
||||
// Open session
|
||||
session, err := ps.module.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION)
|
||||
if err != nil {
|
||||
return session, err
|
||||
}
|
||||
|
||||
// Login
|
||||
// Note: Logged-in status is application-wide, not per session. But in
|
||||
// practice it appears to be okay to login to a token multiple times with the same
|
||||
// credentials.
|
||||
if err = ps.module.Login(session, pkcs11.CKU_USER, ps.pin); err != nil {
|
||||
ps.module.CloseSession(session)
|
||||
return session, err
|
||||
}
|
||||
|
||||
return session, err
|
||||
}
|
||||
return noSession, fmt.Errorf("No slot found matching token label '%s'", ps.tokenLabel)
|
||||
}
|
||||
|
||||
// Public returns the public key for the PKCS #11 key.
|
||||
func (ps *Key) Public() crypto.PublicKey {
|
||||
return &ps.publicKey
|
||||
}
|
||||
|
||||
// Sign performs a signature using the PKCS #11 key.
|
||||
func (ps *Key) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
ps.sessionMu.Lock()
|
||||
defer ps.sessionMu.Unlock()
|
||||
if ps.session == nil {
|
||||
return nil, errors.New("Session was nil")
|
||||
}
|
||||
|
||||
// When the alwaysAuthenticate bit is true (e.g. on a Yubikey NEO in PIV mode),
|
||||
// each Sign has to include a Logout/Login, or the next Sign request will get
|
||||
// CKR_USER_NOT_LOGGED_IN. This is very slow, but on the NEO it's not possible
|
||||
// to clear the CKA_ALWAYS_AUTHENTICATE bit, so this is the only available
|
||||
// workaround.
|
||||
// Also, since logged in / logged out is application state rather than session
|
||||
// state, we take a global lock while we do the logout and login, and during
|
||||
// the signing.
|
||||
if ps.alwaysAuthenticate {
|
||||
modulesMu.Lock()
|
||||
defer modulesMu.Unlock()
|
||||
if err := ps.module.Logout(*ps.session); err != nil {
|
||||
return nil, fmt.Errorf("logout: %s", err)
|
||||
}
|
||||
if err = ps.module.Login(*ps.session, pkcs11.CKU_USER, ps.pin); err != nil {
|
||||
return nil, fmt.Errorf("login: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the length of the hash is as expected
|
||||
hash := opts.HashFunc()
|
||||
hashLen := hash.Size()
|
||||
if len(msg) != hashLen {
|
||||
err = errors.New("input size does not match hash function output size")
|
||||
return
|
||||
}
|
||||
|
||||
// Add DigestInfo prefix
|
||||
// TODO: Switch mechanisms based on CKA_KEY_TYPE
|
||||
mechanism := []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS, nil)}
|
||||
prefix, ok := hashPrefixes[hash]
|
||||
if !ok {
|
||||
err = errors.New("unknown hash function")
|
||||
return
|
||||
}
|
||||
signatureInput := append(prefix, msg...)
|
||||
|
||||
// Perform the sign operation
|
||||
err = ps.module.SignInit(*ps.session, mechanism, ps.privateKeyHandle)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign init: %s", err)
|
||||
}
|
||||
|
||||
signature, err = ps.module.Sign(*ps.session, signatureInput)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
247
Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key/pkcs11key.go
generated
vendored
247
Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key/pkcs11key.go
generated
vendored
|
|
@ -1,247 +0,0 @@
|
|||
// +build !nopkcs11
|
||||
|
||||
// Package pkcs11key implements crypto.Signer for PKCS #11 private
|
||||
// keys. Currently, only RSA keys are support.
|
||||
package pkcs11key
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/pkcs11"
|
||||
)
|
||||
|
||||
// from src/pkg/crypto/rsa/pkcs1v15.go
|
||||
var hashPrefixes = map[crypto.Hash][]byte{
|
||||
crypto.MD5: {0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10},
|
||||
crypto.SHA1: {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14},
|
||||
crypto.SHA224: {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c},
|
||||
crypto.SHA256: {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20},
|
||||
crypto.SHA384: {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30},
|
||||
crypto.SHA512: {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40},
|
||||
crypto.MD5SHA1: {}, // A special TLS case which doesn't use an ASN1 prefix.
|
||||
crypto.RIPEMD160: {0x30, 0x20, 0x30, 0x08, 0x06, 0x06, 0x28, 0xcf, 0x06, 0x03, 0x00, 0x31, 0x04, 0x14},
|
||||
}
|
||||
|
||||
// PKCS11Key is an implementation of the crypto.Signer interface
|
||||
// using a key stored in a PKCS#11 hardware token. This enables
|
||||
// the use of PKCS#11 tokens with the Go x509 library's methods
|
||||
// for signing certificates.
|
||||
type PKCS11Key struct {
|
||||
// The PKCS#11 library to use
|
||||
module *pkcs11.Ctx
|
||||
|
||||
// The slot id of the token to be used. Both this and tokenLabel must match to
|
||||
// be used.
|
||||
slotID int
|
||||
|
||||
// The label of the token to be used.
|
||||
// We will automatically search for this in the slot list.
|
||||
tokenLabel string
|
||||
|
||||
// The PIN to be used to log in to the device
|
||||
pin string
|
||||
|
||||
// The public key corresponding to the private key.
|
||||
publicKey rsa.PublicKey
|
||||
|
||||
// The an ObjectHandle pointing to the private key on the HSM.
|
||||
privateKeyHandle pkcs11.ObjectHandle
|
||||
}
|
||||
|
||||
// New instantiates a new handle to a PKCS #11-backed key.
|
||||
func New(module, tokenLabel, pin, privLabel string, slotID int) (ps *PKCS11Key, err error) {
|
||||
// Set up a new pkcs11 object and initialize it
|
||||
p := pkcs11.New(module)
|
||||
if p == nil {
|
||||
err = errors.New("unable to load PKCS#11 module")
|
||||
return
|
||||
}
|
||||
|
||||
if err = p.Initialize(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize a partial key
|
||||
ps = &PKCS11Key{
|
||||
module: p,
|
||||
slotID: slotID,
|
||||
tokenLabel: tokenLabel,
|
||||
pin: pin,
|
||||
}
|
||||
|
||||
// Look up the private key
|
||||
session, err := ps.openSession()
|
||||
if err != nil {
|
||||
ps.Destroy()
|
||||
return
|
||||
}
|
||||
defer ps.closeSession(session)
|
||||
|
||||
template := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_LABEL, privLabel),
|
||||
}
|
||||
if err = p.FindObjectsInit(session, template); err != nil {
|
||||
ps.Destroy()
|
||||
return
|
||||
}
|
||||
objs, _, err := p.FindObjects(session, 2)
|
||||
if err != nil {
|
||||
ps.Destroy()
|
||||
return
|
||||
}
|
||||
if err = p.FindObjectsFinal(session); err != nil {
|
||||
ps.Destroy()
|
||||
return
|
||||
}
|
||||
|
||||
if len(objs) == 0 {
|
||||
err = errors.New("private key not found")
|
||||
ps.Destroy()
|
||||
return
|
||||
}
|
||||
ps.privateKeyHandle = objs[0]
|
||||
|
||||
// Populate the pubic key from the private key
|
||||
// TODO: Add support for non-RSA keys, switching on CKA_KEY_TYPE
|
||||
template = []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_MODULUS, nil),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, nil),
|
||||
}
|
||||
attr, err := p.GetAttributeValue(session, ps.privateKeyHandle, template)
|
||||
if err != nil {
|
||||
ps.Destroy()
|
||||
return
|
||||
}
|
||||
|
||||
n := big.NewInt(0)
|
||||
e := int(0)
|
||||
gotModulus, gotExponent := false, false
|
||||
for _, a := range attr {
|
||||
if a.Type == pkcs11.CKA_MODULUS {
|
||||
n.SetBytes(a.Value)
|
||||
gotModulus = true
|
||||
} else if a.Type == pkcs11.CKA_PUBLIC_EXPONENT {
|
||||
bigE := big.NewInt(0)
|
||||
bigE.SetBytes(a.Value)
|
||||
e = int(bigE.Int64())
|
||||
gotExponent = true
|
||||
}
|
||||
}
|
||||
if !gotModulus || !gotExponent {
|
||||
ps.Destroy()
|
||||
return
|
||||
}
|
||||
ps.publicKey = rsa.PublicKey{
|
||||
N: n,
|
||||
E: e,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Destroy tears down a PKCS11Key.
|
||||
//
|
||||
// This method must be called before the PKCS11Key is GC'ed, in order
|
||||
// to ensure that the PKCS#11 module itself is properly finalized and
|
||||
// destroyed.
|
||||
//
|
||||
// The idiomatic way to do this (assuming no need for a long-lived
|
||||
// signer) is as follows:
|
||||
//
|
||||
// ps, err := NewPKCS11Signer(...)
|
||||
// if err != nil { ... }
|
||||
// defer ps.Destroy()
|
||||
func (ps *PKCS11Key) Destroy() {
|
||||
if ps.module != nil {
|
||||
ps.module.Finalize()
|
||||
ps.module.Destroy()
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *PKCS11Key) openSession() (session pkcs11.SessionHandle, err error) {
|
||||
// Check if there is a PCKS11 token with slots. It has side-effects that
|
||||
// allow the rest of the code here to work.
|
||||
_, err = ps.module.GetSlotList(true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
slotID := uint(ps.slotID)
|
||||
// Look up slot by id
|
||||
tokenInfo, err := ps.module.GetTokenInfo(slotID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check that token label matches.
|
||||
if tokenInfo.Label != ps.tokenLabel {
|
||||
err = fmt.Errorf("Token label in slot %d was '%s', expected '%s'",
|
||||
slotID, tokenInfo.Label, ps.tokenLabel)
|
||||
return
|
||||
}
|
||||
// Open session
|
||||
session, err = ps.module.OpenSession(slotID, pkcs11.CKF_SERIAL_SESSION)
|
||||
if err != nil {
|
||||
return session, err
|
||||
}
|
||||
|
||||
// Login
|
||||
if err = ps.module.Login(session, pkcs11.CKU_USER, ps.pin); err != nil {
|
||||
return session, err
|
||||
}
|
||||
|
||||
return session, err
|
||||
}
|
||||
|
||||
func (ps *PKCS11Key) closeSession(session pkcs11.SessionHandle) {
|
||||
ps.module.Logout(session)
|
||||
ps.module.CloseSession(session)
|
||||
}
|
||||
|
||||
// Public returns the public key for the PKCS #11 key.
|
||||
func (ps *PKCS11Key) Public() crypto.PublicKey {
|
||||
return &ps.publicKey
|
||||
}
|
||||
|
||||
// Sign performs a signature using the PKCS #11 key.
|
||||
func (ps *PKCS11Key) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
// Verify that the length of the hash is as expected
|
||||
hash := opts.HashFunc()
|
||||
hashLen := hash.Size()
|
||||
if len(msg) != hashLen {
|
||||
err = errors.New("input size does not match hash function output size")
|
||||
return
|
||||
}
|
||||
|
||||
// Add DigestInfo prefix
|
||||
// TODO: Switch mechanisms based on CKA_KEY_TYPE
|
||||
mechanism := []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS, nil)}
|
||||
prefix, ok := hashPrefixes[hash]
|
||||
if !ok {
|
||||
err = errors.New("unknown hash function")
|
||||
return
|
||||
}
|
||||
signatureInput := append(prefix, msg...)
|
||||
|
||||
// Open a session
|
||||
session, err := ps.openSession()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer ps.closeSession(session)
|
||||
|
||||
// Perform the sign operation
|
||||
err = ps.module.SignInit(session, mechanism, ps.privateKeyHandle)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
signature, err = ps.module.Sign(session, signatureInput)
|
||||
return
|
||||
}
|
||||
88
Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key/pkcs11key_bench_test.go
generated
vendored
Normal file
88
Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key/pkcs11key_bench_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package pkcs11key
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var module = flag.String("module", "", "Path to PKCS11 module")
|
||||
var tokenLabel = flag.String("tokenLabel", "", "Token label")
|
||||
var pin = flag.String("pin", "", "PIN")
|
||||
var privateKeyLabel = flag.String("privateKeyLabel", "", "Private key label")
|
||||
var sessionCount = flag.Int("sessions", runtime.GOMAXPROCS(-1), `Number of PKCS#11 sessions to use.
|
||||
For SoftHSM, GOMAXPROCS is appropriate, but for an external HSM the optimum session count depends on the HSM's parallelism.`)
|
||||
|
||||
// BenchmarkPKCS11 signs a certificate repeatedly using a PKCS11 token and
|
||||
// measures speed. To run (with SoftHSM):
|
||||
// go test -bench=. -benchtime 5s ./crypto/pkcs11key/ \
|
||||
// -module /usr/lib/softhsm/libsofthsm.so -token-label "softhsm token" \
|
||||
// -pin 1234 -private-key-label "my key" -cpu 4
|
||||
// You can adjust benchtime if you want to run for longer or shorter, and change
|
||||
// the number of CPUs to select the parallelism you want.
|
||||
func BenchmarkPKCS11(b *testing.B) {
|
||||
if *module == "" || *tokenLabel == "" || *pin == "" || *privateKeyLabel == "" {
|
||||
b.Fatal("Must pass all flags: module, tokenLabel, pin, and privateKeyLabel")
|
||||
return
|
||||
}
|
||||
|
||||
// A minimal, bogus certificate to be signed.
|
||||
// Note: we choose a large N to make up for some of the missing fields in the
|
||||
// bogus certificate, so we wind up something approximately the size of a real
|
||||
// certificate.
|
||||
N := big.NewInt(1)
|
||||
N.Lsh(N, 6000)
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
PublicKeyAlgorithm: x509.RSA,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now(),
|
||||
|
||||
PublicKey: &rsa.PublicKey{
|
||||
N: N,
|
||||
E: 1 << 17,
|
||||
},
|
||||
}
|
||||
|
||||
pool, err := NewPool(*sessionCount, *module, *tokenLabel, *pin, *privateKeyLabel)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer pool.Destroy()
|
||||
|
||||
instance := pool.get()
|
||||
if instance.alwaysAuthenticate {
|
||||
b.Log("WARNING: Token has CKA_ALWAYS_AUTHENTICATE attribute, which makes signing slow.")
|
||||
}
|
||||
pool.put(instance)
|
||||
|
||||
// Reset the benchmarking timer so we don't include setup time.
|
||||
b.ResetTimer()
|
||||
|
||||
// Start recording total time. Go's benchmarking code is interested in
|
||||
// nanoseconds per op, but we're also interested in the total throughput.
|
||||
start := time.Now()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, err = x509.CreateCertificate(rand.Reader, &template, &template, template.PublicKey, pool)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
elapsedTime := time.Now().Sub(start)
|
||||
b.Logf("Time, count, ops / second: %s, %d, %g", elapsedTime, b.N, float64(b.N)*float64(time.Second)/float64(elapsedTime))
|
||||
}
|
||||
|
||||
// Dummy test to avoid getting "warning: no tests found"
|
||||
func TestNothing(t *testing.T) {
|
||||
}
|
||||
100
Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key/pool.go
generated
vendored
Normal file
100
Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key/pool.go
generated
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package pkcs11key
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Pool is a pool of Keys suitable for high performance parallel work. Key
|
||||
// on its own is suitable for multi-threaded use because it has built-in
|
||||
// locking, but one Key can have at most one operation inflight at a time.
|
||||
// If you are using an HSM that supports multiple sessions, you may want to
|
||||
// use a Pool instead, which contains multiple signers. Pool satisfies the
|
||||
// Signer interface just as Key does, and farms out work to multiple sessions
|
||||
// under the hood. This assumes you are calling Sign from multiple goroutines
|
||||
// (as would be common in an RPC or HTTP environment). If you only call Sign
|
||||
// from a single goroutine, you will only ever get single-session performance.
|
||||
type Pool struct {
|
||||
// This slice acts more or less like a concurrent stack. Keys are popped off
|
||||
// the top for use, and then pushed back on when they are no longer in use.
|
||||
signers []*Key
|
||||
// The initial length of signers, before any are popped off for use.
|
||||
totalCount int
|
||||
// This variable signals the condition that there are Keys available to be
|
||||
// used.
|
||||
cond *sync.Cond
|
||||
}
|
||||
|
||||
func (p *Pool) get() *Key {
|
||||
p.cond.L.Lock()
|
||||
for len(p.signers) == 0 {
|
||||
p.cond.Wait()
|
||||
}
|
||||
|
||||
instance := p.signers[len(p.signers)-1]
|
||||
p.signers = p.signers[:len(p.signers)-1]
|
||||
p.cond.L.Unlock()
|
||||
return instance
|
||||
}
|
||||
|
||||
func (p *Pool) put(instance *Key) {
|
||||
p.cond.L.Lock()
|
||||
p.signers = append(p.signers, instance)
|
||||
p.cond.Signal()
|
||||
p.cond.L.Unlock()
|
||||
}
|
||||
|
||||
// Sign performs a signature using an available PKCS #11 key. If there is no key
|
||||
// available, it blocks until there is.
|
||||
func (p *Pool) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
instance := p.get()
|
||||
defer p.put(instance)
|
||||
return instance.Sign(rand, msg, opts)
|
||||
}
|
||||
|
||||
// Public returns the public key of any one of the signers in the pool. Since
|
||||
// they were all created with the same arguments, the public key should be the
|
||||
// same for each one.
|
||||
func (p *Pool) Public() crypto.PublicKey {
|
||||
instance := p.get()
|
||||
defer p.put(instance)
|
||||
return instance.Public()
|
||||
}
|
||||
|
||||
// NewPool creates a pool of Keys of size n.
|
||||
func NewPool(n int, modulePath, tokenLabel, pin, privateKeyLabel string) (*Pool, error) {
|
||||
var err error
|
||||
signers := make([]*Key, n)
|
||||
for i := 0; i < n; i++ {
|
||||
signers[i], err = New(modulePath, tokenLabel, pin, privateKeyLabel)
|
||||
// If any of the signers fail, exit early. This could be, e.g., a bad PIN,
|
||||
// and we want to make sure not to lock the token.
|
||||
if err != nil {
|
||||
for j := 0; j < i; j++ {
|
||||
signers[j].Destroy()
|
||||
}
|
||||
return nil, fmt.Errorf("pkcs11key: problem making Key: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
var mutex sync.Mutex
|
||||
return &Pool{
|
||||
signers: signers,
|
||||
totalCount: len(signers),
|
||||
cond: sync.NewCond(&mutex),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Destroy calls destroy for each of the member keys, shutting down their
|
||||
// sessions.
|
||||
func (p *Pool) Destroy() error {
|
||||
for i := 0; i < p.totalCount; i++ {
|
||||
err := p.get().Destroy()
|
||||
if err != nil {
|
||||
return fmt.Errorf("destroy error: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
package csr
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
|
|
@ -14,6 +15,7 @@ import (
|
|||
"strings"
|
||||
|
||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
|
||||
)
|
||||
|
||||
|
|
@ -32,33 +34,51 @@ type Name struct {
|
|||
OU string // OrganisationalUnitName
|
||||
}
|
||||
|
||||
// A KeyRequest contains the algorithm and key size for a new private
|
||||
// key.
|
||||
type KeyRequest struct {
|
||||
Algo string `json:"algo"`
|
||||
Size int `json:"size"`
|
||||
// A KeyRequest is a generic request for a new key.
|
||||
type KeyRequest interface {
|
||||
Algo() string
|
||||
Size() int
|
||||
Generate() (crypto.PrivateKey, error)
|
||||
SigAlgo() x509.SignatureAlgorithm
|
||||
}
|
||||
|
||||
// The DefaultKeyRequest is used when no key request data is provided
|
||||
// in the request. This should be a safe default.
|
||||
var DefaultKeyRequest = KeyRequest{
|
||||
Algo: "ecdsa",
|
||||
Size: curveP256,
|
||||
// A BasicKeyRequest contains the algorithm and key size for a new private key.
|
||||
type BasicKeyRequest struct {
|
||||
A string `json:"algo"`
|
||||
S int `json:"size"`
|
||||
}
|
||||
|
||||
// NewBasicKeyRequest returns a default BasicKeyRequest.
|
||||
func NewBasicKeyRequest() *BasicKeyRequest {
|
||||
return &BasicKeyRequest{"ecdsa", curveP256}
|
||||
}
|
||||
|
||||
// Algo returns the requested key algorithm represented as a string.
|
||||
func (kr *BasicKeyRequest) Algo() string {
|
||||
return kr.A
|
||||
}
|
||||
|
||||
// Size returns the requested key size.
|
||||
func (kr *BasicKeyRequest) Size() int {
|
||||
return kr.S
|
||||
}
|
||||
|
||||
// Generate generates a key as specified in the request. Currently,
|
||||
// only ECDSA and RSA are supported.
|
||||
func (kr *KeyRequest) Generate() (interface{}, error) {
|
||||
log.Debugf("generate key from request: algo=%s, size=%d", kr.Algo, kr.Size)
|
||||
switch kr.Algo {
|
||||
func (kr *BasicKeyRequest) Generate() (crypto.PrivateKey, error) {
|
||||
log.Debugf("generate key from request: algo=%s, size=%d", kr.Algo(), kr.Size())
|
||||
switch kr.Algo() {
|
||||
case "rsa":
|
||||
if kr.Size < 2048 {
|
||||
if kr.Size() < 2048 {
|
||||
return nil, errors.New("RSA key is too weak")
|
||||
}
|
||||
return rsa.GenerateKey(rand.Reader, kr.Size)
|
||||
if kr.Size() > 8192 {
|
||||
return nil, errors.New("RSA key size too large")
|
||||
}
|
||||
return rsa.GenerateKey(rand.Reader, kr.Size())
|
||||
case "ecdsa":
|
||||
var curve elliptic.Curve
|
||||
switch kr.Size {
|
||||
switch kr.Size() {
|
||||
case curveP256:
|
||||
curve = elliptic.P256()
|
||||
case curveP384:
|
||||
|
|
@ -76,26 +96,26 @@ func (kr *KeyRequest) Generate() (interface{}, error) {
|
|||
|
||||
// SigAlgo returns an appropriate X.509 signature algorithm given the
|
||||
// key request's type and size.
|
||||
func (kr *KeyRequest) SigAlgo() x509.SignatureAlgorithm {
|
||||
switch kr.Algo {
|
||||
func (kr *BasicKeyRequest) SigAlgo() x509.SignatureAlgorithm {
|
||||
switch kr.Algo() {
|
||||
case "rsa":
|
||||
switch {
|
||||
case kr.Size >= 4096:
|
||||
case kr.Size() >= 4096:
|
||||
return x509.SHA512WithRSA
|
||||
case kr.Size >= 3072:
|
||||
case kr.Size() >= 3072:
|
||||
return x509.SHA384WithRSA
|
||||
case kr.Size >= 2048:
|
||||
case kr.Size() >= 2048:
|
||||
return x509.SHA256WithRSA
|
||||
default:
|
||||
return x509.SHA1WithRSA
|
||||
}
|
||||
case "ecdsa":
|
||||
switch {
|
||||
case kr.Size == curveP521:
|
||||
switch kr.Size() {
|
||||
case curveP521:
|
||||
return x509.ECDSAWithSHA512
|
||||
case kr.Size == curveP384:
|
||||
case curveP384:
|
||||
return x509.ECDSAWithSHA384
|
||||
case kr.Size == curveP256:
|
||||
case curveP256:
|
||||
return x509.ECDSAWithSHA256
|
||||
default:
|
||||
return x509.ECDSAWithSHA1
|
||||
|
|
@ -115,10 +135,18 @@ type CAConfig struct {
|
|||
// certificate request functionality.
|
||||
type CertificateRequest struct {
|
||||
CN string
|
||||
Names []Name `json:"names"`
|
||||
Hosts []string `json:"hosts"`
|
||||
KeyRequest *KeyRequest `json:"key,omitempty"`
|
||||
CA *CAConfig `json:"ca,omitempty"`
|
||||
Names []Name `json:"names"`
|
||||
Hosts []string `json:"hosts"`
|
||||
KeyRequest KeyRequest `json:"key,omitempty"`
|
||||
CA *CAConfig `json:"ca,omitempty"`
|
||||
}
|
||||
|
||||
// New returns a new, empty CertificateRequest with a
|
||||
// BasicKeyRequest.
|
||||
func New() *CertificateRequest {
|
||||
return &CertificateRequest{
|
||||
KeyRequest: &BasicKeyRequest{},
|
||||
}
|
||||
}
|
||||
|
||||
// appendIf appends to a if s is not an empty string.
|
||||
|
|
@ -152,13 +180,10 @@ func (cr *CertificateRequest) Name() pkix.Name {
|
|||
func ParseRequest(req *CertificateRequest) (csr, key []byte, err error) {
|
||||
log.Info("received CSR")
|
||||
if req.KeyRequest == nil {
|
||||
req.KeyRequest = &KeyRequest{
|
||||
Algo: DefaultKeyRequest.Algo,
|
||||
Size: DefaultKeyRequest.Size,
|
||||
}
|
||||
req.KeyRequest = NewBasicKeyRequest()
|
||||
}
|
||||
|
||||
log.Infof("generating key: %s-%d", req.KeyRequest.Algo, req.KeyRequest.Size)
|
||||
log.Infof("generating key: %s-%d", req.KeyRequest.Algo(), req.KeyRequest.Size())
|
||||
priv, err := req.KeyRequest.Generate()
|
||||
if err != nil {
|
||||
err = cferr.Wrap(cferr.PrivateKeyError, cferr.GenerationFailed, err)
|
||||
|
|
@ -249,3 +274,53 @@ func IsNameEmpty(n Name) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Regenerate uses the provided CSR as a template for signing a new
|
||||
// CSR using priv.
|
||||
func Regenerate(priv crypto.Signer, csr []byte) ([]byte, error) {
|
||||
req, extra, err := helpers.ParseCSR(csr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(extra) > 0 {
|
||||
return nil, errors.New("csr: trailing data in certificate request")
|
||||
}
|
||||
|
||||
return x509.CreateCertificateRequest(rand.Reader, req, priv)
|
||||
}
|
||||
|
||||
// Generate creates a new CSR from a CertificateRequest structure and
|
||||
// an existing key. The KeyRequest field is ignored.
|
||||
func Generate(priv crypto.Signer, req *CertificateRequest) (csr []byte, err error) {
|
||||
sigAlgo := helpers.SignerAlgo(priv, crypto.SHA256)
|
||||
if sigAlgo == x509.UnknownSignatureAlgorithm {
|
||||
return nil, cferr.New(cferr.PrivateKeyError, cferr.Unavailable)
|
||||
}
|
||||
|
||||
var tpl = x509.CertificateRequest{
|
||||
Subject: req.Name(),
|
||||
SignatureAlgorithm: sigAlgo,
|
||||
}
|
||||
|
||||
for i := range req.Hosts {
|
||||
if ip := net.ParseIP(req.Hosts[i]); ip != nil {
|
||||
tpl.IPAddresses = append(tpl.IPAddresses, ip)
|
||||
} else {
|
||||
tpl.DNSNames = append(tpl.DNSNames, req.Hosts[i])
|
||||
}
|
||||
}
|
||||
|
||||
csr, err = x509.CreateCertificateRequest(rand.Reader, &tpl, priv)
|
||||
if err != nil {
|
||||
log.Errorf("failed to generate a CSR: %v", err)
|
||||
err = cferr.Wrap(cferr.CSRError, cferr.BadRequest, err)
|
||||
return
|
||||
}
|
||||
block := pem.Block{
|
||||
Type: "CERTIFICATE REQUEST",
|
||||
Bytes: csr,
|
||||
}
|
||||
|
||||
log.Info("encoded CSR")
|
||||
csr = pem.EncodeToMemory(&block)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package csr
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
|
|
@ -9,13 +10,13 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
|
||||
)
|
||||
|
||||
// TestKeyRequest ensures that key generation returns the same type of
|
||||
// key specified in the KeyRequest.
|
||||
func TestKeyRequest(t *testing.T) {
|
||||
var kr = &KeyRequest{"ecdsa", 256}
|
||||
|
||||
// TestBasicKeyRequest ensures that key generation returns the same type of
|
||||
// key specified in the BasicKeyRequest.
|
||||
func TestBasicKeyRequest(t *testing.T) {
|
||||
kr := NewBasicKeyRequest()
|
||||
priv, err := kr.Generate()
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
|
|
@ -23,11 +24,11 @@ func TestKeyRequest(t *testing.T) {
|
|||
|
||||
switch priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if kr.Algo != "rsa" {
|
||||
if kr.Algo() != "rsa" {
|
||||
t.Fatal("RSA key generated, but expected", kr.Algo)
|
||||
}
|
||||
case *ecdsa.PrivateKey:
|
||||
if kr.Algo != "ecdsa" {
|
||||
if kr.Algo() != "ecdsa" {
|
||||
t.Fatal("ECDSA key generated, but expected", kr.Algo)
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +37,6 @@ func TestKeyRequest(t *testing.T) {
|
|||
// TestPKIXName validates building a pkix.Name structure from a
|
||||
// CertificateRequest.
|
||||
func TestPKIXName(t *testing.T) {
|
||||
var kr = KeyRequest{"ecdsa", 256}
|
||||
var cr = &CertificateRequest{
|
||||
CN: "Test Common Name",
|
||||
Names: []Name{
|
||||
|
|
@ -56,7 +56,7 @@ func TestPKIXName(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
|
||||
KeyRequest: &kr,
|
||||
KeyRequest: NewBasicKeyRequest(),
|
||||
}
|
||||
|
||||
name := cr.Name()
|
||||
|
|
@ -78,7 +78,6 @@ func TestPKIXName(t *testing.T) {
|
|||
// TestParseRequest ensures that a valid certificate request does not
|
||||
// error.
|
||||
func TestParseRequest(t *testing.T) {
|
||||
var kr = KeyRequest{"ecdsa", 256}
|
||||
var cr = &CertificateRequest{
|
||||
CN: "Test Common Name",
|
||||
Names: []Name{
|
||||
|
|
@ -98,7 +97,7 @@ func TestParseRequest(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "192.168.0.1"},
|
||||
KeyRequest: &kr,
|
||||
KeyRequest: NewBasicKeyRequest(),
|
||||
}
|
||||
|
||||
_, _, err := ParseRequest(cr)
|
||||
|
|
@ -126,7 +125,7 @@ func TestECGeneration(t *testing.T) {
|
|||
var eckey *ecdsa.PrivateKey
|
||||
|
||||
for _, sz := range []int{256, 384, 521} {
|
||||
kr := &KeyRequest{Algo: "ecdsa", Size: sz}
|
||||
kr := &BasicKeyRequest{"ecdsa", sz}
|
||||
priv, err := kr.Generate()
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
|
|
@ -145,13 +144,13 @@ func TestRSAKeyGeneration(t *testing.T) {
|
|||
var rsakey *rsa.PrivateKey
|
||||
|
||||
for _, sz := range []int{2048, 3072, 4096} {
|
||||
kr := &KeyRequest{Algo: "rsa", Size: sz}
|
||||
kr := &BasicKeyRequest{"rsa", sz}
|
||||
priv, err := kr.Generate()
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
rsakey = priv.(*rsa.PrivateKey)
|
||||
if rsakey.PublicKey.N.BitLen() != kr.Size {
|
||||
if rsakey.PublicKey.N.BitLen() != kr.Size() {
|
||||
t.Fatal("Generated key has wrong size.")
|
||||
}
|
||||
if sa := kr.SigAlgo(); sa == x509.UnknownSignatureAlgorithm {
|
||||
|
|
@ -160,26 +159,26 @@ func TestRSAKeyGeneration(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestBadKeyRequest ensures that generating a key from a KeyRequest
|
||||
// TestBadBasicKeyRequest ensures that generating a key from a BasicKeyRequest
|
||||
// fails with an invalid algorithm, or an invalid RSA or ECDSA key
|
||||
// size. An invalid ECDSA key size is any size other than 256, 384, or
|
||||
// 521; an invalid RSA key size is any size less than 2048 bits.
|
||||
func TestBadKeyRequest(t *testing.T) {
|
||||
kr := &KeyRequest{Algo: "yolocrypto", Size: 1024}
|
||||
func TestBadBasicKeyRequest(t *testing.T) {
|
||||
kr := &BasicKeyRequest{"yolocrypto", 1024}
|
||||
if _, err := kr.Generate(); err == nil {
|
||||
t.Fatal("Key generation should fail with invalid algorithm")
|
||||
} else if sa := kr.SigAlgo(); sa != x509.UnknownSignatureAlgorithm {
|
||||
t.Fatal("The wrong signature algorithm was returned from SigAlgo!")
|
||||
}
|
||||
|
||||
kr.Algo = "ecdsa"
|
||||
kr.A = "ecdsa"
|
||||
if _, err := kr.Generate(); err == nil {
|
||||
t.Fatal("Key generation should fail with invalid key size")
|
||||
} else if sa := kr.SigAlgo(); sa != x509.ECDSAWithSHA1 {
|
||||
t.Fatal("The wrong signature algorithm was returned from SigAlgo!")
|
||||
}
|
||||
|
||||
kr.Algo = "rsa"
|
||||
kr.A = "rsa"
|
||||
if _, err := kr.Generate(); err == nil {
|
||||
t.Fatal("Key generation should fail with invalid key size")
|
||||
} else if sa := kr.SigAlgo(); sa != x509.SHA1WithRSA {
|
||||
|
|
@ -188,9 +187,9 @@ func TestBadKeyRequest(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
// TestDefaultKeyRequest makes sure that certificate requests without
|
||||
// TestDefaultBasicKeyRequest makes sure that certificate requests without
|
||||
// explicit key requests fall back to the default key request.
|
||||
func TestDefaultKeyRequest(t *testing.T) {
|
||||
func TestDefaultBasicKeyRequest(t *testing.T) {
|
||||
var req = &CertificateRequest{
|
||||
Names: []Name{
|
||||
{
|
||||
|
|
@ -215,13 +214,14 @@ func TestDefaultKeyRequest(t *testing.T) {
|
|||
t.Fatal("Bad private key was generated!")
|
||||
}
|
||||
|
||||
DefaultKeyRequest := NewBasicKeyRequest()
|
||||
switch block.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
if DefaultKeyRequest.Algo != "rsa" {
|
||||
if DefaultKeyRequest.Algo() != "rsa" {
|
||||
t.Fatal("Invalid default key request.")
|
||||
}
|
||||
case "EC PRIVATE KEY":
|
||||
if DefaultKeyRequest.Algo != "ecdsa" {
|
||||
if DefaultKeyRequest.Algo() != "ecdsa" {
|
||||
t.Fatal("Invalid default key request.")
|
||||
}
|
||||
}
|
||||
|
|
@ -240,12 +240,9 @@ func TestRSACertRequest(t *testing.T) {
|
|||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
|
||||
KeyRequest: &KeyRequest{
|
||||
Algo: "rsa",
|
||||
Size: 2048,
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
|
||||
KeyRequest: &BasicKeyRequest{"rsa", 2048},
|
||||
}
|
||||
_, _, err := ParseRequest(req)
|
||||
if err != nil {
|
||||
|
|
@ -265,12 +262,9 @@ func TestBadCertRequest(t *testing.T) {
|
|||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
|
||||
KeyRequest: &KeyRequest{
|
||||
Algo: "yolo-crypto",
|
||||
Size: 2048,
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
|
||||
KeyRequest: &BasicKeyRequest{"yolo-crypto", 2048},
|
||||
}
|
||||
_, _, err := ParseRequest(req)
|
||||
if err == nil {
|
||||
|
|
@ -303,12 +297,9 @@ func TestGenerator(t *testing.T) {
|
|||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "192.168.0.1"},
|
||||
KeyRequest: &KeyRequest{
|
||||
Algo: "rsa",
|
||||
Size: 2048,
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "192.168.0.1"},
|
||||
KeyRequest: &BasicKeyRequest{"rsa", 2048},
|
||||
}
|
||||
|
||||
csrBytes, _, err := g.ProcessRequest(req)
|
||||
|
|
@ -355,11 +346,8 @@ func TestBadGenerator(t *testing.T) {
|
|||
},
|
||||
},
|
||||
// Missing CN
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
|
||||
KeyRequest: &KeyRequest{
|
||||
Algo: "rsa",
|
||||
Size: 2048,
|
||||
},
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
|
||||
KeyRequest: &BasicKeyRequest{"rsa", 2048},
|
||||
}
|
||||
|
||||
_, _, err := g.ProcessRequest(missingCN)
|
||||
|
|
@ -379,12 +367,9 @@ func TestWeakCSR(t *testing.T) {
|
|||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
|
||||
KeyRequest: &KeyRequest{
|
||||
Algo: "rsa",
|
||||
Size: 1024,
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
|
||||
KeyRequest: &BasicKeyRequest{"rsa", 1024},
|
||||
}
|
||||
g := &Generator{testValidator}
|
||||
|
||||
|
|
@ -431,3 +416,76 @@ func TestIsNameEmpty(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
var req = &CertificateRequest{
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "192.168.0.1"},
|
||||
KeyRequest: &BasicKeyRequest{"ecdsa", 256},
|
||||
}
|
||||
|
||||
key, err := req.KeyRequest.Generate()
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
priv, ok := key.(crypto.Signer)
|
||||
if !ok {
|
||||
t.Fatal("Private key is not a signer.")
|
||||
}
|
||||
|
||||
csrPEM, err := Generate(priv, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
_, _, err = helpers.ParseCSR(csrPEM)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReGenerate(t *testing.T) {
|
||||
var req = &CertificateRequest{
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "192.168.0.1"},
|
||||
KeyRequest: &BasicKeyRequest{"ecdsa", 256},
|
||||
}
|
||||
|
||||
csr, key, err := ParseRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
priv, err := helpers.ParsePrivateKeyPEM(key)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
csr, err = Generate(priv, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
if _, _, err = helpers.ParseCSR(csr); err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"math/big"
|
||||
//"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -231,6 +233,7 @@ func ParseSelfSignedCertificatePEM(certPEM []byte) (*x509.Certificate, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature); err != nil {
|
||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.VerifyFailed, err)
|
||||
}
|
||||
|
|
@ -312,3 +315,105 @@ func GetKeyDERFromPEM(in []byte) ([]byte, error) {
|
|||
|
||||
return nil, cferr.New(cferr.PrivateKeyError, cferr.DecodeFailed)
|
||||
}
|
||||
|
||||
// CheckSignature verifies a signature made by the key on a CSR, such
|
||||
// as on the CSR itself.
|
||||
func CheckSignature(csr *x509.CertificateRequest, algo x509.SignatureAlgorithm, signed, signature []byte) error {
|
||||
var hashType crypto.Hash
|
||||
|
||||
switch algo {
|
||||
case x509.SHA1WithRSA, x509.ECDSAWithSHA1:
|
||||
hashType = crypto.SHA1
|
||||
case x509.SHA256WithRSA, x509.ECDSAWithSHA256:
|
||||
hashType = crypto.SHA256
|
||||
case x509.SHA384WithRSA, x509.ECDSAWithSHA384:
|
||||
hashType = crypto.SHA384
|
||||
case x509.SHA512WithRSA, x509.ECDSAWithSHA512:
|
||||
hashType = crypto.SHA512
|
||||
default:
|
||||
return x509.ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
if !hashType.Available() {
|
||||
return x509.ErrUnsupportedAlgorithm
|
||||
}
|
||||
h := hashType.New()
|
||||
|
||||
h.Write(signed)
|
||||
digest := h.Sum(nil)
|
||||
|
||||
switch pub := csr.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
return rsa.VerifyPKCS1v15(pub, hashType, digest, signature)
|
||||
case *ecdsa.PublicKey:
|
||||
ecdsaSig := new(struct{ R, S *big.Int })
|
||||
if _, err := asn1.Unmarshal(signature, ecdsaSig); err != nil {
|
||||
return err
|
||||
}
|
||||
if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
|
||||
return errors.New("x509: ECDSA signature contained zero or negative values")
|
||||
}
|
||||
if !ecdsa.Verify(pub, digest, ecdsaSig.R, ecdsaSig.S) {
|
||||
return errors.New("x509: ECDSA verification failure")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return x509.ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
// ParseCSR parses a PEM- or DER-encoded PKCS #10 certificate signing request.
|
||||
func ParseCSR(in []byte) (csr *x509.CertificateRequest, rest []byte, err error) {
|
||||
in = bytes.TrimSpace(in)
|
||||
p, rest := pem.Decode(in)
|
||||
if p != nil {
|
||||
if p.Type != "CERTIFICATE REQUEST" {
|
||||
return nil, rest, cferr.New(cferr.CSRError, cferr.BadRequest)
|
||||
}
|
||||
|
||||
csr, err = x509.ParseCertificateRequest(p.Bytes)
|
||||
} else {
|
||||
csr, err = x509.ParseCertificateRequest(in)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, rest, err
|
||||
}
|
||||
|
||||
err = CheckSignature(csr, csr.SignatureAlgorithm, csr.RawTBSCertificateRequest, csr.Signature)
|
||||
if err != nil {
|
||||
return nil, rest, err
|
||||
}
|
||||
|
||||
return csr, rest, nil
|
||||
}
|
||||
|
||||
// SignerAlgo returns an X.509 signature algorithm corresponding to
|
||||
// the crypto.Hash provided from a crypto.Signer.
|
||||
func SignerAlgo(priv crypto.Signer, h crypto.Hash) x509.SignatureAlgorithm {
|
||||
switch priv.Public().(type) {
|
||||
case *rsa.PublicKey:
|
||||
switch h {
|
||||
case crypto.SHA512:
|
||||
return x509.SHA512WithRSA
|
||||
case crypto.SHA384:
|
||||
return x509.SHA384WithRSA
|
||||
case crypto.SHA256:
|
||||
return x509.SHA256WithRSA
|
||||
default:
|
||||
return x509.SHA1WithRSA
|
||||
}
|
||||
case *ecdsa.PublicKey:
|
||||
switch h {
|
||||
case crypto.SHA512:
|
||||
return x509.ECDSAWithSHA512
|
||||
case crypto.SHA384:
|
||||
return x509.ECDSAWithSHA384
|
||||
case crypto.SHA256:
|
||||
return x509.ECDSAWithSHA256
|
||||
default:
|
||||
return x509.ECDSAWithSHA1
|
||||
}
|
||||
default:
|
||||
return x509.UnknownSignatureAlgorithm
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -356,3 +356,25 @@ func TestParsePrivateKeyPEM(t *testing.T) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
// Imported from signers/local/testdata/
|
||||
const ecdsaTestCSR = "testdata/ecdsa256.csr"
|
||||
|
||||
func TestParseCSRPEM(t *testing.T) {
|
||||
in, err := ioutil.ReadFile(ecdsaTestCSR)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
_, _, err = ParseCSR(in)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
in[12]++
|
||||
_, _, err = ParseCSR(in)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected an invalid CSR.")
|
||||
}
|
||||
in[12]--
|
||||
}
|
||||
|
|
|
|||
10
Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers/pkcs11uri/pkcs11uri.go
generated
vendored
10
Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers/pkcs11uri/pkcs11uri.go
generated
vendored
|
|
@ -10,8 +10,8 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/pkcs11"
|
||||
)
|
||||
|
||||
func setIfPresent(val url.Values, k string, target *string) {
|
||||
|
|
@ -27,13 +27,13 @@ var ErrInvalidURI = errors.New(errors.PrivateKeyError, errors.ParseFailed)
|
|||
// ParsePKCS11URI parses a PKCS #11 URI into a PKCS #11
|
||||
// configuration. Note that the module path will override the module
|
||||
// name if present.
|
||||
func ParsePKCS11URI(uri string) (*pkcs11.Config, error) {
|
||||
func ParsePKCS11URI(uri string) (*pkcs11key.Config, error) {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil || u.Scheme != "pkcs11" {
|
||||
return nil, ErrInvalidURI
|
||||
}
|
||||
|
||||
c := new(pkcs11.Config)
|
||||
c := new(pkcs11key.Config)
|
||||
|
||||
pk11PAttr, err := url.ParseQuery(u.Opaque)
|
||||
if err != nil {
|
||||
|
|
@ -44,11 +44,11 @@ func ParsePKCS11URI(uri string) (*pkcs11.Config, error) {
|
|||
if err != nil {
|
||||
return nil, ErrInvalidURI
|
||||
}
|
||||
setIfPresent(pk11PAttr, "token", &c.Token)
|
||||
setIfPresent(pk11PAttr, "token", &c.TokenLabel)
|
||||
setIfPresent(pk11PAttr, "object", &c.PrivateKeyLabel)
|
||||
setIfPresent(pk11QAttr, "module-name", &c.Module)
|
||||
setIfPresent(pk11QAttr, "module-path", &c.Module)
|
||||
setIfPresent(pk11QAttr, "pin-value", &c.PIN)
|
||||
setIfPresent(pk11PAttr, "slot-description", &c.Label)
|
||||
|
||||
var pinSourceURI string
|
||||
setIfPresent(pk11QAttr, "pin-source", &pinSourceURI)
|
||||
|
|
|
|||
40
Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers/pkcs11uri/pkcs11uri_test.go
generated
vendored
40
Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers/pkcs11uri/pkcs11uri_test.go
generated
vendored
|
|
@ -4,15 +4,15 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/pkcs11"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key"
|
||||
)
|
||||
|
||||
type pkcs11UriTest struct {
|
||||
URI string
|
||||
Config *pkcs11.Config
|
||||
Config *pkcs11key.Config
|
||||
}
|
||||
|
||||
func cmpConfigs(a, b *pkcs11.Config) bool {
|
||||
func cmpConfigs(a, b *pkcs11key.Config) bool {
|
||||
if a == nil {
|
||||
if b == nil {
|
||||
return true
|
||||
|
|
@ -25,12 +25,12 @@ func cmpConfigs(a, b *pkcs11.Config) bool {
|
|||
}
|
||||
|
||||
return (a.Module == b.Module) &&
|
||||
(a.Token == b.Token) &&
|
||||
(a.TokenLabel == b.TokenLabel) &&
|
||||
(a.PIN == b.PIN) &&
|
||||
(a.Label == b.Label)
|
||||
(a.PrivateKeyLabel == b.PrivateKeyLabel)
|
||||
}
|
||||
|
||||
func diffConfigs(want, have *pkcs11.Config) {
|
||||
func diffConfigs(want, have *pkcs11key.Config) {
|
||||
if have == nil && want != nil {
|
||||
fmt.Printf("Expected config, have nil.")
|
||||
return
|
||||
|
|
@ -45,9 +45,9 @@ func diffConfigs(want, have *pkcs11.Config) {
|
|||
}
|
||||
|
||||
diff("Module", want.Module, have.Module)
|
||||
diff("Token", want.Token, have.Token)
|
||||
diff("TokenLabel", want.TokenLabel, have.TokenLabel)
|
||||
diff("PIN", want.PIN, have.PIN)
|
||||
diff("Label", want.Label, have.Label)
|
||||
diff("PrivateKeyLabel", want.PrivateKeyLabel, have.PrivateKeyLabel)
|
||||
}
|
||||
|
||||
/* Config from PKCS #11 signer
|
||||
|
|
@ -61,20 +61,20 @@ type Config struct {
|
|||
|
||||
var pkcs11UriCases = []pkcs11UriTest{
|
||||
{"pkcs11:token=Software%20PKCS%2311%20softtoken;manufacturer=Snake%20Oil,%20Inc.?pin-value=the-pin",
|
||||
&pkcs11.Config{
|
||||
Token: "Software PKCS#11 softtoken",
|
||||
PIN: "the-pin",
|
||||
&pkcs11key.Config{
|
||||
TokenLabel: "Software PKCS#11 softtoken",
|
||||
PIN: "the-pin",
|
||||
}},
|
||||
{"pkcs11:slot-description=Sun%20Metaslot",
|
||||
&pkcs11.Config{
|
||||
Label: "Sun Metaslot",
|
||||
{"pkcs11:token=Sun%20Token",
|
||||
&pkcs11key.Config{
|
||||
TokenLabel: "Sun Token",
|
||||
}},
|
||||
{"pkcs11:slot-description=test-label;token=test-token?pin-source=file:testdata/pin&module-name=test-module",
|
||||
&pkcs11.Config{
|
||||
Label: "test-label",
|
||||
Token: "test-token",
|
||||
PIN: "123456",
|
||||
Module: "test-module",
|
||||
{"pkcs11:object=test-privkey;token=test-token?pin-source=file:testdata/pin&module-name=test-module",
|
||||
&pkcs11key.Config{
|
||||
PrivateKeyLabel: "test-privkey",
|
||||
TokenLabel: "test-token",
|
||||
PIN: "123456",
|
||||
Module: "test-module",
|
||||
}},
|
||||
}
|
||||
|
||||
|
|
|
|||
11
Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers/testdata/ecdsa256.csr
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers/testdata/ecdsa256.csr
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBgTCCASgCAQAwgYYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
|
||||
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
|
||||
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRcwFQYDVQQDEw5jbG91ZGZsYXJl
|
||||
LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBn9Ldie6BOcMHezn2dPuYqW
|
||||
z/NoLYMLGNBqhOxUyEidYClI0JW2pWyUgT3A2UazFp1WgE94y7Z+2YlfRz+vcrKg
|
||||
PzA9BgkqhkiG9w0BCQ4xMDAuMCwGA1UdEQQlMCOCDmNsb3VkZmxhcmUuY29tghF3
|
||||
d3djbG91ZGZsYXJlLmNvbTAKBggqhkjOPQQDAgNHADBEAiBM+QRxe8u6rkdr10Jy
|
||||
cxbR6NxrGrNeg5QqiOqF96JEmgIgDbtjd5e3y3I8W/+ih2us3WtMxgnTXfqPd48i
|
||||
VLcv28Q=
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
|
|
@ -1 +1 @@
|
|||
{"cert":"-----BEGIN CERTIFICATE-----\nMIIEBjCCAvCgAwIBAgIIcoeD7FP4p0gwCwYJKoZIhvcNAQELMIGQMQswCQYDVQQG\nEwJVUzEeMBwGA1UEChMVSW50ZXJuZXQgV2lkZ2V0cywgTExDMR4wHAYDVQQLExVD\nZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR\nBgNVBAgTCkNhbGlmb3JuaWExFDASBgNVBAMTC2V4YW1wbGUuY29tMB4XDTE1MDMy\nMzIwMTcwMloXDTE1MDMyMzIwMjIwMlowgZAxCzAJBgNVBAYTAlVTMR4wHAYDVQQK\nExVJbnRlcm5ldCBXaWRnZXRzLCBMTEMxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1\ndGhvcml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZv\ncm5pYTEUMBIGA1UEAxMLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQC/S+yLMwj5TEIWSujzOf3VBMCyiW+KM17fvSK6Wp14OElFs53G\nNNRVJDkIOyx66I29YqvYTO28WyLX3X8z8tS3hH8C9Bp/lG5X644hiQpl5vHSJfxa\nQdvtJZevY6lwnz9JDha1X0P7XzHeyJVC8u3Uq8i4X7t2GC23p0Jz5hEs6YBn6cQa\nui1I40U9teNUPE4ofUOt2EUx2uDiRs0+IOy5gM/GH7XDv88eWwSm/loqwYp5CdAv\n0/jnPqisqen0KXEQNFgZJZSzZy2a1GE4Ie/yc9R8udnkUd5pgIk26T4UDSpNVnDA\n5N7mH2MpcQ4l1ErTVk8WWrM+6X5UVIkWA56LAgMBAAGjZjBkMA4GA1UdDwEB/wQE\nAwIABjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBR8CfC68fIBAkJcjFOD\n6SDYLUTanTAfBgNVHSMEGDAWgBR8CfC68fIBAkJcjFOD6SDYLUTanTALBgkqhkiG\n9w0BAQsDggEBACZdSOd9C+ZiVGSqK113CvvQWMVxgEiprbQ+p2jJ+tmcZdAEDgDO\nEEdjPs1ygdhwJwiQL1C5Ak4Xc91aamP2dZtMlM02UVzTrjsOyC7FShq238pyaeQX\nzXyOOSnriMeuJvipLiStk1WhrbRkq1Zfy5CmX6FYd1dfuljwoDgfAY9GtCi0T6MY\n6vu9f8/S2ei4sTtqkF2/KbBq3K17WqsMmDxCYMWxBaY1z92i01lypPw3tM3k4Gkn\nJnil6RYlqg9odhazZcFXsNNywxg2S2Q2pJ1JLGDypgBpqzMQjd7zP6WOz9MTP8yK\n+V2sF/dDlAQCvl8EPvigqCCOXtL6uHYo0Fw=\n-----END CERTIFICATE-----\n","key":"-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAv0vsizMI+UxCFkro8zn91QTAsolvijNe370iulqdeDhJRbOd\nxjTUVSQ5CDsseuiNvWKr2EztvFsi191/M/LUt4R/AvQaf5RuV+uOIYkKZebx0iX8\nWkHb7SWXr2OpcJ8/SQ4WtV9D+18x3siVQvLt1KvIuF+7dhgtt6dCc+YRLOmAZ+nE\nGrotSONFPbXjVDxOKH1DrdhFMdrg4kbNPiDsuYDPxh+1w7/PHlsEpv5aKsGKeQnQ\nL9P45z6orKnp9ClxEDRYGSWUs2ctmtRhOCHv8nPUfLnZ5FHeaYCJNuk+FA0qTVZw\nwOTe5h9jKXEOJdRK01ZPFlqzPul+VFSJFgOeiwIDAQABAoIBAEzKaZYnUn7gwda5\nE3Iv4VlVoxUIXqIXpQojnoE4RuYvkhpM1BTwuBvFgq9vMZfVKrDPnzQhDX4DB6Wn\n4Jw16nYQ1fuVid2U9AaPJGsJ3Aa0AnbdkMOalHJKGO3rD+WJx5nH8g20xFFwEVDh\nLCnH4vjwTI7WvyXU5uVGW0KzJEnmbpjupMo+3IKSUdZyZspAc2vZby+bU18U7PO2\nfsyW7aZjQ0b2XFKbWpw+RD/HOskBImOqoL9AAzvXEaxFR3mz4OQBtUlzEiogr0/6\ngBNTbyIVSE+0hodtLxJMhi+xIFeoGCyYdpONU3mqKcYjnV4mHfLX2+Sn4hTJtaLg\n7VHyiKECgYEAwC+4RK1E/UU9nvEgaUowpmAtnk40g31ACylOmpAiHaBaJfZ2JToe\nSwd60oFZ5QEA/zoch1niUwgtj4Bn+Iv/sRD3N60DuLg5F+S+0qH7viZb5jAsIYmP\ntF7+nMd6iID+vXhXqL/ElJczGldKm+wKPV9n8KcdvHwDySvsy//WC0cCgYEA/tCR\nHbiLbMyA+stu7i5AdVWsSxmNipD8pjrI6509zerwnwvwX5nxagO3hBmev/jJgJKu\nL3ptZotR5CpcS/PxmNdRHumeQzvN5AnUefB037tQOtocPIAWwnh4a+hf4QtdXRmm\nbtSZPDBaIdEL+6XEpCfHdSktj/8nJ0aftW39rJ0CgYA7hgwiaS/26Z0ePzx0n9/V\nh/BZGYu2NfLCAjcwM/f9CQ8ituT8vrVnM5fc3udCwD7YzdzMJxCr+tpxmamaF3JI\nGyMWgWWnrpcwSU642iaoTCUmdEEW2g5CJTHiyP2wjZNYh49O2qY+B15yiBq5lC/P\ntl7w8DGLkTVy90HOJRzcPQKBgF1rY7iMvvkNZMANs0a5SEJ8PWVvIdhKXYYoCR9w\nj4cd+kelHeQ+0SY35KEWr3/cGyv7GscnUFKHA2fuK9ZgwhNx5Ti7F524oDxZaj+m\na4LW7owVCMMZhr6XYjbVg8lC3GDUlZMOeHt7kp/RP15sINK6DsMibKTdO6KHnufq\nQlV1AoGBAKeRFcw0jSB0PWEVHPDF4pCSz/vhoX/hy0OQN13QWLFhgt/rmTWOCFuJ\nSvIde8oM2pD5f8ZmxzMGkknEnrdX7zz/tE4roImep9G49BbetHGdWVu/TvvDyvXM\nzi//6WQzpjjqQxrl4q7B1tJ4A71fKN6V2u+qSdYg/t31EfE47lCQ\n-----END RSA PRIVATE KEY-----\n"}
|
||||
{"cert":"-----BEGIN CERTIFICATE-----\nMIIECjCCAvKgAwIBAgIIIrWBQ2reGCQwDQYJKoZIhvcNAQELBQAwgZAxCzAJBgNV\nBAYTAlVTMR4wHAYDVQQKExVJbnRlcm5ldCBXaWRnZXRzLCBMTEMxHjAcBgNVBAsT\nFUNlcnRpZmljYXRlIEF1dGhvcml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzET\nMBEGA1UECBMKQ2FsaWZvcm5pYTEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTUw\nOTA5MjExNjAwWhcNMTUwOTA5MjExNjAwWjCBkDELMAkGA1UEBhMCVVMxHjAcBgNV\nBAoTFUludGVybmV0IFdpZGdldHMsIExMQzEeMBwGA1UECxMVQ2VydGlmaWNhdGUg\nQXV0aG9yaXR5MRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxp\nZm9ybmlhMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBALOFN1p4aaMQKqInRanzmf05CD4kYKKVygggOHhD3ZgwFgIG\n20Z0dlTHStY/UvY+2qkpB1Bp9trmys9YvPzt5ElX2szZ2deZc0TWvzZeuZDQVsbx\n0Ea6RGxRnEEoMrpaRFoe2TOKSVKW+SzLC/eoNUoxsZVo6cNsU4BeGqExOWCDFzjd\nCRLJeXqafYeL1dUiXZ028tOZVWLIjaLu3FKENHeDB36gr99KckFdeDnqaAVvu66V\nbF5QGvIv2RmK9y3cq7rIizYfayi/dBS8/AY31OOHcEGsVTZYjqB/s9aIocCOzfvQ\nV5++QdkDiYhIwokB3fT5b4wIb8CLr0GUnHnslnMCAwEAAaNmMGQwDgYDVR0PAQH/\nBAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFCL83Uiwa3VGSXwD\nf8XKgu1l+nX8MB8GA1UdIwQYMBaAFCL83Uiwa3VGSXwDf8XKgu1l+nX8MA0GCSqG\nSIb3DQEBCwUAA4IBAQCRP7HK4JHxY4uYE3VBkx5SSQouZrnpQCOnUhSof3vi5nk0\nlhfIU7nbS6HVyQC4mY47gDrBlKwTr++Kw9BKyK9EgfVWhct8vszcRexZk5mmO3yi\n3s4kqD158SP0t5AMahHGK/VviD/Id4kaX1PIPHW3TfU3Ly4LP6d/NlloaXc8qrsg\nKJO+AXTy9xnEkjtq7lvnP3JLwA1Y+nozqkp46kQv0K+Nz+MBqN5De12I9J7T0k6h\nij4MsiraPdeij6d1cp+OEW8DPe+MCIBYAvbJXtQY5zYPB2/F6JJHMeQ9AmluvUFn\nD3ipCSVbw1Tgpp8xcx+4rt9Vq6Oo8pGi4JR3C7TN\n-----END CERTIFICATE-----\n","csr":"-----BEGIN CERTIFICATE REQUEST-----\nMIIDAjCCAeoCAQAwgZAxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVJbnRlcm5ldCBX\naWRnZXRzLCBMTEMxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1dGhvcml0eTEWMBQG\nA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5pYTEUMBIGA1UE\nAxMLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCz\nhTdaeGmjECqiJ0Wp85n9OQg+JGCilcoIIDh4Q92YMBYCBttGdHZUx0rWP1L2Ptqp\nKQdQafba5srPWLz87eRJV9rM2dnXmXNE1r82XrmQ0FbG8dBGukRsUZxBKDK6WkRa\nHtkziklSlvksywv3qDVKMbGVaOnDbFOAXhqhMTlggxc43QkSyXl6mn2Hi9XVIl2d\nNvLTmVViyI2i7txShDR3gwd+oK/fSnJBXXg56mgFb7uulWxeUBryL9kZivct3Ku6\nyIs2H2sov3QUvPwGN9Tjh3BBrFU2WI6gf7PWiKHAjs370FefvkHZA4mISMKJAd30\n+W+MCG/Ai69BlJx57JZzAgMBAAGgLDAqBgkqhkiG9w0BCQ4xHTAbMBkGA1UdEQQS\nMBCCDmNhLmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQA2E/lSvOtUZjOj\ncOAcALpCVLLTSRdqF7745jSqIrHV8RuB+V6HFi55qoFzCKfJbc81SUqX7DhzoUtP\nNjdq5RfhEh/kWw3GeIN90Y5thVDm0i19A7GY+maJXMjw3LVWlKIo9a7rCOQvCi3O\nkA0QGO1Rc1d6/MneoWUR2tUHjJ83FvO089RaDUQhYpInBXV3GQByJGJeF8USJ/wS\nXy+SLDkaG9mC0TJwWNpldGoZQ57h/QeLtl2wfK3sLga5eVuQO0Lnzt6W3tYvvSAO\nckN/hrTMFMJ/vXHFUPXk8AYUBt0FqgYYHR5JxA9Wl2+Fr+Duw7Qee7/kcbwhbo1M\nLe9GgT/c\n-----END CERTIFICATE REQUEST-----\n","key":"-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAs4U3WnhpoxAqoidFqfOZ/TkIPiRgopXKCCA4eEPdmDAWAgbb\nRnR2VMdK1j9S9j7aqSkHUGn22ubKz1i8/O3kSVfazNnZ15lzRNa/Nl65kNBWxvHQ\nRrpEbFGcQSgyulpEWh7ZM4pJUpb5LMsL96g1SjGxlWjpw2xTgF4aoTE5YIMXON0J\nEsl5epp9h4vV1SJdnTby05lVYsiNou7cUoQ0d4MHfqCv30pyQV14OepoBW+7rpVs\nXlAa8i/ZGYr3LdyrusiLNh9rKL90FLz8BjfU44dwQaxVNliOoH+z1oihwI7N+9BX\nn75B2QOJiEjCiQHd9PlvjAhvwIuvQZSceeyWcwIDAQABAoIBAQCYD0cLwuL31EHI\niCtiAY12CFELEhUoomg26dPrStDwkAqUPOqPiyaQOR8SyyCipCrjDnW7j4YhdUxk\nxW2PcJHl7hzPV9hF3xzcOmpMy3+zQkW+ziT1Q+GhPp90MXCGmN881tRz67af1nHJ\nl1PTuw8ogV9Ch1M6zQ8NtKgp0WAgdn2YlkVrYv9/JaOZdh7ewNQPs+V68bC8tmmz\nEj0kuH06Lzr3zxfxOgAq8Xrv8b5GmFNjcRPOu5WWCRKt26FSwAcI/U5AXRuqVr8o\nthMiXKPA+RMSSy7bDF1EOFYdNyrWaLEzyrKLSxTXTvOT5kIfwVWKTTzdNug/BaYy\neesqkgtBAoGBAOafPTvJHYLHuyb11X2FYblvkLaw+orWvdy1E2sV62M3YiloOp5R\neWteEptvsEjo0ZXZyYqOK5WHsbkdTbPE7QPctKIYdVdztPb+3wWHM9hP1kGWuohW\n+S/kVIwf+ijH10Q3wQPgtsIGrj0Q/rkizD6qqmKz8gNizRwX2k3kkp/1AoGBAMdG\nabBuC9XHxFtVIEzHSXbVn38Z6tjxURcUK7hob8iRfl4JXbHtEKkoM2iIpY/a4YrA\nvjLBy0tNWnurTdT/Md7L3pqU4wVlMY0i5ysae7/vm2WV9ePdcHM1Oa4gFTSL3dW1\nLI3TLwxDD3vlqhP/Js3CMq7wtvTE1T1sWxjiLOPHAoGBAI9t49Bl8SOgAoZliARL\nyw5gE3l7siZdDbHRMCV+eWm4TSWBfEtwUnpzBFGFcfa2TXrL+ytf5j1WKjL9xZCT\nXfDz96eEXJ4qCRdARgYTIyxZ4t/h1Vrr9IhTkj2fuZ5ZQ0la/4Dg5ejf7Mdv5WvQ\n94PV8qf2UALJFNVvBEdDa+ltAoGAVjSYvhEtH4M254fQ3EYN+tF5GSPeG5FxUfmQ\n3EqZqPt/3jBRDwqN/Y99hcgvTycSENNGtHBvgJjq/rrhhbYMHeS13Mtx1kCrifHC\nbTwcsrB12iFgaP2/iqdI15HbeorTIYMpzgTAwp40EZYN2G61m6daA6Hwk7yevt6k\nHgSiBLsCgYEAoou8wIMi0+zyUgIo8MXYhexJFuYvEdHeyYvWN/FA4j/DZelixgW9\no4a0M9yOXStvjUUpG/TpaceJGqtwXk9bMBZML05fT7Im/3Qmh+44fF/a0q0Ard2N\nzdlIuaKlvcK5a6jD+mcN9vzOi4iuGUm1OhfTvHD/IS/rG4r6XhahHm4=\n-----END RSA PRIVATE KEY-----\n"}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
keyRequest = csr.KeyRequest{
|
||||
Algo: "rsa",
|
||||
Size: 2048,
|
||||
keyRequest = csr.BasicKeyRequest{
|
||||
A: "rsa",
|
||||
S: 2048,
|
||||
}
|
||||
CAConfig = csr.CAConfig{
|
||||
PathLength: 1,
|
||||
|
|
@ -249,33 +249,8 @@ func TestCreateSelfSignedCert(t *testing.T) {
|
|||
|
||||
// --- TEST: Create a self-signed certificate from a CSR. --- //
|
||||
|
||||
// Make the request we will use to generate the certificate.
|
||||
keyRequest := csr.KeyRequest{
|
||||
Algo: "rsa",
|
||||
Size: 2048,
|
||||
}
|
||||
CAConfig := csr.CAConfig{
|
||||
PathLength: 1,
|
||||
Expiry: "1/1/2015",
|
||||
}
|
||||
request := csr.CertificateRequest{
|
||||
CN: "example.com",
|
||||
Names: []csr.Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "Internet Widgets, LLC",
|
||||
OU: "Certificate Authority",
|
||||
},
|
||||
},
|
||||
Hosts: []string{"ca.example.com"},
|
||||
KeyRequest: &keyRequest,
|
||||
CA: &CAConfig,
|
||||
}
|
||||
|
||||
// Generate a self-signed certificate from the request.
|
||||
encodedCertFromCode, _, err := CreateSelfSignedCert(request)
|
||||
encodedCertFromCode, _, err := CreateSelfSignedCert(CARequest)
|
||||
checkError(err, t)
|
||||
|
||||
// Now compare to a pre-made certificate made using a JSON file with the
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -10,19 +11,9 @@ import (
|
|||
// signer. If PKCS11.Module is non-empty, PKCS11 signing will be used.
|
||||
// Otherwise signing from a key file will be used.
|
||||
type Config struct {
|
||||
CACertFile string
|
||||
CACertFile string
|
||||
ResponderCertFile string
|
||||
KeyFile string
|
||||
Interval time.Duration
|
||||
PKCS11 PKCS11Config
|
||||
KeyFile string
|
||||
Interval time.Duration
|
||||
PKCS11 pkcs11key.Config
|
||||
}
|
||||
|
||||
// PKCS11Config contains information specific to setting up a PKCS11 OCSP
|
||||
// signer.
|
||||
type PKCS11Config struct {
|
||||
Module string
|
||||
Token string
|
||||
PIN string
|
||||
Label string
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ import (
|
|||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
var statusCode = map[string]int{
|
||||
// StatusCode is a map between string statuses sent by cli/api
|
||||
// to ocsp int statuses
|
||||
var StatusCode = map[string]int{
|
||||
"good": ocsp.Good,
|
||||
"revoked": ocsp.Revoked,
|
||||
"unknown": ocsp.Unknown,
|
||||
|
|
@ -122,7 +124,7 @@ func (s StandardSigner) Sign(req SignRequest) ([]byte, error) {
|
|||
thisUpdate := time.Now().Truncate(time.Hour)
|
||||
nextUpdate := thisUpdate.Add(s.interval)
|
||||
|
||||
status, ok := statusCode[req.Status]
|
||||
status, ok := StatusCode[req.Status]
|
||||
if !ok {
|
||||
return nil, cferr.New(cferr.OCSPError, cferr.InvalidStatus)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,11 @@ func NewPKCS11Signer(cfg ocspConfig.Config) (ocsp.Signer, error) {
|
|||
}
|
||||
|
||||
PKCS11 := cfg.PKCS11
|
||||
priv, err := pkcs11key.New(PKCS11.Module, PKCS11.Token, PKCS11.PIN,
|
||||
PKCS11.Label)
|
||||
priv, err := pkcs11key.New(
|
||||
PKCS11.Module,
|
||||
PKCS11.TokenLabel,
|
||||
PKCS11.PIN,
|
||||
PKCS11.PrivateKeyLabel)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.PrivateKeyError, errors.ReadFailed)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ func NewSourceFromFile(responseFile string) (Source, error) {
|
|||
responsesB64 := regexp.MustCompile("\\s").Split(string(fileContents), -1)
|
||||
src := InMemorySource{}
|
||||
for _, b64 := range responsesB64 {
|
||||
// if the line/space is empty just skip
|
||||
if b64 == "" {
|
||||
continue
|
||||
}
|
||||
der, tmpErr := base64.StdEncoding.DecodeString(b64)
|
||||
if tmpErr != nil {
|
||||
log.Errorf("Base64 decode error on: %s", b64)
|
||||
|
|
|
|||
|
|
@ -14,20 +14,11 @@ import (
|
|||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/local"
|
||||
)
|
||||
|
||||
// Config contains configuration information required to use a PKCS
|
||||
// #11 key.
|
||||
type Config struct {
|
||||
Module string
|
||||
Token string
|
||||
PIN string
|
||||
Label string
|
||||
}
|
||||
|
||||
// Enabled is set to true if PKCS #11 support is present.
|
||||
const Enabled = true
|
||||
|
||||
// New returns a new PKCS #11 signer.
|
||||
func New(caCertFile string, policy *config.Signing, cfg *Config) (signer.Signer, error) {
|
||||
func New(caCertFile string, policy *config.Signing, cfg *pkcs11key.Config) (signer.Signer, error) {
|
||||
if cfg == nil {
|
||||
return nil, errors.New(errors.PrivateKeyError, errors.ReadFailed)
|
||||
}
|
||||
|
|
@ -43,7 +34,7 @@ func New(caCertFile string, policy *config.Signing, cfg *Config) (signer.Signer,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
priv, err := pkcs11key.New(cfg.Module, cfg.Token, cfg.PIN, cfg.Label)
|
||||
priv, err := pkcs11key.New(cfg.Module, cfg.TokenLabel, cfg.PIN, cfg.PrivateKeyLabel)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.PrivateKeyError, errors.ReadFailed)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/csr"
|
||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/info"
|
||||
)
|
||||
|
||||
|
|
@ -147,7 +148,7 @@ func ParseCertificateRequest(s Signer, csrBytes []byte) (template *x509.Certific
|
|||
return
|
||||
}
|
||||
|
||||
err = CheckSignature(csr, csr.SignatureAlgorithm, csr.RawTBSCertificateRequest, csr.Signature)
|
||||
err = helpers.CheckSignature(csr, csr.SignatureAlgorithm, csr.RawTBSCertificateRequest, csr.Signature)
|
||||
if err != nil {
|
||||
err = cferr.Wrap(cferr.CSRError, cferr.KeyMismatch, err)
|
||||
return
|
||||
|
|
@ -165,51 +166,6 @@ func ParseCertificateRequest(s Signer, csrBytes []byte) (template *x509.Certific
|
|||
return
|
||||
}
|
||||
|
||||
// CheckSignature verifies a signature made by the key on a CSR, such
|
||||
// as on the CSR itself.
|
||||
func CheckSignature(csr *x509.CertificateRequest, algo x509.SignatureAlgorithm, signed, signature []byte) error {
|
||||
var hashType crypto.Hash
|
||||
|
||||
switch algo {
|
||||
case x509.SHA1WithRSA, x509.ECDSAWithSHA1:
|
||||
hashType = crypto.SHA1
|
||||
case x509.SHA256WithRSA, x509.ECDSAWithSHA256:
|
||||
hashType = crypto.SHA256
|
||||
case x509.SHA384WithRSA, x509.ECDSAWithSHA384:
|
||||
hashType = crypto.SHA384
|
||||
case x509.SHA512WithRSA, x509.ECDSAWithSHA512:
|
||||
hashType = crypto.SHA512
|
||||
default:
|
||||
return x509.ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
if !hashType.Available() {
|
||||
return x509.ErrUnsupportedAlgorithm
|
||||
}
|
||||
h := hashType.New()
|
||||
|
||||
h.Write(signed)
|
||||
digest := h.Sum(nil)
|
||||
|
||||
switch pub := csr.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
return rsa.VerifyPKCS1v15(pub, hashType, digest, signature)
|
||||
case *ecdsa.PublicKey:
|
||||
ecdsaSig := new(struct{ R, S *big.Int })
|
||||
if _, err := asn1.Unmarshal(signature, ecdsaSig); err != nil {
|
||||
return err
|
||||
}
|
||||
if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
|
||||
return errors.New("x509: ECDSA signature contained zero or negative values")
|
||||
}
|
||||
if !ecdsa.Verify(pub, digest, ecdsaSig.R, ecdsaSig.S) {
|
||||
return errors.New("x509: ECDSA verification failure")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return x509.ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
type subjectPublicKeyInfo struct {
|
||||
Algorithm pkix.AlgorithmIdentifier
|
||||
SubjectPublicKey asn1.BitString
|
||||
|
|
|
|||
17
Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/universal/universal.go
generated
vendored
17
Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/universal/universal.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package universal
|
|||
|
||||
import (
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key"
|
||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/local"
|
||||
|
|
@ -38,12 +39,12 @@ func fileBackedSigner(root *Root, policy *config.Signing) (signer.Signer, bool,
|
|||
// options in the root.
|
||||
func pkcs11Signer(root *Root, policy *config.Signing) (signer.Signer, bool, error) {
|
||||
module := root.Config["pkcs11-module"]
|
||||
token := root.Config["pkcs11-token"]
|
||||
label := root.Config["pkcs11-label"]
|
||||
tokenLabel := root.Config["pkcs11-token-label"]
|
||||
privateKeyLabel := root.Config["pkcs11-private-key-label"]
|
||||
userPIN := root.Config["pkcs11-user-pin"]
|
||||
certFile := root.Config["cert-file"]
|
||||
|
||||
if module == "" && token == "" && label == "" && userPIN == "" {
|
||||
if module == "" && tokenLabel == "" && privateKeyLabel == "" && userPIN == "" {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
|
|
@ -51,11 +52,11 @@ func pkcs11Signer(root *Root, policy *config.Signing) (signer.Signer, bool, erro
|
|||
return nil, true, cferr.New(cferr.PrivateKeyError, cferr.Unavailable)
|
||||
}
|
||||
|
||||
conf := pkcs11.Config{
|
||||
Module: module,
|
||||
Token: token,
|
||||
Label: label,
|
||||
PIN: userPIN,
|
||||
conf := pkcs11key.Config{
|
||||
Module: module,
|
||||
TokenLabel: tokenLabel,
|
||||
PrivateKeyLabel: privateKeyLabel,
|
||||
PIN: userPIN,
|
||||
}
|
||||
|
||||
s, err := pkcs11.New(certFile, policy, &conf)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Ben Johnson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
clock [](https://drone.io/github.com/benbjohnson/clock/latest) [](https://coveralls.io/r/benbjohnson/clock?branch=master) [](https://godoc.org/github.com/benbjohnson/clock) 
|
||||
=====
|
||||
|
||||
Clock is a small library for mocking time in Go. It provides an interface
|
||||
around the standard library's [`time`][time] package so that the application
|
||||
can use the realtime clock while tests can use the mock clock.
|
||||
|
||||
[time]: http://golang.org/pkg/time/
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### Realtime Clock
|
||||
|
||||
Your application can maintain a `Clock` variable that will allow realtime and
|
||||
mock clocks to be interchangable. For example, if you had an `Application` type:
|
||||
|
||||
```go
|
||||
import "github.com/benbjohnson/clock"
|
||||
|
||||
type Application struct {
|
||||
Clock clock.Clock
|
||||
}
|
||||
```
|
||||
|
||||
You could initialize it to use the realtime clock like this:
|
||||
|
||||
```go
|
||||
var app Application
|
||||
app.Clock = clock.New()
|
||||
...
|
||||
```
|
||||
|
||||
Then all timers and time-related functionality should be performed from the
|
||||
`Clock` variable.
|
||||
|
||||
|
||||
### Mocking time
|
||||
|
||||
In your tests, you will want to use a `Mock` clock:
|
||||
|
||||
```go
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
)
|
||||
|
||||
func TestApplication_DoSomething(t *testing.T) {
|
||||
mock := clock.NewMock()
|
||||
app := Application{Clock: mock}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Now that you've initialized your application to use the mock clock, you can
|
||||
adjust the time programmatically. The mock clock always starts from the Unix
|
||||
epoch (midnight, Jan 1, 1970 UTC).
|
||||
|
||||
|
||||
### Controlling time
|
||||
|
||||
The mock clock provides the same functions that the standard library's `time`
|
||||
package provides. For example, to find the current time, you use the `Now()`
|
||||
function:
|
||||
|
||||
```go
|
||||
mock := clock.NewMock()
|
||||
|
||||
// Find the current time.
|
||||
mock.Now().UTC() // 1970-01-01 00:00:00 +0000 UTC
|
||||
|
||||
// Move the clock forward.
|
||||
mock.Add(2 * time.Hour)
|
||||
|
||||
// Check the time again. It's 2 hours later!
|
||||
mock.Now().UTC() // 1970-01-01 02:00:00 +0000 UTC
|
||||
```
|
||||
|
||||
Timers and Tickers are also controlled by this same mock clock. They will only
|
||||
execute when the clock is moved forward:
|
||||
|
||||
```
|
||||
mock := clock.NewMock()
|
||||
count := 0
|
||||
|
||||
// Kick off a timer to increment every 1 mock second.
|
||||
go func() {
|
||||
ticker := clock.Ticker(1 * time.Second)
|
||||
for {
|
||||
<-ticker.C
|
||||
count++
|
||||
}
|
||||
}()
|
||||
runtime.Gosched()
|
||||
|
||||
// Move the clock forward 10 second.
|
||||
mock.Add(10 * time.Second)
|
||||
|
||||
// This prints 10.
|
||||
fmt.Println(count)
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,363 @@
|
|||
package clock
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Clock represents an interface to the functions in the standard library time
|
||||
// package. Two implementations are available in the clock package. The first
|
||||
// is a real-time clock which simply wraps the time package's functions. The
|
||||
// second is a mock clock which will only make forward progress when
|
||||
// programmatically adjusted.
|
||||
type Clock interface {
|
||||
After(d time.Duration) <-chan time.Time
|
||||
AfterFunc(d time.Duration, f func()) *Timer
|
||||
Now() time.Time
|
||||
Sleep(d time.Duration)
|
||||
Tick(d time.Duration) <-chan time.Time
|
||||
Ticker(d time.Duration) *Ticker
|
||||
Timer(d time.Duration) *Timer
|
||||
}
|
||||
|
||||
// New returns an instance of a real-time clock.
|
||||
func New() Clock {
|
||||
return &clock{}
|
||||
}
|
||||
|
||||
// clock implements a real-time clock by simply wrapping the time package functions.
|
||||
type clock struct{}
|
||||
|
||||
func (c *clock) After(d time.Duration) <-chan time.Time { return time.After(d) }
|
||||
|
||||
func (c *clock) AfterFunc(d time.Duration, f func()) *Timer {
|
||||
return &Timer{timer: time.AfterFunc(d, f)}
|
||||
}
|
||||
|
||||
func (c *clock) Now() time.Time { return time.Now() }
|
||||
|
||||
func (c *clock) Sleep(d time.Duration) { time.Sleep(d) }
|
||||
|
||||
func (c *clock) Tick(d time.Duration) <-chan time.Time { return time.Tick(d) }
|
||||
|
||||
func (c *clock) Ticker(d time.Duration) *Ticker {
|
||||
t := time.NewTicker(d)
|
||||
return &Ticker{C: t.C, ticker: t}
|
||||
}
|
||||
|
||||
func (c *clock) Timer(d time.Duration) *Timer {
|
||||
t := time.NewTimer(d)
|
||||
return &Timer{C: t.C, timer: t}
|
||||
}
|
||||
|
||||
// Mock represents a mock clock that only moves forward programmically.
|
||||
// It can be preferable to a real-time clock when testing time-based functionality.
|
||||
type Mock struct {
|
||||
mu sync.Mutex
|
||||
now time.Time // current time
|
||||
timers clockTimers // tickers & timers
|
||||
|
||||
calls Calls
|
||||
waiting []waiting
|
||||
callsMutex sync.Mutex
|
||||
}
|
||||
|
||||
// NewMock returns an instance of a mock clock.
|
||||
// The current time of the mock clock on initialization is the Unix epoch.
|
||||
func NewMock() *Mock {
|
||||
return &Mock{now: time.Unix(0, 0)}
|
||||
}
|
||||
|
||||
// Add moves the current time of the mock clock forward by the duration.
|
||||
// This should only be called from a single goroutine at a time.
|
||||
func (m *Mock) Add(d time.Duration) {
|
||||
// Calculate the final current time.
|
||||
t := m.now.Add(d)
|
||||
|
||||
// Continue to execute timers until there are no more before the new time.
|
||||
for {
|
||||
if !m.runNextTimer(t) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that we end with the new time.
|
||||
m.mu.Lock()
|
||||
m.now = t
|
||||
m.mu.Unlock()
|
||||
|
||||
// Give a small buffer to make sure the other goroutines get handled.
|
||||
gosched()
|
||||
}
|
||||
|
||||
// runNextTimer executes the next timer in chronological order and moves the
|
||||
// current time to the timer's next tick time. The next time is not executed if
|
||||
// it's next time if after the max time. Returns true if a timer is executed.
|
||||
func (m *Mock) runNextTimer(max time.Time) bool {
|
||||
m.mu.Lock()
|
||||
|
||||
// Sort timers by time.
|
||||
sort.Sort(m.timers)
|
||||
|
||||
// If we have no more timers then exit.
|
||||
if len(m.timers) == 0 {
|
||||
m.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
// Retrieve next timer. Exit if next tick is after new time.
|
||||
t := m.timers[0]
|
||||
if t.Next().After(max) {
|
||||
m.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
// Move "now" forward and unlock clock.
|
||||
m.now = t.Next()
|
||||
m.mu.Unlock()
|
||||
|
||||
// Execute timer.
|
||||
t.Tick(m.now)
|
||||
return true
|
||||
}
|
||||
|
||||
// After waits for the duration to elapse and then sends the current time on the returned channel.
|
||||
func (m *Mock) After(d time.Duration) <-chan time.Time {
|
||||
defer m.inc(&m.calls.After)
|
||||
return m.Timer(d).C
|
||||
}
|
||||
|
||||
// AfterFunc waits for the duration to elapse and then executes a function.
|
||||
// A Timer is returned that can be stopped.
|
||||
func (m *Mock) AfterFunc(d time.Duration, f func()) *Timer {
|
||||
defer m.inc(&m.calls.AfterFunc)
|
||||
t := m.Timer(d)
|
||||
t.C = nil
|
||||
t.fn = f
|
||||
return t
|
||||
}
|
||||
|
||||
// Now returns the current wall time on the mock clock.
|
||||
func (m *Mock) Now() time.Time {
|
||||
defer m.inc(&m.calls.Now)
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.now
|
||||
}
|
||||
|
||||
// Sleep pauses the goroutine for the given duration on the mock clock.
|
||||
// The clock must be moved forward in a separate goroutine.
|
||||
func (m *Mock) Sleep(d time.Duration) {
|
||||
defer m.inc(&m.calls.Sleep)
|
||||
<-m.After(d)
|
||||
}
|
||||
|
||||
// Tick is a convenience function for Ticker().
|
||||
// It will return a ticker channel that cannot be stopped.
|
||||
func (m *Mock) Tick(d time.Duration) <-chan time.Time {
|
||||
defer m.inc(&m.calls.Tick)
|
||||
return m.Ticker(d).C
|
||||
}
|
||||
|
||||
// Ticker creates a new instance of Ticker.
|
||||
func (m *Mock) Ticker(d time.Duration) *Ticker {
|
||||
defer m.inc(&m.calls.Ticker)
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
ch := make(chan time.Time)
|
||||
t := &Ticker{
|
||||
C: ch,
|
||||
c: ch,
|
||||
mock: m,
|
||||
d: d,
|
||||
next: m.now.Add(d),
|
||||
}
|
||||
m.timers = append(m.timers, (*internalTicker)(t))
|
||||
return t
|
||||
}
|
||||
|
||||
// Timer creates a new instance of Timer.
|
||||
func (m *Mock) Timer(d time.Duration) *Timer {
|
||||
defer m.inc(&m.calls.Timer)
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
ch := make(chan time.Time)
|
||||
t := &Timer{
|
||||
C: ch,
|
||||
c: ch,
|
||||
mock: m,
|
||||
next: m.now.Add(d),
|
||||
}
|
||||
m.timers = append(m.timers, (*internalTimer)(t))
|
||||
return t
|
||||
}
|
||||
|
||||
func (m *Mock) removeClockTimer(t clockTimer) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for i, timer := range m.timers {
|
||||
if timer == t {
|
||||
copy(m.timers[i:], m.timers[i+1:])
|
||||
m.timers[len(m.timers)-1] = nil
|
||||
m.timers = m.timers[:len(m.timers)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
sort.Sort(m.timers)
|
||||
}
|
||||
|
||||
func (m *Mock) inc(addr *uint32) {
|
||||
m.callsMutex.Lock()
|
||||
defer m.callsMutex.Unlock()
|
||||
*addr++
|
||||
var newWaiting []waiting
|
||||
for _, w := range m.waiting {
|
||||
if m.calls.atLeast(w.expected) {
|
||||
close(w.done)
|
||||
continue
|
||||
}
|
||||
newWaiting = append(newWaiting, w)
|
||||
}
|
||||
m.waiting = newWaiting
|
||||
}
|
||||
|
||||
// Wait waits for at least the relevant calls before returning. The expected
|
||||
// Calls are always over the lifetime of the Mock. Values in the Calls struct
|
||||
// are used as the minimum number of calls, this allows you to wait for only
|
||||
// the calls you care about.
|
||||
func (m *Mock) Wait(s Calls) {
|
||||
m.callsMutex.Lock()
|
||||
if m.calls.atLeast(s) {
|
||||
m.callsMutex.Unlock()
|
||||
return
|
||||
}
|
||||
done := make(chan struct{})
|
||||
m.waiting = append(m.waiting, waiting{expected: s, done: done})
|
||||
m.callsMutex.Unlock()
|
||||
<-done
|
||||
}
|
||||
|
||||
// clockTimer represents an object with an associated start time.
|
||||
type clockTimer interface {
|
||||
Next() time.Time
|
||||
Tick(time.Time)
|
||||
}
|
||||
|
||||
// clockTimers represents a list of sortable timers.
|
||||
type clockTimers []clockTimer
|
||||
|
||||
func (a clockTimers) Len() int { return len(a) }
|
||||
func (a clockTimers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a clockTimers) Less(i, j int) bool { return a[i].Next().Before(a[j].Next()) }
|
||||
|
||||
// Timer represents a single event.
|
||||
// The current time will be sent on C, unless the timer was created by AfterFunc.
|
||||
type Timer struct {
|
||||
C <-chan time.Time
|
||||
c chan time.Time
|
||||
timer *time.Timer // realtime impl, if set
|
||||
next time.Time // next tick time
|
||||
mock *Mock // mock clock, if set
|
||||
fn func() // AfterFunc function, if set
|
||||
}
|
||||
|
||||
// Stop turns off the ticker.
|
||||
func (t *Timer) Stop() {
|
||||
if t.timer != nil {
|
||||
t.timer.Stop()
|
||||
} else {
|
||||
t.mock.removeClockTimer((*internalTimer)(t))
|
||||
}
|
||||
}
|
||||
|
||||
type internalTimer Timer
|
||||
|
||||
func (t *internalTimer) Next() time.Time { return t.next }
|
||||
func (t *internalTimer) Tick(now time.Time) {
|
||||
if t.fn != nil {
|
||||
t.fn()
|
||||
} else {
|
||||
t.c <- now
|
||||
}
|
||||
t.mock.removeClockTimer((*internalTimer)(t))
|
||||
gosched()
|
||||
}
|
||||
|
||||
// Ticker holds a channel that receives "ticks" at regular intervals.
|
||||
type Ticker struct {
|
||||
C <-chan time.Time
|
||||
c chan time.Time
|
||||
ticker *time.Ticker // realtime impl, if set
|
||||
next time.Time // next tick time
|
||||
mock *Mock // mock clock, if set
|
||||
d time.Duration // time between ticks
|
||||
}
|
||||
|
||||
// Stop turns off the ticker.
|
||||
func (t *Ticker) Stop() {
|
||||
if t.ticker != nil {
|
||||
t.ticker.Stop()
|
||||
} else {
|
||||
t.mock.removeClockTimer((*internalTicker)(t))
|
||||
}
|
||||
}
|
||||
|
||||
type internalTicker Ticker
|
||||
|
||||
func (t *internalTicker) Next() time.Time { return t.next }
|
||||
func (t *internalTicker) Tick(now time.Time) {
|
||||
select {
|
||||
case t.c <- now:
|
||||
case <-time.After(1 * time.Millisecond):
|
||||
}
|
||||
t.next = now.Add(t.d)
|
||||
gosched()
|
||||
}
|
||||
|
||||
// Sleep momentarily so that other goroutines can process.
|
||||
func gosched() { runtime.Gosched() }
|
||||
|
||||
// Calls keeps track of the count of calls for each of the methods on the Clock
|
||||
// interface.
|
||||
type Calls struct {
|
||||
After uint32
|
||||
AfterFunc uint32
|
||||
Now uint32
|
||||
Sleep uint32
|
||||
Tick uint32
|
||||
Ticker uint32
|
||||
Timer uint32
|
||||
}
|
||||
|
||||
// atLeast returns true if at least the number of calls in o have been made.
|
||||
func (c Calls) atLeast(o Calls) bool {
|
||||
if c.After < o.After {
|
||||
return false
|
||||
}
|
||||
if c.AfterFunc < o.AfterFunc {
|
||||
return false
|
||||
}
|
||||
if c.Now < o.Now {
|
||||
return false
|
||||
}
|
||||
if c.Sleep < o.Sleep {
|
||||
return false
|
||||
}
|
||||
if c.Tick < o.Tick {
|
||||
return false
|
||||
}
|
||||
if c.Ticker < o.Ticker {
|
||||
return false
|
||||
}
|
||||
if c.Timer < o.Timer {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type waiting struct {
|
||||
expected Calls
|
||||
done chan struct{}
|
||||
}
|
||||
|
|
@ -0,0 +1,536 @@
|
|||
package clock_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/facebookgo/clock"
|
||||
)
|
||||
|
||||
// Ensure that the clock's After channel sends at the correct time.
|
||||
func TestClock_After(t *testing.T) {
|
||||
var ok bool
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
ok = true
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
t.Fatal("too late")
|
||||
}()
|
||||
gosched()
|
||||
|
||||
<-clock.New().After(20 * time.Millisecond)
|
||||
if !ok {
|
||||
t.Fatal("too early")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the clock's AfterFunc executes at the correct time.
|
||||
func TestClock_AfterFunc(t *testing.T) {
|
||||
var ok bool
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
ok = true
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
t.Fatal("too late")
|
||||
}()
|
||||
gosched()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
clock.New().AfterFunc(20*time.Millisecond, func() {
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
if !ok {
|
||||
t.Fatal("too early")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the clock's time matches the standary library.
|
||||
func TestClock_Now(t *testing.T) {
|
||||
a := time.Now().Round(time.Second)
|
||||
b := clock.New().Now().Round(time.Second)
|
||||
if !a.Equal(b) {
|
||||
t.Errorf("not equal: %s != %s", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the clock sleeps for the appropriate amount of time.
|
||||
func TestClock_Sleep(t *testing.T) {
|
||||
var ok bool
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
ok = true
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
t.Fatal("too late")
|
||||
}()
|
||||
gosched()
|
||||
|
||||
clock.New().Sleep(20 * time.Millisecond)
|
||||
if !ok {
|
||||
t.Fatal("too early")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the clock ticks correctly.
|
||||
func TestClock_Tick(t *testing.T) {
|
||||
var ok bool
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
ok = true
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
t.Fatal("too late")
|
||||
}()
|
||||
gosched()
|
||||
|
||||
c := clock.New().Tick(20 * time.Millisecond)
|
||||
<-c
|
||||
<-c
|
||||
if !ok {
|
||||
t.Fatal("too early")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the clock's ticker ticks correctly.
|
||||
func TestClock_Ticker(t *testing.T) {
|
||||
var ok bool
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
ok = true
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
t.Fatal("too late")
|
||||
}()
|
||||
gosched()
|
||||
|
||||
ticker := clock.New().Ticker(50 * time.Millisecond)
|
||||
<-ticker.C
|
||||
<-ticker.C
|
||||
if !ok {
|
||||
t.Fatal("too early")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the clock's ticker can stop correctly.
|
||||
func TestClock_Ticker_Stp(t *testing.T) {
|
||||
var ok bool
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
ok = true
|
||||
}()
|
||||
gosched()
|
||||
|
||||
ticker := clock.New().Ticker(20 * time.Millisecond)
|
||||
<-ticker.C
|
||||
ticker.Stop()
|
||||
select {
|
||||
case <-ticker.C:
|
||||
t.Fatal("unexpected send")
|
||||
case <-time.After(30 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the clock's timer waits correctly.
|
||||
func TestClock_Timer(t *testing.T) {
|
||||
var ok bool
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
ok = true
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
t.Fatal("too late")
|
||||
}()
|
||||
gosched()
|
||||
|
||||
timer := clock.New().Timer(20 * time.Millisecond)
|
||||
<-timer.C
|
||||
if !ok {
|
||||
t.Fatal("too early")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the clock's timer can be stopped.
|
||||
func TestClock_Timer_Stop(t *testing.T) {
|
||||
var ok bool
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
ok = true
|
||||
}()
|
||||
|
||||
timer := clock.New().Timer(20 * time.Millisecond)
|
||||
timer.Stop()
|
||||
select {
|
||||
case <-timer.C:
|
||||
t.Fatal("unexpected send")
|
||||
case <-time.After(30 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the mock's After channel sends at the correct time.
|
||||
func TestMock_After(t *testing.T) {
|
||||
var ok int32
|
||||
clock := clock.NewMock()
|
||||
|
||||
// Create a channel to execute after 10 mock seconds.
|
||||
ch := clock.After(10 * time.Second)
|
||||
go func(ch <-chan time.Time) {
|
||||
<-ch
|
||||
atomic.StoreInt32(&ok, 1)
|
||||
}(ch)
|
||||
|
||||
// Move clock forward to just before the time.
|
||||
clock.Add(9 * time.Second)
|
||||
if atomic.LoadInt32(&ok) == 1 {
|
||||
t.Fatal("too early")
|
||||
}
|
||||
|
||||
// Move clock forward to the after channel's time.
|
||||
clock.Add(1 * time.Second)
|
||||
if atomic.LoadInt32(&ok) == 0 {
|
||||
t.Fatal("too late")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the mock's AfterFunc executes at the correct time.
|
||||
func TestMock_AfterFunc(t *testing.T) {
|
||||
var ok int32
|
||||
clock := clock.NewMock()
|
||||
|
||||
// Execute function after duration.
|
||||
clock.AfterFunc(10*time.Second, func() {
|
||||
atomic.StoreInt32(&ok, 1)
|
||||
})
|
||||
|
||||
// Move clock forward to just before the time.
|
||||
clock.Add(9 * time.Second)
|
||||
if atomic.LoadInt32(&ok) == 1 {
|
||||
t.Fatal("too early")
|
||||
}
|
||||
|
||||
// Move clock forward to the after channel's time.
|
||||
clock.Add(1 * time.Second)
|
||||
if atomic.LoadInt32(&ok) == 0 {
|
||||
t.Fatal("too late")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the mock's AfterFunc doesn't execute if stopped.
|
||||
func TestMock_AfterFunc_Stop(t *testing.T) {
|
||||
// Execute function after duration.
|
||||
clock := clock.NewMock()
|
||||
timer := clock.AfterFunc(10*time.Second, func() {
|
||||
t.Fatal("unexpected function execution")
|
||||
})
|
||||
gosched()
|
||||
|
||||
// Stop timer & move clock forward.
|
||||
timer.Stop()
|
||||
clock.Add(10 * time.Second)
|
||||
gosched()
|
||||
}
|
||||
|
||||
// Ensure that the mock's current time can be changed.
|
||||
func TestMock_Now(t *testing.T) {
|
||||
clock := clock.NewMock()
|
||||
if now := clock.Now(); !now.Equal(time.Unix(0, 0)) {
|
||||
t.Fatalf("expected epoch, got: ", now)
|
||||
}
|
||||
|
||||
// Add 10 seconds and check the time.
|
||||
clock.Add(10 * time.Second)
|
||||
if now := clock.Now(); !now.Equal(time.Unix(10, 0)) {
|
||||
t.Fatalf("expected epoch, got: ", now)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the mock can sleep for the correct time.
|
||||
func TestMock_Sleep(t *testing.T) {
|
||||
var ok int32
|
||||
clock := clock.NewMock()
|
||||
|
||||
// Create a channel to execute after 10 mock seconds.
|
||||
go func() {
|
||||
clock.Sleep(10 * time.Second)
|
||||
atomic.StoreInt32(&ok, 1)
|
||||
}()
|
||||
gosched()
|
||||
|
||||
// Move clock forward to just before the sleep duration.
|
||||
clock.Add(9 * time.Second)
|
||||
if atomic.LoadInt32(&ok) == 1 {
|
||||
t.Fatal("too early")
|
||||
}
|
||||
|
||||
// Move clock forward to the after the sleep duration.
|
||||
clock.Add(1 * time.Second)
|
||||
if atomic.LoadInt32(&ok) == 0 {
|
||||
t.Fatal("too late")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the mock's Tick channel sends at the correct time.
|
||||
func TestMock_Tick(t *testing.T) {
|
||||
var n int32
|
||||
clock := clock.NewMock()
|
||||
|
||||
// Create a channel to increment every 10 seconds.
|
||||
go func() {
|
||||
tick := clock.Tick(10 * time.Second)
|
||||
for {
|
||||
<-tick
|
||||
atomic.AddInt32(&n, 1)
|
||||
}
|
||||
}()
|
||||
gosched()
|
||||
|
||||
// Move clock forward to just before the first tick.
|
||||
clock.Add(9 * time.Second)
|
||||
if atomic.LoadInt32(&n) != 0 {
|
||||
t.Fatalf("expected 0, got %d", n)
|
||||
}
|
||||
|
||||
// Move clock forward to the start of the first tick.
|
||||
clock.Add(1 * time.Second)
|
||||
if atomic.LoadInt32(&n) != 1 {
|
||||
t.Fatalf("expected 1, got %d", n)
|
||||
}
|
||||
|
||||
// Move clock forward over several ticks.
|
||||
clock.Add(30 * time.Second)
|
||||
if atomic.LoadInt32(&n) != 4 {
|
||||
t.Fatalf("expected 4, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the mock's Ticker channel sends at the correct time.
|
||||
func TestMock_Ticker(t *testing.T) {
|
||||
var n int32
|
||||
clock := clock.NewMock()
|
||||
|
||||
// Create a channel to increment every microsecond.
|
||||
go func() {
|
||||
ticker := clock.Ticker(1 * time.Microsecond)
|
||||
for {
|
||||
<-ticker.C
|
||||
atomic.AddInt32(&n, 1)
|
||||
}
|
||||
}()
|
||||
gosched()
|
||||
|
||||
// Move clock forward.
|
||||
clock.Add(10 * time.Microsecond)
|
||||
if atomic.LoadInt32(&n) != 10 {
|
||||
t.Fatalf("unexpected: %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the mock's Ticker channel won't block if not read from.
|
||||
func TestMock_Ticker_Overflow(t *testing.T) {
|
||||
clock := clock.NewMock()
|
||||
ticker := clock.Ticker(1 * time.Microsecond)
|
||||
clock.Add(10 * time.Microsecond)
|
||||
ticker.Stop()
|
||||
}
|
||||
|
||||
// Ensure that the mock's Ticker can be stopped.
|
||||
func TestMock_Ticker_Stop(t *testing.T) {
|
||||
var n int32
|
||||
clock := clock.NewMock()
|
||||
|
||||
// Create a channel to increment every second.
|
||||
ticker := clock.Ticker(1 * time.Second)
|
||||
go func() {
|
||||
for {
|
||||
<-ticker.C
|
||||
atomic.AddInt32(&n, 1)
|
||||
}
|
||||
}()
|
||||
gosched()
|
||||
|
||||
// Move clock forward.
|
||||
clock.Add(5 * time.Second)
|
||||
if atomic.LoadInt32(&n) != 5 {
|
||||
t.Fatalf("expected 5, got: %d", n)
|
||||
}
|
||||
|
||||
ticker.Stop()
|
||||
|
||||
// Move clock forward again.
|
||||
clock.Add(5 * time.Second)
|
||||
if atomic.LoadInt32(&n) != 5 {
|
||||
t.Fatalf("still expected 5, got: %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that multiple tickers can be used together.
|
||||
func TestMock_Ticker_Multi(t *testing.T) {
|
||||
var n int32
|
||||
clock := clock.NewMock()
|
||||
|
||||
go func() {
|
||||
a := clock.Ticker(1 * time.Microsecond)
|
||||
b := clock.Ticker(3 * time.Microsecond)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-a.C:
|
||||
atomic.AddInt32(&n, 1)
|
||||
case <-b.C:
|
||||
atomic.AddInt32(&n, 100)
|
||||
}
|
||||
}
|
||||
}()
|
||||
gosched()
|
||||
|
||||
// Move clock forward.
|
||||
clock.Add(10 * time.Microsecond)
|
||||
gosched()
|
||||
if atomic.LoadInt32(&n) != 310 {
|
||||
t.Fatalf("unexpected: %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleMock_After() {
|
||||
// Create a new mock clock.
|
||||
clock := clock.NewMock()
|
||||
count := 0
|
||||
|
||||
// Create a channel to execute after 10 mock seconds.
|
||||
go func() {
|
||||
<-clock.After(10 * time.Second)
|
||||
count = 100
|
||||
}()
|
||||
runtime.Gosched()
|
||||
|
||||
// Print the starting value.
|
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count)
|
||||
|
||||
// Move the clock forward 5 seconds and print the value again.
|
||||
clock.Add(5 * time.Second)
|
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count)
|
||||
|
||||
// Move the clock forward 5 seconds to the tick time and check the value.
|
||||
clock.Add(5 * time.Second)
|
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count)
|
||||
|
||||
// Output:
|
||||
// 1970-01-01 00:00:00 +0000 UTC: 0
|
||||
// 1970-01-01 00:00:05 +0000 UTC: 0
|
||||
// 1970-01-01 00:00:10 +0000 UTC: 100
|
||||
}
|
||||
|
||||
func ExampleMock_AfterFunc() {
|
||||
// Create a new mock clock.
|
||||
clock := clock.NewMock()
|
||||
count := 0
|
||||
|
||||
// Execute a function after 10 mock seconds.
|
||||
clock.AfterFunc(10*time.Second, func() {
|
||||
count = 100
|
||||
})
|
||||
runtime.Gosched()
|
||||
|
||||
// Print the starting value.
|
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count)
|
||||
|
||||
// Move the clock forward 10 seconds and print the new value.
|
||||
clock.Add(10 * time.Second)
|
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count)
|
||||
|
||||
// Output:
|
||||
// 1970-01-01 00:00:00 +0000 UTC: 0
|
||||
// 1970-01-01 00:00:10 +0000 UTC: 100
|
||||
}
|
||||
|
||||
func ExampleMock_Sleep() {
|
||||
// Create a new mock clock.
|
||||
clock := clock.NewMock()
|
||||
count := 0
|
||||
|
||||
// Execute a function after 10 mock seconds.
|
||||
go func() {
|
||||
clock.Sleep(10 * time.Second)
|
||||
count = 100
|
||||
}()
|
||||
runtime.Gosched()
|
||||
|
||||
// Print the starting value.
|
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count)
|
||||
|
||||
// Move the clock forward 10 seconds and print the new value.
|
||||
clock.Add(10 * time.Second)
|
||||
fmt.Printf("%s: %d\n", clock.Now().UTC(), count)
|
||||
|
||||
// Output:
|
||||
// 1970-01-01 00:00:00 +0000 UTC: 0
|
||||
// 1970-01-01 00:00:10 +0000 UTC: 100
|
||||
}
|
||||
|
||||
func ExampleMock_Ticker() {
|
||||
// Create a new mock clock.
|
||||
clock := clock.NewMock()
|
||||
count := 0
|
||||
|
||||
// Increment count every mock second.
|
||||
go func() {
|
||||
ticker := clock.Ticker(1 * time.Second)
|
||||
for {
|
||||
<-ticker.C
|
||||
count++
|
||||
}
|
||||
}()
|
||||
runtime.Gosched()
|
||||
|
||||
// Move the clock forward 10 seconds and print the new value.
|
||||
clock.Add(10 * time.Second)
|
||||
fmt.Printf("Count is %d after 10 seconds\n", count)
|
||||
|
||||
// Move the clock forward 5 more seconds and print the new value.
|
||||
clock.Add(5 * time.Second)
|
||||
fmt.Printf("Count is %d after 15 seconds\n", count)
|
||||
|
||||
// Output:
|
||||
// Count is 10 after 10 seconds
|
||||
// Count is 15 after 15 seconds
|
||||
}
|
||||
|
||||
func ExampleMock_Timer() {
|
||||
// Create a new mock clock.
|
||||
clock := clock.NewMock()
|
||||
count := 0
|
||||
|
||||
// Increment count after a mock second.
|
||||
go func() {
|
||||
timer := clock.Timer(1 * time.Second)
|
||||
<-timer.C
|
||||
count++
|
||||
}()
|
||||
runtime.Gosched()
|
||||
|
||||
// Move the clock forward 10 seconds and print the new value.
|
||||
clock.Add(10 * time.Second)
|
||||
fmt.Printf("Count is %d after 10 seconds\n", count)
|
||||
|
||||
// Output:
|
||||
// Count is 1 after 10 seconds
|
||||
}
|
||||
|
||||
func warn(v ...interface{}) { fmt.Fprintln(os.Stderr, v...) }
|
||||
func warnf(msg string, v ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", v...) }
|
||||
|
||||
func gosched() { time.Sleep(1 * time.Millisecond) }
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.4
|
||||
|
||||
before_install:
|
||||
- go get -v golang.org/x/tools/cmd/vet
|
||||
- go get -v golang.org/x/tools/cmd/cover
|
||||
- go get -v github.com/golang/lint/golint
|
||||
|
||||
install:
|
||||
- go install -race -v std
|
||||
- go get -race -t -v ./...
|
||||
- go install -race -v ./...
|
||||
|
||||
script:
|
||||
- go vet ./...
|
||||
- $HOME/gopath/bin/golint .
|
||||
- go test -cpu=2 -race -v ./...
|
||||
- go test -cpu=2 -covermode=atomic -coverprofile=coverage.txt ./
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
// Package httpdown provides http.ConnState enabled graceful termination of
|
||||
// http.Server.
|
||||
package httpdown
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/facebookgo/clock"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/facebookgo/stats"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultStopTimeout = time.Minute
|
||||
defaultKillTimeout = time.Minute
|
||||
)
|
||||
|
||||
// A Server allows encapsulates the process of accepting new connections and
|
||||
// serving them, and gracefully shutting down the listener without dropping
|
||||
// active connections.
|
||||
type Server interface {
|
||||
// Wait waits for the serving loop to finish. This will happen when Stop is
|
||||
// called, at which point it returns no error, or if there is an error in the
|
||||
// serving loop. You must call Wait after calling Serve or ListenAndServe.
|
||||
Wait() error
|
||||
|
||||
// Stop stops the listener. It will block until all connections have been
|
||||
// closed.
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// HTTP defines the configuration for serving a http.Server. Multiple calls to
|
||||
// Serve or ListenAndServe can be made on the same HTTP instance. The default
|
||||
// timeouts of 1 minute each result in a maximum of 2 minutes before a Stop()
|
||||
// returns.
|
||||
type HTTP struct {
|
||||
// StopTimeout is the duration before we begin force closing connections.
|
||||
// Defaults to 1 minute.
|
||||
StopTimeout time.Duration
|
||||
|
||||
// KillTimeout is the duration before which we completely give up and abort
|
||||
// even though we still have connected clients. This is useful when a large
|
||||
// number of client connections exist and closing them can take a long time.
|
||||
// Note, this is in addition to the StopTimeout. Defaults to 1 minute.
|
||||
KillTimeout time.Duration
|
||||
|
||||
// Stats is optional. If provided, it will be used to record various metrics.
|
||||
Stats stats.Client
|
||||
|
||||
// Clock allows for testing timing related functionality. Do not specify this
|
||||
// in production code.
|
||||
Clock clock.Clock
|
||||
}
|
||||
|
||||
// Serve provides the low-level API which is useful if you're creating your own
|
||||
// net.Listener.
|
||||
func (h HTTP) Serve(s *http.Server, l net.Listener) Server {
|
||||
stopTimeout := h.StopTimeout
|
||||
if stopTimeout == 0 {
|
||||
stopTimeout = defaultStopTimeout
|
||||
}
|
||||
killTimeout := h.KillTimeout
|
||||
if killTimeout == 0 {
|
||||
killTimeout = defaultKillTimeout
|
||||
}
|
||||
klock := h.Clock
|
||||
if klock == nil {
|
||||
klock = clock.New()
|
||||
}
|
||||
|
||||
ss := &server{
|
||||
stopTimeout: stopTimeout,
|
||||
killTimeout: killTimeout,
|
||||
stats: h.Stats,
|
||||
clock: klock,
|
||||
oldConnState: s.ConnState,
|
||||
listener: l,
|
||||
server: s,
|
||||
serveDone: make(chan struct{}),
|
||||
serveErr: make(chan error, 1),
|
||||
new: make(chan net.Conn),
|
||||
active: make(chan net.Conn),
|
||||
idle: make(chan net.Conn),
|
||||
closed: make(chan net.Conn),
|
||||
stop: make(chan chan struct{}),
|
||||
kill: make(chan chan struct{}),
|
||||
}
|
||||
s.ConnState = ss.connState
|
||||
go ss.manage()
|
||||
go ss.serve()
|
||||
return ss
|
||||
}
|
||||
|
||||
// ListenAndServe returns a Server for the given http.Server. It is equivalent
|
||||
// to ListenAndServe from the standard library, but returns immediately.
|
||||
// Requests will be accepted in a background goroutine. If the http.Server has
|
||||
// a non-nil TLSConfig, a TLS enabled listener will be setup.
|
||||
func (h HTTP) ListenAndServe(s *http.Server) (Server, error) {
|
||||
addr := s.Addr
|
||||
if addr == "" {
|
||||
if s.TLSConfig == nil {
|
||||
addr = ":http"
|
||||
} else {
|
||||
addr = ":https"
|
||||
}
|
||||
}
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
stats.BumpSum(h.Stats, "listen.error", 1)
|
||||
return nil, err
|
||||
}
|
||||
if s.TLSConfig != nil {
|
||||
l = tls.NewListener(l, s.TLSConfig)
|
||||
}
|
||||
return h.Serve(s, l), nil
|
||||
}
|
||||
|
||||
// server manages the serving process and allows for gracefully stopping it.
|
||||
type server struct {
|
||||
stopTimeout time.Duration
|
||||
killTimeout time.Duration
|
||||
stats stats.Client
|
||||
clock clock.Clock
|
||||
|
||||
oldConnState func(net.Conn, http.ConnState)
|
||||
server *http.Server
|
||||
serveDone chan struct{}
|
||||
serveErr chan error
|
||||
listener net.Listener
|
||||
|
||||
new chan net.Conn
|
||||
active chan net.Conn
|
||||
idle chan net.Conn
|
||||
closed chan net.Conn
|
||||
stop chan chan struct{}
|
||||
kill chan chan struct{}
|
||||
|
||||
stopOnce sync.Once
|
||||
stopErr error
|
||||
}
|
||||
|
||||
func (s *server) connState(c net.Conn, cs http.ConnState) {
|
||||
if s.oldConnState != nil {
|
||||
s.oldConnState(c, cs)
|
||||
}
|
||||
|
||||
switch cs {
|
||||
case http.StateNew:
|
||||
s.new <- c
|
||||
case http.StateActive:
|
||||
s.active <- c
|
||||
case http.StateIdle:
|
||||
s.idle <- c
|
||||
case http.StateHijacked, http.StateClosed:
|
||||
s.closed <- c
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) manage() {
|
||||
defer func() {
|
||||
close(s.new)
|
||||
close(s.active)
|
||||
close(s.idle)
|
||||
close(s.closed)
|
||||
close(s.stop)
|
||||
close(s.kill)
|
||||
}()
|
||||
|
||||
var stopDone chan struct{}
|
||||
|
||||
conns := map[net.Conn]http.ConnState{}
|
||||
var countNew, countActive, countIdle float64
|
||||
|
||||
// decConn decrements the count associated with the current state of the
|
||||
// given connection.
|
||||
decConn := func(c net.Conn) {
|
||||
switch conns[c] {
|
||||
default:
|
||||
panic(fmt.Errorf("unknown existing connection: %s", c))
|
||||
case http.StateNew:
|
||||
countNew--
|
||||
case http.StateActive:
|
||||
countActive--
|
||||
case http.StateIdle:
|
||||
countIdle--
|
||||
}
|
||||
}
|
||||
|
||||
// setup a ticker to report various values every minute. if we don't have a
|
||||
// Stats implementation provided, we Stop it so it never ticks.
|
||||
statsTicker := s.clock.Ticker(time.Minute)
|
||||
if s.stats == nil {
|
||||
statsTicker.Stop()
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-statsTicker.C:
|
||||
// we'll only get here when s.stats is not nil
|
||||
s.stats.BumpAvg("http-state.new", countNew)
|
||||
s.stats.BumpAvg("http-state.active", countActive)
|
||||
s.stats.BumpAvg("http-state.idle", countIdle)
|
||||
s.stats.BumpAvg("http-state.total", countNew+countActive+countIdle)
|
||||
case c := <-s.new:
|
||||
conns[c] = http.StateNew
|
||||
countNew++
|
||||
case c := <-s.active:
|
||||
decConn(c)
|
||||
countActive++
|
||||
|
||||
conns[c] = http.StateActive
|
||||
case c := <-s.idle:
|
||||
decConn(c)
|
||||
countIdle++
|
||||
|
||||
conns[c] = http.StateIdle
|
||||
|
||||
// if we're already stopping, close it
|
||||
if stopDone != nil {
|
||||
c.Close()
|
||||
}
|
||||
case c := <-s.closed:
|
||||
stats.BumpSum(s.stats, "conn.closed", 1)
|
||||
decConn(c)
|
||||
delete(conns, c)
|
||||
|
||||
// if we're waiting to stop and are all empty, we just closed the last
|
||||
// connection and we're done.
|
||||
if stopDone != nil && len(conns) == 0 {
|
||||
close(stopDone)
|
||||
return
|
||||
}
|
||||
case stopDone = <-s.stop:
|
||||
// if we're already all empty, we're already done
|
||||
if len(conns) == 0 {
|
||||
close(stopDone)
|
||||
return
|
||||
}
|
||||
|
||||
// close current idle connections right away
|
||||
for c, cs := range conns {
|
||||
if cs == http.StateIdle {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// continue the loop and wait for all the ConnState updates which will
|
||||
// eventually close(stopDone) and return from this goroutine.
|
||||
|
||||
case killDone := <-s.kill:
|
||||
// force close all connections
|
||||
stats.BumpSum(s.stats, "kill.conn.count", float64(len(conns)))
|
||||
for c := range conns {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
// don't block the kill.
|
||||
close(killDone)
|
||||
|
||||
// continue the loop and we wait for all the ConnState updates and will
|
||||
// return from this goroutine when we're all done. otherwise we'll try to
|
||||
// send those ConnState updates on closed channels.
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) serve() {
|
||||
stats.BumpSum(s.stats, "serve", 1)
|
||||
s.serveErr <- s.server.Serve(s.listener)
|
||||
close(s.serveDone)
|
||||
close(s.serveErr)
|
||||
}
|
||||
|
||||
func (s *server) Wait() error {
|
||||
if err := <-s.serveErr; !isUseOfClosedError(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) Stop() error {
|
||||
s.stopOnce.Do(func() {
|
||||
defer stats.BumpTime(s.stats, "stop.time").End()
|
||||
stats.BumpSum(s.stats, "stop", 1)
|
||||
|
||||
// first disable keep-alive for new connections
|
||||
s.server.SetKeepAlivesEnabled(false)
|
||||
|
||||
// then close the listener so new connections can't connect come thru
|
||||
closeErr := s.listener.Close()
|
||||
<-s.serveDone
|
||||
|
||||
// then trigger the background goroutine to stop and wait for it
|
||||
stopDone := make(chan struct{})
|
||||
s.stop <- stopDone
|
||||
|
||||
// wait for stop
|
||||
select {
|
||||
case <-stopDone:
|
||||
case <-s.clock.After(s.stopTimeout):
|
||||
defer stats.BumpTime(s.stats, "kill.time").End()
|
||||
stats.BumpSum(s.stats, "kill", 1)
|
||||
|
||||
// stop timed out, wait for kill
|
||||
killDone := make(chan struct{})
|
||||
s.kill <- killDone
|
||||
select {
|
||||
case <-killDone:
|
||||
case <-s.clock.After(s.killTimeout):
|
||||
// kill timed out, give up
|
||||
stats.BumpSum(s.stats, "kill.timeout", 1)
|
||||
}
|
||||
}
|
||||
|
||||
if closeErr != nil && !isUseOfClosedError(closeErr) {
|
||||
stats.BumpSum(s.stats, "listener.close.error", 1)
|
||||
s.stopErr = closeErr
|
||||
}
|
||||
})
|
||||
return s.stopErr
|
||||
}
|
||||
|
||||
func isUseOfClosedError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if opErr, ok := err.(*net.OpError); ok {
|
||||
err = opErr.Err
|
||||
}
|
||||
return err.Error() == "use of closed network connection"
|
||||
}
|
||||
|
||||
// ListenAndServe is a convenience function to serve and wait for a SIGTERM
|
||||
// or SIGINT before shutting down.
|
||||
func ListenAndServe(s *http.Server, hd *HTTP) error {
|
||||
if hd == nil {
|
||||
hd = &HTTP{}
|
||||
}
|
||||
hs, err := hd.ListenAndServe(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("serving on http://%s/ with pid %d\n", s.Addr, os.Getpid())
|
||||
|
||||
waiterr := make(chan error, 1)
|
||||
go func() {
|
||||
defer close(waiterr)
|
||||
waiterr <- hs.Wait()
|
||||
}()
|
||||
|
||||
signals := make(chan os.Signal, 10)
|
||||
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
|
||||
|
||||
select {
|
||||
case err := <-waiterr:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case s := <-signals:
|
||||
signal.Stop(signals)
|
||||
log.Printf("signal received: %s\n", s)
|
||||
if err := hs.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := <-waiterr; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Println("exiting")
|
||||
return nil
|
||||
}
|
||||
43
Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown_example/main.go
generated
vendored
Normal file
43
Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown_example/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/facebookgo/httpdown"
|
||||
)
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
duration, err := time.ParseDuration(r.FormValue("duration"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 400)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "going to sleep %s with pid %d\n", duration, os.Getpid())
|
||||
w.(http.Flusher).Flush()
|
||||
time.Sleep(duration)
|
||||
fmt.Fprintf(w, "slept %s with pid %d\n", duration, os.Getpid())
|
||||
}
|
||||
|
||||
func main() {
|
||||
server := &http.Server{
|
||||
Addr: "127.0.0.1:8080",
|
||||
Handler: http.HandlerFunc(handler),
|
||||
}
|
||||
hd := &httpdown.HTTP{
|
||||
StopTimeout: 10 * time.Second,
|
||||
KillTimeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
flag.StringVar(&server.Addr, "addr", server.Addr, "http address")
|
||||
flag.DurationVar(&hd.StopTimeout, "stop-timeout", hd.StopTimeout, "stop timeout")
|
||||
flag.DurationVar(&hd.KillTimeout, "kill-timeout", hd.KillTimeout, "kill timeout")
|
||||
flag.Parse()
|
||||
|
||||
if err := httpdown.ListenAndServe(server, hd); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
677
Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown_test.go
generated
vendored
Normal file
677
Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,677 @@
|
|||
package httpdown_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
"github.com/facebookgo/freeport"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/facebookgo/clock"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/facebookgo/httpdown"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/facebookgo/stats"
|
||||
)
|
||||
|
||||
type onCloseListener struct {
|
||||
net.Listener
|
||||
mutex sync.Mutex
|
||||
onClose chan struct{}
|
||||
}
|
||||
|
||||
func (o *onCloseListener) Close() error {
|
||||
// Listener is closed twice, once by Grace, and once by the http library, so
|
||||
// we guard against a double close of the chan.
|
||||
defer func() {
|
||||
o.mutex.Lock()
|
||||
defer o.mutex.Unlock()
|
||||
if o.onClose != nil {
|
||||
close(o.onClose)
|
||||
o.onClose = nil
|
||||
}
|
||||
}()
|
||||
return o.Listener.Close()
|
||||
}
|
||||
|
||||
func NewOnCloseListener(l net.Listener) (net.Listener, chan struct{}) {
|
||||
c := make(chan struct{})
|
||||
return &onCloseListener{Listener: l, onClose: c}, c
|
||||
}
|
||||
|
||||
type closeErrListener struct {
|
||||
net.Listener
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *closeErrListener) Close() error {
|
||||
c.Listener.Close()
|
||||
return c.err
|
||||
}
|
||||
|
||||
type acceptErrListener struct {
|
||||
net.Listener
|
||||
err chan error
|
||||
}
|
||||
|
||||
func (c *acceptErrListener) Accept() (net.Conn, error) {
|
||||
return nil, <-c.err
|
||||
}
|
||||
|
||||
type closeErrConn struct {
|
||||
net.Conn
|
||||
unblockClose chan chan struct{}
|
||||
}
|
||||
|
||||
func (c *closeErrConn) Close() error {
|
||||
ch := <-c.unblockClose
|
||||
|
||||
// Close gets called multiple times, but only the first one gets this ch
|
||||
if ch != nil {
|
||||
defer close(ch)
|
||||
}
|
||||
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
type closeErrConnListener struct {
|
||||
net.Listener
|
||||
unblockClose chan chan struct{}
|
||||
}
|
||||
|
||||
func (l *closeErrConnListener) Accept() (net.Conn, error) {
|
||||
c, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
return &closeErrConn{Conn: c, unblockClose: l.unblockClose}, nil
|
||||
}
|
||||
|
||||
func TestHTTPStopWithNoRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
ensure.Nil(t, err)
|
||||
|
||||
statsDone := make(chan struct{}, 2)
|
||||
hc := &stats.HookClient{
|
||||
BumpSumHook: func(key string, val float64) {
|
||||
if key == "serve" && val == 1 {
|
||||
statsDone <- struct{}{}
|
||||
}
|
||||
if key == "stop" && val == 1 {
|
||||
statsDone <- struct{}{}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
server := &http.Server{}
|
||||
down := &httpdown.HTTP{Stats: hc}
|
||||
s := down.Serve(server, listener)
|
||||
ensure.Nil(t, s.Stop())
|
||||
<-statsDone
|
||||
<-statsDone
|
||||
}
|
||||
|
||||
func TestHTTPStopWithFinishedRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
hello := []byte("hello")
|
||||
fin := make(chan struct{})
|
||||
okHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
defer close(fin)
|
||||
w.Write(hello)
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
ensure.Nil(t, err)
|
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)}
|
||||
transport := &http.Transport{}
|
||||
client := &http.Client{Transport: transport}
|
||||
down := &httpdown.HTTP{}
|
||||
s := down.Serve(server, listener)
|
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String()))
|
||||
ensure.Nil(t, err)
|
||||
actualBody, err := ioutil.ReadAll(res.Body)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, actualBody, hello)
|
||||
ensure.Nil(t, res.Body.Close())
|
||||
|
||||
// At this point the request is finished, and the connection should be alive
|
||||
// but idle (because we have keep alive enabled by default in our Transport).
|
||||
ensure.Nil(t, s.Stop())
|
||||
<-fin
|
||||
|
||||
ensure.Nil(t, s.Wait())
|
||||
}
|
||||
|
||||
func TestHTTPStopWithActiveRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
const count = 10000
|
||||
hello := []byte("hello")
|
||||
finOkHandler := make(chan struct{})
|
||||
okHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
defer close(finOkHandler)
|
||||
w.WriteHeader(200)
|
||||
for i := 0; i < count; i++ {
|
||||
w.Write(hello)
|
||||
}
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
ensure.Nil(t, err)
|
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)}
|
||||
transport := &http.Transport{}
|
||||
client := &http.Client{Transport: transport}
|
||||
down := &httpdown.HTTP{}
|
||||
s := down.Serve(server, listener)
|
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String()))
|
||||
ensure.Nil(t, err)
|
||||
|
||||
finStop := make(chan struct{})
|
||||
go func() {
|
||||
defer close(finStop)
|
||||
ensure.Nil(t, s.Stop())
|
||||
}()
|
||||
|
||||
actualBody, err := ioutil.ReadAll(res.Body)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, actualBody, bytes.Repeat(hello, count))
|
||||
ensure.Nil(t, res.Body.Close())
|
||||
<-finOkHandler
|
||||
<-finStop
|
||||
}
|
||||
|
||||
func TestNewRequestAfterStop(t *testing.T) {
|
||||
t.Parallel()
|
||||
const count = 10000
|
||||
hello := []byte("hello")
|
||||
finOkHandler := make(chan struct{})
|
||||
unblockOkHandler := make(chan struct{})
|
||||
okHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
defer close(finOkHandler)
|
||||
w.WriteHeader(200)
|
||||
const diff = 500
|
||||
for i := 0; i < count-diff; i++ {
|
||||
w.Write(hello)
|
||||
}
|
||||
<-unblockOkHandler
|
||||
for i := 0; i < diff; i++ {
|
||||
w.Write(hello)
|
||||
}
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
listener, onClose := NewOnCloseListener(listener)
|
||||
ensure.Nil(t, err)
|
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)}
|
||||
transport := &http.Transport{}
|
||||
client := &http.Client{Transport: transport}
|
||||
down := &httpdown.HTTP{}
|
||||
s := down.Serve(server, listener)
|
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String()))
|
||||
ensure.Nil(t, err)
|
||||
|
||||
finStop := make(chan struct{})
|
||||
go func() {
|
||||
defer close(finStop)
|
||||
ensure.Nil(t, s.Stop())
|
||||
}()
|
||||
|
||||
// Wait until the listener is closed.
|
||||
<-onClose
|
||||
|
||||
// Now the next request should not be able to connect as the listener is
|
||||
// now closed.
|
||||
_, err = client.Get(fmt.Sprintf("http://%s/", listener.Addr().String()))
|
||||
|
||||
// We should just get "connection refused" here, but sometimes, very rarely,
|
||||
// we get a "connection reset" instead. Unclear why this happens.
|
||||
ensure.Err(t, err, regexp.MustCompile("(connection refused|connection reset by peer)$"))
|
||||
|
||||
// Unblock the handler and ensure we finish writing the rest of the body
|
||||
// successfully.
|
||||
close(unblockOkHandler)
|
||||
actualBody, err := ioutil.ReadAll(res.Body)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, actualBody, bytes.Repeat(hello, count))
|
||||
ensure.Nil(t, res.Body.Close())
|
||||
<-finOkHandler
|
||||
<-finStop
|
||||
}
|
||||
|
||||
func TestHTTPListenerCloseError(t *testing.T) {
|
||||
t.Parallel()
|
||||
expectedError := errors.New("foo")
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
listener = &closeErrListener{Listener: listener, err: expectedError}
|
||||
ensure.Nil(t, err)
|
||||
server := &http.Server{}
|
||||
down := &httpdown.HTTP{}
|
||||
s := down.Serve(server, listener)
|
||||
ensure.DeepEqual(t, s.Stop(), expectedError)
|
||||
}
|
||||
|
||||
func TestHTTPServeError(t *testing.T) {
|
||||
t.Parallel()
|
||||
expectedError := errors.New("foo")
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
errChan := make(chan error)
|
||||
listener = &acceptErrListener{Listener: listener, err: errChan}
|
||||
ensure.Nil(t, err)
|
||||
server := &http.Server{}
|
||||
down := &httpdown.HTTP{}
|
||||
s := down.Serve(server, listener)
|
||||
errChan <- expectedError
|
||||
ensure.DeepEqual(t, s.Wait(), expectedError)
|
||||
ensure.Nil(t, s.Stop())
|
||||
}
|
||||
|
||||
func TestHTTPWithinStopTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
hello := []byte("hello")
|
||||
finOkHandler := make(chan struct{})
|
||||
okHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
defer close(finOkHandler)
|
||||
w.WriteHeader(200)
|
||||
w.Write(hello)
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
ensure.Nil(t, err)
|
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)}
|
||||
transport := &http.Transport{}
|
||||
client := &http.Client{Transport: transport}
|
||||
down := &httpdown.HTTP{StopTimeout: time.Minute}
|
||||
s := down.Serve(server, listener)
|
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String()))
|
||||
ensure.Nil(t, err)
|
||||
|
||||
finStop := make(chan struct{})
|
||||
go func() {
|
||||
defer close(finStop)
|
||||
ensure.Nil(t, s.Stop())
|
||||
}()
|
||||
|
||||
actualBody, err := ioutil.ReadAll(res.Body)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, actualBody, hello)
|
||||
ensure.Nil(t, res.Body.Close())
|
||||
<-finOkHandler
|
||||
<-finStop
|
||||
}
|
||||
|
||||
func TestHTTPStopTimeoutMissed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
klock := clock.NewMock()
|
||||
|
||||
const count = 10000
|
||||
hello := []byte("hello")
|
||||
finOkHandler := make(chan struct{})
|
||||
unblockOkHandler := make(chan struct{})
|
||||
okHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
defer close(finOkHandler)
|
||||
w.Header().Set("Content-Length", fmt.Sprint(len(hello)*count))
|
||||
w.WriteHeader(200)
|
||||
for i := 0; i < count/2; i++ {
|
||||
w.Write(hello)
|
||||
}
|
||||
<-unblockOkHandler
|
||||
for i := 0; i < count/2; i++ {
|
||||
w.Write(hello)
|
||||
}
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
ensure.Nil(t, err)
|
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)}
|
||||
transport := &http.Transport{}
|
||||
client := &http.Client{Transport: transport}
|
||||
down := &httpdown.HTTP{
|
||||
StopTimeout: time.Minute,
|
||||
Clock: klock,
|
||||
}
|
||||
s := down.Serve(server, listener)
|
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String()))
|
||||
ensure.Nil(t, err)
|
||||
|
||||
finStop := make(chan struct{})
|
||||
go func() {
|
||||
defer close(finStop)
|
||||
ensure.Nil(t, s.Stop())
|
||||
}()
|
||||
|
||||
klock.Wait(clock.Calls{After: 1}) // wait for Stop to call After
|
||||
klock.Add(down.StopTimeout)
|
||||
|
||||
_, err = ioutil.ReadAll(res.Body)
|
||||
ensure.Err(t, err, regexp.MustCompile("^unexpected EOF$"))
|
||||
ensure.Nil(t, res.Body.Close())
|
||||
close(unblockOkHandler)
|
||||
<-finOkHandler
|
||||
<-finStop
|
||||
}
|
||||
|
||||
func TestHTTPKillTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
klock := clock.NewMock()
|
||||
|
||||
statsDone := make(chan struct{}, 1)
|
||||
hc := &stats.HookClient{
|
||||
BumpSumHook: func(key string, val float64) {
|
||||
if key == "kill" && val == 1 {
|
||||
statsDone <- struct{}{}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const count = 10000
|
||||
hello := []byte("hello")
|
||||
finOkHandler := make(chan struct{})
|
||||
unblockOkHandler := make(chan struct{})
|
||||
okHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
defer close(finOkHandler)
|
||||
w.Header().Set("Content-Length", fmt.Sprint(len(hello)*count))
|
||||
w.WriteHeader(200)
|
||||
for i := 0; i < count/2; i++ {
|
||||
w.Write(hello)
|
||||
}
|
||||
<-unblockOkHandler
|
||||
for i := 0; i < count/2; i++ {
|
||||
w.Write(hello)
|
||||
}
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
ensure.Nil(t, err)
|
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)}
|
||||
transport := &http.Transport{}
|
||||
client := &http.Client{Transport: transport}
|
||||
down := &httpdown.HTTP{
|
||||
StopTimeout: time.Minute,
|
||||
KillTimeout: time.Minute,
|
||||
Stats: hc,
|
||||
Clock: klock,
|
||||
}
|
||||
s := down.Serve(server, listener)
|
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String()))
|
||||
ensure.Nil(t, err)
|
||||
|
||||
finStop := make(chan struct{})
|
||||
go func() {
|
||||
defer close(finStop)
|
||||
ensure.Nil(t, s.Stop())
|
||||
}()
|
||||
|
||||
klock.Wait(clock.Calls{After: 1}) // wait for Stop to call After
|
||||
klock.Add(down.StopTimeout)
|
||||
|
||||
_, err = ioutil.ReadAll(res.Body)
|
||||
ensure.Err(t, err, regexp.MustCompile("^unexpected EOF$"))
|
||||
ensure.Nil(t, res.Body.Close())
|
||||
close(unblockOkHandler)
|
||||
<-finOkHandler
|
||||
<-finStop
|
||||
<-statsDone
|
||||
}
|
||||
|
||||
func TestHTTPKillTimeoutMissed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
klock := clock.NewMock()
|
||||
|
||||
statsDone := make(chan struct{}, 1)
|
||||
hc := &stats.HookClient{
|
||||
BumpSumHook: func(key string, val float64) {
|
||||
if key == "kill.timeout" && val == 1 {
|
||||
statsDone <- struct{}{}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const count = 10000
|
||||
hello := []byte("hello")
|
||||
finOkHandler := make(chan struct{})
|
||||
unblockOkHandler := make(chan struct{})
|
||||
okHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
defer close(finOkHandler)
|
||||
w.Header().Set("Content-Length", fmt.Sprint(len(hello)*count))
|
||||
w.WriteHeader(200)
|
||||
for i := 0; i < count/2; i++ {
|
||||
w.Write(hello)
|
||||
}
|
||||
<-unblockOkHandler
|
||||
for i := 0; i < count/2; i++ {
|
||||
w.Write(hello)
|
||||
}
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
ensure.Nil(t, err)
|
||||
unblockConnClose := make(chan chan struct{}, 1)
|
||||
listener = &closeErrConnListener{
|
||||
Listener: listener,
|
||||
unblockClose: unblockConnClose,
|
||||
}
|
||||
|
||||
server := &http.Server{Handler: http.HandlerFunc(okHandler)}
|
||||
transport := &http.Transport{}
|
||||
client := &http.Client{Transport: transport}
|
||||
down := &httpdown.HTTP{
|
||||
StopTimeout: time.Minute,
|
||||
KillTimeout: time.Minute,
|
||||
Stats: hc,
|
||||
Clock: klock,
|
||||
}
|
||||
s := down.Serve(server, listener)
|
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String()))
|
||||
ensure.Nil(t, err)
|
||||
|
||||
// Start the Stop process.
|
||||
finStop := make(chan struct{})
|
||||
go func() {
|
||||
defer close(finStop)
|
||||
ensure.Nil(t, s.Stop())
|
||||
}()
|
||||
|
||||
klock.Wait(clock.Calls{After: 1}) // wait for Stop to call After
|
||||
klock.Add(down.StopTimeout) // trigger stop timeout
|
||||
klock.Wait(clock.Calls{After: 2}) // wait for Kill to call After
|
||||
klock.Add(down.KillTimeout) // trigger kill timeout
|
||||
|
||||
// We hit both the StopTimeout & the KillTimeout.
|
||||
<-finStop
|
||||
|
||||
// Then we unblock the Close, so we get an unexpected EOF since we close
|
||||
// before we finish writing the response.
|
||||
connCloseDone := make(chan struct{})
|
||||
unblockConnClose <- connCloseDone
|
||||
<-connCloseDone
|
||||
close(unblockConnClose)
|
||||
|
||||
// Then we unblock the handler which tries to write the rest of the data.
|
||||
close(unblockOkHandler)
|
||||
|
||||
_, err = ioutil.ReadAll(res.Body)
|
||||
ensure.Err(t, err, regexp.MustCompile("^unexpected EOF$"))
|
||||
ensure.Nil(t, res.Body.Close())
|
||||
<-finOkHandler
|
||||
<-statsDone
|
||||
}
|
||||
|
||||
func TestDoubleStop(t *testing.T) {
|
||||
t.Parallel()
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
ensure.Nil(t, err)
|
||||
server := &http.Server{}
|
||||
down := &httpdown.HTTP{}
|
||||
s := down.Serve(server, listener)
|
||||
ensure.Nil(t, s.Stop())
|
||||
ensure.Nil(t, s.Stop())
|
||||
}
|
||||
|
||||
func TestExistingConnState(t *testing.T) {
|
||||
t.Parallel()
|
||||
hello := []byte("hello")
|
||||
fin := make(chan struct{})
|
||||
okHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
defer close(fin)
|
||||
w.Write(hello)
|
||||
}
|
||||
|
||||
var called int32
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
ensure.Nil(t, err)
|
||||
server := &http.Server{
|
||||
Handler: http.HandlerFunc(okHandler),
|
||||
ConnState: func(c net.Conn, s http.ConnState) {
|
||||
atomic.AddInt32(&called, 1)
|
||||
},
|
||||
}
|
||||
transport := &http.Transport{}
|
||||
client := &http.Client{Transport: transport}
|
||||
down := &httpdown.HTTP{}
|
||||
s := down.Serve(server, listener)
|
||||
res, err := client.Get(fmt.Sprintf("http://%s/", listener.Addr().String()))
|
||||
ensure.Nil(t, err)
|
||||
actualBody, err := ioutil.ReadAll(res.Body)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, actualBody, hello)
|
||||
ensure.Nil(t, res.Body.Close())
|
||||
|
||||
ensure.Nil(t, s.Stop())
|
||||
<-fin
|
||||
|
||||
ensure.True(t, atomic.LoadInt32(&called) > 0)
|
||||
}
|
||||
|
||||
func TestHTTPDefaultListenError(t *testing.T) {
|
||||
if os.Getuid() == 0 {
|
||||
t.Skip("cant run this test as root")
|
||||
}
|
||||
|
||||
statsDone := make(chan struct{}, 1)
|
||||
hc := &stats.HookClient{
|
||||
BumpSumHook: func(key string, val float64) {
|
||||
if key == "listen.error" && val == 1 {
|
||||
statsDone <- struct{}{}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
down := &httpdown.HTTP{Stats: hc}
|
||||
_, err := down.ListenAndServe(&http.Server{})
|
||||
ensure.Err(t, err, regexp.MustCompile("listen tcp :80: bind: permission denied"))
|
||||
<-statsDone
|
||||
}
|
||||
|
||||
func TestHTTPSDefaultListenError(t *testing.T) {
|
||||
if os.Getuid() == 0 {
|
||||
t.Skip("cant run this test as root")
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
|
||||
if err != nil {
|
||||
t.Fatalf("error loading cert: %v", err)
|
||||
}
|
||||
|
||||
down := &httpdown.HTTP{}
|
||||
_, err = down.ListenAndServe(&http.Server{
|
||||
TLSConfig: &tls.Config{
|
||||
NextProtos: []string{"http/1.1"},
|
||||
Certificates: []tls.Certificate{cert},
|
||||
},
|
||||
})
|
||||
ensure.Err(t, err, regexp.MustCompile("listen tcp :443: bind: permission denied"))
|
||||
}
|
||||
|
||||
func TestTLS(t *testing.T) {
|
||||
t.Parallel()
|
||||
port, err := freeport.Get()
|
||||
ensure.Nil(t, err)
|
||||
|
||||
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
|
||||
if err != nil {
|
||||
t.Fatalf("error loading cert: %v", err)
|
||||
}
|
||||
const count = 10000
|
||||
hello := []byte("hello")
|
||||
finOkHandler := make(chan struct{})
|
||||
okHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
defer close(finOkHandler)
|
||||
w.WriteHeader(200)
|
||||
for i := 0; i < count; i++ {
|
||||
w.Write(hello)
|
||||
}
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf("0.0.0.0:%d", port),
|
||||
Handler: http.HandlerFunc(okHandler),
|
||||
TLSConfig: &tls.Config{
|
||||
NextProtos: []string{"http/1.1"},
|
||||
Certificates: []tls.Certificate{cert},
|
||||
},
|
||||
}
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
client := &http.Client{Transport: transport}
|
||||
down := &httpdown.HTTP{}
|
||||
s, err := down.ListenAndServe(server)
|
||||
ensure.Nil(t, err)
|
||||
res, err := client.Get(fmt.Sprintf("https://%s/", server.Addr))
|
||||
ensure.Nil(t, err)
|
||||
|
||||
finStop := make(chan struct{})
|
||||
go func() {
|
||||
defer close(finStop)
|
||||
ensure.Nil(t, s.Stop())
|
||||
}()
|
||||
|
||||
actualBody, err := ioutil.ReadAll(res.Body)
|
||||
ensure.Nil(t, err)
|
||||
ensure.DeepEqual(t, actualBody, bytes.Repeat(hello, count))
|
||||
ensure.Nil(t, res.Body.Close())
|
||||
<-finOkHandler
|
||||
<-finStop
|
||||
}
|
||||
|
||||
// localhostCert is a PEM-encoded TLS cert with SAN IPs
|
||||
// "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
|
||||
// of ASN.1 time).
|
||||
// generated from src/pkg/crypto/tls:
|
||||
// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
||||
var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
|
||||
bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj
|
||||
bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBALyCfqwwip8BvTKgVKGdmjZTU8DD
|
||||
ndR+WALmFPIRqn89bOU3s30olKiqYEju/SFoEvMyFRT/TWEhXHDaufThqaMCAwEA
|
||||
AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
|
||||
EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA
|
||||
AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAr/09uy108p51rheIOSnz4zgduyTl
|
||||
M+4AmRo8/U1twEZLgfAGG/GZjREv2y4mCEUIM3HebCAqlA5jpRg76Rf8jw==
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
// localhostKey is the private key for localhostCert.
|
||||
var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIBOQIBAAJBALyCfqwwip8BvTKgVKGdmjZTU8DDndR+WALmFPIRqn89bOU3s30o
|
||||
lKiqYEju/SFoEvMyFRT/TWEhXHDaufThqaMCAwEAAQJAPXuWUxTV8XyAt8VhNQER
|
||||
LgzJcUKb9JVsoS1nwXgPksXnPDKnL9ax8VERrdNr+nZbj2Q9cDSXBUovfdtehcdP
|
||||
qQIhAO48ZsPylbTrmtjDEKiHT2Ik04rLotZYS2U873J6I7WlAiEAypDjYxXyafv/
|
||||
Yo1pm9onwcetQKMW8CS3AjuV9Axzj6cCIEx2Il19fEMG4zny0WPlmbrcKvD/DpJQ
|
||||
4FHrzsYlIVTpAiAas7S1uAvneqd0l02HlN9OxQKKlbUNXNme+rnOnOGS2wIgS0jW
|
||||
zl1jvrOSJeP1PpAHohWz6LOhEr8uvltWkN6x3vE=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
BSD License
|
||||
|
||||
For httpdown software
|
||||
|
||||
Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
Additional Grant of Patent Rights Version 2
|
||||
|
||||
"Software" means the httpdown software distributed by Facebook, Inc.
|
||||
|
||||
Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software
|
||||
("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable
|
||||
(subject to the termination provision below) license under any Necessary
|
||||
Claims, to make, have made, use, sell, offer to sell, import, and otherwise
|
||||
transfer the Software. For avoidance of doubt, no license is granted under
|
||||
Facebook’s rights in any patent claims that are infringed by (i) modifications
|
||||
to the Software made by you or any third party or (ii) the Software in
|
||||
combination with any software or other technology.
|
||||
|
||||
The license granted hereunder will terminate, automatically and without notice,
|
||||
if you (or any of your subsidiaries, corporate affiliates or agents) initiate
|
||||
directly or indirectly, or take a direct financial interest in, any Patent
|
||||
Assertion: (i) against Facebook or any of its subsidiaries or corporate
|
||||
affiliates, (ii) against any party if such Patent Assertion arises in whole or
|
||||
in part from any software, technology, product or service of Facebook or any of
|
||||
its subsidiaries or corporate affiliates, or (iii) against any party relating
|
||||
to the Software. Notwithstanding the foregoing, if Facebook or any of its
|
||||
subsidiaries or corporate affiliates files a lawsuit alleging patent
|
||||
infringement against you in the first instance, and you respond by filing a
|
||||
patent infringement counterclaim in that lawsuit against that party that is
|
||||
unrelated to the Software, the license granted hereunder will not terminate
|
||||
under section (i) of this paragraph due to such counterclaim.
|
||||
|
||||
A "Necessary Claim" is a claim of a patent owned by Facebook that is
|
||||
necessarily infringed by the Software standing alone.
|
||||
|
||||
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
|
||||
or contributory infringement or inducement to infringe any patent, including a
|
||||
cross-claim or counterclaim.
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
httpdown [](https://travis-ci.org/facebookgo/httpdown)
|
||||
========
|
||||
|
||||
Documentation: https://godoc.org/github.com/facebookgo/httpdown
|
||||
|
||||
Package httpdown provides a library that makes it easy to build a HTTP server
|
||||
that can be shutdown gracefully (that is, without dropping any connections).
|
||||
|
||||
If you want graceful restart and not just graceful shutdown, look at the
|
||||
[grace](https://github.com/facebookgo/grace) package which uses this package
|
||||
underneath but also provides graceful restart.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Demo HTTP Server with graceful termination:
|
||||
https://github.com/facebookgo/httpdown/blob/master/httpdown_example/main.go
|
||||
|
||||
1. Install the demo application
|
||||
|
||||
go get github.com/facebookgo/httpdown/httpdown_example
|
||||
|
||||
1. Start it in the first terminal
|
||||
|
||||
httpdown_example
|
||||
|
||||
This will output something like:
|
||||
|
||||
2014/11/18 21:57:50 serving on http://127.0.0.1:8080/ with pid 17
|
||||
|
||||
1. In a second terminal start a slow HTTP request
|
||||
|
||||
curl 'http://localhost:8080/?duration=20s'
|
||||
|
||||
1. In a third terminal trigger a graceful shutdown (using the pid from your output):
|
||||
|
||||
kill -TERM 17
|
||||
|
||||
This will demonstrate that the slow request was served before the server was
|
||||
shutdown. You could also have used `Ctrl-C` instead of `kill` as the example
|
||||
application triggers graceful shutdown on TERM or INT signals.
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
before_install:
|
||||
- go get -v code.google.com/p/go.tools/cmd/vet
|
||||
- go get -v github.com/golang/lint/golint
|
||||
- go get -v code.google.com/p/go.tools/cmd/cover
|
||||
|
||||
install:
|
||||
- go install -race -v std
|
||||
- go get -race -t -v ./...
|
||||
- go install -race -v ./...
|
||||
|
||||
script:
|
||||
- go vet ./...
|
||||
- $HOME/gopath/bin/golint .
|
||||
- go test -cpu=2 -race -v ./...
|
||||
- go test -cpu=2 -covermode=atomic ./...
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
BSD License
|
||||
|
||||
For stats software
|
||||
|
||||
Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
Additional Grant of Patent Rights Version 2
|
||||
|
||||
"Software" means the stats software distributed by Facebook, Inc.
|
||||
|
||||
Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software
|
||||
("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable
|
||||
(subject to the termination provision below) license under any Necessary
|
||||
Claims, to make, have made, use, sell, offer to sell, import, and otherwise
|
||||
transfer the Software. For avoidance of doubt, no license is granted under
|
||||
Facebook’s rights in any patent claims that are infringed by (i) modifications
|
||||
to the Software made by you or any third party or (ii) the Software in
|
||||
combination with any software or other technology.
|
||||
|
||||
The license granted hereunder will terminate, automatically and without notice,
|
||||
if you (or any of your subsidiaries, corporate affiliates or agents) initiate
|
||||
directly or indirectly, or take a direct financial interest in, any Patent
|
||||
Assertion: (i) against Facebook or any of its subsidiaries or corporate
|
||||
affiliates, (ii) against any party if such Patent Assertion arises in whole or
|
||||
in part from any software, technology, product or service of Facebook or any of
|
||||
its subsidiaries or corporate affiliates, or (iii) against any party relating
|
||||
to the Software. Notwithstanding the foregoing, if Facebook or any of its
|
||||
subsidiaries or corporate affiliates files a lawsuit alleging patent
|
||||
infringement against you in the first instance, and you respond by filing a
|
||||
patent infringement counterclaim in that lawsuit against that party that is
|
||||
unrelated to the Software, the license granted hereunder will not terminate
|
||||
under section (i) of this paragraph due to such counterclaim.
|
||||
|
||||
A "Necessary Claim" is a claim of a patent owned by Facebook that is
|
||||
necessarily infringed by the Software standing alone.
|
||||
|
||||
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
|
||||
or contributory infringement or inducement to infringe any patent, including a
|
||||
cross-claim or counterclaim.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
stats [](https://travis-ci.org/facebookgo/stats)
|
||||
=====
|
||||
|
||||
Documentation: https://godoc.org/github.com/facebookgo/stats
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
// Package stats defines a lightweight interface for collecting statistics. It
|
||||
// doesn't provide an implementation, just the shared interface.
|
||||
package stats
|
||||
|
||||
// Client provides methods to collection statistics.
|
||||
type Client interface {
|
||||
// BumpAvg bumps the average for the given key.
|
||||
BumpAvg(key string, val float64)
|
||||
|
||||
// BumpSum bumps the sum for the given key.
|
||||
BumpSum(key string, val float64)
|
||||
|
||||
// BumpHistogram bumps the histogram for the given key.
|
||||
BumpHistogram(key string, val float64)
|
||||
|
||||
// BumpTime is a special version of BumpHistogram which is specialized for
|
||||
// timers. Calling it starts the timer, and it returns a value on which End()
|
||||
// can be called to indicate finishing the timer. A convenient way of
|
||||
// recording the duration of a function is calling it like such at the top of
|
||||
// the function:
|
||||
//
|
||||
// defer s.BumpTime("my.function").End()
|
||||
BumpTime(key string) interface {
|
||||
End()
|
||||
}
|
||||
}
|
||||
|
||||
// PrefixClient adds multiple keys for the same value, with each prefix
|
||||
// added to the key and calls the underlying client.
|
||||
func PrefixClient(prefixes []string, client Client) Client {
|
||||
return &prefixClient{
|
||||
Prefixes: prefixes,
|
||||
Client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type prefixClient struct {
|
||||
Prefixes []string
|
||||
Client Client
|
||||
}
|
||||
|
||||
func (p *prefixClient) BumpAvg(key string, val float64) {
|
||||
for _, prefix := range p.Prefixes {
|
||||
p.Client.BumpAvg(prefix+key, val)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *prefixClient) BumpSum(key string, val float64) {
|
||||
for _, prefix := range p.Prefixes {
|
||||
p.Client.BumpSum(prefix+key, val)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *prefixClient) BumpHistogram(key string, val float64) {
|
||||
for _, prefix := range p.Prefixes {
|
||||
p.Client.BumpHistogram(prefix+key, val)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *prefixClient) BumpTime(key string) interface {
|
||||
End()
|
||||
} {
|
||||
var m multiEnder
|
||||
for _, prefix := range p.Prefixes {
|
||||
m = append(m, p.Client.BumpTime(prefix+key))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// multiEnder combines many enders together.
|
||||
type multiEnder []interface {
|
||||
End()
|
||||
}
|
||||
|
||||
func (m multiEnder) End() {
|
||||
for _, e := range m {
|
||||
e.End()
|
||||
}
|
||||
}
|
||||
|
||||
// HookClient is useful for testing. It provides optional hooks for each
|
||||
// expected method in the interface, which if provided will be called. If a
|
||||
// hook is not provided, it will be ignored.
|
||||
type HookClient struct {
|
||||
BumpAvgHook func(key string, val float64)
|
||||
BumpSumHook func(key string, val float64)
|
||||
BumpHistogramHook func(key string, val float64)
|
||||
BumpTimeHook func(key string) interface {
|
||||
End()
|
||||
}
|
||||
}
|
||||
|
||||
// BumpAvg will call BumpAvgHook if defined.
|
||||
func (c *HookClient) BumpAvg(key string, val float64) {
|
||||
if c.BumpAvgHook != nil {
|
||||
c.BumpAvgHook(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
// BumpSum will call BumpSumHook if defined.
|
||||
func (c *HookClient) BumpSum(key string, val float64) {
|
||||
if c.BumpSumHook != nil {
|
||||
c.BumpSumHook(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
// BumpHistogram will call BumpHistogramHook if defined.
|
||||
func (c *HookClient) BumpHistogram(key string, val float64) {
|
||||
if c.BumpHistogramHook != nil {
|
||||
c.BumpHistogramHook(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
// BumpTime will call BumpTimeHook if defined.
|
||||
func (c *HookClient) BumpTime(key string) interface {
|
||||
End()
|
||||
} {
|
||||
if c.BumpTimeHook != nil {
|
||||
return c.BumpTimeHook(key)
|
||||
}
|
||||
return NoOpEnd
|
||||
}
|
||||
|
||||
type noOpEnd struct{}
|
||||
|
||||
func (n noOpEnd) End() {}
|
||||
|
||||
// NoOpEnd provides a dummy value for use in tests as valid return value for
|
||||
// BumpTime().
|
||||
var NoOpEnd = noOpEnd{}
|
||||
|
||||
// BumpAvg calls BumpAvg on the Client if it isn't nil. This is useful when a
|
||||
// component has an optional stats.Client.
|
||||
func BumpAvg(c Client, key string, val float64) {
|
||||
if c != nil {
|
||||
c.BumpAvg(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
// BumpSum calls BumpSum on the Client if it isn't nil. This is useful when a
|
||||
// component has an optional stats.Client.
|
||||
func BumpSum(c Client, key string, val float64) {
|
||||
if c != nil {
|
||||
c.BumpSum(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
// BumpHistogram calls BumpHistogram on the Client if it isn't nil. This is
|
||||
// useful when a component has an optional stats.Client.
|
||||
func BumpHistogram(c Client, key string, val float64) {
|
||||
if c != nil {
|
||||
c.BumpHistogram(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
// BumpTime calls BumpTime on the Client if it isn't nil. If the Client is nil
|
||||
// it still returns a valid return value which will be a no-op. This is useful
|
||||
// when a component has an optional stats.Client.
|
||||
func BumpTime(c Client, key string) interface {
|
||||
End()
|
||||
} {
|
||||
if c != nil {
|
||||
return c.BumpTime(key)
|
||||
}
|
||||
return NoOpEnd
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package stats_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/facebookgo/ensure"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/facebookgo/stats"
|
||||
)
|
||||
|
||||
// Ensure calling End works even when a BumpTimeHook isn't provided.
|
||||
func TestHookClientBumpTime(t *testing.T) {
|
||||
(&stats.HookClient{}).BumpTime("foo").End()
|
||||
}
|
||||
|
||||
func TestPrefixClient(t *testing.T) {
|
||||
const (
|
||||
prefix1 = "prefix1"
|
||||
prefix2 = "prefix2"
|
||||
avgKey = "avg"
|
||||
avgVal = float64(1)
|
||||
sumKey = "sum"
|
||||
sumVal = float64(2)
|
||||
histogramKey = "histogram"
|
||||
histogramVal = float64(3)
|
||||
timeKey = "time"
|
||||
)
|
||||
|
||||
var keys []string
|
||||
hc := &stats.HookClient{
|
||||
BumpAvgHook: func(key string, val float64) {
|
||||
keys = append(keys, key)
|
||||
ensure.DeepEqual(t, val, avgVal)
|
||||
},
|
||||
BumpSumHook: func(key string, val float64) {
|
||||
keys = append(keys, key)
|
||||
ensure.DeepEqual(t, val, sumVal)
|
||||
},
|
||||
BumpHistogramHook: func(key string, val float64) {
|
||||
keys = append(keys, key)
|
||||
ensure.DeepEqual(t, val, histogramVal)
|
||||
},
|
||||
BumpTimeHook: func(key string) interface {
|
||||
End()
|
||||
} {
|
||||
return multiEnderTest{
|
||||
EndHook: func() {
|
||||
keys = append(keys, key)
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
pc := stats.PrefixClient([]string{prefix1, prefix2}, hc)
|
||||
pc.BumpAvg(avgKey, avgVal)
|
||||
pc.BumpSum(sumKey, sumVal)
|
||||
pc.BumpHistogram(histogramKey, histogramVal)
|
||||
pc.BumpTime(timeKey).End()
|
||||
|
||||
ensure.SameElements(t, keys, []string{
|
||||
prefix1 + avgKey,
|
||||
prefix1 + sumKey,
|
||||
prefix1 + histogramKey,
|
||||
prefix1 + timeKey,
|
||||
prefix2 + avgKey,
|
||||
prefix2 + sumKey,
|
||||
prefix2 + histogramKey,
|
||||
prefix2 + timeKey,
|
||||
})
|
||||
}
|
||||
|
||||
type multiEnderTest struct {
|
||||
EndHook func()
|
||||
}
|
||||
|
||||
func (e multiEnderTest) End() {
|
||||
e.EndHook()
|
||||
}
|
||||
2
Makefile
2
Makefile
|
|
@ -75,4 +75,4 @@ rpm:
|
|||
test/boulder-config.json sa/_db ca/_db $(foreach var,$(OBJECTS), $(OBJDIR)/$(var))
|
||||
|
||||
bin/pkcs11bench: pre
|
||||
go test -o ./bin/pkcs11bench -c ./test/pkcs11bench/
|
||||
go test -o ./bin/pkcs11bench -c ./Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key/
|
||||
|
|
|
|||
|
|
@ -163,13 +163,12 @@ func loadKey(keyConfig cmd.KeyConfig) (priv crypto.Signer, err error) {
|
|||
if pkcs11Config.Module == "" ||
|
||||
pkcs11Config.TokenLabel == "" ||
|
||||
pkcs11Config.PIN == "" ||
|
||||
pkcs11Config.PrivateKeyLabel == "" ||
|
||||
pkcs11Config.SlotID == nil {
|
||||
pkcs11Config.PrivateKeyLabel == "" {
|
||||
err = fmt.Errorf("Missing a field in pkcs11Config %#v", pkcs11Config)
|
||||
return
|
||||
}
|
||||
priv, err = pkcs11key.New(pkcs11Config.Module,
|
||||
pkcs11Config.TokenLabel, pkcs11Config.PIN, pkcs11Config.PrivateKeyLabel, *pkcs11Config.SlotID)
|
||||
pkcs11Config.TokenLabel, pkcs11Config.PIN, pkcs11Config.PrivateKeyLabel)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/codegangsta/cli"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/facebookgo/httpdown"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
|
|
@ -121,6 +122,11 @@ func main() {
|
|||
wfe.IssuerCacheDuration, err = time.ParseDuration(c.WFE.IssuerCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse issuer caching duration")
|
||||
|
||||
wfe.ShutdownStopTimeout, err = time.ParseDuration(c.WFE.ShutdownStopTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse shutdown stop timeout")
|
||||
wfe.ShutdownKillTimeout, err = time.ParseDuration(c.WFE.ShutdownKillTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse shutdown kill timeout")
|
||||
|
||||
wfe.IssuerCert, err = cmd.LoadCert(c.Common.IssuerCert)
|
||||
cmd.FailOnError(err, fmt.Sprintf("Couldn't read issuer cert [%s]", c.Common.IssuerCert))
|
||||
|
||||
|
|
@ -148,10 +154,15 @@ func main() {
|
|||
|
||||
auditlogger.Info(app.VersionString())
|
||||
|
||||
// Add HandlerTimer to output resp time + success/failure stats to statsd
|
||||
|
||||
auditlogger.Info(fmt.Sprintf("Server running, listening on %s...\n", c.WFE.ListenAddress))
|
||||
err = http.ListenAndServe(c.WFE.ListenAddress, HandlerTimer(h, stats))
|
||||
srv := &http.Server{
|
||||
Addr: c.WFE.ListenAddress,
|
||||
Handler: HandlerTimer(h, stats),
|
||||
}
|
||||
hd := &httpdown.HTTP{
|
||||
StopTimeout: wfe.ShutdownStopTimeout,
|
||||
KillTimeout: wfe.ShutdownKillTimeout,
|
||||
}
|
||||
err = httpdown.ListenAndServe(srv, hd)
|
||||
cmd.FailOnError(err, "Error starting HTTP server")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
||||
cfocsp "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/facebookgo/httpdown"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/golang.org/x/crypto/ocsp"
|
||||
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
|
||||
|
||||
|
|
@ -159,9 +160,20 @@ func main() {
|
|||
m := http.NewServeMux()
|
||||
m.Handle(c.OCSPResponder.Path, cfocsp.Responder{Source: src})
|
||||
|
||||
// Add HandlerTimer to output resp time + success/failure stats to statsd
|
||||
auditlogger.Info(fmt.Sprintf("Server running, listening on %s...\n", c.OCSPResponder.ListenAddress))
|
||||
err = http.ListenAndServe(c.OCSPResponder.ListenAddress, HandlerTimer(m, stats))
|
||||
stopTimeout, err := time.ParseDuration(c.OCSPResponder.ShutdownStopTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse shutdown stop timeout")
|
||||
killTimeout, err := time.ParseDuration(c.OCSPResponder.ShutdownKillTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse shutdown kill timeout")
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: c.OCSPResponder.ListenAddress,
|
||||
Handler: HandlerTimer(m, stats),
|
||||
}
|
||||
hd := &httpdown.HTTP{
|
||||
StopTimeout: stopTimeout,
|
||||
KillTimeout: killTimeout,
|
||||
}
|
||||
err = httpdown.ListenAndServe(srv, hd)
|
||||
cmd.FailOnError(err, "Error starting HTTP server")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ type Config struct {
|
|||
IndexCacheDuration string
|
||||
IssuerCacheDuration string
|
||||
|
||||
ShutdownStopTimeout string
|
||||
ShutdownKillTimeout string
|
||||
|
||||
// DebugAddr is the address to run the /debug handlers on.
|
||||
DebugAddr string
|
||||
}
|
||||
|
|
@ -153,6 +156,9 @@ type Config struct {
|
|||
Path string
|
||||
ListenAddress string
|
||||
|
||||
ShutdownStopTimeout string
|
||||
ShutdownKillTimeout string
|
||||
|
||||
// DebugAddr is the address to run the /debug handlers on.
|
||||
DebugAddr string
|
||||
}
|
||||
|
|
@ -237,7 +243,6 @@ type KeyConfig struct {
|
|||
type PKCS11Config struct {
|
||||
Module string
|
||||
TokenLabel string
|
||||
SlotID *int
|
||||
PIN string
|
||||
PrivateKeyLabel string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ type PKCS11Config struct {
|
|||
TokenLabel string
|
||||
PrivateKeyLabel string
|
||||
PIN string
|
||||
SlotID float64
|
||||
}
|
||||
|
||||
func readFiles(c *cli.Context) (issuer, responder, target *x509.Certificate, template ocsp.Response, pkcs11 PKCS11Config, err error) {
|
||||
|
|
@ -159,7 +158,7 @@ func main() {
|
|||
cmd.FailOnError(err, "Failed to read files")
|
||||
|
||||
// Instantiate the private key from PKCS11
|
||||
priv, err := pkcs11key.New(pkcs11.Module, pkcs11.TokenLabel, pkcs11.PIN, pkcs11.PrivateKeyLabel, int(pkcs11.SlotID))
|
||||
priv, err := pkcs11key.New(pkcs11.Module, pkcs11.TokenLabel, pkcs11.PIN, pkcs11.PrivateKeyLabel)
|
||||
cmd.FailOnError(err, "Failed to load PKCS#11 key")
|
||||
|
||||
// Populate the remaining fields in the template
|
||||
|
|
|
|||
|
|
@ -56,14 +56,12 @@ func main() {
|
|||
TokenLabel string
|
||||
PIN string
|
||||
PrivateKeyLabel string
|
||||
SlotID float64
|
||||
}
|
||||
pkcs11Bytes, err := ioutil.ReadFile(pkcs11FileName)
|
||||
panicOnError(err)
|
||||
err = json.Unmarshal(pkcs11Bytes, &pkcs11)
|
||||
panicOnError(err)
|
||||
slotID := int(pkcs11.SlotID)
|
||||
p11key, err := pkcs11key.New(pkcs11.Module, pkcs11.TokenLabel, pkcs11.PIN, pkcs11.PrivateKeyLabel, slotID)
|
||||
p11key, err := pkcs11key.New(pkcs11.Module, pkcs11.TokenLabel, pkcs11.PIN, pkcs11.PrivateKeyLabel)
|
||||
panicOnError(err)
|
||||
|
||||
// All of the certificates start and end at the same time
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"Module": "/usr/local/Cellar/opensc/0.14.0_1/lib/opensc-pkcs11.so",
|
||||
"TokenLabel": "PIV_II (PIV Card Holder pin)",
|
||||
"SlotID": 1,
|
||||
"PrivateKeyLabel": "SIGN key",
|
||||
"PIN": "163246"
|
||||
}
|
||||
|
|
|
|||
125
core/dns.go
125
core/dns.go
|
|
@ -16,30 +16,100 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
// Private CIDRs to ignore per RFC1918 and RFC5735
|
||||
|
||||
// RFC1918
|
||||
// 10.0.0.0/8
|
||||
rfc1918_10 = net.IPNet{
|
||||
IP: []byte{10, 0, 0, 0},
|
||||
Mask: []byte{255, 0, 0, 0},
|
||||
}
|
||||
// 172.16.0.0/12
|
||||
rfc1918_172_16 = net.IPNet{
|
||||
IP: []byte{172, 16, 0, 0},
|
||||
Mask: []byte{255, 240, 0, 0},
|
||||
}
|
||||
// 192.168.0.0/16
|
||||
rfc1918_192_168 = net.IPNet{
|
||||
IP: []byte{192, 168, 0, 0},
|
||||
Mask: []byte{255, 255, 0, 0},
|
||||
}
|
||||
|
||||
// RFC5735
|
||||
// 127.0.0.0/8
|
||||
rfc5735_127 = net.IPNet{
|
||||
IP: []byte{127, 0, 0, 0},
|
||||
Mask: []byte{255, 0, 0, 0},
|
||||
// Private CIDRs to ignore
|
||||
privateNetworks = []net.IPNet{
|
||||
// RFC1918
|
||||
// 10.0.0.0/8
|
||||
net.IPNet{
|
||||
IP: []byte{10, 0, 0, 0},
|
||||
Mask: []byte{255, 0, 0, 0},
|
||||
},
|
||||
// 172.16.0.0/12
|
||||
net.IPNet{
|
||||
IP: []byte{172, 16, 0, 0},
|
||||
Mask: []byte{255, 240, 0, 0},
|
||||
},
|
||||
// 192.168.0.0/16
|
||||
net.IPNet{
|
||||
IP: []byte{192, 168, 0, 0},
|
||||
Mask: []byte{255, 255, 0, 0},
|
||||
},
|
||||
// RFC5735
|
||||
// 127.0.0.0/8
|
||||
net.IPNet{
|
||||
IP: []byte{127, 0, 0, 0},
|
||||
Mask: []byte{255, 0, 0, 0},
|
||||
},
|
||||
// RFC1122 Section 3.2.1.3
|
||||
// 0.0.0.0/8
|
||||
net.IPNet{
|
||||
IP: []byte{0, 0, 0, 0},
|
||||
Mask: []byte{255, 0, 0, 0},
|
||||
},
|
||||
// RFC3927
|
||||
// 169.254.0.0/16
|
||||
net.IPNet{
|
||||
IP: []byte{169, 254, 0, 0},
|
||||
Mask: []byte{255, 255, 0, 0},
|
||||
},
|
||||
// RFC 5736
|
||||
// 192.0.0.0/24
|
||||
net.IPNet{
|
||||
IP: []byte{192, 0, 0, 0},
|
||||
Mask: []byte{255, 255, 255, 0},
|
||||
},
|
||||
// RFC 5737
|
||||
// 192.0.2.0/24
|
||||
net.IPNet{
|
||||
IP: []byte{192, 0, 2, 0},
|
||||
Mask: []byte{255, 255, 255, 0},
|
||||
},
|
||||
// 198.51.100.0/24
|
||||
net.IPNet{
|
||||
IP: []byte{192, 51, 100, 0},
|
||||
Mask: []byte{255, 255, 255, 0},
|
||||
},
|
||||
// 203.0.113.0/24
|
||||
net.IPNet{
|
||||
IP: []byte{203, 0, 113, 0},
|
||||
Mask: []byte{255, 255, 255, 0},
|
||||
},
|
||||
// RFC 3068
|
||||
// 192.88.99.0/24
|
||||
net.IPNet{
|
||||
IP: []byte{192, 88, 99, 0},
|
||||
Mask: []byte{255, 255, 255, 0},
|
||||
},
|
||||
// RFC 2544
|
||||
// 192.18.0.0/15
|
||||
net.IPNet{
|
||||
IP: []byte{192, 18, 0, 0},
|
||||
Mask: []byte{255, 254, 0, 0},
|
||||
},
|
||||
// RFC 3171
|
||||
// 224.0.0.0/4
|
||||
net.IPNet{
|
||||
IP: []byte{224, 0, 0, 0},
|
||||
Mask: []byte{240, 0, 0, 0},
|
||||
},
|
||||
// RFC 1112
|
||||
// 240.0.0.0/4
|
||||
net.IPNet{
|
||||
IP: []byte{240, 0, 0, 0},
|
||||
Mask: []byte{240, 0, 0, 0},
|
||||
},
|
||||
// RFC 919 Section 7
|
||||
// 255.255.255.255/32
|
||||
net.IPNet{
|
||||
IP: []byte{255, 255, 255, 255},
|
||||
Mask: []byte{255, 255, 255, 255},
|
||||
},
|
||||
// RFC 6598
|
||||
// 100.64.0.0./10
|
||||
net.IPNet{
|
||||
IP: []byte{100, 64, 0, 0},
|
||||
Mask: []byte{255, 192, 0, 0},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -121,7 +191,12 @@ func (dnsResolver *DNSResolverImpl) LookupTXT(hostname string) ([]string, time.D
|
|||
}
|
||||
|
||||
func isPrivateV4(ip net.IP) bool {
|
||||
return rfc1918_10.Contains(ip) || rfc1918_172_16.Contains(ip) || rfc1918_192_168.Contains(ip) || rfc5735_127.Contains(ip)
|
||||
for _, net := range privateNetworks {
|
||||
if net.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LookupHost sends a DNS query to find all A records associated with the provided
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@
|
|||
"certNoCacheExpirationWindow": "96h",
|
||||
"indexCacheDuration": "24h",
|
||||
"issuerCacheDuration": "48h",
|
||||
"shutdownStopTimeout": "10s",
|
||||
"shutdownKillTimeout": "1m",
|
||||
"debugAddr": "localhost:8000"
|
||||
},
|
||||
|
||||
|
|
@ -146,6 +148,8 @@
|
|||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
|
||||
"path": "/",
|
||||
"listenAddress": "localhost:4002",
|
||||
"shutdownStopTimeout": "10s",
|
||||
"shutdownKillTimeout": "1m",
|
||||
"debugAddr": "localhost:8005"
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
package pkcs11bench
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key"
|
||||
)
|
||||
|
||||
var module = flag.String("module", "", "Path to PKCS11 module")
|
||||
var tokenLabel = flag.String("tokenLabel", "", "Token label")
|
||||
var pin = flag.String("pin", "", "PIN")
|
||||
var privateKeyLabel = flag.String("privateKeyLabel", "", "Private key label")
|
||||
var slotID = flag.Int("slotID", -1, "Slot")
|
||||
|
||||
// BenchmarkPKCS11 signs a certificate repeatedly using a PKCS11 token and
|
||||
// measures speed. To run:
|
||||
// go test -bench=. -benchtime 1m ./test/pkcs11bench/ \
|
||||
// -module /usr/lib/softhsm/libsofthsm.so -token-label "softhsm token" \
|
||||
// -pin 1234 -private-key-label "my key" -slot-id 7 -v
|
||||
// You can adjust benchtime if you want to run for longer or shorter.
|
||||
// TODO: Parallel benchmarking. Currently if you try this with a Yubikey Neo,
|
||||
// you will get a bunch of CKR_USER_ALREADY_LOGGED_IN errors. This is because
|
||||
// pkcs11key logs into the token before each signing operation (which is probably a
|
||||
// performance bug). Also note that some PKCS11 modules (opensc) are not
|
||||
// threadsafe.
|
||||
func BenchmarkPKCS11(b *testing.B) {
|
||||
if *module == "" || *tokenLabel == "" || *pin == "" || *privateKeyLabel == "" || *slotID == -1 {
|
||||
b.Fatal("Must pass all flags: module, tokenLabel, pin, privateKeyLabel, and slotID")
|
||||
return
|
||||
}
|
||||
// NOTE: To run this test, you will need to edit the following values to match
|
||||
// your PKCS11 token.
|
||||
p, err := pkcs11key.New(*module, *tokenLabel, *pin, *privateKeyLabel, *slotID)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer p.Destroy()
|
||||
|
||||
N := big.NewInt(1)
|
||||
N.Lsh(N, 6000)
|
||||
// A minimal, bogus certificate to be signed.
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
PublicKeyAlgorithm: x509.RSA,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now(),
|
||||
|
||||
PublicKey: &rsa.PublicKey{
|
||||
N: N,
|
||||
E: 1 << 17,
|
||||
},
|
||||
}
|
||||
|
||||
// Reset the benchmarking timer so we don't include setup time.
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err = x509.CreateCertificate(rand.Reader, &template, &template, template.PublicKey, p)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,6 +73,10 @@ type WebFrontEndImpl struct {
|
|||
CertNoCacheExpirationWindow time.Duration
|
||||
IndexCacheDuration time.Duration
|
||||
IssuerCacheDuration time.Duration
|
||||
|
||||
// Gracefull shutdown settings
|
||||
ShutdownStopTimeout time.Duration
|
||||
ShutdownKillTimeout time.Duration
|
||||
}
|
||||
|
||||
func statusCodeFromError(err interface{}) int {
|
||||
|
|
|
|||
Loading…
Reference in New Issue