1
- import { BmpString , Integer } from 'asn1js' ;
2
- import { webcrypto } from 'crypto' ;
3
- import {
4
- AttributeTypeAndValue ,
5
- AuthenticatedSafe ,
6
- CertBag ,
7
- Certificate ,
8
- CryptoEngine ,
9
- PFX ,
10
- PKCS8ShroudedKeyBag ,
11
- PrivateKeyInfo ,
12
- SafeBag ,
13
- SafeContents ,
14
- setEngine ,
15
- } from 'pkijs' ;
16
-
17
- const crypto = new CryptoEngine ( { name : 'node-webcrypto' , crypto : webcrypto as Crypto } ) ;
18
- setEngine ( 'node-webcrypto' , crypto ) ; // We need to do this, because there is a bug in pkijs (https://github.com/PeculiarVentures/PKI.js/issues/379)
1
+ import forge from 'node-forge' ;
2
+ const { pki, md } = forge ;
19
3
20
4
export const generateCertificate = async ( commonName : string , days ?: number ) => {
21
- const algorithm = crypto . getAlgorithmParameters ( 'RSA-PSS' , 'generateKey' ) ;
22
- const { privateKey, publicKey } = await crypto . generateKey (
23
- algorithm . algorithm as EcKeyAlgorithm ,
24
- true ,
25
- algorithm . usages
26
- ) ;
5
+ const keyPair = await new Promise < forge . pki . rsa . KeyPair > ( ( res , rej ) => {
6
+ pki . rsa . generateKeyPair ( { bits : 2048 } , ( err , keyPair ) => ( err ? rej ( err ) : res ( keyPair ) ) ) ;
7
+ } ) ;
8
+ const cert = pki . createCertificate ( ) ;
27
9
28
- const cert = new Certificate ( ) ;
10
+ cert . publicKey = keyPair . publicKey ;
29
11
cert . version = 2 ;
30
- cert . serialNumber = new Integer ( { value : Date . now ( ) } ) ;
31
- cert . notBefore . value = new Date ( ) ;
32
- cert . notAfter . value = new Date ( ) ;
33
- cert . notAfter . value . setDate ( cert . notBefore . value . getDate ( ) + ( days || 365 ) ) ;
12
+ cert . serialNumber = Date . now ( ) . toString ( 10 ) ;
13
+ cert . validity . notBefore = new Date ( ) ;
14
+ cert . validity . notAfter = new Date ( ) ;
15
+ cert . validity . notAfter . setDate ( cert . validity . notBefore . getDate ( ) + ( days || 365 ) ) ;
34
16
35
- cert . issuer . typesAndValues . push (
36
- new AttributeTypeAndValue ( {
37
- type : '2.5.4.3' , // Common name
38
- value : new BmpString ( { value : commonName } ) ,
39
- } )
40
- ) ;
41
- cert . subject . typesAndValues . push (
42
- new AttributeTypeAndValue ( {
43
- type : '2.5.4.3' , // Common name
44
- value : new BmpString ( { value : commonName } ) ,
45
- } )
46
- ) ;
17
+ const attributes = [
18
+ {
19
+ name : 'commonName' ,
20
+ value : commonName ,
21
+ } ,
22
+ ] ;
23
+ cert . setSubject ( attributes ) ;
24
+ cert . setIssuer ( attributes ) ;
47
25
48
- await cert . subjectPublicKeyInfo . importKey ( publicKey , crypto ) ;
49
- await cert . sign ( privateKey , 'SHA-256' , crypto ) ;
26
+ cert . sign ( keyPair . privateKey , md . sha256 . create ( ) ) ;
50
27
51
28
return {
52
- certificate : cert . toSchema ( ) . toBER ( false ) ,
53
- privateKey : await crypto . exportKey ( 'pkcs8' , privateKey ) ,
29
+ certificate : pki . certificateToPem ( cert ) ,
30
+ privateKey : pki . privateKeyToPem ( keyPair . privateKey ) ,
54
31
} ;
55
32
} ;
56
33
57
- export const certificateFingerprint = async ( certificateBuffer : ArrayBuffer , hashAlgorithm ?: 'SHA-256' | 'SHA-1' ) => {
58
- const certificate = await Certificate . fromBER ( certificateBuffer ) ;
59
- const hash = await crypto . digest (
60
- hashAlgorithm || 'SHA-256 ',
61
- certificate . subjectPublicKeyInfo . toSchema ( ) . toBER ( false )
62
- ) ;
63
- return Buffer . from ( hash ) . toString ( 'hex' ) ;
34
+ export const certificateFingerprint = ( certificatePem : string , hashAlgorithm ?: 'SHA-256' | 'SHA-1' ) => {
35
+ const cert = pki . certificateFromPem ( certificatePem ) ;
36
+ return pki . getPublicKeyFingerprint ( cert . publicKey , {
37
+ type : 'SubjectPublicKeyInfo ',
38
+ md : hashAlgorithm === 'SHA-1' ? md . sha1 . create ( ) : md . sha256 . create ( ) ,
39
+ encoding : 'hex' ,
40
+ } ) ;
64
41
} ;
65
42
66
- export const certificateHasExpired = async ( certificateBuffer : ArrayBuffer ) => {
67
- const certificate = await Certificate . fromBER ( certificateBuffer ) ;
68
- return certificate . notAfter . value < new Date ( ) ;
43
+ export const certificateHasExpired = ( certificatePem : string ) => {
44
+ const cert = pki . certificateFromPem ( certificatePem ) ;
45
+ return cert . validity . notAfter < new Date ( ) ;
69
46
} ;
70
47
71
- export const createPkcs12Container = async ( cert : ArrayBuffer , key : ArrayBuffer , password ?: string ) => {
72
- const encodedPassword = new TextEncoder ( ) . encode ( password || '' ) . buffer ;
73
-
74
- const pkcs12 = new PFX ( {
75
- parsedValue : {
76
- integrityMode : 0 , // Password-Based Integrity Mode
77
- authenticatedSafe : new AuthenticatedSafe ( {
78
- parsedValue : {
79
- safeContents : [
80
- {
81
- privacyMode : 0 , // 0 - No privacy mode
82
- value : new SafeContents ( {
83
- safeBags : [
84
- new SafeBag ( {
85
- bagId : '1.2.840.113549.1.12.10.1.2' , // Shrouded Private Key Bag
86
- bagValue : new PKCS8ShroudedKeyBag ( {
87
- parsedValue : PrivateKeyInfo . fromBER ( key ) ,
88
- } ) ,
89
- } ) ,
90
- ] ,
91
- } ) ,
92
- } ,
93
- {
94
- privacyMode : 1 , // 1 - Password based privacy mode,
95
- value : new SafeContents ( {
96
- safeBags : [
97
- new SafeBag ( {
98
- bagId : '1.2.840.113549.1.12.10.1.3' , // Certificate bag
99
- bagValue : new CertBag ( {
100
- parsedValue : Certificate . fromBER ( cert ) ,
101
- } ) ,
102
- } ) ,
103
- ] ,
104
- } ) ,
105
- } ,
106
- ] ,
107
- } ,
108
- } ) ,
109
- } ,
110
- } ) ;
111
-
112
- if ( ! pkcs12 . parsedValue ?. authenticatedSafe )
113
- throw new Error ( 'Broken certificate container: pkcs12.parsedValue.authenticatedSafe is empty' ) ;
114
-
115
- await pkcs12 . parsedValue . authenticatedSafe . parsedValue . safeContents [ 0 ] . value . safeBags [ 0 ] . bagValue . makeInternalValues (
116
- {
117
- password : encodedPassword ,
118
- contentEncryptionAlgorithm : {
119
- name : 'AES-CBC' , // OpenSSL can only handle AES-CBC (https://github.com/PeculiarVentures/PKI.js/blob/469c403d102ee5149e8eb9ad19754c9696ed7c55/test/pkcs12SimpleExample.ts#L438)
120
- length : 128 ,
121
- } ,
122
- hmacHashAlgorithm : 'SHA-1' , // OpenSSL can only handle SHA-1 (https://github.com/PeculiarVentures/PKI.js/blob/469c403d102ee5149e8eb9ad19754c9696ed7c55/test/pkcs12SimpleExample.ts#L441)
123
- iterationCount : 100000 ,
124
- } ,
125
- crypto
126
- ) ;
127
-
128
- pkcs12 . parsedValue . authenticatedSafe . makeInternalValues (
129
- {
130
- safeContents : [
131
- {
132
- // Private key contents are encrypted differently, so this needs to be empty.
133
- } ,
134
- {
135
- password : encodedPassword ,
136
- contentEncryptionAlgorithm : {
137
- name : 'AES-CBC' ,
138
- length : 128 ,
139
- } ,
140
- hmacHashAlgorithm : 'SHA-1' ,
141
- iterationCount : 100000 ,
142
- } ,
143
- ] ,
144
- } ,
145
- crypto
48
+ export const createPkcs12Container = (
49
+ certPem : string ,
50
+ keyPem : string ,
51
+ password ?: string ,
52
+ algorithm ?: 'aes256' | '3des'
53
+ ) => {
54
+ const p12 = forge . pkcs12 . toPkcs12Asn1 (
55
+ pki . privateKeyFromPem ( keyPem ) ,
56
+ pki . certificateFromPem ( certPem ) ,
57
+ password || '' ,
58
+ { algorithm : algorithm || '3des' } // Apparently any sane algorithm is not supported by the typical ingestors (like go), so we default to 3des
146
59
) ;
147
60
148
- await pkcs12 . makeInternalValues (
149
- {
150
- password : encodedPassword ,
151
- iterations : 100000 ,
152
- pbkdf2HashAlgorithm : 'SHA-256' ,
153
- hmacHashAlgorithm : 'SHA-256' ,
154
- } ,
155
- crypto
156
- ) ;
157
- return pkcs12 . toSchema ( ) . toBER ( ) ;
61
+ return forge . asn1 . toDer ( p12 ) ;
158
62
} ;
159
63
160
64
export const arrayBufferToPem = ( buffer : ArrayBuffer , tag : 'CERTIFICATE' | 'PRIVATE KEY' | 'PUBLIC KEY' ) => {
@@ -169,3 +73,12 @@ export const pemToArrayBuffer = (pem: string) => {
169
73
. replace ( / \n / g, '' ) ;
170
74
return Uint8Array . from ( Buffer . from ( base64 , 'base64' ) ) . buffer ;
171
75
} ;
76
+
77
+ export const asn1ValueToDer = ( asn1 : forge . asn1 . Asn1 ) => {
78
+ if ( typeof asn1 . value === 'string' || ! asn1 . constructed ) return forge . asn1 . toDer ( asn1 ) ;
79
+
80
+ return asn1 . value . reduce ( ( acc , cur ) => {
81
+ acc . putBuffer ( forge . asn1 . toDer ( cur ) ) ;
82
+ return acc ;
83
+ } , forge . util . createBuffer ( ) ) ;
84
+ } ;
0 commit comments