mirror of
https://github.com/anidl/multi-downloader-nx.git
synced 2026-01-11 20:10:20 +00:00
489 lines
15 KiB
TypeScript
489 lines
15 KiB
TypeScript
import * as fs from 'fs'
|
|
import { createHash } from 'crypto'
|
|
import { Parser } from 'binary-parser-encoder'
|
|
import ECCKey from './ecc_key'
|
|
|
|
function alignUp(length: number, alignment: number): number {
|
|
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 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 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 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 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 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 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 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 DrmBCertServerInfo = new Parser().uint32be('warning_days')
|
|
|
|
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 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'
|
|
})
|
|
}
|
|
|
|
export class Certificate {
|
|
parsed: any
|
|
_BCERT: Parser
|
|
|
|
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')
|
|
}
|
|
|
|
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_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 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 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)
|
|
|
|
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 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)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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_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+$/, '')
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
struct(): Parser {
|
|
return this._BCERT
|
|
}
|
|
}
|
|
|
|
export class CertificateChain {
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
static load(filePath: string): CertificateChain {
|
|
const data = fs.readFileSync(filePath)
|
|
return CertificateChain.loads(data)
|
|
}
|
|
|
|
dumps(): Buffer {
|
|
return this._BCERT_CHAIN.encode(this.parsed)
|
|
}
|
|
|
|
struct(): Parser {
|
|
return this._BCERT_CHAIN
|
|
}
|
|
|
|
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_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
|
|
}
|
|
|
|
prepend(bcert: Certificate): void {
|
|
this.parsed.certificate_count += 1
|
|
this.parsed.certificates.unshift(bcert.parsed)
|
|
this.parsed.total_length += bcert.dumps().length
|
|
}
|
|
}
|