2019-05-27 00:41:10 +00:00
package api
import (
"bytes"
"context"
"encoding/json"
"fmt"
2021-11-12 23:46:34 +00:00
"io"
2019-05-27 00:41:10 +00:00
"net/http/httptest"
2020-05-07 03:18:12 +00:00
"net/url"
2019-05-27 00:41:10 +00:00
"testing"
"time"
"github.com/go-chi/chi"
2021-08-10 10:39:11 +00:00
"github.com/pkg/errors"
2019-05-27 00:41:10 +00:00
"github.com/smallstep/assert"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/authority/provisioner"
2020-08-24 21:44:11 +00:00
"go.step.sm/crypto/jose"
2019-05-27 00:41:10 +00:00
)
var (
defaultDisableRenewal = false
globalProvisionerClaims = provisioner . Claims {
MinTLSDur : & provisioner . Duration { Duration : 5 * time . Minute } ,
MaxTLSDur : & provisioner . Duration { Duration : 24 * time . Hour } ,
DefaultTLSDur : & provisioner . Duration { Duration : 24 * time . Hour } ,
DisableRenewal : & defaultDisableRenewal ,
}
)
2021-03-25 07:23:57 +00:00
func newProv ( ) acme . Provisioner {
2019-05-27 00:41:10 +00:00
// Initialize provisioners
p := & provisioner . ACME {
Type : "ACME" ,
2021-04-13 02:06:07 +00:00
Name : "test@acme-<test>provisioner.com" ,
2019-05-27 00:41:10 +00:00
}
if err := p . Init ( provisioner . Config { Claims : globalProvisionerClaims } ) ; err != nil {
fmt . Printf ( "%v" , err )
}
return p
}
2021-08-09 08:26:31 +00:00
func newACMEProv ( t * testing . T ) * provisioner . ACME {
p := newProv ( )
a , ok := p . ( * provisioner . ACME )
if ! ok {
t . Fatal ( "not a valid ACME provisioner" )
}
return a
}
2021-12-07 13:57:39 +00:00
func createEABJWS ( jwk * jose . JSONWebKey , hmacKey [ ] byte , keyID string , url string ) ( * jose . JSONWebSignature , error ) {
signer , err := jose . NewSigner (
jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( "HS256" ) ,
Key : hmacKey ,
} ,
& jose . SignerOptions {
ExtraHeaders : map [ jose . HeaderKey ] interface { } {
"kid" : keyID ,
"url" : url ,
} ,
EmbedJWK : false ,
} ,
)
2021-08-09 08:26:31 +00:00
if err != nil {
return nil , err
}
2021-12-07 13:57:39 +00:00
jwkJSONBytes , err := jwk . Public ( ) . MarshalJSON ( )
2021-08-09 08:26:31 +00:00
if err != nil {
return nil , err
}
2021-12-07 13:57:39 +00:00
jws , err := signer . Sign ( jwkJSONBytes )
if err != nil {
return nil , err
2021-08-09 08:26:31 +00:00
}
2021-12-07 13:57:39 +00:00
raw , err := jws . CompactSerialize ( )
if err != nil {
return nil , err
2021-08-09 08:26:31 +00:00
}
2021-12-07 13:57:39 +00:00
parsedJWS , err := jose . ParseJWS ( raw )
if err != nil {
return nil , err
2021-08-09 08:26:31 +00:00
}
2021-12-07 13:57:39 +00:00
return parsedJWS , nil
2021-08-09 08:26:31 +00:00
}
2021-12-07 13:57:39 +00:00
func createRawEABJWS ( jwk * jose . JSONWebKey , hmacKey [ ] byte , keyID string , url string ) ( [ ] byte , error ) {
jws , err := createEABJWS ( jwk , hmacKey , keyID , url )
2021-08-09 08:26:31 +00:00
if err != nil {
return nil , err
}
2021-12-07 13:57:39 +00:00
rawJWS := jws . FullSerialize ( )
return [ ] byte ( rawJWS ) , nil
2021-08-09 08:26:31 +00:00
}
2021-03-11 07:05:46 +00:00
func TestNewAccountRequest_Validate ( t * testing . T ) {
2019-05-27 00:41:10 +00:00
type test struct {
nar * NewAccountRequest
err * acme . Error
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/incompatible-input" : func ( t * testing . T ) test {
return test {
nar : & NewAccountRequest {
OnlyReturnExisting : true ,
Contact : [ ] string { "foo" , "bar" } ,
} ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "incompatible input; onlyReturnExisting must be alone" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/bad-contact" : func ( t * testing . T ) test {
return test {
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "" } ,
} ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "contact cannot be empty string" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"ok" : func ( t * testing . T ) test {
return test {
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
} ,
}
} ,
"ok/onlyReturnExisting" : func ( t * testing . T ) test {
return test {
nar : & NewAccountRequest {
OnlyReturnExisting : true ,
} ,
}
} ,
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
if err := tc . nar . Validate ( ) ; err != nil {
if assert . NotNil ( t , err ) {
ae , ok := err . ( * acme . Error )
assert . True ( t , ok )
assert . HasPrefix ( t , ae . Error ( ) , tc . err . Error ( ) )
assert . Equals ( t , ae . StatusCode ( ) , tc . err . StatusCode ( ) )
assert . Equals ( t , ae . Type , tc . err . Type )
}
} else {
assert . Nil ( t , tc . err )
}
} )
}
}
2021-03-11 07:05:46 +00:00
func TestUpdateAccountRequest_Validate ( t * testing . T ) {
2019-05-27 00:41:10 +00:00
type test struct {
uar * UpdateAccountRequest
err * acme . Error
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/incompatible-input" : func ( t * testing . T ) test {
return test {
uar : & UpdateAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
Status : "foo" ,
} ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "incompatible input; " +
"contact and status updates are mutually exclusive" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/bad-contact" : func ( t * testing . T ) test {
return test {
uar : & UpdateAccountRequest {
Contact : [ ] string { "foo" , "" } ,
} ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "contact cannot be empty string" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/bad-status" : func ( t * testing . T ) test {
return test {
uar : & UpdateAccountRequest {
Status : "foo" ,
} ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "cannot update account " +
"status to foo, only deactivated" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"ok/contact" : func ( t * testing . T ) test {
return test {
uar : & UpdateAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
} ,
}
} ,
"ok/status" : func ( t * testing . T ) test {
return test {
uar : & UpdateAccountRequest {
Status : "deactivated" ,
} ,
}
} ,
2020-05-08 18:52:30 +00:00
"ok/accept-empty" : func ( t * testing . T ) test {
return test {
uar : & UpdateAccountRequest { } ,
}
} ,
2019-05-27 00:41:10 +00:00
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
if err := tc . uar . Validate ( ) ; err != nil {
if assert . NotNil ( t , err ) {
ae , ok := err . ( * acme . Error )
assert . True ( t , ok )
assert . HasPrefix ( t , ae . Error ( ) , tc . err . Error ( ) )
assert . Equals ( t , ae . StatusCode ( ) , tc . err . StatusCode ( ) )
assert . Equals ( t , ae . Type , tc . err . Type )
}
} else {
assert . Nil ( t , tc . err )
}
} )
}
}
2021-03-09 06:35:57 +00:00
func TestHandler_GetOrdersByAccountID ( t * testing . T ) {
2019-05-27 00:41:10 +00:00
accID := "account-id"
// Request with chi context
chiCtx := chi . NewRouteContext ( )
chiCtx . URLParams . Add ( "accID" , accID )
2021-03-12 08:16:48 +00:00
prov := newProv ( )
provName := url . PathEscape ( prov . GetName ( ) )
baseURL := & url . URL { Scheme : "https" , Host : "test.ca.smallstep.com" }
2021-10-08 18:59:57 +00:00
u := fmt . Sprintf ( "http://ca.smallstep.com/acme/%s/account/%s/orders" , provName , accID )
2019-05-27 00:41:10 +00:00
2021-04-13 02:06:07 +00:00
oids := [ ] string { "foo" , "bar" }
oidURLs := [ ] string {
fmt . Sprintf ( "%s/acme/%s/order/foo" , baseURL . String ( ) , provName ) ,
fmt . Sprintf ( "%s/acme/%s/order/bar" , baseURL . String ( ) , provName ) ,
}
2019-05-27 00:41:10 +00:00
type test struct {
2021-03-09 06:35:57 +00:00
db acme . DB
2019-05-27 00:41:10 +00:00
ctx context . Context
statusCode int
2021-03-09 06:35:57 +00:00
err * acme . Error
2019-05-27 00:41:10 +00:00
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/no-account" : func ( t * testing . T ) test {
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB { } ,
2021-03-12 08:16:48 +00:00
ctx : context . Background ( ) ,
2020-02-02 01:35:41 +00:00
statusCode : 400 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorAccountDoesNotExistType , "account does not exist" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/nil-account" : func ( t * testing . T ) test {
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB { } ,
2021-03-12 08:16:48 +00:00
ctx : context . WithValue ( context . Background ( ) , accContextKey , nil ) ,
2020-02-02 01:35:41 +00:00
statusCode : 400 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorAccountDoesNotExistType , "account does not exist" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/account-id-mismatch" : func ( t * testing . T ) test {
acc := & acme . Account { ID : "foo" }
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , accContextKey , acc )
2019-05-27 00:41:10 +00:00
ctx = context . WithValue ( ctx , chi . RouteCtxKey , chiCtx )
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB { } ,
2019-05-27 00:41:10 +00:00
ctx : ctx ,
statusCode : 401 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorUnauthorizedType , "account ID does not match url param" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
2021-03-12 08:16:48 +00:00
"fail/db.GetOrdersByAccountID-error" : func ( t * testing . T ) test {
2019-05-27 00:41:10 +00:00
acc := & acme . Account { ID : accID }
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , accContextKey , acc )
2019-05-27 00:41:10 +00:00
ctx = context . WithValue ( ctx , chi . RouteCtxKey , chiCtx )
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
MockError : acme . NewErrorISE ( "force" ) ,
2019-05-27 00:41:10 +00:00
} ,
ctx : ctx ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "force" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"ok" : func ( t * testing . T ) test {
acc := & acme . Account { ID : accID }
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , accContextKey , acc )
2019-05-27 00:41:10 +00:00
ctx = context . WithValue ( ctx , chi . RouteCtxKey , chiCtx )
2021-03-12 08:16:48 +00:00
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
2019-05-27 00:41:10 +00:00
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
MockGetOrdersByAccountID : func ( ctx context . Context , id string ) ( [ ] string , error ) {
2019-05-27 00:41:10 +00:00
assert . Equals ( t , id , acc . ID )
return oids , nil
} ,
} ,
ctx : ctx ,
statusCode : 200 ,
}
} ,
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2021-03-12 08:16:48 +00:00
h := & Handler { db : tc . db , linker : NewLinker ( "dns" , "acme" ) }
2021-10-08 18:59:57 +00:00
req := httptest . NewRequest ( "GET" , u , nil )
2019-05-27 00:41:10 +00:00
req = req . WithContext ( tc . ctx )
w := httptest . NewRecorder ( )
2021-03-09 06:35:57 +00:00
h . GetOrdersByAccountID ( w , req )
2019-05-27 00:41:10 +00:00
res := w . Result ( )
assert . Equals ( t , res . StatusCode , tc . statusCode )
2021-11-12 23:46:34 +00:00
body , err := io . ReadAll ( res . Body )
2019-05-27 00:41:10 +00:00
res . Body . Close ( )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
if res . StatusCode >= 400 && assert . NotNil ( t , tc . err ) {
var ae acme . Error
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , json . Unmarshal ( bytes . TrimSpace ( body ) , & ae ) )
2021-03-09 06:35:57 +00:00
assert . Equals ( t , ae . Type , tc . err . Type )
assert . Equals ( t , ae . Detail , tc . err . Detail )
assert . Equals ( t , ae . Identifier , tc . err . Identifier )
assert . Equals ( t , ae . Subproblems , tc . err . Subproblems )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
2021-03-12 08:16:48 +00:00
expB , err := json . Marshal ( oidURLs )
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , err )
assert . Equals ( t , bytes . TrimSpace ( body ) , expB )
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/json" } )
}
} )
}
}
2021-03-09 06:35:57 +00:00
func TestHandler_NewAccount ( t * testing . T ) {
2019-05-27 00:41:10 +00:00
prov := newProv ( )
2021-04-13 02:06:07 +00:00
escProvName := url . PathEscape ( prov . GetName ( ) )
2020-05-07 03:18:12 +00:00
baseURL := & url . URL { Scheme : "https" , Host : "test.ca.smallstep.com" }
2019-05-27 00:41:10 +00:00
type test struct {
2021-03-09 06:35:57 +00:00
db acme . DB
2021-03-11 21:10:14 +00:00
acc * acme . Account
2019-05-27 00:41:10 +00:00
ctx context . Context
statusCode int
2021-03-09 06:35:57 +00:00
err * acme . Error
2019-05-27 00:41:10 +00:00
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/no-payload" : func ( t * testing . T ) test {
return test {
2021-03-09 06:35:57 +00:00
ctx : context . Background ( ) ,
2019-05-27 00:41:10 +00:00
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "payload expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/nil-payload" : func ( t * testing . T ) test {
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , payloadContextKey , nil )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "payload expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/unmarshal-payload-error" : func ( t * testing . T ) test {
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { } )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 400 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "failed to " +
"unmarshal new-account request payload: unexpected end of JSON input" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/malformed-payload-error" : func ( t * testing . T ) test {
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "" } ,
}
b , err := json . Marshal ( nar )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { value : b } )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 400 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "contact cannot be empty string" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/no-existing-account" : func ( t * testing . T ) test {
nar := & NewAccountRequest {
OnlyReturnExisting : true ,
}
b , err := json . Marshal ( nar )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { value : b } )
2021-07-17 17:02:47 +00:00
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
2020-02-02 01:35:41 +00:00
statusCode : 400 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorAccountDoesNotExistType , "account does not exist" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/no-jwk" : func ( t * testing . T ) test {
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
}
b , err := json . Marshal ( nar )
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
ctx := context . WithValue ( context . Background ( ) , provisionerContextKey , prov )
ctx = context . WithValue ( ctx , payloadContextKey , & payloadInfo { value : b } )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "jwk expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/nil-jwk" : func ( t * testing . T ) test {
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
}
b , err := json . Marshal ( nar )
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
ctx := context . WithValue ( context . Background ( ) , provisionerContextKey , prov )
ctx = context . WithValue ( ctx , payloadContextKey , & payloadInfo { value : b } )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwkContextKey , nil )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "jwk expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
2021-08-09 08:26:31 +00:00
"fail/new-account-no-eab-provided" : func ( t * testing . T ) test {
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : nil ,
}
b , err := json . Marshal ( nar )
assert . FatalError ( t , err )
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { value : b } )
ctx = context . WithValue ( ctx , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
return test {
ctx : ctx ,
statusCode : 400 ,
err : acme . NewError ( acme . ErrorExternalAccountRequiredType , "no external account binding provided" ) ,
}
} ,
2021-03-11 21:10:14 +00:00
"fail/db.CreateAccount-error" : func ( t * testing . T ) test {
2019-05-27 00:41:10 +00:00
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
}
b , err := json . Marshal ( nar )
assert . FatalError ( t , err )
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { value : b } )
2021-12-07 11:17:41 +00:00
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwkContextKey , jwk )
return test {
db : & acme . MockDB {
MockCreateAccount : func ( ctx context . Context , acc * acme . Account ) error {
assert . Equals ( t , acc . Contact , nar . Contact )
assert . Equals ( t , acc . Key , jwk )
return acme . NewErrorISE ( "force" )
2019-05-27 00:41:10 +00:00
} ,
} ,
ctx : ctx ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "force" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
2021-12-07 11:17:41 +00:00
"fail/acmeProvisionerFromContext" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
b , err := json . Marshal ( nar )
assert . FatalError ( t , err )
scepProvisioner := & provisioner . SCEP {
Type : "SCEP" ,
Name : "test@scep-<test>provisioner.com" ,
}
if err := scepProvisioner . Init ( provisioner . Config { Claims : globalProvisionerClaims } ) ; err != nil {
assert . FatalError ( t , err )
}
ctx := context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { value : b } )
ctx = context . WithValue ( ctx , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , scepProvisioner )
return test {
ctx : ctx ,
statusCode : 500 ,
err : acme . NewError ( acme . ErrorServerInternalType , "provisioner in context is not an ACME provisioner" ) ,
}
} ,
"fail/db.UpdateExternalAccountKey-error" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { value : payloadBytes } )
ctx = context . WithValue ( ctx , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
eak := & acme . ExternalAccountKey {
ID : "eakID" ,
Provisioner : escProvName ,
Reference : "testeak" ,
KeyBytes : [ ] byte { 1 , 3 , 3 , 7 } ,
CreatedAt : time . Now ( ) ,
}
return test {
db : & acme . MockDB {
MockCreateAccount : func ( ctx context . Context , acc * acme . Account ) error {
acc . ID = "accountID"
assert . Equals ( t , acc . Contact , nar . Contact )
assert . Equals ( t , acc . Key , jwk )
return nil
} ,
MockGetExternalAccountKey : func ( ctx context . Context , provisionerName , keyID string ) ( * acme . ExternalAccountKey , error ) {
return eak , nil
} ,
MockUpdateExternalAccountKey : func ( ctx context . Context , provisionerName string , eak * acme . ExternalAccountKey ) error {
return errors . New ( "force" )
} ,
} ,
acc : & acme . Account {
ID : "accountID" ,
Key : jwk ,
Status : acme . StatusValid ,
Contact : [ ] string { "foo" , "bar" } ,
OrdersURL : fmt . Sprintf ( "%s/acme/%s/account/accountID/orders" , baseURL . String ( ) , escProvName ) ,
ExternalAccountBinding : eab ,
} ,
ctx : ctx ,
statusCode : 500 ,
err : acme . NewError ( acme . ErrorServerInternalType , "error updating external account binding key" ) ,
}
} ,
2019-05-27 00:41:10 +00:00
"ok/new-account" : func ( t * testing . T ) test {
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
}
b , err := json . Marshal ( nar )
assert . FatalError ( t , err )
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { value : b } )
ctx = context . WithValue ( ctx , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
return test {
db : & acme . MockDB {
MockCreateAccount : func ( ctx context . Context , acc * acme . Account ) error {
2021-03-11 21:10:14 +00:00
acc . ID = "accountID"
2021-03-09 06:35:57 +00:00
assert . Equals ( t , acc . Contact , nar . Contact )
assert . Equals ( t , acc . Key , jwk )
return nil
2019-05-27 00:41:10 +00:00
} ,
2021-03-11 21:10:14 +00:00
} ,
acc : & acme . Account {
2021-03-15 17:30:12 +00:00
ID : "accountID" ,
Key : jwk ,
Status : acme . StatusValid ,
Contact : [ ] string { "foo" , "bar" } ,
2021-04-13 02:06:07 +00:00
OrdersURL : fmt . Sprintf ( "%s/acme/%s/account/accountID/orders" , baseURL . String ( ) , escProvName ) ,
2019-05-27 00:41:10 +00:00
} ,
ctx : ctx ,
statusCode : 201 ,
}
} ,
"ok/return-existing" : func ( t * testing . T ) test {
nar := & NewAccountRequest {
OnlyReturnExisting : true ,
}
b , err := json . Marshal ( nar )
assert . FatalError ( t , err )
2021-03-11 21:10:14 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
acc := & acme . Account {
ID : "accountID" ,
Key : jwk ,
Status : acme . StatusValid ,
Contact : [ ] string { "foo" , "bar" } ,
}
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , provisionerContextKey , prov )
ctx = context . WithValue ( ctx , payloadContextKey , & payloadInfo { value : b } )
2021-03-11 21:10:14 +00:00
ctx = context . WithValue ( ctx , accContextKey , acc )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
return test {
2019-05-27 00:41:10 +00:00
ctx : ctx ,
2021-03-11 21:10:14 +00:00
acc : acc ,
2019-05-27 00:41:10 +00:00
statusCode : 200 ,
}
} ,
2021-08-09 08:26:31 +00:00
"ok/new-account-no-eab-required" : func ( t * testing . T ) test {
2021-08-10 10:39:11 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
2021-08-09 08:26:31 +00:00
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
2021-08-10 10:39:11 +00:00
ExternalAccountBinding : eab ,
2021-08-09 08:26:31 +00:00
}
b , err := json . Marshal ( nar )
assert . FatalError ( t , err )
prov := newACMEProv ( t )
prov . RequireEAB = false
ctx := context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { value : b } )
ctx = context . WithValue ( ctx , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
return test {
db : & acme . MockDB {
MockCreateAccount : func ( ctx context . Context , acc * acme . Account ) error {
acc . ID = "accountID"
assert . Equals ( t , acc . Contact , nar . Contact )
assert . Equals ( t , acc . Key , jwk )
return nil
} ,
} ,
acc : & acme . Account {
ID : "accountID" ,
Key : jwk ,
Status : acme . StatusValid ,
Contact : [ ] string { "foo" , "bar" } ,
OrdersURL : fmt . Sprintf ( "%s/acme/%s/account/accountID/orders" , baseURL . String ( ) , escProvName ) ,
} ,
ctx : ctx ,
statusCode : 201 ,
}
} ,
"ok/new-account-with-eab" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-08-09 08:26:31 +00:00
assert . FatalError ( t , err )
2021-08-10 10:39:11 +00:00
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-08-09 08:26:31 +00:00
assert . FatalError ( t , err )
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
2021-08-10 10:39:11 +00:00
ExternalAccountBinding : eab ,
2021-08-09 08:26:31 +00:00
}
2021-12-07 11:17:41 +00:00
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
2021-08-09 08:26:31 +00:00
assert . FatalError ( t , err )
prov := newACMEProv ( t )
prov . RequireEAB = true
2021-12-07 11:17:41 +00:00
ctx := context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { value : payloadBytes } )
2021-08-09 08:26:31 +00:00
ctx = context . WithValue ( ctx , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
2021-12-07 11:17:41 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2021-08-09 08:26:31 +00:00
return test {
db : & acme . MockDB {
MockCreateAccount : func ( ctx context . Context , acc * acme . Account ) error {
acc . ID = "accountID"
assert . Equals ( t , acc . Contact , nar . Contact )
assert . Equals ( t , acc . Key , jwk )
return nil
} ,
2021-10-11 21:34:23 +00:00
MockGetExternalAccountKey : func ( ctx context . Context , provisionerName , keyID string ) ( * acme . ExternalAccountKey , error ) {
2021-08-09 08:26:31 +00:00
return & acme . ExternalAccountKey {
2021-09-16 21:09:24 +00:00
ID : "eakID" ,
Provisioner : escProvName ,
Reference : "testeak" ,
KeyBytes : [ ] byte { 1 , 3 , 3 , 7 } ,
CreatedAt : time . Now ( ) ,
2021-08-09 08:26:31 +00:00
} , nil
} ,
MockUpdateExternalAccountKey : func ( ctx context . Context , provisionerName string , eak * acme . ExternalAccountKey ) error {
return nil
} ,
} ,
acc : & acme . Account {
ID : "accountID" ,
Key : jwk ,
Status : acme . StatusValid ,
Contact : [ ] string { "foo" , "bar" } ,
OrdersURL : fmt . Sprintf ( "%s/acme/%s/account/accountID/orders" , baseURL . String ( ) , escProvName ) ,
2021-08-10 10:39:11 +00:00
ExternalAccountBinding : eab ,
2021-08-09 08:26:31 +00:00
} ,
ctx : ctx ,
statusCode : 201 ,
}
} ,
2019-05-27 00:41:10 +00:00
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2021-03-11 21:10:14 +00:00
h := & Handler { db : tc . db , linker : NewLinker ( "dns" , "acme" ) }
2020-05-07 03:18:12 +00:00
req := httptest . NewRequest ( "GET" , "/foo/bar" , nil )
2019-05-27 00:41:10 +00:00
req = req . WithContext ( tc . ctx )
w := httptest . NewRecorder ( )
h . NewAccount ( w , req )
res := w . Result ( )
assert . Equals ( t , res . StatusCode , tc . statusCode )
2021-11-12 23:46:34 +00:00
body , err := io . ReadAll ( res . Body )
2019-05-27 00:41:10 +00:00
res . Body . Close ( )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
if res . StatusCode >= 400 && assert . NotNil ( t , tc . err ) {
var ae acme . Error
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , json . Unmarshal ( bytes . TrimSpace ( body ) , & ae ) )
2021-03-09 06:35:57 +00:00
assert . Equals ( t , ae . Type , tc . err . Type )
assert . Equals ( t , ae . Detail , tc . err . Detail )
assert . Equals ( t , ae . Identifier , tc . err . Identifier )
assert . Equals ( t , ae . Subproblems , tc . err . Subproblems )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
2021-03-11 21:10:14 +00:00
expB , err := json . Marshal ( tc . acc )
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , err )
assert . Equals ( t , bytes . TrimSpace ( body ) , expB )
assert . Equals ( t , res . Header [ "Location" ] ,
2020-05-07 03:18:12 +00:00
[ ] string { fmt . Sprintf ( "%s/acme/%s/account/%s" , baseURL . String ( ) ,
2021-04-13 02:06:07 +00:00
escProvName , "accountID" ) } )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/json" } )
}
} )
}
}
2021-04-13 02:06:07 +00:00
func TestHandler_GetOrUpdateAccount ( t * testing . T ) {
2019-05-27 00:41:10 +00:00
accID := "accountID"
acc := acme . Account {
2021-03-15 17:30:12 +00:00
ID : accID ,
Status : "valid" ,
OrdersURL : fmt . Sprintf ( "https://ca.smallstep.com/acme/account/%s/orders" , accID ) ,
2019-05-27 00:41:10 +00:00
}
prov := newProv ( )
2021-04-13 02:06:07 +00:00
escProvName := url . PathEscape ( prov . GetName ( ) )
2020-05-07 03:18:12 +00:00
baseURL := & url . URL { Scheme : "https" , Host : "test.ca.smallstep.com" }
2019-05-27 00:41:10 +00:00
type test struct {
2021-03-09 06:35:57 +00:00
db acme . DB
2019-05-27 00:41:10 +00:00
ctx context . Context
statusCode int
2021-03-09 06:35:57 +00:00
err * acme . Error
2019-05-27 00:41:10 +00:00
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/no-account" : func ( t * testing . T ) test {
return test {
2021-03-09 06:35:57 +00:00
ctx : context . Background ( ) ,
2020-02-02 01:35:41 +00:00
statusCode : 400 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorAccountDoesNotExistType , "account does not exist" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/nil-account" : func ( t * testing . T ) test {
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , accContextKey , nil )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
2020-02-02 01:35:41 +00:00
statusCode : 400 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorAccountDoesNotExistType , "account does not exist" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/no-payload" : func ( t * testing . T ) test {
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , accContextKey , & acc )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "payload expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/nil-payload" : func ( t * testing . T ) test {
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , accContextKey , & acc )
ctx = context . WithValue ( ctx , payloadContextKey , nil )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "payload expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/unmarshal-payload-error" : func ( t * testing . T ) test {
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , accContextKey , & acc )
ctx = context . WithValue ( ctx , payloadContextKey , & payloadInfo { } )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 400 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "failed to unmarshal new-account request payload: unexpected end of JSON input" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/malformed-payload-error" : func ( t * testing . T ) test {
uar := & UpdateAccountRequest {
Contact : [ ] string { "foo" , "" } ,
}
b , err := json . Marshal ( uar )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , accContextKey , & acc )
ctx = context . WithValue ( ctx , payloadContextKey , & payloadInfo { value : b } )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 400 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "contact cannot be empty string" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
2021-03-12 08:16:48 +00:00
"fail/db.UpdateAccount-error" : func ( t * testing . T ) test {
2019-05-27 00:41:10 +00:00
uar := & UpdateAccountRequest {
Status : "deactivated" ,
}
b , err := json . Marshal ( uar )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , accContextKey , & acc )
ctx = context . WithValue ( ctx , payloadContextKey , & payloadInfo { value : b } )
return test {
db : & acme . MockDB {
MockUpdateAccount : func ( ctx context . Context , upd * acme . Account ) error {
assert . Equals ( t , upd . Status , acme . StatusDeactivated )
assert . Equals ( t , upd . ID , acc . ID )
return acme . NewErrorISE ( "force" )
2019-05-27 00:41:10 +00:00
} ,
} ,
ctx : ctx ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "force" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"ok/deactivate" : func ( t * testing . T ) test {
uar := & UpdateAccountRequest {
Status : "deactivated" ,
}
b , err := json . Marshal ( uar )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , provisionerContextKey , prov )
ctx = context . WithValue ( ctx , accContextKey , & acc )
ctx = context . WithValue ( ctx , payloadContextKey , & payloadInfo { value : b } )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
return test {
db : & acme . MockDB {
MockUpdateAccount : func ( ctx context . Context , upd * acme . Account ) error {
assert . Equals ( t , upd . Status , acme . StatusDeactivated )
assert . Equals ( t , upd . ID , acc . ID )
return nil
2019-05-27 00:41:10 +00:00
} ,
} ,
ctx : ctx ,
statusCode : 200 ,
}
} ,
2020-05-08 18:52:30 +00:00
"ok/update-empty" : func ( t * testing . T ) test {
uar := & UpdateAccountRequest { }
b , err := json . Marshal ( uar )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , provisionerContextKey , prov )
ctx = context . WithValue ( ctx , accContextKey , & acc )
ctx = context . WithValue ( ctx , payloadContextKey , & payloadInfo { value : b } )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
return test {
2020-05-08 18:52:30 +00:00
ctx : ctx ,
statusCode : 200 ,
}
} ,
"ok/update-contacts" : func ( t * testing . T ) test {
2019-05-27 00:41:10 +00:00
uar := & UpdateAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
}
b , err := json . Marshal ( uar )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , provisionerContextKey , prov )
ctx = context . WithValue ( ctx , accContextKey , & acc )
ctx = context . WithValue ( ctx , payloadContextKey , & payloadInfo { value : b } )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
return test {
db : & acme . MockDB {
MockUpdateAccount : func ( ctx context . Context , upd * acme . Account ) error {
assert . Equals ( t , upd . Contact , uar . Contact )
assert . Equals ( t , upd . ID , acc . ID )
return nil
2019-05-27 00:41:10 +00:00
} ,
} ,
ctx : ctx ,
statusCode : 200 ,
}
} ,
"ok/post-as-get" : func ( t * testing . T ) test {
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , provisionerContextKey , prov )
ctx = context . WithValue ( ctx , accContextKey , & acc )
ctx = context . WithValue ( ctx , payloadContextKey , & payloadInfo { isPostAsGet : true } )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
return test {
2019-05-27 00:41:10 +00:00
ctx : ctx ,
statusCode : 200 ,
}
} ,
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2021-03-11 21:10:14 +00:00
h := & Handler { db : tc . db , linker : NewLinker ( "dns" , "acme" ) }
2020-05-07 03:18:12 +00:00
req := httptest . NewRequest ( "GET" , "/foo/bar" , nil )
2019-05-27 00:41:10 +00:00
req = req . WithContext ( tc . ctx )
w := httptest . NewRecorder ( )
2021-04-13 02:06:07 +00:00
h . GetOrUpdateAccount ( w , req )
2019-05-27 00:41:10 +00:00
res := w . Result ( )
assert . Equals ( t , res . StatusCode , tc . statusCode )
2021-11-12 23:46:34 +00:00
body , err := io . ReadAll ( res . Body )
2019-05-27 00:41:10 +00:00
res . Body . Close ( )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
if res . StatusCode >= 400 && assert . NotNil ( t , tc . err ) {
var ae acme . Error
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , json . Unmarshal ( bytes . TrimSpace ( body ) , & ae ) )
2021-03-09 06:35:57 +00:00
assert . Equals ( t , ae . Type , tc . err . Type )
assert . Equals ( t , ae . Detail , tc . err . Detail )
assert . Equals ( t , ae . Identifier , tc . err . Identifier )
assert . Equals ( t , ae . Subproblems , tc . err . Subproblems )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
expB , err := json . Marshal ( acc )
assert . FatalError ( t , err )
assert . Equals ( t , bytes . TrimSpace ( body ) , expB )
assert . Equals ( t , res . Header [ "Location" ] ,
2020-05-07 03:18:12 +00:00
[ ] string { fmt . Sprintf ( "%s/acme/%s/account/%s" , baseURL . String ( ) ,
2021-04-13 02:06:07 +00:00
escProvName , accID ) } )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/json" } )
}
} )
}
}
2021-08-09 08:26:31 +00:00
func Test_keysAreEqual ( t * testing . T ) {
jwkX , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
jwkY , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-08-10 10:39:11 +00:00
wrongJWK , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
wrongJWK . Key = struct { } { }
2021-08-09 08:26:31 +00:00
type args struct {
2021-12-07 11:17:41 +00:00
x * jose . JSONWebKey
y * jose . JSONWebKey
2021-08-09 08:26:31 +00:00
}
tests := [ ] struct {
name string
args args
want bool
} {
{
name : "ok/nil" ,
args : args {
x : jwkX ,
y : nil ,
} ,
want : false ,
} ,
{
name : "ok/equal" ,
args : args {
x : jwkX ,
y : jwkX ,
} ,
want : true ,
} ,
{
name : "ok/not-equal" ,
args : args {
x : jwkX ,
y : jwkY ,
} ,
want : false ,
} ,
2021-08-10 10:39:11 +00:00
{
name : "ok/wrong-key-type" ,
args : args {
x : wrongJWK ,
y : jwkY ,
} ,
want : false ,
} ,
2021-08-09 08:26:31 +00:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if got := keysAreEqual ( tt . args . x , tt . args . y ) ; got != tt . want {
t . Errorf ( "keysAreEqual() = %v, want %v" , got , tt . want )
}
} )
}
}
func TestHandler_validateExternalAccountBinding ( t * testing . T ) {
2021-08-10 10:39:11 +00:00
acmeProv := newACMEProv ( t )
escProvName := url . PathEscape ( acmeProv . GetName ( ) )
baseURL := & url . URL { Scheme : "https" , Host : "test.ca.smallstep.com" }
type test struct {
db acme . DB
2021-08-09 08:26:31 +00:00
ctx context . Context
nar * NewAccountRequest
2021-08-10 10:39:11 +00:00
eak * acme . ExternalAccountKey
err * acme . Error
2021-08-09 08:26:31 +00:00
}
2021-08-10 10:39:11 +00:00
var tests = map [ string ] func ( t * testing . T ) test {
"ok/no-eab-required-but-provided" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
prov := newACMEProv ( t )
ctx := context . WithValue ( context . Background ( ) , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
return test {
db : & acme . MockDB { } ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
eak : nil ,
err : nil ,
}
} ,
"ok/eab" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
2021-08-10 10:39:11 +00:00
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
2021-12-07 11:17:41 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2021-10-08 11:18:23 +00:00
createdAt := time . Now ( )
2021-08-10 10:39:11 +00:00
return test {
db : & acme . MockDB {
2021-10-11 21:34:23 +00:00
MockGetExternalAccountKey : func ( ctx context . Context , provisionerName , keyID string ) ( * acme . ExternalAccountKey , error ) {
2021-08-10 10:39:11 +00:00
return & acme . ExternalAccountKey {
2021-09-16 21:09:24 +00:00
ID : "eakID" ,
Provisioner : escProvName ,
Reference : "testeak" ,
KeyBytes : [ ] byte { 1 , 3 , 3 , 7 } ,
2021-10-08 11:18:23 +00:00
CreatedAt : createdAt ,
2021-08-10 10:39:11 +00:00
} , nil
} ,
} ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
2021-10-08 11:18:23 +00:00
eak : & acme . ExternalAccountKey {
ID : "eakID" ,
Provisioner : escProvName ,
Reference : "testeak" ,
KeyBytes : [ ] byte { 1 , 3 , 3 , 7 } ,
CreatedAt : createdAt ,
} ,
2021-08-10 10:39:11 +00:00
err : nil ,
}
} ,
2021-12-07 11:17:41 +00:00
"fail/acmeProvisionerFromContext" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
b , err := json . Marshal ( nar )
assert . FatalError ( t , err )
scepProvisioner := & provisioner . SCEP {
Type : "SCEP" ,
Name : "test@scep-<test>provisioner.com" ,
}
if err := scepProvisioner . Init ( provisioner . Config { Claims : globalProvisionerClaims } ) ; err != nil {
assert . FatalError ( t , err )
}
ctx := context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { value : b } )
ctx = context . WithValue ( ctx , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , scepProvisioner )
return test {
ctx : ctx ,
err : acme . NewError ( acme . ErrorServerInternalType , "could not load ACME provisioner from context: provisioner in context is not an ACME provisioner" ) ,
}
} ,
2021-08-10 10:39:11 +00:00
"fail/parse-eab-jose" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
2021-10-11 21:34:23 +00:00
eab . Payload += "{}"
2021-08-10 10:39:11 +00:00
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
return test {
db : & acme . MockDB { } ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
eak : nil ,
err : acme . NewErrorISE ( "error parsing externalAccountBinding jws" ) ,
}
} ,
2021-12-07 11:17:41 +00:00
"fail/validate-eab-jws-no-signatures" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
parsedJWS . Signatures = [ ] jose . Signature { }
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
return test {
db : & acme . MockDB { } ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
eak : nil ,
err : acme . NewError ( acme . ErrorMalformedType , "outer JWS must have one signature" ) ,
}
} ,
2021-08-10 10:39:11 +00:00
"fail/retrieve-eab-key-db-failure" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
2021-08-10 10:39:11 +00:00
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
2021-12-07 11:17:41 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2021-08-10 10:39:11 +00:00
return test {
db : & acme . MockDB {
MockError : errors . New ( "db failure" ) ,
} ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
eak : nil ,
err : acme . NewErrorISE ( "error retrieving external account key" ) ,
}
} ,
2021-12-07 11:17:41 +00:00
"fail/db.GetExternalAccountKey-not-found" : func ( t * testing . T ) test {
2021-08-10 10:39:11 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
2021-08-10 10:39:11 +00:00
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
2021-12-07 11:17:41 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2021-08-10 10:39:11 +00:00
return test {
db : & acme . MockDB {
2021-10-11 21:34:23 +00:00
MockGetExternalAccountKey : func ( ctx context . Context , provisionerName , keyID string ) ( * acme . ExternalAccountKey , error ) {
2021-12-07 11:17:41 +00:00
return nil , acme . ErrNotFound
2021-08-10 10:39:11 +00:00
} ,
} ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
eak : nil ,
2021-12-07 11:17:41 +00:00
err : acme . NewErrorISE ( "error retrieving external account key" ) ,
2021-08-10 10:39:11 +00:00
}
} ,
2021-12-07 11:17:41 +00:00
"fail/db.GetExternalAccountKey-error" : func ( t * testing . T ) test {
2021-08-10 10:39:11 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
return test {
db : & acme . MockDB {
MockGetExternalAccountKey : func ( ctx context . Context , provisionerName , keyID string ) ( * acme . ExternalAccountKey , error ) {
return nil , errors . New ( "force" )
} ,
} ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
eak : nil ,
err : acme . NewErrorISE ( "error retrieving external account key" ) ,
}
} ,
"fail/db.GetExternalAccountKey-wrong-provisioner" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
2021-08-10 10:39:11 +00:00
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
2021-12-07 11:17:41 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2021-08-10 10:39:11 +00:00
return test {
db : & acme . MockDB {
MockError : acme . NewError ( acme . ErrorUnauthorizedType , "name of provisioner does not match provisioner for which the EAB key was created" ) ,
} ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
eak : nil ,
2021-12-07 11:17:41 +00:00
err : acme . NewError ( acme . ErrorUnauthorizedType , "the field 'kid' references an unknown key: name of provisioner does not match provisioner for which the EAB key was created" ) ,
2021-08-10 10:39:11 +00:00
}
} ,
"fail/eab-already-bound" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
2021-08-10 10:39:11 +00:00
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
2021-12-07 11:17:41 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2021-08-10 10:39:11 +00:00
createdAt := time . Now ( )
boundAt := time . Now ( ) . Add ( 1 * time . Second )
return test {
db : & acme . MockDB {
2021-10-11 21:34:23 +00:00
MockGetExternalAccountKey : func ( ctx context . Context , provisionerName , keyID string ) ( * acme . ExternalAccountKey , error ) {
2021-08-10 10:39:11 +00:00
return & acme . ExternalAccountKey {
2021-09-16 21:09:24 +00:00
ID : "eakID" ,
Provisioner : escProvName ,
Reference : "testeak" ,
CreatedAt : createdAt ,
AccountID : "some-account-id" ,
BoundAt : boundAt ,
2021-08-10 10:39:11 +00:00
} , nil
} ,
} ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
eak : nil ,
err : acme . NewError ( acme . ErrorUnauthorizedType , "external account binding key with id '%s' was already bound to account '%s' on %s" , "eakID" , "some-account-id" , boundAt ) ,
}
} ,
"fail/eab-verify" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
2021-08-10 10:39:11 +00:00
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
2021-12-07 11:17:41 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2021-08-10 10:39:11 +00:00
return test {
db : & acme . MockDB {
2021-10-11 21:34:23 +00:00
MockGetExternalAccountKey : func ( ctx context . Context , provisionerName , keyID string ) ( * acme . ExternalAccountKey , error ) {
2021-08-10 10:39:11 +00:00
return & acme . ExternalAccountKey {
2021-09-16 21:09:24 +00:00
ID : "eakID" ,
Provisioner : escProvName ,
Reference : "testeak" ,
KeyBytes : [ ] byte { 1 , 2 , 3 , 4 } ,
CreatedAt : time . Now ( ) ,
2021-08-10 10:39:11 +00:00
} , nil
} ,
} ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
eak : nil ,
err : acme . NewErrorISE ( "error verifying externalAccountBinding signature" ) ,
}
} ,
"fail/eab-non-matching-keys" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
differentJWK , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( differentJWK , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-08-10 10:39:11 +00:00
assert . FatalError ( t , err )
2021-12-07 11:17:41 +00:00
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
2021-08-10 10:39:11 +00:00
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , jwkContextKey , jwk )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
2021-12-07 11:17:41 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2021-08-10 10:39:11 +00:00
return test {
db : & acme . MockDB {
2021-10-11 21:34:23 +00:00
MockGetExternalAccountKey : func ( ctx context . Context , provisionerName , keyID string ) ( * acme . ExternalAccountKey , error ) {
2021-08-10 10:39:11 +00:00
return & acme . ExternalAccountKey {
2021-09-16 21:09:24 +00:00
ID : "eakID" ,
Provisioner : escProvName ,
Reference : "testeak" ,
KeyBytes : [ ] byte { 1 , 3 , 3 , 7 } ,
CreatedAt : time . Now ( ) ,
2021-08-10 10:39:11 +00:00
} , nil
} ,
} ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
eak : nil ,
2021-12-07 11:17:41 +00:00
err : acme . NewError ( acme . ErrorUnauthorizedType , "keys in jws and eab payload do not match" ) ,
}
} ,
"fail/no-jwk" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
return test {
db : & acme . MockDB {
MockGetExternalAccountKey : func ( ctx context . Context , provisionerName , keyID string ) ( * acme . ExternalAccountKey , error ) {
return & acme . ExternalAccountKey {
ID : "eakID" ,
Provisioner : escProvName ,
Reference : "testeak" ,
KeyBytes : [ ] byte { 1 , 3 , 3 , 7 } ,
CreatedAt : time . Now ( ) ,
} , nil
} ,
} ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
eak : nil ,
err : acme . NewError ( acme . ErrorServerInternalType , "jwk expected in request context" ) ,
}
} ,
"fail/nil-jwk" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
rawEABJWS , err := createRawEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
eab := & ExternalAccountBinding { }
2021-12-07 13:57:39 +00:00
err = json . Unmarshal ( rawEABJWS , & eab )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
prov := newACMEProv ( t )
prov . RequireEAB = true
ctx := context . WithValue ( context . Background ( ) , jwkContextKey , nil )
ctx = context . WithValue ( ctx , baseURLContextKey , baseURL )
ctx = context . WithValue ( ctx , provisionerContextKey , prov )
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
return test {
db : & acme . MockDB {
MockGetExternalAccountKey : func ( ctx context . Context , provisionerName , keyID string ) ( * acme . ExternalAccountKey , error ) {
return & acme . ExternalAccountKey {
ID : "eakID" ,
Provisioner : escProvName ,
Reference : "testeak" ,
KeyBytes : [ ] byte { 1 , 3 , 3 , 7 } ,
CreatedAt : time . Now ( ) ,
} , nil
} ,
} ,
ctx : ctx ,
nar : & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
} ,
eak : nil ,
err : acme . NewError ( acme . ErrorServerInternalType , "jwk expected in request context" ) ,
2021-08-10 10:39:11 +00:00
}
} ,
2021-08-09 08:26:31 +00:00
}
2021-08-10 10:39:11 +00:00
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2021-08-09 08:26:31 +00:00
h := & Handler {
2021-08-10 10:39:11 +00:00
db : tc . db ,
}
got , err := h . validateExternalAccountBinding ( tc . ctx , tc . nar )
wantErr := tc . err != nil
gotErr := err != nil
if wantErr != gotErr {
t . Errorf ( "Handler.validateExternalAccountBinding() error = %v, want %v" , err , tc . err )
}
if wantErr {
assert . NotNil ( t , err )
assert . Type ( t , & acme . Error { } , err )
ae , _ := err . ( * acme . Error )
assert . Equals ( t , ae . Type , tc . err . Type )
2021-12-07 11:17:41 +00:00
assert . Equals ( t , ae . Status , tc . err . Status )
assert . HasPrefix ( t , ae . Err . Error ( ) , tc . err . Err . Error ( ) )
2021-08-10 10:39:11 +00:00
assert . Equals ( t , ae . Detail , tc . err . Detail )
assert . Equals ( t , ae . Identifier , tc . err . Identifier )
assert . Equals ( t , ae . Subproblems , tc . err . Subproblems )
} else {
if got == nil {
assert . Nil ( t , tc . eak )
} else {
assert . NotNil ( t , tc . eak )
2021-10-08 11:18:23 +00:00
assert . Equals ( t , got . ID , tc . eak . ID )
assert . Equals ( t , got . KeyBytes , tc . eak . KeyBytes )
assert . Equals ( t , got . Provisioner , tc . eak . Provisioner )
assert . Equals ( t , got . Reference , tc . eak . Reference )
assert . Equals ( t , got . CreatedAt , tc . eak . CreatedAt )
assert . Equals ( t , got . AccountID , tc . eak . AccountID )
assert . Equals ( t , got . BoundAt , tc . eak . BoundAt )
2021-08-10 10:39:11 +00:00
}
2021-08-09 08:26:31 +00:00
}
} )
}
}
2021-12-07 11:17:41 +00:00
func Test_validateEABJWS ( t * testing . T ) {
acmeProv := newACMEProv ( t )
escProvName := url . PathEscape ( acmeProv . GetName ( ) )
baseURL := & url . URL { Scheme : "https" , Host : "test.ca.smallstep.com" }
type test struct {
ctx context . Context
jws * jose . JSONWebSignature
keyID string
err * acme . Error
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/nil-jws" : func ( t * testing . T ) test {
return test {
jws : nil ,
err : acme . NewErrorISE ( "no JWS provided" ) ,
}
} ,
"fail/invalid-number-of-signatures" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
eabJWS , err := createEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
eabJWS . Signatures = append ( eabJWS . Signatures , jose . Signature { } )
2021-12-07 11:17:41 +00:00
return test {
2021-12-07 13:57:39 +00:00
jws : eabJWS ,
2021-12-07 11:17:41 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "JWS must have one signature" ) ,
}
} ,
"fail/invalid-algorithm" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
eabJWS , err := createEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
eabJWS . Signatures [ 0 ] . Protected . Algorithm = "HS42"
2021-12-07 11:17:41 +00:00
return test {
2021-12-07 13:57:39 +00:00
jws : eabJWS ,
2021-12-07 11:17:41 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "'alg' field set to invalid algorithm 'HS42'" ) ,
}
} ,
"fail/kid-not-set" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
eabJWS , err := createEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
eabJWS . Signatures [ 0 ] . Protected . KeyID = ""
2021-12-07 11:17:41 +00:00
return test {
2021-12-07 13:57:39 +00:00
jws : eabJWS ,
2021-12-07 11:17:41 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "'kid' field is required" ) ,
}
} ,
"fail/nonce-not-empty" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
eabJWS , err := createEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
eabJWS . Signatures [ 0 ] . Protected . Nonce = "some-bogus-nonce"
2021-12-07 11:17:41 +00:00
return test {
2021-12-07 13:57:39 +00:00
jws : eabJWS ,
2021-12-07 11:17:41 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "'nonce' must not be present" ) ,
}
} ,
"fail/url-not-set" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
eabJWS , err := createEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
delete ( eabJWS . Signatures [ 0 ] . Protected . ExtraHeaders , "url" )
2021-12-07 11:17:41 +00:00
return test {
2021-12-07 13:57:39 +00:00
jws : eabJWS ,
2021-12-07 11:17:41 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "'url' field is required" ) ,
}
} ,
"fail/no-outer-jws" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
eabJWS , err := createEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
ctx := context . WithValue ( context . TODO ( ) , jwsContextKey , nil )
return test {
ctx : ctx ,
2021-12-07 13:57:39 +00:00
jws : eabJWS ,
2021-12-07 11:17:41 +00:00
err : acme . NewErrorISE ( "could not retrieve outer JWS from context" ) ,
}
} ,
"fail/outer-jws-multiple-signatures" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
eabJWS , err := createEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
rawEABJWS := eabJWS . FullSerialize ( )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
eab := & ExternalAccountBinding { }
err = json . Unmarshal ( [ ] byte ( rawEABJWS ) , & eab )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
outerJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
outerJWS . Signatures = append ( outerJWS . Signatures , jose . Signature { } )
ctx := context . WithValue ( context . TODO ( ) , jwsContextKey , outerJWS )
return test {
ctx : ctx ,
2021-12-07 13:57:39 +00:00
jws : eabJWS ,
2021-12-07 11:17:41 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "outer JWS must have one signature" ) ,
}
} ,
"fail/outer-jws-no-url" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
eabJWS , err := createEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
rawEABJWS := eabJWS . FullSerialize ( )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
eab := & ExternalAccountBinding { }
err = json . Unmarshal ( [ ] byte ( rawEABJWS ) , & eab )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
outerJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
ctx := context . WithValue ( context . TODO ( ) , jwsContextKey , outerJWS )
return test {
ctx : ctx ,
2021-12-07 13:57:39 +00:00
jws : eabJWS ,
2021-12-07 11:17:41 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "'url' field must be set in outer JWS" ) ,
}
} ,
"fail/outer-jws-with-different-url" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
eabJWS , err := createEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
rawEABJWS := eabJWS . FullSerialize ( )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
eab := & ExternalAccountBinding { }
err = json . Unmarshal ( [ ] byte ( rawEABJWS ) , & eab )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , "this-is-not-the-same-url-as-in-the-eab-jws" )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
outerJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
ctx := context . WithValue ( context . TODO ( ) , jwsContextKey , outerJWS )
return test {
ctx : ctx ,
2021-12-07 13:57:39 +00:00
jws : eabJWS ,
2021-12-07 11:17:41 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "'url' field is not the same value as the outer JWS" ) ,
}
} ,
"ok" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
url := fmt . Sprintf ( "%s/acme/%s/account/new-account" , baseURL . String ( ) , escProvName )
2021-12-07 13:57:39 +00:00
eabJWS , err := createEABJWS ( jwk , [ ] byte { 1 , 3 , 3 , 7 } , "eakID" , url )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
rawEABJWS := eabJWS . FullSerialize ( )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
2021-12-07 13:57:39 +00:00
eab := & ExternalAccountBinding { }
err = json . Unmarshal ( [ ] byte ( rawEABJWS ) , & eab )
2021-12-07 11:17:41 +00:00
assert . FatalError ( t , err )
nar := & NewAccountRequest {
Contact : [ ] string { "foo" , "bar" } ,
ExternalAccountBinding : eab ,
}
payloadBytes , err := json . Marshal ( nar )
assert . FatalError ( t , err )
so := new ( jose . SignerOptions )
so . WithHeader ( "alg" , jose . SignatureAlgorithm ( jwk . Algorithm ) )
so . WithHeader ( "url" , url )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( payloadBytes )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
outerJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
ctx := context . WithValue ( context . TODO ( ) , jwsContextKey , outerJWS )
return test {
ctx : ctx ,
2021-12-07 13:57:39 +00:00
jws : eabJWS ,
2021-12-07 11:17:41 +00:00
keyID : "eakID" ,
err : nil ,
}
} ,
}
for name , prep := range tests {
tc := prep ( t )
t . Run ( name , func ( t * testing . T ) {
keyID , err := validateEABJWS ( tc . ctx , tc . jws )
wantErr := tc . err != nil
gotErr := err != nil
if wantErr != gotErr {
t . Errorf ( "validateEABJWS() error = %v, want %v" , err , tc . err )
}
if wantErr {
assert . NotNil ( t , err )
assert . Equals ( t , tc . err . Type , err . Type )
assert . Equals ( t , tc . err . Status , err . Status )
assert . HasPrefix ( t , err . Err . Error ( ) , tc . err . Err . Error ( ) )
assert . Equals ( t , tc . err . Detail , err . Detail )
assert . Equals ( t , tc . err . Identifier , err . Identifier )
assert . Equals ( t , tc . err . Subproblems , err . Subproblems )
} else {
assert . Nil ( t , err )
assert . Equals ( t , tc . keyID , keyID )
}
} )
}
}