Merge github.com:letsencrypt/boulder into longserial

This commit is contained in:
Jacob Hoffman-Andrews 2015-09-22 14:04:19 -07:00
commit 668ccc2d97
52 changed files with 3746 additions and 598 deletions

View File

@ -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.

48
Godeps/Godeps.json generated
View File

@ -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",

View 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
}

View 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
}

View File

@ -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
}

View 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) {
}

View 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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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]--
}

View File

@ -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)

View File

@ -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",
}},
}

View 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-----

View File

@ -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"}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -0,0 +1,104 @@
clock [![Build Status](https://drone.io/github.com/benbjohnson/clock/status.png)](https://drone.io/github.com/benbjohnson/clock/latest) [![Coverage Status](https://coveralls.io/repos/benbjohnson/clock/badge.png?branch=master)](https://coveralls.io/r/benbjohnson/clock?branch=master) [![GoDoc](https://godoc.org/github.com/benbjohnson/clock?status.png)](https://godoc.org/github.com/benbjohnson/clock) ![Project status](http://img.shields.io/status/experimental.png?color=red)
=====
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)
```

View File

@ -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{}
}

View File

@ -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) }

View File

@ -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)

View File

@ -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
}

View 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)
}
}

View 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-----`)

View File

@ -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.

View File

@ -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
Facebooks 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.

View File

@ -0,0 +1,41 @@
httpdown [![Build Status](https://secure.travis-ci.org/facebookgo/httpdown.png)](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.

View File

@ -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 ./...

View File

@ -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.

View File

@ -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
Facebooks 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.

View File

@ -0,0 +1,4 @@
stats [![Build Status](https://secure.travis-ci.org/facebookgo/stats.png)](https://travis-ci.org/facebookgo/stats)
=====
Documentation: https://godoc.org/github.com/facebookgo/stats

View File

@ -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
}

View File

@ -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()
}

View File

@ -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/

View File

@ -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
}

View File

@ -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")
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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"
}

View File

@ -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

View File

@ -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"
},

View File

@ -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
}
}
}

View File

@ -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 {