@@ -15,7 +15,7 @@ limitations under the License.
1515*/
1616
1717import x509 from '@peculiar/x509' ;
18- import { generateKeyPairSync } from 'crypto' ;
18+ import { generateKeyPairSync , subtle } from 'crypto' ;
1919import { generateKeyPair } from '../util/key' ;
2020import { CA , initializeCA } from './ca' ;
2121import { fulcioHandler } from './handler' ;
@@ -32,39 +32,39 @@ describe('fulcioHandler', () => {
3232 } ) ;
3333
3434 describe ( '#fn' , ( ) => {
35+ const claims = {
36+ sub : 'http://github.com/foo/workflow.yml@refs/heads/main' ,
37+ iss : 'http://foo.com' ,
38+ event_name : 'workflow_dispatch' ,
39+ job_workflow_ref :
40+ 'foo/attest-demo/.github/workflows/oidc.yml@refs/heads/main' ,
41+ job_workflow_sha : 'ba214227977e57973d87219493ad93eeda9c7d6c' ,
42+ ref : 'refs/heads/main' ,
43+ repository : 'foo/attest-demo' ,
44+ repository_id : '792829709' ,
45+ repository_owner : 'foo' ,
46+ repository_owner_id : '398027' ,
47+ repository_visibility : 'public' ,
48+ run_attempt : '3' ,
49+ run_id : '11997537386' ,
50+ runner_environment : 'github-hosted' ,
51+ sha : 'ba214227977e57973d87219493ad93eeda9c7d6c' ,
52+ workflow : 'OIDC' ,
53+ workflow_ref :
54+ 'foo/attest-demo/.github/workflows/oidc.yml@refs/heads/main' ,
55+ workflow_sha : 'ba214227977e57973d87219493ad93eeda9c7d6c' ,
56+ } ;
57+ const jwt = jwtify ( claims ) ;
58+
3559 it ( 'returns a function' , async ( ) => {
3660 const ca = await initializeCA ( keyPair ) ;
3761 const handler = fulcioHandler ( ca ) ;
3862 expect ( handler . fn ) . toBeInstanceOf ( Function ) ;
3963 } ) ;
4064
41- describe ( 'when invoked' , ( ) => {
65+ describe ( 'when invoked w/ a public key ' , ( ) => {
4266 const { publicKey } = generateKeyPairSync ( 'ec' , { namedCurve : 'P-256' } ) ;
4367
44- const claims = {
45- sub : 'http://github.com/foo/workflow.yml@refs/heads/main' ,
46- iss : 'http://foo.com' ,
47- event_name : 'workflow_dispatch' ,
48- job_workflow_ref :
49- 'foo/attest-demo/.github/workflows/oidc.yml@refs/heads/main' ,
50- job_workflow_sha : 'ba214227977e57973d87219493ad93eeda9c7d6c' ,
51- ref : 'refs/heads/main' ,
52- repository : 'foo/attest-demo' ,
53- repository_id : '792829709' ,
54- repository_owner : 'foo' ,
55- repository_owner_id : '398027' ,
56- repository_visibility : 'public' ,
57- run_attempt : '3' ,
58- run_id : '11997537386' ,
59- runner_environment : 'github-hosted' ,
60- sha : 'ba214227977e57973d87219493ad93eeda9c7d6c' ,
61- workflow : 'OIDC' ,
62- workflow_ref :
63- 'foo/attest-demo/.github/workflows/oidc.yml@refs/heads/main' ,
64- workflow_sha : 'ba214227977e57973d87219493ad93eeda9c7d6c' ,
65- } ;
66- const jwt = jwtify ( claims ) ;
67-
6868 const certRequest = {
6969 credentials : {
7070 oidcIdentityToken : jwt ,
@@ -100,9 +100,14 @@ describe('fulcioHandler', () => {
100100 certs . signedCertificateEmbeddedSct . chain . certificates
101101 ) . toHaveLength ( 2 ) ;
102102
103- const { extensions } = new x509 . X509Certificate (
103+ const { extensions, publicKey } = new x509 . X509Certificate (
104104 certs . signedCertificateEmbeddedSct . chain . certificates [ 0 ]
105105 ) ;
106+
107+ // Ensure public key matches input
108+ expect ( publicKey . toString ( 'pem' ) ) . toEqual (
109+ certRequest . publicKeyRequest . publicKey . content . trimEnd ( )
110+ ) ;
106111 expect (
107112 extensions
108113 . filter ( ( e ) => e . type . startsWith ( '1.3.6.1.4.1.57264' ) )
@@ -125,6 +130,66 @@ describe('fulcioHandler', () => {
125130 } ) ;
126131 } ) ;
127132 } ) ;
133+
134+ describe ( 'when invoked w/ a CSR' , ( ) => {
135+ it ( 'returns a certificate chain' , async ( ) => {
136+ const kp = await subtle . generateKey (
137+ { name : 'ecdsa' , namedCurve : 'P-256' } ,
138+ true ,
139+ [ 'sign' , 'verify' ]
140+ ) ;
141+
142+ const csr = await x509 . Pkcs10CertificateRequestGenerator . create ( {
143+ signingAlgorithm : {
144+ name : 'ECDSA' ,
145+ hash : 'SHA-256' ,
146+ } ,
147+ keys : kp ,
148+ } ) ;
149+
150+ const certRequest = {
151+ credentials : {
152+ oidcIdentityToken : jwt ,
153+ } ,
154+ certificateSigningRequest : csr . toString ( 'pem' ) ,
155+ } ;
156+
157+ const ca = await initializeCA ( keyPair ) ;
158+ const { fn } = fulcioHandler ( ca ) ;
159+
160+ // Make a request
161+ const resp = await fn ( JSON . stringify ( certRequest ) ) ;
162+ expect ( resp . statusCode ) . toBe ( 201 ) ;
163+
164+ // Check the response
165+ const certs = JSON . parse ( resp . response . toString ( ) ) ;
166+ expect ( certs ) . toBeDefined ( ) ;
167+ expect ( certs . signedCertificateEmbeddedSct ) . toBeDefined ( ) ;
168+ expect ( certs . signedCertificateEmbeddedSct . chain ) . toBeDefined ( ) ;
169+ expect (
170+ certs . signedCertificateEmbeddedSct . chain . certificates
171+ ) . toBeDefined ( ) ;
172+ expect (
173+ certs . signedCertificateEmbeddedSct . chain . certificates
174+ ) . toHaveLength ( 2 ) ;
175+
176+ const { extensions, publicKey } = new x509 . X509Certificate (
177+ certs . signedCertificateEmbeddedSct . chain . certificates [ 0 ]
178+ ) ;
179+
180+ // Ensure public key matches the CSR
181+ const expectedKey = await crypto . subtle . exportKey ( 'spki' , kp . publicKey ) ;
182+ expect ( publicKey . toString ( 'base64' ) ) . toEqual (
183+ Buffer . from ( expectedKey ) . toString ( 'base64' )
184+ ) ;
185+
186+ expect (
187+ extensions
188+ . filter ( ( e ) => e . type . startsWith ( '1.3.6.1.4.1.57264' ) )
189+ . map ( ( e ) => e . toString ( 'asn' ) )
190+ ) . toMatchSnapshot ( ) ;
191+ } ) ;
192+ } ) ;
128193 } ) ;
129194} ) ;
130195
0 commit comments