Merge branch 'master' into sig-reuse

This commit is contained in:
Richard Barnes 2015-09-11 15:24:43 -04:00
commit 275b086acc
11 changed files with 272 additions and 241 deletions

View File

@ -33,7 +33,7 @@ EXPOSE 4000
ENV BOULDER_CONFIG /go/src/github.com/letsencrypt/boulder/test/boulder-config.json ENV BOULDER_CONFIG /go/src/github.com/letsencrypt/boulder/test/boulder-config.json
# Get the Let's Encrypt client # Get the Let's Encrypt client
RUN git clone https://www.github.com/letsencrypt/lets-encrypt-preview.git /letsencrypt RUN git clone https://www.github.com/letsencrypt/letsencrypt.git /letsencrypt
WORKDIR /letsencrypt WORKDIR /letsencrypt
RUN ./bootstrap/debian.sh && \ RUN ./bootstrap/debian.sh && \
apt-get clean && \ apt-get clean && \
@ -43,20 +43,13 @@ RUN ./bootstrap/debian.sh && \
RUN virtualenv --no-site-packages -p python2 venv && \ RUN virtualenv --no-site-packages -p python2 venv && \
./venv/bin/pip install -r requirements.txt -e acme -e .[dev,docs,testing] -e letsencrypt-apache -e letsencrypt-nginx ./venv/bin/pip install -r requirements.txt -e acme -e .[dev,docs,testing] -e letsencrypt-apache -e letsencrypt-nginx
RUN go get bitbucket.org/liamstask/goose/cmd/goose
ENV LETSENCRYPT_PATH /letsencrypt ENV LETSENCRYPT_PATH /letsencrypt
# Copy in the Boulder sources # Copy in the Boulder sources
COPY . /go/src/github.com/letsencrypt/boulder COPY . /go/src/github.com/letsencrypt/boulder
# Build Boulder
RUN go install \
github.com/letsencrypt/boulder/cmd/activity-monitor \
github.com/letsencrypt/boulder/cmd/boulder-ca \
github.com/letsencrypt/boulder/cmd/boulder-ra \
github.com/letsencrypt/boulder/cmd/boulder-sa \
github.com/letsencrypt/boulder/cmd/boulder-va \
github.com/letsencrypt/boulder/cmd/boulder-wfe
WORKDIR /go/src/github.com/letsencrypt/boulder WORKDIR /go/src/github.com/letsencrypt/boulder
CMD ["bash", "-c", "service mysql start && \ CMD ["bash", "-c", "service mysql start && \
service rsyslog start && \ service rsyslog start && \

View File

@ -7,49 +7,30 @@ This is an initial implementation of an ACME-based CA. The [ACME protocol](https
[![Build Status](https://travis-ci.org/letsencrypt/boulder.svg)](https://travis-ci.org/letsencrypt/boulder) [![Build Status](https://travis-ci.org/letsencrypt/boulder.svg)](https://travis-ci.org/letsencrypt/boulder)
[![Coverage Status](https://coveralls.io/repos/letsencrypt/boulder/badge.svg)](https://coveralls.io/r/letsencrypt/boulder) [![Coverage Status](https://coveralls.io/repos/letsencrypt/boulder/badge.svg)](https://coveralls.io/r/letsencrypt/boulder)
Docker Quickstart
------ ------
Boulder is available as a [Docker image from Quay.io](https://quay.io/repository/letsencrypt/boulder). The Docker image expects the `config.json` file to be located at `/boulder/config.json` within the container. Boulder has a Dockerfile to make it easy to install and set up all its
dependencies. This approach is most suitable if you just need to set up Boulder
for the purpose of testing client software against it. To start Boulder
in a Docker container, run:
(Note: You can override the `config.json` location by specifying a different BOULDER_CONFIG environment variable, such as with `-e BOULDER_CONFIG=mypath/myfile.config`.) ./test/run-docker.sh
There are no default commands; you must choose one of the executables from the `cmd` path. Slow start
There are several tags available:
- `stable` is maintained by the Let's Encrypt team as a fairly stable copy of Boulder.
- `latest` is a more recent build of Boulder. It may lag behind the `master` ref, as automated builds are being reworked.
- Tags for individual short-format git refs, representing those builds.
A quick-start method for running a Boulder instance is to use one of the example configurations:
docker run -i --name=boulder --read-only=true --rm=true -p 4000:4000 quay.io/letsencrypt/boulder:latest
Alternatively, to run all services locally, using AMQP to pass messages between them, you can use:
```
> python start.py
# start.py will use the configuration specified by BOULDER_CONFIG or test/boulder-config.json
```
To run a single module, specifying the AMQP server, you might use something more like:
```
> docker run --name=boulder --read-only=true --rm=true -v $(pwd)/.boulder-config:/boulder:ro quay.io/letsencrypt/boulder:stable boulder-ra
```
Quickstart
---------- ----------
Boulder requires an installation of RabbitMQ, libtool-ltdl, and This approach is better if you intend to develop on Boulder frequently, because
it's challenging to develop inside the Docker container.
Boulder requires an installation of RabbitMQ, libtool-ltdl, goose, and
MariaDB 10 to work correctly. On Ubuntu and CentOS, you may have to MariaDB 10 to work correctly. On Ubuntu and CentOS, you may have to
install RabbitMQ from https://rabbitmq.com/download.html to get a install RabbitMQ from https://rabbitmq.com/download.html to get a
recent version. recent version.
Also, Boulder requires Go 1.5. As of September 2015 this version is not yet
available in OS repostiories, so you will have to install from https://golang.org/dl/.
Ubuntu: Ubuntu:
sudo apt-get install libltdl3-dev mariadb-server rabbitmq-server sudo apt-get install libltdl3-dev mariadb-server rabbitmq-server
@ -69,19 +50,22 @@ or
(On OS X, using port, you will have to add `CGO_CFLAGS="-I/opt/local/include" CGO_LDFLAGS="-L/opt/local/lib"` to your environment or `go` invocations.) (On OS X, using port, you will have to add `CGO_CFLAGS="-I/opt/local/include" CGO_LDFLAGS="-L/opt/local/lib"` to your environment or `go` invocations.)
``` ```
> go get bitbucket.org/liamstask/goose/cmd/goose
> go get github.com/letsencrypt/boulder/ # Ignore errors about no buildable files > go get github.com/letsencrypt/boulder/ # Ignore errors about no buildable files
> cd $GOPATH/src/github.com/letsencrypt/boulder > cd $GOPATH/src/github.com/letsencrypt/boulder
> ./test/create_db.sh
# This starts each Boulder component with test configs. Ctrl-C kills all. # This starts each Boulder component with test configs. Ctrl-C kills all.
> ./start.py
# Run tests
> ./test.sh > ./test.sh
``` ```
The databases that boulder requires to operate in development and Note: create\_db.sh it uses the root MariaDB user, so if you
testing can be created using test/create\_db.sh. It uses the root have disabled that account you may have to adjust the file or
MariaDB user, so if you have disabled that account you may have to recreate the commands.
adjust the file or recreate the commands.
You can also check out the official client from You can also check out the official client from
https://github.com/letsencrypt/lets-encrypt-preview/ and follow the setup https://github.com/letsencrypt/letsencrypt/ and follow the setup
instructions there. instructions there.
Component Model Component Model
@ -130,7 +114,7 @@ and to [avoid insecure fallback in go
get](https://github.com/golang/go/issues/9637). get](https://github.com/golang/go/issues/9637).
Local development also requires a RabbitMQ installation and MariaDB Local development also requires a RabbitMQ installation and MariaDB
10 installation. MariaDB should be run on port 3306 for the 10 installation (see above). MariaDB should be run on port 3306 for the
default integration tests. default integration tests.
To update the Go dependencies: To update the Go dependencies:

View File

@ -12,6 +12,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"reflect"
"runtime" "runtime"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -194,7 +195,7 @@ func (c *certChecker) checkCert(cert core.Certificate) (problems []string) {
} }
} }
// Check the cert has the correct key usage extensions // Check the cert has the correct key usage extensions
if !core.CmpExtKeyUsageSlice(parsedCert.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) { if !reflect.DeepEqual(parsedCert.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) {
problems = append(problems, "Certificate has incorrect key usage extensions") problems = append(problems, "Certificate has incorrect key usage extensions")
} }
} }

View File

@ -47,7 +47,7 @@ var (
type DNSResolverImpl struct { type DNSResolverImpl struct {
DNSClient *dns.Client DNSClient *dns.Client
Servers []string Servers []string
allowLoopbackAddresses bool allowRestrictedAddresses bool
} }
// NewDNSResolverImpl constructs a new DNS resolver object that utilizes the // NewDNSResolverImpl constructs a new DNS resolver object that utilizes the
@ -61,7 +61,7 @@ func NewDNSResolverImpl(dialTimeout time.Duration, servers []string) *DNSResolve
return &DNSResolverImpl{ return &DNSResolverImpl{
DNSClient: dnsClient, DNSClient: dnsClient,
Servers: servers, Servers: servers,
allowLoopbackAddresses: false, allowRestrictedAddresses: false,
} }
} }
@ -70,7 +70,7 @@ func NewDNSResolverImpl(dialTimeout time.Duration, servers []string) *DNSResolve
// This constructor should *only* be called from tests (unit or integration). // This constructor should *only* be called from tests (unit or integration).
func NewTestDNSResolverImpl(dialTimeout time.Duration, servers []string) *DNSResolverImpl { func NewTestDNSResolverImpl(dialTimeout time.Duration, servers []string) *DNSResolverImpl {
resolver := NewDNSResolverImpl(dialTimeout, servers) resolver := NewDNSResolverImpl(dialTimeout, servers)
resolver.allowLoopbackAddresses = true resolver.allowRestrictedAddresses = true
return resolver return resolver
} }
@ -120,8 +120,8 @@ func (dnsResolver *DNSResolverImpl) LookupTXT(hostname string) ([]string, time.D
return txt, rtt, err return txt, rtt, err
} }
func isPrivateV4(ip net.IP, allowLoopback bool) bool { func isPrivateV4(ip net.IP) bool {
return rfc1918_10.Contains(ip) || rfc1918_172_16.Contains(ip) || rfc1918_192_168.Contains(ip) || (!allowLoopback && rfc5735_127.Contains(ip)) return rfc1918_10.Contains(ip) || rfc1918_172_16.Contains(ip) || rfc1918_192_168.Contains(ip) || rfc5735_127.Contains(ip)
} }
// LookupHost sends a DNS query to find all A records associated with the provided // LookupHost sends a DNS query to find all A records associated with the provided
@ -141,7 +141,7 @@ func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.
for _, answer := range r.Answer { for _, answer := range r.Answer {
if answer.Header().Rrtype == dns.TypeA { if answer.Header().Rrtype == dns.TypeA {
if a, ok := answer.(*dns.A); ok && a.A.To4() != nil && !isPrivateV4(a.A, dnsResolver.allowLoopbackAddresses) { if a, ok := answer.(*dns.A); ok && a.A.To4() != nil && (!isPrivateV4(a.A) || dnsResolver.allowRestrictedAddresses) {
addrs = append(addrs, a.A) addrs = append(addrs, a.A)
} }
} }

View File

@ -11,7 +11,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
"sort"
"strings" "strings"
"time" "time"
@ -101,52 +100,6 @@ func (pd *ProblemDetails) Error() string {
return fmt.Sprintf("%s :: %s", pd.Type, pd.Detail) return fmt.Sprintf("%s :: %s", pd.Type, pd.Detail)
} }
func cmpStrSlice(a, b []string) bool {
if len(a) != len(b) {
return false
}
sort.Strings(a)
sort.Strings(b)
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func CmpExtKeyUsageSlice(a, b []x509.ExtKeyUsage) bool {
if len(a) != len(b) {
return false
}
testMap := make(map[int]bool, len(a))
for i := range a {
testMap[int(a[i])] = true
}
for i := range b {
if !testMap[int(b[i])] {
return false
}
}
return true
}
func cmpIPSlice(a, b []net.IP) bool {
if len(a) != len(b) {
return false
}
testMap := make(map[string]bool, len(a))
for i := range a {
testMap[a[i].String()] = true
}
for i := range b {
if !testMap[b[i].String()] {
return false
}
}
return true
}
// An AcmeIdentifier encodes an identifier that can // An AcmeIdentifier encodes an identifier that can
// be validated by ACME. The protocol allows for different // be validated by ACME. The protocol allows for different
// types of identifier to be supported (DNS names, IP // types of identifier to be supported (DNS names, IP
@ -541,82 +494,6 @@ type ExternalCert struct {
CertDER []byte `db:"rawDERCert"` // DER (binary) encoding of the raw certificate CertDER []byte `db:"rawDERCert"` // DER (binary) encoding of the raw certificate
} }
// MatchesCSR tests the contents of a generated certificate to make sure
// that the PublicKey, CommonName, and DNSNames match those provided in
// the CSR that was used to generate the certificate. It also checks the
// following fields for:
// * notAfter is after earliestExpiry
// * notBefore is not more than 24 hours ago
// * BasicConstraintsValid is true
// * IsCA is false
// * ExtKeyUsage only contains ExtKeyUsageServerAuth & ExtKeyUsageClientAuth
// * Subject only contains CommonName & Names
func (cert Certificate) MatchesCSR(csr *x509.CertificateRequest, earliestExpiry time.Time) (err error) {
parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
if err != nil {
return
}
// Check issued certificate matches what was expected from the CSR
hostNames := make([]string, len(csr.DNSNames))
copy(hostNames, csr.DNSNames)
if len(csr.Subject.CommonName) > 0 {
hostNames = append(hostNames, csr.Subject.CommonName)
}
hostNames = UniqueNames(hostNames)
if !KeyDigestEquals(parsedCertificate.PublicKey, csr.PublicKey) {
err = InternalServerError("Generated certificate public key doesn't match CSR public key")
return
}
if len(csr.Subject.CommonName) > 0 && parsedCertificate.Subject.CommonName != csr.Subject.CommonName {
err = InternalServerError("Generated certificate CommonName doesn't match CSR CommonName")
return
}
if !cmpStrSlice(parsedCertificate.DNSNames, hostNames) {
err = InternalServerError("Generated certificate DNSNames don't match CSR DNSNames")
return
}
if !cmpIPSlice(parsedCertificate.IPAddresses, csr.IPAddresses) {
err = InternalServerError("Generated certificate IPAddresses don't match CSR IPAddresses")
return
}
if !cmpStrSlice(parsedCertificate.EmailAddresses, csr.EmailAddresses) {
err = InternalServerError("Generated certificate EmailAddresses don't match CSR EmailAddresses")
return
}
if len(parsedCertificate.Subject.Country) > 0 || len(parsedCertificate.Subject.Organization) > 0 ||
len(parsedCertificate.Subject.OrganizationalUnit) > 0 || len(parsedCertificate.Subject.Locality) > 0 ||
len(parsedCertificate.Subject.Province) > 0 || len(parsedCertificate.Subject.StreetAddress) > 0 ||
len(parsedCertificate.Subject.PostalCode) > 0 || len(parsedCertificate.Subject.SerialNumber) > 0 {
err = InternalServerError("Generated certificate Subject contains fields other than CommonName or Names")
return
}
if parsedCertificate.NotAfter.After(earliestExpiry) {
err = InternalServerError("Generated certificate expires before earliest expiration")
return
}
now := time.Now()
if now.Sub(parsedCertificate.NotBefore) > time.Hour*24 {
err = InternalServerError(fmt.Sprintf("Generated certificate is back dated %s", now.Sub(parsedCertificate.NotBefore)))
return
}
if !parsedCertificate.BasicConstraintsValid {
err = InternalServerError("Generated certificate doesn't have basic constraints set")
return
}
if parsedCertificate.IsCA {
err = InternalServerError("Generated certificate can sign other certificates")
return
}
if !CmpExtKeyUsageSlice(parsedCertificate.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) {
err = InternalServerError("Generated certificate doesn't have correct key usage extensions")
return
}
return
}
// CertificateStatus structs are internal to the server. They represent the // CertificateStatus structs are internal to the server. They represent the
// latest data about the status of the certificate, required for OCSP updating // latest data about the status of the certificate, required for OCSP updating
// and for validating that the subscriber has accepted the certificate. // and for validating that the subscriber has accepted the certificate.

View File

@ -11,6 +11,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/mail" "net/mail"
"reflect"
"sort"
"strings" "strings"
"time" "time"
@ -203,6 +205,89 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
return authz, err return authz, err
} }
// MatchesCSR tests the contents of a generated certificate to make sure
// that the PublicKey, CommonName, and DNSNames match those provided in
// the CSR that was used to generate the certificate. It also checks the
// following fields for:
// * notAfter is after earliestExpiry
// * notBefore is not more than 24 hours ago
// * BasicConstraintsValid is true
// * IsCA is false
// * ExtKeyUsage only contains ExtKeyUsageServerAuth & ExtKeyUsageClientAuth
// * Subject only contains CommonName & Names
func (ra *RegistrationAuthorityImpl) MatchesCSR(
cert core.Certificate,
csr *x509.CertificateRequest,
earliestExpiry time.Time) (err error) {
parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
if err != nil {
return
}
// Check issued certificate matches what was expected from the CSR
hostNames := make([]string, len(csr.DNSNames))
copy(hostNames, csr.DNSNames)
if len(csr.Subject.CommonName) > 0 {
hostNames = append(hostNames, csr.Subject.CommonName)
}
hostNames = core.UniqueNames(hostNames)
if !core.KeyDigestEquals(parsedCertificate.PublicKey, csr.PublicKey) {
err = core.InternalServerError("Generated certificate public key doesn't match CSR public key")
return
}
if len(csr.Subject.CommonName) > 0 && parsedCertificate.Subject.CommonName != csr.Subject.CommonName {
err = core.InternalServerError("Generated certificate CommonName doesn't match CSR CommonName")
return
}
// Sort both slices of names before comparison.
parsedNames := parsedCertificate.DNSNames
sort.Strings(parsedNames)
sort.Strings(hostNames)
if !reflect.DeepEqual(parsedNames, hostNames) {
err = core.InternalServerError("Generated certificate DNSNames don't match CSR DNSNames")
return
}
if !reflect.DeepEqual(parsedCertificate.IPAddresses, csr.IPAddresses) {
err = core.InternalServerError("Generated certificate IPAddresses don't match CSR IPAddresses")
return
}
if !reflect.DeepEqual(parsedCertificate.EmailAddresses, csr.EmailAddresses) {
err = core.InternalServerError("Generated certificate EmailAddresses don't match CSR EmailAddresses")
return
}
if len(parsedCertificate.Subject.Country) > 0 || len(parsedCertificate.Subject.Organization) > 0 ||
len(parsedCertificate.Subject.OrganizationalUnit) > 0 || len(parsedCertificate.Subject.Locality) > 0 ||
len(parsedCertificate.Subject.Province) > 0 || len(parsedCertificate.Subject.StreetAddress) > 0 ||
len(parsedCertificate.Subject.PostalCode) > 0 || len(parsedCertificate.Subject.SerialNumber) > 0 {
err = core.InternalServerError("Generated certificate Subject contains fields other than CommonName or Names")
return
}
if parsedCertificate.NotAfter.After(earliestExpiry) {
err = core.InternalServerError("Generated certificate expires before earliest expiration")
return
}
now := ra.clk.Now()
if now.Sub(parsedCertificate.NotBefore) > time.Hour*24 {
err = core.InternalServerError(fmt.Sprintf("Generated certificate is back dated %s", now.Sub(parsedCertificate.NotBefore)))
return
}
if !parsedCertificate.BasicConstraintsValid {
err = core.InternalServerError("Generated certificate doesn't have basic constraints set")
return
}
if parsedCertificate.IsCA {
err = core.InternalServerError("Generated certificate can sign other certificates")
return
}
if !reflect.DeepEqual(parsedCertificate.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) {
err = core.InternalServerError("Generated certificate doesn't have correct key usage extensions")
return
}
return
}
// NewCertificate requests the issuance of a certificate. // NewCertificate requests the issuance of a certificate.
func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest, regID int64) (cert core.Certificate, err error) { func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest, regID int64) (cert core.Certificate, err error) {
emptyCert := core.Certificate{} emptyCert := core.Certificate{}
@ -306,7 +391,7 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
return emptyCert, err return emptyCert, err
} }
err = cert.MatchesCSR(csr, earliestExpiry) err = ra.MatchesCSR(cert, csr, earliestExpiry)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.Error = err.Error()
return emptyCert, err return emptyCert, err

View File

@ -8,6 +8,7 @@ package main
import ( import (
"fmt" "fmt"
"net" "net"
"os"
"time" "time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns" "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
@ -19,6 +20,14 @@ func dnsHandler(w dns.ResponseWriter, r *dns.Msg) {
m.SetReply(r) m.SetReply(r)
m.Compress = false m.Compress = false
// Normally this test DNS server will return 127.0.0.1 for everything.
// However, in some situations (for instance Docker), it's useful to return a
// different hardcoded host. You can do so by setting the FAKE_DNS environment
// variable.
fakeDNS := os.Getenv("FAKE_DNS")
if fakeDNS == "" {
fakeDNS = "127.0.0.1"
}
for _, q := range r.Question { for _, q := range r.Question {
fmt.Printf("dns-srv: Query -- [%s] %s\n", q.Name, dns.TypeToString[q.Qtype]) fmt.Printf("dns-srv: Query -- [%s] %s\n", q.Name, dns.TypeToString[q.Qtype])
switch q.Qtype { switch q.Qtype {
@ -30,7 +39,7 @@ func dnsHandler(w dns.ResponseWriter, r *dns.Msg) {
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: 0, Ttl: 0,
} }
record.A = net.ParseIP("127.0.0.1") record.A = net.ParseIP(fakeDNS)
m.Answer = append(m.Answer, record) m.Answer = append(m.Answer, record)
case dns.TypeMX: case dns.TypeMX:

33
test/run-docker.sh Executable file
View File

@ -0,0 +1,33 @@
#!/bin/bash
#
# Build and run a docker image for Boulder. This is suitable for running
# repeatedly during development because Docker will cache the image it builds,
# and will only re-do the minimum necessary.
#
# NOTE: Currently we're not able to effectively cache the DB setup steps,
# because setting up the DB depends on source files in the Boulder repo. So any
# time source files change, Docker treats that as potentially invalidating the
# steps that came after the COPY. In theory we could add a step that copies only
# the files necessary to do the migrations, run them, and then copy the rest of
# the source.
set -o errexit
cd $(dirname $0)/..
# In order to talk to a letsencrypt client running on the host, the fake DNS
# client used in Boulder's start.py needs to know what the host's IP is from the
# perspective of the container. We try to figure it out automatically. If you'd
# like your Boulder instance to always talk to some other host, you can set
# FAKE_DNS to that host's IP address.
if [ -z "${FAKE_DNS}" ] ; then
FAKE_DNS=$(ifconfig docker0 | sed -n 's/ *inet addr:\([0-9.]\+\).*/\1/p')
fi
docker build --tag boulder .
# The -i command makes the instance interactive, so you can kill start.py with Ctrl-C.
docker run \
--interactive \
--tty \
--rm=true \
--publish 4000-4001:4000-4001 \
--publish 8000-8100:8000-8100 \
--env FAKE_DNS="${FAKE_DNS}" \
boulder

View File

@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCaqzue57mgXEoG MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD3tBSSz5+lz4if
TZZoVkkCZraebWgXI8irX2BgQB1A3iZa9onxGPMcWQMxhSuUisbEJi4UkMcVST12 t0imYGEps8RJyuNZ/aCOcE+tOb1nRXbu3DYxmY+6/BWl7P/bbWS5dKiMGUELiaIV
HX01rUwhj41UuBxJvI1w4wvdstssTAaa9c9tsQ5+UED2bFRL1MsyBdbmCF/+pu3i qwnqXmoqiMkPLXcVyXClMloThoNked+vmXzDbwRFp0GWzrCl4q4UWk2wAn70p/XF
+ZIYqWgiKbjVBe3nlAVbo77zizwp3Y4Tp1/TBOwTAuFkHePmkNT63uPm9My/hNzs tDxW5lJx6XNw9EcX3RvpqqZ46jsXY2MGDRD2xQLzZFUxS+nfLOrk5akan50iGqIv
Sm1o+Q519Cf7ry+JQmOVgz/jIgFVGFYJ17EV3KUIpUuDShuyCFATBQspgJSN2DoX tbzesGySO5xTFrwg6sQIJCzkVIwCaCeIeS2ffFe7+MtPDUlXXZNvQ0WfG7iabPJZ
RUlQjXXkNTj23OxxdT/cVLcLJjytyG6e5izME2R2aCkDBWIc1a4/sRJ0R396auPX pvJuNqPtVCCxSav4137b8ymn3F5UaPcYBjosZqK0btDykOkfLfiBHE1V/0xSGQ/U
G6KhJ7o/AgMBAAECggEAC7GkmVgVzc0Mf7uAArV7YaYYapQFCbLX6jUU2VIfpBbn su55fGKPAgMBAAECggEBAJq6qKdUfptq2iM6sG0Ng2QzYQffbslF/0c6rOq+SKv7
uXroZQUo5FzKhAT4jYuMiaoFU+K6Wp6l+fcyz0sh9WugGOaupNiPrRhNfl6WeZvp 3mEyub+d9wSMd1FgjCqbWPHoB2kMVW9JmDAE2XrHY3YSX9XVRrAbVvw6Sb4spmLb
5+9r1nRLjztMHhWErhMRpd+RJuU9NMi0NbP+2sR8LhEPe3OuUBL98LbJqio9y0Bp JnlgXWkPLhH8eqIjloDo6mjYR/MMfBWD3aiT/6Ug1ptFgtEqJkan3cnGyZp6ZlMi
8+JxmATL7vTvORMMamt80bDRGLBzoGySWZ0lEWwTuv8RjiCqO85YmHd9BmnTpOUn jZQOoPyxaSVtYiyKZo7Mng02fwbkjiCQpTKCKk+dUnDQHmPI7kV69/jvqzJzCPwf
siYfEDZ9t+EF6NgJaISMLmcbzxZI5+aPIvfc1TwzDA96LUccGwvAR4qZ/+eYsG2I IWXzfg9vhTuLjiGHNTfVqiEevSkmrEerVVIaC9/UNACkyd2jW+Hhl5lFRVRQBnIc
Iwn1wMMSsvFWeq7AccKL/povPg1Iud0wzkLLZmMjGQKBgQDLRUtENxbVH/T4xw8W 8DmSVQZrFu08LNLlZOBy+kga/JTWv1s8Gb4WXkN70FECgYEA/3bs3hkUDF9/KnKC
tdgfzddQWZmVPtQXHAt2/zmrgJ3DKu7HOUajCGGWdLnoiFrIO2LsQYpP1XUA4hG1 ljceKH05/G8cU84riqwt8lepjwu0Zzq+OVm58CMwljPRi2nuBGsFirFsmKMbAxM7
vzLDBng6SHDqZdGfd8eFaHesafr/FG4zkk+GBIs+P8GzHs2/znpEiqLnPbWrrB3t QjGzcmCp6QEfm0rSKMs68BnmVyTbVoov4l9CNeWiGtRxSHvKXOAcKEMw032cKmeE
2/zBIXluZSytGt1ysC+TZZFwhQKBgQDCymmryD0JRV0M1IgwXOZrO+2MWDdliujx f6NbVXzsaGglaDAjqFBPiQjoVMUCgYEA+Dj9n6QiAy3jF+XqlnjszF+2gPmutBDR
uBfD6DdLCJ5wHrPiySjVvl0njPLDQcWigigJD8Jl0SRQ8JSbceFzEGjzEj3NR2oB bWpLvcJsa2aeuZq4Uw/42d+GneSDYY2O2e51WYwWJU7QvVvKrosDc8vrmq0YDt0S
aLn3Gf3nORo7tJdCZVQn7QcHT+HzAWffm6qjqKzPXjRAEFFcW48Bnw+BU/JuZNOe DsWknVnUImFJVgM/ssepQvDoheR03GB/EiRjSzl9duopi/9I2I6L2Bz0eHWdoe9I
v7dTuyv88wKBgFROGAphwsF/8I0hmht0Lf/60ltL3gvtM++lvQeMkTGVNVlVvBS6 59W7NCbWl0MCgYA3jPnG9fcZFa8GYO4qkgO51DlGvjaPtrZbKkO3ff59/5KUG/7/
p5ZEipzpKpXLv8MeBkgwYpn70PwdxvSXKQmD7GdX1iURN6CpAAJPspq6ldQneBFB Y5Z+NHi9QLlhMoCybz8+QISX0GfwTD9Hjp0x0vk/lKH119hJTKAdU9R1wKr9b4Eo
lGPkDJAzxzVwCCuOCl3VFf1MNcXOq9cUDz9Wj9N+eMoOw1umwQSj8m81AoGAP/li HYS9SZjtcqLLmzeO6KDW1H2Kj7bktavnQXN77HVP4s23kTg/wm75Inm67QKBgQCc
gzycbzMMwG383IVmV8my1ukSKJNatiiUBY96uXX3MzOiONWAR9LhnV+5S0+KrTi6 7KqqukEmcWQYP6pG5rwNmJTwhOkFvXwpCAJKPRf97ip6fG37VSWl18JrH4RPsAaX
FV/LpMzvdHXPGM5qEPROw6Y2DflqY1QV34X10b77UqiZFQFahlJegJRHzRulFdd2 kEFwEzgM7f7ZN6azIf9UvZ59cuC6xNgdrqWbNKb/9TE/x14F0GQZheP6gau/huHO
T5HST7jMyE2TqxWW/h1TZlI/yOnsZrLobuOGKukCgYAGk/TRPSOWEkH3rL7cDJFp vmJntDi1bORHNWUAQ131ipijbvfw4C3fIyfumH8hsQKBgQDggOcVSKmMilITeEls
ql4d33iuW9b3dMvQgkDW8H2kj3QA5os9dtSFLA/fWubULXOPpOsA7Ny7okBxsUBW Ui4K4dQ2feMyCv07LBe2zvrL4EP48dQw40DJKFj8HYPpqmFtjESjYnPw+9YUid5N
tn4ER+HjUHogGir5d5cBBpvi4xM2/B4KaZnX8IHYhFcT42eb1oYmpfz5GcRyqzTX fnssdGmnScfHUhZMoXIhmiEFrsAMDllwftqESi7+QRIie4Owftdpb/8VEyBH7xga
OoetYnUS5t4QuAAMadjtig== MvhT37SxXefJCluspTw7n+am8g==
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

View File

@ -658,7 +658,7 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
// TODO: Implement method of revocation by authorizations on account. // TODO: Implement method of revocation by authorizations on account.
if !(core.KeyDigestEquals(requestKey, parsedCertificate.PublicKey) || if !(core.KeyDigestEquals(requestKey, parsedCertificate.PublicKey) ||
registration.ID == cert.RegistrationID) { registration.ID == cert.RegistrationID) {
logEvent.Error = "Revocation request must be signed by private key of cert to be revoked" logEvent.Error = "Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it."
wfe.log.Debug("Key mismatch for revoke") wfe.log.Debug("Key mismatch for revoke")
wfe.sendError(response, wfe.sendError(response,
logEvent.Error, logEvent.Error,

View File

@ -574,8 +574,18 @@ func TestIssueCertificate(t *testing.T) {
test.AssertNotError(t, err, "Problem setting up HTTP handlers") test.AssertNotError(t, err, "Problem setting up HTTP handlers")
mockLog := wfe.log.SyslogWriter.(*mocks.MockSyslogWriter) mockLog := wfe.log.SyslogWriter.(*mocks.MockSyslogWriter)
// TODO: Use a mock RA so we can test various conditions of authorized, not authorized, etc. // The mock CA we use always returns the same test certificate, with a Not
ra := ra.NewRegistrationAuthorityImpl(clock.NewFake(), wfe.log) // Before of 2015-09-22. Since we're currently using a real RA instead of a
// mock (see below), that date would trigger failures for excessive
// backdating. So we set the fakeClock's time to a time that matches that test
// certificate.
fakeClock := clock.NewFake()
testTime := time.Date(2015, 9, 9, 22, 56, 0, 0, time.UTC)
fakeClock.Add(fakeClock.Now().Sub(testTime))
// TODO: Use a mock RA so we can test various conditions of authorized, not
// authorized, etc.
ra := ra.NewRegistrationAuthorityImpl(fakeClock, wfe.log)
ra.SA = &MockSA{} ra.SA = &MockSA{}
ra.CA = &MockCA{} ra.CA = &MockCA{}
ra.PA = &MockPA{} ra.PA = &MockPA{}
@ -866,8 +876,32 @@ func TestNewRegistration(t *testing.T) {
test.AssertEquals(t, responseWriter.Code, 409) test.AssertEquals(t, responseWriter.Code, 409)
} }
// Valid revocation request for existing, non-revoked cert func makeRevokeRequestJSON() ([]byte, error) {
func TestRevokeCertificate(t *testing.T) { certPemBytes, err := ioutil.ReadFile("test/238.crt")
if err != nil {
return nil, err
}
certBlock, _ := pem.Decode(certPemBytes)
if err != nil {
return nil, err
}
revokeRequest := struct {
Resource string `json:"resource"`
CertificateDER core.JSONBuffer `json:"certificate"`
}{
Resource: "revoke-cert",
CertificateDER: certBlock.Bytes,
}
revokeRequestJSON, err := json.Marshal(revokeRequest)
if err != nil {
return nil, err
}
return revokeRequestJSON, nil
}
// Valid revocation request for existing, non-revoked cert, signed with cert
// key.
func TestRevokeCertificateCertKey(t *testing.T) {
keyPemBytes, err := ioutil.ReadFile("test/238.key") keyPemBytes, err := ioutil.ReadFile("test/238.key")
test.AssertNotError(t, err, "Failed to load key") test.AssertNotError(t, err, "Failed to load key")
key, err := jose.LoadPrivateKey(keyPemBytes) key, err := jose.LoadPrivateKey(keyPemBytes)
@ -877,29 +911,12 @@ func TestRevokeCertificate(t *testing.T) {
signer, err := jose.NewSigner("RS256", rsaKey) signer, err := jose.NewSigner("RS256", rsaKey)
test.AssertNotError(t, err, "Failed to make signer") test.AssertNotError(t, err, "Failed to make signer")
certPemBytes, err := ioutil.ReadFile("test/238.crt") revokeRequestJSON, err := makeRevokeRequestJSON()
test.AssertNotError(t, err, "Failed to load cert") test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
certBlock, _ := pem.Decode(certPemBytes)
test.Assert(t, certBlock != nil, "Failed to decode PEM")
revokeRequest := struct {
Resource string `json:"resource"`
CertificateDER core.JSONBuffer `json:"certificate"`
}{
Resource: "revoke-cert",
CertificateDER: certBlock.Bytes,
}
revokeRequestJSON, err := json.Marshal(revokeRequest)
test.AssertNotError(t, err, "Failed to marshal request")
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
wfe := setupWFE(t) wfe := setupWFE(t)
wfe.RA = &MockRegistrationAuthority{}
wfe.SA = &MockSA{}
wfe.Stats, _ = statsd.NewNoopClient()
wfe.SubscriberAgreementURL = agreementURL
responseWriter := httptest.NewRecorder() responseWriter := httptest.NewRecorder()
responseWriter.Body.Reset()
nonce, err := wfe.nonceService.Nonce() nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce") test.AssertNotError(t, err, "Unable to create nonce")
result, _ := signer.Sign(revokeRequestJSON, nonce) result, _ := signer.Sign(revokeRequestJSON, nonce)
@ -907,25 +924,57 @@ func TestRevokeCertificate(t *testing.T) {
makePostRequest(result.FullSerialize())) makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 200) test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Body.String(), "") test.AssertEquals(t, responseWriter.Body.String(), "")
}
// Valid revocation request for existing, non-revoked cert, signed with account
// key.
func TestRevokeCertificateAccountKey(t *testing.T) {
revokeRequestJSON, err := makeRevokeRequestJSON()
test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
wfe := setupWFE(t)
responseWriter := httptest.NewRecorder()
// Try the revoke request again, signed by account key associated with cert.
// Should also succeed.
responseWriter.Body.Reset()
test1JWK, err := jose.LoadPrivateKey([]byte(test1KeyPrivatePEM)) test1JWK, err := jose.LoadPrivateKey([]byte(test1KeyPrivatePEM))
test.AssertNotError(t, err, "Failed to load key") test.AssertNotError(t, err, "Failed to load key")
test1Key, ok := test1JWK.(*rsa.PrivateKey) test1Key, ok := test1JWK.(*rsa.PrivateKey)
test.Assert(t, ok, "Couldn't load RSA key") test.Assert(t, ok, "Couldn't load RSA key")
accountKeySigner, err := jose.NewSigner("RS256", test1Key) accountKeySigner, err := jose.NewSigner("RS256", test1Key)
test.AssertNotError(t, err, "Failed to make signer") test.AssertNotError(t, err, "Failed to make signer")
nonce, err = wfe.nonceService.Nonce() nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce") test.AssertNotError(t, err, "Unable to create nonce")
result, _ = accountKeySigner.Sign(revokeRequestJSON, nonce) result, _ := accountKeySigner.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter, wfe.RevokeCertificate(responseWriter,
makePostRequest(result.FullSerialize())) makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 200) test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Body.String(), "") test.AssertEquals(t, responseWriter.Body.String(), "")
} }
// A revocation request signed by an unauthorized key.
func TestRevokeCertificateWrongKey(t *testing.T) {
wfe := setupWFE(t)
nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce")
responseWriter := httptest.NewRecorder()
test2JWK, err := jose.LoadPrivateKey([]byte(test2KeyPrivatePEM))
test.AssertNotError(t, err, "Failed to load key")
test2Key, ok := test2JWK.(*rsa.PrivateKey)
test.Assert(t, ok, "Couldn't load RSA key")
accountKeySigner2, err := jose.NewSigner("RS256", test2Key)
test.AssertNotError(t, err, "Failed to make signer")
nonce, err = wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce")
revokeRequestJSON, err := makeRevokeRequestJSON()
test.AssertNotError(t, err, "Unable to create revoke request")
result, _ := accountKeySigner2.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter,
makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 403)
test.AssertEquals(t, responseWriter.Body.String(),
`{"type":"urn:acme:error:unauthorized","detail":"Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it."}`)
}
// Valid revocation request for already-revoked cert // Valid revocation request for already-revoked cert
func TestRevokeCertificateAlreadyRevoked(t *testing.T) { func TestRevokeCertificateAlreadyRevoked(t *testing.T) {
keyPemBytes, err := ioutil.ReadFile("test/178.key") keyPemBytes, err := ioutil.ReadFile("test/178.key")