import { Parser } from 'binary-parser'; type ParsedLicense = { version: number; rights: string; length: number; license: { length: number; signature?: { length: number; type: string; value: string; }; global_container?: { revocationInfo?: { version: number; }; securityLevel?: { level: number; }; }; keyMaterial?: { contentKey?: { kid: string; keyType: number; ciphertype: number; length: number; value: Buffer; }; encryptionKey?: { curve: number; length: number; value: string; }; auxKeys?: { count: number; value: { location: number; value: string; }; }; }; }; }; export class XMRLicenseStructsV2 { static CONTENT_KEY = new Parser() .buffer('kid', { length: 16 }) .uint16('keytype') .uint16('ciphertype') .uint16('length') .buffer('value', { length: 'length', }); static ECC_KEY = new Parser() .uint16('curve') .uint16('length') .buffer('value', { length: 'length', }); static FTLV = new Parser() .uint16('flags') .uint16('type') .uint32('length') .buffer('value', { length: function () { return (this as any).length - 8; }, }); static AUXILIARY_LOCATIONS = new Parser() .uint32('location') .buffer('value', { length: 16 }); static AUXILIARY_KEY_OBJECT = new Parser() .uint16('count') .array('locations', { length: 'count', type: XMRLicenseStructsV2.AUXILIARY_LOCATIONS, }); static SIGNATURE = new Parser() .uint16('type') .uint16('siglength') .buffer('signature', { length: 'siglength', }); static XMR = new Parser() .string('constant', { length: 4, assert: 'XMR\x00' }) .int32('version') .buffer('rightsid', { length: 16 }) .nest('data', { type: XMRLicenseStructsV2.FTLV, }); } enum XMRTYPE { XMR_OUTER_CONTAINER = 0x0001, XMR_GLOBAL_POLICY_CONTAINER = 0x0002, XMR_PLAYBACK_POLICY_CONTAINER = 0x0004, XMR_KEY_MATERIAL_CONTAINER = 0x0009, XMR_RIGHTS_SETTINGS = 0x000d, XMR_EMBEDDED_LICENSE_SETTINGS = 0x0033, XMR_REVOCATION_INFORMATION_VERSION = 0x0032, XMR_SECURITY_LEVEL = 0x0034, XMR_CONTENT_KEY_OBJECT = 0x000a, XMR_ECC_KEY_OBJECT = 0x002a, XMR_SIGNATURE_OBJECT = 0x000b, XMR_OUTPUT_LEVEL_RESTRICTION = 0x0005, XMR_AUXILIARY_KEY_OBJECT = 0x0051, XMR_EXPIRATION_RESTRICTION = 0x0012, XMR_ISSUE_DATE = 0x0013, XMR_EXPLICIT_ANALOG_CONTAINER = 0x0007, } export class XmrUtil { public data: Buffer; public license: ParsedLicense; constructor(data: Buffer, license: ParsedLicense) { this.data = data; this.license = license; } static parse(license: Buffer) { const xmr = XMRLicenseStructsV2.XMR.parse(license); const parsed_license: ParsedLicense = { version: xmr.version, rights: Buffer.from(xmr.rightsid).toString('hex'), length: license.length, license: { length: xmr.data.length, }, }; const container = parsed_license.license; const data = xmr.data; let pos = 0; while (pos < data.length - 16) { const value = XMRLicenseStructsV2.FTLV.parse(data.value.slice(pos)); // XMR_SIGNATURE_OBJECT if (value.type === XMRTYPE.XMR_SIGNATURE_OBJECT) { const signature = XMRLicenseStructsV2.SIGNATURE.parse(value.value); container.signature = { length: value.length, type: signature.type, value: Buffer.from(signature.signature).toString('hex'), }; } // XMRTYPE.XMR_GLOBAL_POLICY_CONTAINER if (value.type === XMRTYPE.XMR_GLOBAL_POLICY_CONTAINER) { container.global_container = {}; let index = 0; while (index < value.length - 16) { const data = XMRLicenseStructsV2.FTLV.parse(value.value.slice(index)); // XMRTYPE.XMR_REVOCATION_INFORMATION_VERSION if (data.type === XMRTYPE.XMR_REVOCATION_INFORMATION_VERSION) { container.global_container.revocationInfo = { version: data.value.readUInt32BE(0), }; } // XMRTYPE.XMR_SECURITY_LEVEL if (data.type === XMRTYPE.XMR_SECURITY_LEVEL) { container.global_container.securityLevel = { level: data.value.readUInt16BE(0), }; } index += data.length; } } // XMRTYPE.XMR_KEY_MATERIAL_CONTAINER if (value.type === XMRTYPE.XMR_KEY_MATERIAL_CONTAINER) { container.keyMaterial = {}; let index = 0; while (index < value.length - 16) { const data = XMRLicenseStructsV2.FTLV.parse(value.value.slice(index)); // XMRTYPE.XMR_CONTENT_KEY_OBJECT if (data.type === XMRTYPE.XMR_CONTENT_KEY_OBJECT) { const content_key = XMRLicenseStructsV2.CONTENT_KEY.parse( data.value ); container.keyMaterial.contentKey = { kid: XmrUtil.fixUUID(content_key.kid).toString('hex'), keyType: content_key.keytype, ciphertype: content_key.ciphertype, length: content_key.length, value: content_key.value, }; } // XMRTYPE.XMR_ECC_KEY_OBJECT if (data.type === XMRTYPE.XMR_ECC_KEY_OBJECT) { const ecc_key = XMRLicenseStructsV2.ECC_KEY.parse(data.value); container.keyMaterial.encryptionKey = { curve: ecc_key.curve, length: ecc_key.length, value: Buffer.from(ecc_key.value).toString('hex'), }; } // XMRTYPE.XMR_AUXILIARY_KEY_OBJECT if (data.type === XMRTYPE.XMR_AUXILIARY_KEY_OBJECT) { const aux_keys = XMRLicenseStructsV2.AUXILIARY_KEY_OBJECT.parse( data.value ); container.keyMaterial.auxKeys = { count: aux_keys.count, value: aux_keys.locations.map((a: any) => { return { location: a.location, value: Buffer.from(a.value).toString('hex'), }; }), }; } index += data.length; } } pos += value.length; } return new XmrUtil(license, parsed_license); } static 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), ]); } }