UNPKG

@iotize/tap

Version:

IoTize Device client for Javascript

337 lines 30.2 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { bufferToHexString } from '@iotize/common/byte-converter'; import { AesEcb128Converter } from '@iotize/common/crypto'; import { deepCopy } from '@iotize/common/utility'; import { ServiceCallRunner, TapError, TapResponse, TapResponseStatusError, } from '@iotize/tap'; import { ResultCode, } from '@iotize/tap/client/api'; import { CryptedFrameConverter, TapClientError, TapRequestHelper, TapStreamWriter, } from '@iotize/tap/client/impl'; import { ScramService, } from '@iotize/tap/service/impl/scram'; import { defer, throwError } from 'rxjs'; import { map, mergeMap, tap as rxTap, shareReplay } from 'rxjs/operators'; import { debug } from './debug'; import { TapScramError } from './scram-errors'; const INITIALIZATION_VECTOR_LENGTH = 16; const DEFAULT_INITIALIZATION_VECTOR = '00000000000000000000000000000000'; const TAG = 'ScramInterceptor'; function _hex(data) { return data ? bufferToHexString(data) : ''; } export class ScramInterceptor { constructor(scramService) { this.scramService = scramService; this._ivFrameCounter = 0; this._ivSupported = true; this._options = { encryption: false, initializationVectorResetPeriod: 100, keys: {}, frameCounter: 0, }; this._encryptedFrameConverter = new CryptedFrameConverter({ next: () => { return this._options.frameCounter++; }, }); this.initializationVectorGenerator = (length) => { const data = new Array(length).fill(0).map((_) => { return Math.floor(Math.random() * 0xff); }); return Uint8Array.from(data); }; } get encryptionAlgo() { if (!this._encryptionAlgo) { throw TapClientError.illegalStateError(`Encryption algo has not been set yet`); } return this._encryptionAlgo; } set ivSupported(v) { this._ivSupported = v; } /** * Setter for the session key * @param key if null, it will stop encryption and remove session key. If true it will update session key used for encryption */ set sessionKey(key) { this._options.keys.sessionKey = key; this._options.frameCounter = 0; if (this._options.keys.sessionKey) { this.refreshEncryptionAlgo(); } else { this.pauseEncryption(); } } get sessionKey() { return this._options.keys.sessionKey; } /** * Get a copy of encryption options */ get options() { return deepCopy(this._options); } setEncryptionKeys(options) { this._options.keys = options; this.refreshEncryptionAlgo(); } getEncryptionKeys() { return this._options.keys; } setFrameCounter(value) { this._options.frameCounter = value; } setInitializationVectorRefreshPeriod(period) { this._options.initializationVectorResetPeriod = period; } intercept(context, next) { const request = context.request; debug(TAG, `exec ${TapRequestHelper.toString(request)} (encryption:${this._options.encryption}, frameCounter: ${this._options.frameCounter} - skip ${context.skipEncryption}, key: ${_hex(this.sessionKey)} iv decode: ${_hex(this._options.keys.ivDecode)}, iv encode: ${_hex(this._options.keys.ivEncode)})`); let obs; if (this._options.encryption && !context.skipEncryption) { obs = this._sendWithEncryption(context, next).pipe(map((response) => { const data = response.rawBody(); return { status: response.status, data, }; })); } else { obs = next.handle(context); } return obs.pipe(rxTap((response) => { debug(TAG, `Response to ${TapRequestHelper.toString(request)} => ${response.status} ${bufferToHexString(response.data)}`); })); } _sendWithEncryption(context, next) { if (!this._encryptedFrameConverter) { return throwError(TapClientError.illegalStateError(`Encrypted frame converter has not been specified yet`)); } let callObs; let newClientIV; if (this._options.initializationVectorResetPeriod === 1) { newClientIV = this.initializationVectorGenerator(INITIALIZATION_VECTOR_LENGTH); this._setEncodeIV(newClientIV); debug(TAG, `Changing initialization vector for every requests. New client IV: ${_hex(newClientIV)}`); const cryptedFrame = this._buildEncryptedFrame(context); const call = this.scramService.sendWithIVCall({ request: cryptedFrame, iv: newClientIV, }); callObs = this._toCallObservable(Object.assign(Object.assign({}, context), { skipEncryption: true }), next, call).pipe(map((response) => { // if (response.isSuccessful() && response.rawBody().length < INITIALIZATION_VECTOR_LENGTH){ // // tap firmware version is too old apparentl. // // CCOM IV resource was already used to generate rand number before firmware version 1.83, thats why there is a success code result // throw TapError.initializationVectorNotSupported( // new Error(`Tap response is too short`) // ); // } const body = response.body(); debug(TAG, `Refreshing decoding initialization vector: ${_hex(body.iv)}`); this._setDecodeIV(body.iv); return TapResponse.create(response.status, body.response); })); } else { if (this._options.initializationVectorResetPeriod > 1 && this._ivFrameCounter >= this._options.initializationVectorResetPeriod && !isRefreshInitializationVectorRequest(context.request)) { debug(TAG, `_ivInterceptor refreshEncryptionInitializationVector is required ${this._ivFrameCounter}/${this._options.initializationVectorResetPeriod}`); if (!this._refreshingInitializationVectorObs) { this._refreshingInitializationVectorObs = defer(() => __awaiter(this, void 0, void 0, function* () { try { return yield this.refreshInitializationVectors(); } finally { this._refreshingInitializationVectorObs = undefined; } })).pipe(shareReplay({ bufferSize: 1, refCount: true })); } else { // debug(TAG, `Refreshing initialization vector has already been asked`); } callObs = this._refreshingInitializationVectorObs.pipe(mergeMap((_) => { const encryptedFrame = this._buildEncryptedFrame(context); return this._toCallObservable(context, next, this.scramService.sendCall(encryptedFrame)); })); } else { const encryptedFrame = this._buildEncryptedFrame(context); const call = this.scramService.sendCall(encryptedFrame); callObs = this._toCallObservable(context, next, call); } } return callObs.pipe(map((encryptedWarpperResponse) => { this._ivFrameCounter++; // debug(TAG, `Iotize client crypted frame: ${encryptedWarpperResponse}`); if (!encryptedWarpperResponse.isSuccessful()) { if (encryptedWarpperResponse.codeRet() === ResultCode.SERVICE_UNAVAILABLE) { throw TapScramError.scramNotStartedYet(context.request); } else if (encryptedWarpperResponse.codeRet() === ResultCode.BAD_REQUEST) { throw TapScramError.invalidScramKey(context.request); } encryptedWarpperResponse.successful(); } const cryptedFrameContent = this.encryptionAlgo.decode(encryptedWarpperResponse.rawBody()); // debug(TAG, `Decrypted frame value: ${_hex(cryptedFrameContent)}`); const apduFrame = this._encryptedFrameConverter.decode(cryptedFrameContent); // debug(TAG, `apduFrame frame value: ${_hex(apduFrame)}`); const tapResponse = context.client.responseDecoder.decode(apduFrame); const apiResponse = new TapResponse(tapResponse, context.request); // let response: TapResponse<any> = this.lwm2mResponseConverter.decode(lwm2mResponseFrame.data); if (context.bodyDecoder) { apiResponse.setBodyDecoder(context.bodyDecoder); } // debug(TAG, `Iotize client decoded response: ${response}`) return apiResponse; })); } _buildEncryptedFrame(context) { const iotizeFrame = TapStreamWriter.create(10 + context.request.payload.length).writeTapRequestFrame(context.request).toBytes; const encryptedFrameModel = this._encryptedFrameConverter.encode(iotizeFrame); const encryptedFrame = this.encryptionAlgo.encode(encryptedFrameModel); return encryptedFrame; } _setEncodeIV(iv) { this._options.keys.ivEncode = iv; this.refreshEncryptionAlgo(); } _setDecodeIV(iv) { this._options.keys.ivDecode = iv; this.refreshEncryptionAlgo(); } _toCallObservable(context, next, call) { const tapRequest = ServiceCallRunner.toTapRequest(call); const bodyDecoderFct = ServiceCallRunner.resolveResponseBodyDecoder(call); const bodyDecoder = bodyDecoderFct ? { decode: bodyDecoderFct, } : undefined; return next .handle(Object.assign(Object.assign({}, context), { request: tapRequest, bodyDecoder })) .pipe(map((tapResponse) => { return new TapResponse(tapResponse, context.request, bodyDecoder); })); } /** * Refresh initialization vectors */ refreshInitializationVectors() { return __awaiter(this, void 0, void 0, function* () { const clientIV = this.initializationVectorGenerator(INITIALIZATION_VECTOR_LENGTH); try { debug(TAG, `Performing initialization vector refresh`); const serverIV = (yield this.scramService.setInitializationVector(clientIV)).body(); this._options.keys.ivEncode = clientIV; this._options.keys.ivDecode = serverIV; this._ivFrameCounter = 0; debug(TAG, `Initialization vector refreshed: server ${_hex(serverIV)} client=${_hex(clientIV)})`); this.refreshEncryptionAlgo(); } catch (err) { debug(TAG, `Cannot refresh initialization vector: ${err.message}`); if (err instanceof TapResponseStatusError) { const tapResponseStatus = err.response.status; if (tapResponseStatus === ResultCode.NOT_IMPLEMENTED || tapResponseStatus === ResultCode.NOT_FOUND) { debug(TAG, `Cannot initialize encryption vectors (firmware version does not support it). Error: ${err.message}`); this._ivSupported = false; throw TapError.initializationVectorNotSupported(err); } } throw err; } }); } /** * Resume encryption session */ resumeEncryption() { debug(TAG, `resume encryption`); this._options.encryption = true; } /** * Pause encryption without destorying session (session keys will not be removed) */ pauseEncryption() { debug(TAG, `pause encryption`); this._options.encryption = false; } /** * Initialize a new encrypted sesssion * Session key will be changed. Initialization vector too (if firmware supports it) */ newSession() { return __awaiter(this, void 0, void 0, function* () { try { debug(TAG, `Creating new encryption session`); this.clearSession(); const response = yield this.scramService.initialize(); response.successful(); const newSessionKey = response.body(); this.sessionKey = newSessionKey; debug(TAG, `Session key will be ${_hex(newSessionKey)} (length=${newSessionKey.length})`); if (this._options.initializationVectorResetPeriod > 0) { try { yield this.refreshInitializationVectors(); } catch (err) { // Ignore initialization vector not supported error if (err.code !== TapError.Code.InitializationVectorNotSupported) { throw err; } } } return newSessionKey; } catch (err) { throw TapError.cannotStartScram(err); } }); } /** * Clear encrypted session. * Destroys frame counter, session keys and initialization vectors * This will also stop encrypted communication if it was running */ clearSession() { this.pauseEncryption(); this._options.keys.ivDecode = undefined; this._options.keys.ivEncode = undefined; this._options.keys.sessionKey = undefined; this._ivFrameCounter = 0; } refreshEncryptionAlgo() { if (this._options.keys.sessionKey) { const algo = new AesEcb128Converter({ key: _hex(this._options.keys.sessionKey), ivDecode: this._options.keys.ivDecode ? _hex(this._options.keys.ivDecode) : DEFAULT_INITIALIZATION_VECTOR, ivEncode: this._options.keys.ivEncode ? _hex(this._options.keys.ivEncode) : DEFAULT_INITIALIZATION_VECTOR, }); this._encryptionAlgo = algo; } } } const SCRAM_CALL_FACTORY = new ScramService(undefined); function isRefreshInitializationVectorRequest(request) { return (TapRequestHelper.pathToString(request.header.path) === SCRAM_CALL_FACTORY.resources.setInitializationVector.path); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2NyYW0taW50ZXJjZXB0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9hdXRoL3NyYy9saWIvc2NyYW0taW50ZXJjZXB0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O0FBQUEsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDbEUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDM0QsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ2xELE9BQU8sRUFDTCxpQkFBaUIsRUFFakIsUUFBUSxFQUNSLFdBQVcsRUFDWCxzQkFBc0IsR0FDdkIsTUFBTSxhQUFhLENBQUM7QUFDckIsT0FBTyxFQUtMLFVBQVUsR0FHWCxNQUFNLHdCQUF3QixDQUFDO0FBQ2hDLE9BQU8sRUFDTCxxQkFBcUIsRUFDckIsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixlQUFlLEdBQ2hCLE1BQU0seUJBQXlCLENBQUM7QUFDakMsT0FBTyxFQUVMLFlBQVksR0FDYixNQUFNLGdDQUFnQyxDQUFDO0FBQ3hDLE9BQU8sRUFBYyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ3JELE9BQU8sRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEdBQUcsSUFBSSxLQUFLLEVBQUUsV0FBVyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFMUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUNoQyxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUE2Qy9DLE1BQU0sNEJBQTRCLEdBQUcsRUFBRSxDQUFDO0FBQ3hDLE1BQU0sNkJBQTZCLEdBQUcsa0NBQWtDLENBQUM7QUFJekUsTUFBTSxHQUFHLEdBQUcsa0JBQWtCLENBQUM7QUFFL0IsU0FBUyxJQUFJLENBQUMsSUFBNEI7SUFDeEMsT0FBTyxJQUFJLENBQUMs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