UNPKG

r2-lcp-js

Version:

Readium 2 LCP bits for NodeJS (TypeScript)

282 lines 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.supports = supports; exports.transformStream = transformStream; exports.getDecryptedSizeStream = getDecryptedSizeStream; const tslib_1 = require("tslib"); const crypto = require("crypto"); const debug_ = require("debug"); const zlib = require("zlib"); const BufferUtils_1 = require("r2-utils-js/dist/es7-es2016/src/_utils/stream/BufferUtils"); const RangeStream_1 = require("r2-utils-js/dist/es7-es2016/src/_utils/stream/RangeStream"); const debug = debug_("r2:lcp#transform/transformer-lcp"); const IS_DEV = (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev"); const AES_BLOCK_SIZE = 16; const readStream = (s, n) => tslib_1.__awaiter(void 0, void 0, void 0, function* () { return new Promise((resolve, reject) => { const onReadable = () => { const b = s.read(n); s.removeListener("readable", onReadable); s.removeListener("error", reject); resolve(b); }; s.on("readable", onReadable); s.on("error", reject); }); }); function supports(lcp, _linkHref, linkPropertiesEncrypted) { if (!lcp) { return false; } if (!linkPropertiesEncrypted) { return false; } if (!lcp.isReady()) { debug("LCP not ready!"); return false; } const check = linkPropertiesEncrypted.Algorithm === "http://www.w3.org/2001/04/xmlenc#aes256-cbc" && ((linkPropertiesEncrypted.Scheme === "http://readium.org/2014/01/lcp" && (linkPropertiesEncrypted.Profile === "http://readium.org/lcp/basic-profile" || linkPropertiesEncrypted.Profile === "http://readium.org/lcp/profile-1.0" || (linkPropertiesEncrypted.Profile && /^http:\/\/readium\.org\/lcp\/profile-2\.[0-9]$/.test(linkPropertiesEncrypted.Profile)))) || (lcp.Encryption.Profile === "http://readium.org/lcp/basic-profile" || lcp.Encryption.Profile === "http://readium.org/lcp/profile-1.0" || (lcp.Encryption.Profile && /^http:\/\/readium\.org\/lcp\/profile-2\.[0-9]$/.test(lcp.Encryption.Profile)))); if (!check) { return false; } return true; } function transformStream(lcp, linkHref, linkPropertiesEncrypted, stream, isPartialByteRangeRequest, partialByteBegin, partialByteEnd) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const isCompressionNone = linkPropertiesEncrypted.Compression === "none"; const isCompressionDeflate = linkPropertiesEncrypted.Compression === "deflate"; let plainTextSize = -1; let nativelyDecryptedStream; let nativelyInflated = false; if (lcp.isNativeNodePlugin()) { if (IS_DEV) { debug("LCP DECRYPT NATIVE: " + linkHref); } let fullEncryptedBuffer; try { fullEncryptedBuffer = yield (0, BufferUtils_1.streamToBufferPromise)(stream.stream); } catch (err) { debug(err); return Promise.reject("OUCH!"); } let res; try { res = yield lcp.decrypt(fullEncryptedBuffer, linkHref, isCompressionDeflate); } catch (err) { debug(err); return Promise.reject("OUCH!"); } const nativelyDecryptedBuffer = res.buffer; nativelyInflated = res.inflated; plainTextSize = nativelyDecryptedBuffer.length; linkPropertiesEncrypted.DecryptedLengthBeforeInflate = plainTextSize; if (!nativelyInflated && linkPropertiesEncrypted.OriginalLength && isCompressionNone && linkPropertiesEncrypted.OriginalLength !== plainTextSize) { debug("############### LCP transformStream() LENGTH NOT MATCH linkPropertiesEncrypted.OriginalLength !== plainTextSize: " + `${linkPropertiesEncrypted.OriginalLength} !== ${plainTextSize}`); } nativelyDecryptedStream = (0, BufferUtils_1.bufferToStream)(nativelyDecryptedBuffer); } else { let cryptoInfo; let cypherBlockPadding = -1; if (linkPropertiesEncrypted.DecryptedLengthBeforeInflate > 0) { plainTextSize = linkPropertiesEncrypted.DecryptedLengthBeforeInflate; cypherBlockPadding = linkPropertiesEncrypted.CypherBlockPadding; } else { try { cryptoInfo = yield getDecryptedSizeStream(lcp, stream); } catch (err) { debug(err); return Promise.reject(err); } plainTextSize = cryptoInfo.length; cypherBlockPadding = cryptoInfo.padding; linkPropertiesEncrypted.DecryptedLengthBeforeInflate = plainTextSize; linkPropertiesEncrypted.CypherBlockPadding = cypherBlockPadding; try { stream = yield stream.reset(); } catch (err) { debug(err); return Promise.reject(err); } if (linkPropertiesEncrypted.OriginalLength && isCompressionNone && linkPropertiesEncrypted.OriginalLength !== plainTextSize) { debug("############### LCP transformStream() LENGTH NOT MATCH linkPropertiesEncrypted.OriginalLength !== plainTextSize: " + `${linkPropertiesEncrypted.OriginalLength} !== ${plainTextSize}`); } } } let destStream; if (nativelyDecryptedStream) { destStream = nativelyDecryptedStream; } else { let rawDecryptStream; let ivBuffer; if (linkPropertiesEncrypted.CypherBlockIV) { ivBuffer = Buffer.from(linkPropertiesEncrypted.CypherBlockIV, "binary"); const cypherRangeStream = new RangeStream_1.RangeStream(AES_BLOCK_SIZE, stream.length - 1, stream.length); stream.stream.pipe(cypherRangeStream); rawDecryptStream = cypherRangeStream; } else { try { ivBuffer = yield readStream(stream.stream, AES_BLOCK_SIZE); } catch (err) { debug(err); return Promise.reject(err); } linkPropertiesEncrypted.CypherBlockIV = ivBuffer.toString("binary"); stream.stream.resume(); rawDecryptStream = stream.stream; } const decryptStream = crypto.createDecipheriv("aes-256-cbc", lcp.ContentKey, ivBuffer); decryptStream.setAutoPadding(false); rawDecryptStream.pipe(decryptStream); destStream = decryptStream; if (linkPropertiesEncrypted.CypherBlockPadding) { const cypherUnpaddedStream = new RangeStream_1.RangeStream(0, plainTextSize - 1, plainTextSize); destStream.pipe(cypherUnpaddedStream); destStream = cypherUnpaddedStream; } } if (!nativelyInflated && isCompressionDeflate) { const inflateStream = zlib.createInflateRaw(); destStream.pipe(inflateStream); destStream = inflateStream; if (!linkPropertiesEncrypted.OriginalLength) { debug("############### RESOURCE ENCRYPTED OVER DEFLATE, BUT NO OriginalLength!"); let fullDeflatedBuffer; try { fullDeflatedBuffer = yield (0, BufferUtils_1.streamToBufferPromise)(destStream); linkPropertiesEncrypted.OriginalLength = fullDeflatedBuffer.length; destStream = (0, BufferUtils_1.bufferToStream)(fullDeflatedBuffer); } catch (err) { debug(err); } } } if (partialByteBegin < 0) { partialByteBegin = 0; } if (partialByteEnd < 0) { partialByteEnd = plainTextSize - 1; if (linkPropertiesEncrypted.OriginalLength) { partialByteEnd = linkPropertiesEncrypted.OriginalLength - 1; } } const l = (!nativelyInflated && linkPropertiesEncrypted.OriginalLength) ? linkPropertiesEncrypted.OriginalLength : plainTextSize; if (isPartialByteRangeRequest) { const rangeStream = new RangeStream_1.RangeStream(partialByteBegin, partialByteEnd, l); destStream.pipe(rangeStream); destStream = rangeStream; } const sal = { length: l, reset: () => tslib_1.__awaiter(this, void 0, void 0, function* () { let resetedStream; try { resetedStream = yield stream.reset(); } catch (err) { debug(err); return Promise.reject(err); } return transformStream(lcp, linkHref, linkPropertiesEncrypted, resetedStream, isPartialByteRangeRequest, partialByteBegin, partialByteEnd); }), stream: destStream, }; return Promise.resolve(sal); }); } function getDecryptedSizeStream(lcp, stream) { return tslib_1.__awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () { const TWO_AES_BLOCK_SIZE = 2 * AES_BLOCK_SIZE; if (stream.length < TWO_AES_BLOCK_SIZE) { reject("crypto err"); return; } const readPos = stream.length - TWO_AES_BLOCK_SIZE; const cypherRangeStream = new RangeStream_1.RangeStream(readPos, readPos + TWO_AES_BLOCK_SIZE - 1, stream.length); stream.stream.pipe(cypherRangeStream); const decrypteds = []; const handle = (ivBuffer, encrypted) => { const decryptStream = crypto.createDecipheriv("aes-256-cbc", lcp.ContentKey, ivBuffer); decryptStream.setAutoPadding(false); const buff1 = decryptStream.update(encrypted); if (buff1) { decrypteds.push(buff1); } const buff2 = decryptStream.final(); if (buff2) { decrypteds.push(buff2); } finish(); }; let finished = false; const finish = () => { if (finished) { return; } finished = true; const decrypted = Buffer.concat(decrypteds); if (decrypted.length !== AES_BLOCK_SIZE) { reject("decrypted.length !== AES_BLOCK_SIZE"); return; } const nPaddingBytes = decrypted[AES_BLOCK_SIZE - 1]; const size = stream.length - AES_BLOCK_SIZE - nPaddingBytes; const res = { length: size, padding: nPaddingBytes, }; resolve(res); }; try { const buf = yield readStream(cypherRangeStream, TWO_AES_BLOCK_SIZE); if (!buf) { reject("!buf (end?)"); return; } if (buf.length !== TWO_AES_BLOCK_SIZE) { reject("buf.length !== TWO_AES_BLOCK_SIZE"); return; } handle(buf.slice(0, AES_BLOCK_SIZE), buf.slice(AES_BLOCK_SIZE)); } catch (err) { debug(err); reject(err); return; } })); }); } //# sourceMappingURL=transformer-lcp.js.map