Skip to content

Commit

Permalink
Merge pull request #105 from letsencrypt/certificate-query
Browse files Browse the repository at this point in the history
Certificate query by serial
  • Loading branch information
jsha committed Apr 21, 2015
2 parents 362f46c + 830f645 commit ba622d4
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 101 deletions.
18 changes: 10 additions & 8 deletions ca/certificate-authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
if err != nil {
return
}
serialHex := fmt.Sprintf("%01X%014X", ca.Prefix, serialDec)
serialHex := fmt.Sprintf("%02X%014X", ca.Prefix, serialDec)

// Send the cert off for signing
req := signer.SignRequest{
Expand Down Expand Up @@ -157,19 +157,21 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
}
certDER := block.Bytes

cert = core.Certificate{
DER: certDER,
Status: core.StatusValid,
}
if err != nil {
return core.Certificate{}, err
}

// Store the cert with the certificate authority, if provided
certID, err := ca.SA.AddCertificate(certDER)
_, err = ca.SA.AddCertificate(certDER)
if err != nil {
ca.DB.Rollback()
return
}

cert = core.Certificate{
ID: certID,
DER: certDER,
Status: core.StatusValid,
}

ca.DB.Commit()
return
}
7 changes: 5 additions & 2 deletions ca/certificate-authority_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"encoding/asn1"
"encoding/hex"
"encoding/pem"
"fmt"
"net/http"
"testing"
"time"
Expand Down Expand Up @@ -352,8 +353,10 @@ func TestIssueCertificate(t *testing.T) {
}

// Verify that the cert got stored in the DB
_, err = sa.GetCertificate(certObj.ID)
test.AssertNotError(t, err, "Certificate not found in database")
shortSerial := fmt.Sprintf("%x", cert.SerialNumber)[0:16]
_, err = sa.GetCertificate(shortSerial)
test.AssertNotError(t, err,
fmt.Sprintf("Certificate %x not found in database", shortSerial))
}

// Test that the CA rejects CSRs with no names
Expand Down
29 changes: 3 additions & 26 deletions cmd/boulder-wfe/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,32 +97,9 @@ func main() {
}
}()

// Go!
newRegPath := "/acme/new-reg"
regPath := "/acme/reg/"
newAuthzPath := "/acme/new-authz"
authzPath := "/acme/authz/"
newCertPath := "/acme/new-cert"
certPath := "/acme/cert/"
wfe.NewReg = c.WFE.BaseURL + newRegPath
wfe.RegBase = c.WFE.BaseURL + regPath
wfe.NewAuthz = c.WFE.BaseURL + newAuthzPath
wfe.AuthzBase = c.WFE.BaseURL + authzPath
wfe.NewCert = c.WFE.BaseURL + newCertPath
wfe.CertBase = c.WFE.BaseURL + certPath
http.HandleFunc(newRegPath, wfe.NewRegistration)
http.HandleFunc(newAuthzPath, wfe.NewAuthorization)
http.HandleFunc(newCertPath, wfe.NewCertificate)
http.HandleFunc(regPath, wfe.Registration)
http.HandleFunc(authzPath, wfe.Authorization)
http.HandleFunc(certPath, wfe.Certificate)

// Add a simple ToS
termsPath := "/terms"
http.HandleFunc(termsPath, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "You agree to do the right thing")
})
wfe.SubscriberAgreementURL = c.WFE.BaseURL + termsPath
// Set up paths
wfe.BaseURL = c.WFE.BaseURL
wfe.HandlePaths()

// Add HandlerTimer to output resp time + success/failure stats to statsd
err = http.ListenAndServe(c.WFE.ListenAddress, HandlerTimer(http.DefaultServeMux, stats))
Expand Down
29 changes: 3 additions & 26 deletions cmd/boulder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,32 +93,9 @@ func main() {
va.RA = &ra
ca.SA = sa

// Go!
newRegPath := "/acme/new-reg"
regPath := "/acme/reg/"
newAuthzPath := "/acme/new-authz"
authzPath := "/acme/authz/"
newCertPath := "/acme/new-cert"
certPath := "/acme/cert/"
wfe.NewReg = c.WFE.BaseURL + newRegPath
wfe.RegBase = c.WFE.BaseURL + regPath
wfe.NewAuthz = c.WFE.BaseURL + newAuthzPath
wfe.AuthzBase = c.WFE.BaseURL + authzPath
wfe.NewCert = c.WFE.BaseURL + newCertPath
wfe.CertBase = c.WFE.BaseURL + certPath
http.HandleFunc(newRegPath, wfe.NewRegistration)
http.HandleFunc(newAuthzPath, wfe.NewAuthorization)
http.HandleFunc(newCertPath, wfe.NewCertificate)
http.HandleFunc(regPath, wfe.Registration)
http.HandleFunc(authzPath, wfe.Authorization)
http.HandleFunc(certPath, wfe.Certificate)

// Add a simple ToS
termsPath := "/terms"
http.HandleFunc(termsPath, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "You agree to do the right thing")
})
wfe.SubscriberAgreementURL = c.WFE.BaseURL + termsPath
// Set up paths
wfe.BaseURL = c.WFE.BaseURL
wfe.HandlePaths()

// We need to tell the RA how to make challenge URIs
// XXX: Better way to do this? Part of improved configuration
Expand Down
9 changes: 4 additions & 5 deletions core/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,12 @@ type Authorization struct {
// Certificate objects are entirely internal to the server. The only
// thing exposed on the wire is the certificate itself.
type Certificate struct {
// An identifier for this authorization, unique across
// authorizations and certificates within this instance.
ID string

// The certificate itself
// The encoded, signed certificate
DER jose.JsonBuffer

// The parsed version of DER. Useful for extracting things like serial number.
ParsedCertificate *x509.Certificate

// The revocation status of the certificate.
// * "valid" - not revoked
// * "revoked" - revoked
Expand Down
5 changes: 4 additions & 1 deletion ra/registration-authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,10 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,

// Create the certificate
ra.log.Audit(fmt.Sprintf("Issuing certificate for %s", names))
cert, err = ra.CA.IssueCertificate(*csr)
if cert, err = ra.CA.IssueCertificate(*csr); err != nil {
return
}
cert.ParsedCertificate, err = x509.ParseCertificate([]byte(cert.DER))
return
}

Expand Down
6 changes: 5 additions & 1 deletion ra/registration-authority_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
"net/url"
"testing"

Expand Down Expand Up @@ -241,9 +242,12 @@ func TestNewCertificate(t *testing.T) {

cert, err := ra.NewCertificate(certRequest, AccountKey)
test.AssertNotError(t, err, "Failed to issue certificate")
parsedCert, err := x509.ParseCertificate(cert.DER)
test.AssertNotError(t, err, "Failed to parse certificate")
shortSerial := fmt.Sprintf("%x", parsedCert.SerialNumber)[0:16]

// Verify that cert shows up and is as expected
dbCert, err := sa.GetCertificate(cert.ID)
dbCert, err := sa.GetCertificate(shortSerial)
test.AssertNotError(t, err, "Could not fetch certificate from database")
test.Assert(t, bytes.Compare(cert.DER, dbCert) == 0, "Certificates differ")

Expand Down
41 changes: 24 additions & 17 deletions sa/storage-authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package sa

import (
"crypto/sha256"
"crypto/x509"
"database/sql"
"encoding/hex"
"encoding/json"
Expand Down Expand Up @@ -76,7 +77,7 @@ func (ssa *SQLStorageAuthority) InitTables() (err error) {
}

// Create certificates table
_, err = tx.Exec("CREATE TABLE certificates (sequence INTEGER, digest TEXT, value BLOB);")
_, err = tx.Exec("CREATE TABLE certificates (serial string, digest TEXT, value BLOB);")
if err != nil {
tx.Rollback()
return
Expand Down Expand Up @@ -164,8 +165,20 @@ func (ssa *SQLStorageAuthority) GetAuthorization(id string) (authz core.Authoriz
return
}

// GetCertificate takes an id consisting of the first, sequential half of a
// serial number and returns the first certificate whose full serial number is
// lexically greater than that id. This allows clients to query on the known
// sequential half of our serial numbers to enumerate all certificates.
// TODO: Add index on certificates table
// TODO: Implement error when there are multiple certificates with the same
// sequential half.
func (ssa *SQLStorageAuthority) GetCertificate(id string) (cert []byte, err error) {
err = ssa.db.QueryRow("SELECT value FROM certificates WHERE digest = ?;", id).Scan(&cert)
if len(id) != 16 {
err = errors.New("Invalid certificate serial " + id)
}
err = ssa.db.QueryRow(
"SELECT value FROM certificates WHERE serial LIKE ? LIMIT 1;",
id + "%").Scan(&cert)
return
}

Expand Down Expand Up @@ -349,28 +362,22 @@ func (ssa *SQLStorageAuthority) FinalizeAuthorization(authz core.Authorization)
return
}

func (ssa *SQLStorageAuthority) AddCertificate(cert []byte) (id string, err error) {
tx, err := ssa.db.Begin()
func (ssa *SQLStorageAuthority) AddCertificate(certDER []byte) (digest string, err error) {
var parsedCertificate *x509.Certificate
parsedCertificate, err = x509.ParseCertificate(certDER)
if err != nil {
return
}
serial := fmt.Sprintf("%x", parsedCertificate.SerialNumber)

// Manually set the index, to avoid AUTOINCREMENT issues
var sequence int64
var scanTarget sql.NullInt64
err = tx.QueryRow("SELECT max(sequence) FROM certificates;").Scan(&scanTarget)
switch {
case !scanTarget.Valid:
sequence = 0
case err != nil:
tx.Rollback()
tx, err := ssa.db.Begin()
if err != nil {
return
default:
sequence += scanTarget.Int64 + 1
}

id = core.Fingerprint256(cert)
_, err = tx.Exec("INSERT INTO certificates (sequence, digest, value) VALUES (?,?,?);", sequence, id, cert)
digest = core.Fingerprint256(certDER)
_, err = tx.Exec("INSERT INTO certificates (serial, digest, value) VALUES (?,?,?);",
serial, digest, certDER)
if err != nil {
tx.Rollback()
return
Expand Down
1 change: 1 addition & 0 deletions test/js/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ function getTerms(resp) {

if (state.termsRequired) {
state.termsURL = links["terms-of-service"];
console.log(state.termsURL);
http.get(state.termsURL, getAgreement)
} else {
inquirer.prompt(questions.domain, getChallenges);
Expand Down

0 comments on commit ba622d4

Please sign in to comment.