import { ToolAPI } from '../api/ToolAPI' import { JosCore } from '../JosCore' import { CacheKeyStore } from './common/CacheKeyStore' import { Constants } from './common/Constants' import { Mkey } from './Mkey' import * as crypto from 'crypto' export class TdeClient { isReady = false private sdk_ver = 2 private cache_ks: CacheKeyStore = new CacheKeyStore() private available_key_list: Map = new Map() private key_chain_is_ready: boolean private major_key_ver: number private corrupt_key_list: any[] = [] private josCore: JosCore private access_token: string private token: { label: string start_ts: number end_ts: number id: string key: Buffer service: string stype: number zone: any } constructor(josCore: JosCore, access_token: string) { this.josCore = josCore this.access_token = access_token this.key_chain_is_ready = false } async init_client() { try { await this.request_voucher_token() this.cache_ks = new CacheKeyStore() await this.fetch_master_keys() this.isReady = true if (!this.key_chain_is_ready) { throw new Error(``) } } catch (e) {} } async request_voucher_token() { const json_response = await ToolAPI.JingdongJosVoucherInfoGet( this.josCore, { access_token: this.access_token, }, this.access_token ) const voucher_info_response = json_response if ('0' !== voucher_info_response.code) { throw new Error(`gw platform error -> ${voucher_info_response.msg}`) } if ('0' !== voucher_info_response.response.errorCode) { throw new Error(`${voucher_info_response.response.errorMsg}`) } const voucher = voucher_info_response.response.data.voucher if (!voucher) { throw new Error(`Request Voucher is null`) } const token_data = JSON.parse(Buffer.from(voucher, 'base64').toString('utf-8')) const sig = token_data.sig const data = token_data.data const external_data = token_data.externalData let zone = external_data.zone if (!zone) { zone = 'CN-0' } // TODO: verify sign /** * py3: * pk = rsa.PublicKey.load_pkcs1_openssl_pem(constants.TMS_PROD_TOKEN_PUB_KEY.encode('utf-8')) verify_result = rsa.verify(json.dumps(data, separators=(',', ':')).encode('utf-8'), base64.b64decode(sig), pk) if verify_result != 'SHA-256': raise InvalidTokenException("Token Signature Validation Failed.") */ let service = data.service if (!service) { service = 'unknown host' } this.token = { label: data.act, start_ts: data.effective, end_ts: data.expired, id: data.id, key: Buffer.from(data.key, 'base64'), service: service, stype: data.stype, zone: zone, } } decrypt_string(base64Str: string): string | null { if (TdeClient.is_encrypted_data(base64Str)) { // validate token const st = this.get_cipher_result(Buffer.from(base64Str, 'base64')) if (st.status === 'UnDecryptable') { } else if (st.status === 'Feasible') { } else if (st.status === 'Malformed') { } const m_key = this.cache_ks.search_dec_key(st.keyid) this.__check_key_status_for_decryption(m_key) try { const pt = m_key.decrypt(Buffer.from(base64Str, 'base64')) return pt } catch (e) {} } return null } private __check_key_status_for_decryption(key: Mkey) { if (!key) { throw new Error(`TODO: SDK_HAS_NO_CORRESPONDING_DEC_KEYS`) } } async fetch_master_keys() { try { const ts = new Date().getTime() const tstr = `{"sdk_ver":${this.sdk_ver},"ts":${ts},"tid":"${this.token.id}"}` const sig_dig = crypto.createHmac('sha256', this.token.key).update(tstr, 'utf8').digest() const json_response = await ToolAPI.JingdongJosMasterKeyGet( this.josCore, { sdk_ver: this.sdk_ver, ts: ts, tid: this.token.id, sig: Buffer.from(sig_dig).toString('base64'), }, this.access_token ) const response = json_response.response this.corrupt_key_list = [] const response_code = response.status_code if (response_code === 0) { this.__import_mkeys(response) } else { throw new Error(response.errorMsg) } } catch (e) { throw e } } private __import_mkeys(response: any) { let enc_rmv_list = this.cache_ks.get_key_id_list(Constants.KStoreType.ENC_STORE) let dec_rmv_list = this.cache_ks.get_key_id_list(Constants.KStoreType.DEC_STORE) const service_key_list = response.service_key_list this.cache_ks.reset_future_key_ids() for (const service_key of service_key_list) { const m_keys: any[] = service_key.keys this.available_key_list[service_key.service] = m_keys.length - 1 for (const m_data of m_keys) { const m_key = new Mkey( service_key.service, Buffer.from(m_data.id, 'base64'), Buffer.from(m_data.key_string, 'base64'), m_data.key_digest, parseInt(m_data.version), parseInt(m_data.key_effective), parseInt(m_data.key_exp), m_data.key_type, service_key.grant_usage, parseInt(m_data.key_status) ) if (m_key.is_valid) { if (service_key.service === this.token.service) { this.major_key_ver = service_key.current_key_version this.cache_ks.update_key(m_data.id, m_key, Constants.KStoreType.ENC_STORE) if (enc_rmv_list.includes(m_data.id)) { enc_rmv_list = enc_rmv_list.filter((i) => i != m_data.id) } } this.cache_ks.update_key(m_data.id, m_key, Constants.KStoreType.DEC_STORE) if (dec_rmv_list.includes(m_data.id)) { dec_rmv_list = dec_rmv_list.filter((i) => i != m_data.id) } } else { this.corrupt_key_list.push(m_data.id) } this.cache_ks.update_future_key_ids(service_key.service, service_key.current_key_version) } // TODO: report this.available_key_list.clear() if (enc_rmv_list.length > 0) { this.cache_ks.remove_keys_via_list(enc_rmv_list, Constants.KStoreType.ENC_STORE) } if (dec_rmv_list.length > 0) { this.cache_ks.remove_keys_via_list(dec_rmv_list, Constants.KStoreType.DEC_STORE) } // TODO: report // this.__send_corrupt_report() // this.__check_valid_key_chain() } } get_cipher_result(ct: Buffer): { keyid: Buffer | null; status: Constants.KeyIdStatus; strong: boolean } { try { const b0 = ct[0] let flag = false if (b0 === Constants.CipherTypes.Large.id || b0 === Constants.CipherTypes.Regular.id) { flag = true } const mk_idx = TdeClient.__extract_key_id(ct, flag) if (!mk_idx) { return { keyid: null, status: 'Malformed', strong: false } } // if cache_ks.search_dec_key(mk_idx) is not null if (this.cache_ks.search_dec_key(mk_idx)) { return { keyid: mk_idx, status: 'Decryptable', strong: flag } } // else if cache_ks.has_future_key_id(mk_idx) else if (this.cache_ks.has_future_key_id(mk_idx)) { return { keyid: mk_idx, status: 'Feasible', strong: flag } } else { return { keyid: null, status: 'UnDecryptable', strong: false } } } catch (e) { return { keyid: null, status: 'Malformed', strong: false } } } static is_encrypted_data(str: string): boolean { try { if (!str || Buffer.from(str, 'base64').toString('base64') !== str) { return false } const val = Buffer.from(str, 'base64') const b0 = val[0] let flag = false if (b0 === Constants.CipherTypes.Large.id || b0 === Constants.CipherTypes.Regular.id) { flag = true } const mk_idx = TdeClient.__extract_key_id(val, flag) if (mk_idx && mk_idx.length > 0) { return true } return false } catch (e) { return false } } private static __extract_key_id(ct: Buffer, strong: boolean): Buffer | null { if (strong) { const eid_len: number = ct.subarray(1, 3).readInt32BE() if (ct.length - 3 < eid_len) { return null } return ct.subarray(3, 3 + eid_len) } else { if (ct.length - 2 < Constants.DEFAULT_KEYID_LEN) { return null } return ct.subarray(2, 2 + Constants.DEFAULT_KEYID_LEN) } } }