@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
1616
17+ import { Crypto } from '@peculiar/webcrypto' ;
1718import x509 from '@peculiar/x509' ;
1819import { generateKeyPairSync } from 'crypto' ;
1920import { generateKeyPair } from '../util/key' ;
@@ -32,39 +33,39 @@ describe('fulcioHandler', () => {
3233 } ) ;
3334
3435 describe ( '#fn' , ( ) => {
36+ const claims = {
37+ sub : 'http://github.com/foo/workflow.yml@refs/heads/main' ,
38+ iss : 'http://foo.com' ,
39+ event_name : 'workflow_dispatch' ,
40+ job_workflow_ref :
41+ 'foo/attest-demo/.github/workflows/oidc.yml@refs/heads/main' ,
42+ job_workflow_sha : 'ba214227977e57973d87219493ad93eeda9c7d6c' ,
43+ ref : 'refs/heads/main' ,
44+ repository : 'foo/attest-demo' ,
45+ repository_id : '792829709' ,
46+ repository_owner : 'foo' ,
47+ repository_owner_id : '398027' ,
48+ repository_visibility : 'public' ,
49+ run_attempt : '3' ,
50+ run_id : '11997537386' ,
51+ runner_environment : 'github-hosted' ,
52+ sha : 'ba214227977e57973d87219493ad93eeda9c7d6c' ,
53+ workflow : 'OIDC' ,
54+ workflow_ref :
55+ 'foo/attest-demo/.github/workflows/oidc.yml@refs/heads/main' ,
56+ workflow_sha : 'ba214227977e57973d87219493ad93eeda9c7d6c' ,
57+ } ;
58+ const jwt = jwtify ( claims ) ;
59+
3560 it ( 'returns a function' , async ( ) => {
3661 const ca = await initializeCA ( keyPair ) ;
3762 const handler = fulcioHandler ( ca ) ;
3863 expect ( handler . fn ) . toBeInstanceOf ( Function ) ;
3964 } ) ;
4065
41- describe ( 'when invoked' , ( ) => {
66+ describe ( 'when invoked w/ a public key ' , ( ) => {
4267 const { publicKey } = generateKeyPairSync ( 'ec' , { namedCurve : 'P-256' } ) ;
4368
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-
6869 const certRequest = {
6970 credentials : {
7071 oidcIdentityToken : jwt ,
@@ -100,9 +101,14 @@ describe('fulcioHandler', () => {
100101 certs . signedCertificateEmbeddedSct . chain . certificates
101102 ) . toHaveLength ( 2 ) ;
102103
103- const { extensions } = new x509 . X509Certificate (
104+ const { extensions, publicKey } = new x509 . X509Certificate (
104105 certs . signedCertificateEmbeddedSct . chain . certificates [ 0 ]
105106 ) ;
107+
108+ // Ensure public key matches input
109+ expect ( publicKey . toString ( 'pem' ) ) . toEqual (
110+ certRequest . publicKeyRequest . publicKey . content . trimEnd ( )
111+ ) ;
106112 expect (
107113 extensions
108114 . filter ( ( e ) => e . type . startsWith ( '1.3.6.1.4.1.57264' ) )
@@ -125,6 +131,69 @@ describe('fulcioHandler', () => {
125131 } ) ;
126132 } ) ;
127133 } ) ;
134+
135+ describe ( 'when invoked w/ a CSR' , ( ) => {
136+ it ( 'returns a certificate chain' , async ( ) => {
137+ const crypto = new Crypto ( ) ;
138+ const kp = await crypto . subtle . generateKey (
139+ { name : 'ecdsa' , namedCurve : 'P-256' } ,
140+ true ,
141+ [ 'sign' , 'verify' ]
142+ ) ;
143+ const csr = await x509 . Pkcs10CertificateRequestGenerator . create (
144+ {
145+ signingAlgorithm : {
146+ name : 'ECDSA' ,
147+ hash : 'SHA-256' ,
148+ } ,
149+ keys : kp ,
150+ } ,
151+ crypto
152+ ) ;
153+
154+ const certRequest = {
155+ credentials : {
156+ oidcIdentityToken : jwt ,
157+ } ,
158+ certificateSigningRequest : csr . toString ( 'pem' ) ,
159+ } ;
160+
161+ const ca = await initializeCA ( keyPair ) ;
162+ const { fn } = fulcioHandler ( ca ) ;
163+
164+ // Make a request
165+ const resp = await fn ( JSON . stringify ( certRequest ) ) ;
166+ expect ( resp . statusCode ) . toBe ( 201 ) ;
167+
168+ // Check the response
169+ const certs = JSON . parse ( resp . response . toString ( ) ) ;
170+ expect ( certs ) . toBeDefined ( ) ;
171+ expect ( certs . signedCertificateEmbeddedSct ) . toBeDefined ( ) ;
172+ expect ( certs . signedCertificateEmbeddedSct . chain ) . toBeDefined ( ) ;
173+ expect (
174+ certs . signedCertificateEmbeddedSct . chain . certificates
175+ ) . toBeDefined ( ) ;
176+ expect (
177+ certs . signedCertificateEmbeddedSct . chain . certificates
178+ ) . toHaveLength ( 2 ) ;
179+
180+ const { extensions, publicKey } = new x509 . X509Certificate (
181+ certs . signedCertificateEmbeddedSct . chain . certificates [ 0 ]
182+ ) ;
183+
184+ // Ensure public key matches the CSR
185+ const expectedKey = await crypto . subtle . exportKey ( 'spki' , kp . publicKey ) ;
186+ expect ( publicKey . toString ( 'base64' ) ) . toEqual (
187+ Buffer . from ( expectedKey ) . toString ( 'base64' )
188+ ) ;
189+
190+ expect (
191+ extensions
192+ . filter ( ( e ) => e . type . startsWith ( '1.3.6.1.4.1.57264' ) )
193+ . map ( ( e ) => e . toString ( 'asn' ) )
194+ ) . toMatchSnapshot ( ) ;
195+ } ) ;
196+ } ) ;
128197 } ) ;
129198} ) ;
130199
0 commit comments