Merge master
This commit is contained in:
commit
5ea17d980a
|
|
@ -42,12 +42,7 @@ func TestChallenges(t *testing.T) {
|
|||
|
||||
var testCertificateRequestBadCSR = []byte(`{"csr":"AAAA"}`)
|
||||
var testCertificateRequestGood = []byte(`{
|
||||
"csr": "MIHRMHgCAQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQWUlnRrm5ErSVkTzBTk3isg1hNydfyY4NM1P_N1S-ZeD39HMrYJsQkUh2tKvy3ztfmEqWpekvO4WRktSa000BPoAAwCgYIKoZIzj0EAwMDSQAwRgIhAIZIBwu4xOUD_4dJuGgceSKaoXTFBQKA3BFBNVJvbpdsAiEAlfq3Dq_8dnYbtmyDdXgopeKkSV5_76VSpcog-wkwEwo",
|
||||
"authorizations": [
|
||||
"https://example.com/authz/1",
|
||||
"https://example.com/authz/2",
|
||||
"https://example.com/authz/3"
|
||||
]
|
||||
"csr": "MIHRMHgCAQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQWUlnRrm5ErSVkTzBTk3isg1hNydfyY4NM1P_N1S-ZeD39HMrYJsQkUh2tKvy3ztfmEqWpekvO4WRktSa000BPoAAwCgYIKoZIzj0EAwMDSQAwRgIhAIZIBwu4xOUD_4dJuGgceSKaoXTFBQKA3BFBNVJvbpdsAiEAlfq3Dq_8dnYbtmyDdXgopeKkSV5_76VSpcog-wkwEwo"
|
||||
}`)
|
||||
|
||||
func TestCertificateRequest(t *testing.T) {
|
||||
|
|
@ -61,9 +56,6 @@ func TestCertificateRequest(t *testing.T) {
|
|||
if err = VerifyCSR(goodCR.CSR); err != nil {
|
||||
t.Errorf("Valid CSR in CertificateRequest failed to verify: %v", err)
|
||||
}
|
||||
if len(goodCR.Authorizations) == 0 {
|
||||
t.Errorf("Certificate request parsing failed to parse authorizations")
|
||||
}
|
||||
|
||||
// Bad CSR
|
||||
var badCR CertificateRequest
|
||||
|
|
|
|||
36
core/dns.go
36
core/dns.go
|
|
@ -122,13 +122,18 @@ func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.
|
|||
return addrs, aRtt, aaaaRtt, nil
|
||||
}
|
||||
|
||||
// LookupCNAME sends a DNS query to find a CNAME record associated hostname and returns the
|
||||
// record target.
|
||||
// LookupCNAME returns the target name if a CNAME record exists for
|
||||
// the given domain name. If the CNAME does not exist (NXDOMAIN,
|
||||
// NXRRSET, or a successful response with no CNAME records), it
|
||||
// returns the empty string and a nil error.
|
||||
func (dnsResolver *DNSResolverImpl) LookupCNAME(hostname string) (string, time.Duration, error) {
|
||||
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeCNAME)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
if r.Rcode == dns.RcodeNXRrset || r.Rcode == dns.RcodeNameError {
|
||||
return "", rtt, nil
|
||||
}
|
||||
if r.Rcode != dns.RcodeSuccess {
|
||||
err = fmt.Errorf("DNS failure: %d-%s for CNAME query", r.Rcode, dns.RcodeToString[r.Rcode])
|
||||
return "", rtt, err
|
||||
|
|
@ -143,9 +148,32 @@ func (dnsResolver *DNSResolverImpl) LookupCNAME(hostname string) (string, time.D
|
|||
return "", rtt, nil
|
||||
}
|
||||
|
||||
// LookupDNAME is LookupCNAME, but for DNAME.
|
||||
func (dnsResolver *DNSResolverImpl) LookupDNAME(hostname string) (string, time.Duration, error) {
|
||||
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeDNAME)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
if r.Rcode == dns.RcodeNXRrset || r.Rcode == dns.RcodeNameError {
|
||||
return "", rtt, nil
|
||||
}
|
||||
if r.Rcode != dns.RcodeSuccess {
|
||||
err = fmt.Errorf("DNS failure: %d-%s for DNAME query", r.Rcode, dns.RcodeToString[r.Rcode])
|
||||
return "", rtt, err
|
||||
}
|
||||
|
||||
for _, answer := range r.Answer {
|
||||
if cname, ok := answer.(*dns.DNAME); ok {
|
||||
return cname.Target, rtt, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", rtt, nil
|
||||
}
|
||||
|
||||
// LookupCAA sends a DNS query to find all CAA records associated with
|
||||
// the provided hostname. If the response code from the resolver is SERVFAIL
|
||||
// an empty slice of CAA records is returned.
|
||||
// the provided hostname. If the response code from the resolver is
|
||||
// SERVFAIL an empty slice of CAA records is returned.
|
||||
func (dnsResolver *DNSResolverImpl) LookupCAA(hostname string) ([]*dns.CAA, time.Duration, error) {
|
||||
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeCAA)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -25,11 +26,14 @@ func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) {
|
|||
m.SetReply(r)
|
||||
m.Compress = false
|
||||
|
||||
appendAnswer := func(rr dns.RR) {
|
||||
m.Answer = append(m.Answer, rr)
|
||||
}
|
||||
for _, q := range r.Question {
|
||||
q.Name = strings.ToLower(q.Name)
|
||||
if q.Name == "servfail.com." {
|
||||
m.Rcode = dns.RcodeServerFailure
|
||||
w.WriteMsg(m)
|
||||
return
|
||||
break
|
||||
}
|
||||
switch q.Qtype {
|
||||
case dns.TypeSOA:
|
||||
|
|
@ -42,31 +46,50 @@ func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) {
|
|||
record.Retry = 1
|
||||
record.Expire = 1
|
||||
record.Minttl = 1
|
||||
|
||||
m.Answer = append(m.Answer, record)
|
||||
w.WriteMsg(m)
|
||||
return
|
||||
appendAnswer(record)
|
||||
case dns.TypeA:
|
||||
if q.Name == "cps.letsencrypt.org." {
|
||||
record := new(dns.A)
|
||||
record.Hdr = dns.RR_Header{Name: "cps.letsencrypt.org.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0}
|
||||
record.A = net.ParseIP("127.0.0.1")
|
||||
|
||||
m.Answer = append(m.Answer, record)
|
||||
w.WriteMsg(m)
|
||||
return
|
||||
appendAnswer(record)
|
||||
}
|
||||
case dns.TypeCNAME:
|
||||
if q.Name == "cname.letsencrypt.org." {
|
||||
record := new(dns.CNAME)
|
||||
record.Hdr = dns.RR_Header{Name: "cname.letsencrypt.org.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 30}
|
||||
record.Target = "cps.letsencrypt.org."
|
||||
appendAnswer(record)
|
||||
}
|
||||
if q.Name == "cname.example.com." {
|
||||
record := new(dns.CNAME)
|
||||
record.Hdr = dns.RR_Header{Name: "cname.example.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 30}
|
||||
record.Target = "CAA.example.com."
|
||||
appendAnswer(record)
|
||||
}
|
||||
case dns.TypeDNAME:
|
||||
if q.Name == "dname.letsencrypt.org." {
|
||||
record := new(dns.DNAME)
|
||||
record.Hdr = dns.RR_Header{Name: "dname.letsencrypt.org.", Rrtype: dns.TypeDNAME, Class: dns.ClassINET, Ttl: 30}
|
||||
record.Target = "cps.letsencrypt.org."
|
||||
appendAnswer(record)
|
||||
}
|
||||
case dns.TypeCAA:
|
||||
if q.Name == "bracewel.net." {
|
||||
if q.Name == "bracewel.net." || q.Name == "caa.example.com." {
|
||||
record := new(dns.CAA)
|
||||
record.Hdr = dns.RR_Header{Name: "bracewel.net.", Rrtype: dns.TypeCAA, Class: dns.ClassINET, Ttl: 0}
|
||||
record.Hdr = dns.RR_Header{Name: q.Name, Rrtype: dns.TypeCAA, Class: dns.ClassINET, Ttl: 0}
|
||||
record.Tag = "issue"
|
||||
record.Value = "letsencrypt.org"
|
||||
record.Flag = 1
|
||||
|
||||
m.Answer = append(m.Answer, record)
|
||||
w.WriteMsg(m)
|
||||
return
|
||||
appendAnswer(record)
|
||||
}
|
||||
if q.Name == "cname.example.com." {
|
||||
record := new(dns.CAA)
|
||||
record.Hdr = dns.RR_Header{Name: "caa.example.com.", Rrtype: dns.TypeCAA, Class: dns.ClassINET, Ttl: 0}
|
||||
record.Tag = "issue"
|
||||
record.Value = "letsencrypt.org"
|
||||
record.Flag = 1
|
||||
appendAnswer(record)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -157,7 +180,7 @@ func TestDNSServFail(t *testing.T) {
|
|||
test.AssertError(t, err, "LookupCNAME didn't return an error")
|
||||
|
||||
_, _, _, err = obj.LookupHost(bad)
|
||||
test.AssertError(t, err, "LookupCNAME didn't return an error")
|
||||
test.AssertError(t, err, "LookupHost didn't return an error")
|
||||
|
||||
// CAA lookup ignores validation failures from the resolver for now
|
||||
// and returns an empty list of CAA records.
|
||||
|
|
@ -204,4 +227,32 @@ func TestDNSLookupCAA(t *testing.T) {
|
|||
caas, _, err = obj.LookupCAA("nonexistent.letsencrypt.org")
|
||||
test.AssertNotError(t, err, "CAA lookup failed")
|
||||
test.Assert(t, len(caas) == 0, "Shouldn't have CAA records")
|
||||
|
||||
caas, _, err = obj.LookupCAA("cname.example.com")
|
||||
test.AssertNotError(t, err, "CAA lookup failed")
|
||||
test.Assert(t, len(caas) > 0, "Should follow CNAME to find CAA")
|
||||
}
|
||||
|
||||
func TestDNSLookupCNAME(t *testing.T) {
|
||||
obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
|
||||
|
||||
target, _, err := obj.LookupCNAME("cps.letsencrypt.org")
|
||||
test.AssertNotError(t, err, "CNAME lookup failed")
|
||||
test.AssertEquals(t, target, "")
|
||||
|
||||
target, _, err = obj.LookupCNAME("cname.letsencrypt.org")
|
||||
test.AssertNotError(t, err, "CNAME lookup failed")
|
||||
test.AssertEquals(t, target, "cps.letsencrypt.org.")
|
||||
}
|
||||
|
||||
func TestDNSLookupDNAME(t *testing.T) {
|
||||
obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
|
||||
|
||||
target, _, err := obj.LookupDNAME("cps.letsencrypt.org")
|
||||
test.AssertNotError(t, err, "DNAME lookup failed")
|
||||
test.AssertEquals(t, target, "")
|
||||
|
||||
target, _, err = obj.LookupDNAME("dname.letsencrypt.org")
|
||||
test.AssertNotError(t, err, "DNAME lookup failed")
|
||||
test.AssertEquals(t, target, "cps.letsencrypt.org.")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ type StorageGetter interface {
|
|||
GetRegistration(int64) (Registration, error)
|
||||
GetRegistrationByKey(jose.JsonWebKey) (Registration, error)
|
||||
GetAuthorization(string) (Authorization, error)
|
||||
GetLatestValidAuthorization(int64, AcmeIdentifier) (Authorization, error)
|
||||
GetCertificate(string) (Certificate, error)
|
||||
GetCertificateByShortSerial(string) (Certificate, error)
|
||||
GetCertificateStatus(string) (CertificateStatus, error)
|
||||
|
|
@ -144,6 +145,7 @@ type DNSResolver interface {
|
|||
LookupTXT(string) ([]string, time.Duration, error)
|
||||
LookupHost(string) ([]net.IP, time.Duration, time.Duration, error)
|
||||
LookupCNAME(string) (string, time.Duration, error)
|
||||
LookupDNAME(string) (string, time.Duration, error)
|
||||
LookupCAA(string) ([]*dns.CAA, time.Duration, error)
|
||||
LookupMX(string) ([]string, time.Duration, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,21 +154,17 @@ type AcmeIdentifier struct {
|
|||
Value string `json:"value"` // The identifier itself
|
||||
}
|
||||
|
||||
// CertificateRequest is just a CSR together with
|
||||
// URIs pointing to authorizations that should collectively
|
||||
// authorize the certificate being requsted.
|
||||
// CertificateRequest is just a CSR
|
||||
//
|
||||
// This data is unmarshalled from JSON by way of rawCertificateRequest, which
|
||||
// represents the actual structure received from the client.
|
||||
type CertificateRequest struct {
|
||||
CSR *x509.CertificateRequest // The CSR
|
||||
Authorizations []AcmeURL // Links to Authorization over the account key
|
||||
Bytes []byte // The original bytes of the CSR, for logging.
|
||||
CSR *x509.CertificateRequest // The CSR
|
||||
Bytes []byte // The original bytes of the CSR, for logging.
|
||||
}
|
||||
|
||||
type rawCertificateRequest struct {
|
||||
CSR JSONBuffer `json:"csr"` // The encoded CSR
|
||||
Authorizations []AcmeURL `json:"authorizations"` // Authorizations
|
||||
CSR JSONBuffer `json:"csr"` // The encoded CSR
|
||||
}
|
||||
|
||||
// UnmarshalJSON provides an implementation for decoding CertificateRequest objects.
|
||||
|
|
@ -184,7 +180,6 @@ func (cr *CertificateRequest) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
|
||||
cr.CSR = csr
|
||||
cr.Authorizations = raw.Authorizations
|
||||
cr.Bytes = raw.CSR
|
||||
return nil
|
||||
}
|
||||
|
|
@ -192,8 +187,7 @@ func (cr *CertificateRequest) UnmarshalJSON(data []byte) error {
|
|||
// MarshalJSON provides an implementation for encoding CertificateRequest objects.
|
||||
func (cr CertificateRequest) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(rawCertificateRequest{
|
||||
CSR: cr.CSR.Raw,
|
||||
Authorizations: cr.Authorizations,
|
||||
CSR: cr.CSR.Raw,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"database/sql"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
// Load SQLite3 for test purposes
|
||||
|
|
@ -71,14 +72,56 @@ func (mock *MockDNS) LookupHost(hostname string) ([]net.IP, time.Duration, time.
|
|||
|
||||
// LookupCNAME is a mock
|
||||
func (mock *MockDNS) LookupCNAME(domain string) (string, time.Duration, error) {
|
||||
return "hostname", 0, nil
|
||||
switch strings.TrimRight(domain, ".") {
|
||||
case "cname-absent.com":
|
||||
return "absent.com.", 30, nil
|
||||
case "cname-critical.com":
|
||||
return "critical.com.", 30, nil
|
||||
case "cname-present.com", "cname-and-dname.com":
|
||||
return "cname-target.present.com.", 30, nil
|
||||
case "cname2-present.com":
|
||||
return "cname-present.com.", 30, nil
|
||||
case "a.cname-loop.com":
|
||||
return "b.cname-loop.com.", 30, nil
|
||||
case "b.cname-loop.com":
|
||||
return "a.cname-loop.com.", 30, nil
|
||||
case "www.caa-loop.com":
|
||||
// nothing wrong with CNAME, but prevents CAA algorithm from terminating
|
||||
return "oops.www.caa-loop.com.", 30, nil
|
||||
case "cname2servfail.com":
|
||||
return "servfail.com.", 30, nil
|
||||
case "cname-servfail.com":
|
||||
return "", 0, fmt.Errorf("SERVFAIL")
|
||||
case "cname2dname.com":
|
||||
return "dname2cname.com.", 30, nil
|
||||
default:
|
||||
return "", 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
// LookupDNAME is a mock
|
||||
func (mock *MockDNS) LookupDNAME(domain string) (string, time.Duration, error) {
|
||||
switch strings.TrimRight(domain, ".") {
|
||||
case "cname-and-dname.com", "dname-present.com":
|
||||
return "dname-target.present.com.", time.Minute, nil
|
||||
case "a.dname-loop.com":
|
||||
return "b.dname-loop.com.", time.Minute, nil
|
||||
case "b.dname-loop.com":
|
||||
return "a.dname-loop.com.", time.Minute, nil
|
||||
case "dname2cname.com":
|
||||
return "cname2-present.com.", time.Minute, nil
|
||||
case "dname-servfail.com":
|
||||
return "", time.Minute, fmt.Errorf("SERVFAIL")
|
||||
default:
|
||||
return "", 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
// LookupCAA is a mock
|
||||
func (mock *MockDNS) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error) {
|
||||
var results []*dns.CAA
|
||||
var record dns.CAA
|
||||
switch domain {
|
||||
switch strings.TrimRight(domain, ".") {
|
||||
case "reserved.com":
|
||||
record.Tag = "issue"
|
||||
record.Value = "symantec.com"
|
||||
|
|
@ -100,7 +143,7 @@ func (mock *MockDNS) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error)
|
|||
|
||||
// LookupMX is a mock
|
||||
func (mock *MockDNS) LookupMX(domain string) ([]string, time.Duration, error) {
|
||||
switch domain {
|
||||
switch strings.TrimRight(domain, ".") {
|
||||
case "letsencrypt.org":
|
||||
fallthrough
|
||||
case "email.com":
|
||||
|
|
|
|||
|
|
@ -279,50 +279,21 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
|
|||
return emptyCert, err
|
||||
}
|
||||
|
||||
// Gather authorized domains from the referenced authorizations
|
||||
authorizedDomains := map[string]bool{}
|
||||
verificationMethodSet := map[string]bool{}
|
||||
earliestExpiry := time.Date(2100, 01, 01, 0, 0, 0, 0, time.UTC)
|
||||
// Check that each requested name has a valid authorization
|
||||
now := time.Now()
|
||||
for _, url := range req.Authorizations {
|
||||
id := lastPathSegment(url)
|
||||
authz, err := ra.SA.GetAuthorization(id)
|
||||
if err != nil || // Couldn't find authorization
|
||||
authz.RegistrationID != registration.ID || // Not for this account
|
||||
authz.Status != core.StatusValid || // Not finalized or not successful
|
||||
authz.Expires.Before(now) || // Expired
|
||||
authz.Identifier.Type != core.IdentifierDNS {
|
||||
// XXX: It may be good to fail here instead of ignoring invalid authorizations.
|
||||
// However, it seems like this treatment is more in the spirit of Postel's
|
||||
// law, and it hides information from attackers.
|
||||
continue
|
||||
earliestExpiry := time.Date(2100, 01, 01, 0, 0, 0, 0, time.UTC)
|
||||
for _, name := range names {
|
||||
authz, err := ra.SA.GetLatestValidAuthorization(registration.ID, core.AcmeIdentifier{Type: core.IdentifierDNS, Value: name})
|
||||
if err != nil || authz.Expires.Before(now) {
|
||||
// unable to find a valid authorization or authz is expired
|
||||
err = core.UnauthorizedError(fmt.Sprintf("Key not authorized for name %s", name))
|
||||
logEvent.Error = err.Error()
|
||||
return emptyCert, err
|
||||
}
|
||||
|
||||
if authz.Expires.Before(earliestExpiry) {
|
||||
earliestExpiry = *authz.Expires
|
||||
}
|
||||
|
||||
for _, challenge := range authz.Challenges {
|
||||
if challenge.Status == core.StatusValid {
|
||||
verificationMethodSet[challenge.Type] = true
|
||||
}
|
||||
}
|
||||
|
||||
authorizedDomains[authz.Identifier.Value] = true
|
||||
}
|
||||
verificationMethods := []string{}
|
||||
for method := range verificationMethodSet {
|
||||
verificationMethods = append(verificationMethods, method)
|
||||
}
|
||||
logEvent.VerificationMethods = verificationMethods
|
||||
|
||||
// Validate all domains
|
||||
for _, name := range names {
|
||||
if !authorizedDomains[name] {
|
||||
err = core.UnauthorizedError(fmt.Sprintf("Key not authorized for name %s", name))
|
||||
logEvent.Error = err.Error()
|
||||
return emptyCert, err
|
||||
}
|
||||
}
|
||||
|
||||
// Mark that we verified the CN and SANs
|
||||
|
|
|
|||
|
|
@ -425,10 +425,8 @@ func TestCertificateKeyNotEqualAccountKey(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Failed to parse CSR")
|
||||
sa.UpdatePendingAuthorization(authz)
|
||||
sa.FinalizeAuthorization(authz)
|
||||
authzURL, _ := url.Parse("http://doesnt.matter/" + authz.ID)
|
||||
certRequest := core.CertificateRequest{
|
||||
CSR: parsedCSR,
|
||||
Authorizations: []core.AcmeURL{core.AcmeURL(*authzURL)},
|
||||
CSR: parsedCSR,
|
||||
}
|
||||
|
||||
// Registration id 1 has key == AccountKeyA
|
||||
|
|
@ -446,14 +444,10 @@ func TestAuthorizationRequired(t *testing.T) {
|
|||
sa.UpdatePendingAuthorization(AuthzFinal)
|
||||
sa.FinalizeAuthorization(AuthzFinal)
|
||||
|
||||
// Construct a cert request referencing the authorization
|
||||
url1, _ := url.Parse("http://doesnt.matter/" + AuthzFinal.ID)
|
||||
|
||||
// ExampleCSR requests not-example.com and www.not-example.com,
|
||||
// but the authorization only covers not-example.com
|
||||
certRequest := core.CertificateRequest{
|
||||
CSR: ExampleCSR,
|
||||
Authorizations: []core.AcmeURL{core.AcmeURL(*url1)},
|
||||
CSR: ExampleCSR,
|
||||
}
|
||||
|
||||
_, err := ra.NewCertificate(certRequest, 1)
|
||||
|
|
@ -475,13 +469,8 @@ func TestNewCertificate(t *testing.T) {
|
|||
authzFinalWWW, _ = sa.NewPendingAuthorization(authzFinalWWW)
|
||||
sa.FinalizeAuthorization(authzFinalWWW)
|
||||
|
||||
// Construct a cert request referencing the two authorizations
|
||||
url1, _ := url.Parse("http://doesnt.matter/" + AuthzFinal.ID)
|
||||
url2, _ := url.Parse("http://doesnt.matter/" + authzFinalWWW.ID)
|
||||
|
||||
certRequest := core.CertificateRequest{
|
||||
CSR: ExampleCSR,
|
||||
Authorizations: []core.AcmeURL{core.AcmeURL(*url1), core.AcmeURL(*url2)},
|
||||
CSR: ExampleCSR,
|
||||
}
|
||||
|
||||
cert, err := ra.NewCertificate(certRequest, 1)
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ const (
|
|||
MethodGetRegistration = "GetRegistration" // SA
|
||||
MethodGetRegistrationByKey = "GetRegistrationByKey" // RA, SA
|
||||
MethodGetAuthorization = "GetAuthorization" // SA
|
||||
MethodGetLatestValidAuthorization = "GetLatestValidAuthorization" // SA
|
||||
MethodGetCertificate = "GetCertificate" // SA
|
||||
MethodGetCertificateByShortSerial = "GetCertificateByShortSerial" // SA
|
||||
MethodGetCertificateStatus = "GetCertificateStatus" // SA
|
||||
|
|
@ -84,6 +85,11 @@ type updateAuthorizationRequest struct {
|
|||
Response core.Challenge
|
||||
}
|
||||
|
||||
type latestValidAuthorizationRequest struct {
|
||||
RegID int64
|
||||
Identifier core.AcmeIdentifier
|
||||
}
|
||||
|
||||
type certificateRequest struct {
|
||||
Req core.CertificateRequest
|
||||
RegID int64
|
||||
|
|
@ -714,6 +720,28 @@ func NewStorageAuthorityServer(rpc RPCServer, impl core.StorageAuthority) error
|
|||
return
|
||||
})
|
||||
|
||||
rpc.Handle(MethodGetLatestValidAuthorization, func(req []byte) (response []byte, err error) {
|
||||
var lvar latestValidAuthorizationRequest
|
||||
if err = json.Unmarshal(req, &lvar); err != nil {
|
||||
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
|
||||
improperMessage(MethodNewAuthorization, err, req)
|
||||
return
|
||||
}
|
||||
|
||||
authz, err := impl.GetLatestValidAuthorization(lvar.RegID, lvar.Identifier)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err = json.Marshal(authz)
|
||||
if err != nil {
|
||||
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
|
||||
errorCondition(MethodGetLatestValidAuthorization, err, req)
|
||||
return
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
rpc.Handle(MethodAddCertificate, func(req []byte) (response []byte, err error) {
|
||||
var acReq addCertificateRequest
|
||||
err = json.Unmarshal(req, &acReq)
|
||||
|
|
@ -956,6 +984,27 @@ func (cac StorageAuthorityClient) GetAuthorization(id string) (authz core.Author
|
|||
return
|
||||
}
|
||||
|
||||
// GetLatestValidAuthorization sends a request to get an Authorization by RegID, Identifier
|
||||
func (cac StorageAuthorityClient) GetLatestValidAuthorization(registrationId int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) {
|
||||
|
||||
var lvar latestValidAuthorizationRequest
|
||||
lvar.RegID = registrationId
|
||||
lvar.Identifier = identifier
|
||||
|
||||
data, err := json.Marshal(lvar)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
jsonAuthz, err := cac.rpc.DispatchSync(MethodGetLatestValidAuthorization, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(jsonAuthz, &authz)
|
||||
return
|
||||
}
|
||||
|
||||
// GetCertificate sends a request to get a Certificate by ID
|
||||
func (cac StorageAuthorityClient) GetCertificate(id string) (cert core.Certificate, err error) {
|
||||
jsonCert, err := cac.rpc.DispatchSync(MethodGetCertificate, []byte(id))
|
||||
|
|
|
|||
|
|
@ -166,6 +166,20 @@ func (ssa *SQLStorageAuthority) GetAuthorization(id string) (authz core.Authoriz
|
|||
return
|
||||
}
|
||||
|
||||
// Get the valid authorization with biggest expire date for a given domain and registrationId
|
||||
func (ssa *SQLStorageAuthority) GetLatestValidAuthorization(registrationId int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) {
|
||||
ident, err := json.Marshal(identifier)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = ssa.dbMap.SelectOne(&authz, "SELECT id, identifier, registrationID, status, expires, challenges, combinations "+
|
||||
"FROM authz "+
|
||||
"WHERE identifier = :identifier AND registrationID = :registrationId AND status = 'valid' "+
|
||||
"ORDER BY expires DESC LIMIT 1",
|
||||
map[string]interface{}{"identifier": string(ident), "registrationId": registrationId})
|
||||
return
|
||||
}
|
||||
|
||||
// GetCertificateByShortSerial takes an id consisting of the first, sequential half of a
|
||||
// serial number and returns the first certificate whose full serial number is
|
||||
// lexically greater than that id. This allows clients to query on the known
|
||||
|
|
|
|||
|
|
@ -123,7 +123,8 @@ func TestAddAuthorization(t *testing.T) {
|
|||
combos[0] = []int{0, 1}
|
||||
|
||||
exp := time.Now().AddDate(0, 0, 1)
|
||||
newPa := core.Authorization{ID: PA.ID, Identifier: core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "wut.com"}, RegistrationID: 0, Status: core.StatusPending, Expires: &exp, Challenges: []core.Challenge{chall}, Combinations: combos}
|
||||
identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "wut.com"}
|
||||
newPa := core.Authorization{ID: PA.ID, Identifier: identifier, RegistrationID: 0, Status: core.StatusPending, Expires: &exp, Challenges: []core.Challenge{chall}, Combinations: combos}
|
||||
err = sa.UpdatePendingAuthorization(newPa)
|
||||
test.AssertNotError(t, err, "Couldn't update pending authorization with ID "+PA.ID)
|
||||
|
||||
|
|
@ -135,6 +136,119 @@ func TestAddAuthorization(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Couldn't get authorization with ID "+PA.ID)
|
||||
}
|
||||
|
||||
func CreateDomainAuth(t *testing.T, domainName string, sa *SQLStorageAuthority) (authz core.Authorization) {
|
||||
// create pending auth
|
||||
authz, err := sa.NewPendingAuthorization(core.Authorization{})
|
||||
test.AssertNotError(t, err, "Couldn't create new pending authorization")
|
||||
test.Assert(t, authz.ID != "", "ID shouldn't be blank")
|
||||
|
||||
// prepare challenge for auth
|
||||
uu, err := url.Parse(domainName)
|
||||
test.AssertNotError(t, err, "Couldn't parse domainName "+domainName)
|
||||
u := core.AcmeURL(*uu)
|
||||
chall := core.Challenge{Type: "simpleHttp", Status: core.StatusValid, URI: u, Token: "THISWOULDNTBEAGOODTOKEN", Path: "test-me"}
|
||||
combos := make([][]int, 1)
|
||||
combos[0] = []int{0, 1}
|
||||
exp := time.Now().AddDate(0, 0, 1) // expire in 1 day
|
||||
|
||||
// validate pending auth
|
||||
authz.Status = core.StatusPending
|
||||
authz.Identifier = core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domainName}
|
||||
authz.RegistrationID = 42
|
||||
authz.Expires = &exp
|
||||
authz.Challenges = []core.Challenge{chall}
|
||||
authz.Combinations = combos
|
||||
|
||||
// save updated auth
|
||||
err = sa.UpdatePendingAuthorization(authz)
|
||||
test.AssertNotError(t, err, "Couldn't update pending authorization with ID "+authz.ID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure we get only valid authorization with correct RegID
|
||||
func TestGetLatestValidAuthorizationBasic(t *testing.T) {
|
||||
sa := initSA(t)
|
||||
|
||||
// attempt to get unauthorized domain
|
||||
authz, err := sa.GetLatestValidAuthorization(0, core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "example.org"})
|
||||
test.AssertError(t, err, "Should not have found a valid auth for example.org")
|
||||
|
||||
// authorize "example.org"
|
||||
authz = CreateDomainAuth(t, "example.org", sa)
|
||||
|
||||
// finalize auth
|
||||
authz.Status = core.StatusValid
|
||||
err = sa.FinalizeAuthorization(authz)
|
||||
test.AssertNotError(t, err, "Couldn't finalize pending authorization with ID "+authz.ID)
|
||||
|
||||
// attempt to get authorized domain with wrong RegID
|
||||
authz, err = sa.GetLatestValidAuthorization(0, core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "example.org"})
|
||||
test.AssertError(t, err, "Should not have found a valid auth for example.org and regID 0")
|
||||
|
||||
// get authorized domain
|
||||
authz, err = sa.GetLatestValidAuthorization(42, core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "example.org"})
|
||||
test.AssertNotError(t, err, "Should have found a valid auth for example.org and regID 42")
|
||||
test.AssertEquals(t, authz.Status, core.StatusValid)
|
||||
test.AssertEquals(t, authz.Identifier.Type, core.IdentifierDNS)
|
||||
test.AssertEquals(t, authz.Identifier.Value, "example.org")
|
||||
test.AssertEquals(t, authz.RegistrationID, int64(42))
|
||||
}
|
||||
|
||||
// Ensure we get the latest valid authorization for an ident
|
||||
func TestGetLatestValidAuthorizationMultiple(t *testing.T) {
|
||||
sa := initSA(t)
|
||||
domain := "example.org"
|
||||
ident := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domain}
|
||||
regID := int64(42)
|
||||
var err error
|
||||
|
||||
// create invalid authz
|
||||
authz := CreateDomainAuth(t, domain, sa)
|
||||
exp := time.Now().AddDate(0, 0, 10) // expire in 10 day
|
||||
authz.Expires = &exp
|
||||
authz.Status = core.StatusInvalid
|
||||
err = sa.FinalizeAuthorization(authz)
|
||||
test.AssertNotError(t, err, "Couldn't finalize pending authorization with ID "+authz.ID)
|
||||
|
||||
// should not get the auth
|
||||
authz, err = sa.GetLatestValidAuthorization(regID, ident)
|
||||
test.AssertError(t, err, "Should not have found a valid auth for "+domain)
|
||||
|
||||
// create valid auth
|
||||
authz = CreateDomainAuth(t, domain, sa)
|
||||
exp = time.Now().AddDate(0, 0, 1) // expire in 1 day
|
||||
authz.Expires = &exp
|
||||
authz.Status = core.StatusValid
|
||||
err = sa.FinalizeAuthorization(authz)
|
||||
test.AssertNotError(t, err, "Couldn't finalize pending authorization with ID "+authz.ID)
|
||||
|
||||
// should get the valid auth even if it's expire date is lower than the invalid one
|
||||
authz, err = sa.GetLatestValidAuthorization(regID, ident)
|
||||
test.AssertNotError(t, err, "Should have found a valid auth for "+domain)
|
||||
test.AssertEquals(t, authz.Status, core.StatusValid)
|
||||
test.AssertEquals(t, authz.Identifier.Type, ident.Type)
|
||||
test.AssertEquals(t, authz.Identifier.Value, ident.Value)
|
||||
test.AssertEquals(t, authz.RegistrationID, regID)
|
||||
|
||||
// create a newer auth
|
||||
newAuthz := CreateDomainAuth(t, domain, sa)
|
||||
exp = time.Now().AddDate(0, 0, 2) // expire in 2 day
|
||||
newAuthz.Expires = &exp
|
||||
newAuthz.Status = core.StatusValid
|
||||
err = sa.FinalizeAuthorization(newAuthz)
|
||||
test.AssertNotError(t, err, "Couldn't finalize pending authorization with ID "+newAuthz.ID)
|
||||
|
||||
authz, err = sa.GetLatestValidAuthorization(regID, ident)
|
||||
test.AssertNotError(t, err, "Should have found a valid auth for "+domain)
|
||||
test.AssertEquals(t, authz.Status, core.StatusValid)
|
||||
test.AssertEquals(t, authz.Identifier.Type, ident.Type)
|
||||
test.AssertEquals(t, authz.Identifier.Value, ident.Value)
|
||||
test.AssertEquals(t, authz.RegistrationID, regID)
|
||||
// make sure we got the latest auth
|
||||
test.AssertEquals(t, authz.ID, newAuthz.ID)
|
||||
}
|
||||
|
||||
func TestAddCertificate(t *testing.T) {
|
||||
sa := initSA(t)
|
||||
|
||||
|
|
|
|||
10
start.sh
10
start.sh
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Run both boulder and cfssl, using test configs.
|
||||
if type realpath >/dev/null 2>/dev/null; then
|
||||
cd $(realpath $(dirname $0))
|
||||
fi
|
||||
|
||||
# Kill all children on exit.
|
||||
export BOULDER_CONFIG=${BOULDER_CONFIG:-test/boulder-config.json}
|
||||
|
||||
exec go run ./cmd/boulder/main.go
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
{
|
||||
"syslog": {
|
||||
"network": "",
|
||||
"server": "",
|
||||
"tag": "boulder"
|
||||
},
|
||||
|
||||
"amqp": {
|
||||
"server": "amqp://guest:guest@localhost:5672",
|
||||
"RA": {
|
||||
"client": "RA.client",
|
||||
"server": "RA.server"
|
||||
},
|
||||
"VA": {
|
||||
"client": "VA.client",
|
||||
"server": "VA.server"
|
||||
},
|
||||
"SA": {
|
||||
"client": "SA.client",
|
||||
"server": "SA.server"
|
||||
},
|
||||
"CA": {
|
||||
"client": "CA.client",
|
||||
"server": "CA.server"
|
||||
}
|
||||
},
|
||||
|
||||
"statsd": {
|
||||
"server": "localhost:8125",
|
||||
"prefix": "Boulder"
|
||||
},
|
||||
|
||||
"wfe": {
|
||||
"listenAddress": "127.0.0.1:4300",
|
||||
"certCacheDuration": "6h",
|
||||
"certNoCacheExpirationWindow": "8765h",
|
||||
"indexCacheDuration": "24h",
|
||||
"issuerCacheDuration": "48h",
|
||||
"debugAddr": "localhost:8000"
|
||||
},
|
||||
|
||||
"ca": {
|
||||
"serialPrefix": 255,
|
||||
"profile": "ee",
|
||||
"dbDriver": "sqlite3",
|
||||
"dbConnect": ":memory:",
|
||||
"debugAddr": "localhost:8001",
|
||||
"testMode": true,
|
||||
"_comment": "This should only be present in testMode. In prod use an HSM.",
|
||||
"Key": {
|
||||
"File": "test/test-ca.key"
|
||||
},
|
||||
"expiry": "2160h",
|
||||
"lifespanOCSP": "96h",
|
||||
"maxNames": 1000,
|
||||
"cfssl": {
|
||||
"signing": {
|
||||
"profiles": {
|
||||
"ee": {
|
||||
"usages": [
|
||||
"digital signature",
|
||||
"key encipherment",
|
||||
"server auth",
|
||||
"client auth"
|
||||
],
|
||||
"backdate": "1h",
|
||||
"is_ca": false,
|
||||
"issuer_urls": [
|
||||
"http://int-x1.letsencrypt.org/cert"
|
||||
],
|
||||
"ocsp_url": "http://int-x1.letsencrypt.org/ocsp",
|
||||
"crl_url": "http://int-x1.letsencrypt.org/crl",
|
||||
"policies": [
|
||||
{
|
||||
"ID": "2.23.140.1.2.1"
|
||||
},
|
||||
{
|
||||
"ID": "1.2.3.4",
|
||||
"Qualifiers": [ {
|
||||
"type": "id-qt-cps",
|
||||
"value": "http://example.com/cps"
|
||||
}, {
|
||||
"type": "id-qt-unotice",
|
||||
"value": "Do What Thou Wilt"
|
||||
} ]
|
||||
}
|
||||
],
|
||||
"expiry": "8760h",
|
||||
"CSRWhitelist": {
|
||||
"PublicKeyAlgorithm": true,
|
||||
"PublicKey": true,
|
||||
"SignatureAlgorithm": true
|
||||
},
|
||||
"UseSerialSeq": true
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"usages": [
|
||||
"digital signature"
|
||||
],
|
||||
"expiry": "8760h"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"ra": {
|
||||
"debugAddr": "localhost:8002"
|
||||
},
|
||||
|
||||
"sa": {
|
||||
"dbDriver": "sqlite3",
|
||||
"dbConnect": ":memory:",
|
||||
"debugAddr": "localhost:8003"
|
||||
},
|
||||
|
||||
"va": {
|
||||
"userAgent": "boulder",
|
||||
"debugAddr": "localhost:8004"
|
||||
},
|
||||
|
||||
"sql": {
|
||||
"SQLDebug": false,
|
||||
"CreateTables": true
|
||||
},
|
||||
|
||||
"mail": {
|
||||
"server": "mail.example.com",
|
||||
"port": "25",
|
||||
"username": "cert-master@example.com",
|
||||
"password": "password"
|
||||
},
|
||||
|
||||
"common": {
|
||||
"baseURL": "http://localhost:4300",
|
||||
"issuerCert": "test/test-ca.pem",
|
||||
"maxKeySize": 4096,
|
||||
"dnsResolver": "127.0.0.1:8053",
|
||||
"dnsTimeout": "10s"
|
||||
},
|
||||
|
||||
"subscriberAgreementURL": "http://localhost:4300/terms/"
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
#!/usr/bin/env python2.7
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
tempdir = tempfile.mkdtemp()
|
||||
|
||||
exit_status = 0
|
||||
|
||||
def die():
|
||||
global exit_status
|
||||
exit_status = 1
|
||||
sys.exit(1)
|
||||
|
||||
def build(path):
|
||||
cmd = 'go build -o %s/%s %s' % (tempdir, os.path.basename(path), path)
|
||||
print(cmd)
|
||||
if subprocess.Popen(cmd, shell=True).wait() != 0:
|
||||
die()
|
||||
|
||||
build('./cmd/boulder')
|
||||
|
||||
boulder = subprocess.Popen('''
|
||||
exec %s/boulder --config test/boulder-test-config.json
|
||||
''' % tempdir, shell=True)
|
||||
|
||||
def run_test():
|
||||
os.chdir('test/js')
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# Wait up to 7 seconds for Boulder to come up.
|
||||
for i in range(0, 7):
|
||||
try:
|
||||
s.connect(('localhost', 4300))
|
||||
break
|
||||
except socket.error, e:
|
||||
time.sleep(1)
|
||||
|
||||
if subprocess.Popen('npm install', shell=True).wait() != 0:
|
||||
die()
|
||||
|
||||
issue = subprocess.Popen('''
|
||||
node test.js --email foo@letsencrypt.org --agree true \
|
||||
--domains foo.com --new-reg http://localhost:4300/acme/new-reg \
|
||||
--certKey %s/key.pem --cert %s/cert.der
|
||||
''' % (tempdir, tempdir), shell=True)
|
||||
if issue.wait() != 0:
|
||||
die()
|
||||
revoke = subprocess.Popen('''
|
||||
node revoke.js %s/cert.der %s/key.pem http://localhost:4300/acme/revoke-cert
|
||||
''' % (tempdir, tempdir), shell=True)
|
||||
if revoke.wait() != 0:
|
||||
die()
|
||||
|
||||
try:
|
||||
run_test()
|
||||
except Exception as e:
|
||||
exit_status = 1
|
||||
print e
|
||||
finally:
|
||||
# Check whether boulder died. This can happen, for instance, if there was an
|
||||
# existing boulder already listening on the port.
|
||||
if boulder.poll() is not None:
|
||||
print("Boulder died")
|
||||
exit_status = 1
|
||||
else:
|
||||
boulder.kill()
|
||||
|
||||
shutil.rmtree(tempdir)
|
||||
|
||||
if exit_status == 0:
|
||||
print("SUCCESS")
|
||||
else:
|
||||
print("FAILURE")
|
||||
sys.exit(exit_status)
|
||||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
|
|
@ -24,6 +25,12 @@ import (
|
|||
"github.com/letsencrypt/boulder/policy"
|
||||
)
|
||||
|
||||
const maxCNAME = 16 // Prevents infinite loops. Same limit as BIND.
|
||||
|
||||
// Returned by CheckCAARecords if it has to follow too many
|
||||
// consecutive CNAME lookups.
|
||||
var ErrTooManyCNAME = errors.New("too many CNAME/DNAME lookups")
|
||||
|
||||
// ValidationAuthorityImpl represents a VA
|
||||
type ValidationAuthorityImpl struct {
|
||||
RA core.RegistrationAuthority
|
||||
|
|
@ -437,36 +444,50 @@ func newCAASet(CAAs []*dns.CAA) *CAASet {
|
|||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) getCAASet(hostname string) (*CAASet, error) {
|
||||
hostname = strings.TrimRight(hostname, ".")
|
||||
splitDomain := strings.Split(hostname, ".")
|
||||
// RFC 6844 CAA set query sequence, 'x.y.z.com' => ['x.y.z.com', 'y.z.com', 'z.com']
|
||||
for i := range splitDomain {
|
||||
queryDomain := strings.Join(splitDomain[i:], ".")
|
||||
// Don't query a public suffix
|
||||
if _, present := policy.PublicSuffixList[queryDomain]; present {
|
||||
label := strings.TrimRight(hostname, ".")
|
||||
cnames := 0
|
||||
// See RFC 6844 "Certification Authority Processing" for pseudocode.
|
||||
for {
|
||||
if strings.IndexRune(label, '.') == -1 {
|
||||
// Reached TLD
|
||||
break
|
||||
}
|
||||
|
||||
// Query CAA records for domain and its alias if it has a CNAME
|
||||
for _, alias := range []bool{false, true} {
|
||||
if alias {
|
||||
target, _, err := va.DNSResolver.LookupCNAME(queryDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queryDomain = target
|
||||
}
|
||||
CAAs, _, err := va.DNSResolver.LookupCAA(queryDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(CAAs) > 0 {
|
||||
return newCAASet(CAAs), nil
|
||||
}
|
||||
if _, present := policy.PublicSuffixList[label]; present {
|
||||
break
|
||||
}
|
||||
CAAs, _, err := va.DNSResolver.LookupCAA(label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(CAAs) > 0 {
|
||||
return newCAASet(CAAs), nil
|
||||
}
|
||||
cname, _, err := va.DNSResolver.LookupCNAME(label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dname, _, err := va.DNSResolver.LookupDNAME(label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cname == "" && dname == "" {
|
||||
// Try parent domain (note we confirmed
|
||||
// earlier that label contains '.')
|
||||
label = label[strings.IndexRune(label, '.')+1:]
|
||||
continue
|
||||
}
|
||||
if cname != "" && dname != "" && cname != dname {
|
||||
return nil, errors.New("both CNAME and DNAME exist for " + label)
|
||||
}
|
||||
if cname != "" {
|
||||
label = cname
|
||||
} else {
|
||||
label = dname
|
||||
}
|
||||
if cnames++; cnames > maxCNAME {
|
||||
return nil, ErrTooManyCNAME
|
||||
}
|
||||
}
|
||||
|
||||
// no CAA records found
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -564,10 +564,23 @@ func TestCAAChecking(t *testing.T) {
|
|||
CAATest{"reserved.com", true, false},
|
||||
// Critical
|
||||
CAATest{"critical.com", true, false},
|
||||
CAATest{"nx.critical.com", true, false},
|
||||
CAATest{"cname-critical.com", true, false},
|
||||
CAATest{"nx.cname-critical.com", true, false},
|
||||
// Good (absent)
|
||||
CAATest{"absent.com", false, true},
|
||||
CAATest{"cname-absent.com", false, true},
|
||||
CAATest{"nx.cname-absent.com", false, true},
|
||||
CAATest{"cname-nx.com", false, true},
|
||||
CAATest{"example.co.uk", false, true},
|
||||
// Good (present)
|
||||
CAATest{"present.com", true, true},
|
||||
CAATest{"cname-present.com", true, true},
|
||||
CAATest{"cname2-present.com", true, true},
|
||||
CAATest{"nx.cname2-present.com", true, true},
|
||||
CAATest{"dname-present.com", true, true},
|
||||
CAATest{"dname2cname.com", true, true},
|
||||
// CNAME to critical
|
||||
}
|
||||
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
|
|
@ -585,6 +598,20 @@ func TestCAAChecking(t *testing.T) {
|
|||
test.AssertError(t, err, "servfail.com")
|
||||
test.Assert(t, !present, "Present should be false")
|
||||
test.Assert(t, !valid, "Valid should be false")
|
||||
|
||||
for _, name := range []string{
|
||||
"www.caa-loop.com",
|
||||
"a.cname-loop.com",
|
||||
"a.dname-loop.com",
|
||||
"cname-servfail.com",
|
||||
"cname2servfail.com",
|
||||
"dname-servfail.com",
|
||||
"cname-and-dname.com",
|
||||
"servfail.com",
|
||||
} {
|
||||
_, _, err = va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: name})
|
||||
test.AssertError(t, err, name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSValidationFailure(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -703,7 +702,6 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
|
|||
return
|
||||
}
|
||||
wfe.logCsr(request.RemoteAddr, init, reg)
|
||||
logEvent.Extra["Authorizations"] = init.Authorizations
|
||||
logEvent.Extra["CSRDNSNames"] = init.CSR.DNSNames
|
||||
logEvent.Extra["CSREmailAddresses"] = init.CSR.EmailAddresses
|
||||
logEvent.Extra["CSRIPAddresses"] = init.CSR.IPAddresses
|
||||
|
|
@ -1083,7 +1081,7 @@ func (wfe *WebFrontEndImpl) BuildID(response http.ResponseWriter, request *http.
|
|||
|
||||
response.Header().Set("Content-Type", "text/plain")
|
||||
response.WriteHeader(http.StatusOK)
|
||||
detailsString := fmt.Sprintf("Boulder=(%s %s) Golang=(%s) BuildHost=(%s)", core.GetBuildID(), core.GetBuildTime(), runtime.Version(), core.GetBuildHost())
|
||||
detailsString := fmt.Sprintf("Boulder=(%s %s)", core.GetBuildID(), core.GetBuildTime())
|
||||
if _, err := fmt.Fprintln(response, detailsString); err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ wk6Oiadty3eQqSBJv0HnpmiEdQVffIK5Pg4M8Dd+aOBnEkbopAJOuA==
|
|||
)
|
||||
|
||||
type MockSA struct {
|
||||
// empty
|
||||
authorizedDomains map[string]bool
|
||||
}
|
||||
|
||||
func (sa *MockSA) GetRegistration(id int64) (core.Registration, error) {
|
||||
|
|
@ -177,6 +177,16 @@ func (sa *MockSA) GetAuthorization(id string) (core.Authorization, error) {
|
|||
return core.Authorization{}, nil
|
||||
}
|
||||
|
||||
func (sa *MockSA) GetLatestValidAuthorization(registrationId int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) {
|
||||
if registrationId == 1 && identifier.Type == "dns" {
|
||||
if sa.authorizedDomains[identifier.Value] || identifier.Value == "not-an-example.com" {
|
||||
exp := time.Now().AddDate(100, 0, 0)
|
||||
return core.Authorization{Status: core.StatusValid, RegistrationID: 1, Expires: &exp, Identifier: identifier}, nil
|
||||
}
|
||||
}
|
||||
return core.Authorization{}, errors.New("no authz")
|
||||
}
|
||||
|
||||
func (sa *MockSA) GetCertificate(serial string) (core.Certificate, error) {
|
||||
// Serial ee == 238.crt
|
||||
if serial == "000000000000000000000000000000ee" {
|
||||
|
|
@ -225,6 +235,9 @@ func (sa *MockSA) AddCertificate(certDER []byte, regID int64) (digest string, er
|
|||
}
|
||||
|
||||
func (sa *MockSA) FinalizeAuthorization(authz core.Authorization) (err error) {
|
||||
if authz.Status == core.StatusValid && authz.Identifier.Type == core.IdentifierDNS {
|
||||
sa.authorizedDomains[authz.Identifier.Value] = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue