Use a remote CFSSL instance for signing
This commit is contained in:
parent
d2c6035abb
commit
ba3a892de2
|
@ -47,21 +47,32 @@ func main() {
|
|||
app.Usage = "Command-line utility to start Boulder's servers in stand-alone mode"
|
||||
app.Version = "0.0.0"
|
||||
|
||||
|
||||
// Specify AMQP Server
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "amqp",
|
||||
Value: "amqp://guest:guest@localhost:5672",
|
||||
EnvVar: "AMQP_SERVER",
|
||||
Usage: "AMQP Broker URI",
|
||||
Name: "amqp",
|
||||
Value: "amqp://guest:guest@localhost:5672",
|
||||
EnvVar: "AMQP_SERVER",
|
||||
Usage: "AMQP Broker URI",
|
||||
},
|
||||
/*
|
||||
cli.StringFlag{
|
||||
Name: "cfssl",
|
||||
Value: "localhost:8888",
|
||||
EnvVar: "CFSSL_SERVER",
|
||||
Usage: "CFSSL Server URI",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cfssl",
|
||||
Value: "tcp://localhost:8888",
|
||||
EnvVar: "CFSSL_PORT",
|
||||
Usage: "CFSSL Server URI",
|
||||
cli.StringFlag{
|
||||
Name: "cfsslAuthKey",
|
||||
EnvVar: "CFSSL_AUTH_KEY",
|
||||
Usage: "CFSSL authentication key",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cfsslProfile",
|
||||
EnvVar: "CFSSL_PROFILE",
|
||||
Usage: "CFSSL signing profile",
|
||||
},
|
||||
*/
|
||||
}
|
||||
|
||||
// One command per element of the system
|
||||
|
@ -79,18 +90,30 @@ func main() {
|
|||
Name: "monolithic",
|
||||
Usage: "Start the CA in monolithic mode, without using AMQP",
|
||||
Action: func(c *cli.Context) {
|
||||
|
||||
// XXX Print the config
|
||||
fmt.Println(c.GlobalString("amqp"))
|
||||
fmt.Println(c.GlobalString("cfssl"))
|
||||
fmt.Println(c.GlobalString("cfsslAuthKey"))
|
||||
fmt.Println(c.GlobalString("cfsslProfile"))
|
||||
|
||||
// Grab parameters
|
||||
cfsslServer := c.GlobalString("cfssl")
|
||||
authKey := c.GlobalString("cfsslAuthKey")
|
||||
profile := c.GlobalString("cfsslProfile")
|
||||
|
||||
// Create the components
|
||||
wfe := boulder.NewWebFrontEndImpl()
|
||||
sa := boulder.NewSimpleStorageAuthorityImpl()
|
||||
ra := boulder.NewRegistrationAuthorityImpl()
|
||||
va := boulder.NewValidationAuthorityImpl()
|
||||
ca, err := boulder.NewCertificateAuthorityImpl()
|
||||
ca, err := boulder.NewCertificateAuthorityImpl(cfsslServer, authKey, profile)
|
||||
failOnError(err, "Unable to create CA")
|
||||
|
||||
// Wire them up
|
||||
wfe.RA = &ra
|
||||
wfe.SA = &sa
|
||||
ra.CA = &ca
|
||||
ra.CA = ca
|
||||
ra.SA = &sa
|
||||
ra.VA = &va
|
||||
va.RA = &ra
|
||||
|
@ -115,6 +138,11 @@ func main() {
|
|||
Name: "monolithic-amqp",
|
||||
Usage: "Start the CA in monolithic mode, using AMQP",
|
||||
Action: func(c *cli.Context) {
|
||||
// Grab parameters
|
||||
cfsslServer := c.GlobalString("cfssl")
|
||||
authKey := c.GlobalString("cfsslAuthKey")
|
||||
profile := c.GlobalString("cfsslProfile")
|
||||
|
||||
// Create an AMQP channel
|
||||
ch := amqpChannel(c.GlobalString("amqp"))
|
||||
|
||||
|
@ -131,7 +159,9 @@ func main() {
|
|||
// ... and corresponding servers
|
||||
// (We need this order so that we can give the servers
|
||||
// references to the clients)
|
||||
cas, err := boulder.NewCertificateAuthorityServer("CA.server", ch)
|
||||
cai, err := boulder.NewCertificateAuthorityImpl(cfsslServer, authKey, profile)
|
||||
failOnError(err, "Failed to create CA impl")
|
||||
cas, err := boulder.NewCertificateAuthorityServer("CA.server", ch, cai)
|
||||
failOnError(err, "Failed to create CA server")
|
||||
vas, err := boulder.NewValidationAuthorityServer("VA.server", ch, &rac)
|
||||
failOnError(err, "Failed to create VA server")
|
||||
|
@ -203,9 +233,16 @@ func main() {
|
|||
Name: "ca",
|
||||
Usage: "Start the CertificateAuthority",
|
||||
Action: func(c *cli.Context) {
|
||||
// Grab parameters
|
||||
cfsslServer := c.GlobalString("cfssl")
|
||||
authKey := c.GlobalString("cfsslAuthKey")
|
||||
profile := c.GlobalString("cfsslProfile")
|
||||
|
||||
ch := amqpChannel(c.GlobalString("amqp"))
|
||||
|
||||
cas, err := boulder.NewCertificateAuthorityServer("CA.server", ch)
|
||||
cai, err := boulder.NewCertificateAuthorityImpl(cfsslServer, authKey, profile)
|
||||
failOnError(err, "Failed to create CA impl")
|
||||
cas, err := boulder.NewCertificateAuthorityServer("CA.server", ch, cai)
|
||||
failOnError(err, "Unable to create CA server")
|
||||
runForever(cas)
|
||||
},
|
||||
|
|
|
@ -6,117 +6,96 @@
|
|||
package boulder
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cfssl/auth"
|
||||
"github.com/cloudflare/cfssl/config"
|
||||
"github.com/cloudflare/cfssl/signer"
|
||||
"github.com/cloudflare/cfssl/signer/remote"
|
||||
)
|
||||
|
||||
type CertificateAuthorityImpl struct {
|
||||
privateKey interface{}
|
||||
certificate x509.Certificate
|
||||
derCertificate []byte
|
||||
signer signer.Signer
|
||||
profile string
|
||||
}
|
||||
|
||||
var (
|
||||
serialNumberBits = uint(64)
|
||||
oneYear = 365 * 24 * time.Hour
|
||||
rootCertificateTemplate = x509.Certificate{
|
||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||
Subject: pkix.Name{Organization: []string{"ACME CA"}},
|
||||
IsCA: true,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment |
|
||||
x509.KeyUsageDigitalSignature |
|
||||
x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
// NewCertificateAuthorityImpl creates a CA that talks to a remote CFSSL
|
||||
// instance. (To use a local signer, simply instantiate CertificateAuthorityImpl
|
||||
// directly.) Communications with the CA are authenticated with MACs,
|
||||
// using CFSSL's authenticated signature scheme. A CA created in this way
|
||||
// issues for a single profile on the remote signer, which is indicated
|
||||
// by name in this constructor.
|
||||
func NewCertificateAuthorityImpl(hostport string, authKey string, profile string) (ca *CertificateAuthorityImpl, err error) {
|
||||
// Create the remote signer
|
||||
localProfile := config.SigningProfile{
|
||||
Expiry: 60 * time.Minute, // BOGUS: Required by CFSSL, but not used
|
||||
RemoteName: hostport,
|
||||
}
|
||||
eeCertificateTemplate = x509.Certificate{
|
||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||
IsCA: false,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment |
|
||||
x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
)
|
||||
|
||||
func newSerialNumber() (*big.Int, error) {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), serialNumberBits)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
localProfile.Provider, err = auth.New(authKey, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
return serialNumber, nil
|
||||
|
||||
signer, err := remote.NewSigner(&config.Signing{Default: &localProfile})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ca = &CertificateAuthorityImpl{signer: signer, profile: profile}
|
||||
return
|
||||
}
|
||||
|
||||
func NewCertificateAuthorityImpl() (CertificateAuthorityImpl, error) {
|
||||
zero := CertificateAuthorityImpl{}
|
||||
|
||||
// Generate a key pair
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest) (cert []byte, err error) {
|
||||
// XXX Take in authorizations and verify that union covers CSR?
|
||||
// Pull hostnames from CSR
|
||||
hostNames := csr.DNSNames // DNSNames + CN from CSR
|
||||
if len(hostNames) < 1 {
|
||||
err = errors.New("Cannot issue a certificate without a hostname.")
|
||||
return
|
||||
}
|
||||
|
||||
// Sign the certificate
|
||||
template := rootCertificateTemplate
|
||||
template.SerialNumber, err = newSerialNumber()
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
template.NotBefore = time.Now()
|
||||
template.NotAfter = template.NotBefore.Add(oneYear)
|
||||
der, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
// Parse the certificate
|
||||
certs, err := x509.ParseCertificates(der)
|
||||
if err != nil || len(certs) == 0 {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
return CertificateAuthorityImpl{
|
||||
privateKey: priv,
|
||||
certificate: *certs[0],
|
||||
derCertificate: der,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ca *CertificateAuthorityImpl) CACertificate() []byte {
|
||||
return ca.derCertificate
|
||||
}
|
||||
|
||||
func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest) ([]byte, error) {
|
||||
template := eeCertificateTemplate
|
||||
|
||||
// Set serial
|
||||
serialNumber, err := newSerialNumber()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
template.SerialNumber = serialNumber
|
||||
|
||||
// Set validity
|
||||
template.NotBefore = time.Now()
|
||||
template.NotAfter = template.NotBefore.Add(oneYear)
|
||||
|
||||
// Set hostnames
|
||||
domains := csr.DNSNames
|
||||
var commonName string
|
||||
if len(csr.Subject.CommonName) > 0 {
|
||||
domains = append(domains, csr.Subject.CommonName)
|
||||
commonName = csr.Subject.CommonName
|
||||
} else {
|
||||
commonName = hostNames[0]
|
||||
}
|
||||
if len(domains) == 0 {
|
||||
return []byte{}, errors.New("No names provided for certificate")
|
||||
}
|
||||
template.Subject = pkix.Name{CommonName: domains[0]}
|
||||
template.DNSNames = domains
|
||||
|
||||
// Sign
|
||||
return x509.CreateCertificate(rand.Reader, &template, &ca.certificate, csr.PublicKey, ca.privateKey)
|
||||
// Convert the CSR to PEM
|
||||
csrPEM := string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE REQUEST",
|
||||
Bytes: csr.Raw,
|
||||
}))
|
||||
|
||||
// Send the cert off for signing
|
||||
req := signer.SignRequest{
|
||||
Request: csrPEM,
|
||||
Profile: ca.profile,
|
||||
Hostname: commonName,
|
||||
Subject: &signer.Subject{
|
||||
CN: commonName,
|
||||
Hosts: hostNames,
|
||||
},
|
||||
}
|
||||
certPEM, err := ca.signer.Sign(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(certPEM) == 0 {
|
||||
err = errors.New("No certificate returned by server")
|
||||
return
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(certPEM)
|
||||
if block == nil || block.Type != "CERTIFICATE" {
|
||||
err = errors.New("Invalid certificate value returned")
|
||||
return
|
||||
}
|
||||
|
||||
cert = block.Bytes
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2014 ISRG. All rights reserved
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package boulder
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// CSR generated by Go:
|
||||
// * Random public key
|
||||
// * CN = example.com
|
||||
// * DNSNames = example.com, www.example.com
|
||||
var CSR_HEX = "308202953082017d0201003016311430120603" +
|
||||
"550403130b6578616d706c652e636f6d30820122300d06092a864886f70d0101010500038201" +
|
||||
"0f003082010a0282010100baaf16e891828470cad87b849a73356f65e20ad3699fd5583a7200" +
|
||||
"e924512d9eeb1dbe16441ad7bd804fa2e5726a06f0af5279012fe6354a5677259f5591984aa9" +
|
||||
"99b8ea3ea10fbd5ecfa30e5f563b41c419374decfc98ea62c611046ad011c326470a2426f46d" +
|
||||
"be6cc44fae3b247e19710810585f9f3ad7f64b2f305aebb72e2829866f89b20b03a300b7ff5f" +
|
||||
"4e6204f41420d9fa731252692cee8e616636723abe8a7053fd86e2673190fa8b618ada5bc735" +
|
||||
"ba57a145af86904a8f55a288d4d6ba9e501530f23f197f5b623443193fc92b7f87d6abbf740d" +
|
||||
"9fc92800c7e0e1484d5eec6ffae1007c139c1ec19d67e749743fe8d8396fe190cfbcf2f90e05" +
|
||||
"230203010001a03a303806092a864886f70d01090e312b302930270603551d110420301e820b" +
|
||||
"6578616d706c652e636f6d820f7777772e6578616d706c652e636f6d300d06092a864886f70d" +
|
||||
"01010b05000382010100514c622dc866b31c86777a26e9b2911618ce5b188591f08007b42772" +
|
||||
"3497b733676a7d493c434fc819b8089869442fd299aa99ff7f7b9df881843727f0c8b89ca62a" +
|
||||
"f8a12b38c963e9210148c4e1c0fc964aef2605f88ed777e6497d2e43e9a9713835d1ae96260c" +
|
||||
"ca826c34c7ae52c77f5d8468643ee1936eadf04e1ebf8bbbb68b0ec7d0ef694432451292e4a3" +
|
||||
"1989fd8339c07e01e04b6dd3834872b828d3f5b2b4dadda0596396e15fbdf446998066a74873" +
|
||||
"2baf53f3f7ebb849e83cf734753c35ab454d1b62e1741a6514c5c575c0c805b4d668fcf71746" +
|
||||
"ef32017613a52d6b807e2977f4fbc0a88b2e263347c4d9e35435333cf4f8288be53df41228ec"
|
||||
|
||||
func TestIssueCertificate(t *testing.T) {
|
||||
// Decode pre-generated CSR
|
||||
csrDER, _ := hex.DecodeString(CSR_HEX)
|
||||
csr, _ := x509.ParseCertificateRequest(csrDER)
|
||||
|
||||
// Create a CA
|
||||
// This assumes that there is a CFSSL instance running that supports these parameters
|
||||
ca, err := NewCertificateAuthorityImpl("localhost:9000", "79999d86250c367a2b517a1ae7d409c1", "ee")
|
||||
AssertNotError(t, err, "Failed to create CA")
|
||||
|
||||
// Sign CSR
|
||||
certDER, err := ca.IssueCertificate(*csr)
|
||||
AssertNotError(t, err, "Failed to sign certificate")
|
||||
|
||||
// Verify cert contents
|
||||
cert, err := x509.ParseCertificate(certDER)
|
||||
AssertNotError(t, err, "Certificate failed to parse")
|
||||
|
||||
AssertEquals(t, cert.Subject.CommonName, "example.com")
|
||||
|
||||
if len(cert.DNSNames) != 2 || cert.DNSNames[0] != "example.com" || cert.DNSNames[1] != "www.example.com" {
|
||||
t.Errorf("Improper list of domain names %v", cert.DNSNames)
|
||||
}
|
||||
}
|
|
@ -282,14 +282,9 @@ func (vac ValidationAuthorityClient) UpdateValidations(authz Authorization) erro
|
|||
|
||||
// CertificateAuthorityClient / Server
|
||||
// -> IssueCertificate
|
||||
func NewCertificateAuthorityServer(serverQueue string, channel *amqp.Channel) (rpc *AmqpRpcServer, err error) {
|
||||
func NewCertificateAuthorityServer(serverQueue string, channel *amqp.Channel, impl CertificateAuthority) (rpc *AmqpRpcServer, err error) {
|
||||
rpc = NewAmqpRpcServer(serverQueue, channel)
|
||||
|
||||
impl, err := NewCertificateAuthorityImpl()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rpc.Handle(MethodIssueCertificate, func(req []byte) []byte {
|
||||
zero := []byte{}
|
||||
|
||||
|
|
Loading…
Reference in New Issue