@@ -7,7 +7,10 @@ import (
77 "crypto/x509"
88 "crypto/x509/pkix"
99 "encoding/base64"
10+ "io"
1011 "math/big"
12+ "net/http"
13+ "net/http/httptest"
1114 "strings"
1215 "testing"
1316 "time"
@@ -126,7 +129,7 @@ func TestValidateChainDepth(t *testing.T) {
126129 }
127130}
128131
129- func newTestCertificate (t * testing.T , serial int64 , name string , parent * x509.Certificate , parentKey * ecdsa.PrivateKey , ca bool ) (cert * x509.Certificate , key * ecdsa.PrivateKey , encoded string ) {
132+ func newTestCertificate (t * testing.T , serial int64 , name string , parent * x509.Certificate , parentKey * ecdsa.PrivateKey , ca bool , mutators ... func ( template * x509. Certificate ) ) (cert * x509.Certificate , key * ecdsa.PrivateKey , encoded string ) {
130133 t .Helper ()
131134
132135 key , err := ecdsa .GenerateKey (elliptic .P256 (), rand .Reader )
@@ -148,6 +151,10 @@ func newTestCertificate(t *testing.T, serial int64, name string, parent *x509.Ce
148151 template .ExtKeyUsage = []x509.ExtKeyUsage {x509 .ExtKeyUsageAny }
149152 }
150153
154+ for _ , mutator := range mutators {
155+ mutator (template )
156+ }
157+
151158 signer , signerKey := template , key
152159
153160 if parent != nil {
@@ -162,3 +169,132 @@ func newTestCertificate(t *testing.T, serial int64, name string, parent *x509.Ce
162169
163170 return cert , key , base64 .StdEncoding .EncodeToString (der )
164171}
172+
173+ // unreachableCRL points the certificate at a distribution point which cannot be reached, so that its revocation status
174+ // cannot be determined.
175+ func unreachableCRL (template * x509.Certificate ) {
176+ template .CRLDistributionPoints = []string {"http://127.0.0.1:1/crl" }
177+ }
178+
179+ // newRevocationListServer serves a CRL signed by the given issuer which lists the given serials as revoked.
180+ func newRevocationListServer (t * testing.T , issuer * x509.Certificate , issuerKey * ecdsa.PrivateKey , revoked ... int64 ) * httptest.Server {
181+ t .Helper ()
182+
183+ entries := make ([]x509.RevocationListEntry , len (revoked ))
184+
185+ for i , serial := range revoked {
186+ entries [i ] = x509.RevocationListEntry {
187+ SerialNumber : big .NewInt (serial ),
188+ RevocationTime : time .Now ().Add (- time .Hour ),
189+ }
190+ }
191+
192+ der , err := x509 .CreateRevocationList (rand .Reader , & x509.RevocationList {
193+ Number : big .NewInt (1 ),
194+ ThisUpdate : time .Now ().Add (- time .Hour ),
195+ NextUpdate : time .Now ().Add (time .Hour ),
196+ RevokedCertificateEntries : entries ,
197+ }, issuer , issuerKey )
198+ require .NoError (t , err )
199+
200+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
201+ w .Header ().Set ("Content-Type" , "application/pkix-crl" )
202+
203+ _ , _ = w .Write (der )
204+ }))
205+
206+ t .Cleanup (server .Close )
207+
208+ return server
209+ }
210+
211+ func TestValidateChainRevocation (t * testing.T ) {
212+ root , rootKey , rootEncoded := newTestCertificate (t , 20 , "root" , nil , nil , true )
213+ inter , interKey , interEncoded := newTestCertificate (t , 21 , "intermediate" , root , rootKey , true )
214+
215+ // A certificate whose status cannot be determined is treated as not revoked, as the distribution point is not
216+ // reliably reachable at the moment a blob is decoded.
217+ _ , _ , leafUnknown := newTestCertificate (t , 22 , "leaf unknown" , inter , interKey , false , unreachableCRL )
218+
219+ leafCRL := newRevocationListServer (t , inter , interKey , 23 )
220+
221+ _ , _ , leafRevoked := newTestCertificate (t , 23 , "leaf revoked" , inter , interKey , false , func (template * x509.Certificate ) {
222+ template .CRLDistributionPoints = []string {leafCRL .URL }
223+ })
224+
225+ interCRL := newRevocationListServer (t , root , rootKey , 25 )
226+
227+ interRevoked , interRevokedKey , interRevokedEncoded := newTestCertificate (t , 25 , "intermediate revoked" , root , rootKey , true , func (template * x509.Certificate ) {
228+ template .CRLDistributionPoints = []string {interCRL .URL }
229+ })
230+
231+ _ , _ , leafOfRevoked := newTestCertificate (t , 26 , "leaf of revoked" , interRevoked , interRevokedKey , false )
232+ _ , _ , leafOK := newTestCertificate (t , 24 , "leaf" , inter , interKey , false )
233+
234+ testCases := []struct {
235+ name string
236+ chain []any
237+ valid bool
238+ err error
239+ }{
240+ {
241+ name : "ShouldPermitLeafWithUnknownStatus" ,
242+ chain : []any {leafUnknown , interEncoded },
243+ valid : true ,
244+ },
245+ {
246+ name : "ShouldPermitDeterminableChain" ,
247+ chain : []any {leafOK , interEncoded },
248+ valid : true ,
249+ },
250+ {
251+ name : "ShouldRejectRevokedLeaf" ,
252+ chain : []any {leafRevoked , interEncoded },
253+ valid : false ,
254+ err : errLeafCertRevoked ,
255+ },
256+ {
257+ name : "ShouldRejectRevokedIntermediate" ,
258+ chain : []any {leafOfRevoked , interRevokedEncoded },
259+ valid : false ,
260+ err : errIntermediateCertRevoked ,
261+ },
262+ }
263+
264+ for _ , tc := range testCases {
265+ t .Run (tc .name , func (t * testing.T ) {
266+ valid , err := validateChain (rootEncoded , tc .chain )
267+
268+ assert .Equal (t , tc .valid , valid )
269+
270+ if tc .err != nil {
271+ assert .Equal (t , tc .err , err )
272+ } else {
273+ assert .NoError (t , err )
274+ }
275+ })
276+ }
277+ }
278+
279+ type recordingReadCloser struct {
280+ io.Reader
281+ closed bool
282+ }
283+
284+ func (r * recordingReadCloser ) Close () error {
285+ r .closed = true
286+
287+ return nil
288+ }
289+
290+ func TestDecodeDoesNotCloseTheReader (t * testing.T ) {
291+ decoder , err := NewDecoder ()
292+ require .NoError (t , err )
293+
294+ rc := & recordingReadCloser {Reader : strings .NewReader ("not a jwt" )}
295+
296+ _ , err = decoder .Decode (rc )
297+ require .Error (t , err )
298+
299+ assert .False (t , rc .closed , "Decode must leave closing the reader to the caller" )
300+ }
0 commit comments