@iotize/tap
Version:
IoTize Device client for Javascript
337 lines • 30.2 kB
JavaScript
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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