Streaming encryption (Web)
This commit is contained in:
parent
9302fdcc56
commit
9853d3d830
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
import Rusha from 'rusha';
|
import Rusha from 'rusha';
|
||||||
import { SHA256 } from 'asmcrypto.js/src/hash/sha256/exports';
|
import { SHA256 } from 'asmcrypto.js/src/hash/sha256/exports';
|
||||||
|
import sha1 from 'hash.js/lib/hash/sha/1';
|
||||||
import sha224 from 'hash.js/lib/hash/sha/224';
|
import sha224 from 'hash.js/lib/hash/sha/224';
|
||||||
import sha384 from 'hash.js/lib/hash/sha/384';
|
import sha384 from 'hash.js/lib/hash/sha/384';
|
||||||
import sha512 from 'hash.js/lib/hash/sha/512';
|
import sha512 from 'hash.js/lib/hash/sha/512';
|
||||||
|
@ -34,7 +35,14 @@ function node_hash(type) {
|
||||||
|
|
||||||
function hashjs_hash(hash) {
|
function hashjs_hash(hash) {
|
||||||
return function(data) {
|
return function(data) {
|
||||||
return util.hex_to_Uint8Array(hash().update(data).digest('hex'));
|
const hashInstance = hash();
|
||||||
|
return data.transform((done, value) => {
|
||||||
|
if (!done) {
|
||||||
|
hashInstance.update(value);
|
||||||
|
} else {
|
||||||
|
return util.hex_to_Uint8Array(hashInstance.digest('hex'));
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,9 +60,10 @@ if (nodeCrypto) { // Use Node native crypto for all hash functions
|
||||||
} else { // Use JS fallbacks
|
} else { // Use JS fallbacks
|
||||||
hash_fns = {
|
hash_fns = {
|
||||||
md5: md5,
|
md5: md5,
|
||||||
sha1: function(data) {
|
sha1: hashjs_hash(sha1),
|
||||||
|
/*sha1: function(data) {
|
||||||
return util.hex_to_Uint8Array(rusha.digest(data));
|
return util.hex_to_Uint8Array(rusha.digest(data));
|
||||||
},
|
},*/
|
||||||
sha224: hashjs_hash(sha224),
|
sha224: hashjs_hash(sha224),
|
||||||
sha256: SHA256.bytes,
|
sha256: SHA256.bytes,
|
||||||
sha384: hashjs_hash(sha384),
|
sha384: hashjs_hash(sha384),
|
||||||
|
|
|
@ -117,12 +117,15 @@ function addheader(customComment) {
|
||||||
/**
|
/**
|
||||||
* Calculates a checksum over the given data and returns it base64 encoded
|
* Calculates a checksum over the given data and returns it base64 encoded
|
||||||
* @param {String} data Data to create a CRC-24 checksum for
|
* @param {String} data Data to create a CRC-24 checksum for
|
||||||
* @returns {String} Base64 encoded checksum
|
* @returns {Uint8Array} Base64 encoded checksum
|
||||||
*/
|
*/
|
||||||
function getCheckSum(data) {
|
function getCheckSum(data) {
|
||||||
const c = createcrc24(data);
|
const crc = createcrc24(data);
|
||||||
const bytes = new Uint8Array([c >> 16, (c >> 8) & 0xFF, c & 0xFF]);
|
return base64.encode(crc);
|
||||||
return base64.encode(bytes);
|
}
|
||||||
|
|
||||||
|
function getCheckSumString(data) {
|
||||||
|
return util.Uint8Array_to_str(getCheckSum(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,10 +136,11 @@ function getCheckSum(data) {
|
||||||
* @returns {Boolean} True if the given checksum is correct; otherwise false
|
* @returns {Boolean} True if the given checksum is correct; otherwise false
|
||||||
*/
|
*/
|
||||||
function verifyCheckSum(data, checksum) {
|
function verifyCheckSum(data, checksum) {
|
||||||
const c = getCheckSum(data);
|
const c = getCheckSumString(data);
|
||||||
const d = checksum;
|
const d = checksum;
|
||||||
return c[0] === d[0] && c[1] === d[1] && c[2] === d[2] && c[3] === d[3];
|
return c[0] === d[0] && c[1] === d[1] && c[2] === d[2] && c[3] === d[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal function to calculate a CRC-24 checksum over a given string (data)
|
* Internal function to calculate a CRC-24 checksum over a given string (data)
|
||||||
* @param {String} data Data to create a CRC-24 checksum for
|
* @param {String} data Data to create a CRC-24 checksum for
|
||||||
|
@ -179,11 +183,16 @@ const crc_table = [
|
||||||
|
|
||||||
function createcrc24(input) {
|
function createcrc24(input) {
|
||||||
let crc = 0xB704CE;
|
let crc = 0xB704CE;
|
||||||
|
return input.transform((done, value) => {
|
||||||
for (let index = 0; index < input.length; index++) {
|
if (!done) {
|
||||||
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ input[index]) & 0xff];
|
for (let index = 0; index < value.length; index++) {
|
||||||
|
crc = (crc << 8) ^ crc_table[((crc >> 16) ^ value[index]) & 0xff];
|
||||||
}
|
}
|
||||||
return crc & 0xffffff;
|
} else {
|
||||||
|
crc &= 0xffffff;
|
||||||
|
return new Uint8Array([crc >> 16, (crc >> 8) & 0xFF, crc & 0xFF]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -315,7 +324,7 @@ function dearmor(text) {
|
||||||
if (!verifyCheckSum(result.data, checksum) && (checksum || config.checksum_required)) {
|
if (!verifyCheckSum(result.data, checksum) && (checksum || config.checksum_required)) {
|
||||||
// will NOT throw error if checksum is empty AND checksum is not required (GPG compatibility)
|
// will NOT throw error if checksum is empty AND checksum is not required (GPG compatibility)
|
||||||
throw new Error("Ascii armor integrity check on message failed: '" + checksum + "' should be '" +
|
throw new Error("Ascii armor integrity check on message failed: '" + checksum + "' should be '" +
|
||||||
getCheckSum(result.data) + "'");
|
getCheckSumString(result.data) + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyHeaders(result.headers);
|
verifyHeaders(result.headers);
|
||||||
|
@ -335,63 +344,70 @@ function dearmor(text) {
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
function armor(messagetype, body, partindex, parttotal, customComment) {
|
function armor(messagetype, body, partindex, parttotal, customComment) {
|
||||||
|
let text;
|
||||||
|
if (messagetype === enums.armor.signed) {
|
||||||
|
text = body.text;
|
||||||
|
body = body.data;
|
||||||
|
}
|
||||||
|
let bodyClone;
|
||||||
|
[body, bodyClone] = body.tee();
|
||||||
const result = [];
|
const result = [];
|
||||||
switch (messagetype) {
|
switch (messagetype) {
|
||||||
case enums.armor.multipart_section:
|
case enums.armor.multipart_section:
|
||||||
result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
|
result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=" + getCheckSum(body) + "\r\n");
|
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
||||||
result.push("-----END PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
|
result.push("-----END PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
|
||||||
break;
|
break;
|
||||||
case enums.armor.multipart_last:
|
case enums.armor.multipart_last:
|
||||||
result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "-----\r\n");
|
result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=" + getCheckSum(body) + "\r\n");
|
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
||||||
result.push("-----END PGP MESSAGE, PART " + partindex + "-----\r\n");
|
result.push("-----END PGP MESSAGE, PART " + partindex + "-----\r\n");
|
||||||
break;
|
break;
|
||||||
case enums.armor.signed:
|
case enums.armor.signed:
|
||||||
result.push("\r\n-----BEGIN PGP SIGNED MESSAGE-----\r\n");
|
result.push("\r\n-----BEGIN PGP SIGNED MESSAGE-----\r\n");
|
||||||
result.push("Hash: " + body.hash + "\r\n\r\n");
|
result.push("Hash: " + body.hash + "\r\n\r\n");
|
||||||
result.push(body.text.replace(/^-/mg, "- -"));
|
result.push(text.replace(/^-/mg, "- -"));
|
||||||
result.push("\r\n-----BEGIN PGP SIGNATURE-----\r\n");
|
result.push("\r\n-----BEGIN PGP SIGNATURE-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body.data));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=" + getCheckSum(body.data) + "\r\n");
|
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
||||||
result.push("-----END PGP SIGNATURE-----\r\n");
|
result.push("-----END PGP SIGNATURE-----\r\n");
|
||||||
break;
|
break;
|
||||||
case enums.armor.message:
|
case enums.armor.message:
|
||||||
result.push("-----BEGIN PGP MESSAGE-----\r\n");
|
result.push("-----BEGIN PGP MESSAGE-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=" + getCheckSum(body) + "\r\n");
|
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
||||||
result.push("-----END PGP MESSAGE-----\r\n");
|
result.push("-----END PGP MESSAGE-----\r\n");
|
||||||
break;
|
break;
|
||||||
case enums.armor.public_key:
|
case enums.armor.public_key:
|
||||||
result.push("-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n");
|
result.push("-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=" + getCheckSum(body) + "\r\n");
|
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
||||||
result.push("-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n");
|
result.push("-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n");
|
||||||
break;
|
break;
|
||||||
case enums.armor.private_key:
|
case enums.armor.private_key:
|
||||||
result.push("-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n");
|
result.push("-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=" + getCheckSum(body) + "\r\n");
|
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
||||||
result.push("-----END PGP PRIVATE KEY BLOCK-----\r\n");
|
result.push("-----END PGP PRIVATE KEY BLOCK-----\r\n");
|
||||||
break;
|
break;
|
||||||
case enums.armor.signature:
|
case enums.armor.signature:
|
||||||
result.push("-----BEGIN PGP SIGNATURE-----\r\n");
|
result.push("-----BEGIN PGP SIGNATURE-----\r\n");
|
||||||
result.push(addheader(customComment));
|
result.push(addheader(customComment));
|
||||||
result.push(base64.encode(body));
|
result.push(base64.encode(body));
|
||||||
result.push("\r\n=" + getCheckSum(body) + "\r\n");
|
result.push("\r\n=", getCheckSum(bodyClone), "\r\n");
|
||||||
result.push("-----END PGP SIGNATURE-----\r\n");
|
result.push("-----END PGP SIGNATURE-----\r\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.join('');
|
return util.concatUint8Array(result.map(part => (util.isString(part) ? util.str_to_Uint8Array(part) : part)));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -12,9 +12,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @requires util
|
||||||
* @module encoding/base64
|
* @module encoding/base64
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import util from '../util';
|
||||||
|
|
||||||
const b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; // Standard radix-64
|
const b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; // Standard radix-64
|
||||||
const b64u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; // URL-safe radix-64
|
const b64u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; // URL-safe radix-64
|
||||||
|
|
||||||
|
@ -30,14 +33,17 @@ function s2r(t, u = false) {
|
||||||
const b64 = u ? b64u : b64s;
|
const b64 = u ? b64u : b64s;
|
||||||
let a;
|
let a;
|
||||||
let c;
|
let c;
|
||||||
let n;
|
|
||||||
const r = [];
|
|
||||||
let l = 0;
|
let l = 0;
|
||||||
let s = 0;
|
let s = 0;
|
||||||
const tl = t.length;
|
|
||||||
|
|
||||||
for (n = 0; n < tl; n++) {
|
return t.transform((done, value) => {
|
||||||
c = t[n];
|
const r = [];
|
||||||
|
|
||||||
|
if (!done) {
|
||||||
|
const tl = value.length;
|
||||||
|
for (let n = 0; n < tl; n++) {
|
||||||
|
c = value[n];
|
||||||
if (s === 0) {
|
if (s === 0) {
|
||||||
r.push(b64.charAt((c >> 2) & 63));
|
r.push(b64.charAt((c >> 2) & 63));
|
||||||
a = (c & 3) << 4;
|
a = (c & 3) << 4;
|
||||||
|
@ -62,6 +68,7 @@ function s2r(t, u = false) {
|
||||||
s = 0;
|
s = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if (s > 0) {
|
if (s > 0) {
|
||||||
r.push(b64.charAt(a));
|
r.push(b64.charAt(a));
|
||||||
l += 1;
|
l += 1;
|
||||||
|
@ -79,7 +86,9 @@ function s2r(t, u = false) {
|
||||||
}
|
}
|
||||||
r.push('=');
|
r.push('=');
|
||||||
}
|
}
|
||||||
return r.join('');
|
}
|
||||||
|
return util.str_to_Uint8Array(r.join(''));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -100,6 +100,12 @@ export { default as KDFParams } from './type/kdf_params';
|
||||||
*/
|
*/
|
||||||
export { default as OID } from './type/oid';
|
export { default as OID } from './type/oid';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see module:type/oid
|
||||||
|
* @name module:openpgp.OID
|
||||||
|
*/
|
||||||
|
export { default as Stream } from './type/stream';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see module:encoding/armor
|
* @see module:encoding/armor
|
||||||
* @name module:openpgp.armor
|
* @name module:openpgp.armor
|
||||||
|
|
|
@ -674,7 +674,7 @@ export function fromText(text, filename, date=new Date(), type='utf8') {
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
export function fromBinary(bytes, filename, date=new Date(), type='binary') {
|
export function fromBinary(bytes, filename, date=new Date(), type='binary') {
|
||||||
if (!util.isUint8Array(bytes)) {
|
if (!util.isUint8Array(bytes) && !util.isStream(bytes)) {
|
||||||
throw new Error('Data must be in the form of a Uint8Array');
|
throw new Error('Data must be in the form of a Uint8Array');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -536,8 +536,8 @@ function checkBinary(data, name) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function checkData(data, name) {
|
function checkData(data, name) {
|
||||||
if (!util.isUint8Array(data) && !util.isString(data)) {
|
if (!util.isUint8Array(data) && !util.isString(data) && !util.isStream(data)) {
|
||||||
throw new Error('Parameter [' + (name || 'data') + '] must be of type String or Uint8Array');
|
throw new Error('Parameter [' + (name || 'data') + '] must be of type String, Uint8Array or ReadableStream');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function checkMessage(message) {
|
function checkMessage(message) {
|
||||||
|
@ -573,7 +573,7 @@ function toArray(param) {
|
||||||
*/
|
*/
|
||||||
function createMessage(data, filename, date=new Date(), type) {
|
function createMessage(data, filename, date=new Date(), type) {
|
||||||
let msg;
|
let msg;
|
||||||
if (util.isUint8Array(data)) {
|
if (util.isUint8Array(data) || util.isStream(data)) {
|
||||||
msg = messageLib.fromBinary(data, filename, date, type);
|
msg = messageLib.fromBinary(data, filename, date, type);
|
||||||
} else if (util.isString(data)) {
|
} else if (util.isString(data)) {
|
||||||
msg = messageLib.fromText(data, filename, date, type);
|
msg = messageLib.fromText(data, filename, date, type);
|
||||||
|
|
|
@ -22,7 +22,9 @@
|
||||||
* @requires util
|
* @requires util
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AES_CFB } from 'asmcrypto.js/src/aes/cfb/exports';
|
import { _AES_asm_instance, _AES_heap_instance } from 'asmcrypto.js/src/aes/exports';
|
||||||
|
import { AES_CFB, AES_CFB_Decrypt, AES_CFB_Encrypt } from 'asmcrypto.js/src/aes/cfb/exports';
|
||||||
|
|
||||||
import crypto from '../crypto';
|
import crypto from '../crypto';
|
||||||
import enums from '../enums';
|
import enums from '../enums';
|
||||||
import util from '../util';
|
import util from '../util';
|
||||||
|
@ -89,12 +91,12 @@ SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlg
|
||||||
const prefix = util.concatUint8Array([prefixrandom, repeat]);
|
const prefix = util.concatUint8Array([prefixrandom, repeat]);
|
||||||
const mdc = new Uint8Array([0xD3, 0x14]); // modification detection code packet
|
const mdc = new Uint8Array([0xD3, 0x14]); // modification detection code packet
|
||||||
|
|
||||||
let tohash = util.concatUint8Array([bytes, mdc]);
|
let [tohash, tohashClone] = util.concatUint8Array([bytes, mdc]).tee();
|
||||||
const hash = crypto.hash.sha1(util.concatUint8Array([prefix, tohash]));
|
const hash = crypto.hash.sha1(util.concatUint8Array([prefix, tohashClone]));
|
||||||
tohash = util.concatUint8Array([tohash, hash]);
|
tohash = util.concatUint8Array([tohash, hash]);
|
||||||
|
|
||||||
if (sessionKeyAlgorithm.substr(0, 3) === 'aes') { // AES optimizations. Native code for node, asmCrypto for browser.
|
if (sessionKeyAlgorithm.substr(0, 3) === 'aes') { // AES optimizations. Native code for node, asmCrypto for browser.
|
||||||
this.encrypted = aesEncrypt(sessionKeyAlgorithm, prefix, tohash, key);
|
this.encrypted = aesEncrypt(sessionKeyAlgorithm, util.concatUint8Array([prefix, tohash]), key);
|
||||||
} else {
|
} else {
|
||||||
this.encrypted = crypto.cfb.encrypt(prefixrandom, sessionKeyAlgorithm, tohash, key, false);
|
this.encrypted = crypto.cfb.encrypt(prefixrandom, sessionKeyAlgorithm, tohash, key, false);
|
||||||
this.encrypted = this.encrypted.subarray(0, prefix.length + tohash.length);
|
this.encrypted = this.encrypted.subarray(0, prefix.length + tohash.length);
|
||||||
|
@ -144,11 +146,17 @@ export default SymEncryptedIntegrityProtected;
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
|
||||||
|
|
||||||
function aesEncrypt(algo, prefix, pt, key) {
|
function aesEncrypt(algo, pt, key) {
|
||||||
if (nodeCrypto) { // Node crypto library.
|
if (nodeCrypto) { // Node crypto library.
|
||||||
return nodeEncrypt(algo, prefix, pt, key);
|
return nodeEncrypt(algo, pt, key);
|
||||||
} // asm.js fallback
|
} // asm.js fallback
|
||||||
return AES_CFB.encrypt(util.concatUint8Array([prefix, pt]), key);
|
const cfb = new AES_CFB_Encrypt(key, undefined, _AES_heap_instance, _AES_asm_instance);
|
||||||
|
return pt.transform((done, value) => {
|
||||||
|
if (!done) {
|
||||||
|
return cfb.process(value).result;
|
||||||
|
}
|
||||||
|
return cfb.finish().result;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function aesDecrypt(algo, ct, key) {
|
function aesDecrypt(algo, ct, key) {
|
||||||
|
|
87
src/type/stream.js
Normal file
87
src/type/stream.js
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import util from '../util';
|
||||||
|
|
||||||
|
function concat(arrays) {
|
||||||
|
const readers = arrays.map(entry => entry.getReader());
|
||||||
|
let current = 0;
|
||||||
|
return new ReadableStream({
|
||||||
|
async pull(controller) {
|
||||||
|
const { done, value } = await readers[current].read();
|
||||||
|
if (!done) {
|
||||||
|
controller.enqueue(value);
|
||||||
|
} else if (++current === arrays.length) {
|
||||||
|
controller.close();
|
||||||
|
} else {
|
||||||
|
await this.pull(controller); // ??? Chrome bug?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { concat };
|
||||||
|
|
||||||
|
|
||||||
|
/*const readerAcquiredMap = new Map();
|
||||||
|
|
||||||
|
const _getReader = ReadableStream.prototype.getReader;
|
||||||
|
ReadableStream.prototype.getReader = function() {
|
||||||
|
if (readerAcquiredMap.has(this)) {
|
||||||
|
console.error(readerAcquiredMap.get(this));
|
||||||
|
} else {
|
||||||
|
readerAcquiredMap.set(this, new Error('Reader for this ReadableStream already acquired here.'));
|
||||||
|
}
|
||||||
|
return _getReader.apply(this, arguments);
|
||||||
|
};*/
|
||||||
|
|
||||||
|
|
||||||
|
ReadableStream.prototype.transform = function(fn) {
|
||||||
|
const reader = this.getReader();
|
||||||
|
return new ReadableStream({
|
||||||
|
async pull(controller) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
const result = fn(done, value);
|
||||||
|
if (result) controller.enqueue(result);
|
||||||
|
if (done) controller.close();
|
||||||
|
if (!done && !result) await this.pull(controller); // ??? Chrome bug?
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ReadableStream.prototype.readToEnd = async function() {
|
||||||
|
const reader = this.getReader();
|
||||||
|
const result = [];
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
result.push(value);
|
||||||
|
}
|
||||||
|
return util.concatUint8Array(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Uint8Array.prototype.getReader = function() {
|
||||||
|
let doneReading = false;
|
||||||
|
return {
|
||||||
|
read: async () => {
|
||||||
|
if (doneReading) {
|
||||||
|
return { value: undefined, done: true };
|
||||||
|
}
|
||||||
|
doneReading = true;
|
||||||
|
return { value: this, done: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Uint8Array.prototype.transform = function(fn) {
|
||||||
|
const result1 = fn(false, this);
|
||||||
|
const result2 = fn(true, undefined);
|
||||||
|
if (result1 && result2) return util.concatUint8Array([result1, result2]);
|
||||||
|
return result1 || result2;
|
||||||
|
};
|
||||||
|
|
||||||
|
Uint8Array.prototype.tee = function() {
|
||||||
|
return [this, this];
|
||||||
|
};
|
||||||
|
|
||||||
|
Uint8Array.prototype.readToEnd = async function() {
|
||||||
|
return this;
|
||||||
|
};
|
16
src/util.js
16
src/util.js
|
@ -29,6 +29,7 @@ import rfc2822 from 'address-rfc2822';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
import util from './util'; // re-import module to access util functions
|
import util from './util'; // re-import module to access util functions
|
||||||
import b64 from './encoding/base64';
|
import b64 from './encoding/base64';
|
||||||
|
import Stream from './type/stream';
|
||||||
|
|
||||||
const isIE11 = typeof navigator !== 'undefined' && !!navigator.userAgent.match(/Trident\/7\.0.*rv:([0-9.]+).*\).*Gecko$/);
|
const isIE11 = typeof navigator !== 'undefined' && !!navigator.userAgent.match(/Trident\/7\.0.*rv:([0-9.]+).*\).*Gecko$/);
|
||||||
|
|
||||||
|
@ -45,6 +46,10 @@ export default {
|
||||||
return Uint8Array.prototype.isPrototypeOf(data);
|
return Uint8Array.prototype.isPrototypeOf(data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isStream: function(data) {
|
||||||
|
return ReadableStream.prototype.isPrototypeOf(data);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get transferable objects to pass buffers with zero copy (similar to "pass by reference" in C++)
|
* Get transferable objects to pass buffers with zero copy (similar to "pass by reference" in C++)
|
||||||
* See: https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage
|
* See: https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage
|
||||||
|
@ -282,6 +287,9 @@ export default {
|
||||||
concatUint8Array: function (arrays) {
|
concatUint8Array: function (arrays) {
|
||||||
let totalLength = 0;
|
let totalLength = 0;
|
||||||
for (let i = 0; i < arrays.length; i++) {
|
for (let i = 0; i < arrays.length; i++) {
|
||||||
|
if (util.isStream(arrays[i])) {
|
||||||
|
return Stream.concat(arrays);
|
||||||
|
}
|
||||||
if (!util.isUint8Array(arrays[i])) {
|
if (!util.isUint8Array(arrays[i])) {
|
||||||
throw new Error('concatUint8Array: Data must be in the form of a Uint8Array');
|
throw new Error('concatUint8Array: Data must be in the form of a Uint8Array');
|
||||||
}
|
}
|
||||||
|
@ -409,6 +417,14 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
print_entire_stream: function (str, stream) {
|
||||||
|
const teed = stream.tee();
|
||||||
|
teed[1].readToEnd().then(result => {
|
||||||
|
console.log(str + ': ' + util.Uint8Array_to_str(result));
|
||||||
|
});
|
||||||
|
return teed[0];
|
||||||
|
},
|
||||||
|
|
||||||
getLeftNBits: function (array, bitcount) {
|
getLeftNBits: function (array, bitcount) {
|
||||||
const rest = bitcount % 8;
|
const rest = bitcount % 8;
|
||||||
if (rest === 0) {
|
if (rest === 0) {
|
||||||
|
|
|
@ -13,5 +13,6 @@ describe('General', function () {
|
||||||
require('./x25519.js');
|
require('./x25519.js');
|
||||||
require('./brainpool.js');
|
require('./brainpool.js');
|
||||||
require('./decompression.js');
|
require('./decompression.js');
|
||||||
|
require('./streaming.js');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
60
test/general/streaming.js
Normal file
60
test/general/streaming.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp');
|
||||||
|
|
||||||
|
const stub = require('sinon/lib/sinon/stub');
|
||||||
|
const chai = require('chai');
|
||||||
|
chai.use(require('chai-as-promised'));
|
||||||
|
|
||||||
|
const { expect } = chai;
|
||||||
|
|
||||||
|
const { Stream, util } = openpgp;
|
||||||
|
|
||||||
|
describe('Streaming', function() {
|
||||||
|
it('Encrypt small message', async function() {
|
||||||
|
const data = new ReadableStream({
|
||||||
|
async start(controller) {
|
||||||
|
controller.enqueue(util.str_to_Uint8Array('hello '));
|
||||||
|
controller.enqueue(util.str_to_Uint8Array('world'));
|
||||||
|
controller.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const encrypted = await openpgp.encrypt({
|
||||||
|
data,
|
||||||
|
passwords: ['test'],
|
||||||
|
});
|
||||||
|
const msgAsciiArmored = util.Uint8Array_to_str(await encrypted.data.readToEnd());
|
||||||
|
const message = openpgp.message.readArmored(msgAsciiArmored);
|
||||||
|
const decrypted = await openpgp.decrypt({
|
||||||
|
passwords: ['test'],
|
||||||
|
message
|
||||||
|
});
|
||||||
|
expect(decrypted.data).to.equal('hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Encrypt larger message', async function() {
|
||||||
|
let plaintext = [];
|
||||||
|
let i = 0;
|
||||||
|
const data = new ReadableStream({
|
||||||
|
async pull(controller) {
|
||||||
|
if (i++ < 10) {
|
||||||
|
let randomBytes = await openpgp.crypto.random.getRandomBytes(1024);
|
||||||
|
controller.enqueue(randomBytes);
|
||||||
|
plaintext.push(randomBytes);
|
||||||
|
} else {
|
||||||
|
controller.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const encrypted = await openpgp.encrypt({
|
||||||
|
data,
|
||||||
|
passwords: ['test'],
|
||||||
|
});
|
||||||
|
const msgAsciiArmored = util.Uint8Array_to_str(await encrypted.data.readToEnd());
|
||||||
|
const message = openpgp.message.readArmored(msgAsciiArmored);
|
||||||
|
const decrypted = await openpgp.decrypt({
|
||||||
|
passwords: ['test'],
|
||||||
|
message,
|
||||||
|
format: 'binary'
|
||||||
|
});
|
||||||
|
expect(decrypted.data).to.deep.equal(util.concatUint8Array(plaintext));
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user