github.com/letsencrypt/boulder@v0.20251208.0/va/tlsalpn.go (about)

     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  	"net/netip"
    17  	"strconv"
    18  	"strings"
    19  
    20  	"github.com/miekg/dns"
    21  
    22  	"github.com/letsencrypt/boulder/core"
    23  	berrors "github.com/letsencrypt/boulder/errors"
    24  	"github.com/letsencrypt/boulder/identifier"
    25  )
    26  
    27  const (
    28  	// ALPN protocol ID for TLS-ALPN-01 challenge
    29  	// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-5.2
    30  	ACMETLS1Protocol = "acme-tls/1"
    31  )
    32  
    33  var (
    34  	// As defined in https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-04#section-5.1
    35  	// id-pe OID + 31 (acmeIdentifier)
    36  	IdPeAcmeIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
    37  	// OID for the Subject Alternative Name extension, as defined in
    38  	// https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
    39  	IdCeSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17}
    40  )
    41  
    42  // certAltNames collects up all of a certificate's subject names (Subject CN and
    43  // Subject Alternate Names) and reduces them to a unique, sorted set, typically for an
    44  // error message
    45  func certAltNames(cert *x509.Certificate) []string {
    46  	var names []string
    47  	if cert.Subject.CommonName != "" {
    48  		names = append(names, cert.Subject.CommonName)
    49  	}
    50  	names = append(names, cert.DNSNames...)
    51  	names = append(names, cert.EmailAddresses...)
    52  	for _, id := range cert.IPAddresses {
    53  		names = append(names, id.String())
    54  	}
    55  	for _, id := range cert.URIs {
    56  		names = append(names, id.String())
    57  	}
    58  	names = core.UniqueLowerNames(names)
    59  	return names
    60  }
    61  
    62  func (va *ValidationAuthorityImpl) tryGetChallengeCert(
    63  	ctx context.Context,
    64  	ident identifier.ACMEIdentifier,
    65  ) (*x509.Certificate, *tls.ConnectionState, core.ValidationRecord, error) {
    66  	validationRecord := core.ValidationRecord{
    67  		Hostname: ident.Value,
    68  		Port:     strconv.Itoa(va.tlsPort),
    69  	}
    70  
    71  	var addrs []netip.Addr
    72  	switch ident.Type {
    73  	case identifier.TypeDNS:
    74  		// Resolve IP addresses for the identifier
    75  		dnsAddrs, dnsResolvers, err := va.getAddrs(ctx, ident.Value)
    76  		if err != nil {
    77  			return nil, nil, validationRecord, err
    78  		}
    79  		addrs, validationRecord.ResolverAddrs = dnsAddrs, dnsResolvers
    80  		validationRecord.AddressesResolved = addrs
    81  	case identifier.TypeIP:
    82  		netIP, err := netip.ParseAddr(ident.Value)
    83  		if err != nil {
    84  			return nil, nil, validationRecord, fmt.Errorf("can't parse IP address %q: %s", ident.Value, err)
    85  		}
    86  		addrs = []netip.Addr{netIP}
    87  		// This field shouldn't be necessary: it's redundant with the Hostname
    88  		// field. But Challenge.RecordsSane expects it, and there's no easy way to
    89  		// special-case IP identifiers within that function.
    90  		validationRecord.AddressesResolved = addrs
    91  	default:
    92  		// This should never happen. The calling function should check the
    93  		// identifier type.
    94  		return nil, nil, validationRecord, fmt.Errorf("unknown identifier type: %s", ident.Type)
    95  	}
    96  
    97  	// Split the available addresses into v4 and v6 addresses
    98  	v4, v6 := availableAddresses(addrs)
    99  	addresses := append(v4, v6...)
   100  
   101  	// This shouldn't happen, but be defensive about it anyway
   102  	if len(addresses) < 1 {
   103  		return nil, nil, validationRecord, berrors.MalformedError("no IP addresses found for %q", ident.Value)
   104  	}
   105  
   106  	// If there is at least one IPv6 address then try it first
   107  	if len(v6) > 0 {
   108  		address := net.JoinHostPort(v6[0].String(), validationRecord.Port)
   109  		validationRecord.AddressUsed = v6[0]
   110  
   111  		cert, cs, err := va.getChallengeCert(ctx, address, ident)
   112  
   113  		// If there is no problem, return immediately
   114  		if err == nil {
   115  			return cert, cs, validationRecord, nil
   116  		}
   117  
   118  		// Otherwise, we note that we tried an address and fall back to trying IPv4
   119  		validationRecord.AddressesTried = append(validationRecord.AddressesTried, validationRecord.AddressUsed)
   120  		va.metrics.ipv4FallbackCounter.Inc()
   121  	}
   122  
   123  	// If there are no IPv4 addresses and we tried an IPv6 address return
   124  	// an error - there's nothing left to try
   125  	if len(v4) == 0 && len(validationRecord.AddressesTried) > 0 {
   126  		return nil, nil, validationRecord, berrors.MalformedError("Unable to contact %q at %q, no IPv4 addresses to try as fallback",
   127  			validationRecord.Hostname, validationRecord.AddressesTried[0])
   128  	} else if len(v4) == 0 && len(validationRecord.AddressesTried) == 0 {
   129  		// It shouldn't be possible that there are no IPv4 addresses and no previous
   130  		// attempts at an IPv6 address connection but be defensive about it anyway
   131  		return nil, nil, validationRecord, berrors.MalformedError("No IP addresses found for %q", validationRecord.Hostname)
   132  	}
   133  
   134  	// Otherwise if there are no IPv6 addresses, or there was an error
   135  	// talking to the first IPv6 address, try the first IPv4 address
   136  	validationRecord.AddressUsed = v4[0]
   137  	address := net.JoinHostPort(v4[0].String(), validationRecord.Port)
   138  	cert, cs, err := va.getChallengeCert(ctx, address, ident)
   139  	return cert, cs, validationRecord, err
   140  }
   141  
   142  func (va *ValidationAuthorityImpl) getChallengeCert(
   143  	ctx context.Context,
   144  	hostPort string,
   145  	ident identifier.ACMEIdentifier,
   146  ) (*x509.Certificate, *tls.ConnectionState, error) {
   147  	var serverName string
   148  	switch ident.Type {
   149  	case identifier.TypeDNS:
   150  		serverName = ident.Value
   151  	case identifier.TypeIP:
   152  		reverseIP, err := dns.ReverseAddr(ident.Value)
   153  		if err != nil {
   154  			va.log.Infof("%s Failed to parse IP address %s.", core.ChallengeTypeTLSALPN01, ident.Value)
   155  			return nil, nil, fmt.Errorf("failed to parse IP address")
   156  		}
   157  		serverName = reverseIP
   158  	default:
   159  		// This should never happen. The calling function should check the
   160  		// identifier type.
   161  		va.log.Infof("%s Unknown identifier type '%s' for %s.", core.ChallengeTypeTLSALPN01, ident.Type, ident.Value)
   162  		return nil, nil, fmt.Errorf("unknown identifier type: %s", ident.Type)
   163  	}
   164  
   165  	va.log.Info(fmt.Sprintf("%s [%s] Attempting to validate for %s %s", core.ChallengeTypeTLSALPN01, ident, hostPort, serverName))
   166  
   167  	dialCtx, cancel := context.WithTimeout(ctx, va.singleDialTimeout)
   168  	defer cancel()
   169  
   170  	dialer := &tls.Dialer{Config: &tls.Config{
   171  		MinVersion: tls.VersionTLS12,
   172  		NextProtos: []string{ACMETLS1Protocol},
   173  		ServerName: serverName,
   174  		// We expect a self-signed challenge certificate, do not verify it here.
   175  		InsecureSkipVerify: true,
   176  	}}
   177  
   178  	// This is a backstop check to avoid connecting to reserved IP addresses.
   179  	// They should have been caught and excluded by `bdns.LookupHost`.
   180  	host, _, err := net.SplitHostPort(hostPort)
   181  	if err != nil {
   182  		return nil, nil, err
   183  	}
   184  	hostIP, _ := netip.ParseAddr(host)
   185  	if (hostIP != netip.Addr{}) {
   186  		err = va.isReservedIPFunc(hostIP)
   187  		if err != nil {
   188  			return nil, nil, err
   189  		}
   190  	}
   191  
   192  	conn, err := dialer.DialContext(dialCtx, "tcp", hostPort)
   193  	if err != nil {
   194  		va.log.Infof("%s connection failure for %s. err=[%#v] errStr=[%s]", core.ChallengeTypeTLSALPN01, ident, err, err)
   195  		if (hostIP != netip.Addr{}) {
   196  			// Wrap the validation error and the IP of the remote host in an
   197  			// IPError so we can display the IP in the problem details returned
   198  			// to the client.
   199  			return nil, nil, ipError{hostIP, err}
   200  		}
   201  		return nil, nil, err
   202  	}
   203  	defer conn.Close()
   204  
   205  	// tls.Dialer.DialContext guarantees that the *net.Conn it returns is a *tls.Conn.
   206  	cs := conn.(*tls.Conn).ConnectionState()
   207  	certs := cs.PeerCertificates
   208  	if len(certs) == 0 {
   209  		va.log.Infof("%s challenge for %s resulted in no certificates", core.ChallengeTypeTLSALPN01, ident.Value)
   210  		return nil, nil, berrors.UnauthorizedError("No certs presented for %s challenge", core.ChallengeTypeTLSALPN01)
   211  	}
   212  	for i, cert := range certs {
   213  		va.log.AuditInfof("%s challenge for %s received certificate (%d of %d): cert=[%s]",
   214  			core.ChallengeTypeTLSALPN01, ident.Value, i+1, len(certs), hex.EncodeToString(cert.Raw))
   215  	}
   216  	return certs[0], &cs, nil
   217  }
   218  
   219  func checkExpectedSAN(cert *x509.Certificate, ident identifier.ACMEIdentifier) error {
   220  	var expectedSANBytes []byte
   221  	switch ident.Type {
   222  	case identifier.TypeDNS:
   223  		if len(cert.DNSNames) != 1 || len(cert.IPAddresses) != 0 {
   224  			return errors.New("wrong number of identifiers")
   225  		}
   226  		if !strings.EqualFold(cert.DNSNames[0], ident.Value) {
   227  			return errors.New("identifier does not match expected identifier")
   228  		}
   229  		bytes, err := asn1.Marshal([]asn1.RawValue{
   230  			{Tag: 2, Class: 2, Bytes: []byte(ident.Value)},
   231  		})
   232  		if err != nil {
   233  			return fmt.Errorf("composing SAN extension: %w", err)
   234  		}
   235  		expectedSANBytes = bytes
   236  	case identifier.TypeIP:
   237  		if len(cert.IPAddresses) != 1 || len(cert.DNSNames) != 0 {
   238  			return errors.New("wrong number of identifiers")
   239  		}
   240  		if !cert.IPAddresses[0].Equal(net.ParseIP(ident.Value)) {
   241  			return errors.New("identifier does not match expected identifier")
   242  		}
   243  		netipAddr, err := netip.ParseAddr(ident.Value)
   244  		if err != nil {
   245  			return fmt.Errorf("parsing IP address identifier: %w", err)
   246  		}
   247  		netipBytes, err := netipAddr.MarshalBinary()
   248  		if err != nil {
   249  			return fmt.Errorf("marshalling IP address identifier: %w", err)
   250  		}
   251  		bytes, err := asn1.Marshal([]asn1.RawValue{
   252  			{Tag: 7, Class: 2, Bytes: netipBytes},
   253  		})
   254  		if err != nil {
   255  			return fmt.Errorf("composing SAN extension: %w", err)
   256  		}
   257  		expectedSANBytes = bytes
   258  	default:
   259  		// This should never happen. The calling function should check the
   260  		// identifier type.
   261  		return fmt.Errorf("unknown identifier type: %s", ident.Type)
   262  	}
   263  
   264  	for _, ext := range cert.Extensions {
   265  		if IdCeSubjectAltName.Equal(ext.Id) {
   266  			if !bytes.Equal(ext.Value, expectedSANBytes) {
   267  				return errors.New("SAN extension does not match expected bytes")
   268  			}
   269  		}
   270  	}
   271  
   272  	return nil
   273  }
   274  
   275  // Confirm that of the OIDs provided, all of them are in the provided list of
   276  // extensions. Also confirms that of the extensions provided that none are
   277  // repeated. Per RFC8737, allows unexpected extensions.
   278  func checkAcceptableExtensions(exts []pkix.Extension, requiredOIDs []asn1.ObjectIdentifier) error {
   279  	oidSeen := make(map[string]bool)
   280  
   281  	for _, ext := range exts {
   282  		if oidSeen[ext.Id.String()] {
   283  			return fmt.Errorf("Extension OID %s seen twice", ext.Id)
   284  		}
   285  		oidSeen[ext.Id.String()] = true
   286  	}
   287  
   288  	for _, required := range requiredOIDs {
   289  		if !oidSeen[required.String()] {
   290  			return fmt.Errorf("Required extension OID %s is not present", required)
   291  		}
   292  	}
   293  
   294  	return nil
   295  }
   296  
   297  func (va *ValidationAuthorityImpl) validateTLSALPN01(ctx context.Context, ident identifier.ACMEIdentifier, keyAuthorization string) ([]core.ValidationRecord, error) {
   298  	if ident.Type != identifier.TypeDNS && ident.Type != identifier.TypeIP {
   299  		va.log.Info(fmt.Sprintf("Identifier type for TLS-ALPN-01 challenge was not DNS or IP: %s", ident))
   300  		return nil, berrors.MalformedError("Identifier type for TLS-ALPN-01 challenge was not DNS or IP")
   301  	}
   302  
   303  	cert, cs, tvr, err := va.tryGetChallengeCert(ctx, ident)
   304  	// Copy the single validationRecord into the slice that we have to return, and
   305  	// get a reference to it so we can modify it if we have to.
   306  	validationRecords := []core.ValidationRecord{tvr}
   307  	validationRecord := &validationRecords[0]
   308  	if err != nil {
   309  		return validationRecords, err
   310  	}
   311  
   312  	if cs.NegotiatedProtocol != ACMETLS1Protocol {
   313  		return validationRecords, berrors.UnauthorizedError(
   314  			"Cannot negotiate ALPN protocol %q for %s challenge",
   315  			ACMETLS1Protocol,
   316  			core.ChallengeTypeTLSALPN01)
   317  	}
   318  
   319  	badCertErr := func(msg string) error {
   320  		hostPort := net.JoinHostPort(validationRecord.AddressUsed.String(), validationRecord.Port)
   321  
   322  		return berrors.UnauthorizedError(
   323  			"Incorrect validation certificate for %s challenge. "+
   324  				"Requested %s from %s. %s",
   325  			core.ChallengeTypeTLSALPN01, ident.Value, hostPort, msg)
   326  	}
   327  
   328  	// The certificate must be self-signed.
   329  	err = cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature)
   330  	if err != nil || !bytes.Equal(cert.RawSubject, cert.RawIssuer) {
   331  		return validationRecords, badCertErr(
   332  			"Received certificate which is not self-signed.")
   333  	}
   334  
   335  	// The certificate must have the subjectAltName and acmeIdentifier
   336  	// extensions, and only one of each.
   337  	allowedOIDs := []asn1.ObjectIdentifier{
   338  		IdPeAcmeIdentifier, IdCeSubjectAltName,
   339  	}
   340  	err = checkAcceptableExtensions(cert.Extensions, allowedOIDs)
   341  	if err != nil {
   342  		return validationRecords, badCertErr(
   343  			fmt.Sprintf("Received certificate with unexpected extensions: %q", err))
   344  	}
   345  
   346  	// The certificate returned must have a subjectAltName extension containing
   347  	// only the identifier being validated and no other entries.
   348  	err = checkExpectedSAN(cert, ident)
   349  	if err != nil {
   350  		names := strings.Join(certAltNames(cert), ", ")
   351  		return validationRecords, badCertErr(
   352  			fmt.Sprintf("Received certificate with unexpected identifiers (%q): %q", names, err))
   353  	}
   354  
   355  	// Verify key authorization in acmeValidation extension
   356  	h := sha256.Sum256([]byte(keyAuthorization))
   357  	for _, ext := range cert.Extensions {
   358  		if IdPeAcmeIdentifier.Equal(ext.Id) {
   359  			va.metrics.tlsALPNOIDCounter.WithLabelValues(IdPeAcmeIdentifier.String()).Inc()
   360  			if !ext.Critical {
   361  				return validationRecords, badCertErr(
   362  					"Received certificate with acmeValidationV1 extension that is not Critical.")
   363  			}
   364  			var extValue []byte
   365  			rest, err := asn1.Unmarshal(ext.Value, &extValue)
   366  			if err != nil || len(rest) > 0 || len(h) != len(extValue) {
   367  				return validationRecords, badCertErr(
   368  					"Received certificate with malformed acmeValidationV1 extension value.")
   369  			}
   370  			if subtle.ConstantTimeCompare(h[:], extValue) != 1 {
   371  				return validationRecords, badCertErr(fmt.Sprintf(
   372  					"Received certificate with acmeValidationV1 extension value %s but expected %s.",
   373  					hex.EncodeToString(extValue),
   374  					hex.EncodeToString(h[:]),
   375  				))
   376  			}
   377  			return validationRecords, nil
   378  		}
   379  	}
   380  
   381  	return validationRecords, badCertErr(
   382  		"Received certificate with no acmeValidationV1 extension.")
   383  }