Merge pull request #247 from letsencrypt/202-periodic-ocsp
RPC Fix and Issue #202: OCSP Update Tool
This commit is contained in:
commit
9167fb067f
10
Makefile
10
Makefile
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ func main() {
|
|||
|
||||
sas := rpc.NewStorageAuthorityServer(c.AMQP.SA.Server, ch, sai)
|
||||
|
||||
auditlogger.Info(app.VersionString())
|
||||
|
||||
cmd.RunUntilSignaled(auditlogger, sas, closeChan)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
42
cmd/shell.go
42
cmd/shell.go
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
15
core/util.go
15
core/util.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, ®); 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, ®istration)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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(®)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
2
test.sh
2
test.sh
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -64,6 +64,12 @@
|
|||
"dbName": ":memory:"
|
||||
},
|
||||
|
||||
"ocsp": {
|
||||
"dbDriver": "sqlite3",
|
||||
"dbName": ":memory:",
|
||||
"minTimeToExpiry": "72h"
|
||||
},
|
||||
|
||||
"mail": {
|
||||
"server": "mail.example.com",
|
||||
"port": "25",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()), ®)
|
||||
test.AssertNotError(t, err, "Couldn't unmarshal returned registration object")
|
||||
|
|
|
|||
Loading…
Reference in New Issue