1 |
|
2 |
|
3 | const { EOL } = require('os')
|
4 |
|
5 | const errors = require('../errors')
|
6 |
|
7 | const { createPublicKey } = require('./key_object')
|
8 | const base64url = require('./base64url')
|
9 | const asn1 = require('./asn1')
|
10 | const computePrimes = require('./rsa_primes')
|
11 | const { OKP_CURVES, EC_CURVES } = require('../registry')
|
12 |
|
13 | const formatPem = (base64pem, descriptor) => `-----BEGIN ${descriptor} KEY-----${EOL}${(base64pem.match(/.{1,64}/g) || []).join(EOL)}${EOL}-----END ${descriptor} KEY-----`
|
14 |
|
15 | const okpToJWK = {
|
16 | private (crv, keyObject) {
|
17 | const der = keyObject.export({ type: 'pkcs8', format: 'der' })
|
18 | const OneAsymmetricKey = asn1.get('OneAsymmetricKey')
|
19 | const { privateKey: { privateKey: d } } = OneAsymmetricKey.decode(der)
|
20 |
|
21 | return {
|
22 | ...okpToJWK.public(crv, createPublicKey(keyObject)),
|
23 | d: base64url.encodeBuffer(d)
|
24 | }
|
25 | },
|
26 | public (crv, keyObject) {
|
27 | const der = keyObject.export({ type: 'spki', format: 'der' })
|
28 |
|
29 | const PublicKeyInfo = asn1.get('PublicKeyInfo')
|
30 |
|
31 | const { publicKey: { data: x } } = PublicKeyInfo.decode(der)
|
32 |
|
33 | return {
|
34 | kty: 'OKP',
|
35 | crv,
|
36 | x: base64url.encodeBuffer(x)
|
37 | }
|
38 | }
|
39 | }
|
40 |
|
41 | const keyObjectToJWK = {
|
42 | rsa: {
|
43 | private (keyObject) {
|
44 | const der = keyObject.export({ type: 'pkcs8', format: 'der' })
|
45 |
|
46 | const PrivateKeyInfo = asn1.get('PrivateKeyInfo')
|
47 | const RSAPrivateKey = asn1.get('RSAPrivateKey')
|
48 |
|
49 | const { privateKey } = PrivateKeyInfo.decode(der)
|
50 | const { version, n, e, d, p, q, dp, dq, qi } = RSAPrivateKey.decode(privateKey)
|
51 |
|
52 | if (version !== 'two-prime') {
|
53 | throw new errors.JOSENotSupported('Private RSA keys with more than two primes are not supported')
|
54 | }
|
55 |
|
56 | return {
|
57 | kty: 'RSA',
|
58 | n: base64url.encodeBigInt(n),
|
59 | e: base64url.encodeBigInt(e),
|
60 | d: base64url.encodeBigInt(d),
|
61 | p: base64url.encodeBigInt(p),
|
62 | q: base64url.encodeBigInt(q),
|
63 | dp: base64url.encodeBigInt(dp),
|
64 | dq: base64url.encodeBigInt(dq),
|
65 | qi: base64url.encodeBigInt(qi)
|
66 | }
|
67 | },
|
68 | public (keyObject) {
|
69 | const der = keyObject.export({ type: 'spki', format: 'der' })
|
70 |
|
71 | const PublicKeyInfo = asn1.get('PublicKeyInfo')
|
72 | const RSAPublicKey = asn1.get('RSAPublicKey')
|
73 |
|
74 | const { publicKey: { data: publicKey } } = PublicKeyInfo.decode(der)
|
75 | const { n, e } = RSAPublicKey.decode(publicKey)
|
76 |
|
77 | return {
|
78 | kty: 'RSA',
|
79 | n: base64url.encodeBigInt(n),
|
80 | e: base64url.encodeBigInt(e)
|
81 | }
|
82 | }
|
83 | },
|
84 | ec: {
|
85 | private (keyObject) {
|
86 | const der = keyObject.export({ type: 'pkcs8', format: 'der' })
|
87 |
|
88 | const PrivateKeyInfo = asn1.get('PrivateKeyInfo')
|
89 | const ECPrivateKey = asn1.get('ECPrivateKey')
|
90 |
|
91 | const { privateKey, algorithm: { parameters: { value: crv } } } = PrivateKeyInfo.decode(der)
|
92 | const { privateKey: d, publicKey: { data: publicKey } } = ECPrivateKey.decode(privateKey)
|
93 |
|
94 | const x = publicKey.slice(1, ((publicKey.length - 1) / 2) + 1)
|
95 | const y = publicKey.slice(((publicKey.length - 1) / 2) + 1)
|
96 |
|
97 | return {
|
98 | kty: 'EC',
|
99 | crv,
|
100 | d: base64url.encodeBuffer(d),
|
101 | x: base64url.encodeBuffer(x),
|
102 | y: base64url.encodeBuffer(y)
|
103 | }
|
104 | },
|
105 | public (keyObject) {
|
106 | const der = keyObject.export({ type: 'spki', format: 'der' })
|
107 |
|
108 | const PublicKeyInfo = asn1.get('PublicKeyInfo')
|
109 |
|
110 | const { publicKey: { data: publicKey }, algorithm: { parameters: { value: crv } } } = PublicKeyInfo.decode(der)
|
111 |
|
112 | const x = publicKey.slice(1, ((publicKey.length - 1) / 2) + 1)
|
113 | const y = publicKey.slice(((publicKey.length - 1) / 2) + 1)
|
114 |
|
115 | return {
|
116 | kty: 'EC',
|
117 | crv,
|
118 | x: base64url.encodeBuffer(x),
|
119 | y: base64url.encodeBuffer(y)
|
120 | }
|
121 | }
|
122 | },
|
123 | ed25519: {
|
124 | private (keyObject) {
|
125 | return okpToJWK.private('Ed25519', keyObject)
|
126 | },
|
127 | public (keyObject) {
|
128 | return okpToJWK.public('Ed25519', keyObject)
|
129 | }
|
130 | },
|
131 | ed448: {
|
132 | private (keyObject) {
|
133 | return okpToJWK.private('Ed448', keyObject)
|
134 | },
|
135 | public (keyObject) {
|
136 | return okpToJWK.public('Ed448', keyObject)
|
137 | }
|
138 | },
|
139 | x25519: {
|
140 | private (keyObject) {
|
141 | return okpToJWK.private('X25519', keyObject)
|
142 | },
|
143 | public (keyObject) {
|
144 | return okpToJWK.public('X25519', keyObject)
|
145 | }
|
146 | },
|
147 | x448: {
|
148 | private (keyObject) {
|
149 | return okpToJWK.private('X448', keyObject)
|
150 | },
|
151 | public (keyObject) {
|
152 | return okpToJWK.public('X448', keyObject)
|
153 | }
|
154 | }
|
155 | }
|
156 |
|
157 | module.exports.keyObjectToJWK = (keyObject) => {
|
158 | if (keyObject.type === 'private') {
|
159 | return keyObjectToJWK[keyObject.asymmetricKeyType].private(keyObject)
|
160 | }
|
161 |
|
162 | return keyObjectToJWK[keyObject.asymmetricKeyType].public(keyObject)
|
163 | }
|
164 |
|
165 | const concatEcPublicKey = (x, y) => ({
|
166 | unused: 0,
|
167 | data: Buffer.concat([
|
168 | Buffer.alloc(1, 4),
|
169 | base64url.decodeToBuffer(x),
|
170 | base64url.decodeToBuffer(y)
|
171 | ])
|
172 | })
|
173 |
|
174 | const jwkToPem = {
|
175 | RSA: {
|
176 | private (jwk, { calculateMissingRSAPrimes }) {
|
177 | const RSAPrivateKey = asn1.get('RSAPrivateKey')
|
178 |
|
179 | if ('oth' in jwk) {
|
180 | throw new errors.JOSENotSupported('Private RSA keys with more than two primes are not supported')
|
181 | }
|
182 |
|
183 | if (jwk.p || jwk.q || jwk.dp || jwk.dq || jwk.qi) {
|
184 | if (!(jwk.p && jwk.q && jwk.dp && jwk.dq && jwk.qi)) {
|
185 | throw new errors.JWKInvalid('all other private key parameters must be present when any one of them is present')
|
186 | }
|
187 | } else if (calculateMissingRSAPrimes) {
|
188 | jwk = computePrimes(jwk)
|
189 | } else if (!calculateMissingRSAPrimes) {
|
190 | throw new errors.JOSENotSupported('importing private RSA keys without all other private key parameters is not enabled, see documentation and its advisory on how and when its ok to enable it')
|
191 | }
|
192 |
|
193 | return RSAPrivateKey.encode({
|
194 | version: 0,
|
195 | n: BigInt(`0x${base64url.decodeToBuffer(jwk.n).toString('hex')}`),
|
196 | e: BigInt(`0x${base64url.decodeToBuffer(jwk.e).toString('hex')}`),
|
197 | d: BigInt(`0x${base64url.decodeToBuffer(jwk.d).toString('hex')}`),
|
198 | p: BigInt(`0x${base64url.decodeToBuffer(jwk.p).toString('hex')}`),
|
199 | q: BigInt(`0x${base64url.decodeToBuffer(jwk.q).toString('hex')}`),
|
200 | dp: BigInt(`0x${base64url.decodeToBuffer(jwk.dp).toString('hex')}`),
|
201 | dq: BigInt(`0x${base64url.decodeToBuffer(jwk.dq).toString('hex')}`),
|
202 | qi: BigInt(`0x${base64url.decodeToBuffer(jwk.qi).toString('hex')}`)
|
203 | }, 'pem', { label: 'RSA PRIVATE KEY' })
|
204 | },
|
205 | public (jwk) {
|
206 | const RSAPublicKey = asn1.get('RSAPublicKey')
|
207 |
|
208 | return RSAPublicKey.encode({
|
209 | version: 0,
|
210 | n: BigInt(`0x${base64url.decodeToBuffer(jwk.n).toString('hex')}`),
|
211 | e: BigInt(`0x${base64url.decodeToBuffer(jwk.e).toString('hex')}`)
|
212 | }, 'pem', { label: 'RSA PUBLIC KEY' })
|
213 | }
|
214 | },
|
215 | EC: {
|
216 | private (jwk) {
|
217 | const ECPrivateKey = asn1.get('ECPrivateKey')
|
218 |
|
219 | return ECPrivateKey.encode({
|
220 | version: 1,
|
221 | privateKey: base64url.decodeToBuffer(jwk.d),
|
222 | parameters: { type: 'namedCurve', value: jwk.crv },
|
223 | publicKey: concatEcPublicKey(jwk.x, jwk.y)
|
224 | }, 'pem', { label: 'EC PRIVATE KEY' })
|
225 | },
|
226 | public (jwk) {
|
227 | const PublicKeyInfo = asn1.get('PublicKeyInfo')
|
228 |
|
229 | return PublicKeyInfo.encode({
|
230 | algorithm: {
|
231 | algorithm: 'ecPublicKey',
|
232 | parameters: { type: 'namedCurve', value: jwk.crv }
|
233 | },
|
234 | publicKey: concatEcPublicKey(jwk.x, jwk.y)
|
235 | }, 'pem', { label: 'PUBLIC KEY' })
|
236 | }
|
237 | },
|
238 | OKP: {
|
239 | private (jwk) {
|
240 | const OneAsymmetricKey = asn1.get('OneAsymmetricKey')
|
241 |
|
242 | const b64 = OneAsymmetricKey.encode({
|
243 | version: 0,
|
244 | privateKey: { privateKey: base64url.decodeToBuffer(jwk.d) },
|
245 | algorithm: { algorithm: jwk.crv }
|
246 | }, 'der')
|
247 |
|
248 |
|
249 | b64.write('04', 12, 1, 'hex')
|
250 |
|
251 | return formatPem(b64.toString('base64'), 'PRIVATE')
|
252 | },
|
253 | public (jwk) {
|
254 | const PublicKeyInfo = asn1.get('PublicKeyInfo')
|
255 |
|
256 | return PublicKeyInfo.encode({
|
257 | algorithm: { algorithm: jwk.crv },
|
258 | publicKey: {
|
259 | unused: 0,
|
260 | data: base64url.decodeToBuffer(jwk.x)
|
261 | }
|
262 | }, 'pem', { label: 'PUBLIC KEY' })
|
263 | }
|
264 | }
|
265 | }
|
266 |
|
267 | module.exports.jwkToPem = (jwk, { calculateMissingRSAPrimes = false } = {}) => {
|
268 | switch (jwk.kty) {
|
269 | case 'EC':
|
270 | if (!EC_CURVES.has(jwk.crv)) {
|
271 | throw new errors.JOSENotSupported(`unsupported EC key curve: ${jwk.crv}`)
|
272 | }
|
273 | break
|
274 | case 'OKP':
|
275 | if (!OKP_CURVES.has(jwk.crv)) {
|
276 | throw new errors.JOSENotSupported(`unsupported OKP key curve: ${jwk.crv}`)
|
277 | }
|
278 | break
|
279 | case 'RSA':
|
280 | break
|
281 | default:
|
282 | throw new errors.JOSENotSupported(`unsupported key type: ${jwk.kty}`)
|
283 | }
|
284 |
|
285 | if (jwk.d) {
|
286 | return jwkToPem[jwk.kty].private(jwk, { calculateMissingRSAPrimes })
|
287 | }
|
288 |
|
289 | return jwkToPem[jwk.kty].public(jwk)
|
290 | }
|