2019-05-27 00:41:10 +00:00
package api
import (
"bytes"
"context"
"crypto"
"encoding/base64"
"encoding/json"
2023-11-24 17:21:01 +00:00
"errors"
2019-05-27 00:41:10 +00:00
"fmt"
"io"
"net/http"
"net/http/httptest"
2020-05-07 03:18:12 +00:00
"net/url"
2019-05-27 00:41:10 +00:00
"strings"
"testing"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/acme"
2023-11-24 17:21:01 +00:00
tassert "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2020-08-24 21:44:11 +00:00
"go.step.sm/crypto/jose"
2022-08-25 20:31:33 +00:00
"go.step.sm/crypto/keyutil"
2019-05-27 00:41:10 +00:00
)
var testBody = [ ] byte ( "foo" )
2023-05-10 06:47:28 +00:00
func testNext ( w http . ResponseWriter , _ * http . Request ) {
2019-05-27 00:41:10 +00:00
w . Write ( testBody )
}
2022-05-03 00:35:35 +00:00
func newBaseContext ( ctx context . Context , args ... interface { } ) context . Context {
for _ , a := range args {
switch v := a . ( type ) {
case acme . DB :
ctx = acme . NewDatabaseContext ( ctx , v )
case acme . Linker :
ctx = acme . NewLinkerContext ( ctx , v )
case acme . PrerequisitesChecker :
ctx = acme . NewPrerequisitesCheckerContext ( ctx , v )
2020-05-07 03:18:12 +00:00
}
}
2022-05-03 00:35:35 +00:00
return ctx
2020-05-07 03:18:12 +00:00
}
2021-03-11 07:05:46 +00:00
func TestHandler_addNonce ( t * testing . T ) {
2021-10-08 18:59:57 +00:00
u := "https://ca.smallstep.com/acme/new-nonce"
2019-05-27 00:41:10 +00:00
type test struct {
2021-03-09 06:35:57 +00:00
db acme . DB
err * acme . Error
2019-05-27 00:41:10 +00:00
statusCode int
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/AddNonce-error" : func ( t * testing . T ) test {
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
MockCreateNonce : func ( ctx context . Context ) ( acme . Nonce , error ) {
return acme . Nonce ( "" ) , acme . NewErrorISE ( "force" )
2019-05-27 00:41:10 +00:00
} ,
} ,
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 {
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
MockCreateNonce : func ( ctx context . Context ) ( acme . Nonce , error ) {
2019-05-27 00:41:10 +00:00
return "bar" , nil
} ,
} ,
statusCode : 200 ,
}
} ,
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2022-05-03 00:35:35 +00:00
ctx := newBaseContext ( context . Background ( ) , tc . db )
2023-08-29 18:52:13 +00:00
req := httptest . NewRequest ( "GET" , u , http . NoBody ) . WithContext ( ctx )
2019-05-27 00:41:10 +00:00
w := httptest . NewRecorder ( )
2022-04-28 02:08:16 +00:00
addNonce ( testNext ) ( 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 . Subproblems , tc . err . Subproblems )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
assert . Equals ( t , res . Header [ "Replay-Nonce" ] , [ ] string { "bar" } )
assert . Equals ( t , res . Header [ "Cache-Control" ] , [ ] string { "no-store" } )
assert . Equals ( t , bytes . TrimSpace ( body ) , testBody )
}
} )
}
}
2021-03-10 18:50:51 +00:00
func TestHandler_addDirLink ( t * testing . T ) {
2019-05-27 00:41:10 +00:00
prov := newProv ( )
2020-05-07 03:18:12 +00:00
provName := url . PathEscape ( prov . GetName ( ) )
baseURL := & url . URL { Scheme : "https" , Host : "test.ca.smallstep.com" }
2019-05-27 00:41:10 +00:00
type test struct {
link string
statusCode int
ctx context . Context
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 {
"ok" : func ( t * testing . T ) test {
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
ctx = acme . NewLinkerContext ( ctx , acme . NewLinker ( "test.ca.smallstep.com" , "acme" ) )
2019-05-27 00:41:10 +00:00
return test {
2020-05-07 03:18:12 +00:00
ctx : ctx ,
link : fmt . Sprintf ( "%s/acme/%s/directory" , baseURL . String ( ) , provName ) ,
2019-05-27 00:41:10 +00:00
statusCode : 200 ,
}
} ,
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2023-08-29 18:52:13 +00:00
req := httptest . NewRequest ( "GET" , "/foo" , http . NoBody )
2019-05-27 00:41:10 +00:00
req = req . WithContext ( tc . ctx )
w := httptest . NewRecorder ( )
2022-04-28 02:08:16 +00:00
addDirLink ( testNext ) ( 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 . Subproblems , tc . err . Subproblems )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
assert . Equals ( t , res . Header [ "Link" ] , [ ] string { fmt . Sprintf ( "<%s>;rel=\"index\"" , tc . link ) } )
assert . Equals ( t , bytes . TrimSpace ( body ) , testBody )
}
} )
}
}
2021-03-11 07:05:46 +00:00
func TestHandler_verifyContentType ( 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" }
2021-10-08 18:59:57 +00:00
u := fmt . Sprintf ( "%s/acme/%s/certificate/abc123" , baseURL . String ( ) , escProvName )
2019-05-27 00:41:10 +00:00
type test struct {
ctx context . Context
contentType string
2021-03-09 06:35:57 +00:00
err * acme . Error
2019-05-27 00:41:10 +00:00
statusCode int
url string
}
var tests = map [ string ] func ( t * testing . T ) test {
2021-04-14 22:35:43 +00:00
"fail/provisioner-not-set" : func ( t * testing . T ) test {
return test {
2021-10-08 18:59:57 +00:00
url : u ,
2021-04-14 22:35:43 +00:00
ctx : context . Background ( ) ,
contentType : "foo" ,
statusCode : 500 ,
err : acme . NewErrorISE ( "provisioner expected in request context" ) ,
}
} ,
2019-05-27 00:41:10 +00:00
"fail/general-bad-content-type" : func ( t * testing . T ) test {
return test {
2021-10-08 18:59:57 +00:00
url : u ,
2022-05-03 00:35:35 +00:00
ctx : acme . NewProvisionerContext ( context . Background ( ) , prov ) ,
2019-05-27 00:41:10 +00:00
contentType : "foo" ,
statusCode : 400 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "expected content-type to be in [application/jose+json], but got foo" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/certificate-bad-content-type" : func ( t * testing . T ) test {
return test {
2022-05-03 00:35:35 +00:00
ctx : acme . NewProvisionerContext ( context . Background ( ) , prov ) ,
2019-05-27 00:41:10 +00:00
contentType : "foo" ,
statusCode : 400 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "expected content-type to be in [application/jose+json application/pkix-cert application/pkcs7-mime], but got foo" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"ok" : func ( t * testing . T ) test {
return test {
2022-05-03 00:35:35 +00:00
ctx : acme . NewProvisionerContext ( context . Background ( ) , prov ) ,
2019-05-27 00:41:10 +00:00
contentType : "application/jose+json" ,
statusCode : 200 ,
}
} ,
"ok/certificate/pkix-cert" : func ( t * testing . T ) test {
return test {
2022-05-03 00:35:35 +00:00
ctx : acme . NewProvisionerContext ( context . Background ( ) , prov ) ,
2019-05-27 00:41:10 +00:00
contentType : "application/pkix-cert" ,
statusCode : 200 ,
}
} ,
"ok/certificate/jose+json" : func ( t * testing . T ) test {
return test {
2022-05-03 00:35:35 +00:00
ctx : acme . NewProvisionerContext ( context . Background ( ) , prov ) ,
2019-05-27 00:41:10 +00:00
contentType : "application/jose+json" ,
statusCode : 200 ,
}
} ,
"ok/certificate/pkcs7-mime" : func ( t * testing . T ) test {
return test {
2022-05-03 00:35:35 +00:00
ctx : acme . NewProvisionerContext ( context . Background ( ) , prov ) ,
2019-05-27 00:41:10 +00:00
contentType : "application/pkcs7-mime" ,
statusCode : 200 ,
}
} ,
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2021-10-08 18:59:57 +00:00
_u := u
2019-05-27 00:41:10 +00:00
if tc . url != "" {
2021-10-08 18:59:57 +00:00
_u = tc . url
2019-05-27 00:41:10 +00:00
}
2023-08-29 18:52:13 +00:00
req := httptest . NewRequest ( "GET" , _u , http . NoBody )
2019-05-27 00:41:10 +00:00
req = req . WithContext ( tc . ctx )
req . Header . Add ( "Content-Type" , tc . contentType )
w := httptest . NewRecorder ( )
2022-04-28 02:08:16 +00:00
verifyContentType ( testNext ) ( 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 . Subproblems , tc . err . Subproblems )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
assert . Equals ( t , bytes . TrimSpace ( body ) , testBody )
}
} )
}
}
2021-03-11 07:05:46 +00:00
func TestHandler_isPostAsGet ( t * testing . T ) {
2021-10-08 18:59:57 +00:00
u := "https://ca.smallstep.com/acme/new-account"
2019-05-27 00:41:10 +00:00
type test struct {
ctx context . Context
2021-03-09 06:35:57 +00:00
err * acme . Error
2019-05-27 00:41:10 +00:00
statusCode int
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/no-payload" : func ( t * testing . T ) test {
return test {
ctx : context . Background ( ) ,
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 {
return test {
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , payloadContextKey , nil ) ,
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/not-post-as-get" : func ( t * testing . T ) test {
return test {
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { } ) ,
2019-05-27 00:41:10 +00:00
statusCode : 400 ,
2021-03-09 06:35:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "expected POST-as-GET" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"ok" : func ( t * testing . T ) test {
return test {
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , payloadContextKey , & payloadInfo { isPostAsGet : true } ) ,
2019-05-27 00:41:10 +00:00
statusCode : 200 ,
}
} ,
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2022-04-28 02:08:16 +00:00
// h := &Handler{}
2023-08-29 18:52:13 +00:00
req := httptest . NewRequest ( "GET" , u , http . NoBody )
2019-05-27 00:41:10 +00:00
req = req . WithContext ( tc . ctx )
w := httptest . NewRecorder ( )
2022-04-28 02:08:16 +00:00
isPostAsGet ( testNext ) ( 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 . Subproblems , tc . err . Subproblems )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
assert . Equals ( t , bytes . TrimSpace ( body ) , testBody )
}
} )
}
}
type errReader int
2023-05-10 06:47:28 +00:00
func ( errReader ) Read ( [ ] byte ) ( int , error ) {
2019-05-27 00:41:10 +00:00
return 0 , errors . New ( "force" )
}
func ( errReader ) Close ( ) error {
return nil
}
2021-03-11 07:05:46 +00:00
func TestHandler_parseJWS ( t * testing . T ) {
2021-10-08 18:59:57 +00:00
u := "https://ca.smallstep.com/acme/new-account"
2019-05-27 00:41:10 +00:00
type test struct {
next nextHTTP
body io . Reader
2021-03-09 06:35:57 +00:00
err * acme . Error
2019-05-27 00:41:10 +00:00
statusCode int
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/read-body-error" : func ( t * testing . T ) test {
return test {
body : errReader ( 0 ) ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "failed to read request body: force" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/parse-jws-error" : func ( t * testing . T ) test {
return test {
body : strings . NewReader ( "foo" ) ,
statusCode : 400 ,
2023-12-13 00:36:48 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "failed to parse JWS from request body: go-jose/go-jose: compact JWS format must have three parts" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"ok" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , new ( jose . SignerOptions ) )
assert . FatalError ( t , err )
signed , err := signer . Sign ( [ ] byte ( "baz" ) )
assert . FatalError ( t , err )
expRaw , err := signed . CompactSerialize ( )
assert . FatalError ( t , err )
return test {
body : strings . NewReader ( expRaw ) ,
next : func ( w http . ResponseWriter , r * http . Request ) {
2021-03-09 06:35:57 +00:00
jws , err := jwsFromContext ( r . Context ( ) )
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , err )
gotRaw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
assert . Equals ( t , gotRaw , expRaw )
w . Write ( testBody )
} ,
statusCode : 200 ,
}
} ,
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2022-04-28 02:08:16 +00:00
// h := &Handler{}
2021-10-08 18:59:57 +00:00
req := httptest . NewRequest ( "GET" , u , tc . body )
2019-05-27 00:41:10 +00:00
w := httptest . NewRecorder ( )
2022-04-28 02:08:16 +00:00
parseJWS ( tc . next ) ( 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 . Subproblems , tc . err . Subproblems )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
assert . Equals ( t , bytes . TrimSpace ( body ) , testBody )
}
} )
}
}
2021-03-11 07:05:46 +00:00
func TestHandler_verifyAndExtractJWSPayload ( t * testing . T ) {
2019-05-27 00:41:10 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
_pub := jwk . Public ( )
pub := & _pub
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 ( [ ] byte ( "baz" ) )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
2021-10-08 18:59:57 +00:00
u := "https://ca.smallstep.com/acme/account/1234"
2019-05-27 00:41:10 +00:00
type test struct {
ctx context . Context
next func ( http . ResponseWriter , * http . Request )
2021-03-09 06:35:57 +00:00
err * acme . Error
2019-05-27 00:41:10 +00:00
statusCode int
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/no-jws" : func ( t * testing . T ) test {
return test {
ctx : context . Background ( ) ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "jws expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/nil-jws" : func ( t * testing . T ) test {
return test {
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , nil ) ,
2019-05-27 00:41:10 +00:00
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "jws expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/no-jwk" : func ( t * testing . T ) test {
return test {
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
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 {
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , jwsContextKey , parsedJWS )
2019-05-27 00:41:10 +00:00
return test {
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( ctx , jwsContextKey , nil ) ,
2019-05-27 00:41:10 +00:00
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
}
} ,
2023-11-29 01:30:28 +00:00
"fail/verify-jws-failure-wrong-jwk" : func ( t * testing . T ) test {
2019-05-27 00:41:10 +00:00
_jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
_pub := _jwk . Public ( )
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , jwsContextKey , parsedJWS )
2021-03-11 07:05:46 +00:00
ctx = context . WithValue ( ctx , jwkContextKey , & _pub )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 400 ,
2023-12-13 00:36:48 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "error verifying jws: go-jose/go-jose: error in cryptographic primitive" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
2023-11-29 01:30:28 +00:00
"fail/verify-jws-failure-too-many-signatures" : func ( t * testing . T ) test {
newParsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
newParsedJWS . Signatures = append ( newParsedJWS . Signatures , newParsedJWS . Signatures ... )
ctx := context . WithValue ( context . Background ( ) , jwsContextKey , newParsedJWS )
ctx = context . WithValue ( ctx , jwkContextKey , pub )
return test {
ctx : ctx ,
statusCode : 400 ,
2023-12-13 00:36:48 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "error verifying jws: go-jose/go-jose: too many signatures in payload; expecting only one" ) ,
2023-11-29 01:30:28 +00:00
}
} ,
"fail/apple-acmeclient-omitting-leading-null-byte-in-signature-with-wrong-jwk" : func ( t * testing . T ) test {
_jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
_pub := _jwk . Public ( )
appleNullByteCaseBody := ` { "payload":"dGVzdC0xMTA1","protected":"eyJhbGciOiJFUzI1NiJ9","signature":"rQPYKYflfKnlgBKqDeWsJH2TJ6iHAnou7sFzXlmYD4ArXqLfYuqotWERKrna2wfzh0pu7USWO2gzlOqRK9qq"} `
appleNullByteCaseJWS , err := jose . ParseJWS ( appleNullByteCaseBody )
require . NoError ( t , err )
ctx := context . WithValue ( context . Background ( ) , jwsContextKey , appleNullByteCaseJWS )
ctx = context . WithValue ( ctx , jwkContextKey , & _pub )
return test {
ctx : ctx ,
statusCode : 400 ,
2023-12-13 00:36:48 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "error verifying jws: go-jose/go-jose: error in cryptographic primitive" ) ,
2023-11-29 01:30:28 +00:00
}
} ,
2019-05-27 00:41:10 +00:00
"fail/algorithm-mismatch" : func ( t * testing . T ) test {
_pub := * pub
clone := & _pub
clone . Algorithm = jose . HS256
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , jwsContextKey , parsedJWS )
2021-03-11 07:05:46 +00:00
ctx = context . WithValue ( ctx , jwkContextKey , clone )
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 , "verifier and signature algorithm do not match" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"ok" : func ( t * testing . T ) test {
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , jwsContextKey , parsedJWS )
2021-03-11 07:05:46 +00:00
ctx = context . WithValue ( ctx , jwkContextKey , pub )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 200 ,
next : func ( w http . ResponseWriter , r * http . Request ) {
2020-05-07 03:18:12 +00:00
p , err := payloadFromContext ( r . Context ( ) )
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , err )
if assert . NotNil ( t , p ) {
assert . Equals ( t , p . value , [ ] byte ( "baz" ) )
assert . False ( t , p . isPostAsGet )
assert . False ( t , p . isEmptyJSON )
}
w . Write ( testBody )
} ,
}
} ,
"ok/empty-algorithm-in-jwk" : func ( t * testing . T ) test {
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , jwsContextKey , parsedJWS )
2021-03-11 07:05:46 +00:00
ctx = context . WithValue ( ctx , jwkContextKey , pub )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 200 ,
next : func ( w http . ResponseWriter , r * http . Request ) {
2020-05-07 03:18:12 +00:00
p , err := payloadFromContext ( r . Context ( ) )
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , err )
if assert . NotNil ( t , p ) {
assert . Equals ( t , p . value , [ ] byte ( "baz" ) )
assert . False ( t , p . isPostAsGet )
assert . False ( t , p . isEmptyJSON )
}
w . Write ( testBody )
} ,
}
} ,
"ok/post-as-get" : func ( t * testing . T ) test {
_jws , err := signer . Sign ( [ ] byte ( "" ) )
assert . FatalError ( t , err )
_raw , err := _jws . CompactSerialize ( )
assert . FatalError ( t , err )
_parsed , err := jose . ParseJWS ( _raw )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , jwsContextKey , _parsed )
2021-03-11 07:05:46 +00:00
ctx = context . WithValue ( ctx , jwkContextKey , pub )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 200 ,
next : func ( w http . ResponseWriter , r * http . Request ) {
2020-05-07 03:18:12 +00:00
p , err := payloadFromContext ( r . Context ( ) )
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , err )
if assert . NotNil ( t , p ) {
assert . Equals ( t , p . value , [ ] byte { } )
assert . True ( t , p . isPostAsGet )
assert . False ( t , p . isEmptyJSON )
}
w . Write ( testBody )
} ,
}
} ,
"ok/empty-json" : func ( t * testing . T ) test {
_jws , err := signer . Sign ( [ ] byte ( "{}" ) )
assert . FatalError ( t , err )
_raw , err := _jws . CompactSerialize ( )
assert . FatalError ( t , err )
_parsed , err := jose . ParseJWS ( _raw )
assert . FatalError ( t , err )
2021-03-09 06:35:57 +00:00
ctx := context . WithValue ( context . Background ( ) , jwsContextKey , _parsed )
2021-03-11 07:05:46 +00:00
ctx = context . WithValue ( ctx , jwkContextKey , pub )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
statusCode : 200 ,
next : func ( w http . ResponseWriter , r * http . Request ) {
2020-05-07 03:18:12 +00:00
p , err := payloadFromContext ( r . Context ( ) )
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , err )
if assert . NotNil ( t , p ) {
assert . Equals ( t , p . value , [ ] byte ( "{}" ) )
assert . False ( t , p . isPostAsGet )
assert . True ( t , p . isEmptyJSON )
}
w . Write ( testBody )
} ,
}
} ,
2023-11-29 01:30:28 +00:00
"ok/apple-acmeclient-omitting-leading-null-byte-in-signature" : func ( t * testing . T ) test {
2023-11-24 17:21:01 +00:00
appleNullByteCaseKey := [ ] byte ( ` {
2023-11-29 01:30:28 +00:00
"kid" : "uioinbiTlJICL0MYsb6ar1totfRA2tiPqWgntF8xUdo" ,
2023-11-24 17:21:01 +00:00
"crv" : "P-256" ,
"alg" : "ES256" ,
"kty" : "EC" ,
2023-11-29 01:30:28 +00:00
"x" : "wlz-Kv9X0h32fzLq-cogls9HxoZQqV-GuWxdb2MCeUY" ,
"y" : "xzP6zRrg_jynYljZTxfJuql_QWtdQR6lpJ52q_6Vavg"
2023-11-24 17:21:01 +00:00
} ` )
appleNullByteCaseJWK := & jose . JSONWebKey { }
err = json . Unmarshal ( appleNullByteCaseKey , appleNullByteCaseJWK )
require . NoError ( t , err )
2023-11-29 01:30:28 +00:00
appleNullByteCaseBody := ` { "payload":"dGVzdC0xMTA1","protected":"eyJhbGciOiJFUzI1NiJ9","signature":"rQPYKYflfKnlgBKqDeWsJH2TJ6iHAnou7sFzXlmYD4ArXqLfYuqotWERKrna2wfzh0pu7USWO2gzlOqRK9qq"} `
2023-11-24 17:21:01 +00:00
appleNullByteCaseJWS , err := jose . ParseJWS ( appleNullByteCaseBody )
require . NoError ( t , err )
ctx := context . WithValue ( context . Background ( ) , jwsContextKey , appleNullByteCaseJWS )
ctx = context . WithValue ( ctx , jwkContextKey , appleNullByteCaseJWK )
return test {
ctx : ctx ,
statusCode : 200 ,
next : func ( w http . ResponseWriter , r * http . Request ) {
p , err := payloadFromContext ( r . Context ( ) )
tassert . NoError ( t , err )
if tassert . NotNil ( t , p ) {
2023-11-29 01:30:28 +00:00
tassert . Equal ( t , [ ] byte ( ` test-1105 ` ) , p . value )
2023-11-24 17:21:01 +00:00
tassert . False ( t , p . isPostAsGet )
tassert . False ( t , p . isEmptyJSON )
}
w . Write ( testBody )
} ,
}
} ,
2019-05-27 00:41:10 +00:00
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2022-04-28 02:08:16 +00:00
// h := &Handler{}
2023-08-29 18:52:13 +00:00
req := httptest . NewRequest ( "GET" , u , http . NoBody )
2019-05-27 00:41:10 +00:00
req = req . WithContext ( tc . ctx )
w := httptest . NewRecorder ( )
2022-04-28 02:08:16 +00:00
verifyAndExtractJWSPayload ( tc . next ) ( 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 . Subproblems , tc . err . Subproblems )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
assert . Equals ( t , bytes . TrimSpace ( body ) , testBody )
}
} )
}
}
2021-03-11 07:05:46 +00:00
func TestHandler_lookupJWK ( t * testing . T ) {
2019-05-27 00:41:10 +00:00
prov := newProv ( )
2020-05-07 03:18:12 +00:00
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 ( "%s/acme/%s/account/1234" ,
2020-05-07 03:18:12 +00:00
baseURL , provName )
2019-05-27 00:41:10 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
accID := "account-id"
2020-05-07 03:18:12 +00:00
prefix := fmt . Sprintf ( "%s/acme/%s/account/" ,
baseURL , provName )
2019-05-27 00:41:10 +00:00
so := new ( jose . SignerOptions )
so . WithHeader ( "kid" , fmt . Sprintf ( "%s%s" , prefix , accID ) )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( [ ] byte ( "baz" ) )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
type test struct {
2022-05-03 00:35:35 +00:00
linker acme . Linker
2021-03-09 06:35:57 +00:00
db acme . DB
2019-05-27 00:41:10 +00:00
ctx context . Context
next func ( http . ResponseWriter , * http . Request )
2021-03-09 06:35:57 +00:00
err * acme . Error
2019-05-27 00:41:10 +00:00
statusCode int
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/no-jws" : func ( t * testing . T ) test {
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
linker : acme . NewLinker ( "test.ca.smallstep.com" , "acme" ) ,
ctx : acme . NewProvisionerContext ( context . Background ( ) , prov ) ,
2019-05-27 00:41:10 +00:00
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "jws expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/nil-jws" : func ( t * testing . T ) test {
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , nil )
2019-05-27 00:41:10 +00:00
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
linker : acme . NewLinker ( "test.ca.smallstep.com" , "acme" ) ,
2019-05-27 00:41:10 +00:00
ctx : ctx ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "jws expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/no-kid" : func ( t * testing . T ) test {
_signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , new ( jose . SignerOptions ) )
assert . FatalError ( t , err )
_jws , err := _signer . Sign ( [ ] byte ( "baz" ) )
assert . FatalError ( t , err )
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , _jws )
2019-05-27 00:41:10 +00:00
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
linker : acme . NewLinker ( "test.ca.smallstep.com" , "acme" ) ,
2019-05-27 00:41:10 +00:00
ctx : ctx ,
statusCode : 400 ,
2023-06-07 06:37:51 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "signature missing 'kid'" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/account-not-found" : func ( t * testing . T ) test {
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2019-05-27 00:41:10 +00:00
return test {
2022-05-03 00:35:35 +00:00
linker : acme . NewLinker ( "test.ca.smallstep.com" , "acme" ) ,
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockGetAccount : func ( ctx context . Context , accID string ) ( * acme . Account , error ) {
2019-05-27 00:41:10 +00:00
assert . Equals ( t , accID , accID )
2023-06-07 06:37:51 +00:00
return nil , acme . ErrNotFound
2019-05-27 00:41:10 +00:00
} ,
} ,
ctx : ctx ,
2020-02-02 01:35:41 +00:00
statusCode : 400 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorAccountDoesNotExistType , "account does not exist" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/GetAccount-error" : func ( t * testing . T ) test {
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2019-05-27 00:41:10 +00:00
return test {
2022-05-03 00:35:35 +00:00
linker : acme . NewLinker ( "test.ca.smallstep.com" , "acme" ) ,
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockGetAccount : func ( ctx context . Context , id string ) ( * acme . Account , error ) {
assert . Equals ( t , id , accID )
2021-03-09 06:35:57 +00:00
return nil , 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
}
} ,
"fail/account-not-valid" : func ( t * testing . T ) test {
acc := & acme . Account { Status : "deactivated" }
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2019-05-27 00:41:10 +00:00
return test {
2022-05-03 00:35:35 +00:00
linker : acme . NewLinker ( "test.ca.smallstep.com" , "acme" ) ,
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockGetAccount : func ( ctx context . Context , id string ) ( * acme . Account , error ) {
assert . Equals ( t , id , accID )
2019-05-27 00:41:10 +00:00
return acc , nil
} ,
} ,
ctx : ctx ,
statusCode : 401 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorUnauthorizedType , "account is not active" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
2023-06-07 06:37:51 +00:00
"fail/account-with-location-prefix/bad-kid" : func ( t * testing . T ) test {
acc := & acme . Account { LocationPrefix : "foobar" , Status : "valid" }
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
return test {
linker : acme . NewLinker ( "test.ca.smallstep.com" , "acme" ) ,
db : & acme . MockDB {
MockGetAccount : func ( ctx context . Context , id string ) ( * acme . Account , error ) {
assert . Equals ( t , id , accID )
return acc , nil
} ,
} ,
ctx : ctx ,
statusCode : http . StatusUnauthorized ,
err : acme . NewError ( acme . ErrorUnauthorizedType , "kid does not match stored account location; expected foobar, but %q" , prefix + accID ) ,
}
} ,
"fail/account-with-location-prefix/bad-provisioner" : func ( t * testing . T ) test {
acc := & acme . Account { LocationPrefix : prefix + accID , Status : "valid" , Key : jwk , ProvisionerName : "other" }
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
return test {
linker : acme . NewLinker ( "test.ca.smallstep.com" , "acme" ) ,
db : & acme . MockDB {
MockGetAccount : func ( ctx context . Context , id string ) ( * acme . Account , error ) {
assert . Equals ( t , id , accID )
return acc , nil
} ,
} ,
ctx : ctx ,
next : func ( w http . ResponseWriter , r * http . Request ) {
_acc , err := accountFromContext ( r . Context ( ) )
assert . FatalError ( t , err )
assert . Equals ( t , _acc , acc )
_jwk , err := jwkFromContext ( r . Context ( ) )
assert . FatalError ( t , err )
assert . Equals ( t , _jwk , jwk )
w . Write ( testBody )
} ,
statusCode : http . StatusUnauthorized ,
err : acme . NewError ( acme . ErrorUnauthorizedType ,
"account provisioner does not match requested provisioner; account provisioner = %s, reqested provisioner = %s" ,
prov . GetName ( ) , "other" ) ,
}
} ,
"ok/account-with-location-prefix" : func ( t * testing . T ) test {
acc := & acme . Account { LocationPrefix : prefix + accID , Status : "valid" , Key : jwk , ProvisionerName : prov . GetName ( ) }
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
return test {
linker : acme . NewLinker ( "test.ca.smallstep.com" , "acme" ) ,
db : & acme . MockDB {
MockGetAccount : func ( ctx context . Context , id string ) ( * acme . Account , error ) {
assert . Equals ( t , id , accID )
return acc , nil
} ,
} ,
ctx : ctx ,
next : func ( w http . ResponseWriter , r * http . Request ) {
_acc , err := accountFromContext ( r . Context ( ) )
assert . FatalError ( t , err )
assert . Equals ( t , _acc , acc )
_jwk , err := jwkFromContext ( r . Context ( ) )
assert . FatalError ( t , err )
assert . Equals ( t , _jwk , jwk )
w . Write ( testBody )
} ,
statusCode : http . StatusOK ,
}
} ,
"ok/account-without-location-prefix" : func ( t * testing . T ) test {
2019-05-27 00:41:10 +00:00
acc := & acme . Account { Status : "valid" , Key : jwk }
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2019-05-27 00:41:10 +00:00
return test {
2022-05-03 00:35:35 +00:00
linker : acme . NewLinker ( "test.ca.smallstep.com" , "acme" ) ,
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockGetAccount : func ( ctx context . Context , id string ) ( * acme . Account , error ) {
assert . Equals ( t , id , accID )
2019-05-27 00:41:10 +00:00
return acc , nil
} ,
} ,
ctx : ctx ,
next : func ( w http . ResponseWriter , r * http . Request ) {
2021-03-10 18:50:51 +00:00
_acc , err := accountFromContext ( r . Context ( ) )
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , err )
assert . Equals ( t , _acc , acc )
2021-03-10 18:50:51 +00:00
_jwk , err := jwkFromContext ( r . Context ( ) )
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , err )
assert . Equals ( t , _jwk , jwk )
w . Write ( testBody )
} ,
statusCode : 200 ,
}
} ,
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2022-05-03 00:35:35 +00:00
ctx := newBaseContext ( tc . ctx , tc . db , tc . linker )
2023-08-29 18:52:13 +00:00
req := httptest . NewRequest ( "GET" , u , http . NoBody )
2022-05-03 00:35:35 +00:00
req = req . WithContext ( ctx )
2019-05-27 00:41:10 +00:00
w := httptest . NewRecorder ( )
2022-04-28 02:08:16 +00:00
lookupJWK ( tc . next ) ( 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 ) {
2021-03-10 18:50:51 +00:00
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 . Subproblems , tc . err . Subproblems )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
assert . Equals ( t , bytes . TrimSpace ( body ) , testBody )
}
} )
}
}
2021-03-11 07:05:46 +00:00
func TestHandler_extractJWK ( t * testing . T ) {
2019-05-27 00:41:10 +00:00
prov := newProv ( )
2020-05-07 03:18:12 +00:00
provName := url . PathEscape ( prov . GetName ( ) )
2019-05-27 00:41:10 +00:00
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
kid , err := jwk . Thumbprint ( crypto . SHA256 )
assert . FatalError ( t , err )
pub := jwk . Public ( )
pub . KeyID = base64 . RawURLEncoding . EncodeToString ( kid )
so := new ( jose . SignerOptions )
so . WithHeader ( "jwk" , pub )
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( [ ] byte ( "baz" ) )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
2021-10-08 18:59:57 +00:00
u := fmt . Sprintf ( "https://ca.smallstep.com/acme/%s/account/1234" ,
2020-05-07 03:18:12 +00:00
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
next func ( http . ResponseWriter , * http . Request )
2021-03-09 06:35:57 +00:00
err * acme . Error
2019-05-27 00:41:10 +00:00
statusCode int
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/no-jws" : func ( t * testing . T ) test {
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
ctx : acme . NewProvisionerContext ( context . Background ( ) , prov ) ,
2019-05-27 00:41:10 +00:00
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "jws expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/nil-jws" : func ( t * testing . T ) test {
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , nil )
2019-05-27 00:41:10 +00:00
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
2019-05-27 00:41:10 +00:00
ctx : ctx ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "jws expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/nil-jwk" : func ( t * testing . T ) test {
_jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{
Protected : jose . Header {
JSONWebKey : nil ,
} ,
} ,
} ,
}
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , _jws )
2019-05-27 00:41:10 +00:00
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
2019-05-27 00:41:10 +00:00
ctx : ctx ,
statusCode : 400 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "jwk expected in protected header" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/invalid-jwk" : func ( t * testing . T ) test {
_jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{
Protected : jose . Header {
JSONWebKey : & jose . JSONWebKey { Key : "foo" } ,
} ,
} ,
} ,
}
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , _jws )
2019-05-27 00:41:10 +00:00
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
2019-05-27 00:41:10 +00:00
ctx : ctx ,
statusCode : 400 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "invalid jwk in protected header" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/GetAccountByKey-error" : func ( t * testing . T ) test {
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockGetAccountByKeyID : func ( ctx context . Context , kid string ) ( * acme . Account , error ) {
assert . Equals ( t , kid , pub . KeyID )
2021-03-09 06:35:57 +00:00
return nil , acme . NewErrorISE ( "force" )
2019-05-27 00:41:10 +00:00
} ,
} ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "force" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/account-not-valid" : func ( t * testing . T ) test {
acc := & acme . Account { Status : "deactivated" }
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockGetAccountByKeyID : func ( ctx context . Context , kid string ) ( * acme . Account , error ) {
assert . Equals ( t , kid , pub . KeyID )
2019-05-27 00:41:10 +00:00
return acc , nil
} ,
} ,
statusCode : 401 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorUnauthorizedType , "account is not active" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"ok" : func ( t * testing . T ) test {
acc := & acme . Account { Status : "valid" }
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockGetAccountByKeyID : func ( ctx context . Context , kid string ) ( * acme . Account , error ) {
assert . Equals ( t , kid , pub . KeyID )
2019-05-27 00:41:10 +00:00
return acc , nil
} ,
} ,
next : func ( w http . ResponseWriter , r * http . Request ) {
2021-03-10 18:50:51 +00:00
_acc , err := accountFromContext ( r . Context ( ) )
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , err )
assert . Equals ( t , _acc , acc )
2021-03-10 18:50:51 +00:00
_jwk , err := jwkFromContext ( r . Context ( ) )
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , err )
assert . Equals ( t , _jwk . KeyID , pub . KeyID )
w . Write ( testBody )
} ,
statusCode : 200 ,
}
} ,
"ok/no-account" : func ( t * testing . T ) test {
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-03-09 06:35:57 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
2019-05-27 00:41:10 +00:00
return test {
ctx : ctx ,
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockGetAccountByKeyID : func ( ctx context . Context , kid string ) ( * acme . Account , error ) {
assert . Equals ( t , kid , pub . KeyID )
2021-03-25 21:54:12 +00:00
return nil , acme . ErrNotFound
2019-05-27 00:41:10 +00:00
} ,
} ,
next : func ( w http . ResponseWriter , r * http . Request ) {
2021-03-10 18:50:51 +00:00
_acc , err := accountFromContext ( r . Context ( ) )
2019-05-27 00:41:10 +00:00
assert . NotNil ( t , err )
assert . Nil ( t , _acc )
2021-03-10 18:50:51 +00:00
_jwk , err := jwkFromContext ( r . Context ( ) )
2019-05-27 00:41:10 +00:00
assert . FatalError ( t , err )
assert . Equals ( t , _jwk . KeyID , pub . KeyID )
w . Write ( testBody )
} ,
statusCode : 200 ,
}
} ,
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2022-05-03 00:35:35 +00:00
ctx := newBaseContext ( tc . ctx , tc . db )
2023-08-29 18:52:13 +00:00
req := httptest . NewRequest ( "GET" , u , http . NoBody )
2022-05-03 00:35:35 +00:00
req = req . WithContext ( ctx )
2019-05-27 00:41:10 +00:00
w := httptest . NewRecorder ( )
2022-04-28 02:08:16 +00:00
extractJWK ( tc . next ) ( 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 ) {
2021-03-10 18:50:51 +00:00
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 . Subproblems , tc . err . Subproblems )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
assert . Equals ( t , bytes . TrimSpace ( body ) , testBody )
}
} )
}
}
2021-03-11 07:05:46 +00:00
func TestHandler_validateJWS ( t * testing . T ) {
2021-10-08 18:59:57 +00:00
u := "https://ca.smallstep.com/acme/account/1234"
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
next func ( http . ResponseWriter , * http . Request )
2021-03-09 06:35:57 +00:00
err * acme . Error
2019-05-27 00:41:10 +00:00
statusCode int
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/no-jws" : func ( t * testing . T ) test {
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
2019-05-27 00:41:10 +00:00
ctx : context . Background ( ) ,
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "jws expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/nil-jws" : func ( t * testing . T ) test {
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , nil ) ,
2019-05-27 00:41:10 +00:00
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "jws expected in request context" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/no-signature" : func ( t * testing . T ) test {
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , & jose . JSONWebSignature { } ) ,
2019-05-27 00:41:10 +00:00
statusCode : 400 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "request body does not contain a signature" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/more-than-one-signature" : func ( t * testing . T ) test {
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{ } ,
{ } ,
} ,
}
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
statusCode : 400 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "request body contains more than one signature" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/unprotected-header-not-empty" : func ( t * testing . T ) test {
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{ Unprotected : jose . Header { Nonce : "abc" } } ,
} ,
}
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
statusCode : 400 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "unprotected header must not be used" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/unsuitable-algorithm-none" : func ( t * testing . T ) test {
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{ Protected : jose . Header { Algorithm : "none" } } ,
} ,
}
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
statusCode : 400 ,
2021-04-13 02:06:07 +00:00
err : acme . NewError ( acme . ErrorBadSignatureAlgorithmType , "unsuitable algorithm: none" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/unsuitable-algorithm-mac" : func ( t * testing . T ) test {
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{ Protected : jose . Header { Algorithm : jose . HS256 } } ,
} ,
}
return test {
2022-05-03 00:35:35 +00:00
db : & acme . MockDB { } ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
statusCode : 400 ,
2021-04-13 02:06:07 +00:00
err : acme . NewError ( acme . ErrorBadSignatureAlgorithmType , "unsuitable algorithm: %s" , jose . HS256 ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/rsa-key-&-alg-mismatch" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
pub := jwk . Public ( )
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{
Protected : jose . Header {
Algorithm : jose . RS256 ,
JSONWebKey : & pub ,
ExtraHeaders : map [ jose . HeaderKey ] interface { } {
2021-10-08 18:59:57 +00:00
"url" : u ,
2019-05-27 00:41:10 +00:00
} ,
} ,
} ,
} ,
}
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockDeleteNonce : func ( ctx context . Context , n acme . Nonce ) error {
2019-05-27 00:41:10 +00:00
return nil
} ,
} ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
statusCode : 400 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "jws key type and algorithm do not match" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/rsa-key-too-small" : func ( t * testing . T ) test {
2022-08-25 20:31:33 +00:00
revert := keyutil . Insecure ( )
defer revert ( )
2019-05-27 00:41:10 +00:00
jwk , err := jose . GenerateJWK ( "RSA" , "" , "" , "sig" , "" , 1024 )
assert . FatalError ( t , err )
pub := jwk . Public ( )
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{
Protected : jose . Header {
Algorithm : jose . RS256 ,
JSONWebKey : & pub ,
ExtraHeaders : map [ jose . HeaderKey ] interface { } {
2021-10-08 18:59:57 +00:00
"url" : u ,
2019-05-27 00:41:10 +00:00
} ,
} ,
} ,
} ,
}
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockDeleteNonce : func ( ctx context . Context , n acme . Nonce ) error {
2019-05-27 00:41:10 +00:00
return nil
} ,
} ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
statusCode : 400 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "rsa keys must be at least 2048 bits (256 bytes) in size" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/UseNonce-error" : func ( t * testing . T ) test {
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{ Protected : jose . Header { Algorithm : jose . ES256 } } ,
} ,
}
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockDeleteNonce : func ( ctx context . Context , n acme . Nonce ) error {
2021-03-09 06:35:57 +00:00
return acme . NewErrorISE ( "force" )
2019-05-27 00:41:10 +00:00
} ,
} ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
statusCode : 500 ,
2021-03-09 06:35:57 +00:00
err : acme . NewErrorISE ( "force" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/no-url-header" : func ( t * testing . T ) test {
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{ Protected : jose . Header { Algorithm : jose . ES256 } } ,
} ,
}
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockDeleteNonce : func ( ctx context . Context , n acme . Nonce ) error {
2019-05-27 00:41:10 +00:00
return nil
} ,
} ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
statusCode : 400 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "jws missing url protected header" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/url-mismatch" : func ( t * testing . T ) test {
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{
Protected : jose . Header {
Algorithm : jose . ES256 ,
ExtraHeaders : map [ jose . HeaderKey ] interface { } {
"url" : "foo" ,
} ,
} ,
} ,
} ,
}
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockDeleteNonce : func ( ctx context . Context , n acme . Nonce ) error {
2019-05-27 00:41:10 +00:00
return nil
} ,
} ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
statusCode : 400 ,
2021-10-08 18:59:57 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "url header in JWS (foo) does not match request url (%s)" , u ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/both-jwk-kid" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
pub := jwk . Public ( )
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{
Protected : jose . Header {
Algorithm : jose . ES256 ,
KeyID : "bar" ,
JSONWebKey : & pub ,
ExtraHeaders : map [ jose . HeaderKey ] interface { } {
2021-10-08 18:59:57 +00:00
"url" : u ,
2019-05-27 00:41:10 +00:00
} ,
} ,
} ,
} ,
}
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockDeleteNonce : func ( ctx context . Context , n acme . Nonce ) error {
2019-05-27 00:41:10 +00:00
return nil
} ,
} ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
statusCode : 400 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "jwk and kid are mutually exclusive" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"fail/no-jwk-kid" : func ( t * testing . T ) test {
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{
Protected : jose . Header {
Algorithm : jose . ES256 ,
ExtraHeaders : map [ jose . HeaderKey ] interface { } {
2021-10-08 18:59:57 +00:00
"url" : u ,
2019-05-27 00:41:10 +00:00
} ,
} ,
} ,
} ,
}
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockDeleteNonce : func ( ctx context . Context , n acme . Nonce ) error {
2019-05-27 00:41:10 +00:00
return nil
} ,
} ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
statusCode : 400 ,
2021-03-10 18:50:51 +00:00
err : acme . NewError ( acme . ErrorMalformedType , "either jwk or kid must be defined in jws protected header" ) ,
2019-05-27 00:41:10 +00:00
}
} ,
"ok/kid" : func ( t * testing . T ) test {
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{
Protected : jose . Header {
Algorithm : jose . ES256 ,
KeyID : "bar" ,
ExtraHeaders : map [ jose . HeaderKey ] interface { } {
2021-10-08 18:59:57 +00:00
"url" : u ,
2019-05-27 00:41:10 +00:00
} ,
} ,
} ,
} ,
}
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockDeleteNonce : func ( ctx context . Context , n acme . Nonce ) error {
2019-05-27 00:41:10 +00:00
return nil
} ,
} ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
next : func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( testBody )
} ,
statusCode : 200 ,
}
} ,
"ok/jwk/ecdsa" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
pub := jwk . Public ( )
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{
Protected : jose . Header {
Algorithm : jose . ES256 ,
JSONWebKey : & pub ,
ExtraHeaders : map [ jose . HeaderKey ] interface { } {
2021-10-08 18:59:57 +00:00
"url" : u ,
2019-05-27 00:41:10 +00:00
} ,
} ,
} ,
} ,
}
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockDeleteNonce : func ( ctx context . Context , n acme . Nonce ) error {
2019-05-27 00:41:10 +00:00
return nil
} ,
} ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
next : func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( testBody )
} ,
statusCode : 200 ,
}
} ,
"ok/jwk/rsa" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "RSA" , "" , "" , "sig" , "" , 2048 )
assert . FatalError ( t , err )
pub := jwk . Public ( )
jws := & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{
Protected : jose . Header {
Algorithm : jose . RS256 ,
JSONWebKey : & pub ,
ExtraHeaders : map [ jose . HeaderKey ] interface { } {
2021-10-08 18:59:57 +00:00
"url" : u ,
2019-05-27 00:41:10 +00:00
} ,
} ,
} ,
} ,
}
return test {
2021-03-09 06:35:57 +00:00
db : & acme . MockDB {
2021-03-10 18:50:51 +00:00
MockDeleteNonce : func ( ctx context . Context , n acme . Nonce ) error {
2019-05-27 00:41:10 +00:00
return nil
} ,
} ,
2021-03-09 06:35:57 +00:00
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , jws ) ,
2019-05-27 00:41:10 +00:00
next : func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( testBody )
} ,
statusCode : 200 ,
}
} ,
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2022-05-03 00:35:35 +00:00
ctx := newBaseContext ( tc . ctx , tc . db )
2023-08-29 18:52:13 +00:00
req := httptest . NewRequest ( "GET" , u , http . NoBody )
2022-05-03 00:35:35 +00:00
req = req . WithContext ( ctx )
2019-05-27 00:41:10 +00:00
w := httptest . NewRecorder ( )
2022-04-28 02:08:16 +00:00
validateJWS ( tc . next ) ( 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 ) {
2021-03-10 18:50:51 +00:00
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 . Subproblems , tc . err . Subproblems )
2019-05-27 00:41:10 +00:00
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
assert . Equals ( t , bytes . TrimSpace ( body ) , testBody )
}
} )
}
}
2021-11-12 15:37:44 +00:00
func Test_canExtractJWKFrom ( t * testing . T ) {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
type args struct {
jws * jose . JSONWebSignature
}
tests := [ ] struct {
name string
args args
want bool
} {
{
name : "no-jws" ,
args : args {
jws : nil ,
} ,
want : false ,
} ,
{
name : "no-signatures" ,
args : args {
jws : & jose . JSONWebSignature {
Signatures : [ ] jose . Signature { } ,
} ,
} ,
want : false ,
} ,
{
name : "no-jwk" ,
args : args {
jws : & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{
Protected : jose . Header { } ,
} ,
} ,
} ,
} ,
want : false ,
} ,
{
name : "ok" ,
args : args {
jws : & jose . JSONWebSignature {
Signatures : [ ] jose . Signature {
{
Protected : jose . Header {
JSONWebKey : jwk ,
} ,
} ,
} ,
} ,
} ,
want : true ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if got := canExtractJWKFrom ( tt . args . jws ) ; got != tt . want {
t . Errorf ( "canExtractJWKFrom() = %v, want %v" , got , tt . want )
}
} )
}
}
func TestHandler_extractOrLookupJWK ( t * testing . T ) {
u := "https://ca.smallstep.com/acme/account"
type test struct {
db acme . DB
2022-05-03 00:35:35 +00:00
linker acme . Linker
2021-11-12 15:37:44 +00:00
statusCode int
ctx context . Context
err * acme . Error
next func ( w http . ResponseWriter , r * http . Request )
}
var tests = map [ string ] func ( t * testing . T ) test {
"ok/extract" : func ( t * testing . T ) test {
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
kid , err := jwk . Thumbprint ( crypto . SHA256 )
assert . FatalError ( t , err )
pub := jwk . Public ( )
pub . KeyID = base64 . RawURLEncoding . EncodeToString ( kid )
so := new ( jose . SignerOptions )
2021-12-02 09:59:56 +00:00
so . WithHeader ( "jwk" , pub ) // JWK for certificate private key flow
2021-11-12 15:37:44 +00:00
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
signed , err := signer . Sign ( [ ] byte ( "foo" ) )
assert . FatalError ( t , err )
raw , err := signed . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
return test {
2022-05-03 00:35:35 +00:00
linker : acme . NewLinker ( "dns" , "acme" ) ,
2021-11-12 15:37:44 +00:00
db : & acme . MockDB {
MockGetAccountByKeyID : func ( ctx context . Context , kid string ) ( * acme . Account , error ) {
assert . Equals ( t , kid , pub . KeyID )
2021-12-02 09:59:56 +00:00
return nil , acme . ErrNotFound
2021-11-12 15:37:44 +00:00
} ,
} ,
ctx : context . WithValue ( context . Background ( ) , jwsContextKey , parsedJWS ) ,
statusCode : 200 ,
next : func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( testBody )
} ,
}
} ,
"ok/lookup" : func ( t * testing . T ) test {
prov := newProv ( )
provName := url . PathEscape ( prov . GetName ( ) )
baseURL := & url . URL { Scheme : "https" , Host : "test.ca.smallstep.com" }
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
assert . FatalError ( t , err )
accID := "accID"
prefix := fmt . Sprintf ( "%s/acme/%s/account/" , baseURL , provName )
so := new ( jose . SignerOptions )
2021-12-02 09:59:56 +00:00
so . WithHeader ( "kid" , fmt . Sprintf ( "%s%s" , prefix , accID ) ) // KID for account private key flow
2021-11-12 15:37:44 +00:00
signer , err := jose . NewSigner ( jose . SigningKey {
Algorithm : jose . SignatureAlgorithm ( jwk . Algorithm ) ,
Key : jwk . Key ,
} , so )
assert . FatalError ( t , err )
jws , err := signer . Sign ( [ ] byte ( "baz" ) )
assert . FatalError ( t , err )
raw , err := jws . CompactSerialize ( )
assert . FatalError ( t , err )
parsedJWS , err := jose . ParseJWS ( raw )
assert . FatalError ( t , err )
acc := & acme . Account { ID : "accID" , Key : jwk , Status : "valid" }
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2021-11-12 15:37:44 +00:00
ctx = context . WithValue ( ctx , jwsContextKey , parsedJWS )
return test {
2022-05-03 00:35:35 +00:00
linker : acme . NewLinker ( "test.ca.smallstep.com" , "acme" ) ,
2021-11-12 15:37:44 +00:00
db : & acme . MockDB {
MockGetAccount : func ( ctx context . Context , accID string ) ( * acme . Account , error ) {
assert . Equals ( t , accID , acc . ID )
return acc , nil
} ,
} ,
ctx : ctx ,
statusCode : 200 ,
next : func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( testBody )
} ,
}
} ,
}
for name , prep := range tests {
tc := prep ( t )
t . Run ( name , func ( t * testing . T ) {
2022-05-03 00:35:35 +00:00
ctx := newBaseContext ( tc . ctx , tc . db , tc . linker )
2023-08-29 18:52:13 +00:00
req := httptest . NewRequest ( "GET" , u , http . NoBody )
2022-05-03 00:35:35 +00:00
req = req . WithContext ( ctx )
2021-11-12 15:37:44 +00:00
w := httptest . NewRecorder ( )
2022-04-28 02:08:16 +00:00
extractOrLookupJWK ( tc . next ) ( w , req )
2021-11-12 15:37:44 +00:00
res := w . Result ( )
assert . Equals ( t , res . StatusCode , tc . statusCode )
2021-11-24 23:44:21 +00:00
body , err := io . ReadAll ( res . Body )
2021-11-12 15:37:44 +00:00
res . Body . Close ( )
assert . FatalError ( t , err )
if res . StatusCode >= 400 && assert . NotNil ( t , tc . err ) {
var ae acme . Error
assert . FatalError ( t , json . Unmarshal ( bytes . TrimSpace ( body ) , & ae ) )
assert . Equals ( t , ae . Type , tc . err . Type )
assert . Equals ( t , ae . Detail , tc . err . Detail )
assert . Equals ( t , ae . Subproblems , tc . err . Subproblems )
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
assert . Equals ( t , bytes . TrimSpace ( body ) , testBody )
}
} )
}
}
2022-03-03 12:00:20 +00:00
func TestHandler_checkPrerequisites ( t * testing . T ) {
prov := newProv ( )
provName := url . PathEscape ( prov . GetName ( ) )
baseURL := & url . URL { Scheme : "https" , Host : "test.ca.smallstep.com" }
u := fmt . Sprintf ( "%s/acme/%s/account/1234" ,
baseURL , provName )
type test struct {
2022-05-03 00:35:35 +00:00
linker acme . Linker
2022-03-03 12:00:20 +00:00
ctx context . Context
prerequisitesChecker func ( context . Context ) ( bool , error )
next func ( http . ResponseWriter , * http . Request )
err * acme . Error
statusCode int
}
var tests = map [ string ] func ( t * testing . T ) test {
"fail/error" : func ( t * testing . T ) test {
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2022-03-03 12:00:20 +00:00
return test {
2022-05-03 00:35:35 +00:00
linker : acme . NewLinker ( "dns" , "acme" ) ,
2022-03-03 12:00:20 +00:00
ctx : ctx ,
prerequisitesChecker : func ( context . Context ) ( bool , error ) { return false , errors . New ( "force" ) } ,
next : func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( testBody )
} ,
err : acme . WrapErrorISE ( errors . New ( "force" ) , "error checking acme provisioner prerequisites" ) ,
statusCode : 500 ,
}
} ,
"fail/prerequisites-nok" : func ( t * testing . T ) test {
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2022-03-03 12:00:20 +00:00
return test {
2022-05-03 00:35:35 +00:00
linker : acme . NewLinker ( "dns" , "acme" ) ,
2022-03-03 12:00:20 +00:00
ctx : ctx ,
prerequisitesChecker : func ( context . Context ) ( bool , error ) { return false , nil } ,
next : func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( testBody )
} ,
err : acme . NewError ( acme . ErrorNotImplementedType , "acme provisioner configuration lacks prerequisites" ) ,
statusCode : 501 ,
}
} ,
"ok" : func ( t * testing . T ) test {
2022-05-03 00:35:35 +00:00
ctx := acme . NewProvisionerContext ( context . Background ( ) , prov )
2022-03-03 12:00:20 +00:00
return test {
2022-05-03 00:35:35 +00:00
linker : acme . NewLinker ( "dns" , "acme" ) ,
2022-03-03 12:00:20 +00:00
ctx : ctx ,
prerequisitesChecker : func ( context . Context ) ( bool , error ) { return true , nil } ,
next : func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( testBody )
} ,
statusCode : 200 ,
}
} ,
}
for name , run := range tests {
tc := run ( t )
t . Run ( name , func ( t * testing . T ) {
2022-05-03 00:40:10 +00:00
ctx := acme . NewPrerequisitesCheckerContext ( tc . ctx , tc . prerequisitesChecker )
2023-08-29 18:52:13 +00:00
req := httptest . NewRequest ( "GET" , u , http . NoBody )
2022-05-03 00:40:10 +00:00
req = req . WithContext ( ctx )
2022-03-03 12:00:20 +00:00
w := httptest . NewRecorder ( )
2022-04-28 02:08:16 +00:00
checkPrerequisites ( tc . next ) ( w , req )
2022-03-03 12:00:20 +00:00
res := w . Result ( )
assert . Equals ( t , res . StatusCode , tc . statusCode )
body , err := io . ReadAll ( res . Body )
res . Body . Close ( )
assert . FatalError ( t , err )
if res . StatusCode >= 400 && assert . NotNil ( t , tc . err ) {
var ae acme . Error
assert . FatalError ( t , json . Unmarshal ( bytes . TrimSpace ( body ) , & ae ) )
assert . Equals ( t , ae . Type , tc . err . Type )
assert . Equals ( t , ae . Detail , tc . err . Detail )
assert . Equals ( t , ae . Subproblems , tc . err . Subproblems )
assert . Equals ( t , res . Header [ "Content-Type" ] , [ ] string { "application/problem+json" } )
} else {
assert . Equals ( t , bytes . TrimSpace ( body ) , testBody )
}
} )
}
}
2023-11-24 17:21:01 +00:00
2023-11-29 01:30:28 +00:00
func Test_retryVerificationWithPatchedSignatures ( t * testing . T ) {
patchedRKey := [ ] byte ( ` {
"kid" : "uioinbiTlJICL0MYsb6ar1totfRA2tiPqWgntF8xUdo" ,
"crv" : "P-256" ,
"alg" : "ES256" ,
"kty" : "EC" ,
"x" : "wlz-Kv9X0h32fzLq-cogls9HxoZQqV-GuWxdb2MCeUY" ,
"y" : "xzP6zRrg_jynYljZTxfJuql_QWtdQR6lpJ52q_6Vavg"
} ` )
patchedRJWK := & jose . JSONWebKey { }
err := json . Unmarshal ( patchedRKey , patchedRJWK )
require . NoError ( t , err )
patchedRBody := ` { "payload":"dGVzdC0xMTA1","protected":"eyJhbGciOiJFUzI1NiJ9","signature":"rQPYKYflfKnlgBKqDeWsJH2TJ6iHAnou7sFzXlmYD4ArXqLfYuqotWERKrna2wfzh0pu7USWO2gzlOqRK9qq"} `
patchedR , err := jose . ParseJWS ( patchedRBody )
require . NoError ( t , err )
patchedSKey := [ ] byte ( ` {
"kid" : "PblXsnK59uTiF5k3mmAN2B6HDPPxqBL_4UGhEG8ZO6g" ,
2023-11-24 17:21:01 +00:00
"crv" : "P-256" ,
"alg" : "ES256" ,
"kty" : "EC" ,
2023-11-29 01:30:28 +00:00
"x" : "T5aM_TOSattXNeUkH1VHZXh8URzdjZTI2zLvVgI0cy0" ,
"y" : "Lf8h8qZnURXIxm6OnQ69kxGC91YtTZRD2GAroEf1UA8"
2023-11-24 17:21:01 +00:00
} ` )
2023-11-29 01:30:28 +00:00
patchedSJWK := & jose . JSONWebKey { }
err = json . Unmarshal ( patchedSKey , patchedSJWK )
2023-11-24 17:21:01 +00:00
require . NoError ( t , err )
2023-11-29 01:30:28 +00:00
patchedSBody := ` { "payload":"dGVzdC02Ng","protected":"eyJhbGciOiJFUzI1NiJ9","signature":"krtSKSgVB04oqx6i9QLeal_wZSnjV1_PSIM3AubT0WRIxnhl_yYbVpa3i53p3dUW56TtP6_SUZboH6SvLHMz"} `
patchedS , err := jose . ParseJWS ( patchedSBody )
2023-11-24 17:21:01 +00:00
require . NoError ( t , err )
2023-11-29 01:30:28 +00:00
patchedRSKey := [ ] byte ( ` {
"kid" : "U8BmBVbZsNUawvhOomJQPa6uYj1rdxCPQWF_nOLVsc4" ,
"crv" : "P-256" ,
"alg" : "ES256" ,
"kty" : "EC" ,
"x" : "Ym0l3GMS6aHBLo-xe73Kub4kafnOBu_QAfOsx5y-bV0" ,
"y" : "wKijX9Cu67HbK94StPcI18WulgRfIMbP2ZU7gQuf3-M"
} ` )
patchedRSJWK := & jose . JSONWebKey { }
err = json . Unmarshal ( patchedRSKey , patchedRSJWK )
require . NoError ( t , err )
patchedRSBody := ` { "payload":"dGVzdC05MDY3","protected":"eyJhbGciOiJFUzI1NiJ9","signature":"2r_My19oRg7mWf9I5JTkNYp8otfEMz-yXRA8ltZTAKZxyJLurpVEgicmNItu7lfcCrGrTgI3Obye_gSaIyc"} `
patchedRS , err := jose . ParseJWS ( patchedRSBody )
require . NoError ( t , err )
patchedRWithWrongJWK , err := jose . ParseJWS ( patchedRBody )
require . NoError ( t , err )
2023-11-24 17:21:01 +00:00
tests := [ ] struct {
2023-11-29 01:30:28 +00:00
name string
jws * jose . JSONWebSignature
jwk * jose . JSONWebKey
expectedData [ ] byte
expectedSignature string
expectedError error
2023-11-24 17:21:01 +00:00
} {
2023-11-29 01:30:28 +00:00
{ "ok/patched-r" , patchedR , patchedRJWK , [ ] byte ( ` test-1105 ` ) , ` AK0D2CmH5Xyp5YASqg3lrCR9kyeohwJ6Lu7Bc15ZmA-AK16i32LqqLVhESq52tsH84dKbu1EljtoM5TqkSvaqg ` , nil } ,
{ "ok/patched-s" , patchedS , patchedSJWK , [ ] byte ( ` test-66 ` ) , ` krtSKSgVB04oqx6i9QLeal_wZSnjV1_PSIM3AubT0WQASMZ4Zf8mG1aWt4ud6d3VFuek7T-v0lGW6B-kryxzMw ` , nil } ,
{ "ok/patched-rs" , patchedRS , patchedRSJWK , [ ] byte ( ` test-9067 ` ) , ` ANq_zMtfaEYO5ln_SOSU5DWKfKLXxDM_sl0QPJbWUwAApnHIku6ulUSCJyY0i27uV9wKsatOAjc5vJ7-BJojJw ` , nil } ,
2023-12-13 00:36:48 +00:00
{ "fail/patched-r-wrong-jwk" , patchedRWithWrongJWK , patchedRSJWK , nil , ` rQPYKYflfKnlgBKqDeWsJH2TJ6iHAnou7sFzXlmYD4ArXqLfYuqotWERKrna2wfzh0pu7USWO2gzlOqRK9qq ` , errors . New ( "go-jose/go-jose: error in cryptographic primitive" ) } ,
2023-11-24 17:21:01 +00:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2023-11-29 01:30:28 +00:00
expectedSignature , decodeErr := base64 . RawURLEncoding . DecodeString ( tt . expectedSignature )
require . NoError ( t , decodeErr )
data , err := retryVerificationWithPatchedSignatures ( tt . jws , tt . jwk )
if tt . expectedError != nil {
tassert . EqualError ( t , err , tt . expectedError . Error ( ) )
tassert . Equal ( t , expectedSignature , tt . jws . Signatures [ 0 ] . Signature )
tassert . Empty ( t , data )
return
}
2023-11-24 17:21:01 +00:00
tassert . NoError ( t , err )
2023-11-29 01:30:28 +00:00
tassert . Len ( t , tt . jws . Signatures [ 0 ] . Signature , 64 )
tassert . Equal ( t , expectedSignature , tt . jws . Signatures [ 0 ] . Signature )
tassert . Equal ( t , tt . expectedData , data )
2023-11-24 17:21:01 +00:00
} )
}
}