1 |
|
2 |
|
3 | const { keyObjectSupported } = require('./runtime_support')
|
4 |
|
5 | let createPublicKey
|
6 | let createPrivateKey
|
7 | let createSecretKey
|
8 | let KeyObject
|
9 | let asInput
|
10 |
|
11 | if (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 |
|
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 |
|
437 | module.exports = { createPublicKey, createPrivateKey, createSecretKey, KeyObject, asInput }
|