UNPKG

23.4 kBJavaScriptView Raw
1/**
2 * @file Web Cryptography API shim
3 * @author Artem S Vybornov <vybornov@gmail.com>
4 * @license MIT
5 */
6(function (global, factory) {
7 if (typeof define === 'function' && define.amd) {
8 // AMD. Register as an anonymous module.
9 define([], function () {
10 return factory(global);
11 });
12 } else if (typeof module === 'object' && module.exports) {
13 // CommonJS-like environments that support module.exports
14 module.exports = factory(global);
15 } else {
16 factory(global);
17 }
18}(typeof self !== 'undefined' ? self : this, function (global) {
19 'use strict';
20
21 if ( typeof Promise !== 'function' )
22 throw "Promise support required";
23
24 var _crypto = global.crypto || global.msCrypto;
25 if ( !_crypto ) return;
26
27 var _subtle = _crypto.subtle || _crypto.webkitSubtle;
28 if ( !_subtle ) return;
29
30 var _Crypto = global.Crypto || _crypto.constructor || Object,
31 _SubtleCrypto = global.SubtleCrypto || _subtle.constructor || Object,
32 _CryptoKey = global.CryptoKey || global.Key || Object;
33
34 var isEdge = global.navigator.userAgent.indexOf('Edge/') > -1;
35 var isIE = !!global.msCrypto && !isEdge;
36 var isWebkit = !_crypto.subtle && !!_crypto.webkitSubtle;
37 if ( !isIE && !isWebkit ) return;
38
39 function s2a ( s ) {
40 return btoa(s).replace(/\=+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
41 }
42
43 function a2s ( s ) {
44 s += '===', s = s.slice( 0, -s.length % 4 );
45 return atob( s.replace(/-/g, '+').replace(/_/g, '/') );
46 }
47
48 function s2b ( s ) {
49 var b = new Uint8Array(s.length);
50 for ( var i = 0; i < s.length; i++ ) b[i] = s.charCodeAt(i);
51 return b;
52 }
53
54 function b2s ( b ) {
55 if ( b instanceof ArrayBuffer ) b = new Uint8Array(b);
56 return String.fromCharCode.apply( String, b );
57 }
58
59 function alg ( a ) {
60 var r = { 'name': (a.name || a || '').toUpperCase().replace('V','v') };
61 switch ( r.name ) {
62 case 'SHA-1':
63 case 'SHA-256':
64 case 'SHA-384':
65 case 'SHA-512':
66 break;
67 case 'AES-CBC':
68 case 'AES-GCM':
69 case 'AES-KW':
70 if ( a.length ) r['length'] = a.length;
71 break;
72 case 'HMAC':
73 if ( a.hash ) r['hash'] = alg(a.hash);
74 if ( a.length ) r['length'] = a.length;
75 break;
76 case 'RSAES-PKCS1-v1_5':
77 if ( a.publicExponent ) r['publicExponent'] = new Uint8Array(a.publicExponent);
78 if ( a.modulusLength ) r['modulusLength'] = a.modulusLength;
79 break;
80 case 'RSASSA-PKCS1-v1_5':
81 case 'RSA-OAEP':
82 if ( a.hash ) r['hash'] = alg(a.hash);
83 if ( a.publicExponent ) r['publicExponent'] = new Uint8Array(a.publicExponent);
84 if ( a.modulusLength ) r['modulusLength'] = a.modulusLength;
85 break;
86 default:
87 throw new SyntaxError("Bad algorithm name");
88 }
89 return r;
90 };
91
92 function jwkAlg ( a ) {
93 return {
94 'HMAC': {
95 'SHA-1': 'HS1',
96 'SHA-256': 'HS256',
97 'SHA-384': 'HS384',
98 'SHA-512': 'HS512',
99 },
100 'RSASSA-PKCS1-v1_5': {
101 'SHA-1': 'RS1',
102 'SHA-256': 'RS256',
103 'SHA-384': 'RS384',
104 'SHA-512': 'RS512',
105 },
106 'RSAES-PKCS1-v1_5': {
107 '': 'RSA1_5',
108 },
109 'RSA-OAEP': {
110 'SHA-1': 'RSA-OAEP',
111 'SHA-256': 'RSA-OAEP-256',
112 },
113 'AES-KW': {
114 '128': 'A128KW',
115 '192': 'A192KW',
116 '256': 'A256KW',
117 },
118 'AES-GCM': {
119 '128': 'A128GCM',
120 '192': 'A192GCM',
121 '256': 'A256GCM',
122 },
123 'AES-CBC': {
124 '128': 'A128CBC',
125 '192': 'A192CBC',
126 '256': 'A256CBC',
127 },
128 }[a.name][ ( a.hash || {} ).name || a.length || '' ];
129 }
130
131 function b2jwk ( k ) {
132 if ( k instanceof ArrayBuffer || k instanceof Uint8Array ) k = JSON.parse( decodeURIComponent( escape( b2s(k) ) ) );
133 var jwk = { 'kty': k.kty, 'alg': k.alg, 'ext': k.ext || k.extractable };
134 switch ( jwk.kty ) {
135 case 'oct':
136 jwk.k = k.k;
137 case 'RSA':
138 [ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi', 'oth' ].forEach( function ( x ) { if ( x in k ) jwk[x] = k[x] } );
139 break;
140 default:
141 throw new TypeError("Unsupported key type");
142 }
143 return jwk;
144 }
145
146 function jwk2b ( k ) {
147 var jwk = b2jwk(k);
148 if ( isIE ) jwk['extractable'] = jwk.ext, delete jwk.ext;
149 return s2b( unescape( encodeURIComponent( JSON.stringify(jwk) ) ) ).buffer;
150 }
151
152 function pkcs2jwk ( k ) {
153 var info = b2der(k), prv = false;
154 if ( info.length > 2 ) prv = true, info.shift(); // remove version from PKCS#8 PrivateKeyInfo structure
155 var jwk = { 'ext': true };
156 switch ( info[0][0] ) {
157 case '1.2.840.113549.1.1.1':
158 var rsaComp = [ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi' ],
159 rsaKey = b2der( info[1] );
160 if ( prv ) rsaKey.shift(); // remove version from PKCS#1 RSAPrivateKey structure
161 for ( var i = 0; i < rsaKey.length; i++ ) {
162 if ( !rsaKey[i][0] ) rsaKey[i] = rsaKey[i].subarray(1);
163 jwk[ rsaComp[i] ] = s2a( b2s( rsaKey[i] ) );
164 }
165 jwk['kty'] = 'RSA';
166 break;
167 default:
168 throw new TypeError("Unsupported key type");
169 }
170 return jwk;
171 }
172
173 function jwk2pkcs ( k ) {
174 var key, info = [ [ '', null ] ], prv = false;
175 switch ( k.kty ) {
176 case 'RSA':
177 var rsaComp = [ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi' ],
178 rsaKey = [];
179 for ( var i = 0; i < rsaComp.length; i++ ) {
180 if ( !( rsaComp[i] in k ) ) break;
181 var b = rsaKey[i] = s2b( a2s( k[ rsaComp[i] ] ) );
182 if ( b[0] & 0x80 ) rsaKey[i] = new Uint8Array(b.length + 1), rsaKey[i].set( b, 1 );
183 }
184 if ( rsaKey.length > 2 ) prv = true, rsaKey.unshift( new Uint8Array([0]) ); // add version to PKCS#1 RSAPrivateKey structure
185 info[0][0] = '1.2.840.113549.1.1.1';
186 key = rsaKey;
187 break;
188 default:
189 throw new TypeError("Unsupported key type");
190 }
191 info.push( new Uint8Array( der2b(key) ).buffer );
192 if ( !prv ) info[1] = { 'tag': 0x03, 'value': info[1] };
193 else info.unshift( new Uint8Array([0]) ); // add version to PKCS#8 PrivateKeyInfo structure
194 return new Uint8Array( der2b(info) ).buffer;
195 }
196
197 var oid2str = { 'KoZIhvcNAQEB': '1.2.840.113549.1.1.1' },
198 str2oid = { '1.2.840.113549.1.1.1': 'KoZIhvcNAQEB' };
199
200 function b2der ( buf, ctx ) {
201 if ( buf instanceof ArrayBuffer ) buf = new Uint8Array(buf);
202 if ( !ctx ) ctx = { pos: 0, end: buf.length };
203
204 if ( ctx.end - ctx.pos < 2 || ctx.end > buf.length ) throw new RangeError("Malformed DER");
205
206 var tag = buf[ctx.pos++],
207 len = buf[ctx.pos++];
208
209 if ( len >= 0x80 ) {
210 len &= 0x7f;
211 if ( ctx.end - ctx.pos < len ) throw new RangeError("Malformed DER");
212 for ( var xlen = 0; len--; ) xlen <<= 8, xlen |= buf[ctx.pos++];
213 len = xlen;
214 }
215
216 if ( ctx.end - ctx.pos < len ) throw new RangeError("Malformed DER");
217
218 var rv;
219
220 switch ( tag ) {
221 case 0x02: // Universal Primitive INTEGER
222 rv = buf.subarray( ctx.pos, ctx.pos += len );
223 break;
224 case 0x03: // Universal Primitive BIT STRING
225 if ( buf[ctx.pos++] ) throw new Error( "Unsupported bit string" );
226 len--;
227 case 0x04: // Universal Primitive OCTET STRING
228 rv = new Uint8Array( buf.subarray( ctx.pos, ctx.pos += len ) ).buffer;
229 break;
230 case 0x05: // Universal Primitive NULL
231 rv = null;
232 break;
233 case 0x06: // Universal Primitive OBJECT IDENTIFIER
234 var oid = btoa( b2s( buf.subarray( ctx.pos, ctx.pos += len ) ) );
235 if ( !( oid in oid2str ) ) throw new Error( "Unsupported OBJECT ID " + oid );
236 rv = oid2str[oid];
237 break;
238 case 0x30: // Universal Constructed SEQUENCE
239 rv = [];
240 for ( var end = ctx.pos + len; ctx.pos < end; ) rv.push( b2der( buf, ctx ) );
241 break;
242 default:
243 throw new Error( "Unsupported DER tag 0x" + tag.toString(16) );
244 }
245
246 return rv;
247 }
248
249 function der2b ( val, buf ) {
250 if ( !buf ) buf = [];
251
252 var tag = 0, len = 0,
253 pos = buf.length + 2;
254
255 buf.push( 0, 0 ); // placeholder
256
257 if ( val instanceof Uint8Array ) { // Universal Primitive INTEGER
258 tag = 0x02, len = val.length;
259 for ( var i = 0; i < len; i++ ) buf.push( val[i] );
260 }
261 else if ( val instanceof ArrayBuffer ) { // Universal Primitive OCTET STRING
262 tag = 0x04, len = val.byteLength, val = new Uint8Array(val);
263 for ( var i = 0; i < len; i++ ) buf.push( val[i] );
264 }
265 else if ( val === null ) { // Universal Primitive NULL
266 tag = 0x05, len = 0;
267 }
268 else if ( typeof val === 'string' && val in str2oid ) { // Universal Primitive OBJECT IDENTIFIER
269 var oid = s2b( atob( str2oid[val] ) );
270 tag = 0x06, len = oid.length;
271 for ( var i = 0; i < len; i++ ) buf.push( oid[i] );
272 }
273 else if ( val instanceof Array ) { // Universal Constructed SEQUENCE
274 for ( var i = 0; i < val.length; i++ ) der2b( val[i], buf );
275 tag = 0x30, len = buf.length - pos;
276 }
277 else if ( typeof val === 'object' && val.tag === 0x03 && val.value instanceof ArrayBuffer ) { // Tag hint
278 val = new Uint8Array(val.value), tag = 0x03, len = val.byteLength;
279 buf.push(0); for ( var i = 0; i < len; i++ ) buf.push( val[i] );
280 len++;
281 }
282 else {
283 throw new Error( "Unsupported DER value " + val );
284 }
285
286 if ( len >= 0x80 ) {
287 var xlen = len, len = 4;
288 buf.splice( pos, 0, (xlen >> 24) & 0xff, (xlen >> 16) & 0xff, (xlen >> 8) & 0xff, xlen & 0xff );
289 while ( len > 1 && !(xlen >> 24) ) xlen <<= 8, len--;
290 if ( len < 4 ) buf.splice( pos, 4 - len );
291 len |= 0x80;
292 }
293
294 buf.splice( pos - 2, 2, tag, len );
295
296 return buf;
297 }
298
299 function CryptoKey ( key, alg, ext, use ) {
300 Object.defineProperties( this, {
301 _key: {
302 value: key
303 },
304 type: {
305 value: key.type,
306 enumerable: true,
307 },
308 extractable: {
309 value: (ext === undefined) ? key.extractable : ext,
310 enumerable: true,
311 },
312 algorithm: {
313 value: (alg === undefined) ? key.algorithm : alg,
314 enumerable: true,
315 },
316 usages: {
317 value: (use === undefined) ? key.usages : use,
318 enumerable: true,
319 },
320 });
321 }
322
323 function isPubKeyUse ( u ) {
324 return u === 'verify' || u === 'encrypt' || u === 'wrapKey';
325 }
326
327 function isPrvKeyUse ( u ) {
328 return u === 'sign' || u === 'decrypt' || u === 'unwrapKey';
329 }
330
331 [ 'generateKey', 'importKey', 'unwrapKey' ]
332 .forEach( function ( m ) {
333 var _fn = _subtle[m];
334
335 _subtle[m] = function ( a, b, c ) {
336 var args = [].slice.call(arguments),
337 ka, kx, ku;
338
339 switch ( m ) {
340 case 'generateKey':
341 ka = alg(a), kx = b, ku = c;
342 break;
343 case 'importKey':
344 ka = alg(c), kx = args[3], ku = args[4];
345 if ( a === 'jwk' ) {
346 b = b2jwk(b);
347 if ( !b.alg ) b.alg = jwkAlg(ka);
348 if ( !b.key_ops ) b.key_ops = ( b.kty !== 'oct' ) ? ( 'd' in b ) ? ku.filter(isPrvKeyUse) : ku.filter(isPubKeyUse) : ku.slice();
349 args[1] = jwk2b(b);
350 }
351 break;
352 case 'unwrapKey':
353 ka = args[4], kx = args[5], ku = args[6];
354 args[2] = c._key;
355 break;
356 }
357
358 if ( m === 'generateKey' && ka.name === 'HMAC' && ka.hash ) {
359 ka.length = ka.length || { 'SHA-1': 512, 'SHA-256': 512, 'SHA-384': 1024, 'SHA-512': 1024 }[ka.hash.name];
360 return _subtle.importKey( 'raw', _crypto.getRandomValues( new Uint8Array( (ka.length+7)>>3 ) ), ka, kx, ku );
361 }
362
363 if ( isWebkit && m === 'generateKey' && ka.name === 'RSASSA-PKCS1-v1_5' && ( !ka.modulusLength || ka.modulusLength >= 2048 ) ) {
364 a = alg(a), a.name = 'RSAES-PKCS1-v1_5', delete a.hash;
365 return _subtle.generateKey( a, true, [ 'encrypt', 'decrypt' ] )
366 .then( function ( k ) {
367 return Promise.all([
368 _subtle.exportKey( 'jwk', k.publicKey ),
369 _subtle.exportKey( 'jwk', k.privateKey ),
370 ]);
371 })
372 .then( function ( keys ) {
373 keys[0].alg = keys[1].alg = jwkAlg(ka);
374 keys[0].key_ops = ku.filter(isPubKeyUse), keys[1].key_ops = ku.filter(isPrvKeyUse);
375 return Promise.all([
376 _subtle.importKey( 'jwk', keys[0], ka, true, keys[0].key_ops ),
377 _subtle.importKey( 'jwk', keys[1], ka, kx, keys[1].key_ops ),
378 ]);
379 })
380 .then( function ( keys ) {
381 return {
382 publicKey: keys[0],
383 privateKey: keys[1],
384 };
385 });
386 }
387
388 if ( ( isWebkit || ( isIE && ( ka.hash || {} ).name === 'SHA-1' ) )
389 && m === 'importKey' && a === 'jwk' && ka.name === 'HMAC' && b.kty === 'oct' ) {
390 return _subtle.importKey( 'raw', s2b( a2s(b.k) ), c, args[3], args[4] );
391 }
392
393 if ( isWebkit && m === 'importKey' && ( a === 'spki' || a === 'pkcs8' ) ) {
394 return _subtle.importKey( 'jwk', pkcs2jwk(b), c, args[3], args[4] );
395 }
396
397 if ( isIE && m === 'unwrapKey' ) {
398 return _subtle.decrypt( args[3], c, b )
399 .then( function ( k ) {
400 return _subtle.importKey( a, k, args[4], args[5], args[6] );
401 });
402 }
403
404 var op;
405 try {
406 op = _fn.apply( _subtle, args );
407 }
408 catch ( e ) {
409 return Promise.reject(e);
410 }
411
412 if ( isIE ) {
413 op = new Promise( function ( res, rej ) {
414 op.onabort =
415 op.onerror = function ( e ) { rej(e) };
416 op.oncomplete = function ( r ) { res(r.target.result) };
417 });
418 }
419
420 op = op.then( function ( k ) {
421 if ( ka.name === 'HMAC' ) {
422 if ( !ka.length ) ka.length = 8 * k.algorithm.length;
423 }
424 if ( ka.name.search('RSA') == 0 ) {
425 if ( !ka.modulusLength ) ka.modulusLength = (k.publicKey || k).algorithm.modulusLength;
426 if ( !ka.publicExponent ) ka.publicExponent = (k.publicKey || k).algorithm.publicExponent;
427 }
428 if ( k.publicKey && k.privateKey ) {
429 k = {
430 publicKey: new CryptoKey( k.publicKey, ka, kx, ku.filter(isPubKeyUse) ),
431 privateKey: new CryptoKey( k.privateKey, ka, kx, ku.filter(isPrvKeyUse) ),
432 };
433 }
434 else {
435 k = new CryptoKey( k, ka, kx, ku );
436 }
437 return k;
438 });
439
440 return op;
441 }
442 });
443
444 [ 'exportKey', 'wrapKey' ]
445 .forEach( function ( m ) {
446 var _fn = _subtle[m];
447
448 _subtle[m] = function ( a, b, c ) {
449 var args = [].slice.call(arguments);
450
451 switch ( m ) {
452 case 'exportKey':
453 args[1] = b._key;
454 break;
455 case 'wrapKey':
456 args[1] = b._key, args[2] = c._key;
457 break;
458 }
459
460 if ( ( isWebkit || ( isIE && ( b.algorithm.hash || {} ).name === 'SHA-1' ) )
461 && m === 'exportKey' && a === 'jwk' && b.algorithm.name === 'HMAC' ) {
462 args[0] = 'raw';
463 }
464
465 if ( isWebkit && m === 'exportKey' && ( a === 'spki' || a === 'pkcs8' ) ) {
466 args[0] = 'jwk';
467 }
468
469 if ( isIE && m === 'wrapKey' ) {
470 return _subtle.exportKey( a, b )
471 .then( function ( k ) {
472 if ( a === 'jwk' ) k = s2b( unescape( encodeURIComponent( JSON.stringify( b2jwk(k) ) ) ) );
473 return _subtle.encrypt( args[3], c, k );
474 });
475 }
476
477 var op;
478 try {
479 op = _fn.apply( _subtle, args );
480 }
481 catch ( e ) {
482 return Promise.reject(e);
483 }
484
485 if ( isIE ) {
486 op = new Promise( function ( res, rej ) {
487 op.onabort =
488 op.onerror = function ( e ) { rej(e) };
489 op.oncomplete = function ( r ) { res(r.target.result) };
490 });
491 }
492
493 if ( m === 'exportKey' && a === 'jwk' ) {
494 op = op.then( function ( k ) {
495 if ( ( isWebkit || ( isIE && ( b.algorithm.hash || {} ).name === 'SHA-1' ) )
496 && b.algorithm.name === 'HMAC') {
497 return { 'kty': 'oct', 'alg': jwkAlg(b.algorithm), 'key_ops': b.usages.slice(), 'ext': true, 'k': s2a( b2s(k) ) };
498 }
499 k = b2jwk(k);
500 if ( !k.alg ) k['alg'] = jwkAlg(b.algorithm);
501 if ( !k.key_ops ) k['key_ops'] = ( b.type === 'public' ) ? b.usages.filter(isPubKeyUse) : ( b.type === 'private' ) ? b.usages.filter(isPrvKeyUse) : b.usages.slice();
502 return k;
503 });
504 }
505
506 if ( isWebkit && m === 'exportKey' && ( a === 'spki' || a === 'pkcs8' ) ) {
507 op = op.then( function ( k ) {
508 k = jwk2pkcs( b2jwk(k) );
509 return k;
510 });
511 }
512
513 return op;
514 }
515 });
516
517 [ 'encrypt', 'decrypt', 'sign', 'verify' ]
518 .forEach( function ( m ) {
519 var _fn = _subtle[m];
520
521 _subtle[m] = function ( a, b, c, d ) {
522 if ( isIE && ( !c.byteLength || ( d && !d.byteLength ) ) )
523 throw new Error("Empy input is not allowed");
524
525 var args = [].slice.call(arguments),
526 ka = alg(a);
527
528 if ( isIE && m === 'decrypt' && ka.name === 'AES-GCM' ) {
529 var tl = a.tagLength >> 3;
530 args[2] = (c.buffer || c).slice( 0, c.byteLength - tl ),
531 a.tag = (c.buffer || c).slice( c.byteLength - tl );
532 }
533
534 args[1] = b._key;
535
536 var op;
537 try {
538 op = _fn.apply( _subtle, args );
539 }
540 catch ( e ) {
541 return Promise.reject(e);
542 }
543
544 if ( isIE ) {
545 op = new Promise( function ( res, rej ) {
546 op.onabort =
547 op.onerror = function ( e ) {
548 rej(e);
549 };
550
551 op.oncomplete = function ( r ) {
552 var r = r.target.result;
553
554 if ( m === 'encrypt' && r instanceof AesGcmEncryptResult ) {
555 var c = r.ciphertext, t = r.tag;
556 r = new Uint8Array( c.byteLength + t.byteLength );
557 r.set( new Uint8Array(c), 0 );
558 r.set( new Uint8Array(t), c.byteLength );
559 r = r.buffer;
560 }
561
562 res(r);
563 };
564 });
565 }
566
567 return op;
568 }
569 });
570
571 if ( isIE ) {
572 var _digest = _subtle.digest;
573
574 _subtle['digest'] = function ( a, b ) {
575 if ( !b.byteLength )
576 throw new Error("Empy input is not allowed");
577
578 var op;
579 try {
580 op = _digest.call( _subtle, a, b );
581 }
582 catch ( e ) {
583 return Promise.reject(e);
584 }
585
586 op = new Promise( function ( res, rej ) {
587 op.onabort =
588 op.onerror = function ( e ) { rej(e) };
589 op.oncomplete = function ( r ) { res(r.target.result) };
590 });
591
592 return op;
593 };
594
595 global.crypto = Object.create( _crypto, {
596 getRandomValues: { value: function ( a ) { return _crypto.getRandomValues(a) } },
597 subtle: { value: _subtle },
598 });
599
600 global.CryptoKey = CryptoKey;
601 }
602
603 if ( isWebkit ) {
604 _crypto.subtle = _subtle;
605
606 global.Crypto = _Crypto;
607 global.SubtleCrypto = _SubtleCrypto;
608 global.CryptoKey = CryptoKey;
609 }
610}));
611
612 export default {} // section modified by isomorphic-webcrypto build