UNPKG

13.5 kBJavaScriptView Raw
1/* global BigInt */
2
3const { keyObjectSupported } = require('./runtime_support')
4
5let createPublicKey
6let createPrivateKey
7let createSecretKey
8let KeyObject
9let asInput
10
11if (keyObjectSupported) {
12 ({ createPublicKey, createPrivateKey, createSecretKey, KeyObject } = require('crypto'))
13 asInput = (input) => input
14} else {
15 const { EOL } = require('os')
16
17 const errors = require('../errors')
18 const isObject = require('./is_object')
19 const asn1 = require('./asn1')
20 const toInput = Symbol('toInput')
21
22 const namedCurve = Symbol('namedCurve')
23
24 asInput = (keyObject, needsPublic) => {
25 if (keyObject instanceof KeyObject) {
26 return keyObject[toInput](needsPublic)
27 }
28
29 return createSecretKey(keyObject)[toInput](needsPublic)
30 }
31
32 const pemToDer = pem => Buffer.from(pem.replace(/(?:-----(?:BEGIN|END)(?: (?:RSA|EC))? (?:PRIVATE|PUBLIC) KEY-----|\s)/g, ''), 'base64')
33 const derToPem = (der, label) => `-----BEGIN ${label}-----${EOL}${(der.toString('base64').match(/.{1,64}/g) || []).join(EOL)}${EOL}-----END ${label}-----`
34 const unsupported = (input) => {
35 const label = typeof input === 'string' ? input : `OID ${input.join('.')}`
36 throw new errors.JOSENotSupported(`${label} is not supported in your Node.js runtime version`)
37 }
38
39 KeyObject = class KeyObject {
40 export ({ cipher, passphrase, type, format } = {}) {
41 if (this._type === 'secret') {
42 return this._buffer
43 }
44
45 if (this._type === 'public') {
46 if (this.asymmetricKeyType === 'rsa') {
47 switch (type) {
48 case 'pkcs1':
49 if (format === 'pem') {
50 return this._pem
51 }
52
53 return pemToDer(this._pem)
54 case 'spki': {
55 const PublicKeyInfo = asn1.get('PublicKeyInfo')
56 const pem = PublicKeyInfo.encode({
57 algorithm: {
58 algorithm: 'rsaEncryption',
59 parameters: { type: 'null' }
60 },
61 publicKey: {
62 unused: 0,
63 data: pemToDer(this._pem)
64 }
65 }, 'pem', { label: 'PUBLIC KEY' })
66
67 return format === 'pem' ? pem : pemToDer(pem)
68 }
69 default:
70 throw new TypeError(`The value ${type} is invalid for option "type"`)
71 }
72 }
73
74 if (this.asymmetricKeyType === 'ec') {
75 if (type !== 'spki') {
76 throw new TypeError(`The value ${type} is invalid for option "type"`)
77 }
78
79 if (format === 'pem') {
80 return this._pem
81 }
82
83 return pemToDer(this._pem)
84 }
85 }
86
87 if (this._type === 'private') {
88 if (passphrase !== undefined || cipher !== undefined) {
89 throw new errors.JOSENotSupported('encrypted private keys are not supported in your Node.js runtime version')
90 }
91
92 if (type === 'pkcs8') {
93 if (this._pkcs8) {
94 if (format === 'der' && typeof this._pkcs8 === 'string') {
95 return pemToDer(this._pkcs8)
96 }
97
98 if (format === 'pem' && Buffer.isBuffer(this._pkcs8)) {
99 return derToPem(this._pkcs8, 'PRIVATE KEY')
100 }
101
102 return this._pkcs8
103 }
104
105 if (this.asymmetricKeyType === 'rsa') {
106 const parsed = this._asn1
107 const RSAPrivateKey = asn1.get('RSAPrivateKey')
108 const privateKey = RSAPrivateKey.encode(parsed)
109 const PrivateKeyInfo = asn1.get('PrivateKeyInfo')
110 const pkcs8 = PrivateKeyInfo.encode({
111 version: 0,
112 privateKey,
113 algorithm: {
114 algorithm: 'rsaEncryption',
115 parameters: { type: 'null' }
116 }
117 })
118
119 this._pkcs8 = pkcs8
120
121 return this.export({ type, format })
122 }
123
124 if (this.asymmetricKeyType === 'ec') {
125 const parsed = this._asn1
126 const ECPrivateKey = asn1.get('ECPrivateKey')
127 const privateKey = ECPrivateKey.encode({
128 version: parsed.version,
129 privateKey: parsed.privateKey,
130 publicKey: parsed.publicKey
131 })
132 const PrivateKeyInfo = asn1.get('PrivateKeyInfo')
133 const pkcs8 = PrivateKeyInfo.encode({
134 version: 0,
135 privateKey,
136 algorithm: {
137 algorithm: 'ecPublicKey',
138 parameters: this._asn1.parameters
139 }
140 })
141
142 this._pkcs8 = pkcs8
143
144 return this.export({ type, format })
145 }
146 }
147
148 if (this.asymmetricKeyType === 'rsa' && type === 'pkcs1') {
149 if (format === 'pem') {
150 return this._pem
151 }
152
153 return pemToDer(this._pem)
154 } else if (this.asymmetricKeyType === 'ec' && type === 'sec1') {
155 if (format === 'pem') {
156 return this._pem
157 }
158
159 return pemToDer(this._pem)
160 } else {
161 throw new TypeError(`The value ${type} is invalid for option "type"`)
162 }
163 }
164 }
165
166 get type () {
167 return this._type
168 }
169
170 get asymmetricKeyType () {
171 return this._asymmetricKeyType
172 }
173
174 get symmetricKeySize () {
175 return this._symmetricKeySize
176 }
177
178 [toInput] (needsPublic) {
179 switch (this._type) {
180 case 'secret':
181 return this._buffer
182 case 'public':
183 return this._pem
184 default:
185 if (needsPublic) {
186 if (!('_pub' in this)) {
187 this._pub = createPublicKey(this)
188 }
189
190 return this._pub[toInput](false)
191 }
192
193 return this._pem
194 }
195 }
196 }
197
198 createSecretKey = (buffer) => {
199 if (!Buffer.isBuffer(buffer) || !buffer.length) {
200 throw new TypeError('input must be a non-empty Buffer instance')
201 }
202
203 const keyObject = new KeyObject()
204 keyObject._buffer = Buffer.from(buffer)
205 keyObject._symmetricKeySize = buffer.length
206 keyObject._type = 'secret'
207
208 return keyObject
209 }
210
211 createPublicKey = (input) => {
212 if (input instanceof KeyObject) {
213 if (input.type !== 'private') {
214 throw new TypeError(`Invalid key object type ${input.type}, expected private.`)
215 }
216
217 switch (input.asymmetricKeyType) {
218 case 'ec': {
219 const PublicKeyInfo = asn1.get('PublicKeyInfo')
220 const key = PublicKeyInfo.encode({
221 algorithm: {
222 algorithm: 'ecPublicKey',
223 parameters: input._asn1.parameters
224 },
225 publicKey: input._asn1.publicKey
226 })
227
228 return createPublicKey({ key, format: 'der', type: 'spki' })
229 }
230 case 'rsa': {
231 const RSAPublicKey = asn1.get('RSAPublicKey')
232 const key = RSAPublicKey.encode(input._asn1)
233 return createPublicKey({ key, format: 'der', type: 'pkcs1' })
234 }
235 }
236 }
237
238 if (typeof input === 'string' || Buffer.isBuffer(input)) {
239 input = { key: input, format: 'pem' }
240 }
241
242 if (!isObject(input)) {
243 throw new TypeError('input must be a string, Buffer or an object')
244 }
245
246 const { format, passphrase } = input
247 let { key, type } = input
248
249 if (typeof key !== 'string' && !Buffer.isBuffer(key)) {
250 throw new TypeError('key must be a string or Buffer')
251 }
252
253 if (format !== 'pem' && format !== 'der') {
254 throw new TypeError('format must be one of "pem" or "der"')
255 }
256
257 let label
258 if (format === 'pem') {
259 key = key.toString()
260 switch (key.split(/\r?\n/g)[0].toString()) {
261 case '-----BEGIN PUBLIC KEY-----':
262 type = 'spki'
263 label = 'PUBLIC KEY'
264 break
265 case '-----BEGIN RSA PUBLIC KEY-----':
266 type = 'pkcs1'
267 label = 'RSA PUBLIC KEY'
268 break
269 case '-----BEGIN CERTIFICATE-----':
270 throw new errors.JOSENotSupported('X.509 certificates are not supported in your Node.js runtime version')
271 case '-----BEGIN PRIVATE KEY-----':
272 case '-----BEGIN EC PRIVATE KEY-----':
273 case '-----BEGIN RSA PRIVATE KEY-----':
274 return createPublicKey(createPrivateKey(key))
275 default:
276 throw new TypeError('unknown/unsupported PEM type')
277 }
278 }
279
280 switch (type) {
281 case 'spki': {
282 const PublicKeyInfo = asn1.get('PublicKeyInfo')
283 const parsed = PublicKeyInfo.decode(key, format, { label })
284
285 let type, keyObject
286 switch (parsed.algorithm.algorithm) {
287 case 'ecPublicKey': {
288 keyObject = new KeyObject()
289 keyObject._asn1 = parsed
290 keyObject._asymmetricKeyType = 'ec'
291 keyObject._type = 'public'
292 keyObject._pem = PublicKeyInfo.encode(parsed, 'pem', { label: 'PUBLIC KEY' })
293
294 break
295 }
296 case 'rsaEncryption': {
297 type = 'pkcs1'
298 keyObject = createPublicKey({ type, key: parsed.publicKey.data, format: 'der' })
299 break
300 }
301 default:
302 unsupported(parsed.algorithm.algorithm)
303 }
304
305 return keyObject
306 }
307 case 'pkcs1': {
308 const RSAPublicKey = asn1.get('RSAPublicKey')
309 const parsed = RSAPublicKey.decode(key, format, { label })
310
311 // special case when private pkcs1 PEM / DER is used with createPublicKey
312 if (parsed.n === BigInt(0)) {
313 return createPublicKey(createPrivateKey({ key, format, type, passphrase }))
314 }
315
316 const keyObject = new KeyObject()
317 keyObject._asn1 = parsed
318 keyObject._asymmetricKeyType = 'rsa'
319 keyObject._type = 'public'
320 keyObject._pem = RSAPublicKey.encode(parsed, 'pem', { label: 'RSA PUBLIC KEY' })
321
322 return keyObject
323 }
324 case 'pkcs8':
325 case 'sec1':
326 return createPublicKey(createPrivateKey({ format, key, type, passphrase }))
327 default:
328 throw new TypeError(`The value ${type} is invalid for option "type"`)
329 }
330 }
331
332 createPrivateKey = (input, hints) => {
333 if (typeof input === 'string' || Buffer.isBuffer(input)) {
334 input = { key: input, format: 'pem' }
335 }
336
337 if (!isObject(input)) {
338 throw new TypeError('input must be a string, Buffer or an object')
339 }
340
341 const { format, passphrase } = input
342 let { key, type } = input
343
344 if (typeof key !== 'string' && !Buffer.isBuffer(key)) {
345 throw new TypeError('key must be a string or Buffer')
346 }
347
348 if (passphrase !== undefined) {
349 throw new errors.JOSENotSupported('encrypted private keys are not supported in your Node.js runtime version')
350 }
351
352 if (format !== 'pem' && format !== 'der') {
353 throw new TypeError('format must be one of "pem" or "der"')
354 }
355
356 let label
357 if (format === 'pem') {
358 key = key.toString()
359 switch (key.split(/\r?\n/g)[0].toString()) {
360 case '-----BEGIN PRIVATE KEY-----':
361 type = 'pkcs8'
362 label = 'PRIVATE KEY'
363 break
364 case '-----BEGIN EC PRIVATE KEY-----':
365 type = 'sec1'
366 label = 'EC PRIVATE KEY'
367 break
368 case '-----BEGIN RSA PRIVATE KEY-----':
369 type = 'pkcs1'
370 label = 'RSA PRIVATE KEY'
371 break
372 default:
373 throw new TypeError('unknown/unsupported PEM type')
374 }
375 }
376
377 switch (type) {
378 case 'pkcs8': {
379 const PrivateKeyInfo = asn1.get('PrivateKeyInfo')
380 const parsed = PrivateKeyInfo.decode(key, format, { label })
381
382 let type, keyObject
383 switch (parsed.algorithm.algorithm) {
384 case 'ecPublicKey': {
385 type = 'sec1'
386 keyObject = createPrivateKey({ type, key: parsed.privateKey, format: 'der' }, { [namedCurve]: parsed.algorithm.parameters.value })
387 break
388 }
389 case 'rsaEncryption': {
390 type = 'pkcs1'
391 keyObject = createPrivateKey({ type, key: parsed.privateKey, format: 'der' })
392 break
393 }
394 default:
395 unsupported(parsed.algorithm.algorithm)
396 }
397
398 keyObject._pkcs8 = key
399 return keyObject
400 }
401 case 'pkcs1': {
402 const RSAPrivateKey = asn1.get('RSAPrivateKey')
403 const parsed = RSAPrivateKey.decode(key, format, { label })
404
405 const keyObject = new KeyObject()
406 keyObject._asn1 = parsed
407 keyObject._asymmetricKeyType = 'rsa'
408 keyObject._type = 'private'
409 keyObject._pem = RSAPrivateKey.encode(parsed, 'pem', { label: 'RSA PRIVATE KEY' })
410
411 return keyObject
412 }
413 case 'sec1': {
414 const ECPrivateKey = asn1.get('ECPrivateKey')
415 let parsed = ECPrivateKey.decode(key, format, { label })
416
417 if (!('parameters' in parsed) && !hints[namedCurve]) {
418 throw new Error('invalid sec1')
419 } else if (!('parameters' in parsed)) {
420 parsed = { ...parsed, parameters: { type: 'namedCurve', value: hints[namedCurve] } }
421 }
422
423 const keyObject = new KeyObject()
424 keyObject._asn1 = parsed
425 keyObject._asymmetricKeyType = 'ec'
426 keyObject._type = 'private'
427 keyObject._pem = ECPrivateKey.encode(parsed, 'pem', { label: 'EC PRIVATE KEY' })
428
429 return keyObject
430 }
431 default:
432 throw new TypeError(`The value ${type} is invalid for option "type"`)
433 }
434 }
435}
436
437module.exports = { createPublicKey, createPrivateKey, createSecretKey, KeyObject, asInput }