UNPKG

10.8 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.getDecryptedSizeStream = exports.transformStream = exports.supports = void 0;
4const crypto = require("crypto");
5const debug_ = require("debug");
6const zlib = require("zlib");
7const BufferUtils_1 = require("r2-utils-js/dist/es8-es2017/src/_utils/stream/BufferUtils");
8const RangeStream_1 = require("r2-utils-js/dist/es8-es2017/src/_utils/stream/RangeStream");
9const debug = debug_("r2:lcp#transform/transformer-lcp");
10const IS_DEV = (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev");
11const AES_BLOCK_SIZE = 16;
12const readStream = async (s, n) => {
13 return new Promise((resolve, reject) => {
14 const onReadable = () => {
15 const b = s.read(n);
16 s.removeListener("readable", onReadable);
17 s.removeListener("error", reject);
18 resolve(b);
19 };
20 s.on("readable", onReadable);
21 s.on("error", reject);
22 });
23};
24function supports(lcp, _linkHref, linkPropertiesEncrypted) {
25 if (!lcp) {
26 return false;
27 }
28 if (!lcp.isReady()) {
29 debug("LCP not ready!");
30 return false;
31 }
32 const check = (linkPropertiesEncrypted.Scheme === "http://readium.org/2014/01/lcp"
33 && (linkPropertiesEncrypted.Profile === "http://readium.org/lcp/basic-profile" ||
34 linkPropertiesEncrypted.Profile === "http://readium.org/lcp/profile-1.0")
35 && linkPropertiesEncrypted.Algorithm === "http://www.w3.org/2001/04/xmlenc#aes256-cbc")
36 ||
37 (linkPropertiesEncrypted.Algorithm === "http://www.w3.org/2001/04/xmlenc#aes256-cbc" &&
38 (lcp.Encryption.Profile === "http://readium.org/lcp/basic-profile" ||
39 lcp.Encryption.Profile === "http://readium.org/lcp/profile-1.0"));
40 if (!check) {
41 return false;
42 }
43 return true;
44}
45exports.supports = supports;
46async function transformStream(lcp, linkHref, linkPropertiesEncrypted, stream, isPartialByteRangeRequest, partialByteBegin, partialByteEnd) {
47 const isCompressionNone = linkPropertiesEncrypted.Compression === "none";
48 const isCompressionDeflate = linkPropertiesEncrypted.Compression === "deflate";
49 let plainTextSize = -1;
50 let nativelyDecryptedStream;
51 let nativelyInflated = false;
52 if (lcp.isNativeNodePlugin()) {
53 if (IS_DEV) {
54 debug("LCP DECRYPT NATIVE: " + linkHref);
55 }
56 let fullEncryptedBuffer;
57 try {
58 fullEncryptedBuffer = await (0, BufferUtils_1.streamToBufferPromise)(stream.stream);
59 }
60 catch (err) {
61 debug(err);
62 return Promise.reject("OUCH!");
63 }
64 let res;
65 try {
66 res = await lcp.decrypt(fullEncryptedBuffer, linkHref, isCompressionDeflate);
67 }
68 catch (err) {
69 debug(err);
70 return Promise.reject("OUCH!");
71 }
72 const nativelyDecryptedBuffer = res.buffer;
73 nativelyInflated = res.inflated;
74 plainTextSize = nativelyDecryptedBuffer.length;
75 linkPropertiesEncrypted.DecryptedLengthBeforeInflate = plainTextSize;
76 if (!nativelyInflated &&
77 linkPropertiesEncrypted.OriginalLength &&
78 isCompressionNone &&
79 linkPropertiesEncrypted.OriginalLength !== plainTextSize) {
80 debug("############### LCP transformStream() LENGTH NOT MATCH linkPropertiesEncrypted.OriginalLength !== plainTextSize: " +
81 `${linkPropertiesEncrypted.OriginalLength} !== ${plainTextSize}`);
82 }
83 nativelyDecryptedStream = (0, BufferUtils_1.bufferToStream)(nativelyDecryptedBuffer);
84 }
85 else {
86 let cryptoInfo;
87 let cypherBlockPadding = -1;
88 if (linkPropertiesEncrypted.DecryptedLengthBeforeInflate > 0) {
89 plainTextSize = linkPropertiesEncrypted.DecryptedLengthBeforeInflate;
90 cypherBlockPadding = linkPropertiesEncrypted.CypherBlockPadding;
91 }
92 else {
93 try {
94 cryptoInfo = await getDecryptedSizeStream(lcp, stream);
95 }
96 catch (err) {
97 debug(err);
98 return Promise.reject(err);
99 }
100 plainTextSize = cryptoInfo.length;
101 cypherBlockPadding = cryptoInfo.padding;
102 linkPropertiesEncrypted.DecryptedLengthBeforeInflate = plainTextSize;
103 linkPropertiesEncrypted.CypherBlockPadding = cypherBlockPadding;
104 try {
105 stream = await stream.reset();
106 }
107 catch (err) {
108 debug(err);
109 return Promise.reject(err);
110 }
111 if (linkPropertiesEncrypted.OriginalLength &&
112 isCompressionNone &&
113 linkPropertiesEncrypted.OriginalLength !== plainTextSize) {
114 debug("############### LCP transformStream() LENGTH NOT MATCH linkPropertiesEncrypted.OriginalLength !== plainTextSize: " +
115 `${linkPropertiesEncrypted.OriginalLength} !== ${plainTextSize}`);
116 }
117 }
118 }
119 let destStream;
120 if (nativelyDecryptedStream) {
121 destStream = nativelyDecryptedStream;
122 }
123 else {
124 let rawDecryptStream;
125 let ivBuffer;
126 if (linkPropertiesEncrypted.CypherBlockIV) {
127 ivBuffer = Buffer.from(linkPropertiesEncrypted.CypherBlockIV, "binary");
128 const cypherRangeStream = new RangeStream_1.RangeStream(AES_BLOCK_SIZE, stream.length - 1, stream.length);
129 stream.stream.pipe(cypherRangeStream);
130 rawDecryptStream = cypherRangeStream;
131 }
132 else {
133 try {
134 ivBuffer = await readStream(stream.stream, AES_BLOCK_SIZE);
135 }
136 catch (err) {
137 debug(err);
138 return Promise.reject(err);
139 }
140 linkPropertiesEncrypted.CypherBlockIV = ivBuffer.toString("binary");
141 stream.stream.resume();
142 rawDecryptStream = stream.stream;
143 }
144 const decryptStream = crypto.createDecipheriv("aes-256-cbc", lcp.ContentKey, ivBuffer);
145 decryptStream.setAutoPadding(false);
146 rawDecryptStream.pipe(decryptStream);
147 destStream = decryptStream;
148 if (linkPropertiesEncrypted.CypherBlockPadding) {
149 const cypherUnpaddedStream = new RangeStream_1.RangeStream(0, plainTextSize - 1, plainTextSize);
150 destStream.pipe(cypherUnpaddedStream);
151 destStream = cypherUnpaddedStream;
152 }
153 }
154 if (!nativelyInflated && isCompressionDeflate) {
155 const inflateStream = zlib.createInflateRaw();
156 destStream.pipe(inflateStream);
157 destStream = inflateStream;
158 if (!linkPropertiesEncrypted.OriginalLength) {
159 debug("############### RESOURCE ENCRYPTED OVER DEFLATE, BUT NO OriginalLength!");
160 let fullDeflatedBuffer;
161 try {
162 fullDeflatedBuffer = await (0, BufferUtils_1.streamToBufferPromise)(destStream);
163 linkPropertiesEncrypted.OriginalLength = fullDeflatedBuffer.length;
164 destStream = (0, BufferUtils_1.bufferToStream)(fullDeflatedBuffer);
165 }
166 catch (err) {
167 debug(err);
168 }
169 }
170 }
171 if (partialByteBegin < 0) {
172 partialByteBegin = 0;
173 }
174 if (partialByteEnd < 0) {
175 partialByteEnd = plainTextSize - 1;
176 if (linkPropertiesEncrypted.OriginalLength) {
177 partialByteEnd = linkPropertiesEncrypted.OriginalLength - 1;
178 }
179 }
180 const l = (!nativelyInflated && linkPropertiesEncrypted.OriginalLength) ?
181 linkPropertiesEncrypted.OriginalLength : plainTextSize;
182 if (isPartialByteRangeRequest) {
183 const rangeStream = new RangeStream_1.RangeStream(partialByteBegin, partialByteEnd, l);
184 destStream.pipe(rangeStream);
185 destStream = rangeStream;
186 }
187 const sal = {
188 length: l,
189 reset: async () => {
190 let resetedStream;
191 try {
192 resetedStream = await stream.reset();
193 }
194 catch (err) {
195 debug(err);
196 return Promise.reject(err);
197 }
198 return transformStream(lcp, linkHref, linkPropertiesEncrypted, resetedStream, isPartialByteRangeRequest, partialByteBegin, partialByteEnd);
199 },
200 stream: destStream,
201 };
202 return Promise.resolve(sal);
203}
204exports.transformStream = transformStream;
205async function getDecryptedSizeStream(lcp, stream) {
206 return new Promise(async (resolve, reject) => {
207 const TWO_AES_BLOCK_SIZE = 2 * AES_BLOCK_SIZE;
208 if (stream.length < TWO_AES_BLOCK_SIZE) {
209 reject("crypto err");
210 return;
211 }
212 const readPos = stream.length - TWO_AES_BLOCK_SIZE;
213 const cypherRangeStream = new RangeStream_1.RangeStream(readPos, readPos + TWO_AES_BLOCK_SIZE - 1, stream.length);
214 stream.stream.pipe(cypherRangeStream);
215 const decrypteds = [];
216 const handle = (ivBuffer, encrypted) => {
217 const decryptStream = crypto.createDecipheriv("aes-256-cbc", lcp.ContentKey, ivBuffer);
218 decryptStream.setAutoPadding(false);
219 const buff1 = decryptStream.update(encrypted);
220 if (buff1) {
221 decrypteds.push(buff1);
222 }
223 const buff2 = decryptStream.final();
224 if (buff2) {
225 decrypteds.push(buff2);
226 }
227 finish();
228 };
229 let finished = false;
230 const finish = () => {
231 if (finished) {
232 return;
233 }
234 finished = true;
235 const decrypted = Buffer.concat(decrypteds);
236 if (decrypted.length !== AES_BLOCK_SIZE) {
237 reject("decrypted.length !== AES_BLOCK_SIZE");
238 return;
239 }
240 const nPaddingBytes = decrypted[AES_BLOCK_SIZE - 1];
241 const size = stream.length - AES_BLOCK_SIZE - nPaddingBytes;
242 const res = {
243 length: size,
244 padding: nPaddingBytes,
245 };
246 resolve(res);
247 };
248 try {
249 const buf = await readStream(cypherRangeStream, TWO_AES_BLOCK_SIZE);
250 if (!buf) {
251 reject("!buf (end?)");
252 return;
253 }
254 if (buf.length !== TWO_AES_BLOCK_SIZE) {
255 reject("buf.length !== TWO_AES_BLOCK_SIZE");
256 return;
257 }
258 handle(buf.slice(0, AES_BLOCK_SIZE), buf.slice(AES_BLOCK_SIZE));
259 }
260 catch (err) {
261 debug(err);
262 reject(err);
263 return;
264 }
265 });
266}
267exports.getDecryptedSizeStream = getDecryptedSizeStream;
268//# sourceMappingURL=transformer-lcp.js.map
\No newline at end of file