linting fix

This commit is contained in:
stratumadev 2024-12-11 17:40:45 +01:00
parent 3583d40d4d
commit 1f12a75009
14 changed files with 1558 additions and 1573 deletions

2
ao.ts
View file

@ -679,7 +679,7 @@ export default class AnimeOnegai implements ServiceClass {
//Handle Decryption if needed
if ((chosenVideoSegments.pssh_wvd || chosenAudioSegments.pssh_wvd) && (videoDownloaded || audioDownloaded)) {
console.info('Decryption Needed, attempting to decrypt');
var encryptionKeys;
let encryptionKeys;
if (cdm === 'widevine') {
encryptionKeys = await getKeysWVD(chosenVideoSegments.pssh_wvd, streamData.widevine_proxy, {});

View file

@ -1742,20 +1742,20 @@ export default class Crunchy implements ServiceClass {
}
const authData = await decReq.res.json() as {'custom_data': string, 'token': string};
var encryptionKeys;
let encryptionKeys;
if (cdm === 'widevine') {
encryptionKeys = await getKeysWVD(chosenVideoSegments.pssh_wvd, 'https://lic.drmtoday.com/license-proxy-widevine/cenc/', {
'dt-custom-data': authData.custom_data,
'x-dt-auth-token': authData.token
})
});
}
if (cdm === 'playready') {
encryptionKeys = await getKeysPRD(chosenVideoSegments.pssh_prd, 'https://lic.drmtoday.com/license-proxy-headerauth/drmtoday/RightsManager.asmx', {
'dt-custom-data': authData.custom_data,
'x-dt-auth-token': authData.token
})
});
}
if (!encryptionKeys || encryptionKeys.length == 0) {

View file

@ -657,7 +657,6 @@ export default class Hidive implements ServiceClass {
const chosenFontSize = options.originalFontSize ? undefined : options.fontSize;
let encryptionKeys: KeyContainer[] = [];
if (!canDecrypt) console.warn('Decryption not enabled!');
if (canDecrypt && cdm === 'playready') console.warn("Hidive doesn't support Playready CDM!");
if (!this.cfg.bin.ffmpeg)
this.cfg.bin = await yamlCfg.loadBinCfg();

View file

@ -1,235 +1,231 @@
import { KeyContainer, Session } from "./license";
import fs from "fs";
import { console } from "./log";
import got from "got";
import { workingDir } from "./module.cfg-loader";
import path from "path";
import { ReadError, Response } from "got";
import { Device } from "./playready/device";
import Cdm from "./playready/cdm";
import { PSSH } from "./playready/pssh";
import { KeyContainer, Session } from './license';
import fs from 'fs';
import { console } from './log';
import got from 'got';
import { workingDir } from './module.cfg-loader';
import path from 'path';
import { ReadError, Response } from 'got';
import { Device } from './playready/device';
import Cdm from './playready/cdm';
import { PSSH } from './playready/pssh';
//read cdm files located in the same directory
let privateKey: Buffer = Buffer.from([]),
identifierBlob: Buffer = Buffer.from([]),
prd: Buffer = Buffer.from([]),
prd_cdm: Cdm | undefined;
export let cdm: "widevine" | "playready";
identifierBlob: Buffer = Buffer.from([]),
prd: Buffer = Buffer.from([]),
prd_cdm: Cdm | undefined;
export let cdm: 'widevine' | 'playready';
export let canDecrypt: boolean;
try {
const files_prd = fs.readdirSync(path.join(workingDir, "playready"));
const prd_file_found = files_prd.find(f => f.includes('.prd'))
if (prd_file_found) {
const file_prd = path.join(workingDir, "playready", prd_file_found);
const stats = fs.statSync(file_prd);
if (stats.size < 1024 * 8 && stats.isFile()) {
const fileContents = fs.readFileSync(file_prd, {
encoding: "utf8",
});
if (fileContents.includes("CERT")) {
prd = fs.readFileSync(file_prd);
const device = Device.loads(prd);
prd_cdm = Cdm.fromDevice(device);
}
}
const files_prd = fs.readdirSync(path.join(workingDir, 'playready'));
const prd_file_found = files_prd.find((f) => f.includes('.prd'));
if (prd_file_found) {
const file_prd = path.join(workingDir, 'playready', prd_file_found);
const stats = fs.statSync(file_prd);
if (stats.size < 1024 * 8 && stats.isFile()) {
const fileContents = fs.readFileSync(file_prd, {
encoding: 'utf8',
});
if (fileContents.includes('CERT')) {
prd = fs.readFileSync(file_prd);
const device = Device.loads(prd);
prd_cdm = Cdm.fromDevice(device);
}
}
}
const files_wvd = fs.readdirSync(path.join(workingDir, "widevine"));
files_wvd.forEach(function (file) {
file = path.join(workingDir, "widevine", file);
const stats = fs.statSync(file);
if (stats.size < 1024 * 8 && stats.isFile()) {
const fileContents = fs.readFileSync(file, { encoding: "utf8" });
if (
fileContents.includes("-BEGIN PRIVATE KEY-") ||
fileContents.includes("-BEGIN RSA PRIVATE KEY-")
) {
privateKey = fs.readFileSync(file);
}
if (fileContents.includes("widevine_cdm_version")) {
identifierBlob = fs.readFileSync(file);
}
}
});
if (privateKey.length !== 0 && identifierBlob.length !== 0) {
cdm = "widevine";
canDecrypt = true;
} else if (prd.length !== 0) {
cdm = "playready";
canDecrypt = true;
} else if (privateKey.length == 0) {
console.warn("Private key missing");
canDecrypt = false;
} else if (identifierBlob.length == 0) {
console.warn("Identifier blob missing");
canDecrypt = false;
} else if (prd.length == 0) {
console.warn("PRD is missing");
canDecrypt = false;
const files_wvd = fs.readdirSync(path.join(workingDir, 'widevine'));
files_wvd.forEach(function (file) {
file = path.join(workingDir, 'widevine', file);
const stats = fs.statSync(file);
if (stats.size < 1024 * 8 && stats.isFile()) {
const fileContents = fs.readFileSync(file, { encoding: 'utf8' });
if (
fileContents.includes('-BEGIN PRIVATE KEY-') ||
fileContents.includes('-BEGIN RSA PRIVATE KEY-')
) {
privateKey = fs.readFileSync(file);
}
if (fileContents.includes('widevine_cdm_version')) {
identifierBlob = fs.readFileSync(file);
}
}
} catch (e) {
console.error(e);
});
if (privateKey.length !== 0 && identifierBlob.length !== 0) {
cdm = 'widevine';
canDecrypt = true;
} else if (prd.length !== 0) {
cdm = 'playready';
canDecrypt = true;
} else if (privateKey.length == 0) {
console.warn('Private key missing');
canDecrypt = false;
} else if (identifierBlob.length == 0) {
console.warn('Identifier blob missing');
canDecrypt = false;
} else if (prd.length == 0) {
console.warn('PRD is missing');
canDecrypt = false;
}
} catch (e) {
console.error(e);
canDecrypt = false;
}
export async function getKeysWVD(
pssh: string | undefined,
licenseServer: string,
authData: Record<string, string>
pssh: string | undefined,
licenseServer: string,
authData: Record<string, string>
): Promise<KeyContainer[]> {
if (!pssh || !canDecrypt) return [];
//pssh found in the mpd manifest
const psshBuffer = Buffer.from(pssh, "base64");
if (!pssh || !canDecrypt) return [];
//pssh found in the mpd manifest
const psshBuffer = Buffer.from(pssh, 'base64');
//Create a new widevine session
const session = new Session({ privateKey, identifierBlob }, psshBuffer);
//Create a new widevine session
const session = new Session({ privateKey, identifierBlob }, psshBuffer);
//Generate license
let response;
try {
response = await got(licenseServer, {
method: "POST",
body: session.createLicenseRequest(),
headers: authData,
responseType: "text",
});
} catch (_error) {
const error = _error as {
name: string;
} & ReadError & {
res: Response<unknown>;
};
if (
error.response &&
error.response.statusCode &&
error.response.statusMessage
) {
console.error(
`${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`
);
} else {
console.error(`${error.name}: ${error.code || error.message}`);
}
if (error.response && !error.res) {
error.res = error.response;
const docTitle = (error.res.body as string).match(
/<title>(.*)<\/title>/
);
if (error.res.body && docTitle) {
console.error(docTitle[1]);
}
}
if (
error.res &&
error.res.body &&
error.response.statusCode &&
error.response.statusCode != 404 &&
error.response.statusCode != 403
) {
console.error("Body:", error.res.body);
}
return [];
}
if (response.statusCode === 200) {
//Parse License and return keys
try {
const json = JSON.parse(response.body);
return session.parseLicense(Buffer.from(json["license"], "base64"));
} catch {
return session.parseLicense(response.rawBody);
}
//Generate license
let response;
try {
response = await got(licenseServer, {
method: 'POST',
body: session.createLicenseRequest(),
headers: authData,
responseType: 'text',
});
} catch (_error) {
const error = _error as {
name: string;
} & ReadError & {
res: Response<unknown>;
};
if (
error.response &&
error.response.statusCode &&
error.response.statusMessage
) {
console.error(
`${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`
);
} else {
console.info(
"License request failed:",
response.statusMessage,
response.body
);
return [];
console.error(`${error.name}: ${error.code || error.message}`);
}
if (error.response && !error.res) {
error.res = error.response;
const docTitle = (error.res.body as string).match(/<title>(.*)<\/title>/);
if (error.res.body && docTitle) {
console.error(docTitle[1]);
}
}
if (
error.res &&
error.res.body &&
error.response.statusCode &&
error.response.statusCode != 404 &&
error.response.statusCode != 403
) {
console.error('Body:', error.res.body);
}
return [];
}
if (response.statusCode === 200) {
//Parse License and return keys
try {
const json = JSON.parse(response.body);
return session.parseLicense(Buffer.from(json['license'], 'base64'));
} catch {
return session.parseLicense(response.rawBody);
}
} else {
console.info(
'License request failed:',
response.statusMessage,
response.body
);
return [];
}
}
export async function getKeysPRD(
pssh: string | undefined,
licenseServer: string,
authData: Record<string, string>
pssh: string | undefined,
licenseServer: string,
authData: Record<string, string>
): Promise<KeyContainer[] | undefined> {
if (!pssh || !canDecrypt || !prd_cdm) return [];
const pssh_parsed = new PSSH(pssh);
if (!pssh || !canDecrypt || !prd_cdm) return [];
const pssh_parsed = new PSSH(pssh);
//Create a new playready session
const session = prd_cdm.getLicenseChallenge(
pssh_parsed.get_wrm_headers(true)[0]
);
//Create a new playready session
const session = prd_cdm.getLicenseChallenge(
pssh_parsed.get_wrm_headers(true)[0]
);
//Generate license
let response;
try {
response = await got(licenseServer, {
method: "POST",
body: session,
headers: authData,
responseType: "text",
});
} catch (_error) {
const error = _error as {
name: string;
} & ReadError & {
res: Response<unknown>;
};
if (
error.response &&
error.response.statusCode &&
error.response.statusMessage
) {
console.error(
`${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`
);
} else {
console.error(`${error.name}: ${error.code || error.message}`);
}
if (error.response && !error.res) {
error.res = error.response;
const docTitle = (error.res.body as string).match(
/<title>(.*)<\/title>/
);
if (error.res.body && docTitle) {
console.error(docTitle[1]);
}
}
if (
error.res &&
error.res.body &&
error.response.statusCode &&
error.response.statusCode != 404 &&
error.response.statusCode != 403
) {
console.error("Body:", error.res.body);
}
return [];
}
if (response.statusCode === 200) {
//Parse License and return keys
try {
const keys = prd_cdm.parseLicense(response.body);
return keys.map((k) => {
return {
kid: k.key_id,
key: k.key,
};
});
} catch {
return undefined;
}
//Generate license
let response;
try {
response = await got(licenseServer, {
method: 'POST',
body: session,
headers: authData,
responseType: 'text',
});
} catch (_error) {
const error = _error as {
name: string;
} & ReadError & {
res: Response<unknown>;
};
if (
error.response &&
error.response.statusCode &&
error.response.statusMessage
) {
console.error(
`${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`
);
} else {
console.info(
"License request failed:",
response.statusMessage,
response.body
);
return [];
console.error(`${error.name}: ${error.code || error.message}`);
}
if (error.response && !error.res) {
error.res = error.response;
const docTitle = (error.res.body as string).match(/<title>(.*)<\/title>/);
if (error.res.body && docTitle) {
console.error(docTitle[1]);
}
}
if (
error.res &&
error.res.body &&
error.response.statusCode &&
error.response.statusCode != 404 &&
error.response.statusCode != 403
) {
console.error('Body:', error.res.body);
}
return [];
}
if (response.statusCode === 200) {
//Parse License and return keys
try {
const keys = prd_cdm.parseLicense(response.body);
return keys.map((k) => {
return {
kid: k.key_id,
key: k.key,
};
});
} catch {
return undefined;
}
} else {
console.info(
'License request failed:',
response.statusMessage,
response.body
);
return [];
}
}

View file

@ -1,489 +1,485 @@
import * as fs from 'fs'
import { createHash } from 'crypto'
import { Parser } from 'binary-parser-encoder'
import ECCKey from './ecc_key'
import * as fs from 'fs';
import { createHash } from 'crypto';
import { Parser } from 'binary-parser-encoder';
import ECCKey from './ecc_key';
import { console } from '../log';
function alignUp(length: number, alignment: number): number {
return Math.ceil(length / alignment) * alignment
return Math.ceil(length / alignment) * alignment;
}
export class BCertStructs {
static DrmBCertBasicInfo = new Parser()
.buffer('cert_id', { length: 16 })
.uint32be('security_level')
.uint32be('flags')
.uint32be('cert_type')
.buffer('public_key_digest', { length: 32 })
.uint32be('expiration_date')
.buffer('client_id', { length: 16 })
static DrmBCertBasicInfo = new Parser()
.buffer('cert_id', { length: 16 })
.uint32be('security_level')
.uint32be('flags')
.uint32be('cert_type')
.buffer('public_key_digest', { length: 32 })
.uint32be('expiration_date')
.buffer('client_id', { length: 16 });
static DrmBCertDomainInfo = new Parser()
.buffer('service_id', { length: 16 })
.buffer('account_id', { length: 16 })
.uint32be('revision_timestamp')
.uint32be('domain_url_length')
.buffer('domain_url', {
length: function () {
return alignUp((this as any).domain_url_length, 4)
}
})
static DrmBCertDomainInfo = new Parser()
.buffer('service_id', { length: 16 })
.buffer('account_id', { length: 16 })
.uint32be('revision_timestamp')
.uint32be('domain_url_length')
.buffer('domain_url', {
length: function () {
return alignUp((this as any).domain_url_length, 4);
},
});
static DrmBCertPCInfo = new Parser().uint32be('security_version')
static DrmBCertPCInfo = new Parser().uint32be('security_version');
static DrmBCertDeviceInfo = new Parser()
.uint32be('max_license')
.uint32be('max_header')
.uint32be('max_chain_depth')
static DrmBCertDeviceInfo = new Parser()
.uint32be('max_license')
.uint32be('max_header')
.uint32be('max_chain_depth');
static DrmBCertFeatureInfo = new Parser()
.uint32be('feature_count')
.array('features', {
type: 'uint32be',
length: 'feature_count'
})
static DrmBCertFeatureInfo = new Parser()
.uint32be('feature_count')
.array('features', {
type: 'uint32be',
length: 'feature_count',
});
static CertKey = new Parser()
.uint16be('type')
.uint16be('length')
.uint32be('flags')
.buffer('key', {
length: function () {
return (this as any).length / 8
}
})
.uint32be('usages_count')
.array('usages', {
type: 'uint32be',
length: 'usages_count'
})
static CertKey = new Parser()
.uint16be('type')
.uint16be('length')
.uint32be('flags')
.buffer('key', {
length: function () {
return (this as any).length / 8;
},
})
.uint32be('usages_count')
.array('usages', {
type: 'uint32be',
length: 'usages_count',
});
static DrmBCertKeyInfo = new Parser()
.uint32be('key_count')
.array('cert_keys', {
type: BCertStructs.CertKey,
length: 'key_count'
})
static DrmBCertKeyInfo = new Parser()
.uint32be('key_count')
.array('cert_keys', {
type: BCertStructs.CertKey,
length: 'key_count',
});
static DrmBCertManufacturerInfo = new Parser()
.uint32be('flags')
.uint32be('manufacturer_name_length')
.buffer('manufacturer_name', {
length: function () {
return alignUp((this as any).manufacturer_name_length, 4)
}
})
.uint32be('model_name_length')
.buffer('model_name', {
length: function () {
return alignUp((this as any).model_name_length, 4)
}
})
.uint32be('model_number_length')
.buffer('model_number', {
length: function () {
return alignUp((this as any).model_number_length, 4)
}
})
static DrmBCertManufacturerInfo = new Parser()
.uint32be('flags')
.uint32be('manufacturer_name_length')
.buffer('manufacturer_name', {
length: function () {
return alignUp((this as any).manufacturer_name_length, 4);
},
})
.uint32be('model_name_length')
.buffer('model_name', {
length: function () {
return alignUp((this as any).model_name_length, 4);
},
})
.uint32be('model_number_length')
.buffer('model_number', {
length: function () {
return alignUp((this as any).model_number_length, 4);
},
});
static DrmBCertSignatureInfo = new Parser()
.uint16be('signature_type')
.uint16be('signature_size')
.buffer('signature', { length: 'signature_size' })
.uint32be('signature_key_size')
.buffer('signature_key', {
length: function () {
return (this as any).signature_key_size / 8
}
})
static DrmBCertSignatureInfo = new Parser()
.uint16be('signature_type')
.uint16be('signature_size')
.buffer('signature', { length: 'signature_size' })
.uint32be('signature_key_size')
.buffer('signature_key', {
length: function () {
return (this as any).signature_key_size / 8;
},
});
static DrmBCertSilverlightInfo = new Parser()
.uint32be('security_version')
.uint32be('platform_identifier')
static DrmBCertSilverlightInfo = new Parser()
.uint32be('security_version')
.uint32be('platform_identifier');
static DrmBCertMeteringInfo = new Parser()
.buffer('metering_id', { length: 16 })
.uint32be('metering_url_length')
.buffer('metering_url', {
length: function () {
return alignUp((this as any).metering_url_length, 4)
}
})
static DrmBCertMeteringInfo = new Parser()
.buffer('metering_id', { length: 16 })
.uint32be('metering_url_length')
.buffer('metering_url', {
length: function () {
return alignUp((this as any).metering_url_length, 4);
},
});
static DrmBCertExtDataSignKeyInfo = new Parser()
.uint16be('type')
.uint16be('length')
.uint32be('flags')
.buffer('key', {
length: function () {
return (this as any).length / 8
}
})
static DrmBCertExtDataSignKeyInfo = new Parser()
.uint16be('type')
.uint16be('length')
.uint32be('flags')
.buffer('key', {
length: function () {
return (this as any).length / 8;
},
});
static BCertExtDataRecord = new Parser()
.uint32be('data_size')
.buffer('data', {
length: 'data_size'
})
static BCertExtDataRecord = new Parser()
.uint32be('data_size')
.buffer('data', {
length: 'data_size',
});
static DrmBCertExtDataSignature = new Parser()
.uint16be('signature_type')
.uint16be('signature_size')
.buffer('signature', {
length: 'signature_size'
})
static DrmBCertExtDataSignature = new Parser()
.uint16be('signature_type')
.uint16be('signature_size')
.buffer('signature', {
length: 'signature_size',
});
static BCertExtDataContainer = new Parser()
.uint32be('record_count')
.array('records', {
length: 'record_count',
type: BCertStructs.BCertExtDataRecord
})
.nest('signature', {
type: BCertStructs.DrmBCertExtDataSignature
})
static BCertExtDataContainer = new Parser()
.uint32be('record_count')
.array('records', {
length: 'record_count',
type: BCertStructs.BCertExtDataRecord,
})
.nest('signature', {
type: BCertStructs.DrmBCertExtDataSignature,
});
static DrmBCertServerInfo = new Parser().uint32be('warning_days')
static DrmBCertServerInfo = new Parser().uint32be('warning_days');
static DrmBcertSecurityVersion = new Parser()
.uint32be('security_version')
.uint32be('platform_identifier')
static DrmBcertSecurityVersion = new Parser()
.uint32be('security_version')
.uint32be('platform_identifier');
static Attribute = new Parser()
.uint16be('flags')
.uint16be('tag')
.uint32be('length')
.choice('attribute', {
tag: 'tag',
choices: {
1: BCertStructs.DrmBCertBasicInfo,
2: BCertStructs.DrmBCertDomainInfo,
3: BCertStructs.DrmBCertPCInfo,
4: BCertStructs.DrmBCertDeviceInfo,
5: BCertStructs.DrmBCertFeatureInfo,
6: BCertStructs.DrmBCertKeyInfo,
7: BCertStructs.DrmBCertManufacturerInfo,
8: BCertStructs.DrmBCertSignatureInfo,
9: BCertStructs.DrmBCertSilverlightInfo,
10: BCertStructs.DrmBCertMeteringInfo,
11: BCertStructs.DrmBCertExtDataSignKeyInfo,
12: BCertStructs.BCertExtDataContainer,
13: BCertStructs.DrmBCertExtDataSignature,
14: new Parser().buffer('data', {
length: function () {
return (this as any).length - 8
}
}),
15: BCertStructs.DrmBCertServerInfo,
16: BCertStructs.DrmBcertSecurityVersion,
17: BCertStructs.DrmBcertSecurityVersion
},
defaultChoice: new Parser().buffer('data', {
length: function () {
return (this as any).length - 8
}
})
})
static Attribute = new Parser()
.uint16be('flags')
.uint16be('tag')
.uint32be('length')
.choice('attribute', {
tag: 'tag',
choices: {
1: BCertStructs.DrmBCertBasicInfo,
2: BCertStructs.DrmBCertDomainInfo,
3: BCertStructs.DrmBCertPCInfo,
4: BCertStructs.DrmBCertDeviceInfo,
5: BCertStructs.DrmBCertFeatureInfo,
6: BCertStructs.DrmBCertKeyInfo,
7: BCertStructs.DrmBCertManufacturerInfo,
8: BCertStructs.DrmBCertSignatureInfo,
9: BCertStructs.DrmBCertSilverlightInfo,
10: BCertStructs.DrmBCertMeteringInfo,
11: BCertStructs.DrmBCertExtDataSignKeyInfo,
12: BCertStructs.BCertExtDataContainer,
13: BCertStructs.DrmBCertExtDataSignature,
14: new Parser().buffer('data', {
length: function () {
return (this as any).length - 8;
},
}),
15: BCertStructs.DrmBCertServerInfo,
16: BCertStructs.DrmBcertSecurityVersion,
17: BCertStructs.DrmBcertSecurityVersion,
},
defaultChoice: new Parser().buffer('data', {
length: function () {
return (this as any).length - 8;
},
}),
});
static BCert = new Parser()
.string('signature', { length: 4, assert: 'CERT' })
.int32be('version')
.int32be('total_length')
.int32be('certificate_length')
.array('attributes', {
type: BCertStructs.Attribute,
lengthInBytes: function () {
return (this as any).total_length - 16
}
})
static BCert = new Parser()
.string('signature', { length: 4, assert: 'CERT' })
.int32be('version')
.int32be('total_length')
.int32be('certificate_length')
.array('attributes', {
type: BCertStructs.Attribute,
lengthInBytes: function () {
return (this as any).total_length - 16;
},
});
static BCertChain = new Parser()
.string('signature', { length: 4, assert: 'CHAI' })
.int32be('version')
.int32be('total_length')
.int32be('flags')
.int32be('certificate_count')
.array('certificates', {
type: BCertStructs.BCert,
length: 'certificate_count'
})
static BCertChain = new Parser()
.string('signature', { length: 4, assert: 'CHAI' })
.int32be('version')
.int32be('total_length')
.int32be('flags')
.int32be('certificate_count')
.array('certificates', {
type: BCertStructs.BCert,
length: 'certificate_count',
});
}
export class Certificate {
parsed: any
_BCERT: Parser
parsed: any;
_BCERT: Parser;
constructor(parsed_bcert: any, bcert_obj: Parser = BCertStructs.BCert) {
this.parsed = parsed_bcert
this._BCERT = bcert_obj
constructor(parsed_bcert: any, bcert_obj: Parser = BCertStructs.BCert) {
this.parsed = parsed_bcert;
this._BCERT = bcert_obj;
}
// UNSTABLE
static new_key_cert(
cert_id: Buffer,
security_level: number,
client_id: Buffer,
signing_key: ECCKey,
encryption_key: ECCKey,
group_key: ECCKey,
parent: CertificateChain,
expiry: number = 0xffffffff,
max_license: number = 10240,
max_header: number = 15360,
max_chain_depth: number = 2
): Certificate {
if (!cert_id) {
throw new Error('Certificate ID is required');
}
if (!client_id) {
throw new Error('Client ID is required');
}
// UNSTABLE
static new_key_cert(
cert_id: Buffer,
security_level: number,
client_id: Buffer,
signing_key: ECCKey,
encryption_key: ECCKey,
group_key: ECCKey,
parent: CertificateChain,
expiry: number = 0xffffffff,
max_license: number = 10240,
max_header: number = 15360,
max_chain_depth: number = 2
): Certificate {
if (!cert_id) {
throw new Error('Certificate ID is required')
}
if (!client_id) {
throw new Error('Client ID is required')
}
const basic_info = {
cert_id: cert_id,
security_level: security_level,
flags: 0,
cert_type: 2,
public_key_digest: signing_key.publicSha256Digest(),
expiration_date: expiry,
client_id: client_id,
};
const basic_info_attribute = {
flags: 1,
tag: 1,
length: BCertStructs.DrmBCertBasicInfo.encode(basic_info).length + 8,
attribute: basic_info,
};
const basic_info = {
cert_id: cert_id,
security_level: security_level,
flags: 0,
cert_type: 2,
public_key_digest: signing_key.publicSha256Digest(),
expiration_date: expiry,
client_id: client_id
}
const basic_info_attribute = {
flags: 1,
tag: 1,
length:
BCertStructs.DrmBCertBasicInfo.encode(basic_info).length + 8,
attribute: basic_info
}
const device_info = {
max_license: max_license,
max_header: max_header,
max_chain_depth: max_chain_depth,
};
const device_info = {
max_license: max_license,
max_header: max_header,
max_chain_depth: max_chain_depth
}
const device_info_attribute = {
flags: 1,
tag: 4,
length: BCertStructs.DrmBCertDeviceInfo.encode(device_info).length + 8,
attribute: device_info,
};
const device_info_attribute = {
flags: 1,
tag: 4,
length:
BCertStructs.DrmBCertDeviceInfo.encode(device_info).length + 8,
attribute: device_info
}
const feature = {
feature_count: 1,
features: [4, 13],
};
const feature_attribute = {
flags: 1,
tag: 5,
length: BCertStructs.DrmBCertFeatureInfo.encode(feature).length + 8,
attribute: feature,
};
const feature = {
feature_count: 1,
features: [4, 13]
}
const feature_attribute = {
flags: 1,
tag: 5,
length: BCertStructs.DrmBCertFeatureInfo.encode(feature).length + 8,
attribute: feature
}
const cert_key_sign = {
type: 1,
length: 512, // bits
flags: 0,
key: signing_key.privateBytes(),
usages_count: 1,
usages: [1],
};
const cert_key_encrypt = {
type: 1,
length: 512, // bits
flags: 0,
key: encryption_key.privateBytes(),
usages_count: 1,
usages: [2],
};
const key_info = {
key_count: 2,
cert_keys: [cert_key_sign, cert_key_encrypt],
};
const key_info_attribute = {
flags: 1,
tag: 6,
length: BCertStructs.DrmBCertKeyInfo.encode(key_info).length + 8,
attribute: key_info,
};
const cert_key_sign = {
type: 1,
length: 512, // bits
flags: 0,
key: signing_key.privateBytes(),
usages_count: 1,
usages: [1]
}
const cert_key_encrypt = {
type: 1,
length: 512, // bits
flags: 0,
key: encryption_key.privateBytes(),
usages_count: 1,
usages: [2]
}
const key_info = {
key_count: 2,
cert_keys: [cert_key_sign, cert_key_encrypt]
}
const key_info_attribute = {
flags: 1,
tag: 6,
length: BCertStructs.DrmBCertKeyInfo.encode(key_info).length + 8,
attribute: key_info
}
const manufacturer_info = parent.get_certificate(0).get_attribute(7);
const manufacturer_info = parent.get_certificate(0).get_attribute(7)
const new_bcert_container = {
signature: 'CERT',
version: 1,
total_length: 0,
certificate_length: 0,
attributes: [
basic_info_attribute,
device_info_attribute,
feature_attribute,
key_info_attribute,
manufacturer_info,
],
};
const new_bcert_container = {
signature: 'CERT',
version: 1,
total_length: 0,
certificate_length: 0,
attributes: [
basic_info_attribute,
device_info_attribute,
feature_attribute,
key_info_attribute,
manufacturer_info
]
}
let payload = BCertStructs.BCert.encode(new_bcert_container);
new_bcert_container.certificate_length = payload.length;
new_bcert_container.total_length = payload.length + 144;
payload = BCertStructs.BCert.encode(new_bcert_container);
let payload = BCertStructs.BCert.encode(new_bcert_container)
new_bcert_container.certificate_length = payload.length
new_bcert_container.total_length = payload.length + 144
payload = BCertStructs.BCert.encode(new_bcert_container)
const hash = createHash('sha256');
hash.update(payload);
const digest = hash.digest();
const hash = createHash('sha256')
hash.update(payload)
const digest = hash.digest()
const signatureObj = group_key.keyPair.sign(digest);
const r = Buffer.from(signatureObj.r.toArray('be', 32));
const s = Buffer.from(signatureObj.s.toArray('be', 32));
const signature = Buffer.concat([r, s]);
const signatureObj = group_key.keyPair.sign(digest)
const r = Buffer.from(signatureObj.r.toArray('be', 32))
const s = Buffer.from(signatureObj.s.toArray('be', 32))
const signature = Buffer.concat([r, s])
const signature_info = {
signature_type: 1,
signature_size: 64,
signature: signature,
signature_key_size: 512, // bits
signature_key: group_key.publicBytes(),
};
const signature_info_attribute = {
flags: 1,
tag: 8,
length:
BCertStructs.DrmBCertSignatureInfo.encode(signature_info).length + 8,
attribute: signature_info,
};
new_bcert_container.attributes.push(signature_info_attribute);
const signature_info = {
signature_type: 1,
signature_size: 64,
signature: signature,
signature_key_size: 512, // bits
signature_key: group_key.publicBytes()
}
const signature_info_attribute = {
flags: 1,
tag: 8,
length:
BCertStructs.DrmBCertSignatureInfo.encode(signature_info)
.length + 8,
attribute: signature_info
}
new_bcert_container.attributes.push(signature_info_attribute)
return new Certificate(new_bcert_container);
}
return new Certificate(new_bcert_container)
static loads(data: string | Buffer): Certificate {
if (typeof data === 'string') {
data = Buffer.from(data, 'base64');
}
if (!Buffer.isBuffer(data)) {
throw new Error(`Expecting Bytes or Base64 input, got ${data}`);
}
static loads(data: string | Buffer): Certificate {
if (typeof data === 'string') {
data = Buffer.from(data, 'base64')
}
if (!Buffer.isBuffer(data)) {
throw new Error(`Expecting Bytes or Base64 input, got ${data}`)
}
const cert = BCertStructs.BCert;
const parsed_bcert = cert.parse(data);
return new Certificate(parsed_bcert, cert);
}
const cert = BCertStructs.BCert
const parsed_bcert = cert.parse(data)
return new Certificate(parsed_bcert, cert)
}
static load(filePath: string): Certificate {
const data = fs.readFileSync(filePath);
return Certificate.loads(data);
}
static load(filePath: string): Certificate {
const data = fs.readFileSync(filePath)
return Certificate.loads(data)
get_attribute(type_: number) {
for (const attribute of this.parsed.attributes) {
if (attribute.tag === type_) {
return attribute;
}
}
}
get_attribute(type_: number) {
for (const attribute of this.parsed.attributes) {
if (attribute.tag === type_) {
return attribute
}
}
get_security_level(): number {
const basic_info_attribute = this.get_attribute(1);
if (basic_info_attribute) {
return basic_info_attribute.attribute.security_level;
}
return 0;
}
get_security_level(): number {
const basic_info_attribute = this.get_attribute(1)
if (basic_info_attribute) {
return basic_info_attribute.attribute.security_level
}
return 0
}
private static _unpad(name: Buffer): string {
return name.toString('utf8').replace(/\0+$/, '');
}
private static _unpad(name: Buffer): string {
return name.toString('utf8').replace(/\0+$/, '')
get_name(): string {
const manufacturer_info_attribute = this.get_attribute(7);
if (manufacturer_info_attribute) {
const manufacturer_info = manufacturer_info_attribute.attribute;
const manufacturer_name = Certificate._unpad(
manufacturer_info.manufacturer_name
);
const model_name = Certificate._unpad(manufacturer_info.model_name);
const model_number = Certificate._unpad(manufacturer_info.model_number);
return `${manufacturer_name} ${model_name} ${model_number}`;
}
return '';
}
get_name(): string {
const manufacturer_info_attribute = this.get_attribute(7)
if (manufacturer_info_attribute) {
const manufacturer_info = manufacturer_info_attribute.attribute
const manufacturer_name = Certificate._unpad(
manufacturer_info.manufacturer_name
)
const model_name = Certificate._unpad(manufacturer_info.model_name)
const model_number = Certificate._unpad(
manufacturer_info.model_number
)
return `${manufacturer_name} ${model_name} ${model_number}`
}
return ''
}
dumps(): Buffer {
return this._BCERT.encode(this.parsed);
}
dumps(): Buffer {
return this._BCERT.encode(this.parsed)
}
struct(): Parser {
return this._BCERT
}
struct(): Parser {
return this._BCERT;
}
}
export class CertificateChain {
parsed: any
_BCERT_CHAIN: Parser
parsed: any;
_BCERT_CHAIN: Parser;
constructor(
parsed_bcert_chain: any,
bcert_chain_obj: Parser = BCertStructs.BCertChain
) {
this.parsed = parsed_bcert_chain
this._BCERT_CHAIN = bcert_chain_obj
constructor(
parsed_bcert_chain: any,
bcert_chain_obj: Parser = BCertStructs.BCertChain
) {
this.parsed = parsed_bcert_chain;
this._BCERT_CHAIN = bcert_chain_obj;
}
static loads(data: string | Buffer): CertificateChain {
if (typeof data === 'string') {
data = Buffer.from(data, 'base64');
}
if (!Buffer.isBuffer(data)) {
throw new Error(`Expecting Bytes or Base64 input, got ${data}`);
}
static loads(data: string | Buffer): CertificateChain {
if (typeof data === 'string') {
data = Buffer.from(data, 'base64')
}
if (!Buffer.isBuffer(data)) {
throw new Error(`Expecting Bytes or Base64 input, got ${data}`)
}
const cert_chain = BCertStructs.BCertChain
try {
const parsed_bcert_chain = cert_chain.parse(data)
return new CertificateChain(parsed_bcert_chain, cert_chain)
} catch (error) {
console.error('Error during parsing:', error)
throw error
}
const cert_chain = BCertStructs.BCertChain;
try {
const parsed_bcert_chain = cert_chain.parse(data);
return new CertificateChain(parsed_bcert_chain, cert_chain);
} catch (error) {
console.error('Error during parsing:', error);
throw error;
}
}
static load(filePath: string): CertificateChain {
const data = fs.readFileSync(filePath)
return CertificateChain.loads(data)
}
static load(filePath: string): CertificateChain {
const data = fs.readFileSync(filePath);
return CertificateChain.loads(data);
}
dumps(): Buffer {
return this._BCERT_CHAIN.encode(this.parsed)
}
dumps(): Buffer {
return this._BCERT_CHAIN.encode(this.parsed);
}
struct(): Parser {
return this._BCERT_CHAIN
}
struct(): Parser {
return this._BCERT_CHAIN;
}
get_certificate(index: number): Certificate {
return new Certificate(this.parsed.certificates[index])
}
get_certificate(index: number): Certificate {
return new Certificate(this.parsed.certificates[index]);
}
get_security_level(): number {
return this.get_certificate(0).get_security_level()
}
get_security_level(): number {
return this.get_certificate(0).get_security_level();
}
get_name(): string {
return this.get_certificate(0).get_name()
}
get_name(): string {
return this.get_certificate(0).get_name();
}
append(bcert: Certificate): void {
this.parsed.certificate_count += 1
this.parsed.certificates.push(bcert.parsed)
this.parsed.total_length += bcert.dumps().length
}
append(bcert: Certificate): void {
this.parsed.certificate_count += 1;
this.parsed.certificates.push(bcert.parsed);
this.parsed.total_length += bcert.dumps().length;
}
prepend(bcert: Certificate): void {
this.parsed.certificate_count += 1
this.parsed.certificates.unshift(bcert.parsed)
this.parsed.total_length += bcert.dumps().length
}
prepend(bcert: Certificate): void {
this.parsed.certificate_count += 1;
this.parsed.certificates.unshift(bcert.parsed);
this.parsed.total_length += bcert.dumps().length;
}
}

View file

@ -1,289 +1,285 @@
import { CertificateChain } from './bcert'
import ECCKey from './ecc_key'
import ElGamal, { Point } from './elgamal'
import XmlKey from './xml_key'
import { CipherType, getCipherType, Key } from './key'
import { XMRLicense } from './xmrlicense'
import crypto from 'crypto'
import { randomBytes } from 'crypto'
import { createHash } from 'crypto'
import elliptic from 'elliptic'
import { Device } from './device'
import { XMLParser } from 'fast-xml-parser'
import { CertificateChain } from './bcert';
import ECCKey from './ecc_key';
import ElGamal, { Point } from './elgamal';
import XmlKey from './xml_key';
import { CipherType, getCipherType, Key } from './key';
import { XMRLicense } from './xmrlicense';
import crypto from 'crypto';
import { randomBytes } from 'crypto';
import { createHash } from 'crypto';
import elliptic from 'elliptic';
import { Device } from './device';
import { XMLParser } from 'fast-xml-parser';
export default class Cdm {
security_level: number
certificate_chain: CertificateChain
encryption_key: ECCKey
signing_key: ECCKey
client_version: string
la_version: number
security_level: number;
certificate_chain: CertificateChain;
encryption_key: ECCKey;
signing_key: ECCKey;
client_version: string;
la_version: number;
curve: elliptic.ec
elgamal: ElGamal
curve: elliptic.ec;
elgamal: ElGamal;
private wmrm_key: elliptic.ec.KeyPair
private xml_key: XmlKey
private wmrm_key: elliptic.ec.KeyPair;
private xml_key: XmlKey;
constructor(
security_level: number,
certificate_chain: CertificateChain,
encryption_key: ECCKey,
signing_key: ECCKey,
client_version: string = '2.4.117.27',
la_version: number = 1
) {
this.security_level = security_level
this.certificate_chain = certificate_chain
this.encryption_key = encryption_key
this.signing_key = signing_key
this.client_version = client_version
this.la_version = la_version
constructor(
security_level: number,
certificate_chain: CertificateChain,
encryption_key: ECCKey,
signing_key: ECCKey,
client_version: string = '2.4.117.27',
la_version: number = 1
) {
this.security_level = security_level;
this.certificate_chain = certificate_chain;
this.encryption_key = encryption_key;
this.signing_key = signing_key;
this.client_version = client_version;
this.la_version = la_version;
this.curve = new elliptic.ec('p256')
this.elgamal = new ElGamal(this.curve)
this.curve = new elliptic.ec('p256');
this.elgamal = new ElGamal(this.curve);
const x =
'c8b6af16ee941aadaa5389b4af2c10e356be42af175ef3face93254e7b0b3d9b'
const y =
'982b27b5cb2341326e56aa857dbfd5c634ce2cf9ea74fca8f2af5957efeea562'
this.wmrm_key = this.curve.keyFromPublic({ x, y }, 'hex')
this.xml_key = new XmlKey()
}
const x =
'c8b6af16ee941aadaa5389b4af2c10e356be42af175ef3face93254e7b0b3d9b';
const y =
'982b27b5cb2341326e56aa857dbfd5c634ce2cf9ea74fca8f2af5957efeea562';
this.wmrm_key = this.curve.keyFromPublic({ x, y }, 'hex');
this.xml_key = new XmlKey();
}
static fromDevice(device: Device): Cdm {
return new Cdm(
device.security_level,
device.group_certificate,
device.encryption_key,
device.signing_key
)
}
static fromDevice(device: Device): Cdm {
return new Cdm(
device.security_level,
device.group_certificate,
device.encryption_key,
device.signing_key
);
}
private getKeyData(): Buffer {
const messagePoint = this.xml_key.getPoint(this.elgamal.curve)
const [point1, point2] = this.elgamal.encrypt(
messagePoint,
this.wmrm_key.getPublic() as Point
)
private getKeyData(): Buffer {
const messagePoint = this.xml_key.getPoint(this.elgamal.curve);
const [point1, point2] = this.elgamal.encrypt(
messagePoint,
this.wmrm_key.getPublic() as Point
);
const bufferArray = Buffer.concat([
ElGamal.toBytes(point1.getX()),
ElGamal.toBytes(point1.getY()),
ElGamal.toBytes(point2.getX()),
ElGamal.toBytes(point2.getY())
])
const bufferArray = Buffer.concat([
ElGamal.toBytes(point1.getX()),
ElGamal.toBytes(point1.getY()),
ElGamal.toBytes(point2.getX()),
ElGamal.toBytes(point2.getY()),
]);
return bufferArray
}
return bufferArray;
}
private getCipherData(): Buffer {
const b64_chain = this.certificate_chain.dumps().toString('base64')
const body = `<Data><CertificateChains><CertificateChain>${b64_chain}</CertificateChain></CertificateChains></Data>`
private getCipherData(): Buffer {
const b64_chain = this.certificate_chain.dumps().toString('base64');
const body = `<Data><CertificateChains><CertificateChain>${b64_chain}</CertificateChain></CertificateChains></Data>`;
const cipher = crypto.createCipheriv(
'aes-128-cbc',
this.xml_key.aesKey,
this.xml_key.aesIv
)
const cipher = crypto.createCipheriv(
'aes-128-cbc',
this.xml_key.aesKey,
this.xml_key.aesIv
);
const ciphertext = Buffer.concat([
cipher.update(Buffer.from(body, 'utf-8')),
cipher.final()
])
const ciphertext = Buffer.concat([
cipher.update(Buffer.from(body, 'utf-8')),
cipher.final(),
]);
return Buffer.concat([this.xml_key.aesIv, ciphertext])
}
return Buffer.concat([this.xml_key.aesIv, ciphertext]);
}
private buildDigestContent(
content_header: string,
nonce: string,
wmrm_cipher: string,
cert_cipher: string
): string {
const clientTime = Math.floor(Date.now() / 1000)
private buildDigestContent(
content_header: string,
nonce: string,
wmrm_cipher: string,
cert_cipher: string
): string {
const clientTime = Math.floor(Date.now() / 1000);
return (
`<LA xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols" Id="SignedData" xml:space="preserve">` +
`<Version>${this.la_version}</Version>` +
`<ContentHeader>${content_header}</ContentHeader>` +
`<CLIENTINFO>` +
`<CLIENTVERSION>${this.client_version}</CLIENTVERSION>` +
`</CLIENTINFO>` +
`<LicenseNonce>${nonce}</LicenseNonce>` +
`<ClientTime>${clientTime}</ClientTime>` +
`<EncryptedData xmlns="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">` +
`<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"></EncryptionMethod>` +
`<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">` +
`<EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">` +
`<EncryptionMethod Algorithm="http://schemas.microsoft.com/DRM/2007/03/protocols#ecc256"></EncryptionMethod>` +
`<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">` +
`<KeyName>WMRMServer</KeyName>` +
`</KeyInfo>` +
`<CipherData>` +
`<CipherValue>${wmrm_cipher}</CipherValue>` +
`</CipherData>` +
`</EncryptedKey>` +
`</KeyInfo>` +
`<CipherData>` +
`<CipherValue>${cert_cipher}</CipherValue>` +
`</CipherData>` +
`</EncryptedData>` +
`</LA>`
)
}
return (
'<LA xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols" Id="SignedData" xml:space="preserve">' +
`<Version>${this.la_version}</Version>` +
`<ContentHeader>${content_header}</ContentHeader>` +
'<CLIENTINFO>' +
`<CLIENTVERSION>${this.client_version}</CLIENTVERSION>` +
'</CLIENTINFO>' +
`<LicenseNonce>${nonce}</LicenseNonce>` +
`<ClientTime>${clientTime}</ClientTime>` +
'<EncryptedData xmlns="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">' +
'<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"></EncryptionMethod>' +
'<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">' +
'<EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">' +
'<EncryptionMethod Algorithm="http://schemas.microsoft.com/DRM/2007/03/protocols#ecc256"></EncryptionMethod>' +
'<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">' +
'<KeyName>WMRMServer</KeyName>' +
'</KeyInfo>' +
'<CipherData>' +
`<CipherValue>${wmrm_cipher}</CipherValue>` +
'</CipherData>' +
'</EncryptedKey>' +
'</KeyInfo>' +
'<CipherData>' +
`<CipherValue>${cert_cipher}</CipherValue>` +
'</CipherData>' +
'</EncryptedData>' +
'</LA>'
);
}
private static buildSignedInfo(digest_value: string): string {
return (
`<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">` +
`<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>` +
`<SignatureMethod Algorithm="http://schemas.microsoft.com/DRM/2007/03/protocols#ecdsa-sha256"></SignatureMethod>` +
`<Reference URI="#SignedData">` +
`<DigestMethod Algorithm="http://schemas.microsoft.com/DRM/2007/03/protocols#sha256"></DigestMethod>` +
`<DigestValue>${digest_value}</DigestValue>` +
`</Reference>` +
`</SignedInfo>`
)
}
private static buildSignedInfo(digest_value: string): string {
return (
'<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">' +
'<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>' +
'<SignatureMethod Algorithm="http://schemas.microsoft.com/DRM/2007/03/protocols#ecdsa-sha256"></SignatureMethod>' +
'<Reference URI="#SignedData">' +
'<DigestMethod Algorithm="http://schemas.microsoft.com/DRM/2007/03/protocols#sha256"></DigestMethod>' +
`<DigestValue>${digest_value}</DigestValue>` +
'</Reference>' +
'</SignedInfo>'
);
}
getLicenseChallenge(content_header: string): string {
const nonce = randomBytes(16).toString('base64')
const wmrm_cipher = this.getKeyData().toString('base64')
const cert_cipher = this.getCipherData().toString('base64')
getLicenseChallenge(content_header: string): string {
const nonce = randomBytes(16).toString('base64');
const wmrm_cipher = this.getKeyData().toString('base64');
const cert_cipher = this.getCipherData().toString('base64');
const la_content = this.buildDigestContent(
content_header,
nonce,
wmrm_cipher,
cert_cipher
)
const la_content = this.buildDigestContent(
content_header,
nonce,
wmrm_cipher,
cert_cipher
);
const la_hash = createHash('sha256')
.update(la_content, 'utf-8')
.digest()
const la_hash = createHash('sha256').update(la_content, 'utf-8').digest();
const signed_info = Cdm.buildSignedInfo(la_hash.toString('base64'))
const signed_info_digest = createHash('sha256')
.update(signed_info, 'utf-8')
.digest()
const signed_info = Cdm.buildSignedInfo(la_hash.toString('base64'));
const signed_info_digest = createHash('sha256')
.update(signed_info, 'utf-8')
.digest();
const signatureObj = this.signing_key.keyPair.sign(signed_info_digest)
const signatureObj = this.signing_key.keyPair.sign(signed_info_digest);
const r = signatureObj.r.toArrayLike(Buffer, 'be', 32)
const s = signatureObj.s.toArrayLike(Buffer, 'be', 32)
const r = signatureObj.r.toArrayLike(Buffer, 'be', 32);
const s = signatureObj.s.toArrayLike(Buffer, 'be', 32);
const rawSignature = Buffer.concat([r, s])
const signatureValue = rawSignature.toString('base64')
const rawSignature = Buffer.concat([r, s]);
const signatureValue = rawSignature.toString('base64');
const publicKeyBytes = this.signing_key.keyPair
.getPublic()
.encode('array', false)
const publicKeyBuffer = Buffer.from(publicKeyBytes)
const publicKeyBase64 = publicKeyBuffer.toString('base64')
const publicKeyBytes = this.signing_key.keyPair
.getPublic()
.encode('array', false);
const publicKeyBuffer = Buffer.from(publicKeyBytes);
const publicKeyBase64 = publicKeyBuffer.toString('base64');
const main_body =
'<?xml version="1.0" encoding="utf-8"?>' +
'<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' +
'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' +
'<soap:Body>' +
'<AcquireLicense xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols">' +
'<challenge>' +
'<Challenge xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols/messages">' +
la_content +
'<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">' +
signed_info +
`<SignatureValue>${signatureValue}</SignatureValue>` +
'<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">' +
'<KeyValue>' +
'<ECCKeyValue>' +
`<PublicKey>${publicKeyBase64}</PublicKey>` +
'</ECCKeyValue>' +
'</KeyValue>' +
'</KeyInfo>' +
'</Signature>' +
'</Challenge>' +
'</challenge>' +
'</AcquireLicense>' +
'</soap:Body>' +
'</soap:Envelope>'
const main_body =
'<?xml version="1.0" encoding="utf-8"?>' +
'<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' +
'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' +
'<soap:Body>' +
'<AcquireLicense xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols">' +
'<challenge>' +
'<Challenge xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols/messages">' +
la_content +
'<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">' +
signed_info +
`<SignatureValue>${signatureValue}</SignatureValue>` +
'<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">' +
'<KeyValue>' +
'<ECCKeyValue>' +
`<PublicKey>${publicKeyBase64}</PublicKey>` +
'</ECCKeyValue>' +
'</KeyValue>' +
'</KeyInfo>' +
'</Signature>' +
'</Challenge>' +
'</challenge>' +
'</AcquireLicense>' +
'</soap:Body>' +
'</soap:Envelope>';
return main_body
}
return main_body;
}
private _decryptEcc256Key(encrypted_key: Buffer): Buffer {
const point1 = this.curve.curve.point(
encrypted_key.subarray(0, 32).toString('hex'),
encrypted_key.subarray(32, 64).toString('hex')
)
const point2 = this.curve.curve.point(
encrypted_key.subarray(64, 96).toString('hex'),
encrypted_key.subarray(96, 128).toString('hex')
)
private _decryptEcc256Key(encrypted_key: Buffer): Buffer {
const point1 = this.curve.curve.point(
encrypted_key.subarray(0, 32).toString('hex'),
encrypted_key.subarray(32, 64).toString('hex')
);
const point2 = this.curve.curve.point(
encrypted_key.subarray(64, 96).toString('hex'),
encrypted_key.subarray(96, 128).toString('hex')
);
const decrypted = ElGamal.decrypt(
[point1, point2],
this.encryption_key.keyPair.getPrivate()
)
const decryptedBytes = decrypted.getX().toArray('be', 32).slice(16, 32)
const decrypted = ElGamal.decrypt(
[point1, point2],
this.encryption_key.keyPair.getPrivate()
);
const decryptedBytes = decrypted.getX().toArray('be', 32).slice(16, 32);
return Buffer.from(decryptedBytes)
}
return Buffer.from(decryptedBytes);
}
parseLicense(license: string | Buffer): {
key_id: string
key_type: number
cipher_type: number
key_length: number
key: string
}[] {
try {
const parser = new XMLParser({
removeNSPrefix: true
})
const result = parser.parse(license)
parseLicense(license: string | Buffer): {
key_id: string;
key_type: number;
cipher_type: number;
key_length: number;
key: string;
}[] {
try {
const parser = new XMLParser({
removeNSPrefix: true,
});
const result = parser.parse(license);
let licenses =
result['Envelope']['Body']['AcquireLicenseResponse'][
'AcquireLicenseResult'
]['Response']['LicenseResponse']['Licenses']['License']
let licenses =
result['Envelope']['Body']['AcquireLicenseResponse'][
'AcquireLicenseResult'
]['Response']['LicenseResponse']['Licenses']['License'];
if (!Array.isArray(licenses)) {
licenses = [licenses]
}
if (!Array.isArray(licenses)) {
licenses = [licenses];
}
var keys = []
const keys = [];
for (const licenseElement of licenses) {
for (const key of XMRLicense.loads(
licenseElement
).get_content_keys()) {
if (getCipherType(key.cipher_type) === CipherType.ECC256) {
keys.push(
new Key(
this.fixUUID(key.key_id),
key.key_type,
key.cipher_type,
key.key_length,
this._decryptEcc256Key(key.encrypted_key)
)
)
}
}
}
return keys
} catch (error) {
throw new Error(`Unable to parse license, ${error}`)
for (const licenseElement of licenses) {
for (const key of XMRLicense.loads(licenseElement).get_content_keys()) {
if (getCipherType(key.cipher_type) === CipherType.ECC256) {
keys.push(
new Key(
this.fixUUID(key.key_id),
key.key_type,
key.cipher_type,
key.key_length,
this._decryptEcc256Key(key.encrypted_key)
)
);
}
}
}
}
fixUUID(data: Buffer): Buffer {
return Buffer.concat([
Buffer.from(data.subarray(0, 4).reverse()),
Buffer.from(data.subarray(4, 6).reverse()),
Buffer.from(data.subarray(6, 8).reverse()),
data.subarray(8, 16)
])
return keys;
} catch (error) {
throw new Error(`Unable to parse license, ${error}`);
}
}
fixUUID(data: Buffer): Buffer {
return Buffer.concat([
Buffer.from(data.subarray(0, 4).reverse()),
Buffer.from(data.subarray(4, 6).reverse()),
Buffer.from(data.subarray(6, 8).reverse()),
data.subarray(8, 16),
]);
}
}

View file

@ -1,90 +1,92 @@
import { Parser } from 'binary-parser-encoder'
import { CertificateChain } from './bcert'
import ECCKey from './ecc_key'
import * as fs from 'fs'
import { Parser } from 'binary-parser-encoder';
import { CertificateChain } from './bcert';
import ECCKey from './ecc_key';
import * as fs from 'fs';
type RawDeviceV2 = {
signature: string
version: number
group_certificate_length: number
group_certificate: Buffer
encryption_key: Buffer
signing_key: Buffer
}
signature: string;
version: number;
group_certificate_length: number;
group_certificate: Buffer;
encryption_key: Buffer;
signing_key: Buffer;
};
class DeviceStructs {
static magic = 'PRD'
static magic = 'PRD';
static v1 = new Parser()
.string('signature', { length: 3, assert: DeviceStructs.magic })
.uint8('version')
.uint32('group_key_length')
.buffer('group_key', { length: 'group_key_length' })
.uint32('group_certificate_length')
.buffer('group_certificate', { length: 'group_certificate_length' })
static v1 = new Parser()
.string('signature', { length: 3, assert: DeviceStructs.magic })
.uint8('version')
.uint32('group_key_length')
.buffer('group_key', { length: 'group_key_length' })
.uint32('group_certificate_length')
.buffer('group_certificate', { length: 'group_certificate_length' });
static v2 = new Parser()
.string('signature', { length: 3, assert: DeviceStructs.magic })
.uint8('version')
.uint32('group_certificate_length')
.buffer('group_certificate', { length: 'group_certificate_length' })
.buffer('encryption_key', { length: 96 })
.buffer('signing_key', { length: 96 })
static v2 = new Parser()
.string('signature', { length: 3, assert: DeviceStructs.magic })
.uint8('version')
.uint32('group_certificate_length')
.buffer('group_certificate', { length: 'group_certificate_length' })
.buffer('encryption_key', { length: 96 })
.buffer('signing_key', { length: 96 });
}
export class Device {
static CURRENT_STRUCT = DeviceStructs.v2
static CURRENT_STRUCT = DeviceStructs.v2;
group_certificate: CertificateChain
encryption_key: ECCKey
signing_key: ECCKey
security_level: number
group_certificate: CertificateChain;
encryption_key: ECCKey;
signing_key: ECCKey;
security_level: number;
constructor(parsedData: RawDeviceV2) {
this.group_certificate = CertificateChain.loads(
parsedData.group_certificate
)
this.encryption_key = ECCKey.loads(parsedData.encryption_key)
this.signing_key = ECCKey.loads(parsedData.signing_key)
this.security_level = this.group_certificate.get_security_level()
}
constructor(parsedData: RawDeviceV2) {
this.group_certificate = CertificateChain.loads(
parsedData.group_certificate
);
this.encryption_key = ECCKey.loads(parsedData.encryption_key);
this.signing_key = ECCKey.loads(parsedData.signing_key);
this.security_level = this.group_certificate.get_security_level();
}
static loads(data: Buffer): Device {
const parsedData = Device.CURRENT_STRUCT.parse(data)
return new Device(parsedData)
}
static loads(data: Buffer): Device {
const parsedData = Device.CURRENT_STRUCT.parse(data);
return new Device(parsedData);
}
static load(filePath: string): Device {
const data = fs.readFileSync(filePath)
return Device.loads(data)
}
static load(filePath: string): Device {
const data = fs.readFileSync(filePath);
return Device.loads(data);
}
dumps(): Buffer {
const groupCertBytes = this.group_certificate.dumps()
const encryptionKeyBytes = this.encryption_key.dumps()
const signingKeyBytes = this.signing_key.dumps()
dumps(): Buffer {
const groupCertBytes = this.group_certificate.dumps();
const encryptionKeyBytes = this.encryption_key.dumps();
const signingKeyBytes = this.signing_key.dumps();
const buildData = {
signature: DeviceStructs.magic,
version: 2,
group_certificate_length: groupCertBytes.length,
group_certificate: groupCertBytes,
encryption_key: encryptionKeyBytes,
signing_key: signingKeyBytes
}
const buildData = {
signature: DeviceStructs.magic,
version: 2,
group_certificate_length: groupCertBytes.length,
group_certificate: groupCertBytes,
encryption_key: encryptionKeyBytes,
signing_key: signingKeyBytes,
};
return Device.CURRENT_STRUCT.encode(buildData)
}
return Device.CURRENT_STRUCT.encode(buildData);
}
dump(filePath: string): void {
const data = this.dumps()
fs.writeFileSync(filePath, data)
}
dump(filePath: string): void {
const data = this.dumps();
fs.writeFileSync(filePath, data);
}
get_name(): string {
const name = `${this.group_certificate.get_name()}_sl${this.security_level}`
return name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()
}
get_name(): string {
const name = `${this.group_certificate.get_name()}_sl${
this.security_level
}`;
return name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
}
}
// Device V2 disabled because unstable provisioning

View file

@ -1,93 +1,93 @@
import elliptic from 'elliptic'
import { createHash } from 'crypto'
import * as fs from 'fs'
import elliptic from 'elliptic';
import { createHash } from 'crypto';
import * as fs from 'fs';
export default class ECCKey {
keyPair: elliptic.ec.KeyPair
keyPair: elliptic.ec.KeyPair;
constructor(keyPair: elliptic.ec.KeyPair) {
this.keyPair = keyPair
constructor(keyPair: elliptic.ec.KeyPair) {
this.keyPair = keyPair;
}
static generate(): ECCKey {
const EC = new elliptic.ec('p256');
const keyPair = EC.genKeyPair();
return new ECCKey(keyPair);
}
static construct(privateKey: Buffer | string | number): ECCKey {
if (Buffer.isBuffer(privateKey)) {
privateKey = privateKey.toString('hex');
} else if (typeof privateKey === 'number') {
privateKey = privateKey.toString(16);
}
static generate(): ECCKey {
const EC = new elliptic.ec('p256')
const keyPair = EC.genKeyPair()
return new ECCKey(keyPair)
const EC = new elliptic.ec('p256');
const keyPair = EC.keyFromPrivate(privateKey, 'hex');
return new ECCKey(keyPair);
}
static loads(data: string | Buffer): ECCKey {
if (typeof data === 'string') {
data = Buffer.from(data, 'base64');
}
if (!Buffer.isBuffer(data)) {
throw new Error(`Expecting Bytes or Base64 input, got ${data}`);
}
static construct(privateKey: Buffer | string | number): ECCKey {
if (Buffer.isBuffer(privateKey)) {
privateKey = privateKey.toString('hex')
} else if (typeof privateKey === 'number') {
privateKey = privateKey.toString(16)
}
const EC = new elliptic.ec('p256')
const keyPair = EC.keyFromPrivate(privateKey, 'hex')
return new ECCKey(keyPair)
if (data.length !== 96 && data.length !== 32) {
throw new Error(
`Invalid data length. Expecting 96 or 32 bytes, got ${data.length}`
);
}
static loads(data: string | Buffer): ECCKey {
if (typeof data === 'string') {
data = Buffer.from(data, 'base64')
}
if (!Buffer.isBuffer(data)) {
throw new Error(`Expecting Bytes or Base64 input, got ${data}`)
}
const privateKey = data.subarray(0, 32);
return ECCKey.construct(privateKey);
}
if (data.length !== 96 && data.length !== 32) {
throw new Error(
`Invalid data length. Expecting 96 or 32 bytes, got ${data.length}`
)
}
static load(filePath: string): ECCKey {
const data = fs.readFileSync(filePath);
return ECCKey.loads(data);
}
const privateKey = data.subarray(0, 32)
return ECCKey.construct(privateKey)
}
dumps(): Buffer {
return Buffer.concat([this.privateBytes(), this.publicBytes()]);
}
static load(filePath: string): ECCKey {
const data = fs.readFileSync(filePath)
return ECCKey.loads(data)
}
dump(filePath: string): void {
fs.writeFileSync(filePath, this.dumps());
}
dumps(): Buffer {
return Buffer.concat([this.privateBytes(), this.publicBytes()])
}
getPoint(): { x: string; y: string } {
const publicKey = this.keyPair.getPublic();
return {
x: publicKey.getX().toString('hex'),
y: publicKey.getY().toString('hex'),
};
}
dump(filePath: string): void {
fs.writeFileSync(filePath, this.dumps())
}
privateBytes(): Buffer {
const privateKey = this.keyPair.getPrivate();
return Buffer.from(privateKey.toArray('be', 32));
}
getPoint(): { x: string; y: string } {
const publicKey = this.keyPair.getPublic()
return {
x: publicKey.getX().toString('hex'),
y: publicKey.getY().toString('hex')
}
}
privateSha256Digest(): Buffer {
const hash = createHash('sha256');
hash.update(this.privateBytes());
return hash.digest();
}
privateBytes(): Buffer {
const privateKey = this.keyPair.getPrivate()
return Buffer.from(privateKey.toArray('be', 32))
}
publicBytes(): Buffer {
const publicKey = this.keyPair.getPublic();
const x = publicKey.getX().toArray('be', 32);
const y = publicKey.getY().toArray('be', 32);
return Buffer.concat([Buffer.from(x), Buffer.from(y)]);
}
privateSha256Digest(): Buffer {
const hash = createHash('sha256')
hash.update(this.privateBytes())
return hash.digest()
}
publicBytes(): Buffer {
const publicKey = this.keyPair.getPublic()
const x = publicKey.getX().toArray('be', 32)
const y = publicKey.getY().toArray('be', 32)
return Buffer.concat([Buffer.from(x), Buffer.from(y)])
}
publicSha256Digest(): Buffer {
const hash = createHash('sha256')
hash.update(this.publicBytes())
return hash.digest()
}
publicSha256Digest(): Buffer {
const hash = createHash('sha256');
hash.update(this.publicBytes());
return hash.digest();
}
}

View file

@ -1,45 +1,45 @@
import { ec as EC } from 'elliptic'
import { randomBytes } from 'crypto'
import BN from 'bn.js'
import { ec as EC } from 'elliptic';
import { randomBytes } from 'crypto';
import BN from 'bn.js';
export interface Point {
getY(): BN
getX(): BN
add(point: Point): Point
mul(n: BN | bigint | number): Point
neg(): Point
getY(): BN;
getX(): BN;
add(point: Point): Point;
mul(n: BN | bigint | number): Point;
neg(): Point;
}
export default class ElGamal {
curve: EC
curve: EC;
constructor(curve: EC) {
this.curve = curve
constructor(curve: EC) {
this.curve = curve;
}
static toBytes(n: BN): Uint8Array {
const byteArray = n.toString(16).padStart(2, '0');
if (byteArray.length % 2 !== 0) {
return Uint8Array.from(Buffer.from('0' + byteArray, 'hex'));
}
return Uint8Array.from(Buffer.from(byteArray, 'hex'));
}
static toBytes(n: BN): Uint8Array {
const byteArray = n.toString(16).padStart(2, '0')
if (byteArray.length % 2 !== 0) {
return Uint8Array.from(Buffer.from('0' + byteArray, 'hex'))
}
return Uint8Array.from(Buffer.from(byteArray, 'hex'))
}
encrypt(messagePoint: Point, publicKey: Point): [Point, Point] {
const ephemeralKey = new BN(randomBytes(32).toString('hex'), 16).mod(
this.curve.n!
);
const ephemeralKeyBigInt = BigInt(ephemeralKey.toString(10));
const point1 = this.curve.g.mul(ephemeralKeyBigInt);
const point2 = messagePoint.add(publicKey.mul(ephemeralKeyBigInt));
encrypt(messagePoint: Point, publicKey: Point): [Point, Point] {
const ephemeralKey = new BN(randomBytes(32).toString('hex'), 16).mod(
this.curve.n!
)
const ephemeralKeyBigInt = BigInt(ephemeralKey.toString(10))
const point1 = this.curve.g.mul(ephemeralKeyBigInt)
const point2 = messagePoint.add(publicKey.mul(ephemeralKeyBigInt))
return [point1, point2];
}
return [point1, point2]
}
static decrypt(encrypted: [Point, Point], privateKey: BN): Point {
const [point1, point2] = encrypted
const sharedSecret = point1.mul(privateKey)
const decryptedMessage = point2.add(sharedSecret.neg())
return decryptedMessage
}
static decrypt(encrypted: [Point, Point], privateKey: BN): Point {
const [point1, point2] = encrypted;
const sharedSecret = point1.mul(privateKey);
const decryptedMessage = point2.add(sharedSecret.neg());
return decryptedMessage;
}
}

View file

@ -1,70 +1,70 @@
export enum KeyType {
Invalid = 0x0000,
AES128CTR = 0x0001,
RC4 = 0x0002,
AES128ECB = 0x0003,
Cocktail = 0x0004,
AESCBC = 0x0005,
UNKNOWN = 0xffff
Invalid = 0x0000,
AES128CTR = 0x0001,
RC4 = 0x0002,
AES128ECB = 0x0003,
Cocktail = 0x0004,
AESCBC = 0x0005,
UNKNOWN = 0xffff,
}
export function getKeyType(value: number): KeyType {
switch (value) {
case KeyType.Invalid:
case KeyType.AES128CTR:
case KeyType.RC4:
case KeyType.AES128ECB:
case KeyType.Cocktail:
case KeyType.AESCBC:
return value
default:
return KeyType.UNKNOWN
}
switch (value) {
case KeyType.Invalid:
case KeyType.AES128CTR:
case KeyType.RC4:
case KeyType.AES128ECB:
case KeyType.Cocktail:
case KeyType.AESCBC:
return value;
default:
return KeyType.UNKNOWN;
}
}
export enum CipherType {
Invalid = 0x0000,
RSA128 = 0x0001,
ChainedLicense = 0x0002,
ECC256 = 0x0003,
ECCforScalableLicenses = 0x0004,
Scalable = 0x0005,
UNKNOWN = 0xffff
Invalid = 0x0000,
RSA128 = 0x0001,
ChainedLicense = 0x0002,
ECC256 = 0x0003,
ECCforScalableLicenses = 0x0004,
Scalable = 0x0005,
UNKNOWN = 0xffff,
}
export function getCipherType(value: number): CipherType {
switch (value) {
case CipherType.Invalid:
case CipherType.RSA128:
case CipherType.ChainedLicense:
case CipherType.ECC256:
case CipherType.ECCforScalableLicenses:
case CipherType.Scalable:
return value
default:
return CipherType.UNKNOWN
}
switch (value) {
case CipherType.Invalid:
case CipherType.RSA128:
case CipherType.ChainedLicense:
case CipherType.ECC256:
case CipherType.ECCforScalableLicenses:
case CipherType.Scalable:
return value;
default:
return CipherType.UNKNOWN;
}
}
export class Key {
key_id: string
key_type: KeyType
cipher_type: CipherType
key_length: number
key: string
key_id: string;
key_type: KeyType;
cipher_type: CipherType;
key_length: number;
key: string;
constructor(
key_id: Buffer | string,
key_type: number,
cipher_type: number,
key_length: number,
key: Buffer | string
) {
this.key_id = Buffer.isBuffer(key_id) ? key_id.toString('hex') : key_id
constructor(
key_id: Buffer | string,
key_type: number,
cipher_type: number,
key_length: number,
key: Buffer | string
) {
this.key_id = Buffer.isBuffer(key_id) ? key_id.toString('hex') : key_id;
this.key_type = getKeyType(key_type)
this.cipher_type = getCipherType(cipher_type)
this.key_length = key_length
this.key = Buffer.isBuffer(key) ? key.toString('hex') : key
}
this.key_type = getKeyType(key_type);
this.cipher_type = getCipherType(cipher_type);
this.key_length = key_length;
this.key = Buffer.isBuffer(key) ? key.toString('hex') : key;
}
}

View file

@ -1,131 +1,121 @@
import { Parser } from 'binary-parser'
import { Buffer } from 'buffer'
import WRMHeader from './wrmheader'
import { Parser } from 'binary-parser';
import { Buffer } from 'buffer';
import WRMHeader from './wrmheader';
const SYSTEM_ID = Buffer.from('9a04f07998404286ab92e65be0885f95', 'hex')
const SYSTEM_ID = Buffer.from('9a04f07998404286ab92e65be0885f95', 'hex');
const PSSHBox = new Parser()
.uint32('length')
.string('pssh', { length: 4, assert: 'pssh' })
.uint32('fullbox')
.buffer('system_id', { length: 16 })
.uint32('data_length')
.buffer('data', { length: 'data_length' })
.uint32('length')
.string('pssh', { length: 4, assert: 'pssh' })
.uint32('fullbox')
.buffer('system_id', { length: 16 })
.uint32('data_length')
.buffer('data', { length: 'data_length' });
const PlayreadyObject = new Parser()
.endianess('little')
.uint16('type')
.uint16('length')
.choice('data', {
tag: 'type',
choices: {
1: new Parser().string('data', {
length: 'length',
encoding: 'utf16le'
})
},
defaultChoice: new Parser().buffer('data', { length: 'length' })
})
.endianess('little')
.uint16('type')
.uint16('length')
.choice('data', {
tag: 'type',
choices: {
1: new Parser().string('data', {
length: 'length',
encoding: 'utf16le',
}),
},
defaultChoice: new Parser().buffer('data', { length: 'length' }),
});
const PlayreadyHeader = new Parser()
.endianess('little')
.uint32('length')
.uint16('record_count')
.array('records', {
length: 'record_count',
type: PlayreadyObject
})
.endianess('little')
.uint32('length')
.uint16('record_count')
.array('records', {
length: 'record_count',
type: PlayreadyObject,
});
function isPlayreadyPsshBox(data: Buffer): boolean {
if (data.length < 28) return false // Ensure enough length
return data.subarray(12, 28).equals(SYSTEM_ID)
if (data.length < 28) return false; // Ensure enough length
return data.subarray(12, 28).equals(SYSTEM_ID);
}
function isUtf16(data: Buffer): boolean {
for (let i = 1; i < data.length; i += 2) {
if (data[i] !== 0) {
return false
}
for (let i = 1; i < data.length; i += 2) {
if (data[i] !== 0) {
return false;
}
return true
}
return true;
}
function* getWrmHeaders(wrm_header: any): IterableIterator<string> {
for (const record of wrm_header.records) {
if (record.type === 1 && typeof record.data === 'string') {
yield record.data
}
for (const record of wrm_header.records) {
if (record.type === 1 && typeof record.data === 'string') {
yield record.data;
}
}
}
export class PSSH {
public wrm_headers: string[]
public wrm_headers: string[];
constructor(data: string | Buffer) {
if (!data) {
throw new Error('Data must not be empty')
}
if (typeof data === 'string') {
try {
data = Buffer.from(data, 'base64')
} catch (e) {
throw new Error(`Could not decode data as Base64: ${e}`)
}
}
try {
if (isPlayreadyPsshBox(data)) {
const pssh_box = PSSHBox.parse(data)
const psshData = pssh_box.data
if (isUtf16(psshData)) {
this.wrm_headers = [psshData.toString('utf16le')]
} else if (isUtf16(psshData.subarray(6))) {
this.wrm_headers = [
psshData.subarray(6).toString('utf16le')
]
} else if (isUtf16(psshData.subarray(10))) {
this.wrm_headers = [
psshData.subarray(10).toString('utf16le')
]
} else {
const playready_header = PlayreadyHeader.parse(psshData)
this.wrm_headers = Array.from(
getWrmHeaders(playready_header)
)
}
} else {
if (isUtf16(data)) {
this.wrm_headers = [data.toString('utf16le')]
} else if (isUtf16(data.subarray(6))) {
this.wrm_headers = [data.subarray(6).toString('utf16le')]
} else if (isUtf16(data.subarray(10))) {
this.wrm_headers = [data.subarray(10).toString('utf16le')]
} else {
const playready_header = PlayreadyHeader.parse(data)
this.wrm_headers = Array.from(
getWrmHeaders(playready_header)
)
}
}
} catch (e) {
throw new Error(
'Could not parse data as a PSSH Box nor a PlayReadyHeader'
)
}
constructor(data: string | Buffer) {
if (!data) {
throw new Error('Data must not be empty');
}
// Header downgrade
public get_wrm_headers(downgrade_to_v4: boolean = false): string[] {
return this.wrm_headers.map(
downgrade_to_v4 ? this._downgrade : (_) => _
)
if (typeof data === 'string') {
try {
data = Buffer.from(data, 'base64');
} catch (e) {
throw new Error(`Could not decode data as Base64: ${e}`);
}
}
private _downgrade(wrm_header: string): string {
const header = new WRMHeader(wrm_header)
return header.to_v4_0_0_0()
try {
if (isPlayreadyPsshBox(data)) {
const pssh_box = PSSHBox.parse(data);
const psshData = pssh_box.data;
if (isUtf16(psshData)) {
this.wrm_headers = [psshData.toString('utf16le')];
} else if (isUtf16(psshData.subarray(6))) {
this.wrm_headers = [psshData.subarray(6).toString('utf16le')];
} else if (isUtf16(psshData.subarray(10))) {
this.wrm_headers = [psshData.subarray(10).toString('utf16le')];
} else {
const playready_header = PlayreadyHeader.parse(psshData);
this.wrm_headers = Array.from(getWrmHeaders(playready_header));
}
} else {
if (isUtf16(data)) {
this.wrm_headers = [data.toString('utf16le')];
} else if (isUtf16(data.subarray(6))) {
this.wrm_headers = [data.subarray(6).toString('utf16le')];
} else if (isUtf16(data.subarray(10))) {
this.wrm_headers = [data.subarray(10).toString('utf16le')];
} else {
const playready_header = PlayreadyHeader.parse(data);
this.wrm_headers = Array.from(getWrmHeaders(playready_header));
}
}
} catch (e) {
throw new Error(
'Could not parse data as a PSSH Box nor a PlayReadyHeader'
);
}
}
// Header downgrade
public get_wrm_headers(downgrade_to_v4: boolean = false): string[] {
return this.wrm_headers.map(downgrade_to_v4 ? this._downgrade : (_) => _);
}
private _downgrade(wrm_header: string): string {
const header = new WRMHeader(wrm_header);
return header.to_v4_0_0_0();
}
}

View file

@ -1,112 +1,118 @@
import { XMLParser } from 'fast-xml-parser'
import { XMLParser } from 'fast-xml-parser';
export class SignedKeyID {
constructor(
public alg_id: string,
public value: string,
public checksum?: string
) {}
constructor(
public alg_id: string,
public value: string,
public checksum?: string
) {}
}
export type Version = '4.0.0.0' | '4.1.0.0' | '4.2.0.0' | '4.3.0.0' | 'UNKNOWN'
export type Version = '4.0.0.0' | '4.1.0.0' | '4.2.0.0' | '4.3.0.0' | 'UNKNOWN';
export type ReturnStructure = [
SignedKeyID[],
string | null,
string | null,
string | null
]
SignedKeyID[],
string | null,
string | null,
string | null
];
interface ParsedWRMHeader {
WRMHEADER: {
'@_version': string
DATA?: any
}
WRMHEADER: {
'@_version': string;
DATA?: any;
};
}
export default class WRMHeader {
private header: ParsedWRMHeader['WRMHEADER']
version: Version
private header: ParsedWRMHeader['WRMHEADER'];
version: Version;
constructor(data: string) {
if (!data) throw new Error('Data must not be empty')
constructor(data: string) {
if (!data) throw new Error('Data must not be empty');
const parser = new XMLParser({
ignoreAttributes: false,
removeNSPrefix: true,
attributeNamePrefix: '@_'
})
const parsed = parser.parse(data) as ParsedWRMHeader
const parser = new XMLParser({
ignoreAttributes: false,
removeNSPrefix: true,
attributeNamePrefix: '@_',
});
const parsed = parser.parse(data) as ParsedWRMHeader;
if (!parsed.WRMHEADER) throw new Error('Data is not a valid WRMHEADER')
if (!parsed.WRMHEADER) throw new Error('Data is not a valid WRMHEADER');
this.header = parsed.WRMHEADER
this.version = WRMHeader.fromString(this.header['@_version'])
this.header = parsed.WRMHEADER;
this.version = WRMHeader.fromString(this.header['@_version']);
}
private static fromString(value: string): Version {
if (['4.0.0.0', '4.1.0.0', '4.2.0.0', '4.3.0.0'].includes(value)) {
return value as Version;
}
return 'UNKNOWN';
}
private static fromString(value: string): Version {
if (['4.0.0.0', '4.1.0.0', '4.2.0.0', '4.3.0.0'].includes(value)) {
return value as Version
}
return 'UNKNOWN'
}
private static ensureList(element: any): any[] {
return Array.isArray(element) ? element : [element];
}
private static ensureList(element: any): any[] {
return Array.isArray(element) ? element : [element]
}
to_v4_0_0_0(): string {
const [key_ids, la_url, lui_url, ds_id] = this.readAttributes();
if (key_ids.length === 0) throw new Error('No Key IDs available');
const key_id = key_ids[0];
return `<WRMHEADER xmlns="http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader" version="4.0.0.0"><DATA><PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></PROTECTINFO><KID>${
key_id.value
}</KID>${la_url ? `<LA_URL>${la_url}</LA_URL>` : ''}${
lui_url ? `<LUI_URL>${lui_url}</LUI_URL>` : ''
}${ds_id ? `<DS_ID>${ds_id}</DS_ID>` : ''}${
key_id.checksum ? `<CHECKSUM>${key_id.checksum}</CHECKSUM>` : ''
}</DATA></WRMHEADER>`;
}
to_v4_0_0_0(): string {
const [key_ids, la_url, lui_url, ds_id] = this.readAttributes()
if (key_ids.length === 0) throw new Error('No Key IDs available')
const key_id = key_ids[0]
return `<WRMHEADER xmlns="http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader" version="4.0.0.0"><DATA><PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></PROTECTINFO><KID>${key_id.value}</KID>${la_url ? `<LA_URL>${la_url}</LA_URL>` : ''}${lui_url ? `<LUI_URL>${lui_url}</LUI_URL>` : ''}${ds_id ? `<DS_ID>${ds_id}</DS_ID>` : ''}${key_id.checksum ? `<CHECKSUM>${key_id.checksum}</CHECKSUM>` : ''}</DATA></WRMHEADER>`
readAttributes(): ReturnStructure {
const data = this.header.DATA;
if (!data)
throw new Error(
'Not a valid PlayReady Header Record, WRMHEADER/DATA required'
);
switch (this.version) {
case '4.0.0.0':
return WRMHeader.read_v4(data);
case '4.1.0.0':
case '4.2.0.0':
case '4.3.0.0':
return WRMHeader.read_vX(data);
default:
throw new Error(`Unsupported version: ${this.version}`);
}
}
readAttributes(): ReturnStructure {
const data = this.header.DATA
if (!data)
throw new Error(
'Not a valid PlayReady Header Record, WRMHEADER/DATA required'
)
switch (this.version) {
case '4.0.0.0':
return WRMHeader.read_v4(data)
case '4.1.0.0':
case '4.2.0.0':
case '4.3.0.0':
return WRMHeader.read_vX(data)
default:
throw new Error(`Unsupported version: ${this.version}`)
}
}
private static read_v4(data: any): ReturnStructure {
const protectInfo = data.PROTECTINFO;
return [
[new SignedKeyID(protectInfo.ALGID, data.KID, data.CHECKSUM)],
data.LA_URL || null,
data.LUI_URL || null,
data.DS_ID || null,
];
}
private static read_v4(data: any): ReturnStructure {
const protectInfo = data.PROTECTINFO
return [
[new SignedKeyID(protectInfo.ALGID, data.KID, data.CHECKSUM)],
data.LA_URL || null,
data.LUI_URL || null,
data.DS_ID || null
]
}
private static read_vX(data: any): ReturnStructure {
const protectInfo = data.PROTECTINFO
const signedKeyIDs: SignedKeyID[] = protectInfo?.KID
? WRMHeader.ensureList(protectInfo.KID).map(
(kid: any) =>
new SignedKeyID(
kid['@_ALGID'] || '',
kid['@_VALUE'],
kid['@_CHECKSUM']
)
)
: []
return [
signedKeyIDs,
data.LA_URL || null,
data.LUI_URL || null,
data.DS_ID || null
]
}
private static read_vX(data: any): ReturnStructure {
const protectInfo = data.PROTECTINFO;
const signedKeyIDs: SignedKeyID[] = protectInfo?.KID
? WRMHeader.ensureList(protectInfo.KID).map(
(kid: any) =>
new SignedKeyID(
kid['@_ALGID'] || '',
kid['@_VALUE'],
kid['@_CHECKSUM']
)
)
: [];
return [
signedKeyIDs,
data.LA_URL || null,
data.LUI_URL || null,
data.DS_ID || null,
];
}
}

View file

@ -1,28 +1,28 @@
import BN from 'bn.js'
import { ec as EC } from 'elliptic'
import ECCKey from './ecc_key'
import ElGamal, { Point } from './elgamal'
import BN from 'bn.js';
import { ec as EC } from 'elliptic';
import ECCKey from './ecc_key';
import ElGamal, { Point } from './elgamal';
export default class XmlKey {
private _sharedPoint: ECCKey
public sharedKeyX: BN
public sharedKeyY: BN
public _shared_key_x_bytes: Uint8Array
public aesIv: Uint8Array
public aesKey: Uint8Array
private _sharedPoint: ECCKey;
public sharedKeyX: BN;
public sharedKeyY: BN;
public _shared_key_x_bytes: Uint8Array;
public aesIv: Uint8Array;
public aesKey: Uint8Array;
constructor() {
this._sharedPoint = ECCKey.generate()
this.sharedKeyX = this._sharedPoint.keyPair.getPublic().getX()
this.sharedKeyY = this._sharedPoint.keyPair.getPublic().getY()
this._shared_key_x_bytes = ElGamal.toBytes(this.sharedKeyX)
this.aesIv = this._shared_key_x_bytes.subarray(0, 16)
this.aesKey = this._shared_key_x_bytes.subarray(16, 32)
}
constructor() {
this._sharedPoint = ECCKey.generate();
this.sharedKeyX = this._sharedPoint.keyPair.getPublic().getX();
this.sharedKeyY = this._sharedPoint.keyPair.getPublic().getY();
this._shared_key_x_bytes = ElGamal.toBytes(this.sharedKeyX);
this.aesIv = this._shared_key_x_bytes.subarray(0, 16);
this.aesKey = this._shared_key_x_bytes.subarray(16, 32);
}
getPoint(curve: EC): Point {
return curve.curve.point(this.sharedKeyX, this.sharedKeyY)
}
getPoint(curve: EC): Point {
return curve.curve.point(this.sharedKeyX, this.sharedKeyY);
}
}
// Make it more undetectable (not working right now)

View file

@ -1,270 +1,270 @@
import { Parser } from 'binary-parser'
import * as fs from 'fs'
import { Parser } from 'binary-parser';
import * as fs from 'fs';
export class XMRLicenseStructs {
static PlayEnablerType = new Parser().buffer('player_enabler_type', {
length: 16
static PlayEnablerType = new Parser().buffer('player_enabler_type', {
length: 16,
});
static DomainRestrictionObject = new Parser()
.buffer('account_id', { length: 16 })
.uint32('revision');
static IssueDateObject = new Parser().uint32('issue_date');
static RevInfoVersionObject = new Parser().uint32('sequence');
static SecurityLevelObject = new Parser().uint16('minimum_security_level');
static EmbeddedLicenseSettingsObject = new Parser().uint16('indicator');
static ECCKeyObject = new Parser()
.uint16('curve_type')
.uint16('key_length')
.buffer('key', {
length: function () {
return (this as any).key_length;
},
});
static SignatureObject = new Parser()
.uint16('signature_type')
.uint16('signature_data_length')
.buffer('signature_data', {
length: function () {
return (this as any).signature_data_length;
},
});
static ContentKeyObject = new Parser()
.buffer('key_id', { length: 16 })
.uint16('key_type')
.uint16('cipher_type')
.uint16('key_length')
.buffer('encrypted_key', {
length: function () {
return (this as any).key_length;
},
});
static RightsSettingsObject = new Parser().uint16('rights');
static OutputProtectionLevelRestrictionObject = new Parser()
.uint16('minimum_compressed_digital_video_opl')
.uint16('minimum_uncompressed_digital_video_opl')
.uint16('minimum_analog_video_opl')
.uint16('minimum_digital_compressed_audio_opl')
.uint16('minimum_digital_uncompressed_audio_opl');
static ExpirationRestrictionObject = new Parser()
.uint32('begin_date')
.uint32('end_date');
static RemovalDateObject = new Parser().uint32('removal_date');
static UplinkKIDObject = new Parser()
.buffer('uplink_kid', { length: 16 })
.uint16('chained_checksum_type')
.uint16('chained_checksum_length')
.buffer('chained_checksum', {
length: function () {
return (this as any).chained_checksum_length;
},
});
static AnalogVideoOutputConfigurationRestriction = new Parser()
.buffer('video_output_protection_id', { length: 16 })
.buffer('binary_configuration_data', {
length: function () {
return (this as any).$parent.length - 16;
},
});
static DigitalVideoOutputRestrictionObject = new Parser()
.buffer('video_output_protection_id', { length: 16 })
.buffer('binary_configuration_data', {
length: function () {
return (this as any).$parent.length - 16;
},
});
static DigitalAudioOutputRestrictionObject = new Parser()
.buffer('audio_output_protection_id', { length: 16 })
.buffer('binary_configuration_data', {
length: function () {
return (this as any).$parent.length - 16;
},
});
static PolicyMetadataObject = new Parser()
.buffer('metadata_type', { length: 16 })
.buffer('policy_data', {
length: function () {
return (this as any).$parent.length - 16;
},
});
static SecureStopRestrictionObject = new Parser().buffer('metering_id', {
length: 16,
});
static MeteringRestrictionObject = new Parser().buffer('metering_id', {
length: 16,
});
static ExpirationAfterFirstPlayRestrictionObject = new Parser().uint32(
'seconds'
);
static GracePeriodObject = new Parser().uint32('grace_period');
static SourceIdObject = new Parser().uint32('source_id');
static AuxiliaryKey = new Parser()
.uint32('location')
.buffer('key', { length: 16 });
static AuxiliaryKeysObject = new Parser()
.uint16('count')
.array('auxiliary_keys', {
length: 'count',
type: XMRLicenseStructs.AuxiliaryKey,
});
static UplinkKeyObject3 = new Parser()
.buffer('uplink_key_id', { length: 16 })
.uint16('chained_length')
.buffer('checksum', {
length: function () {
return (this as any).chained_length;
},
})
.uint16('count')
.array('entries', {
length: 'count',
type: new Parser().uint32('entry'),
});
static DomainRestrictionObject = new Parser()
.buffer('account_id', { length: 16 })
.uint32('revision')
static CopyEnablerObject = new Parser().buffer('copy_enabler_type', {
length: 16,
});
static IssueDateObject = new Parser().uint32('issue_date')
static CopyCountRestrictionObject = new Parser().uint32('count');
static RevInfoVersionObject = new Parser().uint32('sequence')
static MoveObject = new Parser().uint32('minimum_move_protection_level');
static SecurityLevelObject = new Parser().uint16('minimum_security_level')
static XMRObject = (): Parser =>
new Parser()
.namely('self')
.int16('flags')
.int16('type')
.int32('length')
.choice('data', {
tag: 'type',
choices: {
0x0005: XMRLicenseStructs.OutputProtectionLevelRestrictionObject,
0x0008: XMRLicenseStructs.AnalogVideoOutputConfigurationRestriction,
0x000a: XMRLicenseStructs.ContentKeyObject,
0x000b: XMRLicenseStructs.SignatureObject,
0x000d: XMRLicenseStructs.RightsSettingsObject,
0x0012: XMRLicenseStructs.ExpirationRestrictionObject,
0x0013: XMRLicenseStructs.IssueDateObject,
0x0016: XMRLicenseStructs.MeteringRestrictionObject,
0x001a: XMRLicenseStructs.GracePeriodObject,
0x0022: XMRLicenseStructs.SourceIdObject,
0x002a: XMRLicenseStructs.ECCKeyObject,
0x002c: XMRLicenseStructs.PolicyMetadataObject,
0x0029: XMRLicenseStructs.DomainRestrictionObject,
0x0030: XMRLicenseStructs.ExpirationAfterFirstPlayRestrictionObject,
0x0031: XMRLicenseStructs.DigitalAudioOutputRestrictionObject,
0x0032: XMRLicenseStructs.RevInfoVersionObject,
0x0033: XMRLicenseStructs.EmbeddedLicenseSettingsObject,
0x0034: XMRLicenseStructs.SecurityLevelObject,
0x0037: XMRLicenseStructs.MoveObject,
0x0039: XMRLicenseStructs.PlayEnablerType,
0x003a: XMRLicenseStructs.CopyEnablerObject,
0x003b: XMRLicenseStructs.UplinkKIDObject,
0x003d: XMRLicenseStructs.CopyCountRestrictionObject,
0x0050: XMRLicenseStructs.RemovalDateObject,
0x0051: XMRLicenseStructs.AuxiliaryKeysObject,
0x0052: XMRLicenseStructs.UplinkKeyObject3,
0x005a: XMRLicenseStructs.SecureStopRestrictionObject,
0x0059: XMRLicenseStructs.DigitalVideoOutputRestrictionObject,
},
defaultChoice: 'self',
});
static EmbeddedLicenseSettingsObject = new Parser().uint16('indicator')
static ECCKeyObject = new Parser()
.uint16('curve_type')
.uint16('key_length')
.buffer('key', {
length: function () {
return (this as any).key_length
}
})
static SignatureObject = new Parser()
.uint16('signature_type')
.uint16('signature_data_length')
.buffer('signature_data', {
length: function () {
return (this as any).signature_data_length
}
})
static ContentKeyObject = new Parser()
.buffer('key_id', { length: 16 })
.uint16('key_type')
.uint16('cipher_type')
.uint16('key_length')
.buffer('encrypted_key', {
length: function () {
return (this as any).key_length
}
})
static RightsSettingsObject = new Parser().uint16('rights')
static OutputProtectionLevelRestrictionObject = new Parser()
.uint16('minimum_compressed_digital_video_opl')
.uint16('minimum_uncompressed_digital_video_opl')
.uint16('minimum_analog_video_opl')
.uint16('minimum_digital_compressed_audio_opl')
.uint16('minimum_digital_uncompressed_audio_opl')
static ExpirationRestrictionObject = new Parser()
.uint32('begin_date')
.uint32('end_date')
static RemovalDateObject = new Parser().uint32('removal_date')
static UplinkKIDObject = new Parser()
.buffer('uplink_kid', { length: 16 })
.uint16('chained_checksum_type')
.uint16('chained_checksum_length')
.buffer('chained_checksum', {
length: function () {
return (this as any).chained_checksum_length
}
})
static AnalogVideoOutputConfigurationRestriction = new Parser()
.buffer('video_output_protection_id', { length: 16 })
.buffer('binary_configuration_data', {
length: function () {
return (this as any).$parent.length - 16
}
})
static DigitalVideoOutputRestrictionObject = new Parser()
.buffer('video_output_protection_id', { length: 16 })
.buffer('binary_configuration_data', {
length: function () {
return (this as any).$parent.length - 16
}
})
static DigitalAudioOutputRestrictionObject = new Parser()
.buffer('audio_output_protection_id', { length: 16 })
.buffer('binary_configuration_data', {
length: function () {
return (this as any).$parent.length - 16
}
})
static PolicyMetadataObject = new Parser()
.buffer('metadata_type', { length: 16 })
.buffer('policy_data', {
length: function () {
return (this as any).$parent.length - 16
}
})
static SecureStopRestrictionObject = new Parser().buffer('metering_id', {
length: 16
})
static MeteringRestrictionObject = new Parser().buffer('metering_id', {
length: 16
})
static ExpirationAfterFirstPlayRestrictionObject = new Parser().uint32(
'seconds'
)
static GracePeriodObject = new Parser().uint32('grace_period')
static SourceIdObject = new Parser().uint32('source_id')
static AuxiliaryKey = new Parser()
.uint32('location')
.buffer('key', { length: 16 })
static AuxiliaryKeysObject = new Parser()
.uint16('count')
.array('auxiliary_keys', {
length: 'count',
type: XMRLicenseStructs.AuxiliaryKey
})
static UplinkKeyObject3 = new Parser()
.buffer('uplink_key_id', { length: 16 })
.uint16('chained_length')
.buffer('checksum', {
length: function () {
return (this as any).chained_length
}
})
.uint16('count')
.array('entries', {
length: 'count',
type: new Parser().uint32('entry')
})
static CopyEnablerObject = new Parser().buffer('copy_enabler_type', {
length: 16
})
static CopyCountRestrictionObject = new Parser().uint32('count')
static MoveObject = new Parser().uint32('minimum_move_protection_level')
static XMRObject = (): Parser =>
new Parser()
.namely('self')
.int16('flags')
.int16('type')
.int32('length')
.choice('data', {
tag: 'type',
choices: {
0x0005: XMRLicenseStructs.OutputProtectionLevelRestrictionObject,
0x0008: XMRLicenseStructs.AnalogVideoOutputConfigurationRestriction,
0x000a: XMRLicenseStructs.ContentKeyObject,
0x000b: XMRLicenseStructs.SignatureObject,
0x000d: XMRLicenseStructs.RightsSettingsObject,
0x0012: XMRLicenseStructs.ExpirationRestrictionObject,
0x0013: XMRLicenseStructs.IssueDateObject,
0x0016: XMRLicenseStructs.MeteringRestrictionObject,
0x001a: XMRLicenseStructs.GracePeriodObject,
0x0022: XMRLicenseStructs.SourceIdObject,
0x002a: XMRLicenseStructs.ECCKeyObject,
0x002c: XMRLicenseStructs.PolicyMetadataObject,
0x0029: XMRLicenseStructs.DomainRestrictionObject,
0x0030: XMRLicenseStructs.ExpirationAfterFirstPlayRestrictionObject,
0x0031: XMRLicenseStructs.DigitalAudioOutputRestrictionObject,
0x0032: XMRLicenseStructs.RevInfoVersionObject,
0x0033: XMRLicenseStructs.EmbeddedLicenseSettingsObject,
0x0034: XMRLicenseStructs.SecurityLevelObject,
0x0037: XMRLicenseStructs.MoveObject,
0x0039: XMRLicenseStructs.PlayEnablerType,
0x003a: XMRLicenseStructs.CopyEnablerObject,
0x003b: XMRLicenseStructs.UplinkKIDObject,
0x003d: XMRLicenseStructs.CopyCountRestrictionObject,
0x0050: XMRLicenseStructs.RemovalDateObject,
0x0051: XMRLicenseStructs.AuxiliaryKeysObject,
0x0052: XMRLicenseStructs.UplinkKeyObject3,
0x005a: XMRLicenseStructs.SecureStopRestrictionObject,
0x0059: XMRLicenseStructs.DigitalVideoOutputRestrictionObject
},
defaultChoice: 'self'
})
static XmrLicense = new Parser()
.useContextVars()
.buffer('signature', { length: 4 })
.int32('xmr_version')
.buffer('rights_id', { length: 16 })
.array('containers', {
type: XMRLicenseStructs.XMRObject(),
readUntil: 'eof'
})
static XmrLicense = new Parser()
.useContextVars()
.buffer('signature', { length: 4 })
.int32('xmr_version')
.buffer('rights_id', { length: 16 })
.array('containers', {
type: XMRLicenseStructs.XMRObject(),
readUntil: 'eof',
});
}
export class XMRLicense extends XMRLicenseStructs {
parsed: any
_LICENSE: Parser
parsed: any;
_LICENSE: Parser;
constructor(
parsed_license: any,
license_obj: Parser = XMRLicenseStructs.XmrLicense
) {
super()
this.parsed = parsed_license
this._LICENSE = license_obj
constructor(
parsed_license: any,
license_obj: Parser = XMRLicenseStructs.XmrLicense
) {
super();
this.parsed = parsed_license;
this._LICENSE = license_obj;
}
static loads(data: string | Buffer): XMRLicense {
if (typeof data === 'string') {
data = Buffer.from(data, 'base64');
}
if (!Buffer.isBuffer(data)) {
throw new Error(`Expecting Bytes or Base64 input, got ${data}`);
}
static loads(data: string | Buffer): XMRLicense {
if (typeof data === 'string') {
data = Buffer.from(data, 'base64')
}
if (!Buffer.isBuffer(data)) {
throw new Error(`Expecting Bytes or Base64 input, got ${data}`)
}
const licence = XMRLicenseStructs.XmrLicense;
const parsed_license = licence.parse(data);
return new XMRLicense(parsed_license, licence);
}
const licence = XMRLicenseStructs.XmrLicense
const parsed_license = licence.parse(data)
return new XMRLicense(parsed_license, licence)
static load(filePath: string): XMRLicense {
if (typeof filePath !== 'string') {
throw new Error(`Expecting path string, got ${filePath}`);
}
const data = fs.readFileSync(filePath);
return XMRLicense.loads(data);
}
static load(filePath: string): XMRLicense {
if (typeof filePath !== 'string') {
throw new Error(`Expecting path string, got ${filePath}`)
}
const data = fs.readFileSync(filePath)
return XMRLicense.loads(data)
}
dumps(): Buffer {
return this._LICENSE.parse(this.parsed);
}
dumps(): Buffer {
return this._LICENSE.parse(this.parsed)
}
struct(): Parser {
return this._LICENSE;
}
struct(): Parser {
return this._LICENSE
private _locate(container: any): any {
if (container.flags === 2 || container.flags === 3) {
return this._locate(container.data);
} else {
return container;
}
}
private _locate(container: any): any {
if (container.flags === 2 || container.flags === 3) {
return this._locate(container.data)
} else {
return container
}
*get_object(type_: number): Generator<any> {
for (const obj of this.parsed.containers) {
const container = this._locate(obj);
if (container.type === type_) {
yield container.data;
}
}
}
*get_object(type_: number): Generator<any> {
for (const obj of this.parsed.containers) {
const container = this._locate(obj)
if (container.type === type_) {
yield container.data
}
}
}
get_content_keys(): Generator<any> {
return this.get_object(0x000a)
}
get_content_keys(): Generator<any> {
return this.get_object(0x000a);
}
}