Merge master
This commit is contained in:
commit
12589834a3
|
@ -61,6 +61,22 @@ type PKCS11Config struct {
|
|||
Label string
|
||||
}
|
||||
|
||||
// This map is used to detect algorithms in crypto/x509 that
|
||||
// are no longer considered sufficiently strong.
|
||||
// * No MD2, MD5, or SHA-1
|
||||
// * No DSA
|
||||
//
|
||||
// SHA1WithRSA is allowed because there's still a fair bit of it
|
||||
// out there, but we should try to remove it soon.
|
||||
var badSignatureAlgorithms = map[x509.SignatureAlgorithm]bool{
|
||||
x509.UnknownSignatureAlgorithm: true,
|
||||
x509.MD2WithRSA: true,
|
||||
x509.MD5WithRSA: true,
|
||||
x509.DSAWithSHA1: true,
|
||||
x509.DSAWithSHA256: true,
|
||||
x509.ECDSAWithSHA1: true,
|
||||
}
|
||||
|
||||
// CertificateAuthorityImpl represents a CA that signs certificates, CRLs, and
|
||||
// OCSP responses.
|
||||
type CertificateAuthorityImpl struct {
|
||||
|
@ -277,6 +293,12 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
|
|||
ca.log.AuditErr(err)
|
||||
return emptyCert, err
|
||||
}
|
||||
if badSignatureAlgorithms[csr.SignatureAlgorithm] {
|
||||
err = fmt.Errorf("Invalid signature algorithm in CSR")
|
||||
// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
|
||||
ca.log.AuditErr(err)
|
||||
return emptyCert, err
|
||||
}
|
||||
|
||||
// Pull hostnames from CSR
|
||||
// Authorization is checked by the RA
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"encoding/asn1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -275,6 +274,59 @@ var TooManyNameCSRhex = "308202aa30820192020100300d310b3009060355040613025553308
|
|||
"9c470101aed4852cd7b3a0a5a489b264779118b30d51907dc745879a821b" +
|
||||
"85f19a24d20dc5b48141eff276aa73bb41141305905b0223ded7"
|
||||
|
||||
// CSR generated by Go:
|
||||
// * Random public key -- 512 bits long
|
||||
// * CN = (none)
|
||||
// * DNSNames = not-example.com, www.not-example.com, mail.not-example.com
|
||||
var ShortKeyCSRhex = "3082011f3081ca020100300d310b30090603550406130255" +
|
||||
"53305c300d06092a864886f70d0101010500034b00304802" +
|
||||
"4100ca8ff48b32197226bbe6398b6ba62dd4f09be06dc742" +
|
||||
"4b0c6321323d90c58e93cad2b3c91b5809493836f110dfc6" +
|
||||
"bbb3eca5575e6416a3a8189e782f41a29b990203010001a0" +
|
||||
"58305606092a864886f70d01090e3149304730450603551d" +
|
||||
"11043e303c820f6e6f742d6578616d706c652e636f6d8213" +
|
||||
"7777772e6e6f742d6578616d706c652e636f6d82146d6169" +
|
||||
"6c2e6e6f742d6578616d706c652e636f6d300d06092a8648" +
|
||||
"86f70d0101050500034100bfdc34a0a728918f4221413631" +
|
||||
"a70656db80ddb1b8126a02e6ca92c48301c350f29b6049b3" +
|
||||
"c4d70bf22ac273c951e60dd22f6f427b8d234e85f6881fba" +
|
||||
"35c288"
|
||||
|
||||
// CSR generated by Go:
|
||||
// * Random public key
|
||||
// * CN = (none)
|
||||
// * DNSNames = not-example.com, www.not-example.com, mail.not-example.com
|
||||
// * Signature algorithm: SHA1WithRSA
|
||||
var BadAlgorithmCSRhex = "308202aa30820192020100300d310b300906035504061302" +
|
||||
"555330820122300d06092a864886f70d0101010500038201" +
|
||||
"0f003082010a02820101009f70f8d60b07f24b6960e18da8" +
|
||||
"aabdc993c583a6c43c81ae256a1d4328146f3e907fb65dc4" +
|
||||
"f30cb27c96d020e55bf116ef33f3696d2d957b9b408d6762" +
|
||||
"2fe562311c96023100985ed8bc10929e7c552d7674027b63" +
|
||||
"3973ede006b1ba338f9f4b7a88b4ea768c70632dde0f202a" +
|
||||
"f59d41bf9aaa841e022735cfb2822a919b2b9ada07bdd569" +
|
||||
"347894f1aa4908ee86d2019eb8980e819af20b5084eb11f7" +
|
||||
"2f4f51e77c99d09e556ae84a720fa728962c5f7db176ce0a" +
|
||||
"27b824fc63849ad5493ba790cd8f0c0aedf22d7979a95fce" +
|
||||
"f4dfa552f8f85e15f5e7e427002b56f03f040e7cd43145d7" +
|
||||
"7732ef9c4568b0f22aa91ca1e5a133f862939ed14c1859a1" +
|
||||
"fdd36f0203010001a058305606092a864886f70d01090e31" +
|
||||
"49304730450603551d11043e303c820f6e6f742d6578616d" +
|
||||
"706c652e636f6d82137777772e6e6f742d6578616d706c65" +
|
||||
"2e636f6d82146d61696c2e6e6f742d6578616d706c652e63" +
|
||||
"6f6d300d06092a864886f70d0101050500038201010056cf" +
|
||||
"65a690ef9d279caa0b5e44c51a1f97631c43e9f0c67fd329" +
|
||||
"6b0d33acf06a695130fbdb8842a40d690549ab05f44b1738" +
|
||||
"e8419b395563426d76cc5611d17ade13c96c92496a2ed008" +
|
||||
"a78687f25c892af274afd9518540300edd160dc47887b402" +
|
||||
"a0705b156e9b623f47a3bdaea93b84a4cf873ac1e9861b67" +
|
||||
"f742024eff40c7c8e33243a953e9874e678c3f4cd24b4699" +
|
||||
"28214425ef7b9181d8b10b7f07f557bef7c4b22689be4b2a" +
|
||||
"42a0b6f0aaefa4dc8913338785e7f8fc2b1b95c4d189d10f" +
|
||||
"72dbe575cdd6985e2d6eb0d0cbe962de0986cbcc8ca30dc8" +
|
||||
"fa254808b0ee8946253830c087da95404181cb88ec59d399" +
|
||||
"b4b3fcc21bbb1c9f3e1b29fd576a"
|
||||
|
||||
// Times for validity checking
|
||||
var FarFuture = time.Date(2100, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
var FarPast = time.Date(1950, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
@ -546,13 +598,22 @@ func TestShortKey(t *testing.T) {
|
|||
ca.SA = storageAuthority
|
||||
ca.MaxKeySize = 4096
|
||||
|
||||
csrDER, err := ioutil.ReadFile("shortkey-csr.der")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read shortkey-csr.der")
|
||||
}
|
||||
// Test that the CA rejects CSRs that would expire after the intermediate cert
|
||||
csrDER, _ := hex.DecodeString(ShortKeyCSRhex)
|
||||
csr, _ := x509.ParseCertificateRequest(csrDER)
|
||||
_, err = ca.IssueCertificate(*csr, 1, FarFuture)
|
||||
if err == nil {
|
||||
t.Errorf("CA improperly created a certificate with short key.")
|
||||
}
|
||||
test.Assert(t, err != nil, "Issued a certificate with too short a key.")
|
||||
}
|
||||
|
||||
func TestRejectBadAlgorithm(t *testing.T) {
|
||||
cadb, storageAuthority, caConfig := setup(t)
|
||||
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
|
||||
ca.SA = storageAuthority
|
||||
ca.MaxKeySize = 4096
|
||||
|
||||
// Test that the CA rejects CSRs that would expire after the intermediate cert
|
||||
csrDER, _ := hex.DecodeString(BadAlgorithmCSRhex)
|
||||
csr, _ := x509.ParseCertificateRequest(csrDER)
|
||||
_, err = ca.IssueCertificate(*csr, 1, FarFuture)
|
||||
test.Assert(t, err != nil, "Issued a certificate based on a CSR with a weak algorithm.")
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -40,6 +40,7 @@ func main() {
|
|||
dnsTimeout, err := time.ParseDuration(c.VA.DNSTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse DNS timeout")
|
||||
vai.DNSResolver = core.NewDNSResolver(dnsTimeout, []string{c.VA.DNSResolver})
|
||||
vai.UserAgent = c.VA.UserAgent
|
||||
|
||||
for {
|
||||
ch, err := cmd.AmqpChannel(c)
|
||||
|
|
|
@ -55,7 +55,8 @@ func main() {
|
|||
|
||||
blog.SetAuditLogger(auditlogger)
|
||||
|
||||
wfe := wfe.NewWebFrontEndImpl()
|
||||
wfe, err := wfe.NewWebFrontEndImpl()
|
||||
cmd.FailOnError(err, "Unable to create WFE")
|
||||
rac, sac, closeChan := setupWFE(c)
|
||||
wfe.RA = &rac
|
||||
wfe.SA = &sac
|
||||
|
|
|
@ -42,7 +42,8 @@ func main() {
|
|||
go cmd.ProfileCmd("Monolith", stats)
|
||||
|
||||
// Create the components
|
||||
wfei := wfe.NewWebFrontEndImpl()
|
||||
wfei, err := wfe.NewWebFrontEndImpl()
|
||||
cmd.FailOnError(err, "Unable to create WFE")
|
||||
sa, err := sa.NewSQLStorageAuthority(c.SA.DBDriver, c.SA.DBName)
|
||||
cmd.FailOnError(err, "Unable to create SA")
|
||||
sa.SetSQLDebug(c.SQL.SQLDebug)
|
||||
|
@ -53,6 +54,7 @@ func main() {
|
|||
dnsTimeout, err := time.ParseDuration(c.VA.DNSTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse DNS timeout")
|
||||
va.DNSResolver = core.NewDNSResolver(dnsTimeout, []string{c.VA.DNSResolver})
|
||||
va.UserAgent = c.VA.UserAgent
|
||||
|
||||
cadb, err := ca.NewCertificateAuthorityDatabaseImpl(c.CA.DBDriver, c.CA.DBName)
|
||||
cmd.FailOnError(err, "Failed to create CA database")
|
||||
|
|
67
cmd/shell.go
67
cmd/shell.go
|
@ -58,7 +58,7 @@ type Config struct {
|
|||
SA Queue
|
||||
CA Queue
|
||||
OCSP Queue
|
||||
SSL *SSLConfig
|
||||
TLS *TLSConfig
|
||||
}
|
||||
|
||||
WFE struct {
|
||||
|
@ -76,6 +76,7 @@ type Config struct {
|
|||
VA struct {
|
||||
DNSResolver string
|
||||
DNSTimeout string
|
||||
UserAgent string
|
||||
}
|
||||
|
||||
SQL struct {
|
||||
|
@ -130,11 +131,11 @@ type Config struct {
|
|||
SubscriberAgreementURL string
|
||||
}
|
||||
|
||||
// SSLConfig reprents certificates and a key for authenticated TLS.
|
||||
type SSLConfig struct {
|
||||
CertFile string
|
||||
KeyFile string
|
||||
CACertFile *string // Optional
|
||||
// TLSConfig reprents certificates and a key for authenticated TLS.
|
||||
type TLSConfig struct {
|
||||
CertFile *string
|
||||
KeyFile *string
|
||||
CACertFile *string
|
||||
}
|
||||
|
||||
// Queue describes a queue name
|
||||
|
@ -209,43 +210,61 @@ func FailOnError(err error, msg string) {
|
|||
// more aggressive error dropping
|
||||
func AmqpChannel(conf Config) (*amqp.Channel, error) {
|
||||
var conn *amqp.Connection
|
||||
var err error
|
||||
|
||||
log := blog.GetAuditLogger()
|
||||
|
||||
if conf.AMQP.TLS == nil {
|
||||
// Configuration did not specify TLS options, but Dial will
|
||||
// use TLS anyway if the URL scheme is "amqps"
|
||||
conn, err = amqp.Dial(conf.AMQP.Server)
|
||||
|
||||
} else {
|
||||
// They provided TLS options, so let's load them.
|
||||
log.Info("AMQPS: Loading TLS Options.")
|
||||
|
||||
if conf.AMQP.SSL != nil {
|
||||
if strings.HasPrefix(conf.AMQP.Server, "amqps") == false {
|
||||
err := fmt.Errorf("SSL configuration provided, but not using an AMQPS URL")
|
||||
err = fmt.Errorf("AMQPS: TLS configuration provided, but not using an AMQPS URL")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := new(tls.Config)
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(conf.AMQP.SSL.CertFile, conf.AMQP.SSL.KeyFile)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not load Client Certificates: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
cfg.Certificates = append(cfg.Certificates, cert)
|
||||
// If the configuration specified a certificate (or key), load them
|
||||
if conf.AMQP.TLS.CertFile != nil || conf.AMQP.TLS.KeyFile != nil {
|
||||
// But they have to give both.
|
||||
if conf.AMQP.TLS.CertFile == nil || conf.AMQP.TLS.KeyFile == nil {
|
||||
err = fmt.Errorf("AMQPS: You must set both of the configuration values AMQP.TLS.KeyFile and AMQP.TLS.CertFile.")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if conf.AMQP.SSL.CACertFile != nil {
|
||||
cert, err := tls.LoadX509KeyPair(*conf.AMQP.TLS.CertFile, *conf.AMQP.TLS.KeyFile)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("AMQPS: Could not load Client Certificate or Key: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("AMQPS: Configured client certificate for AMQPS.")
|
||||
cfg.Certificates = append(cfg.Certificates, cert)
|
||||
}
|
||||
|
||||
// If the configuration specified a CA certificate, make it the only
|
||||
// available root.
|
||||
if conf.AMQP.TLS.CACertFile != nil {
|
||||
cfg.RootCAs = x509.NewCertPool()
|
||||
|
||||
ca, err := ioutil.ReadFile(*conf.AMQP.SSL.CACertFile)
|
||||
ca, err := ioutil.ReadFile(*conf.AMQP.TLS.CACertFile)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not load CA Certificate: %s", err)
|
||||
err = fmt.Errorf("AMQPS: Could not load CA Certificate: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
cfg.RootCAs.AppendCertsFromPEM(ca)
|
||||
log.Info("AMQPS: Configured CA certificate for AMQPS.")
|
||||
}
|
||||
|
||||
conn, err = amqp.DialTLS(conf.AMQP.Server, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn.Channel()
|
||||
}
|
||||
|
||||
// Configuration did not specify SSL options
|
||||
conn, err := amqp.Dial(conf.AMQP.Server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -26,10 +26,11 @@ type NonceService struct {
|
|||
}
|
||||
|
||||
// NewNonceService constructs a NonceService with defaults
|
||||
func NewNonceService() NonceService {
|
||||
// XXX ignoring possible error due to entropy starvation
|
||||
func NewNonceService() (NonceService, error) {
|
||||
key := make([]byte, 16)
|
||||
rand.Read(key)
|
||||
if _, err := rand.Read(key); err != nil {
|
||||
return NonceService{}, err
|
||||
}
|
||||
|
||||
// It is safe to ignore these errors because they only happen
|
||||
// on key size and block size mismatches.
|
||||
|
@ -42,17 +43,18 @@ func NewNonceService() NonceService {
|
|||
used: make(map[int64]bool, MaxUsed),
|
||||
gcm: gcm,
|
||||
maxUsed: MaxUsed,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ns NonceService) encrypt(counter int64) string {
|
||||
func (ns NonceService) encrypt(counter int64) (string, error) {
|
||||
// Generate a nonce with upper 4 bytes zero
|
||||
// XXX ignoring possible error due to entropy starvation
|
||||
nonce := make([]byte, 12)
|
||||
for i := 0; i < 4; i++ {
|
||||
nonce[i] = 0
|
||||
}
|
||||
rand.Read(nonce[4:])
|
||||
if _, err := rand.Read(nonce[4:]); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Encode counter to plaintext
|
||||
pt := make([]byte, 8)
|
||||
|
@ -65,7 +67,7 @@ func (ns NonceService) encrypt(counter int64) string {
|
|||
ct := ns.gcm.Seal(nil, nonce, pt, nil)
|
||||
copy(ret, nonce[4:])
|
||||
copy(ret[8:], ct)
|
||||
return B64enc(ret)
|
||||
return B64enc(ret), nil
|
||||
}
|
||||
|
||||
func (ns NonceService) decrypt(nonce string) (int64, error) {
|
||||
|
@ -91,7 +93,7 @@ func (ns NonceService) decrypt(nonce string) (int64, error) {
|
|||
}
|
||||
|
||||
// Nonce provides a new Nonce.
|
||||
func (ns *NonceService) Nonce() string {
|
||||
func (ns *NonceService) Nonce() (string, error) {
|
||||
ns.latest++
|
||||
return ns.encrypt(ns.latest)
|
||||
}
|
||||
|
|
|
@ -11,48 +11,65 @@ import (
|
|||
)
|
||||
|
||||
func TestValidNonce(t *testing.T) {
|
||||
ns := NewNonceService()
|
||||
n := ns.Nonce()
|
||||
ns, err := NewNonceService()
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
n, err := ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
test.Assert(t, ns.Valid(n), "Did not recognize fresh nonce")
|
||||
}
|
||||
|
||||
func TestAlreadyUsed(t *testing.T) {
|
||||
ns := NewNonceService()
|
||||
n := ns.Nonce()
|
||||
ns, err := NewNonceService()
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
n, err := ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
test.Assert(t, ns.Valid(n), "Did not recognize fresh nonce")
|
||||
test.Assert(t, !ns.Valid(n), "Recognized the same nonce twice")
|
||||
}
|
||||
|
||||
func TestRejectMalformed(t *testing.T) {
|
||||
ns := NewNonceService()
|
||||
n := ns.Nonce()
|
||||
ns, err := NewNonceService()
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
n, err := ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
test.Assert(t, !ns.Valid("asdf"+n), "Accepted an invalid nonce")
|
||||
}
|
||||
|
||||
func TestRejectUnknown(t *testing.T) {
|
||||
ns1 := NewNonceService()
|
||||
ns2 := NewNonceService()
|
||||
n := ns1.Nonce()
|
||||
ns1, err := NewNonceService()
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
ns2, err := NewNonceService()
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
|
||||
n, err := ns1.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
test.Assert(t, !ns2.Valid(n), "Accepted a foreign nonce")
|
||||
}
|
||||
|
||||
func TestRejectTooLate(t *testing.T) {
|
||||
ns := NewNonceService()
|
||||
ns, err := NewNonceService()
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
|
||||
ns.latest = 2
|
||||
n := ns.Nonce()
|
||||
n, err := ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
ns.latest = 1
|
||||
test.Assert(t, !ns.Valid(n), "Accepted a nonce with a too-high counter")
|
||||
}
|
||||
|
||||
func TestRejectTooEarly(t *testing.T) {
|
||||
ns := NewNonceService()
|
||||
ns, err := NewNonceService()
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
ns.maxUsed = 2
|
||||
|
||||
n0 := ns.Nonce()
|
||||
n1 := ns.Nonce()
|
||||
n2 := ns.Nonce()
|
||||
n3 := ns.Nonce()
|
||||
n0, err := ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
n1, err := ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
n2, err := ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
n3, err := ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
|
||||
test.Assert(t, ns.Valid(n3), "Rejected a valid nonce")
|
||||
test.Assert(t, ns.Valid(n2), "Rejected a valid nonce")
|
||||
|
|
|
@ -231,17 +231,36 @@ func (log *AuditLogger) Audit(msg string) (err error) {
|
|||
return log.auditAtLevel("Logging.Notice", msg)
|
||||
}
|
||||
|
||||
func (log *AuditLogger) formatObjectMessage(msg string, obj interface{}) (string, error) {
|
||||
jsonObj, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
|
||||
log.auditAtLevel("Logging.Err", fmt.Sprintf("Object could not be serialized to JSON. Raw: %+v", obj))
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s JSON=%s", msg, jsonObj), nil
|
||||
}
|
||||
|
||||
// AuditObject sends a NOTICE-severity JSON-serialized object message that is prefixed
|
||||
// with the audit tag, for special handling at the upstream system logger.
|
||||
func (log *AuditLogger) AuditObject(msg string, obj interface{}) (err error) {
|
||||
jsonLogEvent, logErr := json.Marshal(obj)
|
||||
formattedEvent, logErr := log.formatObjectMessage(msg, obj)
|
||||
if logErr != nil {
|
||||
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
|
||||
log.auditAtLevel("Logging.Err", fmt.Sprintf("%s - logEvent could not be serialized. Raw: %+v", msg, obj))
|
||||
return logErr
|
||||
}
|
||||
|
||||
return log.auditAtLevel("Logging.Notice", fmt.Sprintf("%s - %s", msg, jsonLogEvent))
|
||||
return log.auditAtLevel("Logging.Notice", formattedEvent)
|
||||
}
|
||||
|
||||
// InfoObject sends a INFO-severity JSON-serialized object message.
|
||||
func (log *AuditLogger) InfoObject(msg string, obj interface{}) (err error) {
|
||||
formattedEvent, logErr := log.formatObjectMessage(msg, obj)
|
||||
if logErr != nil {
|
||||
return logErr
|
||||
}
|
||||
|
||||
return log.logAtLevel("Logging.Info", formattedEvent)
|
||||
}
|
||||
|
||||
// AuditErr can format an error for auditing; it does so at ERR level.
|
||||
|
|
|
@ -596,37 +596,6 @@ var CAcertPEM = "-----BEGIN CERTIFICATE-----\n" +
|
|||
// * Random public key
|
||||
// * CN = not-example.com
|
||||
// * DNSNames = not-example.com, www.not-example.com
|
||||
/*
|
||||
var CSRhex = "3082028c30820174020100301a311830160603550403130f" +
|
||||
"6e6f742d6578616d706c652e636f6d30820122300d06092a" +
|
||||
"864886f70d01010105000382010f003082010a0282010100" +
|
||||
"aac67dd1e11fae980048b0ac91be005f21d9df8bb38461cc" +
|
||||
"a7dfad601a00e91ae488c240a03ec53a5752a33d837d2d9c" +
|
||||
"357c6a99ea7e55fe75482524480bb367aa85f75541bd0284" +
|
||||
"ede1ab9b54925a5c9f88d08f9dc857ee707a59d3503b31ea" +
|
||||
"64e42099acd70d2204c872ef49983e44cc2bc24389159fc5" +
|
||||
"f6ca41b80540fb7a2fbf8aa43af7f539782f20f185d416cc" +
|
||||
"66a88e5f8913a292b4a217e5b12e8244a9686af3b49ac88b" +
|
||||
"215c6eb097c4befa3e66257a1358791e2bc471c18ba2ca6e" +
|
||||
"161d2dcb53ebcb06e6b4b2e6cd42ff970581bc4971009cbd" +
|
||||
"7ccc3f89648db720e2908a1be613a9c3afb46b477261c1bc" +
|
||||
"c057bc749a102e6bd9dc45d87b2d97c50203010001a02d30" +
|
||||
"2b06092a864886f70d01090e311e301c301a0603551d1104" +
|
||||
"133011820f6e6f742d6578616d706c652e636f6d300d0609" +
|
||||
"2a864886f70d01010b05000382010100a37025c2cb88b4fa" +
|
||||
"f3c8417820a78a069b92cef45a3d2f3fbd18f41a07128258" +
|
||||
"38bc55e6c1c416e5f34c365924391294741c23657ffa77e4" +
|
||||
"aa3d589b560cbb9156aae175637cbb5061d69eefa7f432ca" +
|
||||
"c9e4d03feaa367954cf6986a72ca76307c01852db72b43ae" +
|
||||
"5ab7f673b81b58e06d8af1f681cd7c88d2fbc8c80d592a3f" +
|
||||
"7c3ea20035b73c8e6afc8daea4d168fe469f7da9e4bac660" +
|
||||
"fc207d1d93dce118dc7381e69fa4af37bb4d4d6d5342fa55" +
|
||||
"7798a363aa04cf350ad1748e96eabee04fa379dd98524ea1" +
|
||||
"53f07e1654e6077f4aaaf5c5b27edaf0385b48e0fc281424" +
|
||||
"6363a01370c89e666169276eb133731cff2379d46d2fff9d" +
|
||||
"f277b6d23fa24f9b"
|
||||
*/
|
||||
|
||||
var CSRhex = "308202ae308201960201003027310b300906035504061302" +
|
||||
"5553311830160603550403130f6e6f742d6578616d706c65" +
|
||||
"2e636f6d30820122300d06092a864886f70d010101050003" +
|
||||
|
|
|
@ -8,6 +8,7 @@ package sa
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
// Load both drivers to allow configuring either
|
||||
_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/go-sql-driver/mysql"
|
||||
|
@ -33,6 +34,11 @@ var dialectMap = map[string]interface{}{
|
|||
func NewDbMap(driver string, name string) (*gorp.DbMap, error) {
|
||||
logger := blog.GetAuditLogger()
|
||||
|
||||
// We require this parameter for MySQL, so fail now if it is not present
|
||||
if driver == "mysql" && !strings.Contains(name, "parseTime=true") {
|
||||
return nil, fmt.Errorf("Database name must have parseTime=true")
|
||||
}
|
||||
|
||||
db, err := sql.Open(driver, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -30,3 +30,8 @@ func TestForgottenDialect(t *testing.T) {
|
|||
_, err := NewDbMap("sqlite3", ":memory:")
|
||||
test.AssertError(t, err, "Shouldn't have found the dialect")
|
||||
}
|
||||
|
||||
func TestParseTimeRequired(t *testing.T) {
|
||||
_, err := NewDbMap("mysql", "invalid")
|
||||
test.AssertError(t, err, "DB name must have parseTime=true")
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
"amqp": {
|
||||
"server": "amqp://guest:guest@localhost:5672",
|
||||
"-uncomment_for_AMQPS-ssl": {
|
||||
"-uncomment_for_AMQPS-tls": {
|
||||
"cacertfile": "/etc/boulder/rabbitmq-cacert.pem",
|
||||
"certfile": "/etc/boulder/rabbitmq-cert.pem",
|
||||
"keyfile": "/etc/boulder/rabbitmq-key.pem"
|
||||
|
@ -105,7 +105,8 @@
|
|||
|
||||
"va": {
|
||||
"dnsResolver": "8.8.8.8:53",
|
||||
"dnsTimeout": "10s"
|
||||
"dnsTimeout": "10s",
|
||||
"userAgent": "boulder"
|
||||
},
|
||||
|
||||
"sql": {
|
||||
|
|
|
@ -100,7 +100,8 @@
|
|||
|
||||
"va": {
|
||||
"dnsResolver": "8.8.8.8:53",
|
||||
"dnsTimeout": "10s"
|
||||
"dnsTimeout": "10s",
|
||||
"userAgent": "boulder"
|
||||
},
|
||||
|
||||
"sql": {
|
||||
|
|
|
@ -244,7 +244,7 @@ module.exports = {
|
|||
extensions: [{name: 'subjectAltName', altNames: sans}]
|
||||
}]);
|
||||
|
||||
csr.sign(privateKey);
|
||||
csr.sign(privateKey, forge.md.sha256.create());
|
||||
|
||||
// Convert CSR -> DER -> Base64
|
||||
var der = forge.asn1.toDer(forge.pki.certificationRequestToAsn1(csr));
|
||||
|
|
|
@ -33,6 +33,7 @@ type ValidationAuthorityImpl struct {
|
|||
Stats statsd.Statter
|
||||
IssuerDomain string
|
||||
TestMode bool
|
||||
UserAgent string
|
||||
}
|
||||
|
||||
// NewValidationAuthorityImpl constructs a new VA, and may place it
|
||||
|
@ -64,7 +65,7 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
|
|||
Type: core.MalformedProblem,
|
||||
Detail: "No path provided for SimpleHTTP challenge.",
|
||||
}
|
||||
va.log.Debug(fmt.Sprintf("SimpleHTTP [%s] path empty: %s", identifier, challenge))
|
||||
va.log.Debug(fmt.Sprintf("SimpleHTTP [%s] path empty: %v", identifier, challenge))
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
|
||||
|
@ -80,26 +81,6 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
|
|||
}
|
||||
hostName := identifier.Value
|
||||
|
||||
// Check for DNSSEC failures for A/AAAA records
|
||||
_, rtt, err := va.DNSResolver.LookupHost(hostName)
|
||||
va.Stats.TimingDuration("DnsRtt.LookupHost", rtt, 1.0)
|
||||
if err != nil {
|
||||
if dnssecErr, ok := err.(core.DNSSECError); ok {
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.DNSSECProblem,
|
||||
Detail: dnssecErr.Error(),
|
||||
}
|
||||
} else {
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.ServerInternalProblem,
|
||||
Detail: "Unable to communicate with DNS server",
|
||||
}
|
||||
}
|
||||
challenge.Status = core.StatusInvalid
|
||||
va.log.Debug(fmt.Sprintf("SimpleHTTP [%s] DNS failure: %s", identifier, err))
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
|
||||
var scheme string
|
||||
if input.TLS == nil || (input.TLS != nil && *input.TLS) {
|
||||
scheme = "https"
|
||||
|
@ -126,6 +107,10 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
|
|||
return challenge, err
|
||||
}
|
||||
|
||||
if va.UserAgent != "" {
|
||||
httpRequest.Header["User-Agent"] = []string{va.UserAgent}
|
||||
}
|
||||
|
||||
httpRequest.Host = hostName
|
||||
tr := &http.Transport{
|
||||
// We are talking to a client that does not yet have a certificate,
|
||||
|
@ -225,26 +210,6 @@ func (va ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier,
|
|||
z := sha256.Sum256(RS)
|
||||
zName := fmt.Sprintf("%064x.acme.invalid", z)
|
||||
|
||||
// Check for DNSSEC failures for A/AAAA records
|
||||
_, rtt, err := va.DNSResolver.LookupHost(identifier.Value)
|
||||
va.Stats.TimingDuration("DnsRtt.LookupHost", rtt, 1.0)
|
||||
if err != nil {
|
||||
if dnssecErr, ok := err.(core.DNSSECError); ok {
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.DNSSECProblem,
|
||||
Detail: dnssecErr.Error(),
|
||||
}
|
||||
} else {
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.ServerInternalProblem,
|
||||
Detail: "Unable to communicate with DNS server",
|
||||
}
|
||||
}
|
||||
challenge.Status = core.StatusInvalid
|
||||
va.log.Debug(fmt.Sprintf("DVSNI [%s] DNS failure: %s", identifier, err))
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
|
||||
// Make a connection with SNI = nonceName
|
||||
hostPort := identifier.Value + ":443"
|
||||
if va.TestMode {
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
||||
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/square/go-jose"
|
||||
|
@ -89,15 +90,35 @@ func statusCodeFromError(err interface{}) int {
|
|||
}
|
||||
}
|
||||
|
||||
type requestEvent struct {
|
||||
ID string `json:",omitempty"`
|
||||
RealIP string `json:",omitempty"`
|
||||
ForwardedFor string `json:",omitempty"`
|
||||
Endpoint string `json:",omitempty"`
|
||||
Method string `json:",omitempty"`
|
||||
RequestTime time.Time `json:",omitempty"`
|
||||
ResponseTime time.Time `json:",omitempty"`
|
||||
Error string `json:",omitempty"`
|
||||
Requester int64 `json:",omitempty"`
|
||||
Contacts []core.AcmeURL `json:",omitempty"`
|
||||
|
||||
Extra map[string]interface{} `json:",omitempty"`
|
||||
}
|
||||
|
||||
// NewWebFrontEndImpl constructs a web service for Boulder
|
||||
func NewWebFrontEndImpl() WebFrontEndImpl {
|
||||
func NewWebFrontEndImpl() (WebFrontEndImpl, error) {
|
||||
logger := blog.GetAuditLogger()
|
||||
logger.Notice("Web Front End Starting")
|
||||
|
||||
nonceService, err := core.NewNonceService()
|
||||
if err != nil {
|
||||
return WebFrontEndImpl{}, err
|
||||
}
|
||||
|
||||
return WebFrontEndImpl{
|
||||
log: logger,
|
||||
nonceService: core.NewNonceService(),
|
||||
}
|
||||
nonceService: nonceService,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HandlePaths configures the HTTP engine to use various functions
|
||||
|
@ -127,19 +148,24 @@ func (wfe *WebFrontEndImpl) HandlePaths() {
|
|||
|
||||
// Index serves a simple identification page. It is not part of the ACME spec.
|
||||
func (wfe *WebFrontEndImpl) Index(response http.ResponseWriter, request *http.Request) {
|
||||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
wfe.sendStandardHeaders(response)
|
||||
|
||||
// http://golang.org/pkg/net/http/#example_ServeMux_Handle
|
||||
// The "/" pattern matches everything, so we need to check
|
||||
// that we're at the root here.
|
||||
if request.URL.Path != "/" {
|
||||
logEvent.Error = "Resource not found"
|
||||
http.NotFound(response, request)
|
||||
return
|
||||
}
|
||||
|
||||
if request.Method != "GET" {
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "GET")
|
||||
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
|
||||
wfe.sendError(response, logEvent.Error, request.Method, http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -166,7 +192,13 @@ func sendAllow(response http.ResponseWriter, methods ...string) {
|
|||
}
|
||||
|
||||
func (wfe *WebFrontEndImpl) sendStandardHeaders(response http.ResponseWriter) {
|
||||
response.Header().Set("Replay-Nonce", wfe.nonceService.Nonce())
|
||||
// We do not propagate errors here, because (1) they should be
|
||||
// transient, and (2) they fail closed.
|
||||
nonce, err := wfe.nonceService.Nonce()
|
||||
if err == nil {
|
||||
response.Header().Set("Replay-Nonce", nonce)
|
||||
}
|
||||
|
||||
response.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
}
|
||||
|
||||
|
@ -293,42 +325,53 @@ func link(url, relation string) string {
|
|||
|
||||
// NewRegistration is used by clients to submit a new registration/account
|
||||
func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, request *http.Request) {
|
||||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
wfe.sendStandardHeaders(response)
|
||||
|
||||
if request.Method != "POST" {
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "POST")
|
||||
wfe.sendError(response, "Method not allowed", "", http.StatusMethodNotAllowed)
|
||||
wfe.sendError(response, logEvent.Error, "", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
body, key, _, err := wfe.verifyPOST(request, false)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = wfe.SA.GetRegistrationByKey(*key); err == nil {
|
||||
wfe.sendError(response, "Registration key is already in use", nil, http.StatusConflict)
|
||||
logEvent.Error = "Registration key is already in use"
|
||||
wfe.sendError(response, logEvent.Error, nil, http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
var init core.Registration
|
||||
err = json.Unmarshal(body, &init)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Error unmarshaling JSON", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(init.Agreement) > 0 && init.Agreement != wfe.SubscriberAgreementURL {
|
||||
wfe.sendError(response, fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", init.Agreement, wfe.SubscriberAgreementURL), nil, http.StatusBadRequest)
|
||||
logEvent.Error = fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", init.Agreement, wfe.SubscriberAgreementURL)
|
||||
wfe.sendError(response, logEvent.Error, nil, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
init.Key = *key
|
||||
|
||||
reg, err := wfe.RA.NewRegistration(init)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Error creating new registration", err, statusCodeFromError(err))
|
||||
return
|
||||
}
|
||||
logEvent.Requester = reg.ID
|
||||
logEvent.Contacts = reg.Contact
|
||||
|
||||
// Use an explicitly typed variable. Otherwise `go vet' incorrectly complains
|
||||
// that reg.ID is a string being passed to %d.
|
||||
|
@ -336,6 +379,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 {
|
||||
logEvent.Error = err.Error()
|
||||
// StatusInternalServerError because we just created this registration, it should be OK.
|
||||
wfe.sendError(response, "Error marshaling registration", err, http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -354,16 +398,21 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
|
|||
|
||||
// NewAuthorization is used by clients to submit a new ID Authorization
|
||||
func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, request *http.Request) {
|
||||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
wfe.sendStandardHeaders(response)
|
||||
|
||||
if request.Method != "POST" {
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "POST")
|
||||
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
|
||||
wfe.sendError(response, logEvent.Error, request.Method, http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
body, _, currReg, err := wfe.verifyPOST(request, true)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
if err == sql.ErrNoRows {
|
||||
wfe.sendError(response, "No registration exists matching provided key", err, http.StatusForbidden)
|
||||
} else {
|
||||
|
@ -371,26 +420,33 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
|
|||
}
|
||||
return
|
||||
}
|
||||
logEvent.Requester = currReg.ID
|
||||
logEvent.Contacts = currReg.Contact
|
||||
// Any version of the agreement is acceptable here. Version match is enforced in
|
||||
// wfe.Registration when agreeing the first time. Agreement updates happen
|
||||
// by mailing subscribers and don't require a registration update.
|
||||
if currReg.Agreement == "" {
|
||||
wfe.sendError(response, "Must agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
|
||||
logEvent.Error = "Must agree to subscriber agreement before any further actions"
|
||||
wfe.sendError(response, logEvent.Error, nil, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
var init core.Authorization
|
||||
if err = json.Unmarshal(body, &init); err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Error unmarshaling JSON", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["Identifier"] = init.Identifier
|
||||
|
||||
// Create new authz and return
|
||||
authz, err := wfe.RA.NewAuthorization(init, currReg.ID)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Error creating new authz", err, statusCodeFromError(err))
|
||||
return
|
||||
}
|
||||
logEvent.Extra["AuthzID"] = authz.ID
|
||||
|
||||
// Make a URL for this authz, then blow away the ID and RegID before serializing
|
||||
authzURL := wfe.AuthzBase + string(authz.ID)
|
||||
|
@ -398,6 +454,7 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
|
|||
authz.RegistrationID = 0
|
||||
responseBody, err := json.Marshal(authz)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
// StatusInternalServerError because we generated the authz, it should be OK
|
||||
wfe.sendError(response, "Error marshaling authz", err, http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -408,17 +465,22 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
|
|||
response.Header().Set("Content-Type", "application/json")
|
||||
response.WriteHeader(http.StatusCreated)
|
||||
if _, err = response.Write(responseBody); err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
// RevokeCertificate is used by clients to request the revocation of a cert.
|
||||
func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, request *http.Request) {
|
||||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
wfe.sendStandardHeaders(response)
|
||||
|
||||
if request.Method != "POST" {
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "POST")
|
||||
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
|
||||
wfe.sendError(response, logEvent.Error, request.Method, http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -426,27 +488,33 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
|
|||
// because anyone with the right private key can revoke a certificate.
|
||||
body, requestKey, registration, err := wfe.verifyPOST(request, false)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
logEvent.Requester = registration.ID
|
||||
logEvent.Contacts = registration.Contact
|
||||
|
||||
type RevokeRequest struct {
|
||||
CertificateDER core.JSONBuffer `json:"certificate"`
|
||||
}
|
||||
var revokeRequest RevokeRequest
|
||||
if err = json.Unmarshal(body, &revokeRequest); err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.log.Debug(fmt.Sprintf("Couldn't unmarshal in revoke request %s", string(body)))
|
||||
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
providedCert, err := x509.ParseCertificate(revokeRequest.CertificateDER)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.log.Debug("Couldn't parse cert in revoke request.")
|
||||
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
serial := core.SerialToString(providedCert.SerialNumber)
|
||||
logEvent.Extra["ProvidedCertificateSerial"] = serial
|
||||
cert, err := wfe.SA.GetCertificate(serial)
|
||||
if err != nil || !bytes.Equal(cert.DER, revokeRequest.CertificateDER) {
|
||||
wfe.sendError(response, "No such certificate", err, http.StatusNotFound)
|
||||
|
@ -454,28 +522,37 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
|
|||
}
|
||||
parsedCertificate, err := x509.ParseCertificate(cert.DER)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
// InternalServerError because this is a failure to decode from our DB.
|
||||
wfe.sendError(response, "Invalid certificate", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["RetrievedCertificateSerial"] = core.SerialToString(parsedCertificate.SerialNumber)
|
||||
logEvent.Extra["RetrievedCertificateDNSNames"] = parsedCertificate.DNSNames
|
||||
logEvent.Extra["RetrievedCertificateEmailAddresses"] = parsedCertificate.EmailAddresses
|
||||
logEvent.Extra["RetrievedCertificateIPAddresses"] = parsedCertificate.IPAddresses
|
||||
|
||||
certStatus, err := wfe.SA.GetCertificateStatus(serial)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Certificate status not yet available", err, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["CertificateStatus"] = certStatus.Status
|
||||
|
||||
if certStatus.Status == core.OCSPStatusRevoked {
|
||||
wfe.sendError(response, "Certificate already revoked", "", http.StatusConflict)
|
||||
logEvent.Error = "Certificate already revoked"
|
||||
wfe.sendError(response, logEvent.Error, "", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Implement method of revocation by authorizations on account.
|
||||
if !(core.KeyDigestEquals(requestKey, parsedCertificate.PublicKey) ||
|
||||
registration.ID == cert.RegistrationID) {
|
||||
logEvent.Error = "Revocation request must be signed by private key of cert to be revoked"
|
||||
wfe.log.Debug("Key mismatch for revoke")
|
||||
wfe.sendError(response,
|
||||
"Revocation request must be signed by private key of cert to be revoked",
|
||||
logEvent.Error,
|
||||
requestKey,
|
||||
http.StatusForbidden)
|
||||
return
|
||||
|
@ -483,6 +560,7 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
|
|||
|
||||
err = wfe.RA.RevokeCertificate(*parsedCertificate)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Failed to revoke certificate", err, statusCodeFromError(err))
|
||||
} else {
|
||||
wfe.log.Debug(fmt.Sprintf("Revoked %v", serial))
|
||||
|
@ -493,16 +571,21 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
|
|||
// NewCertificate is used by clients to request the issuance of a cert for an
|
||||
// authorized identifier.
|
||||
func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request *http.Request) {
|
||||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
wfe.sendStandardHeaders(response)
|
||||
|
||||
if request.Method != "POST" {
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "POST")
|
||||
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
|
||||
wfe.sendError(response, logEvent.Error, request.Method, http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
body, key, reg, err := wfe.verifyPOST(request, true)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
if err == sql.ErrNoRows {
|
||||
wfe.sendError(response, "No registration exists matching provided key", err, http.StatusForbidden)
|
||||
} else {
|
||||
|
@ -510,20 +593,27 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
|
|||
}
|
||||
return
|
||||
}
|
||||
logEvent.Requester = reg.ID
|
||||
logEvent.Contacts = reg.Contact
|
||||
// Any version of the agreement is acceptable here. Version match is enforced in
|
||||
// wfe.Registration when agreeing the first time. Agreement updates happen
|
||||
// by mailing subscribers and don't require a registration update.
|
||||
if reg.Agreement == "" {
|
||||
wfe.sendError(response, "Must agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
|
||||
logEvent.Error = "Must agree to subscriber agreement before any further actions"
|
||||
wfe.sendError(response, logEvent.Error, nil, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
var init core.CertificateRequest
|
||||
if err = json.Unmarshal(body, &init); err != nil {
|
||||
fmt.Println(err)
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Error unmarshaling certificate request", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["Authorizations"] = init.Authorizations
|
||||
logEvent.Extra["CSRDNSNames"] = init.CSR.DNSNames
|
||||
logEvent.Extra["CSREmailAddresses"] = init.CSR.EmailAddresses
|
||||
logEvent.Extra["CSRIPAddresses"] = init.CSR.IPAddresses
|
||||
|
||||
wfe.log.Notice(fmt.Sprintf("Client requested new certificate: %v %v %v",
|
||||
request.RemoteAddr, init, key))
|
||||
|
@ -536,6 +626,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
|
|||
// RA for secondary validation.
|
||||
cert, err := wfe.RA.NewCertificate(init, reg.ID)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Error creating new cert", err, statusCodeFromError(err))
|
||||
return
|
||||
}
|
||||
|
@ -546,6 +637,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
|
|||
// enumerate and mirror our certificates.
|
||||
parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response,
|
||||
"Error creating new cert", err,
|
||||
http.StatusBadRequest)
|
||||
|
@ -560,17 +652,19 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
|
|||
response.Header().Set("Content-Type", "application/pkix-cert")
|
||||
response.WriteHeader(http.StatusCreated)
|
||||
if _, err = response.Write(cert.DER); err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (wfe *WebFrontEndImpl) challenge(authz core.Authorization, response http.ResponseWriter, request *http.Request) {
|
||||
func (wfe *WebFrontEndImpl) challenge(authz core.Authorization, response http.ResponseWriter, request *http.Request, logEvent requestEvent) requestEvent {
|
||||
wfe.sendStandardHeaders(response)
|
||||
|
||||
if request.Method != "GET" && request.Method != "POST" {
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "GET", "POST")
|
||||
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
|
||||
return
|
||||
return logEvent
|
||||
}
|
||||
|
||||
// Check that the requested challenge exists within the authorization
|
||||
|
@ -586,24 +680,27 @@ func (wfe *WebFrontEndImpl) challenge(authz core.Authorization, response http.Re
|
|||
}
|
||||
|
||||
if !found {
|
||||
wfe.sendError(response, "Unable to find challenge", request.URL.RawQuery, http.StatusNotFound)
|
||||
return
|
||||
logEvent.Error = "Unable to find challenge"
|
||||
wfe.sendError(response, logEvent.Error, request.URL.RawQuery, http.StatusNotFound)
|
||||
return logEvent
|
||||
}
|
||||
|
||||
switch request.Method {
|
||||
default:
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "GET", "POST")
|
||||
wfe.sendError(response, "Method not allowed", "", http.StatusMethodNotAllowed)
|
||||
return
|
||||
wfe.sendError(response, logEvent.Error, "", http.StatusMethodNotAllowed)
|
||||
return logEvent
|
||||
|
||||
case "GET":
|
||||
challenge := authz.Challenges[challengeIndex]
|
||||
jsonReply, err := json.Marshal(challenge)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
// InternalServerError because this is a failure to decode data passed in
|
||||
// by the caller, which got it from the DB.
|
||||
wfe.sendError(response, "Failed to marshal challenge", err, http.StatusInternalServerError)
|
||||
return
|
||||
return logEvent
|
||||
}
|
||||
|
||||
authzURL := wfe.AuthzBase + string(authz.ID)
|
||||
|
@ -614,55 +711,65 @@ func (wfe *WebFrontEndImpl) challenge(authz core.Authorization, response http.Re
|
|||
response.WriteHeader(http.StatusAccepted)
|
||||
if _, err := response.Write(jsonReply); err != nil {
|
||||
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
|
||||
logEvent.Error = err.Error()
|
||||
return logEvent
|
||||
}
|
||||
|
||||
case "POST":
|
||||
body, _, currReg, err := wfe.verifyPOST(request, true)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
if err == sql.ErrNoRows {
|
||||
wfe.sendError(response, "No registration exists matching provided key", err, http.StatusForbidden)
|
||||
} else {
|
||||
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
|
||||
}
|
||||
return
|
||||
return logEvent
|
||||
}
|
||||
logEvent.Requester = currReg.ID
|
||||
logEvent.Contacts = currReg.Contact
|
||||
// Any version of the agreement is acceptable here. Version match is enforced in
|
||||
// wfe.Registration when agreeing the first time. Agreement updates happen
|
||||
// by mailing subscribers and don't require a registration update.
|
||||
if currReg.Agreement == "" {
|
||||
wfe.sendError(response, "Must agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
|
||||
return
|
||||
logEvent.Error = "Must agree to subscriber agreement before any further actions"
|
||||
wfe.sendError(response, logEvent.Error, nil, http.StatusForbidden)
|
||||
return logEvent
|
||||
}
|
||||
|
||||
// Check that the registration ID matching the key used matches
|
||||
// the registration ID on the authz object
|
||||
if currReg.ID != authz.RegistrationID {
|
||||
logEvent.Error = fmt.Sprintf("User: %v != Authorization: %v", currReg.ID, authz.RegistrationID)
|
||||
wfe.sendError(response, "User registration ID doesn't match registration ID in authorization",
|
||||
fmt.Sprintf("User: %v != Authorization: %v", currReg.ID, authz.RegistrationID),
|
||||
logEvent.Error,
|
||||
http.StatusForbidden)
|
||||
return
|
||||
return logEvent
|
||||
}
|
||||
|
||||
var challengeResponse core.Challenge
|
||||
if err = json.Unmarshal(body, &challengeResponse); err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Error unmarshaling challenge response", err, http.StatusBadRequest)
|
||||
return
|
||||
return logEvent
|
||||
}
|
||||
|
||||
// Ask the RA to update this authorization
|
||||
updatedAuthz, err := wfe.RA.UpdateAuthorization(authz, challengeIndex, challengeResponse)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Unable to update authorization", err, statusCodeFromError(err))
|
||||
return
|
||||
return logEvent
|
||||
}
|
||||
|
||||
challenge := updatedAuthz.Challenges[challengeIndex]
|
||||
// assumption: UpdateAuthorization does not modify order of challenges
|
||||
jsonReply, err := json.Marshal(challenge)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
// StatusInternalServerError because we made the challenges, they should be OK
|
||||
wfe.sendError(response, "Failed to marshal challenge", err, http.StatusInternalServerError)
|
||||
return
|
||||
return logEvent
|
||||
}
|
||||
|
||||
authzURL := wfe.AuthzBase + string(authz.ID)
|
||||
|
@ -672,24 +779,32 @@ func (wfe *WebFrontEndImpl) challenge(authz core.Authorization, response http.Re
|
|||
response.Header().Add("Link", link(authzURL, "up"))
|
||||
response.WriteHeader(http.StatusAccepted)
|
||||
if _, err = response.Write(jsonReply); err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
|
||||
return logEvent
|
||||
}
|
||||
|
||||
}
|
||||
return logEvent
|
||||
}
|
||||
|
||||
// Registration is used by a client to submit an update to their registration.
|
||||
func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *http.Request) {
|
||||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
wfe.sendStandardHeaders(response)
|
||||
|
||||
if request.Method != "POST" {
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "POST")
|
||||
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
|
||||
wfe.sendError(response, logEvent.Error, request.Method, http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
body, _, currReg, err := wfe.verifyPOST(request, true)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
if err == sql.ErrNoRows {
|
||||
wfe.sendError(response,
|
||||
"No registration exists matching provided key",
|
||||
|
@ -700,33 +815,38 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
|
|||
}
|
||||
return
|
||||
}
|
||||
logEvent.Requester = currReg.ID
|
||||
logEvent.Contacts = currReg.Contact
|
||||
|
||||
// Requests to this handler should have a path that leads to a known
|
||||
// registration
|
||||
idStr := parseIDFromPath(request.URL.Path)
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
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", id, http.StatusBadRequest)
|
||||
logEvent.Error = "Registration ID must be a positive non-zero integer"
|
||||
wfe.sendError(response, logEvent.Error, id, http.StatusBadRequest)
|
||||
return
|
||||
} else if id != currReg.ID {
|
||||
wfe.sendError(response, "Request signing key did not match registration key", "", http.StatusForbidden)
|
||||
logEvent.Error = "Request signing key did not match registration key"
|
||||
wfe.sendError(response, logEvent.Error, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
var update core.Registration
|
||||
err = json.Unmarshal(body, &update)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Error unmarshaling registration", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if len(update.Agreement) > 0 && update.Agreement != wfe.SubscriberAgreementURL {
|
||||
wfe.sendError(response,
|
||||
fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]",
|
||||
update.Agreement, wfe.SubscriberAgreementURL), nil, http.StatusBadRequest)
|
||||
logEvent.Error = fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", update.Agreement, wfe.SubscriberAgreementURL)
|
||||
wfe.sendError(response, logEvent.Error, nil, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -739,12 +859,14 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
|
|||
// Ask the RA to update this authorization.
|
||||
updatedReg, err := wfe.RA.UpdateRegistration(currReg, update)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.sendError(response, "Unable to update registration", err, statusCodeFromError(err))
|
||||
return
|
||||
}
|
||||
|
||||
jsonReply, err := json.Marshal(updatedReg)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
// StatusInternalServerError because we just generated the reg, it should be OK
|
||||
wfe.sendError(response, "Failed to marshal registration", err, http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -757,11 +879,15 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
|
|||
// Authorization is used by clients to submit an update to one of their
|
||||
// authorizations.
|
||||
func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request *http.Request) {
|
||||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
wfe.sendStandardHeaders(response)
|
||||
|
||||
if request.Method != "GET" && request.Method != "POST" {
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "GET", "POST")
|
||||
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
|
||||
wfe.sendError(response, logEvent.Error, request.Method, http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -774,17 +900,23 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
|
|||
http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["AuthorizationID"] = authz.ID
|
||||
logEvent.Extra["AuthorizationRegistrationID"] = authz.RegistrationID
|
||||
logEvent.Extra["AuthorizationIdentifier"] = authz.Identifier
|
||||
logEvent.Extra["AuthorizationStatus"] = authz.Status
|
||||
logEvent.Extra["AuthorizationExpires"] = authz.Expires
|
||||
|
||||
// If there is a fragment, then this is actually a request to a challenge URI
|
||||
if len(request.URL.RawQuery) != 0 {
|
||||
wfe.challenge(authz, response, request)
|
||||
logEvent = wfe.challenge(authz, response, request, logEvent)
|
||||
return
|
||||
}
|
||||
|
||||
switch request.Method {
|
||||
default:
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "GET", "POST")
|
||||
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
|
||||
wfe.sendError(response, logEvent.Error, request.Method, http.StatusMethodNotAllowed)
|
||||
return
|
||||
|
||||
case "GET":
|
||||
|
@ -794,6 +926,7 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
|
|||
|
||||
jsonReply, err := json.Marshal(authz)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
// InternalServerError because this is a failure to decode from our DB.
|
||||
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -802,6 +935,7 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
|
|||
response.Header().Set("Content-Type", "application/json")
|
||||
response.WriteHeader(http.StatusOK)
|
||||
if _, err = response.Write(jsonReply); err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
|
||||
}
|
||||
}
|
||||
|
@ -812,37 +946,39 @@ var allHex = regexp.MustCompile("^[0-9a-f]+$")
|
|||
// Certificate is used by clients to request a copy of their current certificate, or to
|
||||
// request a reissuance of the certificate.
|
||||
func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *http.Request) {
|
||||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
wfe.sendStandardHeaders(response)
|
||||
|
||||
if request.Method != "GET" && request.Method != "POST" {
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "GET", "POST")
|
||||
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
|
||||
return
|
||||
wfe.sendError(response, logEvent.Error, request.Method, http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
path := request.URL.Path
|
||||
switch request.Method {
|
||||
default:
|
||||
sendAllow(response, "GET", "POST")
|
||||
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, CertPath) {
|
||||
wfe.sendError(response, "Not found", path, http.StatusNotFound)
|
||||
logEvent.Error = "Certificate not found"
|
||||
wfe.sendError(response, logEvent.Error, path, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
serial := path[len(CertPath):]
|
||||
if len(serial) != 16 || !allHex.Match([]byte(serial)) {
|
||||
wfe.sendError(response, "Not found", serial, http.StatusNotFound)
|
||||
logEvent.Error = "Certificate not found"
|
||||
wfe.sendError(response, logEvent.Error, serial, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
wfe.log.Debug(fmt.Sprintf("Requested certificate ID %s", serial))
|
||||
logEvent.Extra["RequestedSerial"] = serial
|
||||
|
||||
cert, err := wfe.SA.GetCertificateByShortSerial(serial)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
if strings.HasPrefix(err.Error(), "gorp: multiple rows returned") {
|
||||
wfe.sendError(response, "Multiple certificates with same short serial", err, http.StatusConflict)
|
||||
} else {
|
||||
|
@ -856,11 +992,13 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
|
|||
response.Header().Add("Link", link(IssuerPath, "up"))
|
||||
response.WriteHeader(http.StatusOK)
|
||||
if _, err = response.Write(cert.DER); err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
|
||||
}
|
||||
return
|
||||
case "POST":
|
||||
wfe.sendError(response, "Not yet supported", "", http.StatusNotFound)
|
||||
logEvent.Error = "Not yet supported"
|
||||
wfe.sendError(response, logEvent.Error, "", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -868,11 +1006,15 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
|
|||
// Terms is used by the client to obtain the current Terms of Service /
|
||||
// Subscriber Agreement to which the subscriber must agree.
|
||||
func (wfe *WebFrontEndImpl) Terms(response http.ResponseWriter, request *http.Request) {
|
||||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
wfe.sendStandardHeaders(response)
|
||||
|
||||
if request.Method != "GET" {
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "GET")
|
||||
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
|
||||
wfe.sendError(response, logEvent.Error, request.Method, http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -881,9 +1023,13 @@ func (wfe *WebFrontEndImpl) Terms(response http.ResponseWriter, request *http.Re
|
|||
|
||||
// Issuer obtains the issuer certificate used by this instance of Boulder.
|
||||
func (wfe *WebFrontEndImpl) Issuer(response http.ResponseWriter, request *http.Request) {
|
||||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
wfe.sendStandardHeaders(response)
|
||||
|
||||
if request.Method != "GET" {
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "GET")
|
||||
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
|
||||
return
|
||||
|
@ -893,15 +1039,20 @@ func (wfe *WebFrontEndImpl) Issuer(response http.ResponseWriter, request *http.R
|
|||
response.Header().Set("Content-Type", "application/pkix-cert")
|
||||
response.WriteHeader(http.StatusOK)
|
||||
if _, err := response.Write(wfe.IssuerCert); err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
// BuildID tells the requestor what build we're running.
|
||||
func (wfe *WebFrontEndImpl) BuildID(response http.ResponseWriter, request *http.Request) {
|
||||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
wfe.sendStandardHeaders(response)
|
||||
|
||||
if request.Method != "GET" {
|
||||
logEvent.Error = "Method not allowed"
|
||||
sendAllow(response, "GET")
|
||||
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
|
||||
return
|
||||
|
@ -911,6 +1062,33 @@ func (wfe *WebFrontEndImpl) BuildID(response http.ResponseWriter, request *http.
|
|||
response.WriteHeader(http.StatusOK)
|
||||
detailsString := fmt.Sprintf("Boulder=(%s %s) Golang=(%s) BuildHost=(%s)", core.GetBuildID(), core.GetBuildTime(), runtime.Version(), core.GetBuildHost())
|
||||
if _, err := fmt.Fprintln(response, detailsString); err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (wfe *WebFrontEndImpl) logRequestDetails(logEvent *requestEvent) {
|
||||
logEvent.ResponseTime = time.Now()
|
||||
var msg string
|
||||
if logEvent.Error != "" {
|
||||
msg = "Terminated request"
|
||||
} else {
|
||||
msg = "Successful request"
|
||||
}
|
||||
wfe.log.InfoObject(msg, logEvent)
|
||||
}
|
||||
|
||||
func (wfe *WebFrontEndImpl) populateRequestEvent(request *http.Request) (logEvent requestEvent) {
|
||||
logEvent = requestEvent{
|
||||
ID: core.NewToken(),
|
||||
RealIP: request.Header.Get("X-Real-IP"),
|
||||
ForwardedFor: request.Header.Get("X-Forwarded-For"),
|
||||
Method: request.Method,
|
||||
RequestTime: time.Now(),
|
||||
Extra: make(map[string]interface{}, 0),
|
||||
}
|
||||
if request.URL != nil {
|
||||
logEvent.Endpoint = request.URL.String()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -308,14 +308,17 @@ func signRequest(t *testing.T, req string, nonceService *core.NonceService) stri
|
|||
test.AssertNotError(t, err, "Failed to unmarshal key")
|
||||
signer, err := jose.NewSigner("RS256", &accountKey)
|
||||
test.AssertNotError(t, err, "Failed to make signer")
|
||||
result, err := signer.Sign([]byte(req), nonceService.Nonce())
|
||||
nonce, err := nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Failed to make nonce")
|
||||
result, err := signer.Sign([]byte(req), nonce)
|
||||
test.AssertNotError(t, err, "Failed to sign req")
|
||||
ret := result.FullSerialize()
|
||||
return ret
|
||||
}
|
||||
|
||||
func setupWFE() WebFrontEndImpl {
|
||||
wfe := NewWebFrontEndImpl()
|
||||
func setupWFE(t *testing.T) WebFrontEndImpl {
|
||||
wfe, err := NewWebFrontEndImpl()
|
||||
test.AssertNotError(t, err, "Unable to create WFE")
|
||||
|
||||
wfe.NewReg = wfe.BaseURL + NewRegPath
|
||||
wfe.RegBase = wfe.BaseURL + RegPath
|
||||
|
@ -331,7 +334,7 @@ func setupWFE() WebFrontEndImpl {
|
|||
}
|
||||
|
||||
func TestStandardHeaders(t *testing.T) {
|
||||
wfe := setupWFE()
|
||||
wfe := setupWFE(t)
|
||||
|
||||
cases := []struct {
|
||||
path string
|
||||
|
@ -366,7 +369,7 @@ func TestStandardHeaders(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIndex(t *testing.T) {
|
||||
wfe := setupWFE()
|
||||
wfe := setupWFE(t)
|
||||
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
|
@ -392,7 +395,7 @@ func TestIndex(t *testing.T) {
|
|||
// TODO: Write additional test cases for:
|
||||
// - RA returns with a failure
|
||||
func TestIssueCertificate(t *testing.T) {
|
||||
wfe := setupWFE()
|
||||
wfe := setupWFE(t)
|
||||
|
||||
// TODO: Use a mock RA so we can test various conditions of authorized, not authorized, etc.
|
||||
ra := ra.NewRegistrationAuthorityImpl()
|
||||
|
@ -521,7 +524,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestChallenge(t *testing.T) {
|
||||
wfe := setupWFE()
|
||||
wfe := setupWFE(t)
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &MockSA{}
|
||||
|
@ -557,7 +560,7 @@ func TestChallenge(t *testing.T) {
|
|||
Method: "POST",
|
||||
URL: challengeURL,
|
||||
Body: makeBody(signRequest(t, "{}", &wfe.nonceService)),
|
||||
})
|
||||
}, requestEvent{})
|
||||
|
||||
test.AssertEquals(
|
||||
t, responseWriter.Header().Get("Location"),
|
||||
|
@ -571,7 +574,7 @@ func TestChallenge(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewRegistration(t *testing.T) {
|
||||
wfe := setupWFE()
|
||||
wfe := setupWFE(t)
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &MockSA{}
|
||||
|
@ -608,7 +611,9 @@ func TestNewRegistration(t *testing.T) {
|
|||
|
||||
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
|
||||
responseWriter.Body.Reset()
|
||||
result, err := signer.Sign([]byte("foo"), wfe.nonceService.Nonce())
|
||||
nonce, err := wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, err := signer.Sign([]byte("foo"), nonce)
|
||||
wfe.NewRegistration(responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
Body: makeBody(result.FullSerialize()),
|
||||
|
@ -642,9 +647,11 @@ func TestNewRegistration(t *testing.T) {
|
|||
"{\"type\":\"urn:acme:error:malformed\",\"detail\":\"Unable to read/verify body\"}")
|
||||
|
||||
responseWriter.Body.Reset()
|
||||
nonce, err = wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, err = signer.Sign(
|
||||
[]byte("{\"contact\":[\"tel:123456789\"],\"agreement\":\"https://letsencrypt.org/im-bad\"}"),
|
||||
wfe.nonceService.Nonce())
|
||||
nonce)
|
||||
wfe.NewRegistration(responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
Body: makeBody(result.FullSerialize()),
|
||||
|
@ -654,8 +661,9 @@ func TestNewRegistration(t *testing.T) {
|
|||
"{\"type\":\"urn:acme:error:malformed\",\"detail\":\"Provided agreement URL [https://letsencrypt.org/im-bad] does not match current agreement URL ["+agreementURL+"]\"}")
|
||||
|
||||
responseWriter.Body.Reset()
|
||||
result, err = signer.Sign([]byte("{\"contact\":[\"tel:123456789\"],\"agreement\":\""+agreementURL+"\"}"),
|
||||
wfe.nonceService.Nonce())
|
||||
nonce, err = wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, err = signer.Sign([]byte("{\"contact\":[\"tel:123456789\"],\"agreement\":\""+agreementURL+"\"}"), nonce)
|
||||
wfe.NewRegistration(responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
Body: makeBody(result.FullSerialize()),
|
||||
|
@ -685,7 +693,9 @@ func TestNewRegistration(t *testing.T) {
|
|||
|
||||
// POST, Valid JSON, Key already in use
|
||||
responseWriter.Body.Reset()
|
||||
result, err = signer.Sign([]byte("{\"contact\":[\"tel:123456789\"],\"agreement\":\""+agreementURL+"\"}"), wfe.nonceService.Nonce())
|
||||
nonce, err = wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, err = signer.Sign([]byte("{\"contact\":[\"tel:123456789\"],\"agreement\":\""+agreementURL+"\"}"), nonce)
|
||||
|
||||
wfe.NewRegistration(responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
|
@ -719,14 +729,16 @@ func TestRevokeCertificate(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Failed to marshal request")
|
||||
|
||||
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
|
||||
wfe := setupWFE()
|
||||
wfe := setupWFE(t)
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &MockSA{}
|
||||
wfe.SubscriberAgreementURL = agreementURL
|
||||
responseWriter := httptest.NewRecorder()
|
||||
responseWriter.Body.Reset()
|
||||
result, _ := signer.Sign(revokeRequestJSON, wfe.nonceService.Nonce())
|
||||
nonce, err := wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, _ := signer.Sign(revokeRequestJSON, nonce)
|
||||
wfe.RevokeCertificate(responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
Body: makeBody(result.FullSerialize()),
|
||||
|
@ -743,7 +755,9 @@ func TestRevokeCertificate(t *testing.T) {
|
|||
test.Assert(t, ok, "Couldn't load RSA key")
|
||||
accountKeySigner, err := jose.NewSigner("RS256", test1Key)
|
||||
test.AssertNotError(t, err, "Failed to make signer")
|
||||
result, _ = accountKeySigner.Sign(revokeRequestJSON, wfe.nonceService.Nonce())
|
||||
nonce, err = wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, _ = accountKeySigner.Sign(revokeRequestJSON, nonce)
|
||||
wfe.RevokeCertificate(responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
Body: makeBody(result.FullSerialize()),
|
||||
|
@ -775,14 +789,16 @@ func TestRevokeCertificateAlreadyRevoked(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Failed to marshal request")
|
||||
|
||||
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
|
||||
wfe := setupWFE()
|
||||
wfe := setupWFE(t)
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &MockSA{}
|
||||
wfe.SubscriberAgreementURL = agreementURL
|
||||
responseWriter := httptest.NewRecorder()
|
||||
responseWriter.Body.Reset()
|
||||
result, _ := signer.Sign(revokeRequestJSON, wfe.nonceService.Nonce())
|
||||
nonce, err := wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, _ := signer.Sign(revokeRequestJSON, nonce)
|
||||
wfe.RevokeCertificate(responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
Body: makeBody(result.FullSerialize()),
|
||||
|
@ -793,7 +809,7 @@ func TestRevokeCertificateAlreadyRevoked(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAuthorization(t *testing.T) {
|
||||
wfe := setupWFE()
|
||||
wfe := setupWFE(t)
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &MockSA{}
|
||||
|
@ -875,7 +891,7 @@ func TestAuthorization(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRegistration(t *testing.T) {
|
||||
wfe := setupWFE()
|
||||
wfe := setupWFE(t)
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &MockSA{}
|
||||
|
@ -925,7 +941,9 @@ func TestRegistration(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Failed to make signer")
|
||||
|
||||
// Test POST valid JSON but key is not registered
|
||||
result, err := signer.Sign([]byte("{\"agreement\":\""+agreementURL+"\"}"), wfe.nonceService.Nonce())
|
||||
nonce, err := wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, err := signer.Sign([]byte("{\"agreement\":\""+agreementURL+"\"}"), nonce)
|
||||
path, _ = url.Parse("/2")
|
||||
wfe.Registration(responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
|
@ -947,7 +965,9 @@ func TestRegistration(t *testing.T) {
|
|||
path, _ = url.Parse("/2")
|
||||
|
||||
// Test POST valid JSON with registration up in the mock (with incorrect agreement URL)
|
||||
result, err = signer.Sign([]byte("{\"agreement\":\"https://letsencrypt.org/im-bad\"}"), wfe.nonceService.Nonce())
|
||||
nonce, err = wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, err = signer.Sign([]byte("{\"agreement\":\"https://letsencrypt.org/im-bad\"}"), nonce)
|
||||
|
||||
// Test POST valid JSON with registration up in the mock
|
||||
path, _ = url.Parse("/1")
|
||||
|
@ -962,7 +982,9 @@ func TestRegistration(t *testing.T) {
|
|||
responseWriter.Body.Reset()
|
||||
|
||||
// Test POST valid JSON with registration up in the mock (with correct agreement URL)
|
||||
result, err = signer.Sign([]byte("{\"agreement\":\""+agreementURL+"\"}"), wfe.nonceService.Nonce())
|
||||
nonce, err = wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, err = signer.Sign([]byte("{\"agreement\":\""+agreementURL+"\"}"), nonce)
|
||||
wfe.Registration(responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
Body: makeBody(result.FullSerialize()),
|
||||
|
|
Loading…
Reference in New Issue