Skip to content

Commit

Permalink
Merge dd58bdc into 6fe1a8b
Browse files Browse the repository at this point in the history
  • Loading branch information
jsha committed Apr 28, 2015
2 parents 6fe1a8b + dd58bdc commit b8ab8db
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 38 deletions.
11 changes: 3 additions & 8 deletions ca/certificate-authority_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@ import (
"testing"
"time"

"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
apisign "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api/sign"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/auth"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/local"
_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/mattn/go-sqlite3"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/sa"
"github.com/letsencrypt/boulder/test"
)
Expand Down Expand Up @@ -246,10 +245,6 @@ func (cadb *MockCADatabase) IncrementAndGetSerial() (int, error) {
}

func TestIssueCertificate(t *testing.T) {
stats, _ := statsd.NewNoopClient(nil)
// Audit logger
audit, _ := blog.Dial("", "", "tag", stats)

// Decode pre-generated values
caKeyPEM, _ := pem.Decode([]byte(CA_KEY_PEM))
caKey, _ := x509.ParsePKCS1PrivateKey(caKeyPEM.Bytes)
Expand All @@ -265,7 +260,7 @@ func TestIssueCertificate(t *testing.T) {
profileName := "ee"

// Create an SA
sa, err := sa.NewSQLStorageAuthority(audit, "sqlite3", ":memory:")
sa, err := sa.NewSQLStorageAuthority(blog.TestLogger(), "sqlite3", ":memory:")
test.AssertNotError(t, err, "Failed to create SA")
sa.InitTables()

Expand Down Expand Up @@ -311,7 +306,7 @@ func TestIssueCertificate(t *testing.T) {

// Create a CA
// Uncomment to test with a remote signer
ca, err := NewCertificateAuthorityImpl(audit, hostPort, authKey, profileName, 17, cadb)
ca, err := NewCertificateAuthorityImpl(blog.TestLogger(), hostPort, authKey, profileName, 17, cadb)
test.AssertNotError(t, err, "Failed to create CA")
ca.SA = sa

Expand Down
15 changes: 15 additions & 0 deletions core/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

type IdentifierType string
type AcmeStatus string
type OCSPStatus string
type Buffer []byte

const (
Expand All @@ -25,6 +26,11 @@ const (
StatusRevoked = AcmeStatus("revoked") // Object no longer valid
)

const (
OCSPStatusGood = OCSPStatus("good")
OCSPStatusRevoked = OCSPStatus("revoked")
)

const (
ChallengeTypeSimpleHTTPS = "simpleHttps"
ChallengeTypeDVSNI = "dvsni"
Expand Down Expand Up @@ -214,3 +220,12 @@ type Certificate struct {
// * "revoked" - revoked
Status AcmeStatus
}

// CertificateStatus structs are internal to the server. They represent the
// latest data about the status of the certificate, required for OCSP updating
// and for validating that the subscriber has accepted the certificate.
type CertificateStatus struct {
SubscriberApproved bool
Status OCSPStatus
OCSPLastUpdated time.Time
}
8 changes: 8 additions & 0 deletions log/audit-logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ type AuditLogger struct {
Stats statsd.Statter
}

// TestLogger returns an AuditLogger, required to initialize many components
func TestLogger() (logger *AuditLogger) {
stats, _ := statsd.NewNoopClient(nil)
// Audit logger
logger, _ = Dial("", "", "tag", stats)
return
}

// Dial establishes a connection to the log daemon by passing through
// the parameters to the syslog.Dial method.
// See http://golang.org/pkg/log/syslog/#Dial
Expand Down
112 changes: 89 additions & 23 deletions sa/storage-authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"encoding/json"
"errors"
"fmt"
"time"

"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
Expand Down Expand Up @@ -61,32 +62,66 @@ func (ssa *SQLStorageAuthority) InitTables() (err error) {
return
}

// All fields should be created with "NOT NULL" because go's SQL support does not
// handle null values well (see, e.g. https://github.com/go-sql-driver/mysql/issues/59)
statements := []string{

// Create registrations table
_, err = tx.Exec("CREATE TABLE IF NOT EXISTS registrations (id TEXT, thumbprint TEXT, value TEXT);")
if err != nil {
tx.Rollback()
return
}
`CREATE TABLE IF NOT EXISTS registrations (
id TEXT NOT NULL,
thumbprint TEXT NOT NULL,
value TEXT NOT NULL
);`,

// Create pending authorizations table
_, err = tx.Exec("CREATE TABLE IF NOT EXISTS pending_authz (id TEXT, value BLOB);")
if err != nil {
tx.Rollback()
return
}
// TODO: Add NOT NULL to value. Right now it causes test failures because some
// inserts to not fill all fields.
`CREATE TABLE IF NOT EXISTS pending_authz (
id TEXT NOT NULL,
value BLOB
);`,

// Create finalized authorizations table
_, err = tx.Exec("CREATE TABLE IF NOT EXISTS authz (sequence INTEGER, id TEXT, digest TEXT, value BLOB);")
if err != nil {
tx.Rollback()
return
}

// Create certificates table
_, err = tx.Exec("CREATE TABLE IF NOT EXISTS certificates (serial TEXT, digest TEXT, value BLOB);")
if err != nil {
tx.Rollback()
return
`CREATE TABLE IF NOT EXISTS authz (
sequence INTEGER NOT NULL,
id TEXT NOT NULL,
digest TEXT NOT NULL,
value BLOB NOT NULL
);`,

// Create certificates table. This should be effectively append-only, enforced
// by DB permissions.
`CREATE TABLE IF NOT EXISTS certificates (
serial STRING NOT NULL,
digest TEXT NOT NULL,
value BLOB NOT NULL,
issued DATETIME NOT NULL
);`,

// Create certificate status table. This provides metadata about a certificate
// that can change over its lifetime, and rows are updateable unlike the
// certificates table. The serial number primary key matches up with the one
// on certificates.
// subscriberApproved: 1 iff the subscriber has posted back to the server
// that they accept the certificate, otherwise 0.
// status: 'good' or 'revoked'
// ocspLastUpdated: The date and time of the last time we generated an OCSP
// response. If we have never generated one, this has the zero value of
// time.Time, i.e. Jan 1 1970.
`CREATE TABLE IF NOT EXISTS certificateStatus (
serial STRING NOT NULL,
subscriberApproved INTEGER NOT NULL,
status STRING NOT NULL,
ocspLastUpdated DATETIME NOT NULL
);`,
}

for _, statement := range statements {
_, err = tx.Exec(statement)
if err != nil {
tx.Rollback()
return
}
}

err = tx.Commit()
Expand Down Expand Up @@ -181,13 +216,35 @@ func (ssa *SQLStorageAuthority) GetAuthorization(id string) (authz core.Authoriz
func (ssa *SQLStorageAuthority) GetCertificate(id string) (cert []byte, err error) {
if len(id) != 16 {
err = errors.New("Invalid certificate serial " + id)
return
}
err = ssa.db.QueryRow(
"SELECT value FROM certificates WHERE serial LIKE ? LIMIT 1;",
id + "%").Scan(&cert)
return
}

// 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)
return
}
var statusString string;
err = ssa.db.QueryRow(
`SELECT subscriberApproved, status, ocspLastUpdated
FROM certificateStatus
WHERE serial = ?
LIMIT 1;`, id).Scan(&status.SubscriberApproved, &statusString, &status.OCSPLastUpdated)
if err != nil {
return
}
status.Status = core.OCSPStatus(statusString)
return
}

func (ssa *SQLStorageAuthority) NewRegistration() (id string, err error) {
tx, err := ssa.db.Begin()
if err != nil {
Expand Down Expand Up @@ -382,8 +439,17 @@ func (ssa *SQLStorageAuthority) AddCertificate(certDER []byte) (digest string, e
}

digest = core.Fingerprint256(certDER)
_, err = tx.Exec("INSERT INTO certificates (serial, digest, value) VALUES (?,?,?);",
serial, digest, certDER)
_, err = tx.Exec("INSERT INTO certificates (serial, digest, value, issued) VALUES (?,?,?,?);",
serial, digest, certDER, time.Now())
if err != nil {
tx.Rollback()
return
}

_, err = tx.Exec(`INSERT INTO certificateStatus
(serial, subscriberApproved, status, ocspLastUpdated)
VALUES (?, 0, 'good', ?);`,
serial, time.Time{})
if err != nil {
tx.Rollback()
return
Expand Down
67 changes: 67 additions & 0 deletions sa/storage-authority_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,70 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package sa

import (
"io/ioutil"
"testing"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/test"
blog "github.com/letsencrypt/boulder/log"
_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/mattn/go-sqlite3"
)

func TestAddCertificate(t *testing.T) {
sa, err := NewSQLStorageAuthority(blog.TestLogger(), "sqlite3", ":memory:")
test.AssertNotError(t, err, "Failed to create SA")
sa.InitTables()

// An example cert taken from EFF's website
certDER, err := ioutil.ReadFile("www.eff.org.der")
test.AssertNotError(t, err, "Couldn't read example cert DER")

digest, err := sa.AddCertificate(certDER)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
test.AssertEquals(t, digest, "qWoItDZmR4P9eFbeYgXXP3SR4ApnkQj8x4LsB_ORKBo")

// Example cert serial is 0x21bd4, so a prefix of all zeroes should fetch it.
retrievedDER, err := sa.GetCertificate("0000000000000000")
test.AssertNotError(t, err, "Couldn't get www.eff.org.der")
test.AssertByteEquals(t, certDER, retrievedDER)

certificateStatus, err := sa.GetCertificateStatus("00000000000000000000000000021bd4")
test.AssertNotError(t, err, "Couldn't get status for www.eff.org.der")
test.Assert(t, !certificateStatus.SubscriberApproved, "SubscriberApproved should be false")
test.Assert(t, certificateStatus.Status == core.OCSPStatusGood, "OCSP Status should be good")
test.Assert(t, certificateStatus.OCSPLastUpdated.IsZero(), "OCSPLastUpdated should be nil")

// Test cert generated locally by Boulder / CFSSL, serial "ff00000000000002238054509817da5a"
certDER2, err := ioutil.ReadFile("test-cert.der")
test.AssertNotError(t, err, "Couldn't read example cert DER")

digest2, err := sa.AddCertificate(certDER2)
test.AssertNotError(t, err, "Couldn't add test-cert.der")
test.AssertEquals(t, digest2, "CMVYqWzyqUW7pfBF2CxL0Uk6I0Upsk7p4EWSnd_vYx4")

// Example cert serial is 0x21bd4, so a prefix of all zeroes should fetch it.
retrievedDER2, err := sa.GetCertificate("ff00000000000002")
test.AssertNotError(t, err, "Couldn't get test-cert.der")
test.AssertByteEquals(t, certDER2, retrievedDER2)

certificateStatus2, err := sa.GetCertificateStatus("ff00000000000002238054509817da5a")
test.AssertNotError(t, err, "Couldn't get status for test-cert.der")
test.Assert(t, !certificateStatus2.SubscriberApproved, "SubscriberApproved should be false")
test.Assert(t, certificateStatus2.Status == core.OCSPStatusGood, "OCSP Status should be good")
test.Assert(t, certificateStatus2.OCSPLastUpdated.IsZero(), "OCSPLastUpdated should be nil")
}

// TestGetCertificate tests some failure conditions for GetCertificate.
// Success conditions are tested above in TestAddCertificate.
func TestGetCertificate(t *testing.T) {
sa, err := NewSQLStorageAuthority(blog.TestLogger(), "sqlite3", ":memory:")
test.AssertNotError(t, err, "Failed to create SA")
sa.InitTables()

_, err = sa.GetCertificate("")
test.AssertError(t, err, "Should've failed on empty serial")

_, err = sa.GetCertificate("01020304050607080102030405060708")
test.AssertError(t, err, "Should've failed on too-long serial")
}
Binary file added sa/test-cert.der
Binary file not shown.
Binary file added sa/www.eff.org.der
Binary file not shown.
4 changes: 2 additions & 2 deletions test/boulder-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@
"serialPrefix": 255,
"profile": "ee",
"dbDriver": "sqlite3",
"dbName": ":memory:",
"dbName": "boulder-ca.sqlite3",
"testMode": true
},

"sa": {
"dbDriver": "sqlite3",
"dbName": ":memory:"
"dbName": "boulder.sqlite3"
},

"mail": {
Expand Down
30 changes: 25 additions & 5 deletions test/test-tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,58 @@
package test

import (
"bytes"
"fmt"
"strings"
"runtime"
"testing"
"encoding/base64"
)

// Return short format caller info for printing errors, so errors don't all
// appear to come from test-tools.go.
func caller() string {
_, file, line, _ := runtime.Caller(2)
splits := strings.Split(file, "/")
filename := splits[len(splits) - 1]
return fmt.Sprintf("%s:%d:", filename, line)
}

func Assert(t *testing.T, result bool, message string) {
if !result {
t.Error(message)
t.Error(caller(), message)
}
}

func AssertNotError(t *testing.T, err error, message string) {
if err != nil {
t.Error(message, err)
t.Error(caller(), message, ":", err)
}
}

func AssertError(t *testing.T, err error, message string) {
if err == nil {
t.Error(message, err)
t.Error(caller(), message, ":", err)
}
}

func AssertEquals(t *testing.T, one string, two string) {
if one != two {
t.Errorf("String [%s] != [%s]", one, two)
t.Errorf("%s String [%s] != [%s]", caller(), one, two)
}
}

func AssertByteEquals(t *testing.T, one []byte, two []byte) {
if !bytes.Equal(one, two) {
t.Errorf("%s Byte [%s] != [%s]",
caller(),
base64.StdEncoding.EncodeToString(one),
base64.StdEncoding.EncodeToString(two))
}
}
func AssertContains(t *testing.T, haystack string, needle string) {
if !strings.Contains(haystack, needle) {
t.Errorf("String [%s] does not contain [%s]", haystack, needle)
t.Errorf("%s String [%s] does not contain [%s]", caller(), haystack, needle)
}
}

Expand Down

0 comments on commit b8ab8db

Please sign in to comment.