Partly done implementation of revoke.

This commit is contained in:
Jacob Hoffman-Andrews 2015-04-29 18:36:26 -07:00
parent 1d2c6a5d7c
commit 8e30ff81fb
7 changed files with 157 additions and 39 deletions

View File

@ -6,10 +6,12 @@
package ca
import (
"crypto"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"time"
"github.com/letsencrypt/boulder/core"
@ -17,21 +19,40 @@ import (
"github.com/letsencrypt/boulder/policy"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/auth"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/remote"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
)
type Config struct {
Server string
AuthKey string
Profile string
TestMode bool
DBDriver string
DBName string
SerialPrefix int
// A PEM-encoded copy of the issuer certificate.
IssuerCert string
// This field is only allowed if TestMode is true, indicating that we are
// signing with a local key. In production we will use an HSM and this
// IssuerKey must be empty (and TestMode must be false). PEM-encoded private
// key used for signing certificates and OCSP responses.
IssuerKey string
}
// CertificateAuthorityImpl represents a CA that signs certificates, CRLs, and
// OCSP responses.
type CertificateAuthorityImpl struct {
profile string
Signer signer.Signer
SA core.StorageAuthority
PA core.PolicyAuthority
DB core.CertificateAuthorityDatabase
log *blog.AuditLogger
Prefix int // Prepended to the serial number
profile string
Signer signer.Signer
OCSPSigner ocsp.Signer
SA core.StorageAuthority
PA core.PolicyAuthority
DB core.CertificateAuthorityDatabase
log *blog.AuditLogger
Prefix int // Prepended to the serial number
}
// NewCertificateAuthorityImpl creates a CA that talks to a remote CFSSL
@ -40,44 +61,120 @@ type CertificateAuthorityImpl struct {
// 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(logger *blog.AuditLogger, hostport string, authKey string, profile string, serialPrefix int, cadb core.CertificateAuthorityDatabase) (ca *CertificateAuthorityImpl, err error) {
func NewCertificateAuthorityImpl(logger *blog.AuditLogger,
cadb core.CertificateAuthorityDatabase, config Config) (ca *CertificateAuthorityImpl, err error) {
logger.Notice("Certificate Authority Starting")
if config.SerialPrefix == 0 {
err = errors.New("Must have non-zero serial prefix for CA.")
}
// Create the remote signer
localProfile := config.SigningProfile{
localProfile := cfsslConfig.SigningProfile{
Expiry: time.Hour, // BOGUS: Required by CFSSL, but not used
RemoteName: hostport, // BOGUS: Only used as a flag by CFSSL
RemoteServer: hostport,
RemoteName: config.Server, // BOGUS: Only used as a flag by CFSSL
RemoteServer: config.Server,
UseSerialSeq: true,
}
localProfile.Provider, err = auth.New(authKey, nil)
localProfile.Provider, err = auth.New(config.AuthKey, nil)
if err != nil {
return
return nil, err
}
signer, err := remote.NewSigner(&config.Signing{Default: &localProfile})
signer, err := remote.NewSigner(&cfsslConfig.Signing{Default: &localProfile})
if err != nil {
return
return nil, err
}
issuer, err := loadIssuer(config.IssuerCert)
if err != nil {
return nil, err
}
// In test mode, load a private key from a file. In production, use an HSM.
if !config.TestMode {
err = errors.New("OCSP signing with a PKCS#11 key not yet implemented.")
return nil, err
}
issuerKey, err := loadIssuerKey(config.IssuerKey)
if err != nil {
return nil, err
}
// Set up our OCSP signer. Note this calls for both the issuer cert and the
// OCSP signing cert, which are the same in our case.
ocspSigner, err := ocsp.NewSigner(issuer, issuer, issuerKey,
time.Hour * 24 * 4)
pa := policy.NewPolicyAuthorityImpl(logger)
ca = &CertificateAuthorityImpl{
Signer: signer,
profile: profile,
OCSPSigner: ocspSigner,
profile: config.Profile,
PA: pa,
DB: cadb,
Prefix: serialPrefix,
Prefix: config.SerialPrefix,
log: logger,
}
return ca, err
}
func loadIssuer(filename string) (issuerCert *x509.Certificate, err error) {
if filename == "" {
err = errors.New("Issuer certificate was not provided in config.")
return
}
issuerCertPEM, err := ioutil.ReadFile(filename)
if err != nil {
return
}
issuerCert, err = helpers.ParseCertificatePEM(issuerCertPEM)
return
}
func (ca *CertificateAuthorityImpl) RevokeCertificate(serial string) (cert core.Certificate, err error) {
func loadIssuerKey(filename string) (issuerKey crypto.Signer, err error) {
if filename == "" {
err = errors.New("IssuerKey must be provided in test mode.")
return
}
pem, err := ioutil.ReadFile(filename)
if err != nil {
return
}
issuerKey, err = helpers.ParsePrivateKeyPEM(pem)
return
}
func (ca *CertificateAuthorityImpl) RevokeCertificate(serial string) (err error) {
certDER, err := ca.SA.GetCertificate(serial)
if err != nil {
return err
}
cert, err := x509.ParseCertificate(certDER)
if err != nil {
return err
}
signRequest := ocsp.SignRequest{
Certificate: cert,
Status: string(core.OCSPStatusRevoked),
// Per https://tools.ietf.org/html/rfc5280, CRLReason 0 is "unspecified."
// TODO: Add support for specifying reason.
Reason: 0,
RevokedAt: time.Now(),
}
ocspResponse, err := ca.OCSPSigner.Sign(signRequest)
if err != nil {
return err
}
err = ca.SA.MarkCertificateRevoked(serial, ocspResponse)
return err
}
// IssueCertificate attempts to convert a CSR into a signed Certificate, while
// enforcing all policies.
func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest) (cert core.Certificate, err error) {

View File

@ -306,7 +306,16 @@ func TestIssueCertificate(t *testing.T) {
// Create a CA
// Uncomment to test with a remote signer
ca, err := NewCertificateAuthorityImpl(blog.TestLogger(), hostPort, authKey, profileName, 17, cadb)
caConfig := Config{
Server: hostPort,
AuthKey: authKey,
Profile: profileName,
SerialPrefix: 17,
IssuerCert: "../test/test-ca.pem",
IssuerKey: "../test/test-ca.key",
TestMode: true,
}
ca, err := NewCertificateAuthorityImpl(blog.TestLogger(), cadb, caConfig)
test.AssertNotError(t, err, "Failed to create CA")
ca.SA = sa

View File

@ -31,7 +31,7 @@ func main() {
cadb, err := ca.NewCertificateAuthorityDatabaseImpl(auditlogger, c.CA.DBDriver, c.CA.DBName)
cmd.FailOnError(err, "Failed to create CA database")
cai, err := ca.NewCertificateAuthorityImpl(auditlogger, c.CA.Server, c.CA.AuthKey, c.CA.Profile, c.CA.SerialPrefix, cadb)
cai, err := ca.NewCertificateAuthorityImpl(auditlogger, cadb, c.CA)
cmd.FailOnError(err, "Failed to create CA impl")
go cmd.ProfileCmd("CA", stats)

View File

@ -37,6 +37,7 @@ import (
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/rpc"
"github.com/letsencrypt/boulder/ca"
)
// Config stores configuration parameters that applications
@ -59,15 +60,7 @@ type Config struct {
ListenAddress string
}
CA struct {
Server string
AuthKey string
Profile string
TestMode bool
DBDriver string
DBName string
SerialPrefix int
}
CA ca.Config
SA struct {
DBDriver string

View File

@ -100,6 +100,7 @@ type StorageAdder interface {
NewPendingAuthorization() (string, error)
UpdatePendingAuthorization(Authorization) error
FinalizeAuthorization(Authorization) error
MarkCertificateRevoked(serial string, ocspResponse []byte) error
AddCertificate([]byte) (string, error)
}

View File

@ -271,9 +271,9 @@ func (ssa *SQLStorageAuthority) GetCertificate(serial string) (cert []byte, err
// GetCertificateStatus takes a hexadecimal string representing the full 128-bit serial
// number of a certificate and returns data about that certificate's current
// validity.
func (ssa *SQLStorageAuthority) GetCertificateStatus(id string) (status core.CertificateStatus, err error) {
if len(id) != 32 {
err = errors.New("Invalid certificate serial " + id)
func (ssa *SQLStorageAuthority) GetCertificateStatus(serial string) (status core.CertificateStatus, err error) {
if len(serial) != 32 {
err = errors.New("Invalid certificate serial " + serial)
return
}
var statusString string;
@ -281,7 +281,7 @@ func (ssa *SQLStorageAuthority) GetCertificateStatus(id string) (status core.Cer
`SELECT subscriberApproved, status, ocspLastUpdated
FROM certificateStatus
WHERE serial = ?
LIMIT 1;`, id).Scan(&status.SubscriberApproved, &statusString, &status.OCSPLastUpdated)
LIMIT 1;`, serial).Scan(&status.SubscriberApproved, &statusString, &status.OCSPLastUpdated)
if err != nil {
return
}
@ -316,15 +316,30 @@ func (ssa *SQLStorageAuthority) NewRegistration() (id string, err error) {
return
}
func (ssa *SQLStorageAuthority) MarkCertificateRevoked(serial string,
ocspResponse []byte) (err error) {
func (ssa *SQLStorageAuthority) MarkCertificateRevoked(serial string, ocspResponse []byte) (err error) {
if _, err = ssa.GetCertificate(serial); err != nil {
return errors.New(fmt.Sprintf(
"Unable to mark certificate %s revoked: cert not found.", serial))
}
if _, err = ssa.GetCertificateStatus(serial); err != nil {
return errors.New(fmt.Sprintf(
"Unable to mark certificate %s revoked: cert status not found.", serial))
}
tx, err := ssa.db.Begin()
if err != nil {
return
}
//TODO ...
_, err = tx.Exec("UPDATE certificateStatus SET status=?, revokedDate=?",
string(core.OCSPStatusRevoked), time.Now())
if err != nil {
tx.Rollback()
return
}
err = tx.Commit()
return nil
return
}
func (ssa *SQLStorageAuthority) UpdateRegistration(reg core.Registration) (err error) {

View File

@ -42,7 +42,10 @@
"profile": "ee",
"dbDriver": "sqlite3",
"dbName": "boulder-ca.sqlite3",
"testMode": true
"testMode": true,
"issuerCert": "test/test-ca.pem",
"_comment": "This should only be present in testMode. In prod use an HSM."
"issuerKey": "test/test-ca.key"
},
"sa": {