merge upstream/master

This commit is contained in:
Roland Shoemaker 2015-05-18 19:07:04 -07:00
commit 97ff1c8423
32 changed files with 850 additions and 332 deletions

3
.gitignore vendored
View File

@ -8,6 +8,9 @@ _obj
_test
bin
# Test files
test/js/node_modules
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

View File

@ -151,7 +151,7 @@ WebFE -> WebFE: [ verify authorization signature ]
WebFE -> RA: UpdateAuthorization(Authorization)
RA -> RA: [ add responses to authorization ]
RA -> SA: Update(Authorization.ID, Authorization)
WebFE -> VA: UpdateValidations(Authorization)
RA -> VA: UpdateValidations(Authorization)
WebFE -> Client: defer(authorizationID)
VA -> SA: Update(Authorization.ID, Authorization)

View File

@ -7,16 +7,16 @@ package ca
import (
"bytes"
"crypto/x509"
"crypto"
"crypto/x509"
"encoding/asn1"
"encoding/hex"
"fmt"
"io/ioutil"
"net/http"
"os"
"testing"
"time"
"os"
apisign "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api/sign"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/auth"
@ -260,7 +260,6 @@ func TestMain(m *testing.M) {
caCertPEM, _ := ioutil.ReadFile(caCertFile)
caCert, _ := helpers.ParseCertificatePEM(caCertPEM)
// Create an online CFSSL instance
// This is designed to mimic what LE plans to do
authHandler, _ := auth.New(authKey, nil)
@ -330,7 +329,6 @@ func setup(t *testing.T) (cadb core.CertificateAuthorityDatabase, storageAuthori
ssa.InitTables()
storageAuthority = ssa
cadb, _ = NewMockCertificateAuthorityDatabase()
// Create a CA

View File

@ -28,6 +28,9 @@ func main() {
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)
cadb, err := ca.NewCertificateAuthorityDatabaseImpl(c.CA.DBDriver, c.CA.DBName)

View File

@ -25,6 +25,9 @@ func main() {
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)
rai := ra.NewRegistrationAuthorityImpl()

View File

@ -28,6 +28,9 @@ func main() {
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)
sai, err := sa.NewSQLStorageAuthority(c.SA.DBDriver, c.SA.DBName)

View File

@ -25,6 +25,9 @@ func main() {
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)
go cmd.ProfileCmd("VA", stats)

View File

@ -73,6 +73,9 @@ func main() {
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)
wfe := wfe.NewWebFrontEndImpl()

View File

@ -65,6 +65,9 @@ func main() {
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)
// Run StatsD profiling

View File

@ -37,9 +37,9 @@ import (
_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/cmd/cfssl"
"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"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/rpc"
"github.com/letsencrypt/boulder/ca"
)
// Config stores configuration parameters that applications
@ -140,8 +140,8 @@ func (as *AppShell) Run() {
// FailOnError exits and prints an error message if we encountered a problem
func FailOnError(err error, msg string) {
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %s\n", msg, err)
os.Exit(1)
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
panic(fmt.Sprintf("%s: %s", msg, err))
}
}

View File

@ -68,7 +68,7 @@ type RegistrationAuthority interface {
RevokeCertificate(x509.Certificate) error
// [ValidationAuthority]
OnValidationUpdate(Authorization)
OnValidationUpdate(Authorization) error
}
type ValidationAuthority interface {

View File

@ -6,12 +6,12 @@
package core
import (
"strings"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/square/go-jose"
"strings"
"time"
)
@ -288,7 +288,7 @@ type Authorization struct {
// Fields of this type get encoded and decoded JOSE-style, in base64url encoding
// with stripped padding.
type JsonBuffer []byte;
type JsonBuffer []byte
// Url-safe base64 encode that strips padding
func base64URLEncode(data []byte) string {

View File

@ -19,8 +19,8 @@ import (
"encoding/json"
"errors"
"fmt"
blog "github.com/letsencrypt/boulder/log"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/square/go-jose"
blog "github.com/letsencrypt/boulder/log"
"hash"
"io"
"math/big"

View File

@ -6,11 +6,11 @@
package core
import (
"testing"
"fmt"
"github.com/letsencrypt/boulder/test"
"math"
"math/big"
"testing"
)
// challenges.go
@ -18,14 +18,14 @@ func TestNewToken(t *testing.T) {
token := NewToken()
fmt.Println(token)
tokenLength := int(math.Ceil(32 * 8 / 6.0)) // 32 bytes, b64 encoded
test.AssertIntEquals(t,len(token),tokenLength)
test.AssertIntEquals(t, len(token), tokenLength)
collider := map[string]bool{}
// Test for very blatant RNG failures:
// Try 2^20 birthdays in a 2^72 search space...
// our naive collision probability here is 2^-32...
for i:=0; i < 1000000; i++ {
for i := 0; i < 1000000; i++ {
token = NewToken()[:12] // just sample a portion
test.Assert(t,!collider[token],"Token collision!")
test.Assert(t, !collider[token], "Token collision!")
collider[token] = true
}
return

View File

@ -0,0 +1,6 @@
This folder tracks software requirements using UUIDs interspersed with the code.
This is a best-effort mechanism, and while ugly, all other mechanisms for requirements traceability are similarly ugly.
All UUID sets are stored as CSV files in this directory; standard tools such as `grep` should be used to detect all
implementing code.

View File

@ -0,0 +1,10 @@
UUID,Shortname,CPS Reference,Description
11917fa4-10ef-4e0d-9105-bacbe7836a3c,Certificate Requests,,"All Certificate requests Date and time of request, type of event, and request information are automatically logged by the application. This includes Issuance, renewal, and re-key requests as well as sender/requester DN, Certificate serial number, initial application, method of request (online, in-person), source of verification, name of document presented for identity proofing, all fields verified in the application, Certificate common name, new validity period dates, date and time of response and success or failure indication are automatically logged by the application, and all associated error messages and codes. Manual interactions with participants such as telephone or in person inquiries and results of verification calls will be logged manually in a logbook or in a computer-based recording/tracking system and include date/time, description of interaction and identity provided."
4e85d791-09c0-4ab3-a837-d3d67e945134,Revocation Requests,,"All Certificate Revocation requests Date and time of Revocation request, sender/requester DN, Certificate serial number, subject DN of Certificate to revoke, Subscribers common name, Revocation reason, date and time of response and success or failure indication are automatically logged by the application; manual interactions with requestors such as telephone or in person inquiries and requests for Revocation are logged manually in a logbook or in a computer-based recording/tracking system. The date/time, description of interaction and identity provided are also recorded."
a88fd00b-fa62-4a2f-9226-3eef27e2a50e,Certificate Updates,,"The approval or rejection of a Certificate status change request Identity of equipment operator who initiated the request, message contents, message source, destination, and success or failure indication are automatically logged by the application."
d510aa7e-ce9d-44ea-aa6d-4479a5652439,Software Updates and Migrations,,"Any security-relevant changes to the configuration of a component Date and time of modification, name of modifier, description of modification, build information (i.e. size, version number) of any modified files and the reason for modification are manually logged during change management processes."
78722466-9519-42bd-8a16-9c1ec1ca29ea,Compromise Notifications,,"All Certificate compromise notification requests Date and time of notification, identity of person making the notification, identification of entity compromised, and a description of the compromise are logged manually by the personnel who receive the notification (e.g. Help Desk, RA Operators, etc.) and by RA/RA Operators system processing logs."
9cc4d537-8534-4970-8665-4b382abe82f3,Error Conditions,,"Software error conditions Date and time of event, and description of event are automatically logged by the application reporting the event or by the operating system."
03806e9f-b6f3-4b29-b0a2-46fae57646d5,Software Integrity Failures,,"Software check integrity failures Date and time of event, and description of event are automatically logged by the application reporting the event or by the operating system."
0786b6f2-91ca-4f48-9883-842a19084c64,Improper Messages,,"Receipt of improper messages Date and time of event, and description of event are automatically logged by the application reporting the event or by the operating system."
f523f21f-12d2-4c31-b2eb-ee4b7d96d60e,Misrouted Messages,,"Misrouted messages Date and time of event, and description of event are automatically logged by the application reporting the event or by the operating system."
1 UUID Shortname CPS Reference Description
2 11917fa4-10ef-4e0d-9105-bacbe7836a3c Certificate Requests All Certificate requests – Date and time of request, type of event, and request information are automatically logged by the application. This includes Issuance, renewal, and re-key requests as well as sender/requester DN, Certificate serial number, initial application, method of request (online, in-person), source of verification, name of document presented for identity proofing, all fields verified in the application, Certificate common name, new validity period dates, date and time of response and success or failure indication are automatically logged by the application, and all associated error messages and codes. Manual interactions with participants such as telephone or in person inquiries and results of verification calls will be logged manually in a logbook or in a computer-based recording/tracking system and include date/time, description of interaction and identity provided.
3 4e85d791-09c0-4ab3-a837-d3d67e945134 Revocation Requests All Certificate Revocation requests – Date and time of Revocation request, sender/requester DN, Certificate serial number, subject DN of Certificate to revoke, Subscriber’s common name, Revocation reason, date and time of response and success or failure indication are automatically logged by the application; manual interactions with requestors such as telephone or in person inquiries and requests for Revocation are logged manually in a logbook or in a computer-based recording/tracking system. The date/time, description of interaction and identity provided are also recorded.
4 a88fd00b-fa62-4a2f-9226-3eef27e2a50e Certificate Updates The approval or rejection of a Certificate status change request – Identity of equipment operator who initiated the request, message contents, message source, destination, and success or failure indication are automatically logged by the application.
5 d510aa7e-ce9d-44ea-aa6d-4479a5652439 Software Updates and Migrations Any security-relevant changes to the configuration of a component – Date and time of modification, name of modifier, description of modification, build information (i.e. size, version number) of any modified files and the reason for modification are manually logged during change management processes.
6 78722466-9519-42bd-8a16-9c1ec1ca29ea Compromise Notifications All Certificate compromise notification requests – Date and time of notification, identity of person making the notification, identification of entity compromised, and a description of the compromise are logged manually by the personnel who receive the notification (e.g. Help Desk, RA Operators, etc.) and by RA/RA Operators’ system processing logs.
7 9cc4d537-8534-4970-8665-4b382abe82f3 Error Conditions Software error conditions – Date and time of event, and description of event are automatically logged by the application reporting the event or by the operating system.
8 03806e9f-b6f3-4b29-b0a2-46fae57646d5 Software Integrity Failures Software check integrity failures – Date and time of event, and description of event are automatically logged by the application reporting the event or by the operating system.
9 0786b6f2-91ca-4f48-9883-842a19084c64 Improper Messages Receipt of improper messages – Date and time of event, and description of event are automatically logged by the application reporting the event or by the operating system.
10 f523f21f-12d2-4c31-b2eb-ee4b7d96d60e Misrouted Messages Misrouted messages – Date and time of event, and description of event are automatically logged by the application reporting the event or by the operating system.

View File

@ -0,0 +1,17 @@
#!/bin/bash
#
# This method finds all instances of each UUID in the codebase.
#
audit_dir=$(cd $(dirname ${0}); pwd)
root=$(dirname $(dirname ${audit_dir}))
cat ${audit_dir}/audit_events.csv | tail -n +2 | while read r; do
uuid=$(echo $r | awk -F "," '{print $1;}')
desc=$(echo $r | awk -F "," '{print $2;}')
echo "==================================="
echo "==================================="
echo ${desc} ${uuid}
echo ""
grep -C 3 -R --include "*.go" "${uuid}" "${root}"
done

View File

@ -0,0 +1,7 @@
#!/bin/bash
#
# Provides comment lines for each requirement that can be located with tools
echo "Insert these comments into the codebase where appropriate, formatted exactly as printed."
cat audit_events.csv | tail -n +2 | awk -F "," '{print "// AUDIT[", $2, "]", $1;}'

View File

@ -27,6 +27,17 @@ var _Singleton singleton
// The constant used to identify audit-specific messages
const auditTag = "[AUDIT]"
// Constant used to indicate an emergency exit to the executor
const emergencyReturnValue = 13
// exitFunction closes the running system
type exitFunction func()
// Default to calling os.Exit()
func defaultEmergencyExit() {
os.Exit(emergencyReturnValue)
}
// AuditLogger is a System Logger with additional audit-specific methods.
// In addition to all the standard syslog.Writer methods from
// http://golang.org/pkg/log/syslog/#Writer, you can also call
@ -35,6 +46,7 @@ const auditTag = "[AUDIT]"
type AuditLogger struct {
*syslog.Writer
Stats statsd.Statter
exitFunction exitFunction
}
// Dial establishes a connection to the log daemon by passing through
@ -54,7 +66,12 @@ func NewAuditLogger(log *syslog.Writer, stats statsd.Statter) (*AuditLogger, err
if log == nil {
return nil, errors.New("Attempted to use a nil System Logger.")
}
return &AuditLogger{log, stats}, nil
audit := &AuditLogger{
log,
stats,
defaultEmergencyExit,
}
return audit, nil
}
// initializeAuditLogger should only be used in unit tests. Failures in this
@ -96,90 +113,118 @@ func GetAuditLogger() *AuditLogger {
return _Singleton.log
}
// Log the provided message at the appropriate level, writing to
// both stdout and the Logger, as well as informing statsd.
func (log *AuditLogger) logAtLevel(level, msg string) (err error) {
fmt.Printf("%s\n", msg)
log.Stats.Inc(level, 1, 1.0)
switch level {
case "Logging.Alert":
err = log.Writer.Alert(msg)
case "Logging.Crit":
err = log.Writer.Crit(msg)
case "Logging.Debug":
err = log.Writer.Debug(msg)
case "Logging.Emerg":
err = log.Writer.Emerg(msg)
case "Logging.Err":
err = log.Writer.Err(msg)
case "Logging.Info":
err = log.Writer.Info(msg)
case "Logging.Warning":
err = log.Writer.Warning(msg)
}
return
}
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) auditAtLevel(level, msg string) (err error) {
// Submit a separate counter that marks an Audit event
log.Stats.Inc("Logging.Audit", 1, 1.0)
text := fmt.Sprintf("%s %s", auditTag, msg)
return log.logAtLevel(level, text)
}
// 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))
}
}
// WarningErr formats an error for the Warn level.
func (log *AuditLogger) WarningErr(msg error) (err error) {
return log.logAtLevel("Logging.Warning", msg.Error())
}
// Alert level messages pass through normally.
func (log *AuditLogger) Alert(msg string) (err error) {
return log.logAtLevel("Logging.Alert", msg)
}
// Crit level messages are automatically marked for audit
func (log *AuditLogger) Crit(msg string) (err error) {
return log.auditAtLevel("Logging.Crit", msg)
}
// Debug level messages pass through normally.
func (log *AuditLogger) Debug(msg string) (err error) {
return log.logAtLevel("Logging.Debug", msg)
}
// Emerg level messages are automatically marked for audit
func (log *AuditLogger) Emerg(msg string) (err error) {
return log.auditAtLevel("Logging.Emerg", msg)
}
// Err level messages are automatically marked for audit
func (log *AuditLogger) Err(msg string) (err error) {
return log.auditAtLevel("Logging.Err", msg)
}
// Info level messages pass through normally.
func (log *AuditLogger) Info(msg string) (err error) {
return log.logAtLevel("Logging.Info", msg)
}
// Warning level messages pass through normally.
func (log *AuditLogger) Warning(msg string) (err error) {
return log.logAtLevel("Logging.Warning", msg)
}
// Notice level messages pass through normally.
func (log *AuditLogger) Notice(msg string) (err error) {
return log.logAtLevel("Logging.Notice", msg)
}
// Audit sends a NOTICE-severity message that is prefixed with the
// audit tag, for special handling at the upstream system logger.
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) Audit(msg string) (err error) {
fmt.Println(msg)
err = log.Notice(fmt.Sprintf("%s %s", auditTag, msg))
log.Stats.Inc("Logging.Audit", 1, 1.0)
return
return log.auditAtLevel("Logging.Notice", msg)
}
// Audit can format an error for auditing; it does so at ERR level.
// AuditErr can format an error for auditing; it does so at ERR level.
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) AuditErr(msg error) (err error) {
fmt.Println(msg)
err = log.Err(fmt.Sprintf("%s %s", auditTag, msg))
log.Stats.Inc("Logging.Audit", 1, 1.0)
return
return log.auditAtLevel("Logging.Err", msg.Error())
}
// Warning formats an error for the Warn level.
func (log *AuditLogger) WarningErr(msg error) (err error) {
fmt.Println(msg)
err = log.Warning(fmt.Sprintf("%s", msg))
return
// SetEmergencyExitFunc changes the systems' behavior on an emergency exit.
func (log *AuditLogger) SetEmergencyExitFunc(exit exitFunction) {
log.exitFunction = exit
}
func (log *AuditLogger) Alert(msg string) (err error) {
fmt.Println(msg)
log.Stats.Inc("Logging.Alert", 1, 1.0)
return log.Writer.Alert(msg)
}
func (log *AuditLogger) Crit(msg string) (err error) {
fmt.Println(msg)
log.Stats.Inc("Logging.Crit", 1, 1.0)
return log.Writer.Crit(msg)
}
func (log *AuditLogger) Debug(msg string) (err error) {
fmt.Println(msg)
log.Stats.Inc("Logging.Debug", 1, 1.0)
return log.Writer.Debug(msg)
}
func (log *AuditLogger) Emerg(msg string) (err error) {
fmt.Println(msg)
log.Stats.Inc("Logging.Emerg", 1, 1.0)
return log.Writer.Emerg(msg)
}
func (log *AuditLogger) Err(msg string) (err error) {
fmt.Println(msg)
log.Stats.Inc("Logging.Err", 1, 1.0)
return log.Writer.Err(msg)
}
func (log *AuditLogger) Info(msg string) (err error) {
fmt.Println(msg)
log.Stats.Inc("Logging.Info", 1, 1.0)
return log.Writer.Info(msg)
}
func (log *AuditLogger) Warning(msg string) (err error) {
fmt.Println(msg)
log.Stats.Inc("Logging.Warning", 1, 1.0)
return log.Writer.Warning(msg)
}
func (log *AuditLogger) Notice(msg string) (err error) {
fmt.Println(msg)
log.Stats.Inc("Logging.Notice", 1, 1.0)
return log.Writer.Notice(msg)
}
const EMERGENCY_RETVAL = 13
// EmergencyExit triggers an immediate Boulder shutdown in the event of serious
// errors. This function will provide the necessary housekeeping.
// Currently, make an emergency log entry and exit; the Activity Monitor
// should notice the Emerg level event and shut down all components.
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) EmergencyExit(msg string) {
// Some errors may be serious enough to trigger an immediate Boulder
// shutdown. This function will provide the necessary housekeeping.
// Currently, make an emergency log entry and exit; the Activity Monitor
// should notice the Emerg level event and shut down all components.
log.Emerg(msg)
os.Exit(EMERGENCY_RETVAL)
log.auditAtLevel("Logging.Emerg", msg)
log.exitFunction()
}

View File

@ -15,6 +15,7 @@ import (
)
func TestConstruction(t *testing.T) {
t.Parallel()
writer, err := syslog.New(syslog.LOG_EMERG|syslog.LOG_KERN, "tag")
test.AssertNotError(t, err, "Could not construct syslog object")
@ -24,6 +25,7 @@ func TestConstruction(t *testing.T) {
}
func TestSingleton(t *testing.T) {
t.Parallel()
log1 := GetAuditLogger()
test.AssertNotNil(t, log1, "Logger shouldn't be nil")
@ -53,24 +55,28 @@ func TestSingleton(t *testing.T) {
}
func TestDial(t *testing.T) {
t.Parallel()
stats, _ := statsd.NewNoopClient(nil)
_, err := Dial("", "", "tag", stats)
test.AssertNotError(t, err, "Could not construct audit logger")
}
func TestDialError(t *testing.T) {
t.Parallel()
stats, _ := statsd.NewNoopClient(nil)
_, err := Dial("_fail", "_fail", "tag", stats)
test.AssertError(t, err, "Audit Logger should have failed")
}
func TestConstructionNil(t *testing.T) {
t.Parallel()
stats, _ := statsd.NewNoopClient(nil)
_, err := NewAuditLogger(nil, stats)
test.AssertError(t, err, "Nil shouldn't be permitted.")
}
func TestEmit(t *testing.T) {
t.Parallel()
writer, err := syslog.New(syslog.LOG_EMERG|syslog.LOG_KERN, "tag")
test.AssertNotError(t, err, "Could not construct syslog object")
@ -82,6 +88,7 @@ func TestEmit(t *testing.T) {
}
func TestEmitEmpty(t *testing.T) {
t.Parallel()
writer, err := syslog.New(syslog.LOG_EMERG|syslog.LOG_KERN, "tag")
test.AssertNotError(t, err, "Could not construct syslog object")
@ -93,6 +100,7 @@ func TestEmitEmpty(t *testing.T) {
}
func TestEmitErrors(t *testing.T) {
t.Parallel()
stats, _ := statsd.NewNoopClient(nil)
audit, _ := Dial("", "", "tag", stats)
@ -101,6 +109,7 @@ func TestEmitErrors(t *testing.T) {
}
func TestSyslogMethods(t *testing.T) {
t.Parallel()
// Write all logs to UDP on a high port so as to not bother the system
// which is running the test, particularly for Emerg()
writer, err := syslog.Dial("udp", "127.0.0.1:65530", syslog.LOG_INFO|syslog.LOG_LOCAL0, "")
@ -120,3 +129,30 @@ func TestSyslogMethods(t *testing.T) {
audit.Warning("audit-logger_test.go: warning")
audit.Alert("audit-logger_test.go: alert")
}
func TestPanic(t *testing.T) {
t.Parallel()
stats, _ := statsd.NewNoopClient(nil)
audit, _ := Dial("", "", "tag", stats)
defer audit.AuditPanic()
panic("Test panic")
// Can't assert anything here or golint gets angry
}
func TestEmergencyExit(t *testing.T) {
t.Parallel()
// Write all logs to UDP on a high port so as to not bother the system
// which is running the test, particularly for Emerg()
writer, err := syslog.Dial("udp", "127.0.0.1:65530", syslog.LOG_INFO|syslog.LOG_LOCAL0, "")
test.AssertNotError(t, err, "Could not construct syslog object")
stats, _ := statsd.NewNoopClient(nil)
audit, err := NewAuditLogger(writer, stats)
test.AssertNotError(t, err, "Could not construct audit logger")
called := false
audit.SetEmergencyExitFunc(func() { called = true })
audit.EmergencyExit("Emergency!")
test.AssertEquals(t, called, true)
}

View File

@ -7,7 +7,9 @@ package ra
import (
"crypto/x509"
"encoding/json"
"fmt"
"math/big"
"net/url"
"regexp"
"strconv"
@ -46,6 +48,22 @@ func lastPathSegment(url core.AcmeURL) string {
return allButLastPathSegment.ReplaceAllString(url.Path, "")
}
type certificateRequestEvent struct {
ID string `json:"omitempty"`
Requester int64 `json:"omitempty"`
SerialNumber *big.Int `json:"omitempty"`
RequestMethod string `json:"omitempty"`
VerificationMethods []string `json:"omitempty"`
VerifiedFields []string `json:"omitempty"`
CommonName string `json:"omitempty"`
Names []string `json:"omitempty"`
NotBefore time.Time `json:"omitempty"`
NotAfter time.Time `json:"omitempty"`
RequestTime time.Time `json:"omitempty"`
ResponseTime time.Time `json:"omitempty"`
Error string `json:"omitempty"`
}
func (ra *RegistrationAuthorityImpl) NewRegistration(init core.Registration, key jose.JsonWebKey) (reg core.Registration, err error) {
reg = core.Registration{
Key: key,
@ -107,6 +125,31 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest, regID int64) (core.Certificate, error) {
emptyCert := core.Certificate{}
var err error
var logEventResult string
// Assume the worst
logEventResult = "error"
// Construct the log event
logEvent := certificateRequestEvent{
ID: core.NewToken(),
Requester: regID,
RequestMethod: "online",
RequestTime: time.Now(),
}
// No matter what, log the request
defer func() {
jsonLogEvent, logErr := json.Marshal(logEvent)
if logErr != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
ra.log.Audit(fmt.Sprintf("Certificate request logEvent could not be serialized. Raw: %+v", logEvent))
return
}
// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
ra.log.Audit(fmt.Sprintf("Certificate request %s - %s", logEventResult, string(jsonLogEvent)))
}()
if regID <= 0 {
err = fmt.Errorf("Invalid registration ID")
@ -117,23 +160,28 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
// TODO: Verify that other aspects of the CSR are appropriate
csr := req.CSR
if err = core.VerifyCSR(csr); err != nil {
ra.log.Debug("Invalid signature on CSR:" + err.Error())
logEvent.Error = err.Error()
err = core.UnauthorizedError("Invalid signature on CSR")
return emptyCert, err
}
logEvent.CommonName = csr.Subject.CommonName
logEvent.Names = csr.DNSNames
csrPreviousDenied, err := ra.SA.AlreadyDeniedCSR(append(csr.DNSNames, csr.Subject.CommonName))
if err != nil {
logEvent.Error = err.Error()
return emptyCert, err
}
if csrPreviousDenied {
ra.log.Audit(fmt.Sprintf("CSR for names %v was previously revoked/denied", csr.DNSNames))
err = core.UnauthorizedError("CSR has already been revoked/denied")
logEvent.Error = err.Error()
return emptyCert, err
}
registration, err := ra.SA.GetRegistration(regID)
if err != nil {
logEvent.Error = err.Error()
return emptyCert, err
}
@ -144,6 +192,7 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
// Gather authorized domains from the referenced authorizations
authorizedDomains := map[string]bool{}
verificationMethodSet := map[string]bool{}
now := time.Now()
for _, url := range req.Authorizations {
id := lastPathSegment(url)
@ -159,32 +208,54 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
continue
}
for _, challenge := range authz.Challenges {
if challenge.Status == core.StatusValid {
verificationMethodSet[challenge.Type] = true
}
}
authorizedDomains[authz.Identifier.Value] = true
}
verificationMethods := []string{}
for method, _ := range verificationMethodSet {
verificationMethods = append(verificationMethods, method)
}
logEvent.VerificationMethods = verificationMethods
// Validate that authorization key is authorized for all domains
names := csr.DNSNames
if len(csr.Subject.CommonName) > 0 {
names = append(names, csr.Subject.CommonName)
}
// Validate all domains
for _, name := range names {
if !authorizedDomains[name] {
err = core.UnauthorizedError(fmt.Sprintf("Key not authorized for name %s", name))
logEvent.Error = err.Error()
return emptyCert, err
}
}
// Create the certificate
// Mark that we verified the CN and SANs
logEvent.VerifiedFields = []string{"subject.commonName", "subjectAltName"}
// Create the certificate and log the result
var cert core.Certificate
ra.log.Audit(fmt.Sprintf("Issuing certificate for %s", names))
if cert, err = ra.CA.IssueCertificate(*csr, regID); err != nil {
return emptyCert, err
}
cert.ParsedCertificate, err = x509.ParseCertificate([]byte(cert.DER))
if err != nil {
return emptyCert, err
logEvent.Error = err.Error()
return emptyCert, nil
}
cert.ParsedCertificate, err = x509.ParseCertificate([]byte(cert.DER))
logEvent.SerialNumber = cert.ParsedCertificate.SerialNumber
logEvent.CommonName = cert.ParsedCertificate.Subject.CommonName
logEvent.NotBefore = cert.ParsedCertificate.NotBefore
logEvent.NotAfter = cert.ParsedCertificate.NotAfter
logEvent.ResponseTime = time.Now()
logEventResult = "successful"
return cert, nil
}
@ -216,10 +287,20 @@ func (ra *RegistrationAuthorityImpl) UpdateAuthorization(base core.Authorization
}
func (ra *RegistrationAuthorityImpl) RevokeCertificate(cert x509.Certificate) error {
return ra.CA.RevokeCertificate(core.SerialToString(cert.SerialNumber))
serialString := core.SerialToString(cert.SerialNumber)
err := ra.CA.RevokeCertificate(serialString)
// AUDIT[ Revocation Requests ] 4e85d791-09c0-4ab3-a837-d3d67e945134
if err != nil {
ra.log.Audit(fmt.Sprintf("Revocation error - %s - %s", serialString, err))
} else {
ra.log.Audit(fmt.Sprintf("Revocation - %s", serialString))
}
return err
}
func (ra *RegistrationAuthorityImpl) OnValidationUpdate(authz core.Authorization) {
func (ra *RegistrationAuthorityImpl) OnValidationUpdate(authz core.Authorization) error {
// Check to see whether the updated validations are sufficient
// Current policy is to accept if any validation succeeded
for _, val := range authz.Challenges {
@ -239,5 +320,5 @@ func (ra *RegistrationAuthorityImpl) OnValidationUpdate(authz core.Authorization
}
// Finalize the authorization (error ignored)
_ = ra.SA.FinalizeAuthorization(authz)
return ra.SA.FinalizeAuthorization(authz)
}

View File

@ -7,11 +7,12 @@ package rpc
import (
"errors"
"log"
"fmt"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
)
// TODO: AMQP-RPC messages should be wrapped in JWS. To implement that,
@ -51,7 +52,7 @@ func amqpConnect(url string) (ch *amqp.Channel, err error) {
}
// A simplified way to declare and subscribe to an AMQP queue
func amqpSubscribe(ch *amqp.Channel, name string) (msgs <-chan amqp.Delivery, err error) {
func amqpSubscribe(ch *amqp.Channel, name string, log *blog.AuditLogger) (msgs <-chan amqp.Delivery, err error) {
err = ch.ExchangeDeclare(
AmqpExchange,
AmqpExchangeType,
@ -61,7 +62,7 @@ func amqpSubscribe(ch *amqp.Channel, name string) (msgs <-chan amqp.Delivery, er
AmqpNoWait,
nil)
if err != nil {
log.Fatalf("Could not declare exchange: %s", err)
log.Crit(fmt.Sprintf("Could not declare exchange: %s", err))
return
}
@ -73,7 +74,7 @@ func amqpSubscribe(ch *amqp.Channel, name string) (msgs <-chan amqp.Delivery, er
AmqpNoWait,
nil)
if err != nil {
log.Fatalf("Could not declare queue: %s", err)
log.Crit(fmt.Sprintf("Could not declare queue: %s", err))
return
}
@ -84,7 +85,7 @@ func amqpSubscribe(ch *amqp.Channel, name string) (msgs <-chan amqp.Delivery, er
false,
nil)
if err != nil {
log.Fatalf("Could not bind queue: %s", err)
log.Crit(fmt.Sprintf("Could not bind queue: %s", err))
return
}
@ -97,7 +98,7 @@ func amqpSubscribe(ch *amqp.Channel, name string) (msgs <-chan amqp.Delivery, er
AmqpNoWait,
nil)
if err != nil {
log.Fatalf("Could not subscribe to queue: %s", err)
log.Crit(fmt.Sprintf("Could not subscribe to queue: %s", err))
return
}
@ -113,6 +114,7 @@ func amqpSubscribe(ch *amqp.Channel, name string) (msgs <-chan amqp.Delivery, er
type AmqpRPCServer struct {
serverQueue string
channel *amqp.Channel
log *blog.AuditLogger
dispatchTable map[string]func([]byte) []byte
}
@ -120,9 +122,11 @@ type AmqpRPCServer struct {
// Note that you must call Start() to actually start the server
// listening for requests.
func NewAmqpRPCServer(serverQueue string, channel *amqp.Channel) *AmqpRPCServer {
log := blog.GetAuditLogger()
return &AmqpRPCServer{
serverQueue: serverQueue,
channel: channel,
log: log,
dispatchTable: make(map[string]func([]byte) []byte),
}
}
@ -134,7 +138,7 @@ func (rpc *AmqpRPCServer) Handle(method string, handler func([]byte) []byte) {
// Starts the AMQP-RPC server running in a separate thread.
// There is currently no Stop() method.
func (rpc *AmqpRPCServer) Start() (err error) {
msgs, err := amqpSubscribe(rpc.channel, rpc.serverQueue)
msgs, err := amqpSubscribe(rpc.channel, rpc.serverQueue, rpc.log)
if err != nil {
return
}
@ -143,12 +147,14 @@ func (rpc *AmqpRPCServer) Start() (err error) {
for msg := range msgs {
// XXX-JWS: jws.Verify(body)
cb, present := rpc.dispatchTable[msg.Type]
log.Printf(" [s<] received %s(%s) [%s]", msg.Type, core.B64enc(msg.Body), msg.CorrelationId)
rpc.log.Info(fmt.Sprintf(" [s<] received %s(%s) [%s]", 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))
continue
}
response := cb(msg.Body)
log.Printf(" [s>] sending %s(%s) [%s]", msg.Type, core.B64enc(response), msg.CorrelationId)
rpc.log.Info(fmt.Sprintf(" [s>] sending %s(%s) [%s]", msg.Type, core.B64enc(response), msg.CorrelationId))
rpc.channel.Publish(
AmqpExchange,
msg.ReplyTo,
@ -188,6 +194,7 @@ type AmqpRPCCLient struct {
channel *amqp.Channel
pending map[string]chan []byte
timeout time.Duration
log *blog.AuditLogger
}
func NewAmqpRPCCLient(clientQueue, serverQueue string, channel *amqp.Channel) (rpc *AmqpRPCCLient, err error) {
@ -197,10 +204,11 @@ func NewAmqpRPCCLient(clientQueue, serverQueue string, channel *amqp.Channel) (r
channel: channel,
pending: make(map[string]chan []byte),
timeout: 10 * time.Second,
log: blog.GetAuditLogger(),
}
// Subscribe to the response queue and dispatch
msgs, err := amqpSubscribe(rpc.channel, clientQueue)
msgs, err := amqpSubscribe(rpc.channel, clientQueue, nil)
if err != nil {
return
}
@ -211,7 +219,7 @@ func NewAmqpRPCCLient(clientQueue, serverQueue string, channel *amqp.Channel) (r
corrID := msg.CorrelationId
responseChan, present := rpc.pending[corrID]
log.Printf(" [c<] received %s(%s) [%s]", msg.Type, core.B64enc(msg.Body), corrID)
rpc.log.Debug(fmt.Sprintf(" [c<] received %s(%s) [%s]", msg.Type, core.B64enc(msg.Body), corrID))
if !present {
continue
}
@ -236,7 +244,7 @@ func (rpc *AmqpRPCCLient) Dispatch(method string, body []byte) chan []byte {
rpc.pending[corrID] = responseChan
// Send the request
log.Printf(" [c>] sending %s(%s) [%s]", method, core.B64enc(body), corrID)
rpc.log.Debug(fmt.Sprintf(" [c>] sending %s(%s) [%s]", method, core.B64enc(body), corrID))
rpc.channel.Publish(
AmqpExchange,
rpc.serverQueue,
@ -257,15 +265,13 @@ func (rpc *AmqpRPCCLient) DispatchSync(method string, body []byte) (response []b
case response = <-rpc.Dispatch(method, body):
return
case <-time.After(rpc.timeout):
log.Printf(" [c!] AMQP-RPC timeout [%s]", method)
rpc.log.Warning(fmt.Sprintf(" [c!] AMQP-RPC timeout [%s]", method))
err = errors.New("AMQP-RPC timeout")
return
}
}
func (rpc *AmqpRPCCLient) SyncDispatchWithTimeout(method string, body []byte, ttl time.Duration) (response []byte, err error) {
switch {
}
err = errors.New("Not Implemented")
return
}

View File

@ -9,11 +9,12 @@ import (
"crypto/x509"
"encoding/json"
"errors"
"log"
"fmt"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/square/go-jose"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
"github.com/letsencrypt/boulder/core"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/square/go-jose"
blog "github.com/letsencrypt/boulder/log"
)
// This file defines RPC wrappers around the ${ROLE}Impl classes,
@ -78,18 +79,31 @@ type certificateRequest struct {
RegID int64
}
func improperMessage(method string, err error, obj interface{}) {
log := blog.GetAuditLogger()
log.Audit(fmt.Sprintf("Improper message. method: %s err: %s data: %+v", method, err, obj))
}
func errorCondition(method string, err error, obj interface{}) {
log := blog.GetAuditLogger()
log.Audit(fmt.Sprintf("Error condition. method: %s err: %s data: %+v", method, err, obj))
}
func NewRegistrationAuthorityServer(serverQueue string, channel *amqp.Channel, impl core.RegistrationAuthority) (*AmqpRPCServer, error) {
log := blog.GetAuditLogger()
rpc := NewAmqpRPCServer(serverQueue, channel)
rpc.Handle(MethodNewRegistration, func(req []byte) (response []byte) {
var rr registrationRequest
err := json.Unmarshal(req, &rr)
if err != nil {
if err := json.Unmarshal(req, &rr); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodNewRegistration, err, req)
return nil
}
reg, err := impl.NewRegistration(rr.Reg, rr.Key)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodNewRegistration, err, reg)
return nil
}
@ -103,11 +117,15 @@ func NewRegistrationAuthorityServer(serverQueue string, channel *amqp.Channel, i
rpc.Handle(MethodNewAuthorization, func(req []byte) (response []byte) {
var ar authorizationRequest
if err := json.Unmarshal(req, &ar); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodNewAuthorization, err, req)
return nil
}
authz, err := impl.NewAuthorization(ar.Authz, ar.RegID)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodNewAuthorization, err, ar)
return nil
}
@ -119,21 +137,22 @@ func NewRegistrationAuthorityServer(serverQueue string, channel *amqp.Channel, i
})
rpc.Handle(MethodNewCertificate, func(req []byte) []byte {
log.Printf(" [.] Entering MethodNewCertificate")
log.Info(fmt.Sprintf(" [.] Entering MethodNewCertificate"))
var cr certificateRequest
if err := json.Unmarshal(req, &cr); err != nil {
log.Printf(" [!] Error unmarshaling certificate request: %s", err.Error())
log.Printf(" JSON data: %s", string(req))
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodNewCertificate, err, req)
return nil
}
log.Printf(" [.] No problem unmarshaling request")
log.Info(fmt.Sprintf(" [.] No problem unmarshaling request"))
cert, err := impl.NewCertificate(cr.Req, cr.RegID)
if err != nil {
log.Printf(" [!] Error issuing new certificate: %s", err.Error())
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodNewCertificate, err, cr)
return nil
}
log.Printf(" [.] No problem issuing new cert")
log.Info(fmt.Sprintf(" [.] No problem issuing new cert"))
response, err := json.Marshal(cert)
if err != nil {
@ -148,11 +167,15 @@ func NewRegistrationAuthorityServer(serverQueue string, channel *amqp.Channel, i
}
err := json.Unmarshal(req, &request)
if err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodUpdateRegistration, err, req)
return nil
}
reg, err := impl.UpdateRegistration(request.Base, request.Update)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodUpdateRegistration, err, request)
return nil
}
@ -171,11 +194,15 @@ func NewRegistrationAuthorityServer(serverQueue string, channel *amqp.Channel, i
}
err := json.Unmarshal(req, &authz)
if err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodUpdateAuthorization, err, req)
return nil
}
newAuthz, err := impl.UpdateAuthorization(authz.Authz, authz.Index, authz.Response)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodUpdateAuthorization, err, authz)
return nil
}
@ -189,21 +216,32 @@ func NewRegistrationAuthorityServer(serverQueue string, channel *amqp.Channel, i
rpc.Handle(MethodRevokeCertificate, func(req []byte) []byte {
certs, err := x509.ParseCertificates(req)
if err != nil || len(certs) == 0 {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodRevokeCertificate, err, req)
return nil
}
// Error explicitly ignored since response is nil anyway
_ = impl.RevokeCertificate(*certs[0])
err = impl.RevokeCertificate(*certs[0])
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodRevokeCertificate, err, certs)
}
return nil
})
rpc.Handle(MethodOnValidationUpdate, 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(MethodOnValidationUpdate, err, req)
return nil
}
impl.OnValidationUpdate(authz)
if err := impl.OnValidationUpdate(authz); err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodOnValidationUpdate, err, authz)
}
return nil
})
@ -317,7 +355,7 @@ func (rac RegistrationAuthorityClient) RevokeCertificate(cert x509.Certificate)
return
}
func (rac RegistrationAuthorityClient) OnValidationUpdate(authz core.Authorization) {
func (rac RegistrationAuthorityClient) OnValidationUpdate(authz core.Authorization) (err error) {
data, err := json.Marshal(authz)
if err != nil {
return
@ -335,11 +373,15 @@ func NewValidationAuthorityServer(serverQueue string, channel *amqp.Channel, imp
rpc.Handle(MethodUpdateValidations, 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(MethodUpdateValidations, err, req)
return nil
}
// Error explicitly ignored since response is nil anyway
_ = impl.UpdateValidations(authz)
if err := impl.UpdateValidations(authz); err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodUpdateValidations, err, authz)
}
return nil
})
@ -387,11 +429,15 @@ func NewCertificateAuthorityServer(serverQueue string, channel *amqp.Channel, im
csr, err := x509.ParseCertificateRequest(icReq.Bytes)
if err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodIssueCertificate, err, req)
return nil // XXX
}
cert, err := impl.IssueCertificate(*csr, icReq.RegID)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodIssueCertificate, err, csr)
return nil // XXX
}
@ -404,7 +450,11 @@ func NewCertificateAuthorityServer(serverQueue string, channel *amqp.Channel, im
})
rpc.Handle(MethodRevokeCertificateCA, func(req []byte) []byte {
_ = impl.RevokeCertificate(string(req)) // XXX
if err := impl.RevokeCertificate(string(req)); err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodRevokeCertificateCA, err, req)
}
return nil
})
@ -466,37 +516,44 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
reg, err := impl.GetRegistration(intReq.ID)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetRegistration, err, req)
return nil
}
jsonReg, err := json.Marshal(reg)
response, err = json.Marshal(reg)
if err != nil {
return nil
}
response = jsonReg
return response
})
rpc.Handle(MethodGetRegistrationByKey, func(req []byte) (response []byte) {
var jwk jose.JsonWebKey
err := json.Unmarshal(req, &jwk)
if err := json.Unmarshal(req, &jwk); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodGetRegistrationByKey, err, req)
}
reg, err := impl.GetRegistrationByKey(jwk)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetRegistrationByKey, err, jwk)
return nil
}
jsonReg, err := json.Marshal(reg)
response, err = json.Marshal(reg)
if err != nil {
return nil
}
response = jsonReg
return response
})
rpc.Handle(MethodGetAuthorization, func(req []byte) []byte {
authz, err := impl.GetAuthorization(string(req))
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetAuthorization, err, req)
return nil
}
@ -519,6 +576,8 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
id, err := impl.AddCertificate(icReq.Bytes, icReq.RegID)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodAddCertificate, err, req)
return nil
}
return []byte(id)
@ -528,12 +587,18 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
var registration core.Registration
err := json.Unmarshal(req, registration)
if err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodNewRegistration, err, req)
return nil
}
output, err := impl.NewRegistration(registration)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodNewRegistration, err, registration)
return nil
}
jsonOutput, err := json.Marshal(output)
if err != nil {
return nil
@ -543,7 +608,10 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
rpc.Handle(MethodNewPendingAuthorization, func(req []byte) (response []byte) {
id, err := impl.NewPendingAuthorization()
if err == nil {
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodNewPendingAuthorization, err, req)
} else {
response = []byte(id)
}
return response
@ -551,31 +619,40 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
rpc.Handle(MethodUpdatePendingAuthorization, func(req []byte) []byte {
var authz core.Authorization
err := json.Unmarshal(req, authz)
if err != nil {
if err := json.Unmarshal(req, authz); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodUpdatePendingAuthorization, err, req)
return nil
}
// Error explicitly ignored since response is nil anyway
_ = impl.UpdatePendingAuthorization(authz)
if err := impl.UpdatePendingAuthorization(authz); err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodUpdatePendingAuthorization, err, authz)
}
return nil
})
rpc.Handle(MethodFinalizeAuthorization, func(req []byte) []byte {
var authz core.Authorization
err := json.Unmarshal(req, authz)
if err != nil {
if err := json.Unmarshal(req, authz); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodFinalizeAuthorization, err, req)
return nil
}
// Error explicitly ignored since response is nil anyway
_ = impl.FinalizeAuthorization(authz)
if err := impl.FinalizeAuthorization(authz); err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodFinalizeAuthorization, err, authz)
}
return nil
})
rpc.Handle(MethodGetCertificate, func(req []byte) (response []byte) {
cert, err := impl.GetCertificate(string(req))
if err == nil {
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetCertificate, err, req)
} else {
response = []byte(cert)
}
return response
@ -583,7 +660,10 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
rpc.Handle(MethodGetCertificateByShortSerial, func(req []byte) (response []byte) {
cert, err := impl.GetCertificateByShortSerial(string(req))
if err == nil {
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetCertificateByShortSerial, err, req)
} else {
response = []byte(cert)
}
return response
@ -592,6 +672,8 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
rpc.Handle(MethodGetCertificateStatus, func(req []byte) (response []byte) {
status, err := impl.GetCertificateStatus(string(req))
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetCertificateStatus, err, req)
return nil
}
@ -609,13 +691,18 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
ReasonCode int
}
err := json.Unmarshal(req, revokeReq)
if err != nil {
if err := json.Unmarshal(req, revokeReq); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodMarkCertificateRevoked, err, req)
return nil
}
// Error explicitly ignored since response is nil anyway
_ = impl.MarkCertificateRevoked(revokeReq.Serial, revokeReq.OcspResponse, revokeReq.ReasonCode)
err := impl.MarkCertificateRevoked(revokeReq.Serial, revokeReq.OcspResponse, revokeReq.ReasonCode)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodMarkCertificateRevoked, err, revokeReq)
}
return nil
})
@ -624,12 +711,16 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
Names []string
}
err := json.Unmarshal(req, csrReq)
if err != nil {
if err := json.Unmarshal(req, csrReq); err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodAddDeniedCSR, err, req)
return nil
}
err = impl.AddDeniedCSR(csrReq.Names)
if err := impl.AddDeniedCSR(csrReq.Names); err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodAddDeniedCSR, err, csrReq)
}
return nil
})
@ -640,11 +731,15 @@ func NewStorageAuthorityServer(serverQueue string, channel *amqp.Channel, impl c
err := json.Unmarshal(req, csrReq)
if err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
improperMessage(MethodAlreadyDeniedCSR, err, req)
return nil
}
exists, err := impl.AlreadyDeniedCSR(csrReq.Names)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodAlreadyDeniedCSR, err, csrReq)
return nil
}

View File

@ -18,8 +18,8 @@ import (
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
"github.com/letsencrypt/boulder/core"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/square/go-jose"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
)
@ -293,21 +293,21 @@ func statusIsPending(status core.AcmeStatus) bool {
return status == core.StatusPending || status == core.StatusProcessing || status == core.StatusUnknown
}
func existingPending(tx *gorp.Transaction, id string) (bool) {
func existingPending(tx *gorp.Transaction, id string) bool {
var count int64
_ = tx.SelectOne(&count, "SELECT count(*) FROM pending_authz WHERE id = :id", map[string]interface{} {"id": id})
_ = tx.SelectOne(&count, "SELECT count(*) FROM pending_authz WHERE id = :id", map[string]interface{}{"id": id})
return count > 0
}
func existingFinal(tx *gorp.Transaction, id string) (bool) {
func existingFinal(tx *gorp.Transaction, id string) bool {
var count int64
_ = tx.SelectOne(&count, "SELECT count(*) FROM authz WHERE id = :id", map[string]interface{} {"id": id})
_ = tx.SelectOne(&count, "SELECT count(*) FROM authz WHERE id = :id", map[string]interface{}{"id": id})
return count > 0
}
func existingRegistration(tx *gorp.Transaction, id int64) (bool) {
func existingRegistration(tx *gorp.Transaction, id int64) bool {
var count int64
_ = tx.SelectOne(&count, "SELECT count(*) FROM registrations WHERE id = :id", map[string]interface{} {"id": id})
_ = tx.SelectOne(&count, "SELECT count(*) FROM registrations WHERE id = :id", map[string]interface{}{"id": id})
return count > 0
}
@ -330,7 +330,7 @@ func (ssa *SQLStorageAuthority) GetRegistrationByKey(key jose.JsonWebKey) (reg c
return
}
err = ssa.dbMap.SelectOne(&reg, "SELECT * FROM registrations WHERE key = :key", map[string]interface{} {"key": string(keyJson)})
err = ssa.dbMap.SelectOne(&reg, "SELECT * FROM registrations WHERE key = :key", map[string]interface{}{"key": string(keyJson)})
return
}
@ -383,7 +383,7 @@ func (ssa *SQLStorageAuthority) GetCertificateByShortSerial(shortSerial string)
var certificate core.Certificate
err = ssa.dbMap.SelectOne(&certificate, "SELECT * FROM certificates WHERE serial LIKE :shortSerial",
map[string]interface{} {"shortSerial": shortSerial+"%"})
map[string]interface{}{"shortSerial": shortSerial + "%"})
if err != nil {
return
}
@ -400,7 +400,7 @@ func (ssa *SQLStorageAuthority) GetCertificate(serial string) (cert []byte, err
var certificate core.Certificate
err = ssa.dbMap.SelectOne(&certificate, "SELECT * FROM certificates WHERE serial = :serial",
map[string]interface{} {"serial": serial})
map[string]interface{}{"serial": serial})
if err != nil {
return
}
@ -706,7 +706,7 @@ func (ssa *SQLStorageAuthority) AlreadyDeniedCSR(names []string) (already bool,
err = ssa.dbMap.SelectOne(
&denied,
"SELECT count(*) FROM deniedCsrs WHERE names = :names",
map[string]interface{} {"names": strings.ToLower(strings.Join(names, ","))},
map[string]interface{}{"names": strings.ToLower(strings.Join(names, ","))},
)
if err != nil {
return
@ -717,4 +717,3 @@ func (ssa *SQLStorageAuthority) AlreadyDeniedCSR(names []string) (already bool,
return
}

11
test.sh
View File

@ -63,4 +63,15 @@ else
run go test ${dirlist}
fi
echo "Checking for unformatted files:"
unformatted=$(find . -name "*.go" -not -path "./Godeps/*" -print | xargs -n1 gofmt -l)
if [ "x${unformatted}" != "x" ] ; then
echo "Unformatted files found; setting failure state."
echo "Please run 'go fmt' on each of these files and amend your commit to continue."
FAILURE=1
for f in ${unformatted}; do
echo "- ${f}"
done
fi
exit ${FAILURE}

View File

@ -87,6 +87,12 @@ func AssertContains(t *testing.T, haystack string, needle string) {
}
}
func AssertNotContains(t *testing.T, haystack string, needle string) {
if strings.Contains(haystack, needle) {
t.Errorf("%s String [%s] contains [%s]", caller(), haystack, needle)
}
}
func AssertSeverity(t *testing.T, data string, severity int) {
expected := fmt.Sprintf("\"severity\":%d", severity)
AssertContains(t, data, expected)

View File

@ -32,7 +32,7 @@ func NewValidationAuthorityImpl(tm bool) ValidationAuthorityImpl {
// Validation methods
func (va ValidationAuthorityImpl) validateSimpleHTTPS(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge) {
func (va ValidationAuthorityImpl) validateSimpleHTTPS(identifier core.AcmeIdentifier, input core.Challenge) core.Challenge {
challenge := input
if len(challenge.Path) == 0 {
@ -105,7 +105,7 @@ func (va ValidationAuthorityImpl) validateSimpleHTTPS(identifier core.AcmeIdenti
return challenge
}
func (va ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge) {
func (va ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier, input core.Challenge) core.Challenge {
challenge := input
if identifier.Type != "dns" {

View File

@ -6,24 +6,24 @@
package va
import (
"testing"
"net"
"net/http"
"fmt"
"strings"
"math/big"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"crypto/sha256"
"encoding/base64"
"fmt"
"math/big"
"net"
"net/http"
"strings"
"testing"
"time"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/square/go-jose"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/test"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/square/go-jose"
)
func bigIntFromB64(b64 string) *big.Int {
@ -160,7 +160,7 @@ func TestSimpleHttps(t *testing.T) {
func TestDvsni(t *testing.T) {
va := NewValidationAuthorityImpl(true)
a := []byte{1,2,3,4,5,6,7,8,9,0}
a := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
ba := core.B64enc(a)
chall := core.Challenge{R: ba, S: ba}

View File

@ -6,9 +6,9 @@
package wfe
import (
"database/sql"
"bytes"
"crypto/x509"
"database/sql"
"encoding/json"
"errors"
"fmt"
@ -26,6 +26,7 @@ import (
blog "github.com/letsencrypt/boulder/log"
)
// WebFrontEndImpl represents a Boulder web service and its resources
type WebFrontEndImpl struct {
RA core.RegistrationAuthority
SA core.StorageGetter
@ -54,6 +55,7 @@ type WebFrontEndImpl struct {
IssuerCert []byte
}
// NewWebFrontEndImpl constructs a web service for Boulder
func NewWebFrontEndImpl() WebFrontEndImpl {
logger := blog.GetAuditLogger()
logger.Notice("Web Front End Starting")
@ -122,7 +124,7 @@ func parseIDFromPath(path string) string {
return re.ReplaceAllString(path, "")
}
// Problem objects represent problem documents, which are
// ProblemType objects represent problem documents, which are
// returned with HTTP error responses
// https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00
type ProblemType string
@ -194,8 +196,9 @@ func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool) ([]
return []byte(payload), key, reg, nil
}
func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, message string, code int) {
problem := problem{Detail: message}
// Notify the client of an error condition and log it for audit purposes.
func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, details string, debug interface{}, code int) {
problem := problem{Detail: details}
switch code {
case http.StatusForbidden:
problem.Type = UnauthorizedProblem
@ -208,11 +211,26 @@ func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, message stri
case http.StatusInternalServerError:
problem.Type = ServerInternalProblem
}
problemDoc, err := json.Marshal(problem)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
wfe.log.Audit(fmt.Sprintf("Could not marshal error message: %s - %+v", err.Error(), problem))
problemDoc = []byte("{\"detail\": \"Problem marshalling error message.\"}")
}
wfe.log.Debug("Sending error to client: " + string(problemDoc))
switch problem.Type {
case ServerInternalProblem:
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
wfe.log.Audit(fmt.Sprintf("Internal error - %s - %s", details, debug))
case MalformedProblem:
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
wfe.log.Audit(fmt.Sprintf("Improper HTTP request - %s - %s", details, debug))
case UnauthorizedProblem:
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
wfe.log.Audit(fmt.Sprintf("Unauthorized HTTP request - %s - %s", details, debug))
}
// Paraphrased from
// https://golang.org/src/net/http/server.go#L1272
response.Header().Set("Content-Type", "application/problem+json")
@ -226,36 +244,34 @@ func link(url, relation string) string {
func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, request *http.Request) {
if request.Method != "POST" {
wfe.sendError(response, "Method not allowed", http.StatusMethodNotAllowed)
wfe.sendError(response, "Method not allowed", "", http.StatusMethodNotAllowed)
return
}
body, key, _, err := wfe.verifyPOST(request, false)
if err != nil {
wfe.sendError(response, "Unable to read/verify body", http.StatusBadRequest)
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
return
}
var init, unmarshalled core.Registration
err = json.Unmarshal(body, &unmarshalled)
if err != nil {
wfe.sendError(response, "Error unmarshaling JSON", http.StatusBadRequest)
wfe.sendError(response, "Error unmarshaling JSON", err, http.StatusBadRequest)
return
}
init.MergeUpdate(unmarshalled)
reg, err := wfe.RA.NewRegistration(init, *key)
if err != nil {
wfe.sendError(response,
fmt.Sprintf("Error creating new registration: %+v", err),
http.StatusInternalServerError)
wfe.sendError(response, "Error creating new registration", err, http.StatusInternalServerError)
return
}
regURL := wfe.RegBase + string(reg.ID)
responseBody, err := json.Marshal(reg)
if err != nil {
wfe.sendError(response, "Error marshaling authz", http.StatusInternalServerError)
wfe.sendError(response, "Error marshaling authz", err, http.StatusInternalServerError)
return
}
@ -275,24 +291,23 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, request *http.Request) {
if request.Method != "POST" {
wfe.sendError(response, "Method not allowed", http.StatusMethodNotAllowed)
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
}
body, _, currReg, err := wfe.verifyPOST(request, true)
if err != nil {
if err == sql.ErrNoRows {
wfe.sendError(response, "No registration exists matching provided key", http.StatusForbidden)
wfe.sendError(response, "No registration exists matching provided key", err, http.StatusForbidden)
} else {
wfe.sendError(response, "Unable to read/verify body", http.StatusBadRequest)
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
}
return
}
var init core.Authorization
if err = json.Unmarshal(body, &init); err != nil {
wfe.sendError(response, "Error unmarshaling JSON", http.StatusBadRequest)
wfe.sendError(response, "Error unmarshaling JSON", err, http.StatusBadRequest)
return
}
@ -300,7 +315,7 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
authz, err := wfe.RA.NewAuthorization(init, currReg.ID)
if err != nil {
wfe.sendError(response,
fmt.Sprintf("Error creating new authz: %+v", err),
"Error creating new authz", err,
http.StatusInternalServerError)
return
}
@ -310,7 +325,7 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
authz.ID = ""
responseBody, err := json.Marshal(authz)
if err != nil {
wfe.sendError(response, "Error marshaling authz", http.StatusInternalServerError)
wfe.sendError(response, "Error marshaling authz", err, http.StatusInternalServerError)
return
}
@ -327,13 +342,13 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, request *http.Request) {
if request.Method != "POST" {
wfe.sendError(response, "Method not allowed", http.StatusMethodNotAllowed)
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
}
body, requestKey, _, err := wfe.verifyPOST(request, false)
if err != nil {
wfe.sendError(response, "Unable to read/verify body", http.StatusBadRequest)
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
return
}
@ -343,36 +358,36 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
var revokeRequest RevokeRequest
if err = json.Unmarshal(body, &revokeRequest); err != nil {
wfe.log.Debug(fmt.Sprintf("Couldn't unmarshal in revoke request %s", string(body)))
wfe.sendError(response, "Unable to read/verify body", http.StatusBadRequest)
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
return
}
providedCert, err := x509.ParseCertificate(revokeRequest.CertificateDER)
if err != nil {
wfe.log.Debug("Couldn't parse cert in revoke request.")
wfe.sendError(response, "Unable to read/verify body", http.StatusBadRequest)
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
return
}
serial := core.SerialToString(providedCert.SerialNumber)
certDER, err := wfe.SA.GetCertificate(serial)
if err != nil || !bytes.Equal(certDER, revokeRequest.CertificateDER) {
wfe.sendError(response, "No such certificate", http.StatusNotFound)
wfe.sendError(response, "No such certificate", err, http.StatusNotFound)
return
}
parsedCertificate, err := x509.ParseCertificate(certDER)
if err != nil {
wfe.sendError(response, "Invalid certificate", http.StatusInternalServerError)
wfe.sendError(response, "Invalid certificate", err, http.StatusInternalServerError)
return
}
certStatus, err := wfe.SA.GetCertificateStatus(serial)
if err != nil {
wfe.sendError(response, "No such certificate", http.StatusNotFound)
wfe.sendError(response, "No such certificate", err, http.StatusNotFound)
return
}
if certStatus.Status == core.OCSPStatusRevoked {
wfe.sendError(response, "Certificate already revoked", http.StatusConflict)
wfe.sendError(response, "Certificate already revoked", "", http.StatusConflict)
return
}
@ -382,6 +397,7 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
wfe.log.Debug("Key mismatch for revoke")
wfe.sendError(response,
"Revocation request must be signed by private key of cert to be revoked",
requestKey,
http.StatusForbidden)
return
}
@ -390,6 +406,7 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
if err != nil {
wfe.sendError(response,
"Failed to revoke certificate",
err,
http.StatusInternalServerError)
} else {
wfe.log.Debug(fmt.Sprintf("Revoked %v", serial))
@ -401,25 +418,24 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request *http.Request) {
if request.Method != "POST" {
wfe.sendError(response, "Method not allowed", http.StatusMethodNotAllowed)
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
}
body, key, reg, err := wfe.verifyPOST(request, true)
if err != nil {
if err == sql.ErrNoRows {
wfe.sendError(response, "No registration exists matching provided key", http.StatusForbidden)
wfe.sendError(response, "No registration exists matching provided key", err, http.StatusForbidden)
} else {
wfe.sendError(response, "Unable to read/verify body", http.StatusBadRequest)
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
}
return
}
var init core.CertificateRequest
if err = json.Unmarshal(body, &init); err != nil {
fmt.Println(err)
wfe.sendError(response, "Error unmarshaling certificate request", http.StatusBadRequest)
wfe.sendError(response, "Error unmarshaling certificate request", err, http.StatusBadRequest)
return
}
@ -435,7 +451,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
cert, err := wfe.RA.NewCertificate(init, reg.ID)
if err != nil {
wfe.sendError(response,
fmt.Sprintf("Error creating new cert: %+v", err),
"Error creating new cert", err,
http.StatusBadRequest)
return
}
@ -474,46 +490,45 @@ func (wfe *WebFrontEndImpl) Challenge(authz core.Authorization, response http.Re
}
if !found {
wfe.sendError(response,
fmt.Sprintf("Unable to find challenge"),
http.StatusNotFound)
wfe.sendError(response, "Unable to find challenge", request.URL.RawQuery, http.StatusNotFound)
return
}
switch request.Method {
default:
wfe.sendError(response, "Method not allowed", http.StatusMethodNotAllowed)
wfe.sendError(response, "Method not allowed", "", http.StatusMethodNotAllowed)
return
case "POST":
body, _, currReg, err := wfe.verifyPOST(request, true)
if err != nil {
if err == sql.ErrNoRows {
wfe.sendError(response, "No registration exists matching provided key", http.StatusForbidden)
wfe.sendError(response, "No registration exists matching provided key", err, http.StatusForbidden)
} else {
wfe.sendError(response, "Unable to read/verify body", http.StatusBadRequest)
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
}
return
}
var challengeResponse core.Challenge
if err = json.Unmarshal(body, &challengeResponse); err != nil {
wfe.sendError(response, "Error unmarshaling authorization", http.StatusBadRequest)
wfe.sendError(response, "Error unmarshaling authorization", err, http.StatusBadRequest)
return
}
// Check that the registration ID matching the key used matches
// the registration ID on the authz object
if currReg.ID != authz.RegistrationID {
wfe.sendError(response, "User registration ID doesn't match registration ID in authorization", http.StatusForbidden)
wfe.sendError(response, "User registration ID doesn't match registration ID in authorization",
fmt.Sprintf("User: %v != Authorization: %v", currReg.ID, authz.RegistrationID),
http.StatusForbidden)
return
}
// Ask the RA to update this authorization
updatedAuthz, err := wfe.RA.UpdateAuthorization(authz, challengeIndex, challengeResponse)
if err != nil {
wfe.sendError(response, "Unable to update authorization", http.StatusInternalServerError)
wfe.sendError(response, "Unable to update authorization", err, http.StatusInternalServerError)
return
}
@ -521,7 +536,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 challenge", http.StatusInternalServerError)
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
return
}
@ -544,16 +559,16 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
idStr := parseIDFromPath(request.URL.Path)
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
wfe.sendError(response, "Registration ID must be an integer", http.StatusBadRequest)
wfe.sendError(response, "Registration ID must be an integer", err, http.StatusBadRequest)
return
} else if id <= 0 {
wfe.sendError(response, "Registration ID must be a positive non-zero integer", http.StatusBadRequest)
wfe.sendError(response, "Registration ID must be a positive non-zero integer", id, http.StatusBadRequest)
return
}
reg, err := wfe.SA.GetRegistration(id)
if err != nil {
wfe.sendError(response,
fmt.Sprintf("Unable to find registration: %+v", err),
"Unable to find registration", err,
http.StatusNotFound)
return
}
@ -561,13 +576,13 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
switch request.Method {
default:
wfe.sendError(response, "Method not allowed", http.StatusMethodNotAllowed)
wfe.sendError(response, "Method not allowed", "", http.StatusMethodNotAllowed)
return
case "GET":
jsonReply, err := json.Marshal(reg)
if err != nil {
wfe.sendError(response, "Failed to marshal authz", http.StatusInternalServerError)
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
return
}
response.Header().Set("Content-Type", "application/json")
@ -578,9 +593,9 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
body, _, currReg, err := wfe.verifyPOST(request, true)
if err != nil {
if err == sql.ErrNoRows {
wfe.sendError(response, "No registration exists matching provided key", http.StatusForbidden)
wfe.sendError(response, "No registration exists matching provided key", err, http.StatusForbidden)
} else {
wfe.sendError(response, "Unable to read/verify body", http.StatusBadRequest)
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
}
return
}
@ -588,20 +603,20 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
var update core.Registration
err = json.Unmarshal(body, &update)
if err != nil {
wfe.sendError(response, "Error unmarshaling registration", http.StatusBadRequest)
wfe.sendError(response, "Error unmarshaling registration", err, http.StatusBadRequest)
return
}
// Ask the RA to update this authorization
updatedReg, err := wfe.RA.UpdateRegistration(currReg, update)
if err != nil {
wfe.sendError(response, "Unable to update registration", http.StatusInternalServerError)
wfe.sendError(response, "Unable to update registration", err, http.StatusInternalServerError)
return
}
jsonReply, err := json.Marshal(updatedReg)
if err != nil {
wfe.sendError(response, "Failed to marshal authz", http.StatusInternalServerError)
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
return
}
response.Header().Set("Content-Type", "application/json")
@ -617,7 +632,7 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
authz, err := wfe.SA.GetAuthorization(id)
if err != nil {
wfe.sendError(response,
fmt.Sprintf("Unable to find authorization: %+v", err),
"Unable to find authorization", err,
http.StatusNotFound)
return
}
@ -630,13 +645,13 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
switch request.Method {
default:
wfe.sendError(response, "Method not allowed", http.StatusMethodNotAllowed)
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
case "GET":
jsonReply, err := json.Marshal(authz)
if err != nil {
wfe.sendError(response, "Failed to marshal authz", http.StatusInternalServerError)
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
return
}
response.Header().Set("Content-Type", "application/json")
@ -649,35 +664,30 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
var allHex = regexp.MustCompile("^[0-9a-f]+$")
func (wfe *WebFrontEndImpl) notFound(response http.ResponseWriter) {
wfe.sendError(response, "Not found", http.StatusNotFound)
}
func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *http.Request) {
path := request.URL.Path
switch request.Method {
default:
wfe.sendError(response, "Method not allowed", http.StatusMethodNotAllowed)
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
case "GET":
// Certificate paths consist of the CertBase path, plus exactly sixteen hex
// digits.
if !strings.HasPrefix(path, wfe.CertPath) {
wfe.notFound(response)
wfe.sendError(response, "Not found", path, http.StatusNotFound)
return
}
serial := path[len(wfe.CertPath):]
if len(serial) != 16 || !allHex.Match([]byte(serial)) {
wfe.notFound(response)
wfe.sendError(response, "Not found", serial, http.StatusNotFound)
return
}
wfe.log.Debug(fmt.Sprintf("Requested certificate ID %s", serial))
cert, err := wfe.SA.GetCertificateByShortSerial(serial)
if err != nil {
wfe.log.Debug(fmt.Sprintf("Not found cert: %v", err))
wfe.notFound(response)
wfe.sendError(response, "Not found", err, http.StatusNotFound)
return
}

View File

@ -7,7 +7,9 @@ package wfe
import (
"crypto/x509"
"database/sql"
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
@ -29,11 +31,85 @@ type MockSA struct {
// empty
}
func (sa *MockSA) GetRegistration(int64) (core.Registration, error) {
const (
test1KeyPublicJSON = `
{
"kty":"RSA",
"n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
"e":"AAEAAQ"
}`
test1KeyPrivatePEM = `
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAyNWVhtYEKJR21y9xsHV+PD/bYwbXSeNuFal46xYxVfRL5mqh
a7vttvjB/vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K/klBYN8oYvTwwmeSkAz
6ut7ZxPv+nZaT5TJhGk0NT2kh/zSpdriEJ/3vW+mqxYbbBmpvHqsa1/zx9fSuHYc
tAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV+mzfMyboQjujPh7aNJxAWS
q4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF+w8hOTI3XXohUdu
29Se26k2B0PolDSuj0GIQU6+W9TdLXSjBb2SpQIDAQABAoIBAHw58SXYV/Yp72Cn
jjFSW+U0sqWMY7rmnP91NsBjl9zNIe3C41pagm39bTIjB2vkBNR8ZRG7pDEB/QAc
Cn9Keo094+lmTArjL407ien7Ld+koW7YS8TyKADYikZo0vAK3qOy14JfQNiFAF9r
Bw61hG5/E58cK5YwQZe+YcyBK6/erM8fLrJEyw4CV49wWdq/QqmNYU1dx4OExAkl
KMfvYXpjzpvyyTnZuS4RONfHsO8+JTyJVm+lUv2x+bTce6R4W++UhQY38HakJ0x3
XRfXooRv1Bletu5OFlpXfTSGz/5gqsfemLSr5UHncsCcFMgoFBsk2t/5BVukBgC7
PnHrAjkCgYEA887PRr7zu3OnaXKxylW5U5t4LzdMQLpslVW7cLPD4Y08Rye6fF5s
O/jK1DNFXIoUB7iS30qR7HtaOnveW6H8/kTmMv/YAhLO7PAbRPCKxxcKtniEmP1x
ADH0tF2g5uHB/zeZhCo9qJiF0QaJynvSyvSyJFmY6lLvYZsAW+C+PesCgYEA0uCi
Q8rXLzLpfH2NKlLwlJTi5JjE+xjbabgja0YySwsKzSlmvYJqdnE2Xk+FHj7TCnSK
KUzQKR7+rEk5flwEAf+aCCNh3W4+Hp9MmrdAcCn8ZsKmEW/o7oDzwiAkRCmLw/ck
RSFJZpvFoxEg15riT37EjOJ4LBZ6SwedsoGA/a8CgYEA2Ve4sdGSR73/NOKZGc23
q4/B4R2DrYRDPhEySnMGoPCeFrSU6z/lbsUIU4jtQWSaHJPu4n2AfncsZUx9WeSb
OzTCnh4zOw33R4N4W8mvfXHODAJ9+kCc1tax1YRN5uTEYzb2dLqPQtfNGxygA1DF
BkaC9CKnTeTnH3TlKgK8tUcCgYB7J1lcgh+9ntwhKinBKAL8ox8HJfkUM+YgDbwR
sEM69E3wl1c7IekPFvsLhSFXEpWpq3nsuMFw4nsVHwaGtzJYAHByhEdpTDLXK21P
heoKF1sioFbgJB1C/Ohe3OqRLDpFzhXOkawOUrbPjvdBM2Erz/r11GUeSlpNazs7
vsoYXQKBgFwFM1IHmqOf8a2wEFa/a++2y/WT7ZG9nNw1W36S3P04K4lGRNRS2Y/S
snYiqxD9nL7pVqQP2Qbqbn0yD6d3G5/7r86F7Wu2pihM8g6oyMZ3qZvvRIBvKfWo
eROL1ve1vmQF3kjrMPhhK2kr6qdWnTE5XlPllVSZFQenSTzj98AO
-----END RSA PRIVATE KEY-----
`
test2KeyPublicJSON = `
{
"kty":"RSA",
"n":"m5Cpx3vZ0CjATirDpbILvq78fm3Dv5RBkO1VLWFmJj5Mb54vc9oYZWc1V1k-LJoESuuPHhaNO2Eu8T9tslQWcZSzr5NImxAwMk970gVQa-Hqv-Jr6xstrBpq7TKpXHTx2FnfA2wQrfIQSlBXu0t4jdUOr3oJh-QXvma8nLITdtjpC0AZNtqd0QkRJX_90SaNrl18Rr_0JrBH9ZmUSFcf3mo_BtL0Gx0jE3n-iwCI8rQtfyVP__9-n__r4IhalKLzaeio6o-qrdemh0EZgjKGCS1_RpTIeArkO8uia1KgOq-z-GfemKEm4s07WO_a0_9dLqbvpnyyZvUi405m3vGDfQ",
"e":"AAEAAQ"
}`
)
func (sa *MockSA) GetRegistration(id int64) (core.Registration, error) {
if id == 100 {
// Tag meaning "Missing"
return core.Registration{}, errors.New("missing")
}
if id == 101 {
// Tag meaning "Malformed"
return core.Registration{}, nil
}
keyJSON := []byte(test1KeyPublicJSON)
var parsedKey jose.JsonWebKey
parsedKey.UnmarshalJSON(keyJSON)
return core.Registration{Key: parsedKey}, nil
}
func (sa *MockSA) GetRegistrationByKey(jose.JsonWebKey) (core.Registration, error) {
func (sa *MockSA) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, error) {
var test1KeyPublic jose.JsonWebKey
var test2KeyPublic jose.JsonWebKey
test1KeyPublic.UnmarshalJSON([]byte(test1KeyPublicJSON))
test2KeyPublic.UnmarshalJSON([]byte(test2KeyPublicJSON))
if core.KeyDigestEquals(jwk, test1KeyPublic) {
return core.Registration{ID: 1, Key: jwk}, nil
}
if core.KeyDigestEquals(jwk, test2KeyPublic) {
// No key found
return core.Registration{}, sql.ErrNoRows
}
// Return a fake registration
return core.Registration{ID: 1}, nil
}
@ -234,7 +310,7 @@ func TestIssueCertificate(t *testing.T) {
test.AssertEquals(t,
responseWriter.Body.String(),
// TODO: I think this is wrong. The CSR in the payload above was created by openssl and should be valid.
"{\"type\":\"urn:acme:error:malformed\",\"detail\":\"Error creating new cert: Invalid signature on CSR\"}")
"{\"type\":\"urn:acme:error:malformed\",\"detail\":\"Error creating new cert\"}")
}
type MockRegistrationAuthority struct{}
@ -265,7 +341,8 @@ func (ra *MockRegistrationAuthority) RevokeCertificate(cert x509.Certificate) er
return nil
}
func (ra *MockRegistrationAuthority) OnValidationUpdate(authz core.Authorization) {
func (ra *MockRegistrationAuthority) OnValidationUpdate(authz core.Authorization) error {
return nil
}
func TestChallenge(t *testing.T) {
@ -329,7 +406,7 @@ func TestChallenge(t *testing.T) {
"{\"type\":\"dns\",\"uri\":\"/acme/authz/asdf?challenge=foo\"}")
}
func TestRegistration(t *testing.T) {
func TestNewRegistration(t *testing.T) {
wfe := NewWebFrontEndImpl()
wfe.RA = &MockRegistrationAuthority{}
wfe.SA = &MockSA{}
@ -505,3 +582,96 @@ func TestAuthorization(t *testing.T) {
err := json.Unmarshal([]byte(responseWriter.Body.String()), &authz)
test.AssertNotError(t, err, "Couldn't unmarshal returned authorization object")
}
func TestRegistration(t *testing.T) {
wfe := NewWebFrontEndImpl()
wfe.RA = &MockRegistrationAuthority{}
wfe.SA = &MockSA{}
wfe.Stats, _ = statsd.NewNoopClient()
responseWriter := httptest.NewRecorder()
// Test invalid method
path, _ := url.Parse("/1")
wfe.Registration(responseWriter, &http.Request{
Method: "MAKE-COFFEE",
Body: makeBody("invalid"),
URL: path,
})
test.AssertEquals(t,
responseWriter.Body.String(),
"{\"type\":\"urn:acme:error:malformed\",\"detail\":\"Method not allowed\"}")
responseWriter.Body.Reset()
// Test GET missing entry
path, _ = url.Parse("/100")
wfe.Registration(responseWriter, &http.Request{
Method: "GET",
URL: path,
})
test.AssertEquals(t,
responseWriter.Body.String(),
"{\"type\":\"urn:acme:error:malformed\",\"detail\":\"Unable to find registration\"}")
responseWriter.Body.Reset()
// Test GET malformed entry
path, _ = url.Parse("/101")
wfe.Registration(responseWriter, &http.Request{
Method: "GET",
URL: path,
})
test.AssertEquals(t,
responseWriter.Body.String(),
"{\"type\":\"urn:acme:error:serverInternal\",\"detail\":\"Failed to marshal authz\"}")
responseWriter.Body.Reset()
// Test GET proper entry
path, _ = url.Parse("/1")
wfe.Registration(responseWriter, &http.Request{
Method: "GET",
URL: path,
})
test.AssertNotContains(t, responseWriter.Body.String(), "urn:acme:error")
responseWriter.Body.Reset()
// Test POST invalid JSON
path, _ = url.Parse("/2")
wfe.Registration(responseWriter, &http.Request{
Method: "POST",
Body: makeBody("invalid"),
URL: path,
})
test.AssertEquals(t,
responseWriter.Body.String(),
"{\"type\":\"urn:acme:error:malformed\",\"detail\":\"Unable to read/verify body\"}")
responseWriter.Body.Reset()
// Test POST valid JSON but key is not registered
path, _ = url.Parse("/1")
wfe.Registration(responseWriter, &http.Request{
Method: "POST",
Body: makeBody(`{
"payload" : "ewogICJjb250YWN0IjogWwogICAgIm1haWx0bzpjZXJ0LWFkbWluQGV4YW1wbGUuY28ubnoiLAogICAgInRlbDorMjQ5NTU1MTIxMiIKICBdLAogICJhZ3JlZW1lbnQiOiAieWVzIgp9Cg",
"protected" : "eyJhbGciOiJQUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoibTVDcHgzdlowQ2pBVGlyRHBiSUx2cTc4Zm0zRHY1UkJrTzFWTFdGbUpqNU1iNTR2YzlvWVpXYzFWMWstTEpvRVN1dVBIaGFOTzJFdThUOXRzbFFXY1pTenI1TklteEF3TWs5NzBnVlFhLUhxdi1KcjZ4c3RyQnBxN1RLcFhIVHgyRm5mQTJ3UXJmSVFTbEJYdTB0NGpkVU9yM29KaC1RWHZtYThuTElUZHRqcEMwQVpOdHFkMFFrUkpYXzkwU2FOcmwxOFJyXzBKckJIOVptVVNGY2YzbW9fQnRMMEd4MGpFM24taXdDSThyUXRmeVZQX185LW5fX3I0SWhhbEtMemFlaW82by1xcmRlbWgwRVpnaktHQ1MxX1JwVEllQXJrTzh1aWExS2dPcS16LUdmZW1LRW00czA3V09fYTBfOWRMcWJ2cG55eVp2VWk0MDVtM3ZHRGZRIiwiZSI6IkFBRUFBUSJ9fQ",
"signature" : "exg0HJRHk-oSDiaOlgtTkT_COqDRyIAJr4g9fDAJh5GF5evXAfT0Hbkfy4TYzqvF6oOldIaCylYhXjYtve4JLXEMdAj1DaR7kGVALskLg-XbiZ0-IaFBiDDaT6mwyLBTfstX4DD2OL7x0vyuTK16bHEIF0hncwHYVSoX5eFOBQLVu_gjxc7J5OZK4ugSJxZEilTVta0A9EdXdUxth0qqbZg_hJDmGOyNge03C71GbhMs-DF-rujlhe7L4VhcV3U0Wj8kSuAGn_DIHBJ1zM0H46PRgyz_9DgkJ6XnE5W8ZA3kF0VPFSp4ofqBhkFUXLXPPJJUEurAQxBJMaU31ef8bg"
}`),
URL: path,
})
test.AssertEquals(t,
responseWriter.Body.String(),
"{\"type\":\"urn:acme:error:unauthorized\",\"detail\":\"No registration exists matching provided key\"}")
responseWriter.Body.Reset()
// Test POST valid JSON with registration up in the mock
path, _ = url.Parse("/2")
wfe.Registration(responseWriter, &http.Request{
Method: "POST",
Body: makeBody(`{
"payload" : "ewogICJjb250YWN0IjogWwogICAgIm1haWx0bzpjZXJ0LWFkbWluQGV4YW1wbGUuY29tIiwKICAgICJ0ZWw6KzEyMDI1NTUxMjEyIgogIF0sCiAgImFncmVlbWVudCI6ICJ5ZXMiCn0K",
"protected" : "eyJhbGciOiJQUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoieU5XVmh0WUVLSlIyMXk5eHNIVi1QRF9iWXdiWFNlTnVGYWw0NnhZeFZmUkw1bXFoYTd2dHR2akJfdmM3WGcyUnZnQ3hIUENxb3hnTVBUekhyWlQ3NUxqQ3dJVzJLX2tsQllOOG9ZdlR3d21lU2tBejZ1dDdaeFB2LW5aYVQ1VEpoR2swTlQya2hfelNwZHJpRUpfM3ZXLW1xeFliYkJtcHZIcXNhMV96eDlmU3VIWWN0QVpKV3p4elVaWHlrYldNV1FacEVpRTBKNGFqajUxZkluRXpWbjdWeFYtbXpmTXlib1FqdWpQaDdhTkp4QVdTcTRvUUVKSkRnV3dTaDlsZXlvSm9QcE9OSHhoNW5FRTVBakUwMUZrR0lDU3hqcFpzRi13OGhPVEkzWFhvaFVkdTI5U2UyNmsyQjBQb2xEU3VqMEdJUVU2LVc5VGRMWFNqQmIyU3BRIiwiZSI6IkFBRUFBUSJ9fQ",
"signature" : "qZ5WWZJxhub1VCNdgAv-y02YLIc9QtHS7lKxVAiRqXPynENsL7_x63whkfvvHHEUiSyyf9pJCLY9NJfiFr-b4QiBOS7QB4JGj8NTghAeFycPerb6e4XCVx9xKljefybAm5yDOUFjG8PYW-XqrxarnVuykRUBIZuBR2d7sxhH09D5uZzJC9I96D7qEliqiglTdzBCAupDY_V7YQc46UzmQ3O_NGWOHr9Z7WYNOZpADwBzfIyWZQlmq3HxS0xYPYbY8FLYI6NzsHTQFkVGCTmZ7KmsyYsYj6uldchn88zcG9KO-53hZh8S5Kdy5FXh8iB_HqUn4j8yKGC9YmK4ERLlGg"
}`),
URL: path,
})
test.AssertNotContains(t, responseWriter.Body.String(), "urn:acme:error")
responseWriter.Body.Reset()
}