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 }