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