mirror of
https://github.com/foliojs/pdfkit.git
synced 2025-12-08 20:15:54 +00:00
Add PDF security features with ES6
This commit is contained in:
parent
6f0d5ff4ff
commit
f5b6ddd2a9
@ -8,6 +8,7 @@ import fs from 'fs';
|
||||
import PDFObject from './object';
|
||||
import PDFReference from './reference';
|
||||
import PDFPage from './page';
|
||||
import PDFSecurity from './security';
|
||||
import ColorMixin from './mixins/color';
|
||||
import VectorMixin from './mixins/vector';
|
||||
import FontsMixin from './mixins/fonts';
|
||||
@ -22,7 +23,24 @@ class PDFDocument extends stream.Readable {
|
||||
this.options = options;
|
||||
|
||||
// PDF version
|
||||
this.version = 1.3;
|
||||
switch (options.pdfVersion) {
|
||||
case '1.4':
|
||||
this.version = 1.4;
|
||||
break;
|
||||
case '1.5':
|
||||
this.version = 1.5;
|
||||
break;
|
||||
case '1.6':
|
||||
this.version = 1.6;
|
||||
break;
|
||||
case '1.7':
|
||||
case '1.7ext3':
|
||||
this.version = 1.7;
|
||||
break;
|
||||
default:
|
||||
this.version = 1.3;
|
||||
break;
|
||||
}
|
||||
|
||||
// Whether streams should be compressed
|
||||
this.compress = this.options.compress != null ? this.options.compress : true;
|
||||
@ -82,6 +100,12 @@ class PDFDocument extends stream.Readable {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate file ID
|
||||
this._id = PDFSecurity.generateFileID(this.info);
|
||||
|
||||
// Initialize security settings
|
||||
this._security = PDFSecurity.create(this, options);
|
||||
|
||||
// Write the header
|
||||
// PDF version
|
||||
this._write(`%PDF-${this.version}`);
|
||||
@ -213,7 +237,10 @@ Please pipe the document into a Node stream.\
|
||||
val = new String(val);
|
||||
}
|
||||
|
||||
this._info.data[key] = val;
|
||||
let entry = this.ref(val);
|
||||
entry.end();
|
||||
|
||||
this._info.data[key] = entry;
|
||||
}
|
||||
|
||||
this._info.end();
|
||||
@ -224,10 +251,14 @@ Please pipe the document into a Node stream.\
|
||||
}
|
||||
|
||||
this.endOutline();
|
||||
|
||||
|
||||
this._root.end();
|
||||
this._root.data.Pages.end();
|
||||
|
||||
if (this._security) {
|
||||
this._security.end();
|
||||
}
|
||||
|
||||
if (this._waiting === 0) {
|
||||
return this._finalize();
|
||||
} else {
|
||||
@ -248,13 +279,18 @@ Please pipe the document into a Node stream.\
|
||||
}
|
||||
|
||||
// trailer
|
||||
this._write('trailer');
|
||||
this._write(PDFObject.convert({
|
||||
const trailer = {
|
||||
Size: this._offsets.length + 1,
|
||||
Root: this._root,
|
||||
Info: this._info
|
||||
})
|
||||
);
|
||||
Info: this._info,
|
||||
ID: [this._id, this._id]
|
||||
};
|
||||
if (this._security) {
|
||||
trailer.Encrypt = this._security.dictionary;
|
||||
}
|
||||
|
||||
this._write('trailer');
|
||||
this._write(PDFObject.convert(trailer));
|
||||
|
||||
this._write('startxref');
|
||||
this._write(`${xRefOffset}`);
|
||||
@ -270,7 +306,7 @@ Please pipe the document into a Node stream.\
|
||||
};
|
||||
|
||||
const mixin = methods => {
|
||||
Object.assign(PDFDocument.prototype, methods);
|
||||
Object.assign(PDFDocument.prototype, methods);
|
||||
};
|
||||
|
||||
mixin(ColorMixin);
|
||||
|
||||
@ -6,7 +6,7 @@ By Devon Govett
|
||||
import PDFAbstractReference from './abstract_reference';
|
||||
|
||||
const pad = (str, length) => (Array(length + 1).join('0') + str).slice(-length);
|
||||
|
||||
|
||||
const escapableRe = /[\n\r\t\b\f\(\)\\]/g;
|
||||
const escapable = {
|
||||
'\n': '\\n',
|
||||
@ -36,7 +36,7 @@ const swapBytes = function(buff) {
|
||||
};
|
||||
|
||||
class PDFObject {
|
||||
static convert(object) {
|
||||
static convert(object, encryptFn = null) {
|
||||
// String literals are converted to the PDF name type
|
||||
if (typeof object === 'string') {
|
||||
return `/${object}`;
|
||||
@ -54,8 +54,18 @@ class PDFObject {
|
||||
}
|
||||
|
||||
// If so, encode it as big endian UTF-16
|
||||
let stringBuffer;
|
||||
if (isUnicode) {
|
||||
string = swapBytes(new Buffer(`\ufeff${string}`, 'utf16le')).toString('binary');
|
||||
stringBuffer = swapBytes(new Buffer(`\ufeff${string}`, 'utf16le'));
|
||||
} else {
|
||||
stringBuffer = new Buffer(string, 'ascii');
|
||||
}
|
||||
|
||||
// Encrypt the string when necessary
|
||||
if (encryptFn) {
|
||||
string = encryptFn(stringBuffer).toString('binary');
|
||||
} else {
|
||||
string = stringBuffer.toString('binary');
|
||||
}
|
||||
|
||||
// Escape characters as required by the spec
|
||||
@ -71,23 +81,32 @@ class PDFObject {
|
||||
return object.toString();
|
||||
|
||||
} else if (object instanceof Date) {
|
||||
return `(D:${pad(object.getUTCFullYear(), 4)}` +
|
||||
pad(object.getUTCMonth() + 1, 2) +
|
||||
pad(object.getUTCDate(), 2) +
|
||||
pad(object.getUTCHours(), 2) +
|
||||
pad(object.getUTCMinutes(), 2) +
|
||||
pad(object.getUTCSeconds(), 2) +
|
||||
'Z)';
|
||||
let string = `D:${pad(object.getUTCFullYear(), 4)}` +
|
||||
pad(object.getUTCMonth() + 1, 2) +
|
||||
pad(object.getUTCDate(), 2) +
|
||||
pad(object.getUTCHours(), 2) +
|
||||
pad(object.getUTCMinutes(), 2) +
|
||||
pad(object.getUTCSeconds(), 2) + 'Z';
|
||||
|
||||
// Encrypt the string when necessary
|
||||
if (encryptFn) {
|
||||
string = encryptFn(new Buffer(string, 'ascii')).toString('binary');
|
||||
|
||||
// Escape characters as required by the spec
|
||||
string = string.replace(escapableRe, c => escapable[c]);
|
||||
}
|
||||
|
||||
return `(${string})`;
|
||||
|
||||
} else if (Array.isArray(object)) {
|
||||
const items = (object.map((e) => PDFObject.convert(e))).join(' ');
|
||||
const items = (object.map((e) => PDFObject.convert(e, encryptFn))).join(' ');
|
||||
return `[${items}]`;
|
||||
|
||||
} else if ({}.toString.call(object) === '[object Object]') {
|
||||
const out = ['<<'];
|
||||
for (let key in object) {
|
||||
const val = object[key];
|
||||
out.push(`/${key} ${PDFObject.convert(val)}`);
|
||||
out.push(`/${key} ${PDFObject.convert(val, encryptFn)}`);
|
||||
}
|
||||
|
||||
out.push('>>');
|
||||
|
||||
@ -9,7 +9,7 @@ import PDFObject from './object';
|
||||
|
||||
class PDFReference extends PDFAbstractReference {
|
||||
constructor(document, id, data) {
|
||||
super();
|
||||
super();
|
||||
this.document = document;
|
||||
this.id = id;
|
||||
if (data == null) { data = {}; }
|
||||
@ -45,15 +45,25 @@ class PDFReference extends PDFAbstractReference {
|
||||
return setTimeout(() => {
|
||||
this.offset = this.document._offset;
|
||||
|
||||
this.document._write(`${this.id} ${this.gen} obj`);
|
||||
this.document._write(PDFObject.convert(this.data));
|
||||
const encryptFn = this.document._security ? this.document._security.getEncryptFn(this.id, this.gen) : null;
|
||||
|
||||
if (this.buffer.length) {
|
||||
this.buffer = Buffer.concat(this.buffer);
|
||||
if (this.compress) {
|
||||
this.buffer = zlib.deflateSync(this.buffer);
|
||||
this.data.Length = this.buffer.length;
|
||||
}
|
||||
|
||||
if (encryptFn) {
|
||||
this.buffer = encryptFn(this.buffer);
|
||||
}
|
||||
|
||||
this.data.Length = this.buffer.length;
|
||||
}
|
||||
|
||||
this.document._write(`${this.id} ${this.gen} obj`);
|
||||
this.document._write(PDFObject.convert(this.data, encryptFn));
|
||||
|
||||
if (this.buffer.length) {
|
||||
this.document._write('stream');
|
||||
this.document._write(this.buffer);
|
||||
|
||||
|
||||
399
lib/security.js
Normal file
399
lib/security.js
Normal file
@ -0,0 +1,399 @@
|
||||
/*
|
||||
PDFSecurity - represents PDF security settings
|
||||
By Yang Liu <hi@zesik.com>
|
||||
*/
|
||||
|
||||
import CryptoJS from 'crypto-js';
|
||||
import saslprep from 'saslprep';
|
||||
|
||||
class PDFSecurity {
|
||||
static generateFileID(info = {}) {
|
||||
let infoStr = `${new Date().getTime()}\n`;
|
||||
|
||||
for (let key in info) {
|
||||
if (!info.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
infoStr += `${key}: ${info[key].toString()}\n`;
|
||||
}
|
||||
|
||||
return wordArrayToBuffer(CryptoJS.MD5(infoStr));
|
||||
}
|
||||
|
||||
static create(document, options = {}) {
|
||||
if (!options.ownerPassword && !options.userPassword) {
|
||||
return null;
|
||||
}
|
||||
return new PDFSecurity(document, options);
|
||||
}
|
||||
|
||||
constructor(document, options = {}) {
|
||||
if (!options.ownerPassword && !options.userPassword) {
|
||||
throw new Error('None of owner password and user password is defined.');
|
||||
}
|
||||
|
||||
this.document = document;
|
||||
this._setupEncryption(options);
|
||||
}
|
||||
|
||||
_setupEncryption(options) {
|
||||
switch (options.pdfVersion) {
|
||||
case '1.4':
|
||||
case '1.5':
|
||||
this.version = 2;
|
||||
break;
|
||||
case '1.6':
|
||||
case '1.7':
|
||||
this.version = 4;
|
||||
break;
|
||||
case '1.7ext3':
|
||||
this.version = 5;
|
||||
break;
|
||||
default:
|
||||
this.version = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
const encDict = {
|
||||
Filter: 'Standard'
|
||||
};
|
||||
|
||||
switch (this.version) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
this._setupEncryptionV1V2V4(this.version, encDict, options);
|
||||
break;
|
||||
case 5:
|
||||
this._setupEncryptionV5(encDict, options);
|
||||
break;
|
||||
}
|
||||
|
||||
this.dictionary = this.document.ref(encDict);
|
||||
}
|
||||
|
||||
_setupEncryptionV1V2V4(v, encDict, options) {
|
||||
let r, permissions;
|
||||
switch (v) {
|
||||
case 1:
|
||||
r = 2;
|
||||
this.keyBits = 40;
|
||||
permissions = getPermissionsR2(options);
|
||||
break;
|
||||
case 2:
|
||||
r = 3;
|
||||
this.keyBits = 128;
|
||||
permissions = getPermissionsR3(options);
|
||||
break;
|
||||
case 4:
|
||||
r = 4;
|
||||
this.keyBits = 128;
|
||||
permissions = getPermissionsR3(options);
|
||||
break;
|
||||
}
|
||||
|
||||
const paddedUserPassword = processPasswordR2R3R4(options.userPassword);
|
||||
const paddedOwnerPassword = options.ownerPassword ?
|
||||
processPasswordR2R3R4(options.ownerPassword) : paddedUserPassword;
|
||||
|
||||
const ownerPasswordEntry = getOwnerPasswordR2R3R4(r, this.keyBits, paddedUserPassword, paddedOwnerPassword);
|
||||
this.encryptionKey = getEncryptionKeyR2R3R4(r, this.keyBits, this.document._id,
|
||||
paddedUserPassword, ownerPasswordEntry, permissions);
|
||||
let userPasswordEntry;
|
||||
if (r === 2) {
|
||||
userPasswordEntry = getUserPasswordR2(this.encryptionKey);
|
||||
} else {
|
||||
userPasswordEntry = getUserPasswordR3R4(this.document._id, this.encryptionKey);
|
||||
}
|
||||
|
||||
encDict.V = v;
|
||||
if (v >= 2) {
|
||||
encDict.Length = this.keyBits;
|
||||
}
|
||||
if (v === 4) {
|
||||
encDict.CF = {
|
||||
StdCF: {
|
||||
AuthEvent: 'DocOpen',
|
||||
CFM: 'AESV2',
|
||||
Length: this.keyBits / 8
|
||||
}
|
||||
};
|
||||
encDict.StmF = 'StdCF';
|
||||
encDict.StrF = 'StdCF';
|
||||
}
|
||||
encDict.R = r;
|
||||
encDict.O = wordArrayToBuffer(ownerPasswordEntry);
|
||||
encDict.U = wordArrayToBuffer(userPasswordEntry);
|
||||
encDict.P = permissions;
|
||||
}
|
||||
|
||||
_setupEncryptionV5(encDict, options) {
|
||||
this.keyBits = 256;
|
||||
const permissions = getPermissionsR3(options);
|
||||
|
||||
const processedUserPassword = processPasswordR5(options.userPassword);
|
||||
const processedOwnerPassword = options.ownerPassword ?
|
||||
processPasswordR5(options.ownerPassword) : processedUserPassword;
|
||||
|
||||
this.encryptionKey = getEncryptionKeyR5();
|
||||
const userPasswordEntry = getUserPasswordR5(processedUserPassword);
|
||||
const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
|
||||
const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
|
||||
const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry);
|
||||
const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
|
||||
const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry,
|
||||
this.encryptionKey);
|
||||
const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey);
|
||||
|
||||
encDict.V = 5;
|
||||
encDict.Length = this.keyBits;
|
||||
encDict.CF = {
|
||||
StdCF: {
|
||||
AuthEvent: 'DocOpen',
|
||||
CFM: 'AESV3',
|
||||
Length: this.keyBits / 8
|
||||
}
|
||||
};
|
||||
encDict.StmF = 'StdCF';
|
||||
encDict.StrF = 'StdCF';
|
||||
encDict.R = 5;
|
||||
encDict.O = wordArrayToBuffer(ownerPasswordEntry);
|
||||
encDict.OE = wordArrayToBuffer(ownerEncryptionKeyEntry);
|
||||
encDict.U = wordArrayToBuffer(userPasswordEntry);
|
||||
encDict.UE = wordArrayToBuffer(userEncryptionKeyEntry);
|
||||
encDict.P = permissions;
|
||||
encDict.Perms = wordArrayToBuffer(permsEntry);
|
||||
}
|
||||
|
||||
getEncryptFn(obj, gen) {
|
||||
let digest;
|
||||
if (this.version < 5) {
|
||||
digest = this.encryptionKey.clone().concat(CryptoJS.lib.WordArray.create([
|
||||
((obj & 0xff) << 24) | ((obj & 0xff00) << 8) | ((obj >> 8) & 0xff00) | (gen & 0xff), (gen & 0xff00) << 16
|
||||
], 5));
|
||||
}
|
||||
|
||||
if (this.version === 1 || this.version === 2) {
|
||||
let key = CryptoJS.MD5(digest);
|
||||
key.sigBytes = Math.min(16, this.keyBits / 8 + 5);
|
||||
return buffer => wordArrayToBuffer(
|
||||
CryptoJS.RC4.encrypt(CryptoJS.lib.WordArray.create(buffer), key).ciphertext);
|
||||
}
|
||||
|
||||
let key;
|
||||
if (this.version === 4) {
|
||||
key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
|
||||
} else {
|
||||
key = this.encryptionKey;
|
||||
}
|
||||
|
||||
const iv = CryptoJS.lib.WordArray.random(16);
|
||||
const options = {
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
iv
|
||||
};
|
||||
|
||||
return buffer => wordArrayToBuffer(
|
||||
iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
|
||||
}
|
||||
|
||||
end() {
|
||||
this.dictionary.end();
|
||||
}
|
||||
}
|
||||
|
||||
function getPermissionsR2(options) {
|
||||
let permissions = 0xffffffc0 >> 0;
|
||||
if (options.allowPrinting) {
|
||||
permissions |= 0b00000000010;
|
||||
}
|
||||
if (options.allowModifying) {
|
||||
permissions |= 0b000000001000;
|
||||
}
|
||||
if (options.allowCopying) {
|
||||
permissions |= 0b000000010000;
|
||||
}
|
||||
if (options.allowAnnotating) {
|
||||
permissions |= 0b000000100000;
|
||||
}
|
||||
return permissions;
|
||||
}
|
||||
|
||||
function getPermissionsR3(options) {
|
||||
let permissions = 0xfffff0c0 >> 0;
|
||||
if (options.allowPrinting === 'lowResolution') {
|
||||
permissions |= 0b000000000100;
|
||||
}
|
||||
if (options.allowPrinting === 'highResolution') {
|
||||
permissions |= 0b100000000100;
|
||||
}
|
||||
if (options.allowModifying) {
|
||||
permissions |= 0b000000001000;
|
||||
}
|
||||
if (options.allowCopying) {
|
||||
permissions |= 0b000000010000;
|
||||
}
|
||||
if (options.allowAnnotating) {
|
||||
permissions |= 0b000000100000;
|
||||
}
|
||||
if (options.allowFillingForms) {
|
||||
permissions |= 0b000100000000;
|
||||
}
|
||||
if (options.allowContentAccessibility) {
|
||||
permissions |= 0b001000000000;
|
||||
}
|
||||
if (options.allowDocumentAssembly) {
|
||||
permissions |= 0b010000000000;
|
||||
}
|
||||
return permissions;
|
||||
}
|
||||
|
||||
function getUserPasswordR2(encryptionKey) {
|
||||
return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey).ciphertext;
|
||||
}
|
||||
|
||||
function getUserPasswordR3R4(documentId, encryptionKey) {
|
||||
const key = encryptionKey.clone();
|
||||
let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const xorRound = Math.ceil(key.sigBytes / 4);
|
||||
for (let j = 0; j < xorRound; j++) {
|
||||
key.words[j] = encryptionKey.words[j] ^ (i | (i << 8) | (i << 16) | (i << 24));
|
||||
}
|
||||
cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
|
||||
}
|
||||
return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
|
||||
}
|
||||
|
||||
function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
|
||||
let digest = paddedOwnerPassword;
|
||||
let round = r >= 3 ? 51 : 1;
|
||||
for (let i = 0; i < round; i++) {
|
||||
digest = CryptoJS.MD5(digest);
|
||||
}
|
||||
|
||||
const key = digest.clone();
|
||||
key.sigBytes = keyBits / 8;
|
||||
let cipher = paddedUserPassword;
|
||||
round = r >= 3 ? 20 : 1;
|
||||
for (let i = 0; i < round; i++) {
|
||||
const xorRound = Math.ceil(key.sigBytes / 4);
|
||||
for (let j = 0; j < xorRound; j++) {
|
||||
key.words[j] = digest.words[j] ^ (i | (i << 8) | (i << 16) | (i << 24));
|
||||
}
|
||||
cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
|
||||
}
|
||||
return cipher;
|
||||
}
|
||||
|
||||
function getEncryptionKeyR2R3R4(r, keyBits, documentId, paddedUserPassword, ownerPasswordEntry, permissions) {
|
||||
let key = paddedUserPassword.clone()
|
||||
.concat(ownerPasswordEntry)
|
||||
.concat(CryptoJS.lib.WordArray.create([lsbFirstWord(permissions)], 4))
|
||||
.concat(CryptoJS.lib.WordArray.create(documentId));
|
||||
const round = r >= 3 ? 51 : 1;
|
||||
for (let i = 0; i < round; i++) {
|
||||
key = CryptoJS.MD5(key);
|
||||
key.sigBytes = keyBits / 8;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
function getUserPasswordR5(processedUserPassword) {
|
||||
const validationSalt = CryptoJS.lib.WordArray.random(8);
|
||||
const keySalt = CryptoJS.lib.WordArray.random(8);
|
||||
return CryptoJS.SHA256(processedUserPassword.clone().concat(validationSalt))
|
||||
.concat(validationSalt).concat(keySalt);
|
||||
}
|
||||
|
||||
function getUserEncryptionKeyR5(processedUserPassword, userKeySalt, encryptionKey) {
|
||||
const key = CryptoJS.SHA256(processedUserPassword.clone().concat(userKeySalt));
|
||||
const options = {
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.NoPadding,
|
||||
iv: CryptoJS.lib.WordArray.create(null, 16)
|
||||
};
|
||||
return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
|
||||
}
|
||||
|
||||
function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry) {
|
||||
const validationSalt = CryptoJS.lib.WordArray.random(8);
|
||||
const keySalt = CryptoJS.lib.WordArray.random(8);
|
||||
return CryptoJS.SHA256(processedOwnerPassword.clone().concat(validationSalt).concat(userPasswordEntry))
|
||||
.concat(validationSalt).concat(keySalt);
|
||||
}
|
||||
|
||||
function getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, encryptionKey) {
|
||||
const key = CryptoJS.SHA256(processedOwnerPassword.clone().concat(ownerKeySalt).concat(userPasswordEntry));
|
||||
const options = {
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.NoPadding,
|
||||
iv: CryptoJS.lib.WordArray.create(null, 16)
|
||||
};
|
||||
return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
|
||||
}
|
||||
|
||||
function getEncryptionKeyR5() {
|
||||
return CryptoJS.lib.WordArray.random(32);
|
||||
}
|
||||
|
||||
function getEncryptedPermissionsR5(permissions, encryptionKey) {
|
||||
const cipher = CryptoJS.lib.WordArray.create([lsbFirstWord(permissions), 0xffffffff, 0x54616462], 12)
|
||||
.concat(CryptoJS.lib.WordArray.random(4));
|
||||
const options = {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.NoPadding
|
||||
};
|
||||
return CryptoJS.AES.encrypt(cipher, encryptionKey, options).ciphertext;
|
||||
}
|
||||
|
||||
function processPasswordR2R3R4(password = '') {
|
||||
const out = new Buffer(32);
|
||||
const length = password.length;
|
||||
let index = 0;
|
||||
while (index < length && index < 32) {
|
||||
const code = password.charCodeAt(index);
|
||||
if (code > 0xff) {
|
||||
throw new Error('Password contains one or more invalid characters.');
|
||||
}
|
||||
out[index] = code;
|
||||
index++;
|
||||
}
|
||||
while (index < 32) {
|
||||
out[index] = PASSWORD_PADDING[index - length];
|
||||
index++;
|
||||
}
|
||||
return CryptoJS.lib.WordArray.create(out);
|
||||
}
|
||||
|
||||
function processPasswordR5(password = '') {
|
||||
password = unescape(encodeURIComponent(saslprep(password)));
|
||||
const length = Math.min(127, password.length);
|
||||
const out = new Buffer(length);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
out[i] = password.charCodeAt(i);
|
||||
}
|
||||
|
||||
return CryptoJS.lib.WordArray.create(out);
|
||||
}
|
||||
|
||||
function lsbFirstWord(data) {
|
||||
return ((data & 0xff) << 24) | ((data & 0xff00) << 8) | ((data >> 8) & 0xff00) | ((data >> 24) & 0xff);
|
||||
}
|
||||
|
||||
function wordArrayToBuffer(wordArray) {
|
||||
const byteArray = [];
|
||||
for (let i = 0; i < wordArray.sigBytes; i++) {
|
||||
byteArray.push((wordArray.words[Math.floor(i / 4)] >> (8 * (3 - i % 4))) & 0xff);
|
||||
}
|
||||
return Buffer.from(byteArray);
|
||||
}
|
||||
|
||||
const PASSWORD_PADDING = [
|
||||
0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08,
|
||||
0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a
|
||||
];
|
||||
|
||||
export default PDFSecurity;
|
||||
@ -28,7 +28,7 @@
|
||||
"blob-stream": "^0.1.2",
|
||||
"brace": "^0.2.1",
|
||||
"brfs": "~2.0.1",
|
||||
"browserify": "^3.39.0",
|
||||
"browserify": "^13.3.0",
|
||||
"codemirror": "~3.20.0",
|
||||
"coffee-script": ">=1.0.1",
|
||||
"eslint": "^5.3.0",
|
||||
@ -41,9 +41,11 @@
|
||||
"rollup-plugin-cpy": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"fontkit": "^1.0.0",
|
||||
"linebreak": "^0.3.0",
|
||||
"png-js": ">=0.1.0"
|
||||
"png-js": ">=0.1.0",
|
||||
"saslprep": "^1.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "npm run build",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user