2019-05-27 00:41:10 +00:00
package acme
2021-03-24 05:12:25 +00:00
import (
"bytes"
"context"
"crypto"
2022-09-16 01:19:52 +00:00
"crypto/ecdsa"
"crypto/elliptic"
2021-03-24 21:32:18 +00:00
"crypto/rand"
"crypto/rsa"
2021-03-24 06:04:22 +00:00
"crypto/sha256"
2021-03-24 21:32:18 +00:00
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
2021-03-24 05:12:25 +00:00
"encoding/base64"
2021-03-24 21:32:18 +00:00
"encoding/hex"
2023-01-27 14:36:48 +00:00
"encoding/json"
2022-09-16 01:19:52 +00:00
"encoding/pem"
2022-04-07 10:37:34 +00:00
"errors"
2021-03-24 05:12:25 +00:00
"fmt"
2021-03-24 21:32:18 +00:00
"io"
"math/big"
"net"
2021-03-24 05:12:25 +00:00
"net/http"
2021-03-24 21:32:18 +00:00
"net/http/httptest"
2022-09-16 01:19:52 +00:00
"reflect"
2022-11-03 23:58:25 +00:00
"strconv"
2021-03-24 06:04:22 +00:00
"strings"
2021-03-24 05:12:25 +00:00
"testing"
"time"
2022-09-16 01:19:52 +00:00
"github.com/fxamacker/cbor/v2"
2023-04-06 12:35:48 +00:00
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner"
2023-01-31 22:49:34 +00:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2022-09-16 01:19:52 +00:00
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/minica"
2023-04-06 12:35:48 +00:00
"go.step.sm/crypto/x509util"
2021-03-24 05:12:25 +00:00
)
2020-12-18 16:17:12 +00:00
2022-05-03 01:09:26 +00:00
type mockClient struct {
get func ( url string ) ( * http . Response , error )
lookupTxt func ( name string ) ( [ ] string , error )
tlsDial func ( network , addr string , config * tls . Config ) ( * tls . Conn , error )
}
func ( m * mockClient ) Get ( url string ) ( * http . Response , error ) { return m . get ( url ) }
func ( m * mockClient ) LookupTxt ( name string ) ( [ ] string , error ) { return m . lookupTxt ( name ) }
2022-09-16 01:24:43 +00:00
func ( m * mockClient ) TLSDial ( network , addr string , tlsConfig * tls . Config ) ( * tls . Conn , error ) {
return m . tlsDial ( network , addr , tlsConfig )
2022-05-03 01:09:26 +00:00
}
2023-02-10 00:48:43 +00:00
func fatalError ( t * testing . T , err error ) {
t . Helper ( )
if err != nil {
t . Fatal ( err )
}
}
2023-01-27 14:36:48 +00:00
func mustNonAttestationProvisioner ( t * testing . T ) Provisioner {
t . Helper ( )
prov := & provisioner . ACME {
Type : "ACME" ,
Name : "acme" ,
Challenges : [ ] provisioner . ACMEChallenge { provisioner . HTTP_01 } ,
}
if err := prov . Init ( provisioner . Config {
Claims : config . GlobalProvisionerClaims ,
} ) ; err != nil {
t . Fatal ( err )
}
prov . AttestationFormats = [ ] provisioner . ACMEAttestationFormat { "bogus-format" } // results in no attestation formats enabled
return prov
}
2022-09-16 17:02:10 +00:00
func mustAttestationProvisioner ( t * testing . T , roots [ ] byte ) Provisioner {
t . Helper ( )
prov := & provisioner . ACME {
Type : "ACME" ,
Name : "acme" ,
Challenges : [ ] provisioner . ACMEChallenge { provisioner . DEVICE_ATTEST_01 } ,
AttestationRoots : roots ,
}
if err := prov . Init ( provisioner . Config {
Claims : config . GlobalProvisionerClaims ,
} ) ; err != nil {
t . Fatal ( err )
}
return prov
}
2023-02-10 00:48:43 +00:00
func mustAccountAndKeyAuthorization ( t * testing . T , token string ) ( * jose . JSONWebKey , string ) {
t . Helper ( )
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
fatalError ( t , err )
keyAuth , err := KeyAuthorization ( token , jwk )
fatalError ( t , err )
return jwk , keyAuth
}
func mustAttestApple ( t * testing . T , nonce string ) ( [ ] byte , * x509 . Certificate , * x509 . Certificate ) {
t . Helper ( )
ca , err := minica . New ( )
fatalError ( t , err )
signer , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
fatalError ( t , err )
nonceSum := sha256 . Sum256 ( [ ] byte ( nonce ) )
leaf , err := ca . Sign ( & x509 . Certificate {
Subject : pkix . Name { CommonName : "attestation cert" } ,
PublicKey : signer . Public ( ) ,
ExtraExtensions : [ ] pkix . Extension {
{ Id : oidAppleSerialNumber , Value : [ ] byte ( "serial-number" ) } ,
{ Id : oidAppleUniqueDeviceIdentifier , Value : [ ] byte ( "udid" ) } ,
{ Id : oidAppleSecureEnclaveProcessorOSVersion , Value : [ ] byte ( "16.0" ) } ,
{ Id : oidAppleNonce , Value : nonceSum [ : ] } ,
} ,
} )
fatalError ( t , err )
attObj , err := cbor . Marshal ( struct {
Format string ` json:"fmt" `
AttStatement map [ string ] interface { } ` json:"attStmt,omitempty" `
} {
Format : "apple" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw } ,
} ,
} )
fatalError ( t , err )
payload , err := json . Marshal ( struct {
AttObj string ` json:"attObj" `
} {
AttObj : base64 . RawURLEncoding . EncodeToString ( attObj ) ,
} )
fatalError ( t , err )
return payload , leaf , ca . Root
}
func mustAttestYubikey ( t * testing . T , nonce , keyAuthorization string , serial int ) ( [ ] byte , * x509 . Certificate , * x509 . Certificate ) {
ca , err := minica . New ( )
fatalError ( t , err )
keyAuthSum := sha256 . Sum256 ( [ ] byte ( keyAuthorization ) )
signer , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
fatalError ( t , err )
sig , err := signer . Sign ( rand . Reader , keyAuthSum [ : ] , crypto . SHA256 )
fatalError ( t , err )
cborSig , err := cbor . Marshal ( sig )
fatalError ( t , err )
serialNumber , err := asn1 . Marshal ( serial )
fatalError ( t , err )
leaf , err := ca . Sign ( & x509 . Certificate {
Subject : pkix . Name { CommonName : "attestation cert" } ,
PublicKey : signer . Public ( ) ,
ExtraExtensions : [ ] pkix . Extension {
{ Id : oidYubicoSerialNumber , Value : serialNumber } ,
} ,
} )
fatalError ( t , err )
attObj , err := cbor . Marshal ( struct {
Format string ` json:"fmt" `
AttStatement map [ string ] interface { } ` json:"attStmt,omitempty" `
} {
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} )
fatalError ( t , err )
payload , err := json . Marshal ( struct {
AttObj string ` json:"attObj" `
} {
AttObj : base64 . RawURLEncoding . EncodeToString ( attObj ) ,
} )
fatalError ( t , err )
return payload , leaf , ca . Root
}
2021-03-24 06:16:17 +00:00
func Test_storeError ( t * testing . T ) {
type test struct {
2021-03-30 05:58:26 +00:00
ch * Challenge
db DB
markInvalid bool
err * Error
2021-03-24 06:16:17 +00:00
}
err := NewError ( ErrorMalformedType , "foo" )
tests := map [ string ] func ( t * testing . T ) test {
"fail/db.UpdateChallenge-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusValid ,
2021-03-24 06:16:17 +00:00
}
return test {
ch : ch ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusValid , updch . Status )
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 06:16:17 +00:00
return errors . New ( "force" )
} ,
} ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
}
} ,
"fail/db.UpdateChallenge-acme-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusValid ,
2021-03-24 06:16:17 +00:00
}
return test {
ch : ch ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusValid , updch . Status )
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 06:16:17 +00:00
return NewError ( ErrorMalformedType , "bar" )
} ,
} ,
err : NewError ( ErrorMalformedType , "failure saving error to acme challenge: bar" ) ,
}
} ,
"ok" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusValid ,
2021-03-24 06:16:17 +00:00
}
return test {
ch : ch ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusValid , updch . Status )
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 06:16:17 +00:00
return nil
} ,
} ,
}
} ,
2021-03-30 05:58:26 +00:00
"ok/mark-invalid" : func ( t * testing . T ) test {
ch := & Challenge {
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusValid ,
}
return test {
ch : ch ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-30 05:58:26 +00:00
return nil
} ,
} ,
markInvalid : true ,
}
} ,
2021-03-24 06:16:17 +00:00
}
for name , run := range tests {
t . Run ( name , func ( t * testing . T ) {
tc := run ( t )
2021-03-30 05:58:26 +00:00
if err := storeError ( context . Background ( ) , tc . db , tc . ch , tc . markInvalid , err ) ; err != nil {
2023-01-31 22:49:34 +00:00
if assert . Error ( t , tc . err ) {
2022-08-23 19:43:48 +00:00
var k * Error
if errors . As ( err , & k ) {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , tc . err . Type , k . Type )
assert . Equal ( t , tc . err . Detail , k . Detail )
assert . Equal ( t , tc . err . Status , k . Status )
assert . Equal ( t , tc . err . Err . Error ( ) , k . Err . Error ( ) )
2022-08-23 19:43:48 +00:00
} else {
2023-01-31 22:49:34 +00:00
assert . Fail ( t , "unexpected error type" )
2021-03-24 06:16:17 +00:00
}
}
} else {
assert . Nil ( t , tc . err )
}
} )
}
}
2021-03-24 05:12:25 +00:00
func TestKeyAuthorization ( t * testing . T ) {
2019-05-27 00:41:10 +00:00
type test struct {
2021-03-24 05:12:25 +00:00
token string
jwk * jose . JSONWebKey
exp string
2021-03-24 06:04:22 +00:00
err * Error
2019-05-27 00:41:10 +00:00
}
2021-03-24 05:12:25 +00:00
tests := map [ string ] func ( t * testing . T ) test {
"fail/jwk-thumbprint-error" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
jwk . Key = "foo"
return test {
token : "1234" ,
jwk : jwk ,
2021-03-24 06:04:22 +00:00
err : NewErrorISE ( "error generating JWK thumbprint: square/go-jose: unknown key type 'string'" ) ,
2021-03-24 05:12:25 +00:00
}
2019-05-27 00:41:10 +00:00
} ,
2021-03-24 05:12:25 +00:00
"ok" : func ( t * testing . T ) test {
token := "1234"
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
thumbprint , err := jwk . Thumbprint ( crypto . SHA256 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
encPrint := base64 . RawURLEncoding . EncodeToString ( thumbprint )
return test {
token : token ,
jwk : jwk ,
exp : fmt . Sprintf ( "%s.%s" , token , encPrint ) ,
}
2019-05-27 00:41:10 +00:00
} ,
}
2021-03-24 05:12:25 +00:00
for name , run := range tests {
2019-05-27 00:41:10 +00:00
t . Run ( name , func ( t * testing . T ) {
2021-03-24 05:12:25 +00:00
tc := run ( t )
if ka , err := KeyAuthorization ( tc . token , tc . jwk ) ; err != nil {
2023-01-31 22:49:34 +00:00
if assert . Error ( t , tc . err ) {
2022-08-23 19:43:48 +00:00
var k * Error
if errors . As ( err , & k ) {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , tc . err . Type , k . Type )
assert . Equal ( t , tc . err . Detail , k . Detail )
assert . Equal ( t , tc . err . Status , k . Status )
assert . Equal ( t , tc . err . Err . Error ( ) , k . Err . Error ( ) )
2022-08-23 19:43:48 +00:00
} else {
2023-01-31 22:49:34 +00:00
assert . Fail ( t , "unexpected error type" )
2021-03-24 06:04:22 +00:00
}
2019-05-27 00:41:10 +00:00
}
} else {
if assert . Nil ( t , tc . err ) {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , tc . exp , ka )
2019-05-27 00:41:10 +00:00
}
}
} )
}
}
2021-03-24 06:30:59 +00:00
func TestChallenge_Validate ( t * testing . T ) {
type test struct {
2023-01-27 14:36:48 +00:00
ch * Challenge
vc Client
jwk * jose . JSONWebKey
db DB
srv * httptest . Server
payload [ ] byte
ctx context . Context
err * Error
2021-03-24 06:30:59 +00:00
}
tests := map [ string ] func ( t * testing . T ) test {
"ok/already-valid" : func ( t * testing . T ) test {
ch := & Challenge {
Status : StatusValid ,
}
return test {
ch : ch ,
}
} ,
"fail/already-invalid" : func ( t * testing . T ) test {
ch := & Challenge {
Status : StatusInvalid ,
}
return test {
ch : ch ,
}
} ,
"fail/unexpected-type" : func ( t * testing . T ) test {
ch := & Challenge {
Status : StatusPending ,
Type : "foo" ,
}
return test {
ch : ch ,
err : NewErrorISE ( "unexpected challenge type 'foo'" ) ,
}
} ,
"fail/http-01" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-25 07:23:57 +00:00
ID : "chID" ,
Status : StatusPending ,
Type : "http-01" ,
Token : "token" ,
Value : "zap.internal" ,
2021-03-24 06:30:59 +00:00
}
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
2021-03-24 06:30:59 +00:00
return nil , errors . New ( "force" )
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , ChallengeType ( "http-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 06:30:59 +00:00
err := NewError ( ErrorConnectionType , "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s: force" , ch . Token )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 06:30:59 +00:00
return errors . New ( "force" )
} ,
} ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
}
} ,
"ok/http-01" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-25 07:23:57 +00:00
ID : "chID" ,
Status : StatusPending ,
Type : "http-01" ,
Token : "token" ,
Value : "zap.internal" ,
2021-03-24 06:30:59 +00:00
}
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
2021-03-24 06:30:59 +00:00
return nil , errors . New ( "force" )
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , ChallengeType ( "http-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 06:30:59 +00:00
err := NewError ( ErrorConnectionType , "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s: force" , ch . Token )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 06:30:59 +00:00
return nil
} ,
} ,
}
} ,
2022-11-03 23:58:25 +00:00
"ok/http-01-insecure" : func ( t * testing . T ) test {
t . Cleanup ( func ( ) {
InsecurePortHTTP01 = 0
} )
ch := & Challenge {
ID : "chID" ,
Status : StatusPending ,
Type : "http-01" ,
Token : "token" ,
Value : "zap.internal" ,
}
InsecurePortHTTP01 = 8080
return test {
ch : ch ,
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
return nil , errors . New ( "force" )
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , ChallengeType ( "http-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2022-11-03 23:58:25 +00:00
err := NewError ( ErrorConnectionType , "error doing http GET for url http://zap.internal:8080/.well-known/acme-challenge/%s: force" , ch . Token )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2022-11-03 23:58:25 +00:00
return nil
} ,
} ,
}
} ,
2021-03-24 06:30:59 +00:00
"fail/dns-01" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-25 07:23:57 +00:00
ID : "chID" ,
Type : "dns-01" ,
Status : StatusPending ,
Token : "token" ,
Value : "zap.internal" ,
2021-03-24 06:30:59 +00:00
}
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
lookupTxt : func ( url string ) ( [ ] string , error ) {
2021-03-24 06:30:59 +00:00
return nil , errors . New ( "force" )
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , ChallengeType ( "dns-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 06:30:59 +00:00
err := NewError ( ErrorDNSType , "error looking up TXT records for domain %s: force" , ch . Value )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 06:30:59 +00:00
return errors . New ( "force" )
} ,
} ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
}
} ,
"ok/dns-01" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-25 07:23:57 +00:00
ID : "chID" ,
Type : "dns-01" ,
Status : StatusPending ,
Token : "token" ,
Value : "zap.internal" ,
2021-03-24 06:30:59 +00:00
}
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
lookupTxt : func ( url string ) ( [ ] string , error ) {
2021-03-24 06:30:59 +00:00
return nil , errors . New ( "force" )
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , ChallengeType ( "dns-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 06:30:59 +00:00
err := NewError ( ErrorDNSType , "error looking up TXT records for domain %s: force" , ch . Value )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 06:30:59 +00:00
return nil
} ,
} ,
}
} ,
2021-03-24 21:32:18 +00:00
"fail/tls-alpn-01" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-25 07:23:57 +00:00
ID : "chID" ,
Token : "token" ,
Type : "tls-alpn-01" ,
Status : StatusPending ,
Value : "zap.internal" ,
2021-03-24 21:32:18 +00:00
}
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : func ( network , addr string , config * tls . Config ) ( * tls . Conn , error ) {
2021-03-24 21:32:18 +00:00
return nil , errors . New ( "force" )
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorConnectionType , "error doing TLS dial for %v:443: force" , ch . Value )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return errors . New ( "force" )
} ,
} ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
}
} ,
"ok/tls-alpn-01" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-25 07:23:57 +00:00
ID : "chID" ,
Token : "token" ,
Type : "tls-alpn-01" ,
Status : StatusPending ,
Value : "zap.internal" ,
2021-03-24 21:32:18 +00:00
}
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , false , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
2022-11-03 23:58:25 +00:00
return test {
ch : ch ,
vc : & mockClient {
tlsDial : tlsDial ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusValid , updch . Status )
assert . Nil ( t , updch . Error )
2022-11-03 23:58:25 +00:00
return nil
} ,
} ,
srv : srv ,
jwk : jwk ,
}
} ,
"ok/tls-alpn-01-insecure" : func ( t * testing . T ) test {
t . Cleanup ( func ( ) {
InsecurePortTLSALPN01 = 0
} )
ch := & Challenge {
ID : "chID" ,
Token : "token" ,
Type : "tls-alpn-01" ,
Status : StatusPending ,
Value : "zap.internal" ,
}
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2022-11-03 23:58:25 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2022-11-03 23:58:25 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , false , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2022-11-03 23:58:25 +00:00
l , err := net . Listen ( "tcp" , "127.0.0.1:0" )
if err != nil {
if l , err = net . Listen ( "tcp6" , "[::1]:0" ) ; err != nil {
t . Fatalf ( "failed to listen on a port: %v" , err )
}
}
_ , port , err := net . SplitHostPort ( l . Addr ( ) . String ( ) )
if err != nil {
t . Fatalf ( "failed to split host port: %v" , err )
}
// Use an insecure port
InsecurePortTLSALPN01 , err = strconv . Atoi ( port )
if err != nil {
t . Fatalf ( "failed to convert port to int: %v" , err )
}
srv , tlsDial := newTestTLSALPNServer ( cert , func ( srv * httptest . Server ) {
srv . Listener . Close ( )
srv . Listener = l
} )
srv . Start ( )
2021-03-24 21:32:18 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2021-03-24 21:32:18 +00:00
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusValid , updch . Status )
assert . Nil ( t , updch . Error )
2021-03-24 21:32:18 +00:00
return nil
} ,
} ,
srv : srv ,
jwk : jwk ,
}
} ,
2023-01-27 14:36:48 +00:00
"fail/device-attest-01" : func ( t * testing . T ) test {
payload , err := json . Marshal ( struct {
Error string ` json:"error" `
} {
Error : "an error" ,
} )
2023-01-31 22:49:34 +00:00
assert . NoError ( t , err )
2023-01-27 14:36:48 +00:00
return test {
2023-02-10 00:48:43 +00:00
ch : & Challenge {
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
} ,
2023-01-27 14:36:48 +00:00
payload : payload ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
2023-01-27 14:36:48 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "12345678" , updch . Value )
2023-01-27 14:36:48 +00:00
err := NewError ( ErrorRejectedIdentifierType , "payload contained error: an error" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
2023-01-27 14:36:48 +00:00
return errors . New ( "force" )
} ,
} ,
err : NewError ( ErrorServerInternalType , "failure saving error to acme challenge: force" ) ,
}
} ,
"ok/device-attest-01" : func ( t * testing . T ) test {
2023-02-10 00:48:43 +00:00
jwk , keyAuth := mustAccountAndKeyAuthorization ( t , "token" )
payload , leaf , root := mustAttestYubikey ( t , "nonce" , keyAuth , 1234 )
2023-01-27 14:36:48 +00:00
2023-02-10 00:48:43 +00:00
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : root . Raw } )
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , caRoot ) )
2023-01-27 14:36:48 +00:00
return test {
2023-02-10 00:48:43 +00:00
ch : & Challenge {
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "1234" ,
} ,
2023-01-27 14:36:48 +00:00
payload : payload ,
ctx : ctx ,
jwk : jwk ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
MockUpdateAuthorization : func ( ctx context . Context , az * Authorization ) error {
fingerprint , err := keyutil . Fingerprint ( leaf . PublicKey )
assert . NoError ( t , err )
assert . Equal ( t , "azID" , az . ID )
assert . Equal ( t , fingerprint , az . Fingerprint )
return nil
} ,
2023-01-27 14:36:48 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
2023-02-10 00:48:43 +00:00
assert . Equal ( t , "token" , updch . Token )
2023-01-31 22:49:34 +00:00
assert . Equal ( t , StatusValid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "1234" , updch . Value )
2023-01-27 14:36:48 +00:00
return nil
} ,
} ,
}
} ,
2021-03-24 06:30:59 +00:00
}
for name , run := range tests {
t . Run ( name , func ( t * testing . T ) {
tc := run ( t )
2021-03-24 21:32:18 +00:00
if tc . srv != nil {
defer tc . srv . Close ( )
}
2023-01-27 14:36:48 +00:00
ctx := tc . ctx
if ctx == nil {
ctx = context . Background ( )
}
ctx = NewClientContext ( ctx , tc . vc )
if err := tc . ch . Validate ( ctx , tc . db , tc . jwk , tc . payload ) ; err != nil {
2023-01-31 22:49:34 +00:00
if assert . Error ( t , tc . err ) {
2022-08-23 19:43:48 +00:00
var k * Error
if errors . As ( err , & k ) {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , tc . err . Type , k . Type )
assert . Equal ( t , tc . err . Detail , k . Detail )
assert . Equal ( t , tc . err . Status , k . Status )
assert . Equal ( t , tc . err . Err . Error ( ) , k . Err . Error ( ) )
2022-08-23 19:43:48 +00:00
} else {
2023-01-31 22:49:34 +00:00
assert . Fail ( t , "unexpected error type" )
2021-03-24 06:30:59 +00:00
}
}
} else {
assert . Nil ( t , tc . err )
}
} )
}
}
2021-03-24 05:12:25 +00:00
type errReader int
func ( errReader ) Read ( p [ ] byte ) ( n int , err error ) {
return 0 , errors . New ( "force" )
}
func ( errReader ) Close ( ) error {
return nil
}
func TestHTTP01Validate ( t * testing . T ) {
2020-02-07 14:50:22 +00:00
type test struct {
2022-05-03 01:09:26 +00:00
vc Client
2021-03-24 05:12:25 +00:00
ch * Challenge
jwk * jose . JSONWebKey
db DB
2020-02-07 14:50:22 +00:00
err * Error
}
2021-03-24 05:12:25 +00:00
tests := map [ string ] func ( t * testing . T ) test {
"fail/http-get-error-store-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusPending ,
2021-03-24 05:12:25 +00:00
}
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
2021-03-24 05:12:25 +00:00
return nil , errors . New ( "force" )
} ,
2020-02-07 14:50:22 +00:00
} ,
2021-03-24 05:12:25 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 05:12:25 +00:00
err := NewError ( ErrorConnectionType , "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s: force" , ch . Token )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 05:12:25 +00:00
return errors . New ( "force" )
} ,
2020-02-07 14:50:22 +00:00
} ,
2021-03-24 05:12:25 +00:00
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
}
2020-02-07 14:50:22 +00:00
} ,
2021-03-24 05:12:25 +00:00
"ok/http-get-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusPending ,
2020-02-07 14:50:22 +00:00
}
2021-03-24 05:12:25 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
2021-03-24 05:12:25 +00:00
return nil , errors . New ( "force" )
} ,
2019-05-27 00:41:10 +00:00
} ,
2021-03-24 05:12:25 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 05:12:25 +00:00
err := NewError ( ErrorConnectionType , "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s: force" , ch . Token )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 05:12:25 +00:00
return nil
} ,
} ,
}
2019-05-27 00:41:10 +00:00
} ,
2021-03-24 05:12:25 +00:00
"fail/http-get->=400-store-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusPending ,
2021-03-24 05:12:25 +00:00
}
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
2021-03-24 05:12:25 +00:00
return & http . Response {
StatusCode : http . StatusBadRequest ,
2021-03-30 05:58:26 +00:00
Body : errReader ( 0 ) ,
2021-03-24 05:12:25 +00:00
} , nil
} ,
2019-05-27 00:41:10 +00:00
} ,
2021-03-24 05:12:25 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 05:12:25 +00:00
err := NewError ( ErrorConnectionType , "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s with status code 400" , ch . Token )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 05:12:25 +00:00
return errors . New ( "force" )
} ,
} ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
}
2019-05-27 00:41:10 +00:00
} ,
2021-03-24 05:12:25 +00:00
"ok/http-get->=400" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusPending ,
2019-05-27 00:41:10 +00:00
}
2021-03-24 05:12:25 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
2021-03-24 05:12:25 +00:00
return & http . Response {
StatusCode : http . StatusBadRequest ,
2021-03-30 05:58:26 +00:00
Body : errReader ( 0 ) ,
2021-03-24 05:12:25 +00:00
} , nil
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 05:12:25 +00:00
err := NewError ( ErrorConnectionType , "error doing http GET for url http://zap.internal/.well-known/acme-challenge/%s with status code 400" , ch . Token )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 05:12:25 +00:00
return nil
} ,
} ,
}
} ,
"fail/read-body" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusPending ,
2021-03-24 05:12:25 +00:00
}
2019-05-27 00:41:10 +00:00
2021-03-24 05:12:25 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
2021-03-24 05:12:25 +00:00
return & http . Response {
Body : errReader ( 0 ) ,
} , nil
} ,
} ,
err : NewErrorISE ( "error reading response body for url http://zap.internal/.well-known/acme-challenge/%s: force" , ch . Token ) ,
}
} ,
"fail/key-auth-gen-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusPending ,
2019-05-27 00:41:10 +00:00
}
2021-03-24 05:12:25 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
jwk . Key = "foo"
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 05:12:25 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
2021-03-24 05:12:25 +00:00
return & http . Response {
2021-11-12 23:46:34 +00:00
Body : io . NopCloser ( bytes . NewBufferString ( "foo" ) ) ,
2021-03-24 05:12:25 +00:00
} , nil
2019-05-27 00:41:10 +00:00
} ,
} ,
2021-03-24 05:12:25 +00:00
jwk : jwk ,
err : NewErrorISE ( "error generating JWK thumbprint: square/go-jose: unknown key type 'string'" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
2021-03-24 05:12:25 +00:00
"ok/key-auth-mismatch" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusPending ,
2021-03-24 05:12:25 +00:00
}
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 05:12:25 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
2021-03-24 05:12:25 +00:00
return & http . Response {
2021-11-12 23:46:34 +00:00
Body : io . NopCloser ( bytes . NewBufferString ( "foo" ) ) ,
2021-03-24 05:12:25 +00:00
} , nil
} ,
} ,
jwk : jwk ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusInvalid , updch . Status )
2021-03-24 05:12:25 +00:00
err := NewError ( ErrorRejectedIdentifierType ,
"keyAuthorization does not match; expected %s, but got foo" , expKeyAuth )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 05:12:25 +00:00
return nil
2019-05-27 00:41:10 +00:00
} ,
} ,
}
} ,
2021-03-24 05:12:25 +00:00
"fail/key-auth-mismatch-store-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusPending ,
2021-03-24 05:12:25 +00:00
}
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 05:12:25 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
2021-03-24 05:12:25 +00:00
return & http . Response {
2021-11-12 23:46:34 +00:00
Body : io . NopCloser ( bytes . NewBufferString ( "foo" ) ) ,
2021-03-24 05:12:25 +00:00
} , nil
} ,
} ,
jwk : jwk ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusInvalid , updch . Status )
2021-03-24 05:12:25 +00:00
err := NewError ( ErrorRejectedIdentifierType ,
"keyAuthorization does not match; expected %s, but got foo" , expKeyAuth )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 05:12:25 +00:00
return errors . New ( "force" )
2019-05-27 00:41:10 +00:00
} ,
} ,
2021-03-24 05:12:25 +00:00
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
2021-03-24 05:12:25 +00:00
"fail/update-challenge-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusPending ,
2021-03-24 05:12:25 +00:00
}
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
2021-03-24 05:12:25 +00:00
return & http . Response {
2021-11-12 23:46:34 +00:00
Body : io . NopCloser ( bytes . NewBufferString ( expKeyAuth ) ) ,
2021-03-24 05:12:25 +00:00
} , nil
} ,
} ,
jwk : jwk ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusValid , updch . Status )
assert . Nil ( t , updch . Error )
2021-03-24 05:12:25 +00:00
va , err := time . Parse ( time . RFC3339 , updch . ValidatedAt )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
now := clock . Now ( )
assert . True ( t , va . Add ( - time . Minute ) . Before ( now ) )
assert . True ( t , va . Add ( time . Minute ) . After ( now ) )
2019-05-27 00:41:10 +00:00
2021-03-24 05:12:25 +00:00
return errors . New ( "force" )
} ,
} ,
err : NewErrorISE ( "error updating challenge: force" ) ,
}
} ,
"ok" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : "zap.internal" ,
Status : StatusPending ,
2021-03-24 05:12:25 +00:00
}
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 05:12:25 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
get : func ( url string ) ( * http . Response , error ) {
2021-03-24 05:12:25 +00:00
return & http . Response {
2021-11-12 23:46:34 +00:00
Body : io . NopCloser ( bytes . NewBufferString ( expKeyAuth ) ) ,
2021-03-24 05:12:25 +00:00
} , nil
} ,
} ,
jwk : jwk ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Equal ( t , StatusValid , updch . Status )
assert . Nil ( t , updch . Error )
2021-03-24 05:12:25 +00:00
va , err := time . Parse ( time . RFC3339 , updch . ValidatedAt )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
now := clock . Now ( )
assert . True ( t , va . Add ( - time . Minute ) . Before ( now ) )
assert . True ( t , va . Add ( time . Minute ) . After ( now ) )
return nil
2019-05-27 00:41:10 +00:00
} ,
} ,
}
} ,
}
for name , run := range tests {
t . Run ( name , func ( t * testing . T ) {
tc := run ( t )
2022-05-03 01:09:26 +00:00
ctx := NewClientContext ( context . Background ( ) , tc . vc )
if err := http01Validate ( ctx , tc . ch , tc . db , tc . jwk ) ; err != nil {
2023-01-31 22:49:34 +00:00
if assert . Error ( t , tc . err ) {
2022-08-23 19:43:48 +00:00
var k * Error
if errors . As ( err , & k ) {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , tc . err . Type , k . Type )
assert . Equal ( t , tc . err . Detail , k . Detail )
assert . Equal ( t , tc . err . Status , k . Status )
assert . Equal ( t , tc . err . Err . Error ( ) , k . Err . Error ( ) )
2022-08-23 19:43:48 +00:00
} else {
2023-01-31 22:49:34 +00:00
assert . Fail ( t , "unexpected error type" )
2021-03-24 05:12:25 +00:00
}
2019-05-27 00:41:10 +00:00
}
} else {
assert . Nil ( t , tc . err )
}
} )
}
}
2021-03-24 06:04:22 +00:00
func TestDNS01Validate ( t * testing . T ) {
fulldomain := "*.zap.internal"
domain := strings . TrimPrefix ( fulldomain , "*." )
type test struct {
2022-05-03 01:09:26 +00:00
vc Client
2021-03-24 06:04:22 +00:00
ch * Challenge
jwk * jose . JSONWebKey
db DB
err * Error
}
tests := map [ string ] func ( t * testing . T ) test {
"fail/lookupTXT-store-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : fulldomain ,
Status : StatusPending ,
2021-03-24 06:04:22 +00:00
}
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
lookupTxt : func ( url string ) ( [ ] string , error ) {
2021-03-24 06:04:22 +00:00
return nil , errors . New ( "force" )
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , fulldomain , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 06:04:22 +00:00
err := NewError ( ErrorDNSType , "error looking up TXT records for domain %s: force" , domain )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 06:04:22 +00:00
return errors . New ( "force" )
} ,
} ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
}
} ,
"ok/lookupTXT-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : fulldomain ,
Status : StatusPending ,
2021-03-24 06:04:22 +00:00
}
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
lookupTxt : func ( url string ) ( [ ] string , error ) {
2021-03-24 06:04:22 +00:00
return nil , errors . New ( "force" )
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , fulldomain , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 06:04:22 +00:00
err := NewError ( ErrorDNSType , "error looking up TXT records for domain %s: force" , domain )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 06:04:22 +00:00
return nil
} ,
} ,
}
} ,
"fail/key-auth-gen-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : fulldomain ,
Status : StatusPending ,
2021-03-24 06:04:22 +00:00
}
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 06:04:22 +00:00
jwk . Key = "foo"
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
lookupTxt : func ( url string ) ( [ ] string , error ) {
2021-03-24 06:04:22 +00:00
return [ ] string { "foo" } , nil
} ,
} ,
jwk : jwk ,
err : NewErrorISE ( "error generating JWK thumbprint: square/go-jose: unknown key type 'string'" ) ,
}
} ,
"fail/key-auth-mismatch-store-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : fulldomain ,
Status : StatusPending ,
2021-03-24 06:04:22 +00:00
}
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 06:04:22 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 06:04:22 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
lookupTxt : func ( url string ) ( [ ] string , error ) {
2021-03-24 06:04:22 +00:00
return [ ] string { "foo" , "bar" } , nil
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , fulldomain , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 06:04:22 +00:00
err := NewError ( ErrorRejectedIdentifierType , "keyAuthorization does not match; expected %s, but got %s" , expKeyAuth , [ ] string { "foo" , "bar" } )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 06:04:22 +00:00
return errors . New ( "force" )
} ,
} ,
jwk : jwk ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
}
} ,
"ok/key-auth-mismatch-store-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : fulldomain ,
Status : StatusPending ,
2021-03-24 06:04:22 +00:00
}
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 06:04:22 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 06:04:22 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
lookupTxt : func ( url string ) ( [ ] string , error ) {
2021-03-24 06:04:22 +00:00
return [ ] string { "foo" , "bar" } , nil
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , fulldomain , updch . Value )
assert . Equal ( t , StatusPending , updch . Status )
2021-03-24 06:04:22 +00:00
err := NewError ( ErrorRejectedIdentifierType , "keyAuthorization does not match; expected %s, but got %s" , expKeyAuth , [ ] string { "foo" , "bar" } )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 06:04:22 +00:00
return nil
} ,
} ,
jwk : jwk ,
}
} ,
"fail/update-challenge-error" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : fulldomain ,
Status : StatusPending ,
2021-03-24 06:04:22 +00:00
}
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 06:04:22 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 06:04:22 +00:00
h := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
expected := base64 . RawURLEncoding . EncodeToString ( h [ : ] )
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
lookupTxt : func ( url string ) ( [ ] string , error ) {
2021-03-24 06:04:22 +00:00
return [ ] string { "foo" , expected } , nil
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , fulldomain , ch . Value )
assert . Equal ( t , StatusValid , updch . Status )
assert . Nil ( t , updch . Error )
2021-03-24 06:04:22 +00:00
va , err := time . Parse ( time . RFC3339 , updch . ValidatedAt )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 06:04:22 +00:00
now := clock . Now ( )
assert . True ( t , va . Add ( - time . Minute ) . Before ( now ) )
assert . True ( t , va . Add ( time . Minute ) . After ( now ) )
return errors . New ( "force" )
} ,
} ,
jwk : jwk ,
err : NewErrorISE ( "error updating challenge: force" ) ,
}
} ,
"ok" : func ( t * testing . T ) test {
ch := & Challenge {
2021-03-30 05:58:26 +00:00
ID : "chID" ,
Token : "token" ,
Value : fulldomain ,
Status : StatusPending ,
2021-03-24 06:04:22 +00:00
}
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 06:04:22 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 06:04:22 +00:00
h := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
expected := base64 . RawURLEncoding . EncodeToString ( h [ : ] )
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
lookupTxt : func ( url string ) ( [ ] string , error ) {
2021-03-24 06:04:22 +00:00
return [ ] string { "foo" , expected } , nil
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , fulldomain , updch . Value )
assert . Equal ( t , StatusValid , updch . Status )
assert . Nil ( t , updch . Error )
2021-03-24 06:04:22 +00:00
va , err := time . Parse ( time . RFC3339 , updch . ValidatedAt )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 06:04:22 +00:00
now := clock . Now ( )
assert . True ( t , va . Add ( - time . Minute ) . Before ( now ) )
assert . True ( t , va . Add ( time . Minute ) . After ( now ) )
return nil
} ,
} ,
jwk : jwk ,
}
} ,
}
for name , run := range tests {
t . Run ( name , func ( t * testing . T ) {
tc := run ( t )
2022-05-03 01:09:26 +00:00
ctx := NewClientContext ( context . Background ( ) , tc . vc )
if err := dns01Validate ( ctx , tc . ch , tc . db , tc . jwk ) ; err != nil {
2023-01-31 22:49:34 +00:00
if assert . Error ( t , tc . err ) {
2022-08-23 19:43:48 +00:00
var k * Error
if errors . As ( err , & k ) {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , tc . err . Type , k . Type )
assert . Equal ( t , tc . err . Detail , k . Detail )
assert . Equal ( t , tc . err . Status , k . Status )
assert . Equal ( t , tc . err . Err . Error ( ) , k . Err . Error ( ) )
2022-08-23 19:43:48 +00:00
} else {
2023-01-31 22:49:34 +00:00
assert . Fail ( t , "unexpected error type" )
2021-03-24 06:04:22 +00:00
}
}
} else {
assert . Nil ( t , tc . err )
}
} )
}
}
2022-05-03 01:09:26 +00:00
type tlsDialer func ( network , addr string , config * tls . Config ) ( conn * tls . Conn , err error )
2022-11-03 23:58:25 +00:00
func newTestTLSALPNServer ( validationCert * tls . Certificate , opts ... func ( * httptest . Server ) ) ( * httptest . Server , tlsDialer ) {
2021-03-24 21:32:18 +00:00
srv := httptest . NewUnstartedServer ( http . NewServeMux ( ) )
srv . Config . TLSNextProto = map [ string ] func ( * http . Server , * tls . Conn , http . Handler ) {
"acme-tls/1" : func ( _ * http . Server , conn * tls . Conn , _ http . Handler ) {
// no-op
} ,
"http/1.1" : func ( _ * http . Server , conn * tls . Conn , _ http . Handler ) {
panic ( "unexpected http/1.1 next proto" )
} ,
2019-05-27 00:41:10 +00:00
}
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
srv . TLS = & tls . Config {
GetCertificate : func ( hello * tls . ClientHelloInfo ) ( * tls . Certificate , error ) {
if len ( hello . SupportedProtos ) == 1 && hello . SupportedProtos [ 0 ] == "acme-tls/1" {
return validationCert , nil
2019-05-27 00:41:10 +00:00
}
2021-03-24 21:32:18 +00:00
return nil , nil
2019-05-27 00:41:10 +00:00
} ,
2021-03-24 21:32:18 +00:00
NextProtos : [ ] string {
"acme-tls/1" ,
"http/1.1" ,
} ,
}
2021-03-24 05:12:25 +00:00
2022-11-03 23:58:25 +00:00
// Apply options
for _ , fn := range opts {
fn ( srv )
}
2021-03-24 21:32:18 +00:00
srv . Listener = tls . NewListener ( srv . Listener , srv . TLS )
//srv.Config.ErrorLog = log.New(ioutil.Discard, "", 0) // hush
return srv , func ( network , addr string , config * tls . Config ) ( conn * tls . Conn , err error ) {
return tls . DialWithDialer ( & net . Dialer { Timeout : time . Second } , "tcp" , srv . Listener . Addr ( ) . String ( ) , config )
}
}
// noopConn is a mock net.Conn that does nothing.
type noopConn struct { }
func ( c * noopConn ) Read ( _ [ ] byte ) ( n int , err error ) { return 0 , io . EOF }
func ( c * noopConn ) Write ( _ [ ] byte ) ( n int , err error ) { return 0 , io . EOF }
func ( c * noopConn ) Close ( ) error { return nil }
func ( c * noopConn ) LocalAddr ( ) net . Addr { return & net . IPAddr { IP : net . IPv4zero , Zone : "" } }
func ( c * noopConn ) RemoteAddr ( ) net . Addr { return & net . IPAddr { IP : net . IPv4zero , Zone : "" } }
func ( c * noopConn ) SetDeadline ( t time . Time ) error { return nil }
func ( c * noopConn ) SetReadDeadline ( t time . Time ) error { return nil }
func ( c * noopConn ) SetWriteDeadline ( t time . Time ) error { return nil }
func newTLSALPNValidationCert ( keyAuthHash [ ] byte , obsoleteOID , critical bool , names ... string ) ( * tls . Certificate , error ) {
privateKey , err := rsa . GenerateKey ( rand . Reader , 2048 )
if err != nil {
return nil , err
}
certTemplate := & x509 . Certificate {
SerialNumber : big . NewInt ( 1337 ) ,
Subject : pkix . Name {
Organization : [ ] string { "Test" } ,
2020-02-11 14:57:28 +00:00
} ,
2021-03-24 21:32:18 +00:00
NotBefore : time . Now ( ) ,
NotAfter : time . Now ( ) . AddDate ( 0 , 0 , 1 ) ,
KeyUsage : x509 . KeyUsageDigitalSignature | x509 . KeyUsageCertSign ,
ExtKeyUsage : [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageServerAuth } ,
BasicConstraintsValid : true ,
DNSNames : names ,
}
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
if keyAuthHash != nil {
oid := asn1 . ObjectIdentifier { 1 , 3 , 6 , 1 , 5 , 5 , 7 , 1 , 31 }
if obsoleteOID {
oid = asn1 . ObjectIdentifier { 1 , 3 , 6 , 1 , 5 , 5 , 7 , 1 , 30 , 1 }
}
2021-10-08 18:59:57 +00:00
keyAuthHashEnc , _ := asn1 . Marshal ( keyAuthHash )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
certTemplate . ExtraExtensions = [ ] pkix . Extension {
{
Id : oid ,
Critical : critical ,
Value : keyAuthHashEnc ,
} ,
}
}
cert , err := x509 . CreateCertificate ( rand . Reader , certTemplate , certTemplate , privateKey . Public ( ) , privateKey )
if err != nil {
return nil , err
}
return & tls . Certificate {
PrivateKey : privateKey ,
Certificate : [ ] [ ] byte { cert } ,
} , nil
}
func TestTLSALPN01Validate ( t * testing . T ) {
makeTLSCh := func ( ) * Challenge {
return & Challenge {
2021-03-25 07:23:57 +00:00
ID : "chID" ,
Token : "token" ,
Type : "tls-alpn-01" ,
Status : StatusPending ,
Value : "zap.internal" ,
2021-03-24 21:32:18 +00:00
}
}
type test struct {
2022-05-03 01:09:26 +00:00
vc Client
2021-03-24 21:32:18 +00:00
ch * Challenge
jwk * jose . JSONWebKey
db DB
srv * httptest . Server
err * Error
}
tests := map [ string ] func ( t * testing . T ) test {
"fail/tlsDial-store-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 05:12:25 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : func ( network , addr string , config * tls . Config ) ( * tls . Conn , error ) {
2021-03-24 05:12:25 +00:00
return nil , errors . New ( "force" )
} ,
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusPending , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorConnectionType , "error doing TLS dial for %v:443: force" , ch . Value )
2021-03-24 05:12:25 +00:00
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return errors . New ( "force" )
2019-05-27 00:41:10 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
2021-03-24 21:32:18 +00:00
"ok/tlsDial-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 05:12:25 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : func ( network , addr string , config * tls . Config ) ( * tls . Conn , error ) {
2021-03-24 21:32:18 +00:00
return nil , errors . New ( "force" )
2021-03-24 05:12:25 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusPending , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorConnectionType , "error doing TLS dial for %v:443: force" , ch . Value )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return nil
2019-05-27 00:41:10 +00:00
} ,
} ,
}
} ,
2021-03-24 21:32:18 +00:00
"ok/tlsDial-timeout" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
srv , tlsDial := newTestTLSALPNServer ( nil )
// srv.Start() - do not start server to cause timeout
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2021-03-24 21:32:18 +00:00
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusPending , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
err := NewError ( ErrorConnectionType , "error doing TLS dial for %v:443: context deadline exceeded" , ch . Value )
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return nil
} ,
} ,
srv : srv ,
}
} ,
"ok/no-certificates-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2021-03-24 05:12:25 +00:00
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : func ( network , addr string , config * tls . Config ) ( * tls . Conn , error ) {
2021-03-24 21:32:18 +00:00
return tls . Client ( & noopConn { } , config ) , nil
} ,
2021-03-24 05:12:25 +00:00
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "tls-alpn-01 challenge for %v resulted in no certificates" , ch . Value )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return nil
2019-05-27 00:41:10 +00:00
} ,
} ,
}
} ,
2021-03-24 21:32:18 +00:00
"fail/no-certificates-store-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2019-05-27 00:41:10 +00:00
2021-03-24 21:32:18 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : func ( network , addr string , config * tls . Config ) ( * tls . Conn , error ) {
2021-03-24 21:32:18 +00:00
return tls . Client ( & noopConn { } , config ) , nil
} ,
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "tls-alpn-01 challenge for %v resulted in no certificates" , ch . Value )
2021-03-24 05:12:25 +00:00
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return errors . New ( "force" )
} ,
} ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
}
} ,
"ok/error-no-protocol" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2019-05-27 00:41:10 +00:00
2021-03-24 21:32:18 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2019-05-27 00:41:10 +00:00
2021-03-24 21:32:18 +00:00
srv := httptest . NewTLSServer ( nil )
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : func ( network , addr string , config * tls . Config ) ( * tls . Conn , error ) {
2021-03-24 21:32:18 +00:00
return tls . DialWithDialer ( & net . Dialer { Timeout : time . Second } , "tcp" , srv . Listener . Addr ( ) . String ( ) , config )
} ,
2021-03-24 05:12:25 +00:00
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "cannot negotiate ALPN acme-tls/1 protocol for tls-alpn-01 challenge" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return nil
2021-03-24 05:12:25 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
2019-05-27 00:41:10 +00:00
}
} ,
2021-03-24 21:32:18 +00:00
"fail/no-protocol-store-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2021-03-24 05:12:25 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
srv := httptest . NewTLSServer ( nil )
2021-03-24 05:12:25 +00:00
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : func ( network , addr string , config * tls . Config ) ( * tls . Conn , error ) {
2021-03-24 21:32:18 +00:00
return tls . DialWithDialer ( & net . Dialer { Timeout : time . Second } , "tcp" , srv . Listener . Addr ( ) . String ( ) , config )
} ,
2019-05-27 00:41:10 +00:00
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "cannot negotiate ALPN acme-tls/1 protocol for tls-alpn-01 challenge" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return errors . New ( "force" )
2019-05-27 00:41:10 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
2021-06-04 06:42:24 +00:00
"ok/no-names-nor-ips-error" : func ( t * testing . T ) test {
2021-03-24 21:32:18 +00:00
ch := makeTLSCh ( )
2019-05-27 00:41:10 +00:00
2021-03-24 21:32:18 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , false , true )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2019-05-27 00:41:10 +00:00
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
2021-06-04 06:42:24 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v" , ch . Value )
2021-03-24 21:32:18 +00:00
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return nil
2019-05-27 00:41:10 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
2019-05-27 00:41:10 +00:00
}
} ,
2021-03-24 21:32:18 +00:00
"fail/no-names-store-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2019-05-27 00:41:10 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2019-05-27 00:41:10 +00:00
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
2019-05-27 00:41:10 +00:00
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , false , true )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2019-05-27 00:41:10 +00:00
2021-03-24 05:12:25 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2019-05-27 00:41:10 +00:00
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
2021-06-04 06:42:24 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v" , ch . Value )
2021-03-24 21:32:18 +00:00
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return errors . New ( "force" )
2019-05-27 00:41:10 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
2021-03-24 21:32:18 +00:00
"ok/too-many-names-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2019-05-27 00:41:10 +00:00
2021-03-24 21:32:18 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2019-05-27 00:41:10 +00:00
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
2019-05-27 00:41:10 +00:00
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , false , true , ch . Value , "other.internal" )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2019-05-27 00:41:10 +00:00
2021-03-24 05:12:25 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2019-05-27 00:41:10 +00:00
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
2021-06-04 06:42:24 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v" , ch . Value )
2021-03-24 21:32:18 +00:00
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return nil
2019-05-27 00:41:10 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
2020-02-07 14:50:22 +00:00
}
} ,
2021-03-24 21:32:18 +00:00
"ok/wrong-name" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2020-02-07 14:50:22 +00:00
2021-03-24 05:12:25 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , false , true , "other.internal" )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
2020-02-07 14:50:22 +00:00
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2021-03-24 21:32:18 +00:00
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
2021-06-04 06:42:24 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v" , ch . Value )
2021-03-24 21:32:18 +00:00
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return nil
2020-02-07 14:50:22 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
2020-02-07 14:50:22 +00:00
}
} ,
2021-03-24 21:32:18 +00:00
"fail/key-auth-gen-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2020-02-07 14:50:22 +00:00
2021-03-24 05:12:25 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
2021-03-24 21:32:18 +00:00
jwk . Key = "foo"
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , false , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2021-03-24 21:32:18 +00:00
} ,
srv : srv ,
jwk : jwk ,
err : NewErrorISE ( "error generating JWK thumbprint: square/go-jose: unknown key type 'string'" ) ,
}
} ,
"ok/error-no-extension" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( nil , false , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2020-02-07 14:50:22 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2020-02-07 14:50:22 +00:00
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: missing acmeValidationV1 extension" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return nil
2020-02-07 14:50:22 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
2020-02-07 14:50:22 +00:00
}
} ,
2021-03-24 21:32:18 +00:00
"fail/no-extension-store-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2020-02-07 14:50:22 +00:00
2021-03-24 05:12:25 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( nil , false , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2021-03-24 21:32:18 +00:00
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: missing acmeValidationV1 extension" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return errors . New ( "force" )
} ,
} ,
srv : srv ,
jwk : jwk ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
}
} ,
"ok/error-extension-not-critical" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2020-02-07 14:50:22 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , false , false , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2020-02-07 14:50:22 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2020-02-07 14:50:22 +00:00
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: acmeValidationV1 extension not critical" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return nil
2020-02-07 14:50:22 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
2020-02-07 14:50:22 +00:00
}
} ,
2021-03-24 21:32:18 +00:00
"fail/extension-not-critical-store-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2020-02-07 14:50:22 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2020-02-07 14:50:22 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , false , false , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2020-02-07 14:50:22 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2021-03-24 21:32:18 +00:00
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: acmeValidationV1 extension not critical" )
2021-03-24 05:12:25 +00:00
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return errors . New ( "force" )
2021-03-24 05:12:25 +00:00
} ,
2020-02-07 14:50:22 +00:00
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
2020-02-07 14:50:22 +00:00
jwk : jwk ,
2021-03-24 21:32:18 +00:00
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
}
} ,
"ok/error-malformed-extension" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( [ ] byte { 1 , 2 , 3 } , false , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2021-03-24 21:32:18 +00:00
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: malformed acmeValidationV1 extension value" )
2021-03-24 05:12:25 +00:00
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return nil
2020-02-07 14:50:22 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
2020-02-07 14:50:22 +00:00
}
} ,
2021-03-24 21:32:18 +00:00
"fail/malformed-extension-store-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( [ ] byte { 1 , 2 , 3 } , false , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2021-03-24 21:32:18 +00:00
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: malformed acmeValidationV1 extension value" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return errors . New ( "force" )
} ,
} ,
srv : srv ,
jwk : jwk ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
2021-03-24 05:12:25 +00:00
}
} ,
2021-03-24 21:32:18 +00:00
"ok/error-keyauth-mismatch" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
incorrectTokenHash := sha256 . Sum256 ( [ ] byte ( "mismatched" ) )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( incorrectTokenHash [ : ] , false , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2021-03-24 21:32:18 +00:00
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: " +
"expected acmeValidationV1 extension value %s for this challenge but got %s" ,
hex . EncodeToString ( expKeyAuthHash [ : ] ) , hex . EncodeToString ( incorrectTokenHash [ : ] ) )
2021-03-24 05:12:25 +00:00
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return nil
} ,
} ,
srv : srv ,
jwk : jwk ,
}
2021-03-24 05:12:25 +00:00
} ,
2021-03-24 21:32:18 +00:00
"fail/keyauth-mismatch-store-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
incorrectTokenHash := sha256 . Sum256 ( [ ] byte ( "mismatched" ) )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( incorrectTokenHash [ : ] , false , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
2021-03-24 05:12:25 +00:00
2021-03-24 21:32:18 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2021-03-24 05:12:25 +00:00
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: " +
"expected acmeValidationV1 extension value %s for this challenge but got %s" ,
hex . EncodeToString ( expKeyAuthHash [ : ] ) , hex . EncodeToString ( incorrectTokenHash [ : ] ) )
2020-02-07 20:14:08 +00:00
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return errors . New ( "force" )
2020-02-07 20:14:08 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
2020-02-07 20:14:08 +00:00
}
} ,
2021-03-24 21:32:18 +00:00
"ok/error-obsolete-oid" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , true , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
2020-02-07 14:50:22 +00:00
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2020-02-07 14:50:22 +00:00
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: " +
"obsolete id-pe-acmeIdentifier in acmeValidationV1 extension" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return nil
2020-02-07 14:50:22 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
2020-02-07 14:50:22 +00:00
}
} ,
2021-03-24 21:32:18 +00:00
"fail/obsolete-oid-store-error" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , true , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2020-02-07 14:50:22 +00:00
2021-03-24 21:32:18 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
2020-02-07 14:50:22 +00:00
2021-03-24 05:12:25 +00:00
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2019-05-27 00:41:10 +00:00
} ,
2021-03-24 21:32:18 +00:00
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
2021-03-24 21:32:18 +00:00
err := NewError ( ErrorRejectedIdentifierType , "incorrect certificate for tls-alpn-01 challenge: " +
"obsolete id-pe-acmeIdentifier in acmeValidationV1 extension" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2021-03-24 21:32:18 +00:00
return errors . New ( "force" )
2019-05-27 00:41:10 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
err : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"ok" : func ( t * testing . T ) test {
2021-03-24 21:32:18 +00:00
ch := makeTLSCh ( )
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , false , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-03-24 21:32:18 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
2021-06-18 15:27:35 +00:00
return test {
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2021-06-18 15:27:35 +00:00
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusValid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "zap.internal" , updch . Value )
assert . Nil ( t , updch . Error )
2021-06-18 15:27:35 +00:00
return nil
} ,
} ,
srv : srv ,
jwk : jwk ,
}
} ,
"ok/ip" : func ( t * testing . T ) test {
ch := makeTLSCh ( )
ch . Value = "127.0.0.1"
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-06-18 15:27:35 +00:00
expKeyAuth , err := KeyAuthorization ( ch . Token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-06-18 15:27:35 +00:00
expKeyAuthHash := sha256 . Sum256 ( [ ] byte ( expKeyAuth ) )
cert , err := newTLSALPNValidationCert ( expKeyAuthHash [ : ] , false , true , ch . Value )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2021-06-18 15:27:35 +00:00
srv , tlsDial := newTestTLSALPNServer ( cert )
srv . Start ( )
2019-05-27 00:41:10 +00:00
return test {
2021-03-24 21:32:18 +00:00
ch : ch ,
2022-05-03 01:09:26 +00:00
vc : & mockClient {
tlsDial : tlsDial ,
2021-03-24 21:32:18 +00:00
} ,
db : & MockDB {
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusValid , updch . Status )
assert . Equal ( t , ChallengeType ( "tls-alpn-01" ) , updch . Type )
assert . Equal ( t , "127.0.0.1" , updch . Value )
assert . Nil ( t , updch . Error )
2021-03-24 21:32:18 +00:00
return nil
2019-05-27 00:41:10 +00:00
} ,
} ,
2021-03-24 21:32:18 +00:00
srv : srv ,
jwk : jwk ,
2019-05-27 00:41:10 +00:00
}
} ,
}
for name , run := range tests {
t . Run ( name , func ( t * testing . T ) {
tc := run ( t )
2021-03-24 21:32:18 +00:00
if tc . srv != nil {
defer tc . srv . Close ( )
}
2022-05-03 01:09:26 +00:00
ctx := NewClientContext ( context . Background ( ) , tc . vc )
if err := tlsalpn01Validate ( ctx , tc . ch , tc . db , tc . jwk ) ; err != nil {
2023-01-31 22:49:34 +00:00
if assert . Error ( t , tc . err ) {
2022-08-23 19:43:48 +00:00
var k * Error
if errors . As ( err , & k ) {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , tc . err . Type , k . Type )
assert . Equal ( t , tc . err . Detail , k . Detail )
assert . Equal ( t , tc . err . Status , k . Status )
assert . Equal ( t , tc . err . Err . Error ( ) , k . Err . Error ( ) )
assert . Equal ( t , tc . err . Subproblems , k . Subproblems )
2022-08-23 19:43:48 +00:00
} else {
2023-01-31 22:49:34 +00:00
assert . Fail ( t , "unexpected error type" )
2021-03-24 21:32:18 +00:00
}
2019-05-27 00:41:10 +00:00
}
} else {
2021-03-24 21:32:18 +00:00
assert . Nil ( t , tc . err )
2019-05-27 00:41:10 +00:00
}
} )
}
2021-06-18 15:27:35 +00:00
}
func Test_reverseAddr ( t * testing . T ) {
type args struct {
ip net . IP
}
tests := [ ] struct {
name string
args args
wantArpa string
} {
{
name : "ok/ipv4" ,
args : args {
ip : net . ParseIP ( "127.0.0.1" ) ,
} ,
wantArpa : "1.0.0.127.in-addr.arpa." ,
} ,
{
name : "ok/ipv6" ,
args : args {
ip : net . ParseIP ( "2001:db8::567:89ab" ) ,
} ,
wantArpa : "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa." ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if gotArpa := reverseAddr ( tt . args . ip ) ; gotArpa != tt . wantArpa {
t . Errorf ( "reverseAddr() = %v, want %v" , gotArpa , tt . wantArpa )
}
} )
}
}
func Test_serverName ( t * testing . T ) {
type args struct {
ch * Challenge
}
tests := [ ] struct {
name string
args args
want string
} {
{
name : "ok/dns" ,
args : args {
ch : & Challenge {
Value : "example.com" ,
} ,
} ,
want : "example.com" ,
} ,
{
name : "ok/ipv4" ,
args : args {
ch : & Challenge {
Value : "127.0.0.1" ,
} ,
} ,
want : "1.0.0.127.in-addr.arpa." ,
} ,
{
2021-06-25 12:07:40 +00:00
name : "ok/ipv6" ,
2021-06-18 15:27:35 +00:00
args : args {
ch : & Challenge {
Value : "2001:db8::567:89ab" ,
} ,
} ,
want : "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa." ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if got := serverName ( tt . args . ch ) ; got != tt . want {
t . Errorf ( "serverName() = %v, want %v" , got , tt . want )
}
} )
}
2019-05-27 00:41:10 +00:00
}
2022-04-07 10:37:34 +00:00
func Test_http01ChallengeHost ( t * testing . T ) {
tests := [ ] struct {
name string
value string
want string
} {
{
name : "dns" ,
value : "www.example.com" ,
want : "www.example.com" ,
} ,
{
name : "ipv4" ,
value : "127.0.0.1" ,
want : "127.0.0.1" ,
} ,
{
name : "ipv6" ,
value : "::1" ,
want : "[::1]" ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if got := http01ChallengeHost ( tt . value ) ; got != tt . want {
t . Errorf ( "http01ChallengeHost() = %v, want %v" , got , tt . want )
}
} )
}
}
2022-09-16 01:19:52 +00:00
func Test_doAppleAttestationFormat ( t * testing . T ) {
ctx := context . Background ( )
ca , err := minica . New ( )
if err != nil {
t . Fatal ( err )
}
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : ca . Root . Raw } )
signer , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
if err != nil {
t . Fatal ( err )
}
leaf , err := ca . Sign ( & x509 . Certificate {
Subject : pkix . Name { CommonName : "attestation cert" } ,
PublicKey : signer . Public ( ) ,
ExtraExtensions : [ ] pkix . Extension {
{ Id : oidAppleSerialNumber , Value : [ ] byte ( "serial-number" ) } ,
{ Id : oidAppleUniqueDeviceIdentifier , Value : [ ] byte ( "udid" ) } ,
{ Id : oidAppleSecureEnclaveProcessorOSVersion , Value : [ ] byte ( "16.0" ) } ,
{ Id : oidAppleNonce , Value : [ ] byte ( "nonce" ) } ,
} ,
} )
if err != nil {
t . Fatal ( err )
}
2023-02-10 00:48:43 +00:00
fingerprint , err := keyutil . Fingerprint ( signer . Public ( ) )
if err != nil {
t . Fatal ( err )
}
2022-09-16 01:19:52 +00:00
type args struct {
ctx context . Context
prov Provisioner
ch * Challenge
2023-01-27 14:36:48 +00:00
att * attestationObject
2022-09-16 01:19:52 +00:00
}
tests := [ ] struct {
name string
args args
want * appleAttestationData
wantErr bool
} {
2023-01-27 14:36:48 +00:00
{ "ok" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { } , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "apple" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw } ,
} ,
} } , & appleAttestationData {
Nonce : [ ] byte ( "nonce" ) ,
SerialNumber : "serial-number" ,
UDID : "udid" ,
SEPVersion : "16.0" ,
Certificate : leaf ,
2023-02-10 00:48:43 +00:00
Fingerprint : fingerprint ,
2022-09-16 01:19:52 +00:00
} , false } ,
2023-01-27 14:36:48 +00:00
{ "fail apple issuer" , args { ctx , mustAttestationProvisioner ( t , nil ) , & Challenge { } , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "apple" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw } ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail missing x5c" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { } , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "apple" ,
AttStatement : map [ string ] interface { } {
"foo" : "bar" ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail empty issuer" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { } , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "apple" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { } ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail leaf type" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { } , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "apple" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { "leaf" , ca . Intermediate . Raw } ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail leaf parse" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { } , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "apple" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw [ : 100 ] , ca . Intermediate . Raw } ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail intermediate type" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { } , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "apple" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , "intermediate" } ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail intermediate parse" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { } , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "apple" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw [ : 100 ] } ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail verify" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { } , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "apple" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw } ,
} ,
} } , nil , true } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
got , err := doAppleAttestationFormat ( tt . args . ctx , tt . args . prov , tt . args . ch , tt . args . att )
if ( err != nil ) != tt . wantErr {
t . Errorf ( "doAppleAttestationFormat() error = %v, wantErr %v" , err , tt . wantErr )
return
}
if ! reflect . DeepEqual ( got , tt . want ) {
t . Errorf ( "doAppleAttestationFormat() = %v, want %v" , got , tt . want )
}
} )
}
}
func Test_doStepAttestationFormat ( t * testing . T ) {
ctx := context . Background ( )
ca , err := minica . New ( )
if err != nil {
t . Fatal ( err )
}
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : ca . Root . Raw } )
makeLeaf := func ( signer crypto . Signer , serialNumber [ ] byte ) * x509 . Certificate {
leaf , err := ca . Sign ( & x509 . Certificate {
Subject : pkix . Name { CommonName : "attestation cert" } ,
PublicKey : signer . Public ( ) ,
ExtraExtensions : [ ] pkix . Extension {
{ Id : oidYubicoSerialNumber , Value : serialNumber } ,
} ,
} )
if err != nil {
t . Fatal ( err )
}
return leaf
}
mustSigner := func ( kty , crv string , size int ) crypto . Signer {
s , err := keyutil . GenerateSigner ( kty , crv , size )
if err != nil {
t . Fatal ( err )
}
return s
}
signer , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
if err != nil {
t . Fatal ( err )
}
serialNumber , err := asn1 . Marshal ( 1234 )
if err != nil {
t . Fatal ( err )
}
leaf := makeLeaf ( signer , serialNumber )
2023-02-10 00:48:43 +00:00
fingerprint , err := keyutil . Fingerprint ( signer . Public ( ) )
if err != nil {
t . Fatal ( err )
}
2022-09-16 01:19:52 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
if err != nil {
t . Fatal ( err )
}
keyAuth , err := KeyAuthorization ( "token" , jwk )
if err != nil {
t . Fatal ( err )
}
keyAuthSum := sha256 . Sum256 ( [ ] byte ( keyAuth ) )
sig , err := signer . Sign ( rand . Reader , keyAuthSum [ : ] , crypto . SHA256 )
if err != nil {
t . Fatal ( err )
}
cborSig , err := cbor . Marshal ( sig )
if err != nil {
t . Fatal ( err )
}
otherSigner , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
if err != nil {
t . Fatal ( err )
}
otherSig , err := otherSigner . Sign ( rand . Reader , keyAuthSum [ : ] , crypto . SHA256 )
if err != nil {
t . Fatal ( err )
}
otherCBORSig , err := cbor . Marshal ( otherSig )
if err != nil {
t . Fatal ( err )
}
type args struct {
ctx context . Context
prov Provisioner
ch * Challenge
jwk * jose . JSONWebKey
2023-01-27 14:36:48 +00:00
att * attestationObject
2022-09-16 01:19:52 +00:00
}
tests := [ ] struct {
name string
args args
want * stepAttestationData
wantErr bool
} {
2023-01-27 14:36:48 +00:00
{ "ok" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , & stepAttestationData {
SerialNumber : "1234" ,
Certificate : leaf ,
2023-02-10 00:48:43 +00:00
Fingerprint : fingerprint ,
2022-09-16 01:19:52 +00:00
} , false } ,
2023-01-27 14:36:48 +00:00
{ "fail yubico issuer" , args { ctx , mustAttestationProvisioner ( t , nil ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail x5c type" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] [ ] byte { leaf . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail x5c empty" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail leaf type" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { "leaf" , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail leaf parse" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw [ : 100 ] , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail intermediate type" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , "intermediate" } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail intermediate parse" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw [ : 100 ] } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail verify" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail sig type" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : string ( cborSig ) ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail sig unmarshal" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : [ ] byte ( "bad-sig" ) ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail keyAuthorization" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , & jose . JSONWebKey { Key : [ ] byte ( "not an asymmetric key" ) } , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail sig verify P-256" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : otherCBORSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail sig verify P-384" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { makeLeaf ( mustSigner ( "EC" , "P-384" , 0 ) , serialNumber ) . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail sig verify RSA" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { makeLeaf ( mustSigner ( "RSA" , "" , 2048 ) , serialNumber ) . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail sig verify Ed25519" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { makeLeaf ( mustSigner ( "OKP" , "Ed25519" , 0 ) , serialNumber ) . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
2023-01-27 14:36:48 +00:00
{ "fail unmarshal serial number" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-09-16 01:19:52 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { makeLeaf ( signer , [ ] byte ( "bad-serial" ) ) . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
got , err := doStepAttestationFormat ( tt . args . ctx , tt . args . prov , tt . args . ch , tt . args . jwk , tt . args . att )
if ( err != nil ) != tt . wantErr {
t . Errorf ( "doStepAttestationFormat() error = %#v, wantErr %v" , err , tt . wantErr )
return
}
if ! reflect . DeepEqual ( got , tt . want ) {
t . Errorf ( "doStepAttestationFormat() = %v, want %v" , got , tt . want )
}
} )
}
}
2022-10-11 01:27:11 +00:00
func Test_doStepAttestationFormat_noCAIntermediate ( t * testing . T ) {
ctx := context . Background ( )
// This CA simulates a YubiKey v5.2.4, where the attestation intermediate in
// the CA does not have the basic constraint extension. With the current
2022-10-11 17:04:42 +00:00
// validation of the certificate the test case below returns an error. If
// we change the validation to support this use case, the test case below
// should change.
2022-10-11 01:27:11 +00:00
//
// See https://github.com/Yubico/yubikey-manager/issues/522
ca , err := minica . New ( minica . WithIntermediateTemplate ( ` { "subject": {{ toJson .Subject }} } ` ) )
if err != nil {
t . Fatal ( err )
}
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : ca . Root . Raw } )
makeLeaf := func ( signer crypto . Signer , serialNumber [ ] byte ) * x509 . Certificate {
leaf , err := ca . Sign ( & x509 . Certificate {
Subject : pkix . Name { CommonName : "attestation cert" } ,
PublicKey : signer . Public ( ) ,
ExtraExtensions : [ ] pkix . Extension {
{ Id : oidYubicoSerialNumber , Value : serialNumber } ,
} ,
} )
if err != nil {
t . Fatal ( err )
}
return leaf
}
signer , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
if err != nil {
t . Fatal ( err )
}
serialNumber , err := asn1 . Marshal ( 1234 )
if err != nil {
t . Fatal ( err )
}
leaf := makeLeaf ( signer , serialNumber )
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
if err != nil {
t . Fatal ( err )
}
keyAuth , err := KeyAuthorization ( "token" , jwk )
if err != nil {
t . Fatal ( err )
}
keyAuthSum := sha256 . Sum256 ( [ ] byte ( keyAuth ) )
sig , err := signer . Sign ( rand . Reader , keyAuthSum [ : ] , crypto . SHA256 )
if err != nil {
t . Fatal ( err )
}
cborSig , err := cbor . Marshal ( sig )
if err != nil {
t . Fatal ( err )
}
type args struct {
ctx context . Context
prov Provisioner
ch * Challenge
jwk * jose . JSONWebKey
2023-01-27 14:36:48 +00:00
att * attestationObject
2022-10-11 01:27:11 +00:00
}
tests := [ ] struct {
name string
args args
want * stepAttestationData
wantErr bool
} {
2023-01-27 14:36:48 +00:00
{ "fail no intermediate" , args { ctx , mustAttestationProvisioner ( t , caRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
2022-10-11 01:27:11 +00:00
Format : "step" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} } , nil , true } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
got , err := doStepAttestationFormat ( tt . args . ctx , tt . args . prov , tt . args . ch , tt . args . jwk , tt . args . att )
if ( err != nil ) != tt . wantErr {
t . Errorf ( "doStepAttestationFormat() error = %#v, wantErr %v" , err , tt . wantErr )
return
}
if ! reflect . DeepEqual ( got , tt . want ) {
t . Errorf ( "doStepAttestationFormat() = %v, want %v" , got , tt . want )
}
} )
}
}
2023-01-27 14:36:48 +00:00
func Test_deviceAttest01Validate ( t * testing . T ) {
invalidPayload := "!?"
errorPayload , err := json . Marshal ( struct {
Error string ` json:"error" `
} {
Error : "an error" ,
} )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
errorBase64Payload , err := json . Marshal ( struct {
AttObj string ` json:"attObj" `
} {
AttObj : "?!" ,
} )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
errorCBORPayload , err := json . Marshal ( struct {
AttObj string ` json:"attObj" `
} {
AttObj : "AAAA" ,
} )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
type args struct {
ctx context . Context
ch * Challenge
db DB
jwk * jose . JSONWebKey
payload [ ] byte
}
type test struct {
args args
wantErr * Error
}
tests := map [ string ] func ( t * testing . T ) test {
2023-02-10 00:48:43 +00:00
"fail/getAuthorization" : func ( t * testing . T ) test {
return test {
args : args {
ch : & Challenge {
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
} ,
db : & MockDB {
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
return nil , errors . New ( "not found" )
} ,
} ,
payload : [ ] byte ( invalidPayload ) ,
} ,
wantErr : NewErrorISE ( "error loading authorization: not found" ) ,
}
} ,
2023-01-27 14:36:48 +00:00
"fail/json.Unmarshal" : func ( t * testing . T ) test {
return test {
args : args {
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
} ,
db : & MockDB {
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
2023-01-27 14:36:48 +00:00
} ,
payload : [ ] byte ( invalidPayload ) ,
} ,
wantErr : NewErrorISE ( "error unmarshalling JSON: invalid character '!' looking for beginning of value" ) ,
}
} ,
"fail/storeError" : func ( t * testing . T ) test {
return test {
args : args {
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
2023-01-27 14:36:48 +00:00
} ,
payload : errorPayload ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
2023-01-27 14:36:48 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "12345678" , updch . Value )
2023-01-27 14:36:48 +00:00
err := NewError ( ErrorRejectedIdentifierType , "payload contained error: an error" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2023-01-27 14:36:48 +00:00
return errors . New ( "force" )
} ,
} ,
} ,
wantErr : NewErrorISE ( "failure saving error to acme challenge: force" ) ,
}
} ,
"ok/storeError-return-nil" : func ( t * testing . T ) test {
return test {
args : args {
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
2023-01-27 14:36:48 +00:00
} ,
payload : errorPayload ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
2023-01-27 14:36:48 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "12345678" , updch . Value )
2023-01-27 14:36:48 +00:00
err := NewError ( ErrorRejectedIdentifierType , "payload contained error: an error" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2023-01-27 14:36:48 +00:00
return nil
} ,
} ,
} ,
wantErr : nil ,
}
} ,
"fail/base64-decode" : func ( t * testing . T ) test {
return test {
args : args {
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
} ,
db : & MockDB {
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
2023-01-27 14:36:48 +00:00
} ,
payload : errorBase64Payload ,
} ,
wantErr : NewErrorISE ( "error base64 decoding attObj: illegal base64 data at input byte 0" ) ,
}
} ,
"fail/cbor.Unmarshal" : func ( t * testing . T ) test {
return test {
args : args {
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
} ,
db : & MockDB {
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
2023-01-27 14:36:48 +00:00
} ,
payload : errorCBORPayload ,
} ,
wantErr : NewErrorISE ( "error unmarshalling CBOR: cbor: cannot unmarshal positive integer into Go value of type acme.attestationObject" ) ,
}
} ,
"ok/prov.IsAttestationFormatEnabled" : func ( t * testing . T ) test {
2023-02-10 00:48:43 +00:00
jwk , keyAuth := mustAccountAndKeyAuthorization ( t , "token" )
payload , _ , _ := mustAttestYubikey ( t , "nonce" , keyAuth , 12345678 )
2023-01-27 14:36:48 +00:00
ctx := NewProvisionerContext ( context . Background ( ) , mustNonAttestationProvisioner ( t ) )
2023-02-10 00:48:43 +00:00
2023-01-27 14:36:48 +00:00
return test {
args : args {
ctx : ctx ,
2023-02-10 00:48:43 +00:00
jwk : jwk ,
2023-01-27 14:36:48 +00:00
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
2023-01-27 14:36:48 +00:00
} ,
payload : payload ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
2023-01-27 14:36:48 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "12345678" , updch . Value )
2023-01-27 14:36:48 +00:00
err := NewError ( ErrorBadAttestationStatementType , "attestation format %q is not enabled" , "step" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2023-01-27 14:36:48 +00:00
return nil
} ,
} ,
} ,
wantErr : nil ,
}
} ,
"ok/doAppleAttestationFormat-storeError" : func ( t * testing . T ) test {
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , nil ) )
attObj , err := cbor . Marshal ( struct {
Format string ` json:"fmt" `
AttStatement map [ string ] interface { } ` json:"attStmt,omitempty" `
} {
Format : "apple" ,
AttStatement : map [ string ] interface { } { } ,
} )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
payload , err := json . Marshal ( struct {
AttObj string ` json:"attObj" `
} {
AttObj : base64 . RawURLEncoding . EncodeToString ( attObj ) ,
} )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
return test {
args : args {
ctx : ctx ,
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
2023-01-27 14:36:48 +00:00
} ,
payload : payload ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
2023-01-27 14:36:48 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "12345678" , updch . Value )
2023-01-27 14:36:48 +00:00
err := NewError ( ErrorBadAttestationStatementType , "x5c not present" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2023-01-27 14:36:48 +00:00
return nil
} ,
} ,
} ,
wantErr : nil ,
}
} ,
"ok/doAppleAttestationFormat-non-matching-nonce" : func ( t * testing . T ) test {
2023-02-10 00:48:43 +00:00
jwk , _ := mustAccountAndKeyAuthorization ( t , "token" )
payload , _ , root := mustAttestApple ( t , "bad-nonce" )
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : root . Raw } )
2023-01-27 14:36:48 +00:00
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , caRoot ) )
2023-02-10 00:48:43 +00:00
2023-01-27 14:36:48 +00:00
return test {
args : args {
ctx : ctx ,
2023-02-10 00:48:43 +00:00
jwk : jwk ,
2023-01-27 14:36:48 +00:00
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "serial-number" ,
2023-01-27 14:36:48 +00:00
} ,
payload : payload ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
2023-01-27 14:36:48 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
2023-02-10 00:48:43 +00:00
assert . Equal ( t , "serial-number" , updch . Value )
2023-01-27 14:36:48 +00:00
err := NewError ( ErrorBadAttestationStatementType , "challenge token does not match" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2023-01-27 14:36:48 +00:00
return nil
} ,
} ,
} ,
wantErr : nil ,
}
} ,
"ok/doAppleAttestationFormat-non-matching-challenge-value" : func ( t * testing . T ) test {
2023-02-10 00:48:43 +00:00
jwk , _ := mustAccountAndKeyAuthorization ( t , "token" )
payload , _ , root := mustAttestApple ( t , "nonce" )
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : root . Raw } )
2023-01-27 14:36:48 +00:00
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , caRoot ) )
return test {
args : args {
ctx : ctx ,
2023-02-10 00:48:43 +00:00
jwk : jwk ,
2023-01-27 14:36:48 +00:00
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "nonce" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "non-matching-value" ,
2023-01-27 14:36:48 +00:00
} ,
payload : payload ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
2023-01-27 14:36:48 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "nonce" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "non-matching-value" , updch . Value )
2023-01-27 14:36:48 +00:00
err := NewError ( ErrorBadAttestationStatementType , "permanent identifier does not match" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2023-01-27 14:36:48 +00:00
return nil
} ,
} ,
} ,
wantErr : nil ,
}
} ,
"ok/doStepAttestationFormat-storeError" : func ( t * testing . T ) test {
ca , err := minica . New ( )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : ca . Root . Raw } )
signer , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
token := "token"
keyAuth , err := KeyAuthorization ( token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
keyAuthSum := sha256 . Sum256 ( [ ] byte ( keyAuth ) )
sig , err := signer . Sign ( rand . Reader , keyAuthSum [ : ] , crypto . SHA256 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
cborSig , err := cbor . Marshal ( sig )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , caRoot ) )
attObj , err := cbor . Marshal ( struct {
Format string ` json:"fmt" `
AttStatement map [ string ] interface { } ` json:"attStmt,omitempty" `
} {
Format : "step" ,
AttStatement : map [ string ] interface { } {
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
payload , err := json . Marshal ( struct {
AttObj string ` json:"attObj" `
} {
AttObj : base64 . RawURLEncoding . EncodeToString ( attObj ) ,
} )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
return test {
args : args {
ctx : ctx ,
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
2023-01-27 14:36:48 +00:00
} ,
payload : payload ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
2023-01-27 14:36:48 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "12345678" , updch . Value )
2023-01-27 14:36:48 +00:00
err := NewError ( ErrorBadAttestationStatementType , "x5c not present" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2023-01-27 14:36:48 +00:00
return nil
} ,
} ,
} ,
wantErr : nil ,
}
} ,
"ok/doStepAttestationFormat-non-matching-identifier" : func ( t * testing . T ) test {
2023-02-10 00:48:43 +00:00
jwk , keyAuth := mustAccountAndKeyAuthorization ( t , "token" )
payload , leaf , root := mustAttestYubikey ( t , "nonce" , keyAuth , 87654321 )
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : root . Raw } )
2023-01-27 14:36:48 +00:00
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , caRoot ) )
2023-02-10 00:48:43 +00:00
2023-01-27 14:36:48 +00:00
return test {
args : args {
ctx : ctx ,
2023-02-10 00:48:43 +00:00
jwk : jwk ,
2023-01-27 14:36:48 +00:00
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
2023-01-27 14:36:48 +00:00
} ,
payload : payload ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
MockUpdateAuthorization : func ( ctx context . Context , az * Authorization ) error {
fingerprint , err := keyutil . Fingerprint ( leaf . PublicKey )
assert . NoError ( t , err )
assert . Equal ( t , "azID" , az . ID )
assert . Equal ( t , fingerprint , az . Fingerprint )
return nil
} ,
2023-01-27 14:36:48 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "12345678" , updch . Value )
2023-01-27 14:36:48 +00:00
err := NewError ( ErrorBadAttestationStatementType , "permanent identifier does not match" ) .
AddSubproblems ( NewSubproblemWithIdentifier (
ErrorMalformedType ,
Identifier { Type : "permanent-identifier" , Value : "12345678" } ,
"challenge identifier \"12345678\" doesn't match the attested hardware identifier \"87654321\"" ,
) )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2023-01-27 14:36:48 +00:00
return nil
} ,
} ,
} ,
wantErr : nil ,
}
} ,
"ok/unknown-attestation-format" : func ( t * testing . T ) test {
ca , err := minica . New ( )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
signer , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
token := "token"
keyAuth , err := KeyAuthorization ( token , jwk )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
keyAuthSum := sha256 . Sum256 ( [ ] byte ( keyAuth ) )
sig , err := signer . Sign ( rand . Reader , keyAuthSum [ : ] , crypto . SHA256 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
cborSig , err := cbor . Marshal ( sig )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
ctx := NewProvisionerContext ( context . Background ( ) , mustNonAttestationProvisioner ( t ) )
makeLeaf := func ( signer crypto . Signer , serialNumber [ ] byte ) * x509 . Certificate {
leaf , err := ca . Sign ( & x509 . Certificate {
Subject : pkix . Name { CommonName : "attestation cert" } ,
PublicKey : signer . Public ( ) ,
ExtraExtensions : [ ] pkix . Extension {
{ Id : oidYubicoSerialNumber , Value : serialNumber } ,
} ,
} )
if err != nil {
t . Fatal ( err )
}
return leaf
}
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
serialNumber , err := asn1 . Marshal ( 87654321 )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
leaf := makeLeaf ( signer , serialNumber )
attObj , err := cbor . Marshal ( struct {
Format string ` json:"fmt" `
AttStatement map [ string ] interface { } ` json:"attStmt,omitempty" `
} {
Format : "bogus-format" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { leaf . Raw , ca . Intermediate . Raw } ,
"alg" : - 7 ,
"sig" : cborSig ,
} ,
} )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
payload , err := json . Marshal ( struct {
AttObj string ` json:"attObj" `
} {
AttObj : base64 . RawURLEncoding . EncodeToString ( attObj ) ,
} )
2023-01-31 22:49:34 +00:00
require . NoError ( t , err )
2023-01-27 14:36:48 +00:00
return test {
args : args {
ctx : ctx ,
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
2023-01-27 14:36:48 +00:00
} ,
payload : payload ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
2023-01-27 14:36:48 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "12345678" , updch . Value )
2023-01-27 14:36:48 +00:00
err := NewError ( ErrorBadAttestationStatementType , "unexpected attestation object format" )
2023-01-31 22:49:34 +00:00
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
2023-01-27 14:36:48 +00:00
return nil
} ,
} ,
jwk : jwk ,
} ,
wantErr : nil ,
}
} ,
2023-02-10 00:48:43 +00:00
"fail/db.UpdateAuthorization" : func ( t * testing . T ) test {
jwk , keyAuth := mustAccountAndKeyAuthorization ( t , "token" )
payload , leaf , root := mustAttestYubikey ( t , "nonce" , keyAuth , 12345678 )
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : root . Raw } )
2023-01-27 14:36:48 +00:00
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , caRoot ) )
2023-02-10 00:48:43 +00:00
return test {
args : args {
ctx : ctx ,
jwk : jwk ,
ch : & Challenge {
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
} ,
payload : payload ,
db : & MockDB {
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
MockUpdateAuthorization : func ( ctx context . Context , az * Authorization ) error {
fingerprint , err := keyutil . Fingerprint ( leaf . PublicKey )
assert . NoError ( t , err )
assert . Equal ( t , "azID" , az . ID )
assert . Equal ( t , fingerprint , az . Fingerprint )
return errors . New ( "force" )
} ,
2023-01-27 14:36:48 +00:00
} ,
} ,
2023-02-10 00:48:43 +00:00
wantErr : NewError ( ErrorServerInternalType , "error updating authorization: force" ) ,
}
} ,
"fail/db.UpdateChallenge" : func ( t * testing . T ) test {
jwk , keyAuth := mustAccountAndKeyAuthorization ( t , "token" )
payload , leaf , root := mustAttestYubikey ( t , "nonce" , keyAuth , 12345678 )
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : root . Raw } )
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , caRoot ) )
2023-01-27 14:36:48 +00:00
return test {
args : args {
ctx : ctx ,
2023-02-10 00:48:43 +00:00
jwk : jwk ,
2023-01-27 14:36:48 +00:00
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
2023-01-27 14:36:48 +00:00
} ,
payload : payload ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
MockUpdateAuthorization : func ( ctx context . Context , az * Authorization ) error {
fingerprint , err := keyutil . Fingerprint ( leaf . PublicKey )
assert . NoError ( t , err )
assert . Equal ( t , "azID" , az . ID )
assert . Equal ( t , fingerprint , az . Fingerprint )
return nil
} ,
2023-01-27 14:36:48 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
2023-01-31 22:49:34 +00:00
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusValid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "12345678" , updch . Value )
2023-01-27 14:36:48 +00:00
return errors . New ( "force" )
} ,
} ,
} ,
wantErr : NewError ( ErrorServerInternalType , "error updating challenge: force" ) ,
}
} ,
2023-01-31 22:49:34 +00:00
"ok" : func ( t * testing . T ) test {
2023-02-10 00:48:43 +00:00
jwk , keyAuth := mustAccountAndKeyAuthorization ( t , "token" )
payload , leaf , root := mustAttestYubikey ( t , "nonce" , keyAuth , 12345678 )
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : root . Raw } )
2023-01-31 22:49:34 +00:00
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , caRoot ) )
2023-02-10 00:48:43 +00:00
2023-01-31 22:49:34 +00:00
return test {
args : args {
ctx : ctx ,
2023-02-10 00:48:43 +00:00
jwk : jwk ,
2023-01-31 22:49:34 +00:00
ch : & Challenge {
2023-02-10 00:48:43 +00:00
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "12345678" ,
2023-01-31 22:49:34 +00:00
} ,
payload : payload ,
db : & MockDB {
2023-02-10 00:48:43 +00:00
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
MockUpdateAuthorization : func ( ctx context . Context , az * Authorization ) error {
fingerprint , err := keyutil . Fingerprint ( leaf . PublicKey )
assert . NoError ( t , err )
assert . Equal ( t , "azID" , az . ID )
assert . Equal ( t , fingerprint , az . Fingerprint )
return nil
} ,
2023-01-31 22:49:34 +00:00
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusValid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "12345678" , updch . Value )
return nil
} ,
} ,
} ,
wantErr : nil ,
}
} ,
2023-01-27 14:36:48 +00:00
}
for name , run := range tests {
t . Run ( name , func ( t * testing . T ) {
tc := run ( t )
if err := deviceAttest01Validate ( tc . args . ctx , tc . args . ch , tc . args . db , tc . args . jwk , tc . args . payload ) ; err != nil {
2023-01-31 22:49:34 +00:00
assert . Error ( t , tc . wantErr )
assert . EqualError ( t , err , tc . wantErr . Error ( ) )
2023-01-27 14:36:48 +00:00
return
}
2023-01-31 22:49:34 +00:00
assert . Nil ( t , tc . wantErr )
2023-01-27 14:36:48 +00:00
} )
}
}
2023-04-06 12:35:48 +00:00
var (
oidTPMManufacturer = asn1 . ObjectIdentifier { 2 , 23 , 133 , 2 , 1 }
oidTPMModel = asn1 . ObjectIdentifier { 2 , 23 , 133 , 2 , 2 }
oidTPMVersion = asn1 . ObjectIdentifier { 2 , 23 , 133 , 2 , 3 }
)
func generateValidAKCertificate ( t * testing . T ) * x509 . Certificate {
t . Helper ( )
signer , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
require . NoError ( t , err )
template := & x509 . Certificate {
PublicKey : signer . Public ( ) ,
Version : 3 ,
IsCA : false ,
UnknownExtKeyUsage : [ ] asn1 . ObjectIdentifier { oidTCGKpAIKCertificate } ,
}
asn1Value := [ ] byte ( fmt . Sprintf ( ` { "extraNames":[ { "type": %q, "value": %q}, { "type": %q, "value": %q}, { "type": %q, "value": %q}]} ` , oidTPMManufacturer , "1414747215" , oidTPMModel , "SLB 9670 TPM2.0" , oidTPMVersion , "7.55" ) )
sans := [ ] x509util . SubjectAlternativeName {
{ Type : x509util . DirectoryNameType ,
ASN1Value : asn1Value } ,
}
ext , err := createSubjectAltNameExtension ( nil , nil , nil , nil , sans , true )
require . NoError ( t , err )
ext . Set ( template )
ca , err := minica . New ( )
require . NoError ( t , err )
cert , err := ca . Sign ( template )
require . NoError ( t , err )
return cert
}
func Test_validateAKCertificate ( t * testing . T ) {
cert := generateValidAKCertificate ( t )
tests := [ ] struct {
name string
c * x509 . Certificate
expErr error
} {
{
name : "ok" ,
c : cert ,
expErr : nil ,
} ,
{
name : "fail/version" ,
c : & x509 . Certificate {
Version : 1 ,
} ,
expErr : errors . New ( "AK certificate has invalid version 1; only version 3 is allowed" ) ,
} ,
{
name : "fail/subject" ,
c : & x509 . Certificate {
Version : 3 ,
Subject : pkix . Name { CommonName : "fail!" } ,
} ,
expErr : errors . New ( ` AK certificate subject must be empty; got "CN=fail!" ` ) ,
} ,
{
name : "fail/isCA" ,
c : & x509 . Certificate {
Version : 3 ,
IsCA : true ,
} ,
expErr : errors . New ( "AK certificate must not be a CA" ) ,
} ,
{
name : "fail/extendedKeyUsage" ,
c : & x509 . Certificate {
Version : 3 ,
} ,
expErr : errors . New ( "AK certificate is missing Extended Key Usage extension" ) ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
err := validateAKCertificate ( tt . c )
if tt . expErr != nil {
if assert . Error ( t , err ) {
assert . EqualError ( t , err , tt . expErr . Error ( ) )
}
return
}
assert . NoError ( t , err )
} )
}
}
func Test_validateAKCertificateSubjectAlternativeNames ( t * testing . T ) {
ok := generateValidAKCertificate ( t )
t . Helper ( )
signer , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
require . NoError ( t , err )
getBase := func ( ) * x509 . Certificate {
return & x509 . Certificate {
PublicKey : signer . Public ( ) ,
Version : 3 ,
IsCA : false ,
UnknownExtKeyUsage : [ ] asn1 . ObjectIdentifier { oidTCGKpAIKCertificate } ,
}
}
ca , err := minica . New ( )
require . NoError ( t , err )
missingManufacturerASN1 := [ ] byte ( fmt . Sprintf ( ` { "extraNames":[ { "type": %q, "value": %q}, { "type": %q, "value": %q}]} ` , oidTPMModel , "SLB 9670 TPM2.0" , oidTPMVersion , "7.55" ) )
sans := [ ] x509util . SubjectAlternativeName {
{ Type : x509util . DirectoryNameType ,
ASN1Value : missingManufacturerASN1 } ,
}
ext , err := createSubjectAltNameExtension ( nil , nil , nil , nil , sans , true )
require . NoError ( t , err )
missingManufacturer := getBase ( )
ext . Set ( missingManufacturer )
missingManufacturer , err = ca . Sign ( missingManufacturer )
require . NoError ( t , err )
missingModelASN1 := [ ] byte ( fmt . Sprintf ( ` { "extraNames":[ { "type": %q, "value": %q}, { "type": %q, "value": %q}]} ` , oidTPMManufacturer , "1414747215" , oidTPMVersion , "7.55" ) )
sans = [ ] x509util . SubjectAlternativeName {
{ Type : x509util . DirectoryNameType ,
ASN1Value : missingModelASN1 } ,
}
ext , err = createSubjectAltNameExtension ( nil , nil , nil , nil , sans , true )
require . NoError ( t , err )
missingModel := getBase ( )
ext . Set ( missingModel )
missingModel , err = ca . Sign ( missingModel )
require . NoError ( t , err )
missingFirmwareVersionASN1 := [ ] byte ( fmt . Sprintf ( ` { "extraNames":[ { "type": %q, "value": %q}, { "type": %q, "value": %q}]} ` , oidTPMManufacturer , "1414747215" , oidTPMModel , "SLB 9670 TPM2.0" ) )
sans = [ ] x509util . SubjectAlternativeName {
{ Type : x509util . DirectoryNameType ,
ASN1Value : missingFirmwareVersionASN1 } ,
}
ext , err = createSubjectAltNameExtension ( nil , nil , nil , nil , sans , true )
require . NoError ( t , err )
missingFirmwareVersion := getBase ( )
ext . Set ( missingFirmwareVersion )
missingFirmwareVersion , err = ca . Sign ( missingFirmwareVersion )
require . NoError ( t , err )
tests := [ ] struct {
name string
c * x509 . Certificate
expErr error
} {
{ "ok" , ok , nil } ,
{ "fail/missing-manufacturer" , missingManufacturer , errors . New ( "missing TPM manufacturer" ) } ,
{ "fail/missing-model" , missingModel , errors . New ( "missing TPM model" ) } ,
{ "fail/missing-firmware-version" , missingFirmwareVersion , errors . New ( "missing TPM version" ) } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
err := validateAKCertificateSubjectAlternativeNames ( tt . c )
if tt . expErr != nil {
if assert . Error ( t , err ) {
assert . EqualError ( t , err , tt . expErr . Error ( ) )
}
return
}
assert . NoError ( t , err )
} )
}
}
func Test_validateAKCertificateExtendedKeyUsage ( t * testing . T ) {
ok := generateValidAKCertificate ( t )
missingEKU := & x509 . Certificate { }
t . Helper ( )
signer , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
require . NoError ( t , err )
template := & x509 . Certificate {
PublicKey : signer . Public ( ) ,
ExtKeyUsage : [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageServerAuth } ,
}
ca , err := minica . New ( )
require . NoError ( t , err )
wrongEKU , err := ca . Sign ( template )
require . NoError ( t , err )
tests := [ ] struct {
name string
c * x509 . Certificate
expErr error
} {
{ "ok" , ok , nil } ,
{ "fail/wrong-eku" , wrongEKU , errors . New ( "AK certificate is missing Extended Key Usage value tcg-kp-AIKCertificate (2.23.133.8.3)" ) } ,
{ "fail/missing-eku" , missingEKU , errors . New ( "AK certificate is missing Extended Key Usage extension" ) } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
err := validateAKCertificateExtendedKeyUsage ( tt . c )
if tt . expErr != nil {
if assert . Error ( t , err ) {
assert . EqualError ( t , err , tt . expErr . Error ( ) )
}
return
}
assert . NoError ( t , err )
} )
}
}
// createSubjectAltNameExtension will construct an Extension containing all
// SubjectAlternativeNames held in a Certificate. It implements more types than
// the golang x509 library, so it is used whenever OtherName or RegisteredID
// type SANs are present in the certificate.
//
// See also https://datatracker.ietf.org/doc/html/rfc5280.html#section-4.2.1.6
//
// TODO(hs): this was copied from go.step.sm/crypto/x509util to make it easier
// to create the SAN extension for testing purposes. Should it be exposed instead?
func createSubjectAltNameExtension ( dnsNames , emailAddresses x509util . MultiString , ipAddresses x509util . MultiIP , uris x509util . MultiURL , sans [ ] x509util . SubjectAlternativeName , subjectIsEmpty bool ) ( x509util . Extension , error ) {
var zero x509util . Extension
var rawValues [ ] asn1 . RawValue
for _ , dnsName := range dnsNames {
rawValue , err := x509util . SubjectAlternativeName {
Type : x509util . DNSType , Value : dnsName ,
} . RawValue ( )
if err != nil {
return zero , err
}
rawValues = append ( rawValues , rawValue )
}
for _ , emailAddress := range emailAddresses {
rawValue , err := x509util . SubjectAlternativeName {
Type : x509util . EmailType , Value : emailAddress ,
} . RawValue ( )
if err != nil {
return zero , err
}
rawValues = append ( rawValues , rawValue )
}
for _ , ip := range ipAddresses {
rawValue , err := x509util . SubjectAlternativeName {
Type : x509util . IPType , Value : ip . String ( ) ,
} . RawValue ( )
if err != nil {
return zero , err
}
rawValues = append ( rawValues , rawValue )
}
for _ , uri := range uris {
rawValue , err := x509util . SubjectAlternativeName {
Type : x509util . URIType , Value : uri . String ( ) ,
} . RawValue ( )
if err != nil {
return zero , err
}
rawValues = append ( rawValues , rawValue )
}
for _ , san := range sans {
rawValue , err := san . RawValue ( )
if err != nil {
return zero , err
}
rawValues = append ( rawValues , rawValue )
}
// Now marshal the rawValues into the ASN1 sequence, and create an Extension object to hold the extension
rawBytes , err := asn1 . Marshal ( rawValues )
if err != nil {
return zero , fmt . Errorf ( "error marshaling SubjectAlternativeName extension to ASN1: %w" , err )
}
return x509util . Extension {
ID : x509util . ObjectIdentifier ( oidSubjectAlternativeName ) ,
Critical : subjectIsEmpty ,
Value : rawBytes ,
} , nil
}