UNPKG

8.95 kBJavaScriptView Raw
1/* global BigInt */
2
3const { EOL } = require('os')
4
5const errors = require('../errors')
6
7const { createPublicKey } = require('./key_object')
8const base64url = require('./base64url')
9const asn1 = require('./asn1')
10const computePrimes = require('./rsa_primes')
11const { OKP_CURVES, EC_CURVES } = require('../registry')
12
13const formatPem = (base64pem, descriptor) => `-----BEGIN ${descriptor} KEY-----${EOL}${(base64pem.match(/.{1,64}/g) || []).join(EOL)}${EOL}-----END ${descriptor} KEY-----`
14
15const 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
41const 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
157module.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
165const 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
174const 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 // TODO: WHYYY? https://github.com/indutny/asn1.js/issues/110
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
267module.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}