...

Source file src/github.com/letsencrypt/boulder/va/tlsalpn.go

Documentation: github.com/letsencrypt/boulder/va

     1  package va
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/sha256"
     7  	"crypto/subtle"
     8  	"crypto/tls"
     9  	"crypto/x509"
    10  	"crypto/x509/pkix"
    11  	"encoding/asn1"
    12  	"encoding/hex"
    13  	"errors"
    14  	"fmt"
    15  	"net"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"github.com/letsencrypt/boulder/core"
    20  	"github.com/letsencrypt/boulder/identifier"
    21  	"github.com/letsencrypt/boulder/probs"
    22  )
    23  
    24  const (
    25  	// ALPN protocol ID for TLS-ALPN-01 challenge
    26  	// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-5.2
    27  	ACMETLS1Protocol = "acme-tls/1"
    28  )
    29  
    30  var (
    31  	// As defined in https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-04#section-5.1
    32  	// id-pe OID + 31 (acmeIdentifier)
    33  	IdPeAcmeIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
    34  	// OID for the Subject Alternative Name extension, as defined in
    35  	// https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
    36  	IdCeSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17}
    37  )
    38  
    39  // certAltNames collects up all of a certificate's subject names (Subject CN and
    40  // Subject Alternate Names) and reduces them to a unique, sorted set, typically for an
    41  // error message
    42  func certAltNames(cert *x509.Certificate) []string {
    43  	var names []string
    44  	if cert.Subject.CommonName != "" {
    45  		names = append(names, cert.Subject.CommonName)
    46  	}
    47  	names = append(names, cert.DNSNames...)
    48  	names = append(names, cert.EmailAddresses...)
    49  	for _, id := range cert.IPAddresses {
    50  		names = append(names, id.String())
    51  	}
    52  	for _, id := range cert.URIs {
    53  		names = append(names, id.String())
    54  	}
    55  	names = core.UniqueLowerNames(names)
    56  	return names
    57  }
    58  
    59  func (va *ValidationAuthorityImpl) tryGetChallengeCert(ctx context.Context,
    60  	identifier identifier.ACMEIdentifier, challenge core.Challenge,
    61  	tlsConfig *tls.Config) (*x509.Certificate, *tls.ConnectionState, []core.ValidationRecord, *probs.ProblemDetails) {
    62  
    63  	allAddrs, err := va.getAddrs(ctx, identifier.Value)
    64  	validationRecords := []core.ValidationRecord{
    65  		{
    66  			Hostname:          identifier.Value,
    67  			AddressesResolved: allAddrs,
    68  			Port:              strconv.Itoa(va.tlsPort),
    69  		},
    70  	}
    71  	if err != nil {
    72  		return nil, nil, validationRecords, detailedError(err)
    73  	}
    74  	thisRecord := &validationRecords[0]
    75  
    76  	// Split the available addresses into v4 and v6 addresses
    77  	v4, v6 := availableAddresses(allAddrs)
    78  	addresses := append(v4, v6...)
    79  
    80  	// This shouldn't happen, but be defensive about it anyway
    81  	if len(addresses) < 1 {
    82  		return nil, nil, validationRecords, probs.Malformed("no IP addresses found for %q", identifier.Value)
    83  	}
    84  
    85  	// If there is at least one IPv6 address then try it first
    86  	if len(v6) > 0 {
    87  		address := net.JoinHostPort(v6[0].String(), thisRecord.Port)
    88  		thisRecord.AddressUsed = v6[0]
    89  
    90  		cert, cs, prob := va.getChallengeCert(ctx, address, identifier, challenge, tlsConfig)
    91  
    92  		// If there is no problem, return immediately
    93  		if err == nil {
    94  			return cert, cs, validationRecords, prob
    95  		}
    96  
    97  		// Otherwise, we note that we tried an address and fall back to trying IPv4
    98  		thisRecord.AddressesTried = append(thisRecord.AddressesTried, thisRecord.AddressUsed)
    99  		va.metrics.ipv4FallbackCounter.Inc()
   100  	}
   101  
   102  	// If there are no IPv4 addresses and we tried an IPv6 address return
   103  	// an error - there's nothing left to try
   104  	if len(v4) == 0 && len(thisRecord.AddressesTried) > 0 {
   105  		return nil, nil, validationRecords, probs.Malformed("Unable to contact %q at %q, no IPv4 addresses to try as fallback",
   106  			thisRecord.Hostname, thisRecord.AddressesTried[0])
   107  	} else if len(v4) == 0 && len(thisRecord.AddressesTried) == 0 {
   108  		// It shouldn't be possible that there are no IPv4 addresses and no previous
   109  		// attempts at an IPv6 address connection but be defensive about it anyway
   110  		return nil, nil, validationRecords, probs.Malformed("No IP addresses found for %q", thisRecord.Hostname)
   111  	}
   112  
   113  	// Otherwise if there are no IPv6 addresses, or there was an error
   114  	// talking to the first IPv6 address, try the first IPv4 address
   115  	thisRecord.AddressUsed = v4[0]
   116  	cert, cs, prob := va.getChallengeCert(ctx, net.JoinHostPort(v4[0].String(), thisRecord.Port),
   117  		identifier, challenge, tlsConfig)
   118  	return cert, cs, validationRecords, prob
   119  }
   120  
   121  func (va *ValidationAuthorityImpl) getChallengeCert(
   122  	ctx context.Context,
   123  	hostPort string,
   124  	identifier identifier.ACMEIdentifier,
   125  	challenge core.Challenge,
   126  	config *tls.Config,
   127  ) (*x509.Certificate, *tls.ConnectionState, *probs.ProblemDetails) {
   128  	va.log.Info(fmt.Sprintf("%s [%s] Attempting to validate for %s %s", challenge.Type, identifier, hostPort, config.ServerName))
   129  	// We expect a self-signed challenge certificate, do not verify it here.
   130  	config.InsecureSkipVerify = true
   131  
   132  	dialCtx, cancel := context.WithTimeout(ctx, va.singleDialTimeout)
   133  	defer cancel()
   134  
   135  	dialer := &tls.Dialer{Config: config}
   136  	conn, err := dialer.DialContext(dialCtx, "tcp", hostPort)
   137  	if err != nil {
   138  		va.log.Infof("%s connection failure for %s. err=[%#v] errStr=[%s]", challenge.Type, identifier, err, err)
   139  		host, _, splitErr := net.SplitHostPort(hostPort)
   140  		if splitErr == nil && net.ParseIP(host) != nil {
   141  			// Wrap the validation error and the IP of the remote host in an
   142  			// IPError so we can display the IP in the problem details returned
   143  			// to the client.
   144  			return nil, nil, detailedError(ipError{net.ParseIP(host), err})
   145  		}
   146  		return nil, nil, detailedError(err)
   147  	}
   148  	defer conn.Close()
   149  
   150  	// tls.Dialer.DialContext guarantees that the *net.Conn it returns is a *tls.Conn.
   151  	cs := conn.(*tls.Conn).ConnectionState()
   152  	certs := cs.PeerCertificates
   153  	if len(certs) == 0 {
   154  		va.log.Infof("%s challenge for %s resulted in no certificates", challenge.Type, identifier.Value)
   155  		return nil, nil, probs.Unauthorized(fmt.Sprintf("No certs presented for %s challenge", challenge.Type))
   156  	}
   157  	for i, cert := range certs {
   158  		va.log.AuditInfof("%s challenge for %s received certificate (%d of %d): cert=[%s]",
   159  			challenge.Type, identifier.Value, i+1, len(certs), hex.EncodeToString(cert.Raw))
   160  	}
   161  	return certs[0], &cs, nil
   162  }
   163  
   164  func checkExpectedSAN(cert *x509.Certificate, name identifier.ACMEIdentifier) error {
   165  	if len(cert.DNSNames) != 1 {
   166  		return errors.New("wrong number of dNSNames")
   167  	}
   168  
   169  	for _, ext := range cert.Extensions {
   170  		if IdCeSubjectAltName.Equal(ext.Id) {
   171  			expectedSANs, err := asn1.Marshal([]asn1.RawValue{
   172  				{Tag: 2, Class: 2, Bytes: []byte(cert.DNSNames[0])},
   173  			})
   174  			if err != nil || !bytes.Equal(expectedSANs, ext.Value) {
   175  				return errors.New("SAN extension does not match expected bytes")
   176  			}
   177  		}
   178  	}
   179  
   180  	if !strings.EqualFold(cert.DNSNames[0], name.Value) {
   181  		return errors.New("dNSName does not match expected identifier")
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  // Confirm that of the OIDs provided, all of them are in the provided list of
   188  // extensions. Also confirms that of the extensions provided that none are
   189  // repeated. Per RFC8737, allows unexpected extensions.
   190  func checkAcceptableExtensions(exts []pkix.Extension, requiredOIDs []asn1.ObjectIdentifier) error {
   191  	oidSeen := make(map[string]bool)
   192  
   193  	for _, ext := range exts {
   194  		if oidSeen[ext.Id.String()] {
   195  			return fmt.Errorf("Extension OID %s seen twice", ext.Id)
   196  		}
   197  		oidSeen[ext.Id.String()] = true
   198  	}
   199  
   200  	for _, required := range requiredOIDs {
   201  		if !oidSeen[required.String()] {
   202  			return fmt.Errorf("Required extension OID %s is not present", required)
   203  		}
   204  	}
   205  
   206  	return nil
   207  }
   208  
   209  func (va *ValidationAuthorityImpl) validateTLSALPN01(ctx context.Context, identifier identifier.ACMEIdentifier, challenge core.Challenge) ([]core.ValidationRecord, *probs.ProblemDetails) {
   210  	if identifier.Type != "dns" {
   211  		va.log.Info(fmt.Sprintf("Identifier type for TLS-ALPN-01 was not DNS: %s", identifier))
   212  		return nil, probs.Malformed("Identifier type for TLS-ALPN-01 was not DNS")
   213  	}
   214  
   215  	cert, cs, validationRecords, problem := va.tryGetChallengeCert(ctx, identifier, challenge, &tls.Config{
   216  		MinVersion: tls.VersionTLS12,
   217  		NextProtos: []string{ACMETLS1Protocol},
   218  		ServerName: identifier.Value,
   219  	})
   220  	if problem != nil {
   221  		return validationRecords, problem
   222  	}
   223  
   224  	if cs.NegotiatedProtocol != ACMETLS1Protocol {
   225  		errText := fmt.Sprintf(
   226  			"Cannot negotiate ALPN protocol %q for %s challenge",
   227  			ACMETLS1Protocol,
   228  			core.ChallengeTypeTLSALPN01,
   229  		)
   230  		return validationRecords, probs.Unauthorized(errText)
   231  	}
   232  
   233  	badCertErr := func(msg string) *probs.ProblemDetails {
   234  		hostPort := net.JoinHostPort(validationRecords[0].AddressUsed.String(), validationRecords[0].Port)
   235  
   236  		return probs.Unauthorized(fmt.Sprintf(
   237  			"Incorrect validation certificate for %s challenge. "+
   238  				"Requested %s from %s. "+
   239  				"%s",
   240  			challenge.Type, identifier.Value, hostPort, msg,
   241  		))
   242  	}
   243  
   244  	// The certificate must be self-signed.
   245  	err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature)
   246  	if err != nil || !bytes.Equal(cert.RawSubject, cert.RawIssuer) {
   247  		return validationRecords, badCertErr(
   248  			"Received certificate which is not self-signed.")
   249  	}
   250  
   251  	// The certificate must have the subjectAltName and acmeIdentifier
   252  	// extensions, and only one of each.
   253  	allowedOIDs := []asn1.ObjectIdentifier{
   254  		IdPeAcmeIdentifier, IdCeSubjectAltName,
   255  	}
   256  	err = checkAcceptableExtensions(cert.Extensions, allowedOIDs)
   257  	if err != nil {
   258  		return validationRecords, badCertErr(
   259  			fmt.Sprintf("Received certificate with unexpected extensions: %q", err))
   260  	}
   261  
   262  	// The certificate returned must have a subjectAltName extension containing
   263  	// only the dNSName being validated and no other entries.
   264  	err = checkExpectedSAN(cert, identifier)
   265  	if err != nil {
   266  		names := strings.Join(certAltNames(cert), ", ")
   267  		return validationRecords, badCertErr(
   268  			fmt.Sprintf("Received certificate with unexpected identifiers (%q): %q", names, err))
   269  	}
   270  
   271  	// Verify key authorization in acmeValidation extension
   272  	h := sha256.Sum256([]byte(challenge.ProvidedKeyAuthorization))
   273  	for _, ext := range cert.Extensions {
   274  		if IdPeAcmeIdentifier.Equal(ext.Id) {
   275  			va.metrics.tlsALPNOIDCounter.WithLabelValues(IdPeAcmeIdentifier.String()).Inc()
   276  			if !ext.Critical {
   277  				return validationRecords, badCertErr(
   278  					"Received certificate with acmeValidationV1 extension that is not Critical.")
   279  			}
   280  			var extValue []byte
   281  			rest, err := asn1.Unmarshal(ext.Value, &extValue)
   282  			if err != nil || len(rest) > 0 || len(h) != len(extValue) {
   283  				return validationRecords, badCertErr(
   284  					"Received certificate with malformed acmeValidationV1 extension value.")
   285  			}
   286  			if subtle.ConstantTimeCompare(h[:], extValue) != 1 {
   287  				return validationRecords, badCertErr(fmt.Sprintf(
   288  					"Received certificate with acmeValidationV1 extension value %s but expected %s.",
   289  					hex.EncodeToString(extValue),
   290  					hex.EncodeToString(h[:]),
   291  				))
   292  			}
   293  			return validationRecords, nil
   294  		}
   295  	}
   296  
   297  	return validationRecords, badCertErr(
   298  		"Received certificate with no acmeValidationV1 extension.")
   299  }
   300  

View as plain text