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
# 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
RUN ./bootstrap/debian.sh && \
apt-get clean && \
@ -43,20 +43,13 @@ RUN ./bootstrap/debian.sh && \
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
RUN go get bitbucket.org/liamstask/goose/cmd/goose
ENV LETSENCRYPT_PATH /letsencrypt
# Copy in the Boulder sources
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
CMD ["bash", "-c", "service mysql 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)
[![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.
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
Slow start
----------
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
install RabbitMQ from https://rabbitmq.com/download.html to get a
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:
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.)
```
> go get bitbucket.org/liamstask/goose/cmd/goose
> go get github.com/letsencrypt/boulder/ # Ignore errors about no buildable files
> cd $GOPATH/src/github.com/letsencrypt/boulder
> ./test/create_db.sh
# This starts each Boulder component with test configs. Ctrl-C kills all.
> ./start.py
# Run tests
> ./test.sh
```
The databases that boulder requires to operate in development and
testing can be created using test/create\_db.sh. It uses the root
MariaDB user, so if you have disabled that account you may have to
adjust the file or recreate the commands.
Note: create\_db.sh it uses the root MariaDB user, so if you
have disabled that account you may have to adjust the file or
recreate the commands.
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.
Component Model
@ -130,7 +114,7 @@ and to [avoid insecure fallback in go
get](https://github.com/golang/go/issues/9637).
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.
To update the Go dependencies:

View File

@ -12,6 +12,7 @@ import (
"io/ioutil"
"os"
"path"
"reflect"
"runtime"
"sync"
"sync/atomic"
@ -194,7 +195,7 @@ func (c *certChecker) checkCert(cert core.Certificate) (problems []string) {
}
}
// 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")
}
}

View File

@ -45,9 +45,9 @@ var (
// DNSResolverImpl represents a client that talks to an external resolver
type DNSResolverImpl struct {
DNSClient *dns.Client
Servers []string
allowLoopbackAddresses bool
DNSClient *dns.Client
Servers []string
allowRestrictedAddresses bool
}
// NewDNSResolverImpl constructs a new DNS resolver object that utilizes the
@ -59,9 +59,9 @@ func NewDNSResolverImpl(dialTimeout time.Duration, servers []string) *DNSResolve
dnsClient.DialTimeout = dialTimeout
return &DNSResolverImpl{
DNSClient: dnsClient,
Servers: servers,
allowLoopbackAddresses: false,
DNSClient: dnsClient,
Servers: servers,
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).
func NewTestDNSResolverImpl(dialTimeout time.Duration, servers []string) *DNSResolverImpl {
resolver := NewDNSResolverImpl(dialTimeout, servers)
resolver.allowLoopbackAddresses = true
resolver.allowRestrictedAddresses = true
return resolver
}
@ -120,8 +120,8 @@ func (dnsResolver *DNSResolverImpl) LookupTXT(hostname string) ([]string, time.D
return txt, rtt, err
}
func isPrivateV4(ip net.IP, allowLoopback bool) bool {
return rfc1918_10.Contains(ip) || rfc1918_172_16.Contains(ip) || rfc1918_192_168.Contains(ip) || (!allowLoopback && rfc5735_127.Contains(ip))
func isPrivateV4(ip net.IP) bool {
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
@ -141,7 +141,7 @@ func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.
for _, answer := range r.Answer {
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)
}
}

View File

@ -11,7 +11,6 @@ import (
"encoding/json"
"fmt"
"net"
"sort"
"strings"
"time"
@ -101,52 +100,6 @@ func (pd *ProblemDetails) Error() string {
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
// be validated by ACME. The protocol allows for different
// 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
}
// 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
// latest data about the status of the certificate, required for OCSP updating
// and for validating that the subscriber has accepted the certificate.

View File

@ -11,6 +11,8 @@ import (
"errors"
"fmt"
"net/mail"
"reflect"
"sort"
"strings"
"time"
@ -203,6 +205,89 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
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.
func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest, regID int64) (cert core.Certificate, err error) {
emptyCert := core.Certificate{}
@ -306,7 +391,7 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
return emptyCert, err
}
err = cert.MatchesCSR(csr, earliestExpiry)
err = ra.MatchesCSR(cert, csr, earliestExpiry)
if err != nil {
logEvent.Error = err.Error()
return emptyCert, err

View File

@ -8,6 +8,7 @@ package main
import (
"fmt"
"net"
"os"
"time"
"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.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 {
fmt.Printf("dns-srv: Query -- [%s] %s\n", q.Name, dns.TypeToString[q.Qtype])
switch q.Qtype {
@ -30,7 +39,7 @@ func dnsHandler(w dns.ResponseWriter, r *dns.Msg) {
Class: dns.ClassINET,
Ttl: 0,
}
record.A = net.ParseIP("127.0.0.1")
record.A = net.ParseIP(fakeDNS)
m.Answer = append(m.Answer, record)
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-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCaqzue57mgXEoG
TZZoVkkCZraebWgXI8irX2BgQB1A3iZa9onxGPMcWQMxhSuUisbEJi4UkMcVST12
HX01rUwhj41UuBxJvI1w4wvdstssTAaa9c9tsQ5+UED2bFRL1MsyBdbmCF/+pu3i
+ZIYqWgiKbjVBe3nlAVbo77zizwp3Y4Tp1/TBOwTAuFkHePmkNT63uPm9My/hNzs
Sm1o+Q519Cf7ry+JQmOVgz/jIgFVGFYJ17EV3KUIpUuDShuyCFATBQspgJSN2DoX
RUlQjXXkNTj23OxxdT/cVLcLJjytyG6e5izME2R2aCkDBWIc1a4/sRJ0R396auPX
G6KhJ7o/AgMBAAECggEAC7GkmVgVzc0Mf7uAArV7YaYYapQFCbLX6jUU2VIfpBbn
uXroZQUo5FzKhAT4jYuMiaoFU+K6Wp6l+fcyz0sh9WugGOaupNiPrRhNfl6WeZvp
5+9r1nRLjztMHhWErhMRpd+RJuU9NMi0NbP+2sR8LhEPe3OuUBL98LbJqio9y0Bp
8+JxmATL7vTvORMMamt80bDRGLBzoGySWZ0lEWwTuv8RjiCqO85YmHd9BmnTpOUn
siYfEDZ9t+EF6NgJaISMLmcbzxZI5+aPIvfc1TwzDA96LUccGwvAR4qZ/+eYsG2I
Iwn1wMMSsvFWeq7AccKL/povPg1Iud0wzkLLZmMjGQKBgQDLRUtENxbVH/T4xw8W
tdgfzddQWZmVPtQXHAt2/zmrgJ3DKu7HOUajCGGWdLnoiFrIO2LsQYpP1XUA4hG1
vzLDBng6SHDqZdGfd8eFaHesafr/FG4zkk+GBIs+P8GzHs2/znpEiqLnPbWrrB3t
2/zBIXluZSytGt1ysC+TZZFwhQKBgQDCymmryD0JRV0M1IgwXOZrO+2MWDdliujx
uBfD6DdLCJ5wHrPiySjVvl0njPLDQcWigigJD8Jl0SRQ8JSbceFzEGjzEj3NR2oB
aLn3Gf3nORo7tJdCZVQn7QcHT+HzAWffm6qjqKzPXjRAEFFcW48Bnw+BU/JuZNOe
v7dTuyv88wKBgFROGAphwsF/8I0hmht0Lf/60ltL3gvtM++lvQeMkTGVNVlVvBS6
p5ZEipzpKpXLv8MeBkgwYpn70PwdxvSXKQmD7GdX1iURN6CpAAJPspq6ldQneBFB
lGPkDJAzxzVwCCuOCl3VFf1MNcXOq9cUDz9Wj9N+eMoOw1umwQSj8m81AoGAP/li
gzycbzMMwG383IVmV8my1ukSKJNatiiUBY96uXX3MzOiONWAR9LhnV+5S0+KrTi6
FV/LpMzvdHXPGM5qEPROw6Y2DflqY1QV34X10b77UqiZFQFahlJegJRHzRulFdd2
T5HST7jMyE2TqxWW/h1TZlI/yOnsZrLobuOGKukCgYAGk/TRPSOWEkH3rL7cDJFp
ql4d33iuW9b3dMvQgkDW8H2kj3QA5os9dtSFLA/fWubULXOPpOsA7Ny7okBxsUBW
tn4ER+HjUHogGir5d5cBBpvi4xM2/B4KaZnX8IHYhFcT42eb1oYmpfz5GcRyqzTX
OoetYnUS5t4QuAAMadjtig==
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD3tBSSz5+lz4if
t0imYGEps8RJyuNZ/aCOcE+tOb1nRXbu3DYxmY+6/BWl7P/bbWS5dKiMGUELiaIV
qwnqXmoqiMkPLXcVyXClMloThoNked+vmXzDbwRFp0GWzrCl4q4UWk2wAn70p/XF
tDxW5lJx6XNw9EcX3RvpqqZ46jsXY2MGDRD2xQLzZFUxS+nfLOrk5akan50iGqIv
tbzesGySO5xTFrwg6sQIJCzkVIwCaCeIeS2ffFe7+MtPDUlXXZNvQ0WfG7iabPJZ
pvJuNqPtVCCxSav4137b8ymn3F5UaPcYBjosZqK0btDykOkfLfiBHE1V/0xSGQ/U
su55fGKPAgMBAAECggEBAJq6qKdUfptq2iM6sG0Ng2QzYQffbslF/0c6rOq+SKv7
3mEyub+d9wSMd1FgjCqbWPHoB2kMVW9JmDAE2XrHY3YSX9XVRrAbVvw6Sb4spmLb
JnlgXWkPLhH8eqIjloDo6mjYR/MMfBWD3aiT/6Ug1ptFgtEqJkan3cnGyZp6ZlMi
jZQOoPyxaSVtYiyKZo7Mng02fwbkjiCQpTKCKk+dUnDQHmPI7kV69/jvqzJzCPwf
IWXzfg9vhTuLjiGHNTfVqiEevSkmrEerVVIaC9/UNACkyd2jW+Hhl5lFRVRQBnIc
8DmSVQZrFu08LNLlZOBy+kga/JTWv1s8Gb4WXkN70FECgYEA/3bs3hkUDF9/KnKC
ljceKH05/G8cU84riqwt8lepjwu0Zzq+OVm58CMwljPRi2nuBGsFirFsmKMbAxM7
QjGzcmCp6QEfm0rSKMs68BnmVyTbVoov4l9CNeWiGtRxSHvKXOAcKEMw032cKmeE
f6NbVXzsaGglaDAjqFBPiQjoVMUCgYEA+Dj9n6QiAy3jF+XqlnjszF+2gPmutBDR
bWpLvcJsa2aeuZq4Uw/42d+GneSDYY2O2e51WYwWJU7QvVvKrosDc8vrmq0YDt0S
DsWknVnUImFJVgM/ssepQvDoheR03GB/EiRjSzl9duopi/9I2I6L2Bz0eHWdoe9I
59W7NCbWl0MCgYA3jPnG9fcZFa8GYO4qkgO51DlGvjaPtrZbKkO3ff59/5KUG/7/
Y5Z+NHi9QLlhMoCybz8+QISX0GfwTD9Hjp0x0vk/lKH119hJTKAdU9R1wKr9b4Eo
HYS9SZjtcqLLmzeO6KDW1H2Kj7bktavnQXN77HVP4s23kTg/wm75Inm67QKBgQCc
7KqqukEmcWQYP6pG5rwNmJTwhOkFvXwpCAJKPRf97ip6fG37VSWl18JrH4RPsAaX
kEFwEzgM7f7ZN6azIf9UvZ59cuC6xNgdrqWbNKb/9TE/x14F0GQZheP6gau/huHO
vmJntDi1bORHNWUAQ131ipijbvfw4C3fIyfumH8hsQKBgQDggOcVSKmMilITeEls
Ui4K4dQ2feMyCv07LBe2zvrL4EP48dQw40DJKFj8HYPpqmFtjESjYnPw+9YUid5N
fnssdGmnScfHUhZMoXIhmiEFrsAMDllwftqESi7+QRIie4Owftdpb/8VEyBH7xga
MvhT37SxXefJCluspTw7n+am8g==
-----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.
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"
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.sendError(response,
logEvent.Error,

View File

@ -574,8 +574,18 @@ func TestIssueCertificate(t *testing.T) {
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
mockLog := wfe.log.SyslogWriter.(*mocks.MockSyslogWriter)
// TODO: Use a mock RA so we can test various conditions of authorized, not authorized, etc.
ra := ra.NewRegistrationAuthorityImpl(clock.NewFake(), wfe.log)
// The mock CA we use always returns the same test certificate, with a Not
// 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.CA = &MockCA{}
ra.PA = &MockPA{}
@ -866,8 +876,32 @@ func TestNewRegistration(t *testing.T) {
test.AssertEquals(t, responseWriter.Code, 409)
}
// Valid revocation request for existing, non-revoked cert
func TestRevokeCertificate(t *testing.T) {
func makeRevokeRequestJSON() ([]byte, error) {
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")
test.AssertNotError(t, err, "Failed to load key")
key, err := jose.LoadPrivateKey(keyPemBytes)
@ -877,29 +911,12 @@ func TestRevokeCertificate(t *testing.T) {
signer, err := jose.NewSigner("RS256", rsaKey)
test.AssertNotError(t, err, "Failed to make signer")
certPemBytes, err := ioutil.ReadFile("test/238.crt")
test.AssertNotError(t, err, "Failed to load cert")
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")
revokeRequestJSON, err := makeRevokeRequestJSON()
test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
wfe := setupWFE(t)
wfe.RA = &MockRegistrationAuthority{}
wfe.SA = &MockSA{}
wfe.Stats, _ = statsd.NewNoopClient()
wfe.SubscriberAgreementURL = agreementURL
responseWriter := httptest.NewRecorder()
responseWriter.Body.Reset()
nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce")
result, _ := signer.Sign(revokeRequestJSON, nonce)
@ -907,25 +924,57 @@ func TestRevokeCertificate(t *testing.T) {
makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 200)
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))
test.AssertNotError(t, err, "Failed to load key")
test1Key, ok := test1JWK.(*rsa.PrivateKey)
test.Assert(t, ok, "Couldn't load RSA key")
accountKeySigner, err := jose.NewSigner("RS256", test1Key)
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")
result, _ = accountKeySigner.Sign(revokeRequestJSON, nonce)
result, _ := accountKeySigner.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter,
makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 200)
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
func TestRevokeCertificateAlreadyRevoked(t *testing.T) {
keyPemBytes, err := ioutil.ReadFile("test/178.key")