Merge branch 'master' into challenge-head

This commit is contained in:
Tom Clegg 2015-10-09 19:54:45 -07:00
commit 22ef139419
32 changed files with 594 additions and 70 deletions

View File

@ -1,7 +1,7 @@
language: go
go:
- 1.5
- 1.5.1
addons:
hosts:
@ -56,3 +56,4 @@ env:
script:
- bash test.sh

20
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,20 @@
# Contributing to Boulder
> **Note:** We are currently in a *General Availability* only merge window, meaning
> we will only be reviewing & merging patches which close a issue tagged with the *General
> Availability* milestone.
Thanks for helping us build Boulder, if you haven't already had a chance to look
over our patch submission guidelines take a minute to do so now.
* [Patch requirements](https://github.com/letsencrypt/boulder/wiki/Boulder-Development#patch-requirements)
* [Review requirements](https://github.com/letsencrypt/boulder/wiki/Boulder-Development#review-requirements)
* [Patch guidelines](https://github.com/letsencrypt/boulder/wiki/Boulder-Development#patch-guidelines)
* [Deployability](https://github.com/letsencrypt/boulder/wiki/Boulder-Development#deployability)
* [Good zero values for config fields](https://github.com/letsencrypt/boulder/wiki/Boulder-Development#good-zero-values-for-config-fields)
* [Flag-gated RPCs](https://github.com/letsencrypt/boulder/wiki/Boulder-Development#flag-gated-rpcs)
* [Dependencies](https://github.com/letsencrypt/boulder/wiki/Boulder-Development#dependencies)
## Problems or questions?
The best place to ask dev related questions is either the [discussion list](https://groups.google.com/a/letsencrypt.org/forum/#!forum/ca-dev) or [IRC](https://webchat.freenode.net/?channels=#letsencrypt).

View File

@ -16,6 +16,7 @@ import (
"fmt"
"io/ioutil"
"math/big"
"strings"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
@ -223,7 +224,8 @@ func (ca *CertificateAuthorityImpl) RevokeCertificate(serial string, reasonCode
}
// IssueCertificate attempts to convert a CSR into a signed Certificate, while
// enforcing all policies.
// enforcing all policies. Names (domains) in the CertificateRequest will be
// lowercased before storage.
func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest, regID int64) (core.Certificate, error) {
emptyCert := core.Certificate{}
var err error
@ -253,10 +255,10 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
hostNames := make([]string, len(csr.DNSNames))
copy(hostNames, csr.DNSNames)
if len(csr.Subject.CommonName) > 0 {
commonName = csr.Subject.CommonName
hostNames = append(hostNames, csr.Subject.CommonName)
commonName = strings.ToLower(csr.Subject.CommonName)
hostNames = append(hostNames, commonName)
} else if len(hostNames) > 0 {
commonName = hostNames[0]
commonName = strings.ToLower(hostNames[0])
} else {
err = core.MalformedRequestError("Cannot issue a certificate without a hostname.")
// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
@ -265,7 +267,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
}
// Collapse any duplicate names. Note that this operation may re-order the names
hostNames = core.UniqueNames(hostNames)
hostNames = core.UniqueLowerNames(hostNames)
if ca.MaxNames > 0 && len(hostNames) > ca.MaxNames {
err = core.MalformedRequestError(fmt.Sprintf("Certificate request has %d > %d names", len(hostNames), ca.MaxNames))
ca.log.WarningErr(err)

View File

@ -11,6 +11,7 @@ import (
"encoding/asn1"
"fmt"
"io/ioutil"
"sort"
"testing"
"time"
@ -64,7 +65,7 @@ var (
DupeNameCSR = mustRead("./testdata/dupe_name.der.csr")
// CSR generated by Go:
// * Random pulic key
// * Random public key
// * CN = [none]
// * DNSNames = not-example.com, www.not-example.com, mail.example.com
TooManyNameCSR = mustRead("./testdata/too_many_names.der.csr")
@ -81,7 +82,14 @@ var (
// * DNSNames = not-example.com, www.not-example.com, mail.not-example.com
// * Signature algorithm: SHA1WithRSA
BadAlgorithmCSR = mustRead("./testdata/bad_algorithm.der.csr")
log = mocks.UseMockLog()
// CSR generated by Go:
// * Random public key
// * CN = CapiTalizedLetters.com
// * DNSNames = moreCAPs.com, morecaps.com, evenMOREcaps.com, Capitalizedletters.COM
CapitalizedCSR = mustRead("./testdata/capitalized_cn_and_san.der.csr")
log = mocks.UseMockLog()
)
// CFSSL config
@ -421,3 +429,24 @@ func TestRejectBadAlgorithm(t *testing.T) {
_, ok := err.(core.MalformedRequestError)
test.Assert(t, ok, "Incorrect error type returned")
}
func TestCapitalizedLetters(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ctx.caConfig.MaxNames = 3
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
csr, _ := x509.ParseCertificateRequest(CapitalizedCSR)
cert, err := ca.IssueCertificate(*csr, ctx.reg.ID)
test.AssertNotError(t, err, "Failed to gracefully handle a CSR with capitalized names")
parsedCert, err := x509.ParseCertificate(cert.DER)
test.AssertNotError(t, err, "Error parsing certificate produced by CA")
test.AssertEquals(t, "capitalizedletters.com", parsedCert.Subject.CommonName)
sort.Strings(parsedCert.DNSNames)
expected := []string{"capitalizedletters.com", "evenmorecaps.com", "morecaps.com"}
test.AssertDeepEquals(t, expected, parsedCert.DNSNames)
}

Binary file not shown.

69
ca/testdata/testcsr.go vendored Normal file
View File

@ -0,0 +1,69 @@
// Hack up the x509.CertificateRequest in here, run `go run testcsr.go`, and a
// DER-encoded CertificateRequest will be printed to stdout.
package main
import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"os"
)
// A 2048-bit RSA private key
var pemPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA5cpXqfCaUDD+hf93j5jxbrhK4jrJAzfAEjeZj/Lx5Rv/7eEO
uhS2DdCU2is82vR6yJ7EidUYVz/nUAjSTP7JIEsbyvfsfACABbqRyGltHlJnULVH
y/EMjt9xKZf17T8tOLHVUEAJTxsvjKn4TMIQJTNrAqm/lNrUXmCIR41Go+3RBGC6
YdAKEwcZMCzrjQGF06mC6/6xMmYMSMd6+VQRFIPpuPK/6BBp1Tgju2LleRC5uatj
QcFOoilGkfh1RnZp3GJ7q58KaqHiPmjl31rkY5vS3LP7yfU5TRBcxCSG8l8LKuRt
MArkbTEtj3PkDjbipL/SkLrZ28e5w9Egl4g1MwIDAQABAoIBABZqY5zPPK5f6SQ3
JHmciMitL5jb9SncMV9VjyRMpa4cyh1xW9dpF81HMI4Ls7cELEoPuspbQDGaqTzU
b3dVT1dYHFDzWF1MSzDD3162cg+IKE3mMSfCzt/NCiPtj+7hv86NAmr+pCnUVBIb
rn4GXD7UwjaTSn4Bzr+aGREpxd9Nr0JdNQwxVHZ75A92vTihCfaXyMCjhW3JEpF9
N89XehgidoGgtUxxeeb+WsO3nvVBpLv/HDxMTx/IDzvSA5nLlYMcqVzb7IJoeAQu
og0WJKlniYzvIdoQ6/hGydAW5sKd0qWh0JPYs7uLKAWrdAWvrFAp7//fYKVamalU
8pUu/WkCgYEA+tcTQ3qTnVh41O9YeM/7NULpIkuCAlR+PBRky294zho9nGQIPdaW
VNvyqqjLaHaXJVokYHbU4hDk6RbrhoWVd4Po/5g9cUkT1f6nrdZGRkg4XOCzHWvV
Yrqh3eYYX4bdiH5EhB78m0rrbjHfd7SF3cdYNzOUS2kJvCInYC6zPx8CgYEA6oRr
UhZFuoqRsEb28ELM8sHvdIMA/C3aWCu+nUGQ4gHSEb4uvuOD/7tQNuCaBioiXVPM
/4hjk9jHJcjYf5l33ANqIP7JiYAt4rzTWXF3iS6kQOhQhjksSlSnWqw0Uu1DtlpG
rzeG1ZkBuwH7Bx0yj4sGSz5sAvyF44aRsE6AC20CgYEArafWO0ISDb1hMbFdo44B
ELd45Pg3UluiZP+NZFWQ4cbC3pFWL1FvE+KNll5zK6fmLcLBKlM6QCOIBmKKvb+f
YXVeCg0ghFweMmkxNqUAU8nN02bwOa8ctFQWmaOhPgkFN2iLEJjPMsdkRA6c8ad1
gbtvNBAuWyKlzawrbGgISesCgYBkGEjGLINubx5noqJbQee/5U6S6CdPezKqV2Fw
NT/ldul2cTn6d5krWYOPKKYU437vXokst8XooKm/Us41CAfEfCCcHKNgcLklAXsj
ve5LOwEYQw+7ekORJjiX1tAuZN51wmpQ9t4x5LB8ZQgDrU6bPbdd/jKTw7xRtGoS
Wi8EsQKBgG8iGy3+kVBIjKHxrN5jVs3vj/l/fQL0WRMLCMmVuDBfsKyy3f9n8R1B
/KdwoyQFwsLOyr5vAjiDgpFurXQbVyH4GDFiJGS1gb6MNcinwSTpsbOLLV7zgibX
A2NgiQ+UeWMia16dZVd6gGDlY3lQpeyLdsdDd+YppNfy9vedjbvT
-----END RSA PRIVATE KEY-----`
func main() {
block, _ := pem.Decode([]byte(pemPrivateKey))
rsaPriv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
log.Fatalf("Failed to parse private key: %s", err)
}
req := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: "CapiTalizedLetters.com",
},
DNSNames: []string{
"moreCAPs.com",
"morecaps.com",
"evenMOREcaps.com",
"Capitalizedletters.COM",
},
}
csr, err := x509.CreateCertificateRequest(rand.Reader, req, rsaPriv)
if err != nil {
log.Fatalf("unable to create CSR: %s", err)
}
_, err = os.Stdout.Write(csr)
if err != nil {
log.Fatalf("unable to write to stdout: %s", err)
}
}

View File

@ -46,7 +46,8 @@ func main() {
rateLimitPolicies, err := cmd.LoadRateLimitPolicies(c.RA.RateLimitPoliciesFilename)
cmd.FailOnError(err, "Couldn't load rate limit policies file")
rai := ra.NewRegistrationAuthorityImpl(clock.Default(), auditlogger, stats, rateLimitPolicies)
rai := ra.NewRegistrationAuthorityImpl(clock.Default(), auditlogger, stats,
rateLimitPolicies, c.RA.MaxContactsPerRegistration)
rai.PA = pa
raDNSTimeout, err := time.ParseDuration(c.Common.DNSTimeout)
cmd.FailOnError(err, "Couldn't parse RA DNS timeout")

View File

@ -205,9 +205,7 @@ func TestGetAndProcessCerts(t *testing.T) {
BasicConstraintsValid: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
}
reg, err := sa.NewRegistration(core.Registration{
Key: satest.GoodJWK(),
})
reg := satest.CreateWorkingRegistration(t, sa)
test.AssertNotError(t, err, "Couldn't create registration")
for i := int64(0); i < 5; i++ {
rawCert.SerialNumber = big.NewInt(i)

View File

@ -14,6 +14,7 @@ import (
"encoding/json"
"fmt"
"math/big"
"net"
"testing"
"text/template"
"time"
@ -171,14 +172,16 @@ func TestFindExpiringCertificates(t *testing.T) {
Contact: []*core.AcmeURL{
emailA,
},
Key: keyA,
Key: keyA,
InitialIP: net.ParseIP("2.3.2.3"),
}
regB := core.Registration{
ID: 2,
Contact: []*core.AcmeURL{
emailB,
},
Key: keyB,
Key: keyB,
InitialIP: net.ParseIP("2.3.2.3"),
}
regA, err = ctx.ssa.NewRegistration(regA)
if err != nil {
@ -298,7 +301,8 @@ func TestLifetimeOfACert(t *testing.T) {
Contact: []*core.AcmeURL{
emailA,
},
Key: keyA,
Key: keyA,
InitialIP: net.ParseIP("1.2.2.1"),
}
regA, err = ctx.ssa.NewRegistration(regA)
if err != nil {
@ -401,7 +405,8 @@ func TestDontFindRevokedCert(t *testing.T) {
Contact: []*core.AcmeURL{
emailA,
},
Key: keyA,
Key: keyA,
InitialIP: net.ParseIP("6.5.5.6"),
}
regA, err = ctx.ssa.NewRegistration(regA)
if err != nil {

View File

@ -435,6 +435,7 @@ func main() {
go updater.newCertificatesLoop.loop()
go updater.oldOCSPResponsesLoop.loop()
go updater.missingSCTReceiptsLoop.loop()
cmd.FailOnError(err, "Failed to create updater")

View File

@ -17,6 +17,10 @@ type RateLimitConfig struct {
// These are counted by "base domain" aka eTLD+1, so any entries in the
// overrides section must be an eTLD+1 according to the publicsuffix package.
CertificatesPerName RateLimitPolicy `yaml:"certificatesPerName"`
// Number of registrations that can be created per IP.
// Note: Since this is checked before a registration is created, setting a
// RegistrationOverride on it has no effect.
RegistrationsPerIP RateLimitPolicy `yaml:"registrationsPerIP"`
}
// RateLimitPolicy describes a general limiting policy

View File

@ -99,6 +99,8 @@ type Config struct {
MaxConcurrentRPCServerRequests int64
MaxContactsPerRegistration int
// DebugAddr is the address to run the /debug handlers on.
DebugAddr string
}

View File

@ -112,6 +112,7 @@ type StorageGetter interface {
AlreadyDeniedCSR([]string) (bool, error)
CountCertificatesRange(time.Time, time.Time) (int64, error)
CountCertificatesByNames([]string, time.Time, time.Time) (map[string]int, error)
CountRegistrationsByIP(net.IP, time.Time, time.Time) (int, error)
GetSCTReceipt(string, string) (SignedCertificateTimestamp, error)
}

View File

@ -168,6 +168,12 @@ type Registration struct {
// Agreement with terms of service
Agreement string `json:"agreement,omitempty"`
// InitialIP is the IP address from which the registration was created
InitialIP net.IP `json:"initialIp"`
// CreatedAt is the time the registration was created.
CreatedAt time.Time `json:"createdAt"`
}
// MergeUpdate copies a subset of information from the input Registration
@ -741,6 +747,13 @@ type SignedCertificateTimestamp struct {
LockCol int64
}
// RPCSignedCertificateTimestamp is a wrapper around SignedCertificateTimestamp
// so that it can be passed through the RPC layer properly. Without this wrapper
// the UnmarshalJSON method below will be used when marshaling/unmarshaling the
// object, which is not what we want as it is not symmetrical (as it is intended
// to unmarshal a rawSignedCertificateTimestamp into a SignedCertificateTimestamp)
type RPCSignedCertificateTimestamp SignedCertificateTimestamp
type rawSignedCertificateTimestamp struct {
Version uint8 `json:"sct_version"`
LogID string `json:"id"`

View File

@ -472,11 +472,12 @@ func GetBuildHost() (retID string) {
return
}
// UniqueNames returns the set of all unique names in the input.
func UniqueNames(names []string) (unique []string) {
// UniqueLowerNames returns the set of all unique names in the input after all
// of them are lowercased. The returned names will be in their lowercased form.
func UniqueLowerNames(names []string) (unique []string) {
nameMap := make(map[string]int, len(names))
for _, name := range names {
nameMap[name] = 1
nameMap[strings.ToLower(name)] = 1
}
unique = make([]string, 0, len(nameMap))

View File

@ -11,6 +11,7 @@ import (
"math"
"math/big"
"net/url"
"sort"
"testing"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
@ -113,3 +114,9 @@ func TestAcmeURL(t *testing.T) {
a := (*AcmeURL)(u)
test.AssertEquals(t, s, a.String())
}
func TestUniqueLowerNames(t *testing.T) {
u := UniqueLowerNames([]string{"foobar.com", "fooBAR.com", "baz.com", "foobar.com", "bar.com", "bar.com"})
sort.Strings(u)
test.AssertDeepEquals(t, []string{"bar.com", "baz.com", "foobar.com"}, u)
}

View File

@ -163,7 +163,13 @@ func (sa *StorageAuthority) GetRegistration(id int64) (core.Registration, error)
var parsedKey jose.JsonWebKey
parsedKey.UnmarshalJSON(keyJSON)
return core.Registration{ID: id, Key: parsedKey, Agreement: agreementURL}, nil
return core.Registration{
ID: id,
Key: parsedKey,
Agreement: agreementURL,
InitialIP: net.ParseIP("5.6.7.8"),
CreatedAt: time.Date(2003, 9, 27, 0, 0, 0, 0, time.UTC),
}, nil
}
// GetRegistrationByKey is a mock
@ -325,6 +331,11 @@ func (sa *StorageAuthority) CountCertificatesByNames(_ []string, _, _ time.Time)
return
}
// CountRegistrationsByIP is a mock
func (sa *StorageAuthority) CountRegistrationsByIP(_ net.IP, _, _ time.Time) (int, error) {
return 0, nil
}
// Publisher is a mock
type Publisher struct {
// empty

View File

@ -64,7 +64,7 @@ const (
whitelistedPartnerRegID = -1
)
var dnsLabelRegexp = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9-]{0,62}$")
var dnsLabelRegexp = regexp.MustCompile("^[a-z0-9][a-z0-9-]{0,62}$")
var punycodeRegexp = regexp.MustCompile("^xn--")
func isDNSCharacter(ch byte) bool {
@ -112,7 +112,8 @@ func (e SyntaxError) Error() string { return "Syntax error" }
func (e NonPublicError) Error() string { return "Name does not end in a public suffix" }
// WillingToIssue determines whether the CA is willing to issue for the provided
// identifier.
// identifier. It expects domains in id to be lowercase to prevent mismatched
// cases breaking queries.
//
// We place several criteria on identifiers we are willing to issue for:
//
@ -132,8 +133,6 @@ func (e NonPublicError) Error() string { return "Name does not end in a
// XXX: Is there any need for this method to be constant-time? We're
// going to refuse to issue anyway, but timing could leak whether
// names are on the blacklist.
//
// XXX: We should probably fold everything to lower-case somehow.
func (pa PolicyAuthorityImpl) WillingToIssue(id core.AcmeIdentifier, regID int64) error {
if id.Type != core.IdentifierDNS {
return InvalidIdentifierError{}
@ -146,7 +145,6 @@ func (pa PolicyAuthorityImpl) WillingToIssue(id core.AcmeIdentifier, regID int64
}
}
domain = strings.ToLower(domain)
if len(domain) > 255 {
return SyntaxError{}
}

View File

@ -86,6 +86,9 @@ func TestWillingToIssue(t *testing.T) {
`zombocom`,
`localhost`,
`mail`,
// disallow capitalized letters for #927
`CapitalizedLetters.com`,
}
shouldBeNonPublic := []string{

View File

@ -9,6 +9,7 @@ import (
"crypto/x509"
"errors"
"fmt"
"net"
"net/mail"
"reflect"
"sort"
@ -50,10 +51,11 @@ type RegistrationAuthorityImpl struct {
tiMu *sync.RWMutex
totalIssuedCache int
lastIssuedCount *time.Time
maxContactsPerReg int
}
// NewRegistrationAuthorityImpl constructs a new RA object.
func NewRegistrationAuthorityImpl(clk clock.Clock, logger *blog.AuditLogger, stats statsd.Statter, policies cmd.RateLimitConfig) RegistrationAuthorityImpl {
func NewRegistrationAuthorityImpl(clk clock.Clock, logger *blog.AuditLogger, stats statsd.Statter, policies cmd.RateLimitConfig, maxContactsPerReg int) RegistrationAuthorityImpl {
ra := RegistrationAuthorityImpl{
stats: stats,
clk: clk,
@ -61,6 +63,7 @@ func NewRegistrationAuthorityImpl(clk clock.Clock, logger *blog.AuditLogger, sta
authorizationLifetime: DefaultAuthorizationLifetime,
rlPolicies: policies,
tiMu: new(sync.RWMutex),
maxContactsPerReg: maxContactsPerReg,
}
return ra
}
@ -138,17 +141,44 @@ func (ra *RegistrationAuthorityImpl) setIssuanceCount() (int, error) {
return ra.totalIssuedCache, nil
}
// noRegistrationID is used for the regID parameter to GetThreshold when no
// registration-based overrides are necessary.
const noRegistrationID = -1
func (ra *RegistrationAuthorityImpl) checkRegistrationLimit(ip net.IP) error {
limit := ra.rlPolicies.RegistrationsPerIP
if limit.Enabled() {
now := ra.clk.Now()
count, err := ra.SA.CountRegistrationsByIP(ip, limit.WindowBegin(now), now)
if err != nil {
return err
}
if count >= limit.GetThreshold(ip.String(), noRegistrationID) {
return core.RateLimitedError("Too many registrations from this IP")
}
}
return nil
}
// NewRegistration constructs a new Registration from a request.
func (ra *RegistrationAuthorityImpl) NewRegistration(init core.Registration) (reg core.Registration, err error) {
if err = core.GoodKey(init.Key.Key); err != nil {
return core.Registration{}, core.MalformedRequestError(fmt.Sprintf("Invalid public key: %s", err.Error()))
}
if err = ra.checkRegistrationLimit(init.InitialIP); err != nil {
return core.Registration{}, err
}
reg = core.Registration{
Key: init.Key,
}
reg.MergeUpdate(init)
err = validateContacts(reg.Contact, ra.DNSResolver, ra.stats)
// This field isn't updatable by the end user, so it isn't copied by
// MergeUpdate. But we need to fill it in for new registrations.
reg.InitialIP = init.InitialIP
err = ra.validateContacts(reg.Contact)
if err != nil {
return
}
@ -165,15 +195,20 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(init core.Registration) (re
return
}
func validateContacts(contacts []*core.AcmeURL, resolver core.DNSResolver, stats statsd.Statter) (err error) {
func (ra *RegistrationAuthorityImpl) validateContacts(contacts []*core.AcmeURL) (err error) {
if ra.maxContactsPerReg > 0 && len(contacts) > ra.maxContactsPerReg {
return core.MalformedRequestError(fmt.Sprintf("Too many contacts provided: %d > %d",
len(contacts), ra.maxContactsPerReg))
}
for _, contact := range contacts {
switch contact.Scheme {
case "tel":
continue
case "mailto":
rtt, err := validateEmail(contact.Opaque, resolver)
stats.TimingDuration("RA.DNS.RTT.MX", rtt, 1.0)
stats.Inc("RA.DNS.Rate", 1, 1.0)
rtt, err := validateEmail(contact.Opaque, ra.DNSResolver)
ra.stats.TimingDuration("RA.DNS.RTT.MX", rtt, 1.0)
ra.stats.Inc("RA.DNS.Rate", 1, 1.0)
if err != nil {
return err
}
@ -186,7 +221,8 @@ func validateContacts(contacts []*core.AcmeURL, resolver core.DNSResolver, stats
return
}
// NewAuthorization constuct a new Authz from a request.
// NewAuthorization constuct a new Authz from a request. Values (domains) in
// request.Identifier will be lowercased before storage.
func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization, regID int64) (authz core.Authorization, err error) {
reg, err := ra.SA.GetRegistration(regID)
if err != nil {
@ -195,6 +231,7 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
}
identifier := request.Identifier
identifier.Value = strings.ToLower(identifier.Value)
// Check that the identifier is present and appropriate
if err = ra.PA.WillingToIssue(identifier, regID); err != nil {
@ -275,7 +312,7 @@ func (ra *RegistrationAuthorityImpl) MatchesCSR(
if len(csr.Subject.CommonName) > 0 {
hostNames = append(hostNames, csr.Subject.CommonName)
}
hostNames = core.UniqueNames(hostNames)
hostNames = core.UniqueLowerNames(hostNames)
if !core.KeyDigestEquals(parsedCertificate.PublicKey, csr.PublicKey) {
err = core.InternalServerError("Generated certificate public key doesn't match CSR public key")
@ -546,7 +583,7 @@ func (ra *RegistrationAuthorityImpl) checkLimits(names []string, regID int64) er
func (ra *RegistrationAuthorityImpl) UpdateRegistration(base core.Registration, update core.Registration) (reg core.Registration, err error) {
base.MergeUpdate(update)
err = validateContacts(base.Contact, ra.DNSResolver, ra.stats)
err = ra.validateContacts(base.Contact)
if err != nil {
return
}

View File

@ -13,6 +13,7 @@ import (
"encoding/json"
"encoding/pem"
"fmt"
"net"
"net/url"
"testing"
"time"
@ -212,7 +213,10 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
csrDER, _ := hex.DecodeString(CSRhex)
ExampleCSR, _ = x509.ParseCertificateRequest(csrDER)
Registration, _ = ssa.NewRegistration(core.Registration{Key: AccountKeyA})
Registration, _ = ssa.NewRegistration(core.Registration{
Key: AccountKeyA,
InitialIP: net.ParseIP("3.2.3.3"),
})
stats, _ := statsd.NewNoopClient()
ra := NewRegistrationAuthorityImpl(fc, blog.GetAuditLogger(), stats, cmd.RateLimitConfig{
@ -220,7 +224,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
Threshold: 100,
Window: cmd.ConfigDuration{Duration: 24 * 90 * time.Hour},
},
})
}, 1)
ra.SA = ssa
ra.VA = va
ra.CA = &ca
@ -255,30 +259,34 @@ func assertAuthzEqual(t *testing.T, a1, a2 core.Authorization) {
}
func TestValidateContacts(t *testing.T) {
_, _, ra, _, cleanUp := initAuthorities(t)
defer cleanUp()
tel, _ := core.ParseAcmeURL("tel:")
ansible, _ := core.ParseAcmeURL("ansible:earth.sol.milkyway.laniakea/letsencrypt")
validEmail, _ := core.ParseAcmeURL("mailto:admin@email.com")
invalidEmail, _ := core.ParseAcmeURL("mailto:admin@example.com")
malformedEmail, _ := core.ParseAcmeURL("mailto:admin.com")
nStats, _ := statsd.NewNoopClient()
err := validateContacts([]*core.AcmeURL{}, &mocks.DNSResolver{}, nStats)
err := ra.validateContacts([]*core.AcmeURL{})
test.AssertNotError(t, err, "No Contacts")
err = validateContacts([]*core.AcmeURL{tel}, &mocks.DNSResolver{}, nStats)
err = ra.validateContacts([]*core.AcmeURL{tel, validEmail})
test.AssertError(t, err, "Too Many Contacts")
err = ra.validateContacts([]*core.AcmeURL{tel})
test.AssertNotError(t, err, "Simple Telephone")
err = validateContacts([]*core.AcmeURL{validEmail}, &mocks.DNSResolver{}, nStats)
err = ra.validateContacts([]*core.AcmeURL{validEmail})
test.AssertNotError(t, err, "Valid Email")
err = validateContacts([]*core.AcmeURL{invalidEmail}, &mocks.DNSResolver{}, nStats)
err = ra.validateContacts([]*core.AcmeURL{invalidEmail})
test.AssertError(t, err, "Invalid Email")
err = validateContacts([]*core.AcmeURL{malformedEmail}, &mocks.DNSResolver{}, nStats)
err = ra.validateContacts([]*core.AcmeURL{malformedEmail})
test.AssertError(t, err, "Malformed Email")
err = validateContacts([]*core.AcmeURL{ansible}, &mocks.DNSResolver{}, nStats)
err = ra.validateContacts([]*core.AcmeURL{ansible})
test.AssertError(t, err, "Unknown scehme")
}
@ -303,8 +311,9 @@ func TestNewRegistration(t *testing.T) {
defer cleanUp()
mailto, _ := core.ParseAcmeURL("mailto:foo@letsencrypt.org")
input := core.Registration{
Contact: []*core.AcmeURL{mailto},
Key: AccountKeyB,
Contact: []*core.AcmeURL{mailto},
Key: AccountKeyB,
InitialIP: net.ParseIP("7.6.6.5"),
}
result, err := ra.NewRegistration(input)
@ -332,6 +341,7 @@ func TestNewRegistrationNoFieldOverwrite(t *testing.T) {
Key: AccountKeyC,
Contact: []*core.AcmeURL{mailto},
Agreement: "I agreed",
InitialIP: net.ParseIP("5.0.5.0"),
}
result, err := ra.NewRegistration(input)
@ -403,6 +413,25 @@ func TestNewAuthorization(t *testing.T) {
t.Log("DONE TestNewAuthorization")
}
func TestNewAuthorizationCapitalLetters(t *testing.T) {
_, sa, ra, _, cleanUp := initAuthorities(t)
defer cleanUp()
authzReq := core.Authorization{
Identifier: core.AcmeIdentifier{
Type: core.IdentifierDNS,
Value: "NOT-example.COM",
},
}
authz, err := ra.NewAuthorization(authzReq, Registration.ID)
test.AssertNotError(t, err, "NewAuthorization failed")
test.AssertEquals(t, "not-example.com", authz.Identifier.Value)
dbAuthz, err := sa.GetAuthorization(authz.ID)
test.AssertNotError(t, err, "Could not fetch authorization from database")
assertAuthzEqual(t, authz, dbAuthz)
}
func TestUpdateAuthorization(t *testing.T) {
va, sa, ra, _, cleanUp := initAuthorities(t)
defer cleanUp()

View File

@ -10,6 +10,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"time"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
@ -62,6 +63,7 @@ const (
MethodAlreadyDeniedCSR = "AlreadyDeniedCSR" // SA
MethodCountCertificatesRange = "CountCertificatesRange" // SA
MethodCountCertificatesByNames = "CountCertificatesByNames" // SA
MethodCountRegistrationsByIP = "CountRegistrationsByIP" // SA
MethodGetSCTReceipt = "GetSCTReceipt" // SA
MethodAddSCTReceipt = "AddSCTReceipt" // SA
MethodSubmitToCT = "SubmitToCT" // Pub
@ -151,6 +153,12 @@ type countCertificatesByNamesRequest struct {
Latest time.Time
}
type countRegistrationsByIPRequest struct {
IP net.IP
Earliest time.Time
Latest time.Time
}
// Response structs
type caaResponse struct {
Present bool
@ -1042,6 +1050,20 @@ func NewStorageAuthorityServer(rpc Server, impl core.StorageAuthority) error {
return json.Marshal(counts)
})
rpc.Handle(MethodCountRegistrationsByIP, func(req []byte) (response []byte, err error) {
var cReq countRegistrationsByIPRequest
err = json.Unmarshal(req, &cReq)
if err != nil {
return
}
count, err := impl.CountRegistrationsByIP(cReq.IP, cReq.Earliest, cReq.Latest)
if err != nil {
return
}
return json.Marshal(count)
})
rpc.Handle(MethodGetSCTReceipt, func(req []byte) (response []byte, err error) {
var gsctReq struct {
Serial string
@ -1056,7 +1078,7 @@ func NewStorageAuthorityServer(rpc Server, impl core.StorageAuthority) error {
}
sct, err := impl.GetSCTReceipt(gsctReq.Serial, gsctReq.LogID)
jsonResponse, err := json.Marshal(core.SignedCertificateTimestamp(sct))
jsonResponse, err := json.Marshal(core.RPCSignedCertificateTimestamp(sct))
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetSCTReceipt, err, req)
@ -1067,7 +1089,7 @@ func NewStorageAuthorityServer(rpc Server, impl core.StorageAuthority) error {
})
rpc.Handle(MethodAddSCTReceipt, func(req []byte) (response []byte, err error) {
var sct core.SignedCertificateTimestamp
var sct core.RPCSignedCertificateTimestamp
err = json.Unmarshal(req, &sct)
if err != nil {
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
@ -1370,6 +1392,23 @@ func (cac StorageAuthorityClient) CountCertificatesByNames(names []string, earli
return
}
// CountRegistrationsByIP calls CountRegistrationsByIP on the remote
// StorageAuthority.
func (cac StorageAuthorityClient) CountRegistrationsByIP(ip net.IP, earliest, latest time.Time) (count int, err error) {
var cReq countRegistrationsByIPRequest
cReq.IP, cReq.Earliest, cReq.Latest = ip, earliest, latest
data, err := json.Marshal(cReq)
if err != nil {
return
}
response, err := cac.rpc.DispatchSync(MethodCountRegistrationsByIP, data)
if err != nil {
return
}
err = json.Unmarshal(response, &count)
return
}
// GetSCTReceipt retrieves an SCT according to the serial number of a certificate
// and the logID of the log to which it was submitted.
func (cac StorageAuthorityClient) GetSCTReceipt(serial string, logID string) (receipt core.SignedCertificateTimestamp, err error) {

View File

@ -0,0 +1,14 @@
-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
ALTER TABLE `registrations` ADD COLUMN (
`initialIP` BINARY(16) NOT NULL DEFAULT "",
`createdAt` DATETIME NOT NULL
);
CREATE INDEX `initialIP_createdAt` on `registrations` (`initialIP`, `createdAt`);
-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back
DROP INDEX `initialIP_createdAt` on `registrations`;

59
sa/ip_range_test.go Normal file
View File

@ -0,0 +1,59 @@
// Copyright 2014 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package sa
import (
"net"
"testing"
)
func TestIncrementIP(t *testing.T) {
testCases := []struct {
ip string
index int
expected string
}{
{"0.0.0.0", 128, "0.0.0.1"},
{"0.0.0.255", 128, "0.0.1.0"},
{"127.0.0.1", 128, "127.0.0.2"},
{"1.2.3.4", 120, "1.2.4.4"},
{"::1", 128, "::2"},
{"2002:1001:4008::", 128, "2002:1001:4008::1"},
{"2002:1001:4008::", 48, "2002:1001:4009::"},
{"2002:1001:ffff::", 48, "2002:1002::"},
{"ffff:ffff:ffff::", 48, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"},
}
for _, tc := range testCases {
ip := net.ParseIP(tc.ip).To16()
actual := incrementIP(ip, tc.index)
expectedIP := net.ParseIP(tc.expected)
if !actual.Equal(expectedIP) {
t.Errorf("Expected incrementIP(%s, %d) to be %s, instead got %s",
tc.ip, tc.index, expectedIP, actual.String())
}
}
}
func TestIPRange(t *testing.T) {
testCases := []struct {
ip string
expectedBegin string
expectedEnd string
}{
{"28.45.45.28", "28.45.45.28", "28.45.45.29"},
{"2002:1001:4008::", "2002:1001:4008::", "2002:1001:4009::"},
}
for _, tc := range testCases {
ip := net.ParseIP(tc.ip)
expectedBegin := net.ParseIP(tc.expectedBegin)
expectedEnd := net.ParseIP(tc.expectedEnd)
actualBegin, actualEnd := ipRange(ip)
if !expectedBegin.Equal(actualBegin) || !expectedEnd.Equal(actualEnd) {
t.Errorf("Expected ipRange(%s) to be (%s, %s), got (%s, %s)",
tc.ip, tc.expectedBegin, tc.expectedEnd, actualBegin, actualEnd)
}
}
}

View File

@ -9,6 +9,7 @@ import (
"encoding/json"
"fmt"
"math"
"net"
"time"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
@ -31,6 +32,10 @@ type regModel struct {
KeySHA256 string `db:"jwk_sha256"`
Contact []*core.AcmeURL `db:"contact"`
Agreement string `db:"agreement"`
// InitialIP is stored as sixteen binary bytes, regardless of whether it
// represents a v4 or v6 IP address.
InitialIP []byte `db:"initialIp"`
CreatedAt time.Time `db:"createdAt"`
LockCol int64
}
@ -65,12 +70,17 @@ func registrationToModel(r *core.Registration) (*regModel, error) {
if err != nil {
return nil, err
}
if r.InitialIP == nil {
return nil, fmt.Errorf("initialIP was nil")
}
rm := &regModel{
ID: r.ID,
Key: key,
KeySHA256: sha,
Contact: r.Contact,
Agreement: r.Agreement,
InitialIP: []byte(r.InitialIP.To16()),
CreatedAt: r.CreatedAt,
}
return rm, nil
}
@ -87,6 +97,8 @@ func modelToRegistration(rm *regModel) (core.Registration, error) {
Key: *k,
Contact: rm.Contact,
Agreement: rm.Agreement,
InitialIP: net.IP(rm.InitialIP),
CreatedAt: rm.CreatedAt,
}
return r, nil
}

View File

@ -2,7 +2,9 @@ package satest
import (
"encoding/json"
"net"
"testing"
"time"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
"github.com/letsencrypt/boulder/core"
@ -42,8 +44,10 @@ func CreateWorkingRegistration(t *testing.T, sa core.StorageAuthority) core.Regi
}
contacts := []*core.AcmeURL{contact}
reg, err := sa.NewRegistration(core.Registration{
Key: GoodJWK(),
Contact: contacts,
Key: GoodJWK(),
Contact: contacts,
InitialIP: net.ParseIP("88.77.66.11"),
CreatedAt: time.Date(2003, 5, 10, 0, 0, 0, 0, time.UTC),
})
if err != nil {
t.Fatalf("Unable to create new registration")

View File

@ -12,6 +12,8 @@ import (
"encoding/json"
"errors"
"fmt"
"math/big"
"net"
"sort"
"strings"
"time"
@ -229,6 +231,80 @@ func (ssa *SQLStorageAuthority) GetLatestValidAuthorization(registrationID int64
return ssa.GetAuthorization(auth.ID)
}
// incrementIP returns a copy of `ip` incremented at a bit index `index`,
// or in other words the first IP of the next highest subnet given a mask of
// length `index`.
// In order to easily account for overflow, we treat ip as a big.Int and add to
// it. If the increment overflows the max size of a net.IP, return the highest
// possible net.IP.
func incrementIP(ip net.IP, index int) net.IP {
bigInt := new(big.Int)
bigInt.SetBytes([]byte(ip))
incr := new(big.Int).Lsh(big.NewInt(1), 128-uint(index))
bigInt.Add(bigInt, incr)
// bigInt.Bytes can be shorter than 16 bytes, so stick it into a
// full-sized net.IP.
resultBytes := bigInt.Bytes()
if len(resultBytes) > 16 {
return net.ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
}
result := make(net.IP, 16)
copy(result[16-len(resultBytes):], resultBytes)
return result
}
// ipRange returns a range of IP addresses suitable for querying MySQL for the
// purpose of rate limiting using a range that is inclusive on the lower end and
// exclusive at the higher end. If ip is an IPv4 address, it returns that address,
// plus the one immediately higher than it. If ip is an IPv6 address, it applies
// a /48 mask to it and returns the lowest IP in the resulting network, and the
// first IP outside of the resulting network.
func ipRange(ip net.IP) (net.IP, net.IP) {
ip = ip.To16()
// For IPv6, match on a certain subnet range, since one person can commonly
// have an entire /48 to themselves.
maskLength := 48
// For IPv4 addresses, do a match on exact address, so begin = ip and end =
// next higher IP.
if ip.To4() != nil {
maskLength = 128
}
mask := net.CIDRMask(maskLength, 128)
begin := ip.Mask(mask)
end := incrementIP(begin, maskLength)
return begin, end
}
// CountRegistrationsByIP returns the number of registrations created in the
// time range in an IP range. For IPv4 addresses, that range is limited to the
// single IP. For IPv6 addresses, that range is a /48, since it's not uncommon
// for one person to have a /48 to themselves.
func (ssa *SQLStorageAuthority) CountRegistrationsByIP(ip net.IP, earliest time.Time, latest time.Time) (int, error) {
var count int64
beginIP, endIP := ipRange(ip)
err := ssa.dbMap.SelectOne(
&count,
`SELECT COUNT(1) FROM registrations
WHERE
:beginIP <= initialIP AND
initialIP < :endIP AND
:earliest < createdAt AND
createdAt <= :latest`,
map[string]interface{}{
"ip": ip.String(),
"earliest": earliest,
"latest": latest,
"beginIP": []byte(beginIP),
"endIP": []byte(endIP),
})
if err != nil {
return -1, err
}
return int(count), nil
}
// TooManyCertificatesError indicates that the number of certificates returned by
// CountCertificates exceeded the hard-coded limit of 10,000 certificates.
type TooManyCertificatesError string
@ -344,6 +420,7 @@ func (ssa *SQLStorageAuthority) NewRegistration(reg core.Registration) (core.Reg
if err != nil {
return reg, err
}
rm.CreatedAt = ssa.clk.Now()
err = ssa.dbMap.Insert(rm)
if err != nil {
return reg, err
@ -446,17 +523,32 @@ func (ssa *SQLStorageAuthority) MarkCertificateRevoked(serial string, ocspRespon
// UpdateRegistration stores an updated Registration
func (ssa *SQLStorageAuthority) UpdateRegistration(reg core.Registration) error {
rm, err := registrationToModel(&reg)
lookupResult, err := ssa.dbMap.Get(regModel{}, reg.ID)
if err != nil {
return err
}
if lookupResult == nil {
msg := fmt.Sprintf("No registrations with ID %d", reg.ID)
return core.NoSuchRegistrationError(msg)
}
existingRegModel, ok := lookupResult.(*regModel)
if !ok {
// Shouldn't happen
return fmt.Errorf("Incorrect type returned from registration lookup")
}
n, err := ssa.dbMap.Update(rm)
updatedRegModel, err := registrationToModel(&reg)
if err != nil {
return err
}
updatedRegModel.LockCol = existingRegModel.LockCol
n, err := ssa.dbMap.Update(updatedRegModel)
if err != nil {
return err
}
if n == 0 {
msg := fmt.Sprintf("Requested registration not found %v", reg.ID)
msg := fmt.Sprintf("Requested registration not found %d", reg.ID)
return core.NoSuchRegistrationError(msg)
}

View File

@ -15,6 +15,9 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"math/big"
"net"
"net/url"
"testing"
"time"
@ -41,7 +44,7 @@ func initSA(t *testing.T) (*SQLStorageAuthority, clock.FakeClock, func()) {
dbMap.TraceOn("SQL: ", &SQLLogger{log})
fc := clock.NewFake()
fc.Add(1 * time.Hour)
fc.Set(time.Date(2015, 3, 4, 5, 0, 0, 0, time.UTC))
sa, err := NewSQLStorageAuthority(dbMap, fc)
if err != nil {
@ -60,7 +63,7 @@ var (
)
func TestAddRegistration(t *testing.T) {
sa, _, cleanUp := initSA(t)
sa, clk, cleanUp := initSA(t)
defer cleanUp()
jwk := satest.GoodJWK()
@ -71,8 +74,9 @@ func TestAddRegistration(t *testing.T) {
}
contacts := []*core.AcmeURL{contact}
reg, err := sa.NewRegistration(core.Registration{
Key: jwk,
Contact: contacts,
Key: jwk,
Contact: contacts,
InitialIP: net.ParseIP("43.34.43.34"),
})
if err != nil {
t.Fatalf("Couldn't create new registration: %s", err)
@ -87,15 +91,23 @@ func TestAddRegistration(t *testing.T) {
test.AssertNotError(t, err, fmt.Sprintf("Couldn't get registration with ID %v", reg.ID))
expectedReg := core.Registration{
ID: reg.ID,
Key: jwk,
ID: reg.ID,
Key: jwk,
InitialIP: net.ParseIP("43.34.43.34"),
CreatedAt: clk.Now(),
}
test.AssertEquals(t, dbReg.ID, expectedReg.ID)
test.Assert(t, core.KeyDigestEquals(dbReg.Key, expectedReg.Key), "Stored key != expected")
u, _ := core.ParseAcmeURL("test.com")
newReg := core.Registration{ID: reg.ID, Key: jwk, Contact: []*core.AcmeURL{u}, Agreement: "yes"}
newReg := core.Registration{
ID: reg.ID,
Key: jwk,
Contact: []*core.AcmeURL{u},
InitialIP: net.ParseIP("72.72.72.72"),
Agreement: "yes",
}
err = sa.UpdateRegistration(newReg)
test.AssertNotError(t, err, fmt.Sprintf("Couldn't get registration with ID %v", reg.ID))
dbReg, err = sa.GetRegistrationByKey(jwk)
@ -582,3 +594,48 @@ func TestCountCertificates(t *testing.T) {
test.AssertNotError(t, err, "Couldn't get certificate count for the last 24hrs")
test.AssertEquals(t, count, int64(0))
}
func TestCountRegistrationsByIP(t *testing.T) {
sa, fc, cleanUp := initSA(t)
defer cleanUp()
contact := core.AcmeURL(url.URL{
Scheme: "mailto",
Opaque: "foo@example.com",
})
_, err := sa.NewRegistration(core.Registration{
Key: jose.JsonWebKey{Key: &rsa.PublicKey{N: big.NewInt(1), E: 1}},
Contact: []*core.AcmeURL{&contact},
InitialIP: net.ParseIP("43.34.43.34"),
})
test.AssertNotError(t, err, "Couldn't insert registration")
_, err = sa.NewRegistration(core.Registration{
Key: jose.JsonWebKey{Key: &rsa.PublicKey{N: big.NewInt(2), E: 1}},
Contact: []*core.AcmeURL{&contact},
InitialIP: net.ParseIP("2001:cdba:1234:5678:9101:1121:3257:9652"),
})
test.AssertNotError(t, err, "Couldn't insert registration")
_, err = sa.NewRegistration(core.Registration{
Key: jose.JsonWebKey{Key: &rsa.PublicKey{N: big.NewInt(3), E: 1}},
Contact: []*core.AcmeURL{&contact},
InitialIP: net.ParseIP("2001:cdba:1234:5678:9101:1121:3257:9653"),
})
test.AssertNotError(t, err, "Couldn't insert registration")
earliest := fc.Now().Add(-time.Hour * 24)
latest := fc.Now()
count, err := sa.CountRegistrationsByIP(net.ParseIP("1.1.1.1"), earliest, latest)
test.AssertNotError(t, err, "Failed to count registrations")
test.AssertEquals(t, count, 0)
count, err = sa.CountRegistrationsByIP(net.ParseIP("43.34.43.34"), earliest, latest)
test.AssertNotError(t, err, "Failed to count registrations")
test.AssertEquals(t, count, 1)
count, err = sa.CountRegistrationsByIP(net.ParseIP("2001:cdba:1234:5678:9101:1121:3257:9652"), earliest, latest)
test.AssertNotError(t, err, "Failed to count registrations")
test.AssertEquals(t, count, 2)
count, err = sa.CountRegistrationsByIP(net.ParseIP("2001:cdba:1234:0000:0000:0000:0000:0000"), earliest, latest)
test.AssertNotError(t, err, "Failed to count registrations")
test.AssertEquals(t, count, 2)
}

View File

@ -121,6 +121,7 @@
"ra": {
"rateLimitPoliciesFilename": "test/rate-limit-policies.yml",
"maxConcurrentRPCServerRequests": 16,
"maxContactsPerRegistration": 100,
"debugAddr": "localhost:8002"
},
@ -189,6 +190,7 @@
},
"publisher": {
"maxConcurrentRPCServerRequests": 16,
"debugAddr": "localhost:8009"
},
@ -208,8 +210,7 @@
"submissionRetries": 1,
"submissionBackoff": "1s",
"intermediateBundleFilename": "test/test-ca.pem"
},
"maxConcurrentRPCServerRequests": 16
}
},
"certChecker": {

View File

@ -15,3 +15,8 @@ certificatesPerName:
nginx.wtf: 10000
registrationOverrides:
101: 1000
registrationsPerIP:
window: 168h # 1 week
threshold: 3
overrides:
127.0.0.1: 1000000

View File

@ -11,6 +11,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"regexp"
"strconv"
@ -547,6 +548,17 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
return
}
init.Key = *key
init.InitialIP = net.ParseIP(request.Header.Get("X-Real-IP"))
if init.InitialIP == nil {
host, _, err := net.SplitHostPort(request.RemoteAddr)
if err == nil {
init.InitialIP = net.ParseIP(host)
} else {
logEvent.Error = "Couldn't parse RemoteAddr"
wfe.sendError(response, logEvent.Error, nil, http.StatusInternalServerError)
return
}
}
reg, err := wfe.RA.NewRegistration(init)
if err != nil {

View File

@ -225,7 +225,7 @@ func setupWFE(t *testing.T) WebFrontEndImpl {
func makePostRequest(body string) *http.Request {
return &http.Request{
Method: "POST",
RemoteAddr: "1.1.1.1",
RemoteAddr: "1.1.1.1:7882",
Header: map[string][]string{
"Content-Length": []string{fmt.Sprintf("%d", len(body))},
},
@ -547,7 +547,7 @@ func TestIssueCertificate(t *testing.T) {
// TODO: Use a mock RA so we can test various conditions of authorized, not
// authorized, etc.
stats, _ := statsd.NewNoopClient(nil)
ra := ra.NewRegistrationAuthorityImpl(fakeClock, wfe.log, stats, cmd.RateLimitConfig{})
ra := ra.NewRegistrationAuthorityImpl(fakeClock, wfe.log, stats, cmd.RateLimitConfig{}, 0)
ra.SA = &mocks.StorageAuthority{}
ra.CA = &MockCA{}
ra.PA = &MockPA{}
@ -845,12 +845,13 @@ func TestNewRegistration(t *testing.T) {
wfe.NewRegistration(responseWriter,
makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Body.String(), `{"id":0,"key":{"kty":"RSA","n":"qnARLrT7Xz4gRcKyLdydmCr-ey9OuPImX4X40thk3on26FkMznR3fRjs66eLK7mmPcBZ6uOJseURU6wAaZNmemoYx1dMvqvWWIyiQleHSD7Q8vBrhR6uIoO4jAzJZR-ChzZuSDt7iHN-3xUVspu5XGwXU_MVJZshTwp4TaFx5elHIT_ObnTvTOU3Xhish07AbgZKmWsVbXh5s-CrIicU4OexJPgunWZ_YJJueOKmTvnLlTV4MzKR2oZlBKZ27S0-SfdV_QDx_ydle5oMAyKVtlAV35cyPMIsYNwgUGBCdY_2Uzi5eX0lTc7MPRwz6qR1kip-i59VcGcUQgqHV6Fyqw","e":"AQAB"},"contact":["tel:123456789"],"agreement":"http://example.invalid/terms"}`)
var reg core.Registration
err = json.Unmarshal([]byte(responseWriter.Body.String()), &reg)
test.AssertNotError(t, err, "Couldn't unmarshal returned registration object")
test.Assert(t, len(reg.Contact) >= 1, "No contact field in registration")
test.AssertEquals(t, reg.Contact[0].String(), "tel:123456789")
test.AssertEquals(t, reg.Agreement, "http://example.invalid/terms")
test.AssertEquals(t, reg.InitialIP.String(), "1.1.1.1")
test.AssertEquals(
t, responseWriter.Header().Get("Location"),
@ -1353,11 +1354,7 @@ func TestLogCsrPem(t *testing.T) {
wfe.logCsr(req, certificateRequest, reg)
matches := mockLog.GetAllMatching("Certificate request")
test.Assert(t, len(matches) == 1,
"Incorrect number of certificate request log entries")
test.AssertEquals(t, matches[0].Priority, syslog.LOG_NOTICE)
test.AssertEquals(t, matches[0].Message, `[AUDIT] Certificate request JSON={"ClientAddr":"10.0.0.1,172.16.0.1,12.34.98.76","CsrBase64":"MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAycX3ca+fViOuRWF38mssORISFxbJvspDfhPGRBZDxJ63NIqQzupB+6dp48xkcX7Z/KDaRJStcpJT2S0u33moNT4FHLklQBETLhExDk66cmlz6Xibp3LGZAwhWuec7wJoEwIgY8oq4rxihIyGq7HVIJoq9DqZGrUgfZMDeEJqbphukQOaXGEop7mD+eeu8+z5EVkB1LiJ6Yej6R8MAhVPHzG5fyOu6YVo6vY6QgwjRLfZHNj5XthxgPIEETZlUbiSoI6J19GYHvLURBTy5Ys54lYAPIGfNwcIBAH4gtH9FrYcDY68R22rp4iuxdvkf03ZWiT0F2W1y7/C9B2jayTzvQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAHd6Do9DIZ2hvdt1GwBXYjsqprZidT/DYOMfYcK17KlvdkFT58XrBH88ulLZ72NXEpiFMeTyzfs3XEyGq/Bbe7TBGVYZabUEh+LOskYwhgcOuThVN7tHnH5rhN+gb7cEdysjTb1QL+vOUwYgV75CB6PE5JVYK+cQsMIVvo0Kz4TpNgjJnWzbcH7h0mtvub+fCv92vBPjvYq8gUDLNrok6rbg05tdOJkXsF2G/W+Q6sf2Fvx0bK5JeH4an7P7cXF9VG9nd4sRt5zd+L3IcyvHVKxNhIJXZVH0AOqh/1YrKI9R0QKQiZCEy0xN1okPlcaIVaFhb7IKAHPxTI3r5f72LXY=","Registration":{"id":789,"key":{"kty":"RSA","n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ","e":"AQAB"},"agreement":"http://example.invalid/terms"}}`)
assertCsrLogged(t, mockLog)
}
func TestLengthRequired(t *testing.T) {