Support draft-acme-ip and IP address identifiers (#221)
Implements https://datatracker.ietf.org/doc/draft-ietf-acme-ip/ support in Pebble.
This commit is contained in:
parent
0ecf7e0534
commit
bc4da68d49
|
|
@ -13,6 +13,7 @@ const (
|
||||||
StatusDeactivated = "deactivated"
|
StatusDeactivated = "deactivated"
|
||||||
|
|
||||||
IdentifierDNS = "dns"
|
IdentifierDNS = "dns"
|
||||||
|
IdentifierIP = "ip"
|
||||||
|
|
||||||
ChallengeHTTP01 = "http-01"
|
ChallengeHTTP01 = "http-01"
|
||||||
ChallengeTLSALPN01 = "tls-alpn-01"
|
ChallengeTLSALPN01 = "tls-alpn-01"
|
||||||
|
|
|
||||||
12
ca/ca.go
12
ca/ca.go
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/letsencrypt/pebble/acme"
|
"github.com/letsencrypt/pebble/acme"
|
||||||
|
|
@ -156,12 +157,14 @@ func (ca *CAImpl) newIntermediateIssuer() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ca *CAImpl) newCertificate(domains []string, key crypto.PublicKey, accountID string) (*core.Certificate, error) {
|
func (ca *CAImpl) newCertificate(domains []string, ips []net.IP, key crypto.PublicKey, accountID string) (*core.Certificate, error) {
|
||||||
var cn string
|
var cn string
|
||||||
if len(domains) > 0 {
|
if len(domains) > 0 {
|
||||||
cn = domains[0]
|
cn = domains[0]
|
||||||
|
} else if len(ips) > 0 {
|
||||||
|
cn = ips[0].String()
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("must specify at least one domain name")
|
return nil, fmt.Errorf("must specify at least one domain name or IP address")
|
||||||
}
|
}
|
||||||
|
|
||||||
issuer := ca.intermediate
|
issuer := ca.intermediate
|
||||||
|
|
@ -171,7 +174,8 @@ func (ca *CAImpl) newCertificate(domains []string, key crypto.PublicKey, account
|
||||||
|
|
||||||
serial := makeSerial()
|
serial := makeSerial()
|
||||||
template := &x509.Certificate{
|
template := &x509.Certificate{
|
||||||
DNSNames: domains,
|
DNSNames: domains,
|
||||||
|
IPAddresses: ips,
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: cn,
|
CommonName: cn,
|
||||||
},
|
},
|
||||||
|
|
@ -250,7 +254,7 @@ func (ca *CAImpl) CompleteOrder(order *core.Order) {
|
||||||
|
|
||||||
// issue a certificate for the csr
|
// issue a certificate for the csr
|
||||||
csr := order.ParsedCSR
|
csr := order.ParsedCSR
|
||||||
cert, err := ca.newCertificate(csr.DNSNames, csr.PublicKey, order.AccountID)
|
cert, err := ca.newCertificate(csr.DNSNames, csr.IPAddresses, csr.PublicKey, order.AccountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ca.log.Printf("Error: unable to issue order: %s", err.Error())
|
ca.log.Printf("Error: unable to issue order: %s", err.Error())
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ func (o *Order) GetStatus() (string, error) {
|
||||||
return acme.StatusPending, nil
|
return acme.StatusPending, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fullyAuthorized := len(o.Names) == authzStatuses[acme.StatusValid]
|
fullyAuthorized := len(o.Identifiers) == authzStatuses[acme.StatusValid]
|
||||||
|
|
||||||
// If the order isn't fully authorized we've encountered an internal error:
|
// If the order isn't fully authorized we've encountered an internal error:
|
||||||
// Above we checked for any invalid or pending authzs and should have returned
|
// Above we checked for any invalid or pending authzs and should have returned
|
||||||
|
|
|
||||||
56
va/va.go
56
va/va.go
|
|
@ -87,7 +87,7 @@ func certNames(cert *x509.Certificate) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type vaTask struct {
|
type vaTask struct {
|
||||||
Identifier string
|
Identifier acme.Identifier
|
||||||
Challenge *core.Challenge
|
Challenge *core.Challenge
|
||||||
Account *core.Account
|
Account *core.Account
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +144,7 @@ func New(
|
||||||
return va
|
return va
|
||||||
}
|
}
|
||||||
|
|
||||||
func (va VAImpl) ValidateChallenge(ident string, chal *core.Challenge, acct *core.Account) {
|
func (va VAImpl) ValidateChallenge(ident acme.Identifier, chal *core.Challenge, acct *core.Account) {
|
||||||
task := &vaTask{
|
task := &vaTask{
|
||||||
Identifier: ident,
|
Identifier: ident,
|
||||||
Challenge: chal,
|
Challenge: chal,
|
||||||
|
|
@ -272,7 +272,7 @@ func (va VAImpl) performValidation(task *vaTask, results chan<- *core.Validation
|
||||||
// type. For example comparison, a real DNS-01 validation would set
|
// type. For example comparison, a real DNS-01 validation would set
|
||||||
// the URL to the `_acme-challenge` subdomain.
|
// the URL to the `_acme-challenge` subdomain.
|
||||||
results <- &core.ValidationRecord{
|
results <- &core.ValidationRecord{
|
||||||
URL: task.Identifier,
|
URL: task.Identifier.Value,
|
||||||
ValidatedAt: time.Now(),
|
ValidatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
@ -333,15 +333,21 @@ func (va VAImpl) validateDNS01(task *vaTask) *core.ValidationRecord {
|
||||||
|
|
||||||
func (va VAImpl) validateTLSALPN01(task *vaTask) *core.ValidationRecord {
|
func (va VAImpl) validateTLSALPN01(task *vaTask) *core.ValidationRecord {
|
||||||
portString := strconv.Itoa(va.tlsPort)
|
portString := strconv.Itoa(va.tlsPort)
|
||||||
hostPort := net.JoinHostPort(task.Identifier, portString)
|
hostPort := net.JoinHostPort(task.Identifier.Value, portString)
|
||||||
|
var serverNameIdentifier string
|
||||||
|
switch task.Identifier.Type {
|
||||||
|
case acme.IdentifierDNS:
|
||||||
|
serverNameIdentifier = task.Identifier.Value
|
||||||
|
case acme.IdentifierIP:
|
||||||
|
serverNameIdentifier = reverseaddr(task.Identifier.Value)
|
||||||
|
}
|
||||||
result := &core.ValidationRecord{
|
result := &core.ValidationRecord{
|
||||||
URL: hostPort,
|
URL: hostPort,
|
||||||
ValidatedAt: time.Now(),
|
ValidatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
cs, problem := va.fetchConnectionState(hostPort, &tls.Config{
|
cs, problem := va.fetchConnectionState(hostPort, &tls.Config{
|
||||||
ServerName: task.Identifier,
|
ServerName: serverNameIdentifier,
|
||||||
NextProtos: []string{acme.ACMETLS1Protocol},
|
NextProtos: []string{acme.ACMETLS1Protocol},
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
})
|
})
|
||||||
|
|
@ -367,7 +373,16 @@ func (va VAImpl) validateTLSALPN01(task *vaTask) *core.ValidationRecord {
|
||||||
leafCert := certs[0]
|
leafCert := certs[0]
|
||||||
|
|
||||||
// Verify SNI - certificate returned must be issued only for the domain we are verifying.
|
// Verify SNI - certificate returned must be issued only for the domain we are verifying.
|
||||||
if len(leafCert.DNSNames) != 1 || !strings.EqualFold(leafCert.DNSNames[0], task.Identifier) {
|
var namematch bool
|
||||||
|
switch task.Identifier.Type {
|
||||||
|
case acme.IdentifierDNS:
|
||||||
|
namematch = len(leafCert.DNSNames) == 1 && strings.EqualFold(leafCert.DNSNames[0], task.Identifier.Value)
|
||||||
|
case acme.IdentifierIP:
|
||||||
|
namematch = len(leafCert.IPAddresses) == 1 && leafCert.IPAddresses[0].Equal(net.ParseIP(task.Identifier.Value))
|
||||||
|
default:
|
||||||
|
namematch = false
|
||||||
|
}
|
||||||
|
if !namematch {
|
||||||
names := certNames(leafCert)
|
names := certNames(leafCert)
|
||||||
errText := fmt.Sprintf(
|
errText := fmt.Sprintf(
|
||||||
"Incorrect validation certificate for %s challenge. "+
|
"Incorrect validation certificate for %s challenge. "+
|
||||||
|
|
@ -430,7 +445,7 @@ func (va VAImpl) fetchConnectionState(hostPort string, config *tls.Config) (*tls
|
||||||
}
|
}
|
||||||
|
|
||||||
func (va VAImpl) validateHTTP01(task *vaTask) *core.ValidationRecord {
|
func (va VAImpl) validateHTTP01(task *vaTask) *core.ValidationRecord {
|
||||||
body, url, err := va.fetchHTTP(task.Identifier, task.Challenge.Token)
|
body, url, err := va.fetchHTTP(task.Identifier.Value, task.Challenge.Token)
|
||||||
|
|
||||||
result := &core.ValidationRecord{
|
result := &core.ValidationRecord{
|
||||||
URL: url,
|
URL: url,
|
||||||
|
|
@ -458,10 +473,11 @@ func (va VAImpl) validateHTTP01(task *vaTask) *core.ValidationRecord {
|
||||||
// purpose HTTP function
|
// purpose HTTP function
|
||||||
func (va VAImpl) fetchHTTP(identifier string, token string) ([]byte, string, *acme.ProblemDetails) {
|
func (va VAImpl) fetchHTTP(identifier string, token string) ([]byte, string, *acme.ProblemDetails) {
|
||||||
path := fmt.Sprintf("%s%s", acme.HTTP01BaseURL, token)
|
path := fmt.Sprintf("%s%s", acme.HTTP01BaseURL, token)
|
||||||
|
portString := strconv.Itoa(va.httpPort)
|
||||||
|
|
||||||
url := &url.URL{
|
url := &url.URL{
|
||||||
Scheme: "http",
|
Scheme: "http",
|
||||||
Host: fmt.Sprintf("%s:%d", identifier, va.httpPort),
|
Host: net.JoinHostPort(identifier, portString),
|
||||||
Path: path,
|
Path: path,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -517,3 +533,25 @@ func (va VAImpl) fetchHTTP(identifier string, token string) ([]byte, string, *ac
|
||||||
|
|
||||||
return body, url.String(), nil
|
return body, url.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reverseaddr function is borrowed from net/dnsclient.go[0] and the Go std library.
|
||||||
|
// [0]: https://golang.org/src/net/dnsclient.go
|
||||||
|
func reverseaddr(addr string) string {
|
||||||
|
ip := net.ParseIP(addr)
|
||||||
|
if ip == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// Apperently IP type in net package saves all ip in ipv6 formant, from biggest byte to smallest. we need last 4 bytes, so ip[15] to ip[12]
|
||||||
|
if ip.To4() != nil {
|
||||||
|
return fmt.Sprintf("%d.%d.%d.%d.in-addr.arpa.", ip[15], ip[14], ip[13], ip[12])
|
||||||
|
}
|
||||||
|
// Must be IPv6
|
||||||
|
buf := make([]string, 0, len(ip)+1)
|
||||||
|
// Add it, in reverse, to the buffer
|
||||||
|
for i := len(ip) - 1; i >= 0; i-- {
|
||||||
|
buf = append(buf, fmt.Sprintf("%x.%x", ip[i]&0x0F, ip[i]>>4))
|
||||||
|
}
|
||||||
|
// Append "ip6.arpa." and return (buf already has the final '.') see RFC3152 for how this address is constructed.
|
||||||
|
buf = append(buf, "ip6.arpa.")
|
||||||
|
return strings.Join(buf, ".")
|
||||||
|
}
|
||||||
|
|
|
||||||
138
wfe/wfe.go
138
wfe/wfe.go
|
|
@ -1,6 +1,7 @@
|
||||||
package wfe
|
package wfe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
|
@ -1013,11 +1014,20 @@ func (wfe *WebFrontEndImpl) verifyOrder(order *core.Order) *acme.ProblemDetails
|
||||||
if len(idents) == 0 {
|
if len(idents) == 0 {
|
||||||
return acme.MalformedProblem("Order did not specify any identifiers")
|
return acme.MalformedProblem("Order did not specify any identifiers")
|
||||||
}
|
}
|
||||||
// Check that all of the identifiers in the new-order are DNS type
|
// Check that all of the identifiers in the new-order are DNS or IPaddress type
|
||||||
|
// Validity check of ipaddresses are done here.
|
||||||
for _, ident := range idents {
|
for _, ident := range idents {
|
||||||
|
if ident.Type == acme.IdentifierIP {
|
||||||
|
if net.ParseIP(ident.Value) == nil {
|
||||||
|
return acme.MalformedProblem(fmt.Sprintf(
|
||||||
|
"Order included malformed IP type identifier value: %q\n",
|
||||||
|
ident.Value))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
if ident.Type != acme.IdentifierDNS {
|
if ident.Type != acme.IdentifierDNS {
|
||||||
return acme.MalformedProblem(fmt.Sprintf(
|
return acme.MalformedProblem(fmt.Sprintf(
|
||||||
"Order included non-DNS type identifier: type %q, value %q",
|
"Order included unsupported type identifier: type %q, value %q",
|
||||||
ident.Type, ident.Value))
|
ident.Type, ident.Value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1083,12 +1093,12 @@ func (wfe *WebFrontEndImpl) makeAuthorizations(order *core.Order, request *http.
|
||||||
// Lock the order for reading
|
// Lock the order for reading
|
||||||
order.RLock()
|
order.RLock()
|
||||||
// Create one authz for each name in the order's parsed CSR
|
// Create one authz for each name in the order's parsed CSR
|
||||||
for _, name := range order.Names {
|
for _, name := range order.Identifiers {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
expires := now.Add(pendingAuthzExpire)
|
expires := now.Add(pendingAuthzExpire)
|
||||||
ident := acme.Identifier{
|
ident := acme.Identifier{
|
||||||
Type: acme.IdentifierDNS,
|
Type: name.Type,
|
||||||
Value: name,
|
Value: name.Value,
|
||||||
}
|
}
|
||||||
authz := &core.Authorization{
|
authz := &core.Authorization{
|
||||||
ID: newToken(),
|
ID: newToken(),
|
||||||
|
|
@ -1166,8 +1176,14 @@ func (wfe *WebFrontEndImpl) makeChallenges(authz *core.Authorization, request *h
|
||||||
}
|
}
|
||||||
chals = []*core.Challenge{chal}
|
chals = []*core.Challenge{chal}
|
||||||
} else {
|
} else {
|
||||||
// Non-wildcard authorizations get all of the enabled challenge types
|
// IP addresses get HTTP-01 and TLS-ALPN challenges
|
||||||
enabledChallenges := []string{acme.ChallengeHTTP01, acme.ChallengeTLSALPN01, acme.ChallengeDNS01}
|
var enabledChallenges []string
|
||||||
|
if authz.Identifier.Value == acme.IdentifierIP {
|
||||||
|
enabledChallenges = []string{acme.ChallengeHTTP01, acme.ChallengeTLSALPN01}
|
||||||
|
} else {
|
||||||
|
// Non-wildcard, non-IP identifier authorizations get all of the enabled challenge types
|
||||||
|
enabledChallenges = []string{acme.ChallengeHTTP01, acme.ChallengeTLSALPN01, acme.ChallengeDNS01}
|
||||||
|
}
|
||||||
for _, chalType := range enabledChallenges {
|
for _, chalType := range enabledChallenges {
|
||||||
chal, err := wfe.makeChallenge(chalType, authz, request)
|
chal, err := wfe.makeChallenge(chalType, authz, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1211,6 +1227,28 @@ func (wfe *WebFrontEndImpl) NewOrder(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var orderDNSs []string
|
||||||
|
var orderIPs []net.IP
|
||||||
|
for _, ident := range newOrder.Identifiers {
|
||||||
|
switch ident.Type {
|
||||||
|
case acme.IdentifierDNS:
|
||||||
|
orderDNSs = append(orderDNSs, ident.Value)
|
||||||
|
case acme.IdentifierIP:
|
||||||
|
orderIPs = append(orderIPs, net.ParseIP(ident.Value))
|
||||||
|
default:
|
||||||
|
wfe.sendError(acme.MalformedProblem(
|
||||||
|
fmt.Sprintf("Order includes unknown identifier type %s", ident.Type)), response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orderDNSs = uniqueLowerNames(orderDNSs)
|
||||||
|
orderIPs = uniqueIPs(orderIPs)
|
||||||
|
var uniquenames []acme.Identifier
|
||||||
|
for _, name := range orderDNSs {
|
||||||
|
uniquenames = append(uniquenames, acme.Identifier{Value: name, Type: acme.IdentifierDNS})
|
||||||
|
}
|
||||||
|
for _, ip := range orderIPs {
|
||||||
|
uniquenames = append(uniquenames, acme.Identifier{Value: ip.String(), Type: acme.IdentifierIP})
|
||||||
|
}
|
||||||
expires := time.Now().AddDate(0, 0, 1)
|
expires := time.Now().AddDate(0, 0, 1)
|
||||||
order := &core.Order{
|
order := &core.Order{
|
||||||
ID: newToken(),
|
ID: newToken(),
|
||||||
|
|
@ -1220,7 +1258,7 @@ func (wfe *WebFrontEndImpl) NewOrder(
|
||||||
Expires: expires.UTC().Format(time.RFC3339),
|
Expires: expires.UTC().Format(time.RFC3339),
|
||||||
// Only the Identifiers, NotBefore and NotAfter from the submitted order
|
// Only the Identifiers, NotBefore and NotAfter from the submitted order
|
||||||
// are carried forward
|
// are carried forward
|
||||||
Identifiers: newOrder.Identifiers,
|
Identifiers: uniquenames,
|
||||||
NotBefore: newOrder.NotBefore,
|
NotBefore: newOrder.NotBefore,
|
||||||
NotAfter: newOrder.NotAfter,
|
NotAfter: newOrder.NotAfter,
|
||||||
},
|
},
|
||||||
|
|
@ -1233,15 +1271,6 @@ func (wfe *WebFrontEndImpl) NewOrder(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all of the DNS identifier values up into a []string
|
|
||||||
var orderNames []string
|
|
||||||
for _, ident := range order.Identifiers {
|
|
||||||
orderNames = append(orderNames, ident.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the unique lower version of the names on the order object
|
|
||||||
order.Names = uniqueLowerNames(orderNames)
|
|
||||||
|
|
||||||
// Create the authorizations for the order
|
// Create the authorizations for the order
|
||||||
err = wfe.makeAuthorizations(order, request)
|
err = wfe.makeAuthorizations(order, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1397,7 +1426,7 @@ func (wfe *WebFrontEndImpl) FinalizeOrder(
|
||||||
orderAccountID := existingOrder.AccountID
|
orderAccountID := existingOrder.AccountID
|
||||||
orderStatus := existingOrder.Status
|
orderStatus := existingOrder.Status
|
||||||
orderExpires := existingOrder.ExpiresDate
|
orderExpires := existingOrder.ExpiresDate
|
||||||
orderNames := existingOrder.Names
|
orderIdentifiers := existingOrder.Identifiers
|
||||||
// And then immediately unlock it again - we don't defer() here because
|
// And then immediately unlock it again - we don't defer() here because
|
||||||
// `maybeIssue` will also acquire a read lock and we call that before
|
// `maybeIssue` will also acquire a read lock and we call that before
|
||||||
// returning
|
// returning
|
||||||
|
|
@ -1450,22 +1479,55 @@ func (wfe *WebFrontEndImpl) FinalizeOrder(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// split order identifiers per types
|
||||||
|
var orderDNSs []string
|
||||||
|
var orderIPs []net.IP
|
||||||
|
for _, ident := range orderIdentifiers {
|
||||||
|
switch ident.Type {
|
||||||
|
case acme.IdentifierDNS:
|
||||||
|
orderDNSs = append(orderDNSs, ident.Value)
|
||||||
|
case acme.IdentifierIP:
|
||||||
|
orderIPs = append(orderIPs, net.ParseIP(ident.Value))
|
||||||
|
default:
|
||||||
|
wfe.sendError(acme.MalformedProblem(
|
||||||
|
fmt.Sprintf("Order includes unknown identifier type %s", ident.Type)), response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// looks like saving order to db doesn't preserve order of Identifiers, so sort them again.
|
||||||
|
orderDNSs = uniqueLowerNames(orderDNSs)
|
||||||
|
orderIPs = uniqueIPs(orderIPs)
|
||||||
|
|
||||||
|
// sort and deduplicate CSR SANs
|
||||||
|
csrDNSs := uniqueLowerNames(parsedCSR.DNSNames)
|
||||||
|
csrIPs := uniqueIPs(parsedCSR.IPAddresses)
|
||||||
|
|
||||||
// Check that the CSR has the same number of names as the initial order contained
|
// Check that the CSR has the same number of names as the initial order contained
|
||||||
csrNames := uniqueLowerNames(parsedCSR.DNSNames)
|
if len(csrDNSs) != len(orderDNSs) {
|
||||||
if len(csrNames) != len(orderNames) {
|
|
||||||
wfe.sendError(acme.UnauthorizedProblem(
|
wfe.sendError(acme.UnauthorizedProblem(
|
||||||
"Order includes different number of names than CSR specifies"), response)
|
"Order includes different number of DNSnames identifiers than CSR specifies"), response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(csrIPs) != len(orderIPs) {
|
||||||
|
wfe.sendError(acme.UnauthorizedProblem(
|
||||||
|
"Order includes different number of IP address identifiers than CSR specifies"), response)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the CSR's names match the order names exactly
|
// Check that the CSR's names match the order names exactly
|
||||||
for i, name := range orderNames {
|
for i, name := range orderDNSs {
|
||||||
if name != csrNames[i] {
|
if name != csrDNSs[i] {
|
||||||
wfe.sendError(acme.UnauthorizedProblem(
|
wfe.sendError(acme.UnauthorizedProblem(
|
||||||
fmt.Sprintf("CSR is missing Order domain %q", name)), response)
|
fmt.Sprintf("CSR is missing Order domain %q", name)), response)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for i, IP := range orderIPs {
|
||||||
|
if !csrIPs[i].Equal(IP) {
|
||||||
|
wfe.sendError(acme.UnauthorizedProblem(
|
||||||
|
fmt.Sprintf("CSR is missing Order IP %q", IP)), response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Lock and update the order with the parsed CSR and the began processing
|
// Lock and update the order with the parsed CSR and the began processing
|
||||||
// state.
|
// state.
|
||||||
|
|
@ -1718,12 +1780,11 @@ func (wfe *WebFrontEndImpl) validateAuthzForChallenge(authz *core.Authorization)
|
||||||
defer authz.RUnlock()
|
defer authz.RUnlock()
|
||||||
|
|
||||||
ident := authz.Identifier
|
ident := authz.Identifier
|
||||||
if ident.Type != acme.IdentifierDNS {
|
if ident.Type != acme.IdentifierDNS && ident.Type != acme.IdentifierIP {
|
||||||
return nil, acme.MalformedProblem(
|
return nil, acme.MalformedProblem(
|
||||||
fmt.Sprintf("Authorization identifier was type %s, only %s is supported",
|
fmt.Sprintf("Authorization identifier was type %s, only %s and %s are supported",
|
||||||
ident.Type, acme.IdentifierDNS))
|
ident.Type, acme.IdentifierDNS, acme.IdentifierIP))
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.After(authz.ExpiresDate) {
|
if now.After(authz.ExpiresDate) {
|
||||||
return nil, acme.MalformedProblem(
|
return nil, acme.MalformedProblem(
|
||||||
|
|
@ -1825,14 +1886,14 @@ func (wfe *WebFrontEndImpl) updateChallenge(
|
||||||
|
|
||||||
// Lock the authorization to get the identifier value
|
// Lock the authorization to get the identifier value
|
||||||
authz.RLock()
|
authz.RLock()
|
||||||
ident := authz.Identifier.Value
|
ident := authz.Identifier
|
||||||
authz.RUnlock()
|
authz.RUnlock()
|
||||||
|
|
||||||
// If the identifier value is for a wildcard domain then strip the wildcard
|
// If the identifier value is for a wildcard domain then strip the wildcard
|
||||||
// prefix before dispatching the validation to ensure the base domain is
|
// prefix before dispatching the validation to ensure the base domain is
|
||||||
// validated.
|
// validated.
|
||||||
if strings.HasPrefix(ident, "*.") {
|
if strings.HasPrefix(ident.Value, "*.") {
|
||||||
ident = strings.TrimPrefix(ident, "*.")
|
ident.Value = strings.TrimPrefix(ident.Value, "*.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit a validation job to the VA, this will be processed asynchronously
|
// Submit a validation job to the VA, this will be processed asynchronously
|
||||||
|
|
@ -1926,6 +1987,23 @@ func uniqueLowerNames(names []string) []string {
|
||||||
return unique
|
return unique
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// uniqueIPs returns the set of all unique IP addresses in the input.
|
||||||
|
// The returned IP addresses will be sorted in ascending order in text form.
|
||||||
|
func uniqueIPs(IPs []net.IP) []net.IP {
|
||||||
|
uniqMap := make(map[string]net.IP)
|
||||||
|
for _, ip := range IPs {
|
||||||
|
uniqMap[ip.String()] = ip
|
||||||
|
}
|
||||||
|
results := make([]net.IP, len(uniqMap))
|
||||||
|
for _, v := range uniqMap {
|
||||||
|
results = append(results, v)
|
||||||
|
}
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
return bytes.Compare(results[i], results[j]) < 0
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
// RevokeCert revokes an ACME certificate.
|
// RevokeCert revokes an ACME certificate.
|
||||||
// It currently only implements one method of ACME revocation:
|
// It currently only implements one method of ACME revocation:
|
||||||
// Signing the revocation request by signing it with the certificate
|
// Signing the revocation request by signing it with the certificate
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue