Add SQL storage and have the CertificateAuthority write to it.

This commit is contained in:
Richard Barnes 2015-03-04 13:30:47 -05:00
parent ba3a892de2
commit 0a3732f8a7
7 changed files with 161 additions and 56 deletions

View File

@ -12,6 +12,8 @@ import (
"github.com/streadway/amqp"
"net/http"
"os"
_ "github.com/mattn/go-sqlite3"
)
// Exit and print error message if we encountered a problem
@ -55,24 +57,22 @@ func main() {
EnvVar: "AMQP_SERVER",
Usage: "AMQP Broker URI",
},
/*
cli.StringFlag{
Name: "cfssl",
Value: "localhost:8888",
EnvVar: "CFSSL_SERVER",
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",
},
*/
cli.StringFlag{
Name: "cfssl",
Value: "localhost:8888",
EnvVar: "CFSSL_SERVER",
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
@ -104,7 +104,8 @@ func main() {
// Create the components
wfe := boulder.NewWebFrontEndImpl()
sa := boulder.NewSimpleStorageAuthorityImpl()
sa, err := boulder.NewSQLStorageAuthority("sqlite3", ":memory:")
failOnError(err, "Unable to create SA")
ra := boulder.NewRegistrationAuthorityImpl()
va := boulder.NewValidationAuthorityImpl()
ca, err := boulder.NewCertificateAuthorityImpl(cfsslServer, authKey, profile)
@ -112,9 +113,9 @@ func main() {
// Wire them up
wfe.RA = &ra
wfe.SA = &sa
wfe.SA = sa
ra.CA = ca
ra.SA = &sa
ra.SA = sa
ra.VA = &va
va.RA = &ra
@ -167,7 +168,9 @@ func main() {
failOnError(err, "Failed to create VA server")
ras, err := boulder.NewRegistrationAuthorityServer("RA.server", ch, &vac, &cac, &sac)
failOnError(err, "Failed to create RA server")
sas := boulder.NewStorageAuthorityServer("SA.server", ch)
sai, err := boulder.NewSQLStorageAuthority("sqlite3", ":memory:")
failOnError(err, "Failed to create SA impl")
sas := boulder.NewStorageAuthorityServer("SA.server", ch, sai)
// Start the servers
cas.Start()
@ -253,7 +256,9 @@ func main() {
Action: func(c *cli.Context) {
ch := amqpChannel(c.GlobalString("amqp"))
sas := boulder.NewStorageAuthorityServer("SA.server", ch)
sai, err := boulder.NewSQLStorageAuthority("sqlite3", ":memory:")
failOnError(err, "Failed to create SA impl")
sas := boulder.NewStorageAuthorityServer("SA.server", ch, sai)
runForever(sas)
},
},

View File

@ -20,6 +20,7 @@ import (
type CertificateAuthorityImpl struct {
signer signer.Signer
profile string
SA StorageAuthority
}
// NewCertificateAuthorityImpl creates a CA that talks to a remote CFSSL
@ -49,7 +50,7 @@ func NewCertificateAuthorityImpl(hostport string, authKey string, profile string
return
}
func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest) (cert []byte, err error) {
func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest) (certID string, 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
@ -95,7 +96,9 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
err = errors.New("Invalid certificate value returned")
return
}
cert = block.Bytes
// Store the cert with the certificate authority, if provided
certID, err = ca.SA.AddCertificate(cert)
return
}

View File

@ -9,6 +9,8 @@ import (
"crypto/x509"
"encoding/hex"
"testing"
_ "github.com/mattn/go-sqlite3"
)
// CSR generated by Go:
@ -39,13 +41,19 @@ func TestIssueCertificate(t *testing.T) {
csrDER, _ := hex.DecodeString(CSR_HEX)
csr, _ := x509.ParseCertificateRequest(csrDER)
// Create an SA
sa, err := NewSQLStorageAuthority("sqlite3", ":memory:")
AssertNotError(t, err, "Failed to create SA")
sa.InitTables()
// 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")
ca.SA = sa
// Sign CSR
certDER, err := ca.IssueCertificate(*csr)
certID, certDER, err := ca.IssueCertificate(*csr)
AssertNotError(t, err, "Failed to sign certificate")
// Verify cert contents
@ -57,4 +65,8 @@ func TestIssueCertificate(t *testing.T) {
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)
}
// Verify that the cert got stored in the DB
_, err = sa.GetCertificate(certID)
AssertNotError(t, err, "Certificate not found in database")
}

View File

@ -66,7 +66,7 @@ type ValidationAuthority interface {
type CertificateAuthority interface {
// [RegistrationAuthority]
IssueCertificate(x509.CertificateRequest) ([]byte, error)
IssueCertificate(x509.CertificateRequest) (string, []byte, error)
}
type StorageGetter interface {
@ -83,4 +83,6 @@ type StorageUpdater interface {
type StorageAuthority interface {
StorageGetter
StorageUpdater
AddCertificate([]byte) (string, error)
}

View File

@ -33,7 +33,7 @@ func forbiddenIdentifier(id string) bool {
// A DNS label is a part separated by dots, e.g. www.foo.net has labels
// "www", "foo", and "net".
const maxLabels = 10
labels := strings.SplitN(id, ".", maxLabels + 1)
labels := strings.SplitN(id, ".", maxLabels+1)
if len(labels) < 2 || len(labels) > maxLabels {
return true
}
@ -46,7 +46,7 @@ func forbiddenIdentifier(id string) bool {
}
// Only alphanumerics and dash are allowed in identifiers.
// TODO: Before identifiers reach this function, do lowercasing.
if ! dnsLabelRegexp.MatchString(label) {
if !dnsLabelRegexp.MatchString(label) {
return true
}
@ -68,7 +68,7 @@ func forbiddenIdentifier(id string) bool {
}
// Also forbid an all-numeric final label.
if ipAddressRegexp.MatchString(labels[len(labels) - 1]) {
if ipAddressRegexp.MatchString(labels[len(labels)-1]) {
return true
}
@ -151,14 +151,14 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req CertificateRequest, jwk
}
// Create the certificate
cert, err := ra.CA.IssueCertificate(*csr)
certID, cert, err := ra.CA.IssueCertificate(*csr)
if err != nil {
return zero, CertificateIssuanceError("Error issuing certificate")
}
// Identify the certificate object by the cert's SHA-256 fingerprint
certObj := Certificate{
ID: fingerprint256(cert),
ID: certID,
DER: cert,
Status: StatusValid,
}

View File

@ -39,6 +39,7 @@ const (
MethodIssueCertificate = "IssueCertificate" // CA
MethodGet = "Get" // SA
MethodUpdate = "Update" // SA
MethodAddCertificate = "AddCertificate" // SA
)
// RegistrationAuthorityClient / Server
@ -293,11 +294,20 @@ func NewCertificateAuthorityServer(serverQueue string, channel *amqp.Channel, im
return zero // XXX
}
cert, err := impl.IssueCertificate(*csr)
certID, cert, err := impl.IssueCertificate(*csr)
if err != nil {
return zero // XXX
}
return cert
serialized, err := json.Marshal(map[string]string{
"certID": certID,
"cert": b64enc(cert),
})
if err != nil {
return zero // XXX
}
return serialized
})
return
@ -317,12 +327,22 @@ func NewCertificateAuthorityClient(clientQueue, serverQueue string, channel *amq
return
}
func (cac CertificateAuthorityClient) IssueCertificate(csr x509.CertificateRequest) (cert []byte, err error) {
cert, err = cac.rpc.DispatchSync(MethodIssueCertificate, csr.Raw)
func (cac CertificateAuthorityClient) IssueCertificate(csr x509.CertificateRequest) (certID string, cert []byte, err error) {
serialized, err := cac.rpc.DispatchSync(MethodIssueCertificate, csr.Raw)
if len(cert) == 0 {
// TODO: Better error handling
return []byte{}, errors.New("RPC resulted in error")
return
}
var parsed map[string]string
err = json.Unmarshal(serialized, &parsed)
if err != nil {
return
}
certID, _ = parsed["certID"]
cert64, _ := parsed["cert"]
cert, err = b64dec(cert64)
return
}
@ -347,11 +367,9 @@ type storageRecord struct {
Content string
}
func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel) (rpc *AmqpRpcServer) {
func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl StorageAuthority) (rpc *AmqpRpcServer) {
rpc = NewAmqpRpcServer(serverQueue, channel)
impl := NewSimpleStorageAuthorityImpl()
rpc.Handle(MethodGet, func(req []byte) (response []byte) {
id := string(req)
@ -378,6 +396,14 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel) (rpc *
return
})
rpc.Handle(MethodAddCertificate, func(req []byte) (response []byte) {
id, err := impl.AddCertificate(req)
if err == nil {
response = []byte(id)
}
return
})
return
}
@ -468,3 +494,13 @@ func (cac StorageAuthorityClient) Get(token string) (object interface{}, err err
err = errors.New("I can't serialize that!")
return
}
func (cac StorageAuthorityClient) AddCertificate(cert []byte) (certID string, err error) {
response, err := cac.rpc.DispatchSync(MethodAddCertificate, cert)
if len(response) == 0 {
err = errors.New("AddCertificate RPC failed") // XXX
return
}
certID = string(response)
return
}

View File

@ -6,33 +6,80 @@
package boulder
import (
"fmt"
"crypto/sha256"
"database/sql"
)
type SimpleStorageAuthorityImpl struct {
Storage map[string]interface{}
type SQLStorageAuthority struct {
db *sql.DB
bucket map[string]interface{} // XXX included only for backward compat
}
func (sa *SimpleStorageAuthorityImpl) dumpState() {
fmt.Printf("Storage state: \n%+v\n", sa.Storage)
func digest256(data []byte) []byte {
d := sha256.New()
d.Write(data)
return d.Sum(nil)
}
func NewSimpleStorageAuthorityImpl() SimpleStorageAuthorityImpl {
return SimpleStorageAuthorityImpl{
Storage: make(map[string]interface{}),
func NewSQLStorageAuthority(driver string, name string) (ssa *SQLStorageAuthority, err error) {
db, err := sql.Open(driver, name)
if err != nil {
return
}
if err = db.Ping(); err != nil {
return
}
ssa = &SQLStorageAuthority{
db: db,
bucket: make(map[string]interface{}),
}
return
}
func (sa *SimpleStorageAuthorityImpl) Update(token string, object interface{}) error {
sa.Storage[token] = object
func (ssa *SQLStorageAuthority) InitTables() (err error) {
// Create certificates table
query := "CREATE TABLE certificates (location INTEGER, digest TEXT, value BLOB);"
_, err = ssa.db.Exec(query)
return
}
// DEPRECATED
func (ssa *SQLStorageAuthority) Update(key string, value interface{}) (err error) {
ssa.bucket[key] = value
return nil
}
func (sa *SimpleStorageAuthorityImpl) Get(token string) (interface{}, error) {
value, ok := sa.Storage[token]
if ok {
return value, nil
} else {
return struct{}{}, NotFoundError("Unknown storage token")
// DEPRECATED
func (ssa *SQLStorageAuthority) Get(key string) (value interface{}, err error) {
value, ok := ssa.bucket[key]
if !ok {
err = NotFoundError("Unknown storage key")
}
return
}
func (ssa *SQLStorageAuthority) AddCertificate(cert []byte) (id string, err error) {
// Manually set the index, to avoid AUTOINCREMENT issues
var location int64
var scanTarget sql.NullInt64
err = ssa.db.QueryRow("SELECT max(location) FROM certificates").Scan(&scanTarget)
switch {
case !scanTarget.Valid:
location = 0
case err != nil:
return
default:
location += scanTarget.Int64 + 1
}
id = fingerprint256(cert)
query := "INSERT INTO certificates (location, digest, value) VALUES (?,?,?);"
_, err = ssa.db.Exec(query, location, id, cert)
return
}
func (ssa *SQLStorageAuthority) GetCertificate(id string) (cert []byte, err error) {
err = ssa.db.QueryRow("SELECT value FROM certificates WHERE digest = ?", id).Scan(&cert)
return
}