460 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			460 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
// 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 ra
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/x509"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"net/mail"
 | 
						|
	"net/url"
 | 
						|
	"regexp"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/letsencrypt/boulder/core"
 | 
						|
	blog "github.com/letsencrypt/boulder/log"
 | 
						|
	"github.com/letsencrypt/boulder/policy"
 | 
						|
)
 | 
						|
 | 
						|
// RegistrationAuthorityImpl defines an RA.
 | 
						|
//
 | 
						|
// NOTE: All of the fields in RegistrationAuthorityImpl need to be
 | 
						|
// populated, or there is a risk of panic.
 | 
						|
type RegistrationAuthorityImpl struct {
 | 
						|
	CA          core.CertificateAuthority
 | 
						|
	VA          core.ValidationAuthority
 | 
						|
	SA          core.StorageAuthority
 | 
						|
	PA          core.PolicyAuthority
 | 
						|
	DNSResolver core.DNSResolver
 | 
						|
	log         *blog.AuditLogger
 | 
						|
 | 
						|
	AuthzBase  string
 | 
						|
	MaxKeySize int
 | 
						|
}
 | 
						|
 | 
						|
// NewRegistrationAuthorityImpl constructs a new RA object.
 | 
						|
func NewRegistrationAuthorityImpl() RegistrationAuthorityImpl {
 | 
						|
	logger := blog.GetAuditLogger()
 | 
						|
	logger.Notice("Registration Authority Starting")
 | 
						|
 | 
						|
	ra := RegistrationAuthorityImpl{log: logger}
 | 
						|
	ra.PA = policy.NewPolicyAuthorityImpl()
 | 
						|
	return ra
 | 
						|
}
 | 
						|
 | 
						|
var allButLastPathSegment = regexp.MustCompile("^.*/")
 | 
						|
 | 
						|
func lastPathSegment(url core.AcmeURL) string {
 | 
						|
	return allButLastPathSegment.ReplaceAllString(url.Path, "")
 | 
						|
}
 | 
						|
 | 
						|
func validateEmail(address string, resolver core.DNSResolver) (err error) {
 | 
						|
	_, err = mail.ParseAddress(address)
 | 
						|
	if err != nil {
 | 
						|
		err = core.MalformedRequestError(fmt.Sprintf("%s is not a valid e-mail address", address))
 | 
						|
		return
 | 
						|
	}
 | 
						|
	splitEmail := strings.SplitN(address, "@", -1)
 | 
						|
	domain := strings.ToLower(splitEmail[len(splitEmail)-1])
 | 
						|
	var mx []string
 | 
						|
	mx, _, err = resolver.LookupMX(domain)
 | 
						|
	if err != nil || len(mx) == 0 {
 | 
						|
		err = core.MalformedRequestError(fmt.Sprintf("No MX record for domain %s", domain))
 | 
						|
		return
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func validateContacts(contacts []core.AcmeURL, resolver core.DNSResolver) (err error) {
 | 
						|
	for _, contact := range contacts {
 | 
						|
		switch contact.Scheme {
 | 
						|
		case "tel":
 | 
						|
			continue
 | 
						|
		case "mailto":
 | 
						|
			err = validateEmail(contact.Opaque, resolver)
 | 
						|
			if err != nil {
 | 
						|
				return
 | 
						|
			}
 | 
						|
		default:
 | 
						|
			err = core.MalformedRequestError(fmt.Sprintf("Contact method %s is not supported", contact.Scheme))
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
type certificateRequestEvent struct {
 | 
						|
	ID                  string    `json:",omitempty"`
 | 
						|
	Requester           int64     `json:",omitempty"`
 | 
						|
	SerialNumber        string    `json:",omitempty"`
 | 
						|
	RequestMethod       string    `json:",omitempty"`
 | 
						|
	VerificationMethods []string  `json:",omitempty"`
 | 
						|
	VerifiedFields      []string  `json:",omitempty"`
 | 
						|
	CommonName          string    `json:",omitempty"`
 | 
						|
	Names               []string  `json:",omitempty"`
 | 
						|
	NotBefore           time.Time `json:",omitempty"`
 | 
						|
	NotAfter            time.Time `json:",omitempty"`
 | 
						|
	RequestTime         time.Time `json:",omitempty"`
 | 
						|
	ResponseTime        time.Time `json:",omitempty"`
 | 
						|
	Error               string    `json:",omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// 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, ra.MaxKeySize); err != nil {
 | 
						|
		return core.Registration{}, core.MalformedRequestError(fmt.Sprintf("Invalid public key: %s", err.Error()))
 | 
						|
	}
 | 
						|
	reg = core.Registration{
 | 
						|
		RecoveryToken: core.NewToken(),
 | 
						|
		Key:           init.Key,
 | 
						|
	}
 | 
						|
	reg.MergeUpdate(init)
 | 
						|
 | 
						|
	err = validateContacts(reg.Contact, ra.DNSResolver)
 | 
						|
	if err != nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Store the authorization object, then return it
 | 
						|
	reg, err = ra.SA.NewRegistration(reg)
 | 
						|
	if err != nil {
 | 
						|
		// InternalServerError since the user-data was validated before being
 | 
						|
		// passed to the SA.
 | 
						|
		err = core.InternalServerError(err.Error())
 | 
						|
	}
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// NewAuthorization constuct a new Authz from a request.
 | 
						|
func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization, regID int64) (authz core.Authorization, err error) {
 | 
						|
	if regID <= 0 {
 | 
						|
		err = core.MalformedRequestError(fmt.Sprintf("Invalid registration ID: %d", regID))
 | 
						|
		return authz, err
 | 
						|
	}
 | 
						|
 | 
						|
	identifier := request.Identifier
 | 
						|
 | 
						|
	// Check that the identifier is present and appropriate
 | 
						|
	if err = ra.PA.WillingToIssue(identifier); err != nil {
 | 
						|
		err = core.UnauthorizedError(err.Error())
 | 
						|
		return authz, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Check CAA records for the requested identifier
 | 
						|
	present, valid, err := ra.VA.CheckCAARecords(identifier)
 | 
						|
	if err != nil {
 | 
						|
		return authz, err
 | 
						|
	}
 | 
						|
	// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
 | 
						|
	ra.log.Audit(fmt.Sprintf("Checked CAA records for %s, registration ID %d [Present: %v, Valid for issuance: %v]", identifier.Value, regID, present, valid))
 | 
						|
	if !valid {
 | 
						|
		err = errors.New("CAA check for identifier failed")
 | 
						|
		return authz, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Create validations, but we have to update them with URIs later
 | 
						|
	challenges, combinations := ra.PA.ChallengesFor(identifier)
 | 
						|
 | 
						|
	// Partially-filled object
 | 
						|
	authz = core.Authorization{
 | 
						|
		Identifier:     identifier,
 | 
						|
		RegistrationID: regID,
 | 
						|
		Status:         core.StatusPending,
 | 
						|
		Combinations:   combinations,
 | 
						|
	}
 | 
						|
 | 
						|
	// Get a pending Auth first so we can get our ID back, then update with challenges
 | 
						|
	authz, err = ra.SA.NewPendingAuthorization(authz)
 | 
						|
	if err != nil {
 | 
						|
		// InternalServerError since the user-data was validated before being
 | 
						|
		// passed to the SA.
 | 
						|
		err = core.InternalServerError(fmt.Sprintf("Invalid authorization request: %s", err))
 | 
						|
		return authz, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Construct all the challenge URIs
 | 
						|
	for i := range challenges {
 | 
						|
		// Ignoring these errors because we construct the URLs to be correct
 | 
						|
		challengeURI, _ := url.Parse(ra.AuthzBase + authz.ID + "?challenge=" + strconv.Itoa(i))
 | 
						|
		challenges[i].URI = core.AcmeURL(*challengeURI)
 | 
						|
 | 
						|
		if !challenges[i].IsSane(false) {
 | 
						|
			// InternalServerError because we generated these challenges, they should
 | 
						|
			// be OK.
 | 
						|
			err = core.InternalServerError(fmt.Sprintf("Challenge didn't pass sanity check: %+v", challenges[i]))
 | 
						|
			return authz, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Update object
 | 
						|
	authz.Challenges = challenges
 | 
						|
 | 
						|
	// Store the authorization object, then return it
 | 
						|
	err = ra.SA.UpdatePendingAuthorization(authz)
 | 
						|
	if err != nil {
 | 
						|
		// InternalServerError because we created the authorization just above,
 | 
						|
		// and adding Sane challenges should not break it.
 | 
						|
		err = core.InternalServerError(err.Error())
 | 
						|
	}
 | 
						|
	return authz, err
 | 
						|
}
 | 
						|
 | 
						|
// NewCertificate requests the issuance of a certificate.
 | 
						|
func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest, regID int64) (cert core.Certificate, err error) {
 | 
						|
	emptyCert := core.Certificate{}
 | 
						|
	var logEventResult string
 | 
						|
 | 
						|
	// Assume the worst
 | 
						|
	logEventResult = "error"
 | 
						|
 | 
						|
	// Construct the log event
 | 
						|
	logEvent := certificateRequestEvent{
 | 
						|
		ID:            core.NewToken(),
 | 
						|
		Requester:     regID,
 | 
						|
		RequestMethod: "online",
 | 
						|
		RequestTime:   time.Now(),
 | 
						|
	}
 | 
						|
 | 
						|
	// No matter what, log the request
 | 
						|
	defer func() {
 | 
						|
		// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
 | 
						|
		ra.log.AuditObject(fmt.Sprintf("Certificate request - %s", logEventResult), logEvent)
 | 
						|
	}()
 | 
						|
 | 
						|
	if regID <= 0 {
 | 
						|
		err = core.MalformedRequestError(fmt.Sprintf("Invalid registration ID: %d", regID))
 | 
						|
		return emptyCert, err
 | 
						|
	}
 | 
						|
 | 
						|
	registration, err := ra.SA.GetRegistration(regID)
 | 
						|
	if err != nil {
 | 
						|
		logEvent.Error = err.Error()
 | 
						|
		return emptyCert, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Verify the CSR
 | 
						|
	csr := req.CSR
 | 
						|
	if err = core.VerifyCSR(csr); err != nil {
 | 
						|
		logEvent.Error = err.Error()
 | 
						|
		err = core.UnauthorizedError("Invalid signature on CSR")
 | 
						|
		return emptyCert, err
 | 
						|
	}
 | 
						|
 | 
						|
	logEvent.CommonName = csr.Subject.CommonName
 | 
						|
	logEvent.Names = csr.DNSNames
 | 
						|
 | 
						|
	// Validate that authorization key is authorized for all domains
 | 
						|
	names := make([]string, len(csr.DNSNames))
 | 
						|
	copy(names, csr.DNSNames)
 | 
						|
	if len(csr.Subject.CommonName) > 0 {
 | 
						|
		names = append(names, csr.Subject.CommonName)
 | 
						|
	}
 | 
						|
 | 
						|
	if len(names) == 0 {
 | 
						|
		err = core.UnauthorizedError("CSR has no names in it")
 | 
						|
		logEvent.Error = err.Error()
 | 
						|
		return emptyCert, err
 | 
						|
	}
 | 
						|
 | 
						|
	csrPreviousDenied, err := ra.SA.AlreadyDeniedCSR(names)
 | 
						|
	if err != nil {
 | 
						|
		logEvent.Error = err.Error()
 | 
						|
		return emptyCert, err
 | 
						|
	}
 | 
						|
	if csrPreviousDenied {
 | 
						|
		err = core.UnauthorizedError("CSR has already been revoked/denied")
 | 
						|
		logEvent.Error = err.Error()
 | 
						|
		return emptyCert, err
 | 
						|
	}
 | 
						|
 | 
						|
	if core.KeyDigestEquals(csr.PublicKey, registration.Key) {
 | 
						|
		err = core.MalformedRequestError("Certificate public key must be different than account key")
 | 
						|
		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)
 | 
						|
	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
 | 
						|
		}
 | 
						|
 | 
						|
		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
 | 
						|
	logEvent.VerifiedFields = []string{"subject.commonName", "subjectAltName"}
 | 
						|
 | 
						|
	// Create the certificate and log the result
 | 
						|
	if cert, err = ra.CA.IssueCertificate(*csr, regID, earliestExpiry); err != nil {
 | 
						|
		// While this could be InternalServerError for certain conditions, most
 | 
						|
		// of the failure reasons (such as GoodKey failing) are caused by malformed
 | 
						|
		// requests.
 | 
						|
		logEvent.Error = err.Error()
 | 
						|
		err = core.MalformedRequestError("Certificate request was invalid")
 | 
						|
		return emptyCert, err
 | 
						|
	}
 | 
						|
 | 
						|
	err = cert.MatchesCSR(csr, earliestExpiry)
 | 
						|
	if err != nil {
 | 
						|
		logEvent.Error = err.Error()
 | 
						|
		return emptyCert, err
 | 
						|
	}
 | 
						|
 | 
						|
	parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
 | 
						|
	if err != nil {
 | 
						|
		// InternalServerError because the certificate from the CA should be
 | 
						|
		// parseable.
 | 
						|
		err = core.InternalServerError(err.Error())
 | 
						|
		logEvent.Error = err.Error()
 | 
						|
		return emptyCert, err
 | 
						|
	}
 | 
						|
 | 
						|
	logEvent.SerialNumber = core.SerialToString(parsedCertificate.SerialNumber)
 | 
						|
	logEvent.CommonName = parsedCertificate.Subject.CommonName
 | 
						|
	logEvent.NotBefore = parsedCertificate.NotBefore
 | 
						|
	logEvent.NotAfter = parsedCertificate.NotAfter
 | 
						|
	logEvent.ResponseTime = time.Now()
 | 
						|
 | 
						|
	logEventResult = "successful"
 | 
						|
	return cert, nil
 | 
						|
}
 | 
						|
 | 
						|
// UpdateRegistration updates an existing Registration with new values.
 | 
						|
func (ra *RegistrationAuthorityImpl) UpdateRegistration(base core.Registration, update core.Registration) (reg core.Registration, err error) {
 | 
						|
	base.MergeUpdate(update)
 | 
						|
 | 
						|
	err = validateContacts(base.Contact, ra.DNSResolver)
 | 
						|
	if err != nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	reg = base
 | 
						|
	err = ra.SA.UpdateRegistration(base)
 | 
						|
	if err != nil {
 | 
						|
		// InternalServerError since the user-data was validated before being
 | 
						|
		// passed to the SA.
 | 
						|
		err = core.InternalServerError(fmt.Sprintf("Could not update registration: %s", err))
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// UpdateAuthorization updates an authorization with new values.
 | 
						|
func (ra *RegistrationAuthorityImpl) UpdateAuthorization(base core.Authorization, challengeIndex int, response core.Challenge) (authz core.Authorization, err error) {
 | 
						|
	// Copy information over that the client is allowed to supply
 | 
						|
	authz = base
 | 
						|
	if challengeIndex >= len(authz.Challenges) {
 | 
						|
		err = core.MalformedRequestError(fmt.Sprintf("Invalid challenge index: %d", challengeIndex))
 | 
						|
		return
 | 
						|
	}
 | 
						|
	authz.Challenges[challengeIndex] = authz.Challenges[challengeIndex].MergeResponse(response)
 | 
						|
 | 
						|
	// Store the updated version
 | 
						|
	if err = ra.SA.UpdatePendingAuthorization(authz); err != nil {
 | 
						|
		// This can pretty much only happen when the client corrupts the Challenge
 | 
						|
		// data.
 | 
						|
		err = core.MalformedRequestError("Challenge data was corrupted")
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Dispatch to the VA for service
 | 
						|
	ra.VA.UpdateValidations(authz, challengeIndex)
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// RevokeCertificate terminates trust in the certificate provided.
 | 
						|
func (ra *RegistrationAuthorityImpl) RevokeCertificate(cert x509.Certificate) (err error) {
 | 
						|
	serialString := core.SerialToString(cert.SerialNumber)
 | 
						|
	err = ra.CA.RevokeCertificate(serialString, 0)
 | 
						|
 | 
						|
	// AUDIT[ Revocation Requests ] 4e85d791-09c0-4ab3-a837-d3d67e945134
 | 
						|
	if err != nil {
 | 
						|
		ra.log.Audit(fmt.Sprintf("Revocation error - %s - %s", serialString, err))
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	ra.log.Audit(fmt.Sprintf("Revocation - %s", serialString))
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// OnValidationUpdate is called when a given Authorization is updated by the VA.
 | 
						|
func (ra *RegistrationAuthorityImpl) OnValidationUpdate(authz core.Authorization) error {
 | 
						|
	// Consider validation successful if any of the combinations
 | 
						|
	// specified in the authorization has been fulfilled
 | 
						|
	validated := map[int]bool{}
 | 
						|
	for i, ch := range authz.Challenges {
 | 
						|
		if ch.Status == core.StatusValid {
 | 
						|
			validated[i] = true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for _, combo := range authz.Combinations {
 | 
						|
		comboValid := true
 | 
						|
		for _, i := range combo {
 | 
						|
			if !validated[i] {
 | 
						|
				comboValid = false
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if comboValid {
 | 
						|
			authz.Status = core.StatusValid
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// If no validation succeeded, then the authorization is invalid
 | 
						|
	// NOTE: This only works because we only ever do one validation
 | 
						|
	if authz.Status != core.StatusValid {
 | 
						|
		authz.Status = core.StatusInvalid
 | 
						|
	} else {
 | 
						|
		// TODO: Enable configuration of expiry time
 | 
						|
		exp := time.Now().Add(365 * 24 * time.Hour)
 | 
						|
		authz.Expires = &exp
 | 
						|
	}
 | 
						|
 | 
						|
	// Finalize the authorization (error ignored)
 | 
						|
	return ra.SA.FinalizeAuthorization(authz)
 | 
						|
}
 |