Skip to content

Commit df2909a

Browse files
alexzorinjsha
authored andcommitted
va: Send extValue in TLSALPN unauthorized response (letsencrypt#4330)
Brings it to be more in line with the responses from the other two challenges and will hopefully make the challenge a lot easier to debug (like in the recent community thread). ```json "error": { "type": "urn:ietf:params:acme:error:unauthorized", "detail": "Incorrect validation certificate for tls-alpn-01 challenge. Expected acmeValidationV1 extension value 836bf5358f8a32826c61faeff2e0225b00756f935b00ed3002cabb9d536b9f53 for this challenge but got 8539b12e31c306b81a0aedab4128722c6ad71f71f46316a3c71612f47df0e532", "status": 403 }, ```
1 parent 2131065 commit df2909a

File tree

2 files changed

+100
-27
lines changed

2 files changed

+100
-27
lines changed

va/tlsalpn.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,19 +222,20 @@ func (va *ValidationAuthorityImpl) validateTLSALPN01(ctx context.Context, identi
222222
}
223223
if !ext.Critical {
224224
errText := fmt.Sprintf("Incorrect validation certificate for %s challenge. "+
225-
"acmeValidationV1 extension not critical.", core.ChallengeTypeTLSALPN01)
225+
"acmeValidationV1 extension not critical", core.ChallengeTypeTLSALPN01)
226226
return validationRecords, probs.Unauthorized(errText)
227227
}
228228
var extValue []byte
229229
rest, err := asn1.Unmarshal(ext.Value, &extValue)
230-
if err != nil || len(rest) > 0 {
230+
if err != nil || len(rest) > 0 || len(h) != len(extValue) {
231231
errText := fmt.Sprintf("Incorrect validation certificate for %s challenge. "+
232-
"Malformed acmeValidationV1 extension value.", core.ChallengeTypeTLSALPN01)
232+
"Malformed acmeValidationV1 extension value", core.ChallengeTypeTLSALPN01)
233233
return validationRecords, probs.Unauthorized(errText)
234234
}
235235
if subtle.ConstantTimeCompare(h[:], extValue) != 1 {
236236
errText := fmt.Sprintf("Incorrect validation certificate for %s challenge. "+
237-
"Invalid acmeValidationV1 extension value.", core.ChallengeTypeTLSALPN01)
237+
"Expected acmeValidationV1 extension value %s for this challenge but got %s",
238+
core.ChallengeTypeTLSALPN01, hex.EncodeToString(h[:]), hex.EncodeToString(extValue))
238239
return validationRecords, probs.Unauthorized(errText)
239240
}
240241
return validationRecords, nil

va/tlsalpn_test.go

Lines changed: 95 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"crypto/x509"
99
"crypto/x509/pkix"
1010
"encoding/asn1"
11+
"encoding/hex"
1112
"fmt"
1213
"math/big"
1314
"net"
@@ -69,29 +70,7 @@ func tlssniSrvWithNames(t *testing.T, chall core.Challenge, names ...string) *ht
6970
return hs
7071
}
7172

72-
func tlsalpn01Srv(t *testing.T, chall core.Challenge, oid asn1.ObjectIdentifier, names ...string) *httptest.Server {
73-
template := tlsCertTemplate(names)
74-
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
75-
cert := &tls.Certificate{
76-
Certificate: [][]byte{certBytes},
77-
PrivateKey: &TheKey,
78-
}
79-
80-
shasum := sha256.Sum256([]byte(chall.ProvidedKeyAuthorization))
81-
encHash, _ := asn1.Marshal(shasum[:])
82-
acmeExtension := pkix.Extension{
83-
Id: oid,
84-
Critical: true,
85-
Value: encHash,
86-
}
87-
88-
template.ExtraExtensions = []pkix.Extension{acmeExtension}
89-
certBytes, _ = x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
90-
acmeCert := &tls.Certificate{
91-
Certificate: [][]byte{certBytes},
92-
PrivateKey: &TheKey,
93-
}
94-
73+
func tlsalpn01SrvWithCert(t *testing.T, chall core.Challenge, oid asn1.ObjectIdentifier, names []string, cert *tls.Certificate, acmeCert *tls.Certificate) *httptest.Server {
9574
tlsConfig := &tls.Config{
9675
Certificates: []tls.Certificate{},
9776
ClientAuth: tls.NoClientCert,
@@ -118,6 +97,31 @@ func tlsalpn01Srv(t *testing.T, chall core.Challenge, oid asn1.ObjectIdentifier,
11897
return hs
11998
}
12099

100+
func tlsalpn01Srv(t *testing.T, chall core.Challenge, oid asn1.ObjectIdentifier, names ...string) *httptest.Server {
101+
template := tlsCertTemplate(names)
102+
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
103+
cert := &tls.Certificate{
104+
Certificate: [][]byte{certBytes},
105+
PrivateKey: &TheKey,
106+
}
107+
108+
shasum := sha256.Sum256([]byte(chall.ProvidedKeyAuthorization))
109+
encHash, _ := asn1.Marshal(shasum[:])
110+
acmeExtension := pkix.Extension{
111+
Id: oid,
112+
Critical: true,
113+
Value: encHash,
114+
}
115+
template.ExtraExtensions = []pkix.Extension{acmeExtension}
116+
certBytes, _ = x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
117+
acmeCert := &tls.Certificate{
118+
Certificate: [][]byte{certBytes},
119+
PrivateKey: &TheKey,
120+
}
121+
122+
return tlsalpn01SrvWithCert(t, chall, oid, names, cert, acmeCert)
123+
}
124+
121125
func TestTLSALPN01FailIP(t *testing.T) {
122126
chall := createChallenge(core.ChallengeTypeTLSALPN01)
123127
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, "localhost")
@@ -366,6 +370,16 @@ func TestValidateTLSALPN01BadChallenge(t *testing.T) {
366370
t.Fatalf("TLS ALPN validation should have failed.")
367371
}
368372
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
373+
374+
expectedDigest := sha256.Sum256([]byte(chall.ProvidedKeyAuthorization))
375+
badDigest := sha256.Sum256([]byte(chall2.ProvidedKeyAuthorization))
376+
377+
test.AssertEquals(t, prob.Detail, fmt.Sprintf(
378+
"Incorrect validation certificate for %s challenge. "+
379+
"Expected acmeValidationV1 extension value %s for this challenge but got %s",
380+
core.ChallengeTypeTLSALPN01,
381+
hex.EncodeToString(expectedDigest[:]),
382+
hex.EncodeToString(badDigest[:])))
369383
}
370384

371385
func TestValidateTLSALPN01BrokenSrv(t *testing.T) {
@@ -414,3 +428,61 @@ func TestValidateTLSALPN01BadUTFSrv(t *testing.T) {
414428
`first certificate had names "localhost, %s"`,
415429
port, "\ufffd(\ufffd\ufffd"))
416430
}
431+
432+
// TestValidateTLSALPN01MalformedExtnValue tests that validating TLS-ALPN-01
433+
// against a host that returns a certificate that contains an ASN.1 DER
434+
// acmeValidation extension value that does not parse or is the wrong length
435+
// will result in an Unauthorized problem
436+
func TestValidateTLSALPN01MalformedExtnValue(t *testing.T) {
437+
chall := createChallenge(core.ChallengeTypeTLSALPN01)
438+
439+
names := []string{"localhost"}
440+
template := tlsCertTemplate(names)
441+
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
442+
cert := &tls.Certificate{
443+
Certificate: [][]byte{certBytes},
444+
PrivateKey: &TheKey,
445+
}
446+
447+
wrongTypeDER, _ := asn1.Marshal("a string")
448+
wrongLengthDER, _ := asn1.Marshal(make([]byte, 31))
449+
badExtensions := []pkix.Extension{
450+
{
451+
Id: IdPeAcmeIdentifier,
452+
Critical: true,
453+
Value: wrongTypeDER,
454+
},
455+
{
456+
Id: IdPeAcmeIdentifier,
457+
Critical: true,
458+
Value: wrongLengthDER,
459+
},
460+
}
461+
462+
malformedMsg := fmt.Sprintf("Incorrect validation certificate for %s challenge. "+
463+
"Malformed acmeValidationV1 extension value", core.ChallengeTypeTLSALPN01)
464+
465+
for _, badExt := range badExtensions {
466+
template.ExtraExtensions = []pkix.Extension{badExt}
467+
certBytes, _ = x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
468+
acmeCert := &tls.Certificate{
469+
Certificate: [][]byte{certBytes},
470+
PrivateKey: &TheKey,
471+
}
472+
473+
hs := tlsalpn01SrvWithCert(t, chall, IdPeAcmeIdentifier, names, cert, acmeCert)
474+
va, _ := setup(hs, 0, "", nil)
475+
476+
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
477+
hs.Close()
478+
479+
if prob == nil {
480+
t.Errorf("TLS ALPN validation should have failed for acmeValidation extension %+v.",
481+
badExt)
482+
continue
483+
}
484+
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
485+
test.AssertEquals(t, prob.Detail, malformedMsg)
486+
}
487+
488+
}

0 commit comments

Comments
 (0)