Skip to content

Commit

Permalink
Experimant: EPUB encryption support test
Browse files Browse the repository at this point in the history
  • Loading branch information
MurakamiShinyu committed Jul 7, 2023
1 parent 9617665 commit 72f109e
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 1 deletion.
83 changes: 83 additions & 0 deletions packages/core/src/vivliostyle/epub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,65 @@ export function makeDeobfuscator(uid: string): (p1: Blob) => Task.Result<Blob> {
};
}

export function makeDecrypter(uid: string): (p1: Blob) => Task.Result<Blob> {
// for test
const hexKey =
"87fb7a5c9358aa21d1ea991d9f17781c20c3d420718f6342592e06b0421a220c";
const rawKey = new Uint8Array(32);
for (let i = 0; i < 32; i++) {
rawKey[i] = parseInt(hexKey.substr(i * 2, 2), 16);
}
return (blob) => {
const frame = Task.newFrame("decrypter") as Task.Frame<Blob>;

makeCryptoKey(rawKey).then((key) => {
Net.readBlob(blob).then((buf) => {
const iv = buf.slice(0, 16);
const cipher = buf.slice(16);

doDecrypt(key, iv, cipher).then((buf) => {
frame.finish(Net.makeBlob([buf]));
});
});
});

return frame.result();
};
}

function makeCryptoKey(rawKey: Uint8Array): Task.Result<CryptoKey> {
const frame: Task.Frame<CryptoKey> = Task.newFrame("makeCryptoKey");
const continuation = frame.suspend();
window.crypto.subtle
.importKey("raw", rawKey, "AES-CBC", true, ["decrypt"])
.then((key) => {
continuation.schedule(key);
});
return frame.result();
}

function doDecrypt(
key: CryptoKey,
iv: ArrayBuffer,
cipher: ArrayBuffer,
): Task.Result<ArrayBuffer> {
const frame: Task.Frame<ArrayBuffer> = Task.newFrame("doDecrypt");
const continuation = frame.suspend();
window.crypto.subtle
.decrypt(
{
name: "AES-CBC",
iv: iv,
},
key,
cipher,
)
.then((buf) => {
continuation.schedule(buf);
});
return frame.result();
}

type RawMeta = {
[key: string]: RawMetaItem[];
};
Expand Down Expand Up @@ -954,6 +1013,24 @@ export class OPFDoc {
.child("CipherData")
.child("CipherReference")
.attribute("URI");
const cryptoURLs = !encXML
? []
: encXML
.doc()
.child("encryption")
.child("EncryptedData")
.predicate(
XmlDoc.predicate.withChild(
"EncryptionMethod",
XmlDoc.predicate.withAttribute(
"Algorithm",
"http://www.w3.org/2001/04/xmlenc#aes256-cbc",
),
),
)
.child("CipherData")
.child("CipherReference")
.attribute("URI");
const mediaTypeElems = pkg
.child("bindings")
.child("mediaType")
Expand Down Expand Up @@ -984,6 +1061,12 @@ export class OPFDoc {
this.store.deobfuscators[this.pubURL + idpfObfURLs[i]] = deobfuscator;
}
}
if (cryptoURLs.length > 0 && this.uid) {
const decrypter = makeDecrypter(this.uid);
for (let i = 0; i < cryptoURLs.length; i++) {
Net.epubDecrypters[this.pubURL + cryptoURLs[i]] = decrypter;
}
}
if (this.prePaginated) {
this.assignAutoPages();
}
Expand Down
46 changes: 46 additions & 0 deletions packages/core/src/vivliostyle/net.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export enum XMLHttpRequestResponseType {

export type Response = Net.Response;

export let epubDecrypters: { [key: string]: (p1: Blob) => Task.Result<Blob> } =
{};

export function ajax(
url: string,
opt_type?: XMLHttpRequestResponseType,
Expand All @@ -47,6 +50,31 @@ export function ajax(
opt_contentType?: string,
): Task.Result<Response> {
const frame: Task.Frame<Response> = Task.newFrame("ajax");

const decrypter =
opt_type === XMLHttpRequestResponseType.BLOB ? null : epubDecrypters?.[url];
if (decrypter) {
ajax(url, XMLHttpRequestResponseType.BLOB).then((xhr1) => {
if (!xhr1.responseBlob) {
frame.finish(xhr1);
return;
}
decrypter(xhr1.responseBlob).then((blob) => {
ajax(
createObjectURL(blob),
opt_type,
opt_method,
opt_data,
opt_contentType,
).then((xhr2) => {
xhr2.url = url;
frame.finish(xhr2);
});
});
});
return frame.result();
}

const request = new XMLHttpRequest();
const continuation = frame.suspend(request);
const response: Response = {
Expand Down Expand Up @@ -322,6 +350,24 @@ export function loadElement(
): TaskUtil.Fetcher<string> {
const fetcher = new TaskUtil.Fetcher(() => {
const frame: Task.Frame<string> = Task.newFrame("loadElement");

const decrypter = epubDecrypters?.[src];
if (decrypter) {
ajax(src, XMLHttpRequestResponseType.BLOB).then((xhr) => {
if (!xhr.responseBlob) {
frame.finish("error");
return;
}
decrypter(xhr.responseBlob).then((blob) => {
const fetcher2 = loadElement(elem, createObjectURL(blob));
fetcher2.get().then((type) => {
frame.finish(type);
});
});
});
return frame.result();
}

const continuation = frame.suspend(elem);
let done = false;
const handler = (evt: Event) => {
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/vivliostyle/vgen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1417,7 +1417,13 @@ export class ViewFactory
}
return false;
};
if (!hasAutoWidth && !hasAutoHeight && !isInsideRunningElement()) {
const decrypter = Net.epubDecrypters?.[delayedSrc];
if (
!decrypter &&
!hasAutoWidth &&
!hasAutoHeight &&
!isInsideRunningElement()
) {
// No need to wait for the image, does not affect layout
this.page.fetchers.push(imageFetcher);
} else {
Expand Down

0 comments on commit 72f109e

Please sign in to comment.