Use HMAC symmetric key to sign/verify and add webkit shim

For webkit support, I removed the public/private signing keys and
replaced them with a symmetric HMAC signing key. It achieves the same
thing and besides adding webkit support, is also a bit cleaner.

Webkit handles key export is a non-standard way, so with this shim we
have to export and import public keys in “spki” format. Webkit also
requires slightly different options to be passed in for some operations.
This commit is contained in:
Alan Friedman 2016-01-25 15:50:33 -05:00
parent 663fee0797
commit 0dad45ed91
3 changed files with 781 additions and 142 deletions

View File

@ -61,9 +61,6 @@ $(function() {
this.setSelectionRange(0, 9999);
});
var crypto = window.crypto;
var cryptoSubtle = window.crypto.subtle || window.crypto.webkitSubtle;
let socket = io(roomId);
FastClick.attach(document.body);
@ -93,107 +90,22 @@ $(function() {
$inputMessage.focus();
Promise.all([
createPrimaryKeys(),
createSigningKeys()
createPrimaryKeys();
])
.then(function(data) {
keys.primary = {
keys = {
public: data[0].publicKey,
private: data[0].privateKey
};
keys.signing = {
public: data[1].publicKey,
private: data[1].privateKey
};
return Promise.all([
exportKey(data[0].publicKey),
exportKey(data[1].publicKey)
exportKey(data[0].publicKey, "spki")
]);
})
.then(function(exportedKeys) {
// Tell the server your username and send public keys
socket.emit('add user', {
username: username,
publicPrimaryKey: exportedKeys[0],
publicSigningKey: exportedKeys[1]
});
});
}
}
// Sends a chat message
function sendMessage () {
// Don't send unless other users exist
if (users.length <= 1) return;
let message = $inputMessage.val();
// Prevent markup from being injected into the message
message = cleanInput(message);
// if there is a non-empty message and a socket connection
if (message && connected) {
$inputMessage.val('');
$('#send-message-btn').removeClass('active');
addChatMessage({
username: username,
message: message
});
let vector = crypto.getRandomValues(new Uint8Array(16));
let secretKey;
let secretKeys;
let messageData;
let signature;
// Generate new secret key and vector for each message
createSecretKey()
.then(function(key) {
secretKey = key;
// Generate secretKey and encrypt with each user's public key
let promises = [];
_.each(users, function(user) {
// It not me
if (user.username !== window.username) {
let promise = new Promise(function(resolve, reject) {
let thisUser = user;
let exportedSecretKey;
exportKey(key, "raw")
.then(function(data) {
exportedSecretKey = data;
return encryptSecretKey(data, thisUser.publicPrimaryKey);
})
.then(function(encryptedSecretKey) {
var encData = new Uint8Array(encryptedSecretKey);
var str = convertArrayBufferViewToString(encData);
resolve({
id: thisUser.id,
secretKey: str
});
});
});
promises.push(promise);
}
});
return Promise.all(promises);
})
.then(function(data) {
secretKeys = data;
messageData = convertStringToArrayBufferView(message);
return signKey(messageData, keys.signing.private)
})
.then(function(data) {
signature = data;
return encryptMessage(messageData, secretKey, vector)
})
.then(function(encryptedData) {
let msg = convertArrayBufferViewToString(new Uint8Array(encryptedData));
let vct = convertArrayBufferViewToString(new Uint8Array(vector));
let sig = convertArrayBufferViewToString(new Uint8Array(signature));
socket.emit('new message', {
message: msg,
vector: vct,
secretKeys: secretKeys,
signature: sig
publicKey: exportedKeys[0]
});
});
}
@ -387,15 +299,13 @@ $(function() {
let promise = new Promise(function(resolve, reject) {
let currentUser = user;
Promise.all([
importPrimaryKey(currentUser.publicPrimaryKey),
importSigningKey(currentUser.publicSigningKey)
importPrimaryKey(currentUser.publicKey, "spki")
])
.then(function(keys) {
users.push({
id: currentUser.id,
username: currentUser.username,
publicPrimaryKey: keys[0],
publicSigningKey: keys[1]
publicKey: keys[0]
});
resolve();
});
@ -417,10 +327,6 @@ $(function() {
$('#first-modal').modal('show');
}
$('.modal').on('shown.bs.modal', function (e) {
autosize.update($('textarea.share-text'));
});
log(data.username + ' joined');
renderParticipantsList();
@ -428,6 +334,103 @@ $(function() {
});
// Sends a chat message
function sendMessage () {
// Don't send unless other users exist
if (users.length <= 1) return;
let message = $inputMessage.val();
// Prevent markup from being injected into the message
message = cleanInput(message);
// if there is a non-empty message and a socket connection
if (message && connected) {
$inputMessage.val('');
$('#send-message-btn').removeClass('active');
addChatMessage({
username: username,
message: message
});
let vector = crypto.getRandomValues(new Uint8Array(16));
let secretKey;
let secretKeys;
let messageData;
let signature;
let signingKey;
let encryptedMessageData;
// Generate new secret key and vector for each message
createSecretKey()
.then(function(key) {
secretKey = key;
return createSigningKey();
})
.then(function(key) {
signingKey = key;
// Generate secretKey and encrypt with each user's public key
let promises = [];
_.each(users, function(user) {
// If not me
if (user.username !== window.username) {
let promise = new Promise(function(resolve, reject) {
let thisUser = user;
let secretKeyStr;
// Export secret key
exportKey(secretKey, "raw")
.then(function(data) {
return encryptSecretKey(data, thisUser.publicKey);
})
.then(function(encryptedSecretKey) {
let encData = new Uint8Array(encryptedSecretKey);
secretKeyStr = convertArrayBufferViewToString(encData);
// Export HMAC signing key
return exportKey(signingKey, "raw");
})
.then(function(data) {
// Encrypt signing key with user's public key
return encryptSigningKey(data, thisUser.publicKey);
})
.then(function(encryptedSigningKey) {
let encData = new Uint8Array(encryptedSigningKey);
var str = convertArrayBufferViewToString(encData);
resolve({
id: thisUser.id,
secretKey: secretKeyStr,
encryptedSigningKey: str
});
});
});
promises.push(promise);
}
});
return Promise.all(promises);
})
.then(function(data) {
secretKeys = data;
messageData = convertStringToArrayBufferView(message);
return signKey(messageData, signingKey);
})
.then(function(data) {
signature = data;
return encryptMessage(messageData, secretKey, vector);
})
.then(function(data) {
encryptedMessageData = data;
let msg = convertArrayBufferViewToString(new Uint8Array(encryptedMessageData));
let vct = convertArrayBufferViewToString(new Uint8Array(vector));
let sig = convertArrayBufferViewToString(new Uint8Array(signature));
socket.emit('new message', {
message: msg,
vector: vct,
secretKeys: secretKeys,
signature: sig
});
});
}
}
// Whenever the server emits 'new message', update the chat body
socket.on('new message', function (data) {
// Don't show messages if no key
@ -447,7 +450,7 @@ $(function() {
let vectorData = convertStringToArrayBufferView(vector);
let secretKeys = data.secretKeys;
let decryptedMessageData;
let decryptedMessage;
let decryptedMessage;
let mySecretKey = _.find(secretKeys, function(key) {
return key.id === myUserId;
@ -455,13 +458,11 @@ $(function() {
let signature = data.signature;
let signatureData = convertStringToArrayBufferView(signature);
let secretKeyArrayBuffer = convertStringToArrayBufferView(mySecretKey.secretKey);
let signingKeyArrayBuffer = convertStringToArrayBufferView(mySecretKey.encryptedSigningKey);
decryptSecretKey(secretKeyArrayBuffer, keys.primary.private)
decryptSecretKey(secretKeyArrayBuffer, keys.private)
.then(function(data) {
return new Uint8Array(data);
})
.then(function(data) {
return importSecretKey(data, "raw");
return importSecretKey(new Uint8Array(data), "raw");
})
.then(function(data) {
let secretKey = data;
@ -470,14 +471,14 @@ $(function() {
.then(function(data) {
decryptedMessageData = data;
decryptedMessage = convertArrayBufferViewToString(new Uint8Array(data))
return decryptSigningKey(signingKeyArrayBuffer, keys.private)
})
.then(function() {
// Find who sent msg (senderId), get their public key and verifyKey() with it and signature
let sender = _.find(users, function(user) {
return user.id === senderId;
});
let senderPublicVerifyKey = sender.publicSigningKey;
return verifyKey(signatureData, decryptedMessageData, senderPublicVerifyKey)
.then(function(data) {
return importSigningKey(new Uint8Array(data), "raw");
})
.then(function(data) {
let signingKey = data;
return verifyKey(signatureData, decryptedMessageData, signingKey);
})
.then(function(bool) {
if (bool) {
@ -587,13 +588,12 @@ $(function() {
return str;
}
function createSigningKeys() {
return cryptoSubtle.generateKey(
function createSigningKey() {
return window.crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
name: "HMAC",
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
//length: 256, //optional, if you want your key length to differ from the hash function's block length
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["sign", "verify"] //can be any combination of "sign" and "verify"
@ -601,7 +601,7 @@ $(function() {
}
function createPrimaryKeys() {
return cryptoSubtle.generateKey(
return window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048, //can be 1024, 2048, or 4096
@ -614,21 +614,24 @@ $(function() {
}
function createSecretKey() {
return cryptoSubtle.generateKey(
return window.crypto.subtle.generateKey(
{
name: "AES-CBC",
length: 256, //can be 128, 192, or 256
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt", "wrapKey", "unwrapKey"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey"
["encrypt", "decrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey"
);
}
function encryptSecretKey(data, secretKey) {
// Secret key will be recipient's public key
return cryptoSubtle.encrypt(
return window.crypto.subtle.encrypt(
{
name: "RSA-OAEP"
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
},
secretKey,
data //ArrayBuffer of data you want to encrypt
@ -637,9 +640,41 @@ $(function() {
function decryptSecretKey(data, key) {
// key will be my private key
return cryptoSubtle.decrypt(
return window.crypto.subtle.decrypt(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
//label: Uint8Array([...]) //optional
},
key,
data //ArrayBuffer of the data
);
}
function encryptSigningKey(data, signingKey) {
// Secret key will be recipient's public key
return window.crypto.subtle.encrypt(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
},
signingKey,
data //ArrayBuffer of data you want to encrypt
);
}
function decryptSigningKey(data, key) {
// key will be my private key
return window.crypto.subtle.decrypt(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
//label: Uint8Array([...]) //optional
},
key,
@ -648,7 +683,7 @@ $(function() {
}
function encryptMessage(data, secretKey, iv) {
return cryptoSubtle.encrypt(
return window.crypto.subtle.encrypt(
{
name: "AES-CBC",
//Don't re-use initialization vectors!
@ -661,7 +696,7 @@ $(function() {
}
function decryptMessage(data, secretKey, iv) {
return cryptoSubtle.decrypt(
return window.crypto.subtle.decrypt(
{
name: "AES-CBC",
iv: iv, //The initialization vector you used to encrypt
@ -672,7 +707,7 @@ $(function() {
}
function importSecretKey(jwkData, format) {
return cryptoSubtle.importKey(
return window.crypto.subtle.importKey(
format || "jwk", //can be "jwk" or "raw"
//this is an example jwk key, "raw" would be an ArrayBuffer
jwkData,
@ -684,15 +719,18 @@ $(function() {
);
}
function importPrimaryKey(jwkData) {
function importPrimaryKey(jwkData, format) {
// Will be someone's public key
return cryptoSubtle.importKey(
"jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
let hashObj = {
name: "RSA-OAEP"
};
if (!window.crypto.webkitSubtle) {
hashObj.hash = {name: "SHA-256"};
}
return window.crypto.subtle.importKey(
format || "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
jwkData,
{ //these are the algorithm options
name: "RSA-OAEP",
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
},
hashObj,
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt"] //"encrypt" or "wrapKey" for public key import or
//"decrypt" or "unwrapKey" for private key imports
@ -701,20 +739,21 @@ $(function() {
function exportKey(key, format) {
// Will be public primary key or public signing key
return cryptoSubtle.exportKey(
return window.crypto.subtle.exportKey(
format || "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
key //can be a publicKey or privateKey, as long as extractable was true
);
}
function importSigningKey(jwkData) {
return cryptoSubtle.importKey(
"jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
return window.crypto.subtle.importKey(
"raw", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
//this is an example jwk key, other key types are Uint8Array objects
jwkData,
{ //these are the algorithm options
name: "RSASSA-PKCS1-v1_5",
name: "HMAC",
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
//length: 256, //optional, if you want your key length to differ from the hash function's block length
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["verify"] //"verify" for public key import, "sign" for private key imports
@ -723,9 +762,10 @@ $(function() {
function signKey(data, keyToSignWith) {
// Will use my private key
return cryptoSubtle.sign(
return window.crypto.subtle.sign(
{
name: "RSASSA-PKCS1-v1_5"
name: "HMAC",
hash: {name: "SHA-256"}
},
keyToSignWith, //from generateKey or importKey above
data //ArrayBuffer of data you want to sign
@ -734,9 +774,10 @@ $(function() {
function verifyKey(signature, data, keyToVerifyWith) {
// Will verify with sender's public key
return cryptoSubtle.verify(
return window.crypto.subtle.verify(
{
name: "RSASSA-PKCS1-v1_5"
name: "HMAC",
hash: {name: "SHA-256"}
},
keyToVerifyWith, //from generateKey or importKey above
signature, //ArrayBuffer of the signature

597
src/public/vendor/web-crypto-shim.js vendored Normal file
View File

@ -0,0 +1,597 @@
/**
* @file Web Cryptography API shim
* @author Artem S Vybornov <vybornov@gmail.com>
* @license MIT
*/
!function ( global ) {
'use strict';
if ( typeof Promise !== 'function' )
throw "Promise support required";
var _crypto = global.crypto || global.msCrypto;
if ( !_crypto ) return;
var _subtle = _crypto.subtle || _crypto.webkitSubtle;
if ( !_subtle ) return;
var _Crypto = global.Crypto || _crypto.constructor || Object,
_SubtleCrypto = global.SubtleCrypto || _subtle.constructor || Object,
_CryptoKey = global.CryptoKey || global.Key || Object;
var isIE = !!global.msCrypto,
isWebkit = !!_crypto.webkitSubtle;
if ( !isIE && !isWebkit ) return;
function s2a ( s ) {
return btoa(s).replace(/\=+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
}
function a2s ( s ) {
s += '===', s = s.slice( 0, -s.length % 4 );
return atob( s.replace(/-/g, '+').replace(/_/g, '/') );
}
function s2b ( s ) {
var b = new Uint8Array(s.length);
for ( var i = 0; i < s.length; i++ ) b[i] = s.charCodeAt(i);
return b;
}
function b2s ( b ) {
if ( b instanceof ArrayBuffer ) b = new Uint8Array(b);
return String.fromCharCode.apply( String, b );
}
function alg ( a ) {
var r = { 'name': (a.name || a || '').toUpperCase().replace('V','v') };
switch ( r.name ) {
case 'SHA-1':
case 'SHA-256':
case 'SHA-384':
case 'SHA-512':
break;
case 'AES-CBC':
case 'AES-GCM':
case 'AES-KW':
if ( a.length ) r['length'] = a.length;
break;
case 'HMAC':
if ( a.hash ) r['hash'] = alg(a.hash);
if ( a.length ) r['length'] = a.length;
break;
case 'RSAES-PKCS1-v1_5':
if ( a.publicExponent ) r['publicExponent'] = new Uint8Array(a.publicExponent);
if ( a.modulusLength ) r['modulusLength'] = a.modulusLength;
break;
case 'RSASSA-PKCS1-v1_5':
case 'RSA-OAEP':
if ( a.hash ) r['hash'] = alg(a.hash);
if ( a.publicExponent ) r['publicExponent'] = new Uint8Array(a.publicExponent);
if ( a.modulusLength ) r['modulusLength'] = a.modulusLength;
break;
default:
throw new SyntaxError("Bad algorithm name");
}
return r;
};
function jwkAlg ( a ) {
return {
'HMAC': {
'SHA-1': 'HS1',
'SHA-256': 'HS256',
'SHA-384': 'HS384',
'SHA-512': 'HS512',
},
'RSASSA-PKCS1-v1_5': {
'SHA-1': 'RS1',
'SHA-256': 'RS256',
'SHA-384': 'RS384',
'SHA-512': 'RS512',
},
'RSAES-PKCS1-v1_5': {
'': 'RSA1_5',
},
'RSA-OAEP': {
'SHA-1': 'RSA-OAEP',
'SHA-256': 'RSA-OAEP-256',
},
'AES-KW': {
'128': 'A128KW',
'192': 'A192KW',
'256': 'A256KW',
},
'AES-GCM': {
'128': 'A128GCM',
'192': 'A192GCM',
'256': 'A256GCM',
},
'AES-CBC': {
'128': 'A128CBC',
'192': 'A192CBC',
'256': 'A256CBC',
},
}[a.name][ ( a.hash || {} ).name || a.length || '' ];
}
function b2jwk ( k ) {
if ( k instanceof ArrayBuffer || k instanceof Uint8Array ) k = JSON.parse( decodeURIComponent( escape( b2s(k) ) ) );
var jwk = { 'kty': k.kty, 'alg': k.alg, 'ext': k.ext || k.extractable };
switch ( jwk.kty ) {
case 'oct':
jwk.k = k.k;
case 'RSA':
[ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi', 'oth' ].forEach( function ( x ) { if ( x in k ) jwk[x] = k[x] } );
break;
default:
throw new TypeError("Unsupported key type");
}
return jwk;
}
function jwk2b ( k ) {
var jwk = b2jwk(k);
if ( isIE ) jwk['extractable'] = jwk.ext, delete jwk.ext;
return s2b( unescape( encodeURIComponent( JSON.stringify(jwk) ) ) ).buffer;
}
function pkcs2jwk ( k ) {
var info = b2der(k), prv = false;
if ( info.length > 2 ) prv = true, info.shift(); // remove version from PKCS#8 PrivateKeyInfo structure
var jwk = { 'ext': true };
switch ( info[0][0] ) {
case '1.2.840.113549.1.1.1':
var rsaComp = [ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi' ],
rsaKey = b2der( info[1] );
if ( prv ) rsaKey.shift(); // remove version from PKCS#1 RSAPrivateKey structure
for ( var i = 0; i < rsaKey.length; i++ ) {
if ( !rsaKey[i][0] ) rsaKey[i] = rsaKey[i].subarray(1);
jwk[ rsaComp[i] ] = s2a( b2s( rsaKey[i] ) );
}
jwk['kty'] = 'RSA';
break;
default:
throw new TypeError("Unsupported key type");
}
return jwk;
}
function jwk2pkcs ( k ) {
var key, info = [ [ '', null ] ], prv = false;
switch ( k.kty ) {
case 'RSA':
var rsaComp = [ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi' ],
rsaKey = [];
for ( var i = 0; i < rsaComp.length; i++ ) {
if ( !( rsaComp[i] in k ) ) break;
var b = rsaKey[i] = s2b( a2s( k[ rsaComp[i] ] ) );
if ( b[0] & 0x80 ) rsaKey[i] = new Uint8Array(b.length + 1), rsaKey[i].set( b, 1 );
}
if ( rsaKey.length > 2 ) prv = true, rsaKey.unshift( new Uint8Array([0]) ); // add version to PKCS#1 RSAPrivateKey structure
info[0][0] = '1.2.840.113549.1.1.1';
key = rsaKey;
break;
default:
throw new TypeError("Unsupported key type");
}
info.push( new Uint8Array( der2b(key) ).buffer );
if ( !prv ) info[1] = { 'tag': 0x03, 'value': info[1] };
else info.unshift( new Uint8Array([0]) ); // add version to PKCS#8 PrivateKeyInfo structure
return new Uint8Array( der2b(info) ).buffer;
}
var oid2str = { 'KoZIhvcNAQEB': '1.2.840.113549.1.1.1' },
str2oid = { '1.2.840.113549.1.1.1': 'KoZIhvcNAQEB' };
function b2der ( buf, ctx ) {
if ( buf instanceof ArrayBuffer ) buf = new Uint8Array(buf);
if ( !ctx ) ctx = { pos: 0, end: buf.length };
if ( ctx.end - ctx.pos < 2 || ctx.end > buf.length ) throw new RangeError("Malformed DER");
var tag = buf[ctx.pos++],
len = buf[ctx.pos++];
if ( len >= 0x80 ) {
len &= 0x7f;
if ( ctx.end - ctx.pos < len ) throw new RangeError("Malformed DER");
for ( var xlen = 0; len--; ) xlen <<= 8, xlen |= buf[ctx.pos++];
len = xlen;
}
if ( ctx.end - ctx.pos < len ) throw new RangeError("Malformed DER");
var rv;
switch ( tag ) {
case 0x02: // Universal Primitive INTEGER
rv = buf.subarray( ctx.pos, ctx.pos += len );
break;
case 0x03: // Universal Primitive BIT STRING
if ( buf[ctx.pos++] ) throw new Error( "Unsupported bit string" );
len--;
case 0x04: // Universal Primitive OCTET STRING
rv = new Uint8Array( buf.subarray( ctx.pos, ctx.pos += len ) ).buffer;
break;
case 0x05: // Universal Primitive NULL
rv = null;
break;
case 0x06: // Universal Primitive OBJECT IDENTIFIER
var oid = btoa( b2s( buf.subarray( ctx.pos, ctx.pos += len ) ) );
if ( !( oid in oid2str ) ) throw new Error( "Unsupported OBJECT ID " + oid );
rv = oid2str[oid];
break;
case 0x30: // Universal Constructed SEQUENCE
rv = [];
for ( var end = ctx.pos + len; ctx.pos < end; ) rv.push( b2der( buf, ctx ) );
break;
default:
throw new Error( "Unsupported DER tag 0x" + tag.toString(16) );
}
return rv;
}
function der2b ( val, buf ) {
if ( !buf ) buf = [];
var tag = 0, len = 0,
pos = buf.length + 2;
buf.push( 0, 0 ); // placeholder
if ( val instanceof Uint8Array ) { // Universal Primitive INTEGER
tag = 0x02, len = val.length;
for ( var i = 0; i < len; i++ ) buf.push( val[i] );
}
else if ( val instanceof ArrayBuffer ) { // Universal Primitive OCTET STRING
tag = 0x04, len = val.byteLength, val = new Uint8Array(val);
for ( var i = 0; i < len; i++ ) buf.push( val[i] );
}
else if ( val === null ) { // Universal Primitive NULL
tag = 0x05, len = 0;
}
else if ( typeof val === 'string' && val in str2oid ) { // Universal Primitive OBJECT IDENTIFIER
var oid = s2b( atob( str2oid[val] ) );
tag = 0x06, len = oid.length;
for ( var i = 0; i < len; i++ ) buf.push( oid[i] );
}
else if ( val instanceof Array ) { // Universal Constructed SEQUENCE
for ( var i = 0; i < val.length; i++ ) der2b( val[i], buf );
tag = 0x30, len = buf.length - pos;
}
else if ( typeof val === 'object' && val.tag === 0x03 && val.value instanceof ArrayBuffer ) { // Tag hint
val = new Uint8Array(val.value), tag = 0x03, len = val.byteLength;
buf.push(0); for ( var i = 0; i < len; i++ ) buf.push( val[i] );
len++;
}
else {
throw new Error( "Unsupported DER value " + val );
}
if ( len >= 0x80 ) {
var xlen = len, len = 4;
buf.splice( pos, 0, (xlen >> 24) & 0xff, (xlen >> 16) & 0xff, (xlen >> 8) & 0xff, xlen & 0xff );
while ( len > 1 && !(xlen >> 24) ) xlen <<= 8, len--;
if ( len < 4 ) buf.splice( pos, 4 - len );
len |= 0x80;
}
buf.splice( pos - 2, 2, tag, len );
return buf;
}
function CryptoKey ( key, alg, ext, use ) {
Object.defineProperties( this, {
_key: {
value: key
},
type: {
value: key.type,
enumerable: true,
},
extractable: {
value: (ext === undefined) ? key.extractable : ext,
enumerable: true,
},
algorithm: {
value: (alg === undefined) ? key.algorithm : alg,
enumerable: true,
},
usages: {
value: (use === undefined) ? key.usages : use,
enumerable: true,
},
});
}
function isPubKeyUse ( u ) {
return u === 'verify' || u === 'encrypt' || u === 'wrapKey';
}
function isPrvKeyUse ( u ) {
return u === 'sign' || u === 'decrypt' || u === 'unwrapKey';
}
[ 'generateKey', 'importKey', 'unwrapKey' ]
.forEach( function ( m ) {
var _fn = _subtle[m];
_subtle[m] = function ( a, b, c ) {
var args = [].slice.call(arguments),
ka, kx, ku;
switch ( m ) {
case 'generateKey':
ka = alg(a), kx = b, ku = c;
break;
case 'importKey':
ka = alg(c), kx = args[3], ku = args[4];
if ( a === 'jwk' ) {
b = b2jwk(b);
if ( !b.alg ) b.alg = jwkAlg(ka);
if ( !b.key_ops ) b.key_ops = ( b.kty !== 'oct' ) ? ( 'd' in b ) ? ku.filter(isPrvKeyUse) : ku.filter(isPubKeyUse) : ku.slice();
args[1] = jwk2b(b);
}
break;
case 'unwrapKey':
ka = args[4], kx = args[5], ku = args[6];
args[2] = c._key;
break;
}
if ( m === 'generateKey' && ka.name === 'HMAC' && ka.hash ) {
ka.length = ka.length || { 'SHA-1': 512, 'SHA-256': 512, 'SHA-384': 1024, 'SHA-512': 1024 }[ka.hash.name];
return _subtle.importKey( 'raw', _crypto.getRandomValues( new Uint8Array( (ka.length+7)>>3 ) ), ka, kx, ku );
}
if ( isWebkit && m === 'generateKey' && ka.name === 'RSASSA-PKCS1-v1_5' && ( !ka.modulusLength || ka.modulusLength >= 2048 ) ) {
a = alg(a), a.name = 'RSAES-PKCS1-v1_5', delete a.hash;
return _subtle.generateKey( a, true, [ 'encrypt', 'decrypt' ] )
.then( function ( k ) {
return Promise.all([
_subtle.exportKey( 'jwk', k.publicKey ),
_subtle.exportKey( 'jwk', k.privateKey ),
]);
})
.then( function ( keys ) {
keys[0].alg = keys[1].alg = jwkAlg(ka);
keys[0].key_ops = ku.filter(isPubKeyUse), keys[1].key_ops = ku.filter(isPrvKeyUse);
return Promise.all([
_subtle.importKey( 'jwk', keys[0], ka, kx, keys[0].key_ops ),
_subtle.importKey( 'jwk', keys[1], ka, kx, keys[1].key_ops ),
]);
})
.then( function ( keys ) {
return {
publicKey: keys[0],
privateKey: keys[1],
};
});
}
if ( ( isWebkit || ( isIE && ( ka.hash || {} ).name === 'SHA-1' ) )
&& m === 'importKey' && a === 'jwk' && ka.name === 'HMAC' && b.kty === 'oct' ) {
return _subtle.importKey( 'raw', s2b( a2s(b.k) ), c, args[3], args[4] );
}
if ( isWebkit && m === 'importKey' && ( a === 'spki' || a === 'pkcs8' ) ) {
return _subtle.importKey( 'jwk', pkcs2jwk(b), c, args[3], args[4] );
}
if ( isIE && m === 'unwrapKey' ) {
return _subtle.decrypt( args[3], c, b )
.then( function ( k ) {
return _subtle.importKey( a, k, args[4], args[5], args[6] );
});
}
var op;
try {
op = _fn.apply( _subtle, args );
}
catch ( e ) {
return Promise.reject(e);
}
if ( isIE ) {
op = new Promise( function ( res, rej ) {
op.onabort =
op.onerror = function ( e ) { rej(e) };
op.oncomplete = function ( r ) { res(r.target.result) };
});
}
op = op.then( function ( k ) {
if ( ka.name === 'HMAC' ) {
if ( !ka.length ) ka.length = 8 * k.algorithm.length;
}
if ( ka.name.search('RSA') == 0 ) {
if ( !ka.modulusLength ) ka.modulusLength = (k.publicKey || k).algorithm.modulusLength;
if ( !ka.publicExponent ) ka.publicExponent = (k.publicKey || k).algorithm.publicExponent;
}
if ( k.publicKey && k.privateKey ) {
k = {
publicKey: new CryptoKey( k.publicKey, ka, kx, ku.filter(isPubKeyUse) ),
privateKey: new CryptoKey( k.privateKey, ka, kx, ku.filter(isPrvKeyUse) ),
};
}
else {
k = new CryptoKey( k, ka, kx, ku );
}
return k;
});
return op;
}
});
[ 'exportKey', 'wrapKey' ]
.forEach( function ( m ) {
var _fn = _subtle[m];
_subtle[m] = function ( a, b, c ) {
var args = [].slice.call(arguments);
switch ( m ) {
case 'exportKey':
args[1] = b._key;
break;
case 'wrapKey':
args[1] = b._key, args[2] = c._key;
break;
}
if ( ( isWebkit || ( isIE && ( b.algorithm.hash || {} ).name === 'SHA-1' ) )
&& m === 'exportKey' && a === 'jwk' && b.algorithm.name === 'HMAC' ) {
args[0] = 'raw';
}
if ( isWebkit && m === 'exportKey' && ( a === 'spki' || a === 'pkcs8' ) ) {
args[0] = 'jwk';
}
if ( isIE && m === 'wrapKey' ) {
return _subtle.exportKey( a, b )
.then( function ( k ) {
if ( a === 'jwk' ) k = s2b( unescape( encodeURIComponent( JSON.stringify( b2jwk(k) ) ) ) );
return _subtle.encrypt( args[3], c, k );
});
}
var op;
try {
op = _fn.apply( _subtle, args );
}
catch ( e ) {
return Promise.reject(e);
}
if ( isIE ) {
op = new Promise( function ( res, rej ) {
op.onabort =
op.onerror = function ( e ) { rej(e) };
op.oncomplete = function ( r ) { res(r.target.result) };
});
}
if ( m === 'exportKey' && a === 'jwk' ) {
op = op.then( function ( k ) {
if ( ( isWebkit || ( isIE && ( b.algorithm.hash || {} ).name === 'SHA-1' ) )
&& b.algorithm.name === 'HMAC') {
return { 'kty': 'oct', 'alg': jwkAlg(b.algorithm), 'key_ops': b.usages.slice(), 'ext': true, 'k': s2a( b2s(k) ) };
}
k = b2jwk(k);
if ( !k.alg ) k['alg'] = jwkAlg(b.algorithm);
if ( !k.key_ops ) k['key_ops'] = ( b.type === 'public' ) ? b.usages.filter(isPubKeyUse) : ( b.type === 'private' ) ? b.usages.filter(isPrvKeyUse) : b.usages.slice();
return k;
});
}
if ( isWebkit && m === 'exportKey' && ( a === 'spki' || a === 'pkcs8' ) ) {
op = op.then( function ( k ) {
k = jwk2pkcs( b2jwk(k) );
return k;
});
}
return op;
}
});
[ 'encrypt', 'decrypt', 'sign', 'verify' ]
.forEach( function ( m ) {
var _fn = _subtle[m];
_subtle[m] = function ( a, b, c, d ) {
if ( isIE && ( !c.byteLength || ( d && !d.byteLength ) ) )
throw new Error("Empy input is not allowed");
var args = [].slice.call(arguments),
ka = alg(a);
if ( isIE && m === 'decrypt' && ka.name === 'AES-GCM' ) {
var tl = a.tagLength >> 3;
args[2] = (c.buffer || c).slice( 0, c.byteLength - tl ),
a.tag = (c.buffer || c).slice( c.byteLength - tl );
}
args[1] = b._key;
var op;
try {
op = _fn.apply( _subtle, args );
}
catch ( e ) {
return Promise.reject(e);
}
if ( isIE ) {
op = new Promise( function ( res, rej ) {
op.onabort =
op.onerror = function ( e ) {
rej(e);
};
op.oncomplete = function ( r ) {
var r = r.target.result;
if ( m === 'encrypt' && r instanceof AesGcmEncryptResult ) {
var c = r.ciphertext, t = r.tag;
r = new Uint8Array( c.byteLength + t.byteLength );
r.set( new Uint8Array(c), 0 );
r.set( new Uint8Array(t), c.byteLength );
r = r.buffer;
}
res(r);
};
});
}
return op;
}
});
if ( isIE ) {
var _digest = _subtle.digest;
_subtle['digest'] = function ( a, b ) {
if ( !b.byteLength )
throw new Error("Empy input is not allowed");
var op;
try {
op = _digest.call( _subtle, a, b );
}
catch ( e ) {
return Promise.reject(e);
}
op = new Promise( function ( res, rej ) {
op.onabort =
op.onerror = function ( e ) { rej(e) };
op.oncomplete = function ( r ) { res(r.target.result) };
});
return op;
};
global.crypto = Object.create( _crypto, {
getRandomValues: { value: function ( a ) { return _crypto.getRandomValues(a) } },
subtle: { value: _subtle },
});
global.CryptoKey = CryptoKey;
}
if ( isWebkit ) {
_crypto.subtle = _subtle;
global.Crypto = _Crypto;
global.SubtleCrypto = _SubtleCrypto;
global.CryptoKey = CryptoKey;
}
}(this);

View File

@ -169,6 +169,7 @@
<script src="/vendor/modernizr-custom.min.js"></script>
<script src="/vendor/autosize.min.js"></script>
<script src="/vendor/bootstrap-switch.min.js"></script>
<script src="/vendor/web-crypto-shim.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fastclick/1.0.6/fastclick.min.js"></script>
<script src="/main.js"></script>
<script>