UNPKG

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