2019-03-05 08:07:13 +00:00
package api
import (
"bytes"
2019-11-06 21:33:23 +00:00
"context"
2019-03-05 08:07:13 +00:00
"crypto/tls"
"crypto/x509"
"encoding/json"
2021-11-12 23:46:34 +00:00
"io"
2019-03-05 08:07:13 +00:00
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/pkg/errors"
2022-03-18 13:25:34 +00:00
2019-03-05 08:07:13 +00:00
"github.com/smallstep/assert"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner"
2019-12-18 22:44:08 +00:00
"github.com/smallstep/certificates/errs"
2019-03-05 08:07:13 +00:00
"github.com/smallstep/certificates/logging"
)
func TestRevokeRequestValidate ( t * testing . T ) {
type test struct {
rr * RevokeRequest
2019-12-18 22:44:08 +00:00
err * errs . Error
2019-03-05 08:07:13 +00:00
}
tests := map [ string ] test {
"error/missing serial" : {
rr : & RevokeRequest { } ,
2019-12-18 22:44:08 +00:00
err : & errs . Error { Err : errors . New ( "missing serial" ) , Status : http . StatusBadRequest } ,
2019-03-05 08:07:13 +00:00
} ,
2022-08-09 18:04:00 +00:00
"error/bad sn" : {
rr : & RevokeRequest { Serial : "sn" } ,
err : & errs . Error { Err : errors . New ( "'sn' is not a valid serial number - use a base 10 representation or add a prefix indicating the base" ) , Status : http . StatusBadRequest } ,
} ,
2019-03-05 08:07:13 +00:00
"error/bad reasonCode" : {
rr : & RevokeRequest {
2022-08-09 18:04:00 +00:00
Serial : "10" ,
2019-03-05 08:07:13 +00:00
ReasonCode : 15 ,
Passive : true ,
} ,
2019-12-18 22:44:08 +00:00
err : & errs . Error { Err : errors . New ( "reasonCode out of bounds" ) , Status : http . StatusBadRequest } ,
2019-03-05 08:07:13 +00:00
} ,
"error/non-passive not implemented" : {
rr : & RevokeRequest {
2022-08-09 18:04:00 +00:00
Serial : "10" ,
2019-03-05 08:07:13 +00:00
ReasonCode : 8 ,
Passive : false ,
} ,
2019-12-18 22:44:08 +00:00
err : & errs . Error { Err : errors . New ( "non-passive revocation not implemented" ) , Status : http . StatusNotImplemented } ,
2019-03-05 08:07:13 +00:00
} ,
"ok" : {
rr : & RevokeRequest {
2022-08-09 18:04:00 +00:00
Serial : "10" ,
2019-03-05 08:07:13 +00:00
ReasonCode : 9 ,
Passive : true ,
} ,
} ,
}
for name , tc := range tests {
t . Run ( name , func ( t * testing . T ) {
if err := tc . rr . Validate ( ) ; err != nil {
switch v := err . ( type ) {
2019-12-18 22:44:08 +00:00
case * errs . Error :
2019-03-05 08:07:13 +00:00
assert . HasPrefix ( t , v . Error ( ) , tc . err . Error ( ) )
assert . Equals ( t , v . StatusCode ( ) , tc . err . Status )
default :
t . Errorf ( "unexpected error type: %T" , v )
}
} else {
assert . Nil ( t , tc . err )
}
} )
}
}
func Test_caHandler_Revoke ( t * testing . T ) {
type test struct {
input string
auth Authority
tls * tls . ConnectionState
statusCode int
expected [ ] byte
}
tests := map [ string ] func ( * testing . T ) test {
"400/json read error" : func ( t * testing . T ) test {
return test {
input : "{" ,
statusCode : http . StatusBadRequest ,
}
} ,
"400/invalid request body" : func ( t * testing . T ) test {
input , err := json . Marshal ( RevokeRequest { } )
assert . FatalError ( t , err )
return test {
input : string ( input ) ,
statusCode : http . StatusBadRequest ,
}
} ,
"200/ott" : func ( t * testing . T ) test {
input , err := json . Marshal ( RevokeRequest {
2022-08-09 18:04:00 +00:00
Serial : "10" ,
2019-03-05 08:07:13 +00:00
ReasonCode : 4 ,
Reason : "foo" ,
OTT : "valid" ,
Passive : true ,
} )
assert . FatalError ( t , err )
return test {
input : string ( input ) ,
statusCode : http . StatusOK ,
auth : & mockAuthority {
2022-05-05 00:35:34 +00:00
authorize : func ( ctx context . Context , ott string ) ( [ ] provisioner . SignOption , error ) {
2019-11-06 21:33:23 +00:00
return nil , nil
} ,
revoke : func ( ctx context . Context , opts * authority . RevokeOptions ) error {
2019-03-05 08:07:13 +00:00
assert . True ( t , opts . PassiveOnly )
assert . False ( t , opts . MTLS )
2022-08-09 18:04:00 +00:00
assert . Equals ( t , opts . Serial , "10" )
2019-03-05 08:07:13 +00:00
assert . Equals ( t , opts . ReasonCode , 4 )
assert . Equals ( t , opts . Reason , "foo" )
return nil
} ,
} ,
expected : [ ] byte ( ` { "status":"ok"} ` ) ,
}
} ,
"400/no OTT and no peer certificate" : func ( t * testing . T ) test {
input , err := json . Marshal ( RevokeRequest {
2022-08-09 18:04:00 +00:00
Serial : "10" ,
2019-03-05 08:07:13 +00:00
ReasonCode : 4 ,
Passive : true ,
} )
assert . FatalError ( t , err )
return test {
input : string ( input ) ,
statusCode : http . StatusBadRequest ,
}
} ,
"200/no ott" : func ( t * testing . T ) test {
cs := & tls . ConnectionState {
PeerCertificates : [ ] * x509 . Certificate { parseCertificate ( certPEM ) } ,
}
input , err := json . Marshal ( RevokeRequest {
Serial : "1404354960355712309" ,
ReasonCode : 4 ,
Reason : "foo" ,
Passive : true ,
} )
assert . FatalError ( t , err )
return test {
input : string ( input ) ,
statusCode : http . StatusOK ,
tls : cs ,
auth : & mockAuthority {
2022-05-05 00:35:34 +00:00
authorize : func ( ctx context . Context , ott string ) ( [ ] provisioner . SignOption , error ) {
2019-11-06 21:33:23 +00:00
return nil , nil
} ,
revoke : func ( ctx context . Context , ri * authority . RevokeOptions ) error {
2019-03-05 08:07:13 +00:00
assert . True ( t , ri . PassiveOnly )
assert . True ( t , ri . MTLS )
assert . Equals ( t , ri . Serial , "1404354960355712309" )
assert . Equals ( t , ri . ReasonCode , 4 )
assert . Equals ( t , ri . Reason , "foo" )
return nil
} ,
loadProvisionerByCertificate : func ( crt * x509 . Certificate ) ( provisioner . Interface , error ) {
return & mockProvisioner {
getID : func ( ) string {
return "mock-provisioner-id"
} ,
} , err
} ,
} ,
expected : [ ] byte ( ` { "status":"ok"} ` ) ,
}
} ,
"500/ott authority.Revoke" : func ( t * testing . T ) test {
input , err := json . Marshal ( RevokeRequest {
2022-08-09 18:04:00 +00:00
Serial : "10" ,
2019-03-05 08:07:13 +00:00
ReasonCode : 4 ,
Reason : "foo" ,
OTT : "valid" ,
Passive : true ,
} )
assert . FatalError ( t , err )
return test {
input : string ( input ) ,
statusCode : http . StatusInternalServerError ,
auth : & mockAuthority {
2022-05-05 00:35:34 +00:00
authorize : func ( ctx context . Context , ott string ) ( [ ] provisioner . SignOption , error ) {
2019-11-06 21:33:23 +00:00
return nil , nil
} ,
revoke : func ( ctx context . Context , opts * authority . RevokeOptions ) error {
2020-01-24 06:04:34 +00:00
return errs . InternalServer ( "force" )
2019-03-05 08:07:13 +00:00
} ,
} ,
}
} ,
"403/ott authority.Revoke" : func ( t * testing . T ) test {
input , err := json . Marshal ( RevokeRequest {
2022-08-09 18:04:00 +00:00
Serial : "10" ,
2019-03-05 08:07:13 +00:00
ReasonCode : 4 ,
Reason : "foo" ,
OTT : "valid" ,
Passive : true ,
} )
assert . FatalError ( t , err )
return test {
input : string ( input ) ,
statusCode : http . StatusForbidden ,
auth : & mockAuthority {
2022-05-05 00:35:34 +00:00
authorize : func ( ctx context . Context , ott string ) ( [ ] provisioner . SignOption , error ) {
2019-11-06 21:33:23 +00:00
return nil , nil
} ,
revoke : func ( ctx context . Context , opts * authority . RevokeOptions ) error {
2019-03-05 08:07:13 +00:00
return errors . New ( "force" )
} ,
} ,
}
} ,
}
for name , _tc := range tests {
tc := _tc ( t )
t . Run ( name , func ( t * testing . T ) {
2022-04-27 17:38:53 +00:00
mockMustAuthority ( t , tc . auth )
2019-03-05 08:07:13 +00:00
req := httptest . NewRequest ( "POST" , "http://example.com/revoke" , strings . NewReader ( tc . input ) )
if tc . tls != nil {
req . TLS = tc . tls
}
w := httptest . NewRecorder ( )
2022-04-27 17:38:53 +00:00
Revoke ( logging . NewResponseLogger ( w ) , req )
2019-03-05 08:07:13 +00:00
res := w . Result ( )
assert . Equals ( t , tc . statusCode , res . StatusCode )
2021-11-12 23:46:34 +00:00
body , err := io . ReadAll ( res . Body )
2019-03-05 08:07:13 +00:00
res . Body . Close ( )
assert . FatalError ( t , err )
if tc . statusCode < http . StatusBadRequest {
if ! bytes . Equal ( bytes . TrimSpace ( body ) , tc . expected ) {
t . Errorf ( "caHandler.Root Body = %s, wants %s" , body , tc . expected )
}
}
} )
}
}