Add SQL storage and have the CertificateAuthority write to it.
This commit is contained in:
parent
ba3a892de2
commit
0a3732f8a7
|
@ -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)
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue