Merge pull request #247 from letsencrypt/202-periodic-ocsp

RPC Fix and Issue #202: OCSP Update Tool
This commit is contained in:
James 'J.C.' Jones 2015-05-28 23:10:28 -07:00
commit 9167fb067f
32 changed files with 841 additions and 321 deletions

View File

@ -9,7 +9,11 @@ OBJECTS = activity-monitor \
boulder-ra \
boulder-sa \
boulder-va \
boulder-wfe
boulder-wfe \
ocsp-updater
REVID = $(shell git symbolic-ref --short HEAD):$(shell git rev-parse --short HEAD)
BUILD_ID_VAR = github.com/letsencrypt/boulder/core.BuildID
.PHONY: all build
all: build
@ -22,8 +26,8 @@ pre:
# Compile each of the binaries
$(OBJECTS): pre
go build -o ./bin/$@ cmd/$@/main.go
go build -o ./bin/$@ -ldflags "-X $(BUILD_ID_VAR) $(REVID)" cmd/$@/main.go
clean:
rm -f $(OBJDIR)/*
rmdir $(OBJDIR)
rmdir $(OBJDIR)

View File

@ -21,11 +21,12 @@ import (
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/auth"
cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
cfsslOCSP "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
"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"
)
// Config defines the JSON configuration file schema
type Config struct {
Server string
AuthKey string
@ -51,7 +52,7 @@ type Config struct {
type CertificateAuthorityImpl struct {
profile string
Signer signer.Signer
OCSPSigner ocsp.Signer
OCSPSigner cfsslOCSP.Signer
SA core.StorageAuthority
PA core.PolicyAuthority
DB core.CertificateAuthorityDatabase
@ -111,7 +112,7 @@ func NewCertificateAuthorityImpl(cadb core.CertificateAuthorityDatabase, config
// 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,
ocspSigner, err := cfsslOCSP.NewSigner(issuer, issuer, issuerKey,
time.Hour*24*4)
if err != nil {
return nil, err
@ -179,6 +180,25 @@ func dupeNames(names []string) bool {
return false
}
// GenerateOCSP produces a new OCSP response and returns it
func (ca *CertificateAuthorityImpl) GenerateOCSP(xferObj core.OCSPSigningRequest) ([]byte, error) {
cert, err := x509.ParseCertificate(xferObj.CertDER)
if err != nil {
return nil, err
}
signRequest := cfsslOCSP.SignRequest{
Certificate: cert,
Status: xferObj.Status,
Reason: xferObj.Reason,
RevokedAt: xferObj.RevokedAt,
}
ocspResponse, err := ca.OCSPSigner.Sign(signRequest)
return ocspResponse, err
}
// RevokeCertificate revokes the trust of the Cert referred to by the provided Serial.
func (ca *CertificateAuthorityImpl) RevokeCertificate(serial string, reasonCode int) (err error) {
certDER, err := ca.SA.GetCertificate(serial)
if err != nil {
@ -189,7 +209,7 @@ func (ca *CertificateAuthorityImpl) RevokeCertificate(serial string, reasonCode
return err
}
signRequest := ocsp.SignRequest{
signRequest := cfsslOCSP.SignRequest{
Certificate: cert,
Status: string(core.OCSPStatusRevoked),
Reason: reasonCode,

View File

@ -12,13 +12,14 @@ package main
import (
"fmt"
"os"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
"github.com/letsencrypt/boulder/analysis"
"github.com/letsencrypt/boulder/cmd"
blog "github.com/letsencrypt/boulder/log"
"time"
)
const (
@ -150,6 +151,8 @@ func main() {
go cmd.ProfileCmd("AM", stats)
auditlogger.Info(app.VersionString())
startMonitor(ch, auditlogger, stats)
}

View File

@ -50,7 +50,7 @@ func main() {
ch := cmd.AmqpChannel(c.AMQP.Server)
closeChan := ch.NotifyClose(make(chan *amqp.Error, 1))
sac, err := rpc.NewStorageAuthorityClient(c.AMQP.SA.Client, c.AMQP.SA.Client, ch)
sac, err := rpc.NewStorageAuthorityClient("CA->SA", c.AMQP.SA.Server, ch)
cmd.FailOnError(err, "Failed to create SA client")
cai.SA = &sac
@ -58,6 +58,8 @@ func main() {
cas, err := rpc.NewCertificateAuthorityServer(c.AMQP.CA.Server, ch, cai)
cmd.FailOnError(err, "Unable to create CA server")
auditlogger.Info(app.VersionString())
cmd.RunUntilSignaled(auditlogger, cas, closeChan)
}
}

View File

@ -13,6 +13,7 @@ import (
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/ra"
"github.com/letsencrypt/boulder/rpc"
"github.com/letsencrypt/boulder/wfe"
)
func main() {
@ -31,6 +32,7 @@ func main() {
blog.SetAuditLogger(auditlogger)
rai := ra.NewRegistrationAuthorityImpl()
rai.AuthzBase = c.WFE.BaseURL + wfe.AuthzPath
go cmd.ProfileCmd("RA", stats)
@ -38,13 +40,13 @@ func main() {
ch := cmd.AmqpChannel(c.AMQP.Server)
closeChan := ch.NotifyClose(make(chan *amqp.Error, 1))
vac, err := rpc.NewValidationAuthorityClient(c.AMQP.VA.Client, c.AMQP.VA.Server, ch)
vac, err := rpc.NewValidationAuthorityClient("RA->VA", c.AMQP.VA.Server, ch)
cmd.FailOnError(err, "Unable to create VA client")
cac, err := rpc.NewCertificateAuthorityClient(c.AMQP.CA.Client, c.AMQP.CA.Server, ch)
cac, err := rpc.NewCertificateAuthorityClient("RA->CA", c.AMQP.CA.Server, ch)
cmd.FailOnError(err, "Unable to create CA client")
sac, err := rpc.NewStorageAuthorityClient(c.AMQP.SA.Client, c.AMQP.SA.Server, ch)
sac, err := rpc.NewStorageAuthorityClient("RA->SA", c.AMQP.SA.Server, ch)
cmd.FailOnError(err, "Unable to create SA client")
rai.VA = &vac
@ -54,6 +56,8 @@ func main() {
ras, err := rpc.NewRegistrationAuthorityServer(c.AMQP.RA.Server, ch, &rai)
cmd.FailOnError(err, "Unable to create RA server")
auditlogger.Info(app.VersionString())
cmd.RunUntilSignaled(auditlogger, ras, closeChan)
}

View File

@ -50,6 +50,8 @@ func main() {
sas := rpc.NewStorageAuthorityServer(c.AMQP.SA.Server, ch, sai)
auditlogger.Info(app.VersionString())
cmd.RunUntilSignaled(auditlogger, sas, closeChan)
}
}

View File

@ -38,7 +38,7 @@ func main() {
ch := cmd.AmqpChannel(c.AMQP.Server)
closeChan := ch.NotifyClose(make(chan *amqp.Error, 1))
rac, err := rpc.NewRegistrationAuthorityClient(c.AMQP.RA.Client, c.AMQP.RA.Server, ch)
rac, err := rpc.NewRegistrationAuthorityClient("VA->RA", c.AMQP.RA.Server, ch)
cmd.FailOnError(err, "Unable to create RA client")
vai.RA = &rac
@ -46,6 +46,8 @@ func main() {
vas, err := rpc.NewValidationAuthorityServer(c.AMQP.VA.Server, ch, &vai)
cmd.FailOnError(err, "Unable to create VA server")
auditlogger.Info(app.VersionString())
cmd.RunUntilSignaled(auditlogger, vas, closeChan)
}
}

View File

@ -8,10 +8,10 @@ package main
import (
"fmt"
"net/http"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
"time"
"github.com/letsencrypt/boulder/cmd"
blog "github.com/letsencrypt/boulder/log"
@ -23,10 +23,10 @@ func setupWFE(c cmd.Config) (rpc.RegistrationAuthorityClient, rpc.StorageAuthori
ch := cmd.AmqpChannel(c.AMQP.Server)
closeChan := ch.NotifyClose(make(chan *amqp.Error, 1))
rac, err := rpc.NewRegistrationAuthorityClient(c.AMQP.RA.Client, c.AMQP.RA.Server, ch)
rac, err := rpc.NewRegistrationAuthorityClient("WFE->RA", c.AMQP.RA.Server, ch)
cmd.FailOnError(err, "Unable to create RA client")
sac, err := rpc.NewStorageAuthorityClient(c.AMQP.SA.Client, c.AMQP.SA.Server, ch)
sac, err := rpc.NewStorageAuthorityClient("WFE->SA", c.AMQP.SA.Server, ch)
cmd.FailOnError(err, "Unable to create SA client")
return rac, sac, closeChan
@ -110,6 +110,8 @@ func main() {
wfe.BaseURL = c.WFE.BaseURL
wfe.HandlePaths()
auditlogger.Info(app.VersionString())
// Add HandlerTimer to output resp time + success/failure stats to statsd
err = http.ListenAndServe(c.WFE.ListenAddress, HandlerTimer(http.DefaultServeMux, stats))
cmd.FailOnError(err, "Error starting HTTP server")

View File

@ -74,12 +74,13 @@ func main() {
go cmd.ProfileCmd("Monolith", stats)
// Create the components
wfe := wfe.NewWebFrontEndImpl()
wfei := wfe.NewWebFrontEndImpl()
sa, err := sa.NewSQLStorageAuthority(c.SA.DBDriver, c.SA.DBName)
cmd.FailOnError(err, "Unable to create SA")
sa.SetSQLDebug(c.SQL.SQLDebug)
ra := ra.NewRegistrationAuthorityImpl()
va := va.NewValidationAuthorityImpl(c.CA.TestMode)
cadb, err := ca.NewCertificateAuthorityDatabaseImpl(c.CA.DBDriver, c.CA.DBName)
@ -97,12 +98,12 @@ func main() {
}
// Wire them up
wfe.RA = &ra
wfe.SA = sa
wfe.Stats = stats
wfe.SubscriberAgreementURL = c.SubscriberAgreementURL
wfei.RA = &ra
wfei.SA = sa
wfei.Stats = stats
wfei.SubscriberAgreementURL = c.SubscriberAgreementURL
wfe.IssuerCert, err = cmd.LoadCert(c.CA.IssuerCert)
wfei.IssuerCert, err = cmd.LoadCert(c.CA.IssuerCert)
cmd.FailOnError(err, fmt.Sprintf("Couldn't read issuer cert [%s]", c.CA.IssuerCert))
ra.CA = ca
@ -112,12 +113,11 @@ func main() {
ca.SA = sa
// Set up paths
wfe.BaseURL = c.WFE.BaseURL
wfe.HandlePaths()
ra.AuthzBase = c.WFE.BaseURL + wfe.AuthzPath
wfei.BaseURL = c.WFE.BaseURL
wfei.HandlePaths()
// We need to tell the RA how to make challenge URIs
// XXX: Better way to do this? Part of improved configuration
ra.AuthzBase = wfe.AuthzBase
auditlogger.Info(app.VersionString())
fmt.Fprintf(os.Stderr, "Server running, listening on %s...\n", c.WFE.ListenAddress)
err = http.ListenAndServe(c.WFE.ListenAddress, HandlerTimer(http.DefaultServeMux, stats))

View File

@ -1,4 +1,4 @@
// Copyright 2014 ISRG. All rights reserved
// Copyright 2015 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
@ -19,6 +19,7 @@ import (
"golang.org/x/crypto/ocsp"
"github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
)
@ -134,6 +135,8 @@ func main() {
// Configure HTTP
http.Handle(c.OCSP.Path, cfocsp.Responder{Source: src})
auditlogger.Info(app.VersionString())
// Add HandlerTimer to output resp time + success/failure stats to statsd
err = http.ListenAndServe(c.WFE.ListenAddress, HandlerTimer(http.DefaultServeMux, stats))
cmd.FailOnError(err, "Error starting HTTP server")

203
cmd/ocsp-updater/main.go Normal file
View File

@ -0,0 +1,203 @@
// Copyright 2015 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"crypto/x509"
"database/sql"
"fmt"
"math"
"time"
// Load both drivers to allow configuring either
_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/go-sql-driver/mysql"
_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/mattn/go-sqlite3"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/codegangsta/cli"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
"github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/rpc"
"github.com/letsencrypt/boulder/sa"
)
const ocspResponseLimit int = 128
func setupClients(c cmd.Config) (rpc.CertificateAuthorityClient, chan *amqp.Error) {
ch := cmd.AmqpChannel(c.AMQP.Server)
closeChan := ch.NotifyClose(make(chan *amqp.Error, 1))
cac, err := rpc.NewCertificateAuthorityClient("OCSP->CA", c.AMQP.CA.Server, ch)
cmd.FailOnError(err, "Unable to create CA client")
return cac, closeChan
}
func processResponse(cac rpc.CertificateAuthorityClient, tx *gorp.Transaction, serial string) error {
certObj, err := tx.Get(core.Certificate{}, serial)
if err != nil {
return err
}
statusObj, err := tx.Get(core.CertificateStatus{}, serial)
if err != nil {
return err
}
cert, ok := certObj.(*core.Certificate)
if !ok {
return fmt.Errorf("Cast failure")
}
status, ok := statusObj.(*core.CertificateStatus)
if !ok {
return fmt.Errorf("Cast failure")
}
_, err = x509.ParseCertificate(cert.DER)
if err != nil {
return err
}
signRequest := core.OCSPSigningRequest{
CertDER: cert.DER,
Reason: status.RevokedReason,
Status: string(status.Status),
RevokedAt: status.RevokedDate,
}
ocspResponse, err := cac.GenerateOCSP(signRequest)
if err != nil {
return err
}
timeStamp := time.Now()
// Record the response.
ocspResp := &core.OcspResponse{Serial: serial, CreatedAt: timeStamp, Response: ocspResponse}
err = tx.Insert(ocspResp)
if err != nil {
return err
}
// Reset the update clock
status.OCSPLastUpdated = timeStamp
_, err = tx.Update(status)
if err != nil {
return err
}
// Done
return nil
}
func findStaleResponses(cac rpc.CertificateAuthorityClient, dbMap *gorp.DbMap, oldestLastUpdatedTime time.Time, responseLimit int) error {
log := blog.GetAuditLogger()
var certificateStatus []core.CertificateStatus
_, err := dbMap.Select(&certificateStatus,
`SELECT cs.* FROM certificateStatus AS cs
WHERE cs.ocspLastUpdated < ?
ORDER BY cs.ocspLastUpdated ASC
LIMIT ?`, oldestLastUpdatedTime, responseLimit)
if err == sql.ErrNoRows {
log.Info("All up to date. No OCSP responses needed.")
} else if err != nil {
log.Err(fmt.Sprintf("Error loading certificate status: %s", err))
} else {
log.Info(fmt.Sprintf("Processing OCSP Responses...\n"))
for i, status := range certificateStatus {
log.Info(fmt.Sprintf("OCSP %d: %s", i, status.Serial))
// Each response gets a transaction. To speed this up, we can batch
// transactions.
tx, err := dbMap.Begin()
if err != nil {
log.Err(fmt.Sprintf("Error starting transaction, aborting: %s", err))
tx.Rollback()
return err
}
if err := processResponse(cac, tx, status.Serial); err != nil {
log.Err(fmt.Sprintf("Could not process OCSP Response for %s: %s", status.Serial, err))
tx.Rollback()
return err
} else {
log.Info(fmt.Sprintf("OCSP %d: %s OK", i, status.Serial))
tx.Commit()
}
}
}
return err
}
func main() {
app := cmd.NewAppShell("ocsp-updater")
app.App.Flags = append(app.App.Flags, cli.IntFlag{
Name: "limit",
Value: ocspResponseLimit,
EnvVar: "OCSP_LIMIT",
Usage: "Count of responses to process per run",
})
app.Config = func(c *cli.Context, config cmd.Config) cmd.Config {
config.OCSP.ResponseLimit = c.GlobalInt("limit")
return config
}
app.Action = func(c cmd.Config) {
// Set up logging
stats, err := statsd.NewClient(c.Statsd.Server, c.Statsd.Prefix)
cmd.FailOnError(err, "Couldn't connect to statsd")
auditlogger, err := blog.Dial(c.Syslog.Network, c.Syslog.Server, c.Syslog.Tag, stats)
cmd.FailOnError(err, "Could not connect to Syslog")
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
defer auditlogger.AuditPanic()
blog.SetAuditLogger(auditlogger)
// Configure DB
dbMap, err := sa.NewDbMap(c.OCSP.DBDriver, c.OCSP.DBName)
cmd.FailOnError(err, "Could not connect to database")
cac, closeChan := setupClients(c)
go func() {
// Abort if we disconnect from AMQP
for {
for err := range closeChan {
auditlogger.Warning(fmt.Sprintf("AMQP Channel closed, aborting early: [%s]", err))
panic(err)
}
}
}()
auditlogger.Info(app.VersionString())
// Calculate the cut-off timestamp
dur, err := time.ParseDuration(c.OCSP.MinTimeToExpiry)
cmd.FailOnError(err, "Could not parse MinTimeToExpiry from config.")
oldestLastUpdatedTime := time.Now().Add(-dur)
auditlogger.Info(fmt.Sprintf("Searching for OCSP reponses older than %s", oldestLastUpdatedTime))
count := int(math.Min(float64(ocspResponseLimit), float64(c.OCSP.ResponseLimit)))
err = findStaleResponses(cac, dbMap, oldestLastUpdatedTime, count)
if err != nil {
auditlogger.WarningErr(err)
}
}
app.Run()
}

View File

@ -38,6 +38,7 @@ import (
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/codegangsta/cli"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
"github.com/letsencrypt/boulder/ca"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/rpc"
)
@ -51,10 +52,11 @@ type Config struct {
// General
AMQP struct {
Server string
RA QueuePair
VA QueuePair
SA QueuePair
CA QueuePair
RA Queue
VA Queue
SA Queue
CA Queue
OCSP Queue
}
WFE struct {
@ -98,24 +100,26 @@ type Config struct {
}
OCSP struct {
DBDriver string
DBName string
Path string
DBDriver string
DBName string
Path string
MinTimeToExpiry string
ResponseLimit int
}
SubscriberAgreementURL string
}
// QueuePair describes a client-server pair of queue names
type QueuePair struct {
Client string
// Queue describes a queue name
type Queue struct {
Server string
}
// AppShell contains CLI Metadata
type AppShell struct {
Action func(Config)
app *cli.App
Config func(*cli.Context, Config) Config
App *cli.App
}
// NewAppShell creates a basic AppShell object containing CLI metadata
@ -130,16 +134,17 @@ func NewAppShell(name string) (shell *AppShell) {
Name: "config",
Value: "config.json",
EnvVar: "BOULDER_CONFIG",
Usage: "Path to Config JSON",
},
}
return &AppShell{app: app}
return &AppShell{App: app}
}
// Run begins the application context, reading config and passing
// control to the default commandline action.
func (as *AppShell) Run() {
as.app.Action = func(c *cli.Context) {
as.App.Action = func(c *cli.Context) {
configFileName := c.GlobalString("config")
configJSON, err := ioutil.ReadFile(configFileName)
FailOnError(err, "Unable to read config file")
@ -148,13 +153,22 @@ func (as *AppShell) Run() {
err = json.Unmarshal(configJSON, &config)
FailOnError(err, "Failed to read configuration")
if as.Config != nil {
config = as.Config(c, config)
}
as.Action(config)
}
err := as.app.Run(os.Args)
err := as.App.Run(os.Args)
FailOnError(err, "Failed to run application")
}
// VersionString produces a friendly Application version string
func (as *AppShell) VersionString() string {
return fmt.Sprintf("%s (version %s, build %s)", as.App.Name, as.App.Version, core.GetBuildID())
}
// FailOnError exits and prints an error message if we encountered a problem
func FailOnError(err error, msg string) {
if err != nil {

View File

@ -80,6 +80,7 @@ type CertificateAuthority interface {
// [RegistrationAuthority]
IssueCertificate(x509.CertificateRequest, int64) (Certificate, error)
RevokeCertificate(string, int) error
GenerateOCSP(OCSPSigningRequest) ([]byte, error)
}
type PolicyAuthority interface {
@ -101,7 +102,7 @@ type StorageAdder interface {
NewRegistration(Registration) (Registration, error)
UpdateRegistration(Registration) error
NewPendingAuthorization() (string, error)
NewPendingAuthorization(Authorization) (Authorization, error)
UpdatePendingAuthorization(Authorization) error
FinalizeAuthorization(Authorization) error
MarkCertificateRevoked(serial string, ocspResponse []byte, reasonCode int) error

View File

@ -100,7 +100,7 @@ func (cr CertificateRequest) MarshalJSON() ([]byte, error) {
// to account keys.
type Registration struct {
// Unique identifier
ID int64 `json:"-" db:"id"`
ID int64 `json:"id" db:"id"`
// Account key to which the details are attached
Key jose.JsonWebKey `json:"key" db:"jwk"`
@ -250,7 +250,7 @@ func (ch Challenge) MergeResponse(resp Challenge) Challenge {
// of an account key holder to act on behalf of a domain. This
// struct is intended to be used both internally and for JSON
// marshaling on the wire. Any fields that should be suppressed
// on the wire (e.g., ID) must be made empty before marshaling.
// on the wire (e.g., ID, regID) must be made empty before marshaling.
type Authorization struct {
// An identifier for this authorization, unique across
// authorizations and certificates within this instance.
@ -260,7 +260,7 @@ type Authorization struct {
Identifier AcmeIdentifier `json:"identifier,omitempty" db:"identifier"`
// The registration ID associated with the authorization
RegistrationID int64 `json:"-" db:"registrationID"`
RegistrationID int64 `json:"regId,omitempty" db:"registrationID"`
// The status of the validation of this authorization
Status AcmeStatus `json:"status,omitempty" db:"status"`
@ -321,9 +321,6 @@ func (jb *JsonBuffer) UnmarshalJSON(data []byte) (err error) {
type Certificate struct {
RegistrationID int64 `db:"registrationID"`
// The parsed version of DER. Useful for extracting things like serial number.
ParsedCertificate *x509.Certificate `db:"-"`
// The revocation status of the certificate.
// * "valid" - not revoked
// * "revoked" - revoked
@ -400,3 +397,11 @@ type DeniedCsr struct {
Names string `db:"names"`
}
// OCSPSigningRequest is a transfer object representing an OCSP Signing Request
type OCSPSigningRequest struct {
CertDER []byte
Status string
Reason int
RevokedAt time.Time
}

View File

@ -28,6 +28,12 @@ import (
"strings"
)
// Package Variables Variables
// BuildID is set by the compiler (using -ldflags "-X core.BuildID $(git rev-parse --short HEAD)")
// and is used by GetBuildID
var BuildID string
// Errors
type NotSupportedError string
@ -232,3 +238,12 @@ func StringToSerial(serial string) (*big.Int, error) {
_, err := fmt.Sscanf(serial, "%032x", &serialNum)
return &serialNum, err
}
// GetBuildID identifies what build is running.
func GetBuildID() (retID string) {
retID = BuildID
if retID == "" {
retID = "Unspecified"
}
return
}

View File

@ -48,3 +48,7 @@ func TestSerialUtils(t *testing.T) {
test.AssertEquals(t, fmt.Sprintf("%v", err), "Serial number should be 32 characters long")
fmt.Println(badSerial)
}
func TestBuildID(t *testing.T) {
test.AssertEquals(t, "Unspecified", GetBuildID())
}

View File

@ -11,6 +11,8 @@ import (
"fmt"
"log/syslog"
"os"
"runtime"
"strings"
"sync"
"time"
@ -149,12 +151,21 @@ func (log *AuditLogger) auditAtLevel(level, msg string) (err error) {
return log.logAtLevel(level, text)
}
// Return short format caller info for panic events, skipping to before the
// panic handler.
func caller(level int) string {
_, file, line, _ := runtime.Caller(level)
splits := strings.Split(file, "/")
filename := splits[len(splits)-1]
return fmt.Sprintf("%s:%d:", filename, line)
}
// AuditPanic catches panicking executables. This method should be added
// in a defer statement as early as possible
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) AuditPanic() {
if err := recover(); err != nil {
log.Audit(fmt.Sprintf("Panic: %v", err))
log.Audit(fmt.Sprintf("Panic: %s %v", caller(4), err))
}
}

View File

@ -87,16 +87,27 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
return authz, err
}
// Create validations
// TODO: Assign URLs
// Create validations, but we have to update them with URIs later
challenges, combinations := ra.PA.ChallengesFor(identifier)
authID, err := ra.SA.NewPendingAuthorization()
// Partially-filled object
authz = core.Authorization{
Identifier: identifier,
RegistrationID: regID,
Status: core.StatusPending,
Combinations: combinations,
}
// Get a pending Auth first so we can get our ID back, then update with challenges
authz, err = ra.SA.NewPendingAuthorization(authz)
if err != nil {
return authz, err
}
// Construct all the challenge URIs
for i := range challenges {
// Ignoring these errors because we construct the URLs to be correct
challengeURI, _ := url.Parse(ra.AuthzBase + authID + "?challenge=" + strconv.Itoa(i))
challengeURI, _ := url.Parse(ra.AuthzBase + authz.ID + "?challenge=" + strconv.Itoa(i))
challenges[i].URI = core.AcmeURL(*challengeURI)
if !challenges[i].IsSane(false) {
@ -105,15 +116,8 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
}
}
// Create a new authorization object
authz = core.Authorization{
ID: authID,
Identifier: identifier,
RegistrationID: regID,
Status: core.StatusPending,
Challenges: challenges,
Combinations: combinations,
}
// Update object
authz.Challenges = challenges
// Store the authorization object, then return it
err = ra.SA.UpdatePendingAuthorization(authz)
@ -238,12 +242,16 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
return emptyCert, nil
}
cert.ParsedCertificate, err = x509.ParseCertificate([]byte(cert.DER))
parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
if err != nil {
logEvent.Error = err.Error()
return emptyCert, err
}
logEvent.SerialNumber = cert.ParsedCertificate.SerialNumber
logEvent.CommonName = cert.ParsedCertificate.Subject.CommonName
logEvent.NotBefore = cert.ParsedCertificate.NotBefore
logEvent.NotAfter = cert.ParsedCertificate.NotAfter
logEvent.SerialNumber = parsedCertificate.SerialNumber
logEvent.CommonName = parsedCertificate.Subject.CommonName
logEvent.NotBefore = parsedCertificate.NotBefore
logEvent.NotAfter = parsedCertificate.NotAfter
logEvent.ResponseTime = time.Now()
logEventResult = "successful"

View File

@ -189,7 +189,7 @@ func TestNewAuthorization(t *testing.T) {
func TestUpdateAuthorization(t *testing.T) {
_, va, sa, ra := initAuthorities(t)
AuthzInitial.ID, _ = sa.NewPendingAuthorization()
AuthzInitial, _ = sa.NewPendingAuthorization(AuthzInitial)
sa.UpdatePendingAuthorization(AuthzInitial)
authz, err := ra.UpdateAuthorization(AuthzInitial, ResponseIndex, Response)
@ -218,7 +218,7 @@ func TestUpdateAuthorization(t *testing.T) {
func TestOnValidationUpdate(t *testing.T) {
_, _, sa, ra := initAuthorities(t)
AuthzUpdated.ID, _ = sa.NewPendingAuthorization()
AuthzUpdated, _ = sa.NewPendingAuthorization(AuthzUpdated)
sa.UpdatePendingAuthorization(AuthzUpdated)
// Simulate a successful simpleHTTPS challenge
@ -245,7 +245,7 @@ func TestOnValidationUpdate(t *testing.T) {
func TestCertificateKeyNotEqualAccountKey(t *testing.T) {
_, _, sa, ra := initAuthorities(t)
authz := core.Authorization{}
authz.ID, _ = sa.NewPendingAuthorization()
authz, _ = sa.NewPendingAuthorization(authz)
authz.Identifier = core.AcmeIdentifier{
Type: core.IdentifierDNS,
Value: "www.example.com",
@ -276,14 +276,14 @@ func TestCertificateKeyNotEqualAccountKey(t *testing.T) {
func TestNewCertificate(t *testing.T) {
_, _, sa, ra := initAuthorities(t)
AuthzFinal.RegistrationID = 1
AuthzFinal.ID, _ = sa.NewPendingAuthorization()
AuthzFinal, _ = sa.NewPendingAuthorization(AuthzFinal)
sa.UpdatePendingAuthorization(AuthzFinal)
sa.FinalizeAuthorization(AuthzFinal)
// Inject another final authorization to cover www.example.com
AuthzFinalWWW = AuthzFinal
AuthzFinalWWW.Identifier.Value = "www.example.com"
AuthzFinalWWW.ID, _ = sa.NewPendingAuthorization()
AuthzFinalWWW, _ = sa.NewPendingAuthorization(AuthzFinalWWW)
sa.FinalizeAuthorization(AuthzFinalWWW)
// Construct a cert request referencing the two authorizations

View File

@ -8,6 +8,7 @@ package rpc
import (
"errors"
"fmt"
"os"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
@ -147,14 +148,14 @@ func (rpc *AmqpRPCServer) Start() (err error) {
for msg := range msgs {
// XXX-JWS: jws.Verify(body)
cb, present := rpc.dispatchTable[msg.Type]
rpc.log.Info(fmt.Sprintf(" [s<] received %s(%s) [%s]", msg.Type, core.B64enc(msg.Body), msg.CorrelationId))
rpc.log.Info(fmt.Sprintf(" [s<][%s][%s] received %s(%s) [%s]", rpc.serverQueue, msg.ReplyTo, msg.Type, core.B64enc(msg.Body), msg.CorrelationId))
if !present {
// AUDIT[ Misrouted Messages ] f523f21f-12d2-4c31-b2eb-ee4b7d96d60e
rpc.log.Audit(fmt.Sprintf("Misrouted message: %s - %s - %s", msg.Type, core.B64enc(msg.Body), msg.CorrelationId))
rpc.log.Audit(fmt.Sprintf(" [s<][%s][%s] Misrouted message: %s - %s - %s", rpc.serverQueue, msg.ReplyTo, msg.Type, core.B64enc(msg.Body), msg.CorrelationId))
continue
}
response := cb(msg.Body)
rpc.log.Info(fmt.Sprintf(" [s>] sending %s(%s) [%s]", msg.Type, core.B64enc(response), msg.CorrelationId))
rpc.log.Info(fmt.Sprintf(" [s>][%s][%s] replying %s(%s) [%s]", rpc.serverQueue, msg.ReplyTo, msg.Type, core.B64enc(response), msg.CorrelationId))
rpc.channel.Publish(
AmqpExchange,
msg.ReplyTo,
@ -197,7 +198,14 @@ type AmqpRPCCLient struct {
log *blog.AuditLogger
}
func NewAmqpRPCCLient(clientQueue, serverQueue string, channel *amqp.Channel) (rpc *AmqpRPCCLient, err error) {
func NewAmqpRPCCLient(clientQueuePrefix, serverQueue string, channel *amqp.Channel) (rpc *AmqpRPCCLient, err error) {
hostname, err := os.Hostname()
if err != nil {
return nil, err
}
clientQueue := fmt.Sprintf("%s.%s", clientQueuePrefix, hostname)
rpc = &AmqpRPCCLient{
serverQueue: serverQueue,
clientQueue: clientQueue,
@ -210,7 +218,7 @@ func NewAmqpRPCCLient(clientQueue, serverQueue string, channel *amqp.Channel) (r
// Subscribe to the response queue and dispatch
msgs, err := amqpSubscribe(rpc.channel, clientQueue, nil)
if err != nil {
return
return nil, err
}
go func() {
@ -219,8 +227,10 @@ func NewAmqpRPCCLient(clientQueue, serverQueue string, channel *amqp.Channel) (r
corrID := msg.CorrelationId
responseChan, present := rpc.pending[corrID]
rpc.log.Debug(fmt.Sprintf(" [c<] received %s(%s) [%s]", msg.Type, core.B64enc(msg.Body), corrID))
rpc.log.Debug(fmt.Sprintf(" [c<][%s] response %s(%s) [%s]", clientQueue, msg.Type, core.B64enc(msg.Body), corrID))
if !present {
// AUDIT[ Misrouted Messages ] f523f21f-12d2-4c31-b2eb-ee4b7d96d60e
rpc.log.Audit(fmt.Sprintf(" [c<][%s] Misrouted message: %s - %s - %s", clientQueue, msg.Type, core.B64enc(msg.Body), msg.CorrelationId))
continue
}
responseChan <- msg.Body
@ -228,7 +238,7 @@ func NewAmqpRPCCLient(clientQueue, serverQueue string, channel *amqp.Channel) (r
}
}()
return
return rpc, err
}
func (rpc *AmqpRPCCLient) SetTimeout(ttl time.Duration) {
@ -244,7 +254,7 @@ func (rpc *AmqpRPCCLient) Dispatch(method string, body []byte) chan []byte {
rpc.pending[corrID] = responseChan
// Send the request
rpc.log.Debug(fmt.Sprintf(" [c>] sending %s(%s) [%s]", method, core.B64enc(body), corrID))
rpc.log.Debug(fmt.Sprintf(" [c>][%s] requesting %s(%s) [%s]", rpc.clientQueue, method, core.B64enc(body), corrID))
rpc.channel.Publish(
AmqpExchange,
rpc.serverQueue,
@ -265,7 +275,7 @@ func (rpc *AmqpRPCCLient) DispatchSync(method string, body []byte) (response []b
case response = <-rpc.Dispatch(method, body):
return
case <-time.After(rpc.timeout):
rpc.log.Warning(fmt.Sprintf(" [c!] AMQP-RPC timeout [%s]", method))
rpc.log.Warning(fmt.Sprintf(" [c!][%s] AMQP-RPC timeout [%s]", rpc.clientQueue, method))
err = errors.New("AMQP-RPC timeout")
return
}

View File

@ -38,11 +38,11 @@ const (
MethodNewCertificate = "NewCertificate" // RA
MethodUpdateRegistration = "UpdateRegistration" // RA, SA
MethodUpdateAuthorization = "UpdateAuthorization" // RA
MethodRevokeCertificate = "RevokeCertificate" // RA
MethodRevokeCertificate = "RevokeCertificate" // RA, CA
MethodOnValidationUpdate = "OnValidationUpdate" // RA
MethodUpdateValidations = "UpdateValidations" // VA
MethodIssueCertificate = "IssueCertificate" // CA
MethodRevokeCertificateCA = "RevokeCertificateCA" // CA
MethodGenerateOCSP = "GenerateOCSP" // CA
MethodGetRegistration = "GetRegistration" // SA
MethodGetRegistrationByKey = "GetRegistrationByKey" // RA, SA
MethodGetAuthorization = "GetAuthorization" // SA
@ -108,7 +108,9 @@ func NewRegistrationAuthorityServer(serverQueue string, channel *amqp.Channel, i
response, err = json.Marshal(reg)
if err != nil {
response = []byte{}
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodNewRegistration, err, req)
return nil
}
return response
})
@ -130,6 +132,8 @@ func NewRegistrationAuthorityServer(serverQueue string, channel *amqp.Channel, i
response, err = json.Marshal(authz)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodNewAuthorization, err, req)
return nil
}
return response
@ -155,6 +159,8 @@ func NewRegistrationAuthorityServer(serverQueue string, channel *amqp.Channel, i
response, err := json.Marshal(cert)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodNewCertificate, err, req)
return nil
}
return response
@ -180,7 +186,9 @@ func NewRegistrationAuthorityServer(serverQueue string, channel *amqp.Channel, i
response, err = json.Marshal(reg)
if err != nil {
response = []byte{}
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodUpdateRegistration, err, req)
return nil
}
return response
})
@ -207,6 +215,8 @@ func NewRegistrationAuthorityServer(serverQueue string, channel *amqp.Channel, i
response, err = json.Marshal(newAuthz)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodUpdateAuthorization, err, req)
return nil
}
return response
@ -432,6 +442,8 @@ func NewCertificateAuthorityServer(serverQueue string, channel *amqp.Channel, im
}
err := json.Unmarshal(req, &icReq)
if err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodIssueCertificate, err, req)
return nil
}
@ -451,30 +463,53 @@ func NewCertificateAuthorityServer(serverQueue string, channel *amqp.Channel, im
serialized, err := json.Marshal(cert)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetRegistration, err, req)
return nil // XXX
}
return serialized
})
rpc.Handle(MethodRevokeCertificateCA, func(req []byte) []byte {
rpc.Handle(MethodRevokeCertificate, func(req []byte) []byte {
var revokeReq struct {
Serial string
ReasonCode int
}
err := json.Unmarshal(req, &revokeReq)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodRevokeCertificate, err, req)
return nil
}
if err := impl.RevokeCertificate(revokeReq.Serial, revokeReq.ReasonCode); err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodRevokeCertificateCA, err, req)
errorCondition(MethodRevokeCertificate, err, req)
}
return nil
})
rpc.Handle(MethodGenerateOCSP, func(req []byte) []byte {
var xferObj core.OCSPSigningRequest
err := json.Unmarshal(req, &xferObj)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGenerateOCSP, err, req)
return nil
}
data, err := impl.GenerateOCSP(xferObj)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGenerateOCSP, err, req)
return nil
}
return data
})
return
}
@ -524,22 +559,54 @@ func (cac CertificateAuthorityClient) RevokeCertificate(serial string, reasonCod
data, err := json.Marshal(revokeReq)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodRevokeCertificate, err, revokeReq)
return
}
_, err = cac.rpc.DispatchSync(MethodRevokeCertificateCA, data)
_, err = cac.rpc.DispatchSync(MethodRevokeCertificate, data)
return
}
func (cac CertificateAuthorityClient) GenerateOCSP(signRequest core.OCSPSigningRequest) (resp []byte, err error) {
data, err := json.Marshal(signRequest)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetRegistration, err, signRequest)
return
}
resp, err = cac.rpc.DispatchSync(MethodGenerateOCSP, data)
return
}
func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl core.StorageAuthority) *AmqpRPCServer {
rpc := NewAmqpRPCServer(serverQueue, channel)
rpc.Handle(MethodUpdateRegistration, func(req []byte) (response []byte) {
var reg core.Registration
if err := json.Unmarshal(req, &reg); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodUpdateRegistration, err, req)
return nil
}
if err := impl.UpdateRegistration(reg); err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodUpdateRegistration, err, req)
}
return nil
})
rpc.Handle(MethodGetRegistration, func(req []byte) (response []byte) {
var intReq struct {
ID int64
}
err := json.Unmarshal(req, &intReq)
if err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodGetRegistration, err, req)
return nil
}
@ -552,6 +619,8 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
response, err = json.Marshal(reg)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetRegistration, err, req)
return nil
}
return response
@ -562,6 +631,7 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
if err := json.Unmarshal(req, &jwk); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodGetRegistrationByKey, err, req)
return nil
}
reg, err := impl.GetRegistrationByKey(jwk)
@ -573,6 +643,8 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
response, err = json.Marshal(reg)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetRegistrationByKey, err, req)
return nil
}
return response
@ -588,6 +660,8 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
jsonAuthz, err := json.Marshal(authz)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetAuthorization, err, req)
return nil
}
return jsonAuthz
@ -600,6 +674,8 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
}
err := json.Unmarshal(req, &icReq)
if err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodAddCertificate, err, req)
return nil
}
@ -612,9 +688,9 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
return []byte(id)
})
rpc.Handle(MethodNewRegistration, func(req []byte) (response []byte) {
rpc.Handle(MethodNewRegistration, func(req []byte) []byte {
var registration core.Registration
err := json.Unmarshal(req, registration)
err := json.Unmarshal(req, &registration)
if err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodNewRegistration, err, req)
@ -630,25 +706,40 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
jsonOutput, err := json.Marshal(output)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodNewRegistration, err, req)
return nil
}
return []byte(jsonOutput)
})
rpc.Handle(MethodNewPendingAuthorization, func(req []byte) (response []byte) {
id, err := impl.NewPendingAuthorization()
rpc.Handle(MethodNewPendingAuthorization, func(req []byte) []byte {
var authz core.Authorization
if err := json.Unmarshal(req, &authz); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodNewPendingAuthorization, err, req)
return nil
}
output, err := impl.NewPendingAuthorization(authz)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodNewPendingAuthorization, err, req)
} else {
response = []byte(id)
return nil
}
return response
jsonOutput, err := json.Marshal(output)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodNewPendingAuthorization, err, req)
return nil
}
return []byte(jsonOutput)
})
rpc.Handle(MethodUpdatePendingAuthorization, func(req []byte) []byte {
var authz core.Authorization
if err := json.Unmarshal(req, authz); err != nil {
if err := json.Unmarshal(req, &authz); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodUpdatePendingAuthorization, err, req)
return nil
@ -663,7 +754,7 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
rpc.Handle(MethodFinalizeAuthorization, func(req []byte) []byte {
var authz core.Authorization
if err := json.Unmarshal(req, authz); err != nil {
if err := json.Unmarshal(req, &authz); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodFinalizeAuthorization, err, req)
return nil
@ -708,6 +799,8 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
jsonStatus, err := json.Marshal(status)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetCertificateStatus, err, req)
return nil
}
return jsonStatus
@ -720,7 +813,7 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
ReasonCode int
}
if err := json.Unmarshal(req, revokeReq); err != nil {
if err := json.Unmarshal(req, &revokeReq); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodMarkCertificateRevoked, err, req)
return nil
@ -740,7 +833,7 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
Names []string
}
err := json.Unmarshal(req, csrReq)
err := json.Unmarshal(req, &csrReq)
if err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodAlreadyDeniedCSR, err, req)
@ -804,7 +897,7 @@ func (cac StorageAuthorityClient) GetRegistrationByKey(key jose.JsonWebKey) (reg
return
}
jsonReg, err := cac.rpc.DispatchSync(MethodGetRegistration, jsonKey)
jsonReg, err := cac.rpc.DispatchSync(MethodGetRegistrationByKey, jsonKey)
if err != nil {
return
}
@ -885,7 +978,7 @@ func (cac StorageAuthorityClient) NewRegistration(reg core.Registration) (output
err = errors.New("NewRegistration RPC failed") // XXX
return
}
err = json.Unmarshal(response, output)
err = json.Unmarshal(response, &output)
if err != nil {
err = errors.New("NewRegistration RPC failed")
return
@ -893,13 +986,21 @@ func (cac StorageAuthorityClient) NewRegistration(reg core.Registration) (output
return output, nil
}
func (cac StorageAuthorityClient) NewPendingAuthorization() (id string, err error) {
response, err := cac.rpc.DispatchSync(MethodNewPendingAuthorization, nil)
func (cac StorageAuthorityClient) NewPendingAuthorization(authz core.Authorization) (output core.Authorization, err error) {
jsonAuthz, err := json.Marshal(authz)
if err != nil {
return
}
response, err := cac.rpc.DispatchSync(MethodNewPendingAuthorization, jsonAuthz)
if err != nil || len(response) == 0 {
err = errors.New("NewPendingAuthorization RPC failed") // XXX
return
}
id = string(response)
err = json.Unmarshal(response, &output)
if err != nil {
err = errors.New("NewRegistration RPC failed")
return
}
return
}

75
sa/database.go Normal file
View File

@ -0,0 +1,75 @@
// Copyright 2015 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package sa
import (
"database/sql"
"fmt"
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
)
var dialectMap map[string]interface{} = map[string]interface{}{
"sqlite3": gorp.SqliteDialect{},
"mysql": gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"},
"postgres": gorp.PostgresDialect{},
}
// NewDbMap creates the root gorp mapping object. Create one of these for each
// database schema you wish to map. Each DbMap contains a list of mapped tables.
// It automatically maps the tables for the primary parts of Boulder around the
// Storage Authority. This may require some further work when we use a disjoint
// schema, like that for `certificate-authority-data.go`.
func NewDbMap(driver string, name string) (*gorp.DbMap, error) {
logger := blog.GetAuditLogger()
db, err := sql.Open(driver, name)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
logger.Debug(fmt.Sprintf("Connecting to database %s %s", driver, name))
dialect, ok := dialectMap[driver].(gorp.Dialect)
if !ok {
err = fmt.Errorf("Couldn't find dialect for %s", driver)
return nil, err
}
logger.Info(fmt.Sprintf("Connected to database %s %s", driver, name))
dbmap := &gorp.DbMap{Db: db, Dialect: dialect, TypeConverter: BoulderTypeConverter{}}
initTables(dbmap)
return dbmap, err
}
// initTables constructs the table map for the ORM. If you want to also create
// the tables, call CreateTablesIfNotExists on the DbMap.
func initTables(dbMap *gorp.DbMap) {
regTable := dbMap.AddTableWithName(core.Registration{}, "registrations").SetKeys(true, "ID")
regTable.SetVersionCol("LockCol")
regTable.ColMap("Key").SetMaxSize(1024).SetNotNull(true)
pendingAuthzTable := dbMap.AddTableWithName(pendingauthzModel{}, "pending_authz").SetKeys(false, "ID")
pendingAuthzTable.SetVersionCol("LockCol")
pendingAuthzTable.ColMap("Challenges").SetMaxSize(1536)
authzTable := dbMap.AddTableWithName(authzModel{}, "authz").SetKeys(false, "ID")
authzTable.ColMap("Challenges").SetMaxSize(1536)
dbMap.AddTableWithName(core.Certificate{}, "certificates").SetKeys(false, "Serial")
dbMap.AddTableWithName(core.CertificateStatus{}, "certificateStatus").SetKeys(false, "Serial").SetVersionCol("LockCol")
dbMap.AddTableWithName(core.OcspResponse{}, "ocspResponses").SetKeys(true, "ID")
dbMap.AddTableWithName(core.Crl{}, "crls").SetKeys(false, "Serial")
dbMap.AddTableWithName(core.DeniedCsr{}, "deniedCsrs").SetKeys(true, "ID")
}

6
sa/database_test.go Normal file
View File

@ -0,0 +1,6 @@
// Copyright 2015 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package sa

View File

@ -8,7 +8,6 @@ package sa
import (
"crypto/sha256"
"crypto/x509"
"database/sql"
"encoding/json"
"errors"
"fmt"
@ -35,12 +34,6 @@ func digest256(data []byte) []byte {
return d.Sum(nil)
}
var dialectMap map[string]interface{} = map[string]interface{}{
"sqlite3": gorp.SqliteDialect{},
"mysql": gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"},
"postgres": gorp.PostgresDialect{},
}
// Utility models
type pendingauthzModel struct {
core.Authorization
@ -54,111 +47,6 @@ type authzModel struct {
Sequence int64 `db:"sequence"`
}
// Type converter
type boulderTypeConverter struct{}
func (tc boulderTypeConverter) ToDb(val interface{}) (interface{}, error) {
switch t := val.(type) {
case core.AcmeIdentifier, []core.Challenge, []core.AcmeURL, [][]int:
jsonBytes, err := json.Marshal(t)
if err != nil {
return nil, err
}
return string(jsonBytes), nil
case jose.JsonWebKey:
// HACK: Some of our storage methods, like NewAuthorization, expect to
// write to the DB with the default, empty key, so we treat it specially,
// serializing to an empty string. TODO: Modify authorizations to refer
// to a registration id, and make sure registration ids are always filled.
// https://github.com/letsencrypt/boulder/issues/181
if t.Key == nil {
return "", nil
}
jsonBytes, err := t.MarshalJSON()
if err != nil {
return "", err
}
return string(jsonBytes), nil
case core.AcmeStatus:
return string(t), nil
case core.OCSPStatus:
return string(t), nil
default:
return val, nil
}
}
func (tc boulderTypeConverter) FromDb(target interface{}) (gorp.CustomScanner, bool) {
switch target.(type) {
case *core.AcmeIdentifier, *[]core.Challenge, *[]core.AcmeURL, *[][]int, core.JsonBuffer:
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
if !ok {
return errors.New("FromDb: Unable to convert *string")
}
b := []byte(*s)
return json.Unmarshal(b, target)
}
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
case *jose.JsonWebKey:
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
if !ok {
return errors.New("FromDb: Unable to convert *string")
}
b := []byte(*s)
k := target.(*jose.JsonWebKey)
if *s != "" {
return k.UnmarshalJSON(b)
} else {
// HACK: Sometimes we can have an empty string the in the DB where a
// key should be. We should fix that (see HACK above). In the meantime,
// return the default JsonWebKey in such situations.
// https://github.com/letsencrypt/boulder/issues/181
return nil
}
}
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
case *core.AcmeStatus:
binder := func(holder, target interface{}) error {
s := holder.(*string)
st := target.(*core.AcmeStatus)
*st = core.AcmeStatus(*s)
return nil
}
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
case *core.OCSPStatus:
binder := func(holder, target interface{}) error {
s := holder.(*string)
st := target.(*core.OCSPStatus)
*st = core.OCSPStatus(*s)
return nil
}
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
default:
return gorp.CustomScanner{}, false
}
}
func NewDbMap(driver, dbName string) (dbMap *gorp.DbMap, err error) {
db, err := sql.Open(driver, dbName)
if err != nil {
return
}
if err = db.Ping(); err != nil {
return
}
dialect, ok := dialectMap[driver].(gorp.Dialect)
if !ok {
err = fmt.Errorf("Couldn't find dialect for %s", driver)
return
}
dbMap = &gorp.DbMap{Db: db, Dialect: dialect, TypeConverter: boulderTypeConverter{}}
return
}
// SQLLogger adapts the AuditLogger to a format GORP can use.
type SQLLogger struct {
log *blog.AuditLogger
@ -185,7 +73,6 @@ func NewSQLStorageAuthority(driver string, name string) (ssa *SQLStorageAuthorit
bucket: make(map[string]interface{}),
}
ssa.initTables()
return
}
@ -199,27 +86,6 @@ func (ssa *SQLStorageAuthority) SetSQLDebug(state bool) {
}
}
// initTables constructs the table map for the ORM. If you want to also create
// the tables, call CreateTablesIfNotExists.
func (ssa *SQLStorageAuthority) initTables() {
regTable := ssa.dbMap.AddTableWithName(core.Registration{}, "registrations").SetKeys(true, "ID")
regTable.SetVersionCol("LockCol")
regTable.ColMap("Key").SetMaxSize(1024).SetNotNull(true)
pendingAuthzTable := ssa.dbMap.AddTableWithName(pendingauthzModel{}, "pending_authz").SetKeys(false, "ID")
pendingAuthzTable.SetVersionCol("LockCol")
pendingAuthzTable.ColMap("Challenges").SetMaxSize(1536)
authzTable := ssa.dbMap.AddTableWithName(authzModel{}, "authz").SetKeys(false, "ID")
authzTable.ColMap("Challenges").SetMaxSize(1536)
ssa.dbMap.AddTableWithName(core.Certificate{}, "certificates").SetKeys(false, "Serial")
ssa.dbMap.AddTableWithName(core.CertificateStatus{}, "certificateStatus").SetKeys(false, "Serial").SetVersionCol("LockCol")
ssa.dbMap.AddTableWithName(core.OcspResponse{}, "ocspResponses").SetKeys(true, "ID")
ssa.dbMap.AddTableWithName(core.Crl{}, "crls").SetKeys(false, "Serial")
ssa.dbMap.AddTableWithName(core.DeniedCsr{}, "deniedCsrs").SetKeys(true, "ID")
}
// CreateTablesIfNotExists instructs the ORM to create any missing tables.
func (ssa *SQLStorageAuthority) CreateTablesIfNotExists() (err error) {
err = ssa.dbMap.CreateTablesIfNotExists()
@ -358,7 +224,12 @@ func (ssa *SQLStorageAuthority) GetRegistration(id int64) (reg core.Registration
err = fmt.Errorf("No registrations with ID %d", id)
return
}
reg = *regObj.(*core.Registration)
regPtr, ok := regObj.(*core.Registration)
if !ok {
err = fmt.Errorf("Invalid cast")
}
reg = *regPtr
return
}
@ -430,19 +301,19 @@ func (ssa *SQLStorageAuthority) GetCertificateByShortSerial(shortSerial string)
// GetCertificate takes a serial number and returns the corresponding
// certificate, or error if it does not exist.
func (ssa *SQLStorageAuthority) GetCertificate(serial string) (cert []byte, err error) {
func (ssa *SQLStorageAuthority) GetCertificate(serial string) ([]byte, error) {
if len(serial) != 32 {
err = errors.New("Invalid certificate serial " + serial)
return
err := fmt.Errorf("Invalid certificate serial %s", serial)
return nil, err
}
var certificate core.Certificate
err = ssa.dbMap.SelectOne(&certificate, "SELECT * FROM certificates WHERE serial = :serial",
map[string]interface{}{"serial": serial})
certObj, err := ssa.dbMap.Get(core.Certificate{}, serial)
if err != nil {
return
return nil, err
}
return certificate.DER, nil
cert := certObj.(*core.Certificate)
return cert.DER, err
}
// GetCertificateStatus takes a hexadecimal string representing the full 128-bit serial
@ -463,16 +334,16 @@ func (ssa *SQLStorageAuthority) GetCertificateStatus(serial string) (status core
return
}
func (ssa *SQLStorageAuthority) NewRegistration(reg core.Registration) (output core.Registration, err error) {
func (ssa *SQLStorageAuthority) NewRegistration(reg core.Registration) (core.Registration, error) {
tx, err := ssa.dbMap.Begin()
if err != nil {
return
return reg, err
}
err = tx.Insert(&reg)
if err != nil {
tx.Rollback()
return
return reg, err
}
err = tx.Commit()
@ -552,27 +423,28 @@ func (ssa *SQLStorageAuthority) UpdateRegistration(reg core.Registration) (err e
return
}
func (ssa *SQLStorageAuthority) NewPendingAuthorization() (id string, err error) {
func (ssa *SQLStorageAuthority) NewPendingAuthorization(authz core.Authorization) (output core.Authorization, err error) {
tx, err := ssa.dbMap.Begin()
if err != nil {
return
}
// Check that it doesn't exist already
id = core.NewToken()
for existingPending(tx, id) || existingFinal(tx, id) {
id = core.NewToken()
authz.ID = core.NewToken()
for existingPending(tx, authz.ID) || existingFinal(tx, authz.ID) {
authz.ID = core.NewToken()
}
// Insert a stub row in pending
pending_authz := &pendingauthzModel{Authorization: core.Authorization{ID: id}}
err = tx.Insert(pending_authz)
pending_authz := pendingauthzModel{Authorization: authz}
err = tx.Insert(&pending_authz)
if err != nil {
tx.Rollback()
return
}
err = tx.Commit()
output = pending_authz.Authorization
return
}

View File

@ -91,15 +91,18 @@ func TestAddRegistration(t *testing.T) {
func TestAddAuthorization(t *testing.T) {
sa := initSA(t)
paID, err := sa.NewPendingAuthorization()
PA := core.Authorization{}
PA, err := sa.NewPendingAuthorization(PA)
test.AssertNotError(t, err, "Couldn't create new pending authorization")
test.Assert(t, paID != "", "ID shouldn't be blank")
test.Assert(t, PA.ID != "", "ID shouldn't be blank")
dbPa, err := sa.GetAuthorization(paID)
test.AssertNotError(t, err, "Couldn't get pending authorization with ID "+paID)
dbPa, err := sa.GetAuthorization(PA.ID)
test.AssertNotError(t, err, "Couldn't get pending authorization with ID "+PA.ID)
test.AssertMarshaledEquals(t, PA, dbPa)
expectedPa := core.Authorization{ID: paID}
test.AssertEquals(t, dbPa.ID, expectedPa.ID)
expectedPa := core.Authorization{ID: PA.ID}
test.AssertMarshaledEquals(t, dbPa.ID, expectedPa.ID)
var jwk jose.JsonWebKey
err = json.Unmarshal([]byte(theKey), &jwk)
@ -116,16 +119,16 @@ func TestAddAuthorization(t *testing.T) {
combos := make([][]int, 1)
combos[0] = []int{0, 1}
newPa := core.Authorization{ID: paID, Identifier: core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "wut.com"}, RegistrationID: 0, Status: core.StatusPending, Expires: time.Now().AddDate(0, 0, 1), Challenges: []core.Challenge{chall}, Combinations: combos, Contact: []core.AcmeURL{u}}
newPa := core.Authorization{ID: PA.ID, Identifier: core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "wut.com"}, RegistrationID: 0, Status: core.StatusPending, Expires: time.Now().AddDate(0, 0, 1), Challenges: []core.Challenge{chall}, Combinations: combos, Contact: []core.AcmeURL{u}}
err = sa.UpdatePendingAuthorization(newPa)
test.AssertNotError(t, err, "Couldn't update pending authorization with ID "+paID)
test.AssertNotError(t, err, "Couldn't update pending authorization with ID "+PA.ID)
newPa.Status = core.StatusValid
err = sa.FinalizeAuthorization(newPa)
test.AssertNotError(t, err, "Couldn't finalize pending authorization with ID "+paID)
test.AssertNotError(t, err, "Couldn't finalize pending authorization with ID "+PA.ID)
dbPa, err = sa.GetAuthorization(paID)
test.AssertNotError(t, err, "Couldn't get authorization with ID "+paID)
dbPa, err = sa.GetAuthorization(PA.ID)
test.AssertNotError(t, err, "Couldn't get authorization with ID "+PA.ID)
}
func TestAddCertificate(t *testing.T) {

102
sa/type-converter.go Normal file
View File

@ -0,0 +1,102 @@
// Copyright 2015 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package sa
import (
"encoding/json"
"errors"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/square/go-jose"
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
"github.com/letsencrypt/boulder/core"
)
// BoulderTypeConverter is used by Gorp for storing objects in DB.
type BoulderTypeConverter struct{}
// ToDb converts a Boulder object to one suitable for the DB representation.
func (tc BoulderTypeConverter) ToDb(val interface{}) (interface{}, error) {
switch t := val.(type) {
case core.AcmeIdentifier, []core.Challenge, []core.AcmeURL, [][]int:
jsonBytes, err := json.Marshal(t)
if err != nil {
return nil, err
}
return string(jsonBytes), nil
case jose.JsonWebKey:
// HACK: Some of our storage methods, like NewAuthorization, expect to
// write to the DB with the default, empty key, so we treat it specially,
// serializing to an empty string. TODO: Modify authorizations to refer
// to a registration id, and make sure registration ids are always filled.
// https://github.com/letsencrypt/boulder/issues/181
if t.Key == nil {
return "", nil
}
jsonBytes, err := t.MarshalJSON()
if err != nil {
return "", err
}
return string(jsonBytes), nil
case core.AcmeStatus:
return string(t), nil
case core.OCSPStatus:
return string(t), nil
default:
return val, nil
}
}
// FromDb converts a DB representation back into a Boulder object.
func (tc BoulderTypeConverter) FromDb(target interface{}) (gorp.CustomScanner, bool) {
switch target.(type) {
case *core.AcmeIdentifier, *[]core.Challenge, *[]core.AcmeURL, *[][]int, core.JsonBuffer:
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
if !ok {
return errors.New("FromDb: Unable to convert *string")
}
b := []byte(*s)
return json.Unmarshal(b, target)
}
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
case *jose.JsonWebKey:
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
if !ok {
return errors.New("FromDb: Unable to convert *string")
}
b := []byte(*s)
k := target.(*jose.JsonWebKey)
if *s != "" {
return k.UnmarshalJSON(b)
} else {
// HACK: Sometimes we can have an empty string the in the DB where a
// key should be. We should fix that (see HACK above). In the meantime,
// return the default JsonWebKey in such situations.
// https://github.com/letsencrypt/boulder/issues/181
return nil
}
}
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
case *core.AcmeStatus:
binder := func(holder, target interface{}) error {
s := holder.(*string)
st := target.(*core.AcmeStatus)
*st = core.AcmeStatus(*s)
return nil
}
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
case *core.OCSPStatus:
binder := func(holder, target interface{}) error {
s := holder.(*string)
st := target.(*core.OCSPStatus)
*st = core.OCSPStatus(*s)
return nil
}
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
default:
return gorp.CustomScanner{}, false
}
}

View File

@ -0,0 +1,6 @@
// Copyright 2015 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package sa

View File

@ -63,7 +63,7 @@ else
run go test ${dirlist}
fi
run python test/integration-test.py
[ ${FAILURE} == 0 ] && run python test/integration-test.py
unformatted=$(find . -name "*.go" -not -path "./Godeps/*" -print | xargs -n1 gofmt -l)
if [ "x${unformatted}" != "x" ] ; then

View File

@ -64,6 +64,12 @@
"dbName": ":memory:"
},
"ocsp": {
"dbDriver": "sqlite3",
"dbName": ":memory:",
"minTimeToExpiry": "72h"
},
"mail": {
"server": "mail.example.com",
"port": "25",

View File

@ -8,8 +8,10 @@ package test
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"math/big"
"reflect"
"runtime"
"strings"
"testing"
@ -54,6 +56,23 @@ func AssertEquals(t *testing.T, one interface{}, two interface{}) {
}
}
func AssertDeepEquals(t *testing.T, one interface{}, two interface{}) {
if !reflect.DeepEqual(one, two) {
t.Errorf("%s [%+v] !(deep)= [%+v]", caller(), one, two)
}
}
func AssertMarshaledEquals(t *testing.T, one interface{}, two interface{}) {
oneJSON, err := json.Marshal(one)
AssertNotError(t, err, "Could not marshal 1st argument")
twoJSON, err := json.Marshal(two)
AssertNotError(t, err, "Could not marshal 2nd argument")
if !bytes.Equal(oneJSON, twoJSON) {
t.Errorf("%s [%s] !(json)= [%s]", caller(), oneJSON, twoJSON)
}
}
func AssertNotEquals(t *testing.T, one interface{}, two interface{}) {
if one == two {
t.Errorf("%s [%v] == [%v]", caller(), one, two)

View File

@ -26,6 +26,19 @@ import (
blog "github.com/letsencrypt/boulder/log"
)
const (
NewRegPath = "/acme/new-reg"
RegPath = "/acme/reg/"
NewAuthzPath = "/acme/new-authz"
AuthzPath = "/acme/authz/"
NewCertPath = "/acme/new-cert"
CertPath = "/acme/cert/"
RevokeCertPath = "/acme/revoke-cert/"
TermsPath = "/terms"
IssuerPath = "/acme/issuer-cert"
BuildIDPath = "/build"
)
// WebFrontEndImpl represents a Boulder web service and its resources
type WebFrontEndImpl struct {
RA core.RegistrationAuthority
@ -34,22 +47,13 @@ type WebFrontEndImpl struct {
log *blog.AuditLogger
// URL configuration parameters
BaseURL string
NewReg string
NewRegPath string
RegBase string
RegPath string
NewAuthz string
NewAuthzPath string
AuthzBase string
AuthzPath string
NewCert string
NewCertPath string
CertBase string
CertPath string
RevokeCertPath string
TermsPath string
IssuerPath string
BaseURL string
NewReg string
RegBase string
NewAuthz string
AuthzBase string
NewCert string
CertBase string
// Issuer certificate (DER) for /acme/issuer-cert
IssuerCert []byte
@ -63,37 +67,29 @@ func NewWebFrontEndImpl() WebFrontEndImpl {
logger := blog.GetAuditLogger()
logger.Notice("Web Front End Starting")
return WebFrontEndImpl{
log: logger,
NewRegPath: "/acme/new-reg",
RegPath: "/acme/reg/",
NewAuthzPath: "/acme/new-authz",
AuthzPath: "/acme/authz/",
NewCertPath: "/acme/new-cert",
CertPath: "/acme/cert/",
RevokeCertPath: "/acme/revoke-cert/",
TermsPath: "/terms",
IssuerPath: "/acme/issuer-cert",
log: logger,
}
}
func (wfe *WebFrontEndImpl) HandlePaths() {
wfe.NewReg = wfe.BaseURL + wfe.NewRegPath
wfe.RegBase = wfe.BaseURL + wfe.RegPath
wfe.NewAuthz = wfe.BaseURL + wfe.NewAuthzPath
wfe.AuthzBase = wfe.BaseURL + wfe.AuthzPath
wfe.NewCert = wfe.BaseURL + wfe.NewCertPath
wfe.CertBase = wfe.BaseURL + wfe.CertPath
wfe.NewReg = wfe.BaseURL + NewRegPath
wfe.RegBase = wfe.BaseURL + RegPath
wfe.NewAuthz = wfe.BaseURL + NewAuthzPath
wfe.AuthzBase = wfe.BaseURL + AuthzPath
wfe.NewCert = wfe.BaseURL + NewCertPath
wfe.CertBase = wfe.BaseURL + CertPath
http.HandleFunc("/", wfe.Index)
http.HandleFunc(wfe.NewRegPath, wfe.NewRegistration)
http.HandleFunc(wfe.NewAuthzPath, wfe.NewAuthorization)
http.HandleFunc(wfe.NewCertPath, wfe.NewCertificate)
http.HandleFunc(wfe.RegPath, wfe.Registration)
http.HandleFunc(wfe.AuthzPath, wfe.Authorization)
http.HandleFunc(wfe.CertPath, wfe.Certificate)
http.HandleFunc(wfe.RevokeCertPath, wfe.RevokeCertificate)
http.HandleFunc(wfe.TermsPath, wfe.Terms)
http.HandleFunc(wfe.IssuerPath, wfe.Issuer)
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)
http.HandleFunc(RevokeCertPath, wfe.RevokeCertificate)
http.HandleFunc(TermsPath, wfe.Terms)
http.HandleFunc(IssuerPath, wfe.Issuer)
http.HandleFunc(BuildIDPath, wfe.BuildID)
}
// Method implementations
@ -282,7 +278,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
regURL := fmt.Sprintf("%s%d", wfe.RegBase, id)
responseBody, err := json.Marshal(reg)
if err != nil {
wfe.sendError(response, "Error marshaling authz", err, http.StatusInternalServerError)
wfe.sendError(response, "Error marshaling registration", err, http.StatusInternalServerError)
return
}
@ -335,9 +331,10 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
return
}
// Make a URL for this authz, then blow away the ID before serializing
// Make a URL for this authz, then blow away the ID and RegID before serializing
authzURL := wfe.AuthzBase + string(authz.ID)
authz.ID = ""
authz.RegistrationID = 0
responseBody, err := json.Marshal(authz)
if err != nil {
wfe.sendError(response, "Error marshaling authz", err, http.StatusInternalServerError)
@ -481,13 +478,20 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
// We use only the sequential part of the serial number, because it should
// uniquely identify the certificate, and this makes it easy for anybody to
// enumerate and mirror our certificates.
serial := cert.ParsedCertificate.SerialNumber
parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
if err != nil {
wfe.sendError(response,
"Error creating new cert", err,
http.StatusBadRequest)
return
}
serial := parsedCertificate.SerialNumber
certURL := fmt.Sprintf("%s%016x", wfe.CertBase, serial.Rsh(serial, 64))
// TODO The spec says a client should send an Accept: application/pkix-cert
// header; either explicitly insist or tolerate
response.Header().Add("Location", certURL)
response.Header().Add("Link", link(wfe.BaseURL+wfe.IssuerPath, "up"))
response.Header().Add("Link", link(wfe.BaseURL+IssuerPath, "up"))
response.Header().Set("Content-Type", "application/pkix-cert")
response.WriteHeader(http.StatusCreated)
if _, err = response.Write(cert.DER); err != nil {
@ -537,7 +541,7 @@ func (wfe *WebFrontEndImpl) Challenge(authz core.Authorization, response http.Re
var challengeResponse core.Challenge
if err = json.Unmarshal(body, &challengeResponse); err != nil {
wfe.sendError(response, "Error unmarshaling authorization", err, http.StatusBadRequest)
wfe.sendError(response, "Error unmarshaling challenge response", err, http.StatusBadRequest)
return
}
@ -561,7 +565,7 @@ func (wfe *WebFrontEndImpl) Challenge(authz core.Authorization, response http.Re
// assumption: UpdateAuthorization does not modify order of challenges
jsonReply, err := json.Marshal(challenge)
if err != nil {
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
wfe.sendError(response, "Failed to marshal challenge", err, http.StatusInternalServerError)
return
}
@ -640,7 +644,7 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
jsonReply, err := json.Marshal(updatedReg)
if err != nil {
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
wfe.sendError(response, "Failed to marshal registration", err, http.StatusInternalServerError)
return
}
response.Header().Set("Content-Type", "application/json")
@ -671,6 +675,10 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
return
case "GET":
// Blank out ID and regID
authz.ID = ""
authz.RegistrationID = 0
jsonReply, err := json.Marshal(authz)
if err != nil {
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
@ -696,11 +704,11 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
case "GET":
// Certificate paths consist of the CertBase path, plus exactly sixteen hex
// digits.
if !strings.HasPrefix(path, wfe.CertPath) {
if !strings.HasPrefix(path, CertPath) {
wfe.sendError(response, "Not found", path, http.StatusNotFound)
return
}
serial := path[len(wfe.CertPath):]
serial := path[len(CertPath):]
if len(serial) != 16 || !allHex.Match([]byte(serial)) {
wfe.sendError(response, "Not found", serial, http.StatusNotFound)
return
@ -719,7 +727,7 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
// TODO: Content negotiation
response.Header().Set("Content-Type", "application/pkix-cert")
response.Header().Add("Link", link(wfe.IssuerPath, "up"))
response.Header().Add("Link", link(IssuerPath, "up"))
response.WriteHeader(http.StatusOK)
if _, err = response.Write(cert); err != nil {
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
@ -738,3 +746,12 @@ func (wfe *WebFrontEndImpl) Issuer(w http.ResponseWriter, r *http.Request) {
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
}
}
// BuildID tells the requestor what build we're running.
func (wfe *WebFrontEndImpl) BuildID(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
if _, err := fmt.Fprintln(w, core.GetBuildID()); err != nil {
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
}
}

View File

@ -497,7 +497,7 @@ func TestNewRegistration(t *testing.T) {
Body: makeBody(signRequest(t, "{\"contact\":[\"tel:123456789\"],\"agreement\":\"https://letsencrypt.org/be-good\"}")),
})
test.AssertEquals(t, responseWriter.Body.String(), "{\"key\":{\"kty\":\"RSA\",\"n\":\"z2NsNdHeqAiGdPP8KuxfQXat_uatOK9y12SyGpfKw1sfkizBIsNxERjNDke6Wp9MugN9srN3sr2TDkmQ-gK8lfWo0v1uG_QgzJb1vBdf_hH7aejgETRGLNJZOdaKDsyFnWq1WGJq36zsHcd0qhggTk6zVwqczSxdiWIAZzEakIUZ13KxXvoepYLY0Q-rEEQiuX71e4hvhfeJ4l7m_B-awn22UUVvo3kCqmaRlZT-36vmQhDGoBsoUo1KBEU44jfeK5PbNRk7vDJuH0B7qinr_jczHcvyD-2TtPzKaCioMtNh_VZbPNDaG67sYkQlC15-Ff3HPzKKJW2XvkVG91qMvQ\",\"e\":\"AAEAAQ\"},\"recoveryToken\":\"\",\"contact\":[\"tel:123456789\"],\"agreement\":\"https://letsencrypt.org/be-good\",\"thumbprint\":\"\"}")
test.AssertEquals(t, responseWriter.Body.String(), "{\"id\":0,\"key\":{\"kty\":\"RSA\",\"n\":\"z2NsNdHeqAiGdPP8KuxfQXat_uatOK9y12SyGpfKw1sfkizBIsNxERjNDke6Wp9MugN9srN3sr2TDkmQ-gK8lfWo0v1uG_QgzJb1vBdf_hH7aejgETRGLNJZOdaKDsyFnWq1WGJq36zsHcd0qhggTk6zVwqczSxdiWIAZzEakIUZ13KxXvoepYLY0Q-rEEQiuX71e4hvhfeJ4l7m_B-awn22UUVvo3kCqmaRlZT-36vmQhDGoBsoUo1KBEU44jfeK5PbNRk7vDJuH0B7qinr_jczHcvyD-2TtPzKaCioMtNh_VZbPNDaG67sYkQlC15-Ff3HPzKKJW2XvkVG91qMvQ\",\"e\":\"AAEAAQ\"},\"recoveryToken\":\"\",\"contact\":[\"tel:123456789\"],\"agreement\":\"https://letsencrypt.org/be-good\",\"thumbprint\":\"\"}")
var reg core.Registration
err := json.Unmarshal([]byte(responseWriter.Body.String()), &reg)
test.AssertNotError(t, err, "Couldn't unmarshal returned registration object")