Skip to content

Commit 4b47373

Browse files
author
Ivan Bertona
committed
Add support for TLS-ALPN-01 challenge.
1 parent f8eec06 commit 4b47373

File tree

10 files changed

+1072
-41
lines changed

10 files changed

+1072
-41
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@
1818
coverage.txt
1919
vendor
2020
output
21+
.idea

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ It's super easy to get started and to operate `step-ca` thanks to [streamlined i
5050
### [Your own private ACME Server](https://smallstep.com/blog/private-acme-server/)
5151
- Issue certificates using ACMEv2 ([RFC8555](https://tools.ietf.org/html/rfc8555)), **the protocol used by Let's Encrypt**
5252
- Great for [using ACME in development & pre-production](https://smallstep.com/blog/private-acme-server/#local-development-pre-production)
53-
- Supports the `http-01` and `dns-01` ACME challenge types
53+
- Supports the `http-01`, `tls-alpn-01`, and `dns-01` ACME challenge types
5454
- Works with any compliant ACME client including [certbot](https://smallstep.com/blog/private-acme-server/#certbot-uploads-acme-certbot-png-certbot-example), [acme.sh](https://smallstep.com/blog/private-acme-server/#acme-sh-uploads-acme-acme-sh-png-acme-sh-example), [Caddy](https://smallstep.com/blog/private-acme-server/#caddy-uploads-acme-caddy-png-caddy-example), and [traefik](https://smallstep.com/blog/private-acme-server/#traefik-uploads-acme-traefik-png-traefik-example)
5555
- Get certificates programmatically (e.g., in [Go](https://smallstep.com/blog/private-acme-server/#golang-uploads-acme-golang-png-go-example), [Python](https://smallstep.com/blog/private-acme-server/#python-uploads-acme-python-png-python-example), [Node.js](https://smallstep.com/blog/private-acme-server/#node-js-uploads-acme-node-js-png-node-js-example))
5656

acme/authority.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package acme
22

33
import (
44
"crypto"
5+
"crypto/tls"
56
"crypto/x509"
67
"encoding/base64"
78
"net"
@@ -265,9 +266,15 @@ func (a *Authority) ValidateChallenge(p provisioner.Interface, accID, chID strin
265266
client := http.Client{
266267
Timeout: time.Duration(30 * time.Second),
267268
}
269+
dialer := &net.Dialer{
270+
Timeout: 30 * time.Second,
271+
}
268272
ch, err = ch.validate(a.db, jwk, validateOptions{
269273
httpGet: client.Get,
270274
lookupTxt: net.LookupTXT,
275+
tlsDial: func(network, addr string, config *tls.Config) (*tls.Conn, error) {
276+
return tls.DialWithDialer(dialer, network, addr, config)
277+
},
271278
})
272279
if err != nil {
273280
return nil, Wrap(err, "error attempting challenge validation")

acme/authority_test.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ func TestAuthorityGetAuthz(t *testing.T) {
730730
}
731731
},
732732
"ok": func(t *testing.T) test {
733-
var ch1B, ch2B = &[]byte{}, &[]byte{}
733+
var ch1B, ch2B, ch3B = &[]byte{}, &[]byte{}, &[]byte{}
734734
count := 0
735735
mockdb := &db.MockNoSQLDB{
736736
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
@@ -739,6 +739,8 @@ func TestAuthorityGetAuthz(t *testing.T) {
739739
*ch1B = newval
740740
case 1:
741741
*ch2B = newval
742+
case 2:
743+
*ch3B = newval
742744
}
743745
count++
744746
return nil, true, nil
@@ -758,6 +760,8 @@ func TestAuthorityGetAuthz(t *testing.T) {
758760
assert.FatalError(t, err)
759761
ch2, err := unmarshalChallenge(*ch2B)
760762
assert.FatalError(t, err)
763+
ch3, err := unmarshalChallenge(*ch3B)
764+
assert.FatalError(t, err)
761765
count = 0
762766
mockdb = &db.MockNoSQLDB{
763767
MGet: func(bucket, key []byte) ([]byte, error) {
@@ -771,6 +775,10 @@ func TestAuthorityGetAuthz(t *testing.T) {
771775
assert.Equals(t, bucket, challengeTable)
772776
assert.Equals(t, key, []byte(ch2.getID()))
773777
ret = *ch2B
778+
case 2:
779+
assert.Equals(t, bucket, challengeTable)
780+
assert.Equals(t, key, []byte(ch3.getID()))
781+
ret = *ch3B
774782
}
775783
count++
776784
return ret, nil
@@ -796,6 +804,10 @@ func TestAuthorityGetAuthz(t *testing.T) {
796804
assert.Equals(t, bucket, challengeTable)
797805
assert.Equals(t, key, []byte(ch2.getID()))
798806
ret = *ch2B
807+
case 3:
808+
assert.Equals(t, bucket, challengeTable)
809+
assert.Equals(t, key, []byte(ch3.getID()))
810+
ret = *ch3B
799811
}
800812
count++
801813
return ret, nil
@@ -876,21 +888,25 @@ func TestAuthorityNewOrder(t *testing.T) {
876888
case 1:
877889
assert.Equals(t, bucket, challengeTable)
878890
case 2:
879-
assert.Equals(t, bucket, authzTable)
880-
case 3:
881891
assert.Equals(t, bucket, challengeTable)
892+
case 3:
893+
assert.Equals(t, bucket, authzTable)
882894
case 4:
883895
assert.Equals(t, bucket, challengeTable)
884896
case 5:
885-
assert.Equals(t, bucket, authzTable)
897+
assert.Equals(t, bucket, challengeTable)
886898
case 6:
899+
assert.Equals(t, bucket, challengeTable)
900+
case 7:
901+
assert.Equals(t, bucket, authzTable)
902+
case 8:
887903
assert.Equals(t, bucket, orderTable)
888904
var o order
889905
assert.FatalError(t, json.Unmarshal(newval, &o))
890906
*acmeO, err = o.toACME(nil, dir, prov)
891907
assert.FatalError(t, err)
892908
*accID = o.AccountID
893-
case 7:
909+
case 9:
894910
assert.Equals(t, bucket, ordersByAccountIDTable)
895911
assert.Equals(t, string(key), *accID)
896912
}

acme/authz.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ func newDNSAuthz(db nosql.DB, accID string, identifier Identifier) (authz, error
294294

295295
ba.Challenges = []string{}
296296
if !ba.Wildcard {
297-
// http challenges are only permitted if the DNS is not a wildcard dns.
297+
// http and alpn challenges are only permitted if the DNS is not a wildcard dns.
298298
ch1, err := newHTTP01Challenge(db, ChallengeOptions{
299299
AccountID: accID,
300300
AuthzID: ba.ID,
@@ -303,15 +303,25 @@ func newDNSAuthz(db nosql.DB, accID string, identifier Identifier) (authz, error
303303
return nil, Wrap(err, "error creating http challenge")
304304
}
305305
ba.Challenges = append(ba.Challenges, ch1.getID())
306+
307+
ch2, err := newTLSALPN01Challenge(db, ChallengeOptions{
308+
AccountID: accID,
309+
AuthzID: ba.ID,
310+
Identifier: ba.Identifier,
311+
})
312+
if err != nil {
313+
return nil, Wrap(err, "error creating alpn challenge")
314+
}
315+
ba.Challenges = append(ba.Challenges, ch2.getID())
306316
}
307-
ch2, err := newDNS01Challenge(db, ChallengeOptions{
317+
ch3, err := newDNS01Challenge(db, ChallengeOptions{
308318
AccountID: accID,
309319
AuthzID: ba.ID,
310320
Identifier: identifier})
311321
if err != nil {
312322
return nil, Wrap(err, "error creating dns challenge")
313323
}
314-
ba.Challenges = append(ba.Challenges, ch2.getID())
324+
ba.Challenges = append(ba.Challenges, ch3.getID())
315325

316326
da := &dnsAuthz{ba}
317327
if err := da.save(db, nil); err != nil {

acme/authz_test.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ func TestNewAuthz(t *testing.T) {
173173
err: ServerInternalErr(errors.New("error creating http challenge: error saving acme challenge: force")),
174174
}
175175
},
176-
"fail/new-dns-chall-error": func(t *testing.T) test {
176+
"fail/new-tls-alpn-chall-error": func(t *testing.T) test {
177177
count := 0
178178
return test{
179179
iden: iden,
@@ -186,6 +186,22 @@ func TestNewAuthz(t *testing.T) {
186186
return nil, true, nil
187187
},
188188
},
189+
err: ServerInternalErr(errors.New("error creating alpn challenge: error saving acme challenge: force")),
190+
}
191+
},
192+
"fail/new-dns-chall-error": func(t *testing.T) test {
193+
count := 0
194+
return test{
195+
iden: iden,
196+
db: &db.MockNoSQLDB{
197+
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
198+
if count == 2 {
199+
return nil, false, errors.New("force")
200+
}
201+
count++
202+
return nil, true, nil
203+
},
204+
},
189205
err: ServerInternalErr(errors.New("error creating dns challenge: error saving acme challenge: force")),
190206
}
191207
},
@@ -195,7 +211,7 @@ func TestNewAuthz(t *testing.T) {
195211
iden: iden,
196212
db: &db.MockNoSQLDB{
197213
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
198-
if count == 2 {
214+
if count == 3 {
199215
return nil, false, errors.New("force")
200216
}
201217
count++
@@ -212,7 +228,7 @@ func TestNewAuthz(t *testing.T) {
212228
iden: iden,
213229
db: &db.MockNoSQLDB{
214230
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
215-
if count == 2 {
231+
if count == 3 {
216232
assert.Equals(t, bucket, authzTable)
217233
assert.Equals(t, old, nil)
218234

@@ -690,7 +706,8 @@ func TestAuthzUpdateStatus(t *testing.T) {
690706
},
691707
"ok/valid": func(t *testing.T) test {
692708
var (
693-
ch2 challenge
709+
ch3 challenge
710+
ch2Bytes = &([]byte{})
694711
ch1Bytes = &([]byte{})
695712
err error
696713
)
@@ -701,7 +718,9 @@ func TestAuthzUpdateStatus(t *testing.T) {
701718
if count == 0 {
702719
*ch1Bytes = newval
703720
} else if count == 1 {
704-
ch2, err = unmarshalChallenge(newval)
721+
*ch2Bytes = newval
722+
} else if count == 2 {
723+
ch3, err = unmarshalChallenge(newval)
705724
assert.FatalError(t, err)
706725
}
707726
count++
@@ -717,10 +736,10 @@ func TestAuthzUpdateStatus(t *testing.T) {
717736
assert.Fatal(t, ok)
718737
_az.baseAuthz.Error = MalformedErr(nil)
719738

720-
_ch, ok := ch2.(*dns01Challenge)
739+
_ch, ok := ch3.(*dns01Challenge)
721740
assert.Fatal(t, ok)
722741
_ch.baseChallenge.Status = StatusValid
723-
chb, err := json.Marshal(ch2)
742+
chb, err := json.Marshal(ch3)
724743

725744
clone := az.clone()
726745
clone.Status = StatusValid
@@ -736,6 +755,10 @@ func TestAuthzUpdateStatus(t *testing.T) {
736755
count++
737756
return *ch1Bytes, nil
738757
}
758+
if count == 1 {
759+
count++
760+
return *ch2Bytes, nil
761+
}
739762
count++
740763
return chb, nil
741764
},

0 commit comments

Comments
 (0)