1package tlsalpn01
2
3import (
4	"crypto/rand"
5	"crypto/rsa"
6	"crypto/sha256"
7	"crypto/subtle"
8	"crypto/tls"
9	"encoding/asn1"
10	"net/http"
11	"testing"
12
13	"github.com/go-acme/lego/v3/acme"
14	"github.com/go-acme/lego/v3/acme/api"
15	"github.com/go-acme/lego/v3/challenge"
16	"github.com/go-acme/lego/v3/platform/tester"
17	"github.com/stretchr/testify/assert"
18	"github.com/stretchr/testify/require"
19)
20
21func TestChallenge(t *testing.T) {
22	_, apiURL, tearDown := tester.SetupFakeAPI()
23	defer tearDown()
24
25	domain := "localhost:23457"
26
27	mockValidate := func(_ *api.Core, _ string, chlng acme.Challenge) error {
28		conn, err := tls.Dial("tcp", domain, &tls.Config{
29			InsecureSkipVerify: true,
30		})
31		require.NoError(t, err, "Expected to connect to challenge server without an error")
32
33		// Expect the server to only return one certificate
34		connState := conn.ConnectionState()
35		assert.Len(t, connState.PeerCertificates, 1, "Expected the challenge server to return exactly one certificate")
36
37		remoteCert := connState.PeerCertificates[0]
38		assert.Len(t, remoteCert.DNSNames, 1, "Expected the challenge certificate to have exactly one DNSNames entry")
39		assert.Equal(t, domain, remoteCert.DNSNames[0], "challenge certificate DNSName ")
40		assert.NotEmpty(t, remoteCert.Extensions, "Expected the challenge certificate to contain extensions")
41
42		idx := -1
43		for i, ext := range remoteCert.Extensions {
44			if idPeAcmeIdentifierV1.Equal(ext.Id) {
45				idx = i
46				break
47			}
48		}
49
50		require.NotEqual(t, -1, idx, "Expected the challenge certificate to contain an extension with the id-pe-acmeIdentifier id,")
51
52		ext := remoteCert.Extensions[idx]
53		assert.True(t, ext.Critical, "Expected the challenge certificate id-pe-acmeIdentifier extension to be marked as critical")
54
55		zBytes := sha256.Sum256([]byte(chlng.KeyAuthorization))
56		value, err := asn1.Marshal(zBytes[:sha256.Size])
57		require.NoError(t, err, "Expected marshaling of the keyAuth to return no error")
58
59		if subtle.ConstantTimeCompare(value, ext.Value) != 1 {
60			t.Errorf("Expected the challenge certificate id-pe-acmeIdentifier extension to contain the SHA-256 digest of the keyAuth, %v, but was %v", zBytes[:], ext.Value)
61		}
62
63		return nil
64	}
65
66	privateKey, err := rsa.GenerateKey(rand.Reader, 512)
67	require.NoError(t, err, "Could not generate test key")
68
69	core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey)
70	require.NoError(t, err)
71
72	solver := NewChallenge(
73		core,
74		mockValidate,
75		&ProviderServer{port: "23457"},
76	)
77
78	authz := acme.Authorization{
79		Identifier: acme.Identifier{
80			Value: domain,
81		},
82		Challenges: []acme.Challenge{
83			{Type: challenge.TLSALPN01.String(), Token: "tlsalpn1"},
84		},
85	}
86
87	err = solver.Solve(authz)
88	require.NoError(t, err)
89}
90
91func TestChallengeInvalidPort(t *testing.T) {
92	_, apiURL, tearDown := tester.SetupFakeAPI()
93	defer tearDown()
94
95	privateKey, err := rsa.GenerateKey(rand.Reader, 128)
96	require.NoError(t, err, "Could not generate test key")
97
98	core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey)
99	require.NoError(t, err)
100
101	solver := NewChallenge(
102		core,
103		func(_ *api.Core, _ string, _ acme.Challenge) error { return nil },
104		&ProviderServer{port: "123456"},
105	)
106
107	authz := acme.Authorization{
108		Identifier: acme.Identifier{
109			Value: "localhost:123456",
110		},
111		Challenges: []acme.Challenge{
112			{Type: challenge.TLSALPN01.String(), Token: "tlsalpn1"},
113		},
114	}
115
116	err = solver.Solve(authz)
117	require.Error(t, err)
118	assert.Contains(t, err.Error(), "invalid port")
119	assert.Contains(t, err.Error(), "123456")
120}
121