UNPKG

gdata-vaas

Version:

An SDK to for G DATA VaaS. Verdicts as a service.

396 lines 20.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); 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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Vaas = exports.VAAS_URL = void 0; const isomorphic_ws_1 = __importDefault(require("@d-fischer/isomorphic-ws")); const sha256 = __importStar(require("fast-sha256")); const message_1 = require("./messages/message"); const authentication_response_1 = require("./messages/authentication_response"); const authentication_request_1 = require("./messages/authentication_request"); const typescript_json_serializer_1 = require("typescript-json-serializer"); const verdict_response_1 = require("./messages/verdict_response"); const websocket_error_1 = require("./messages/websocket_error"); const verdict_request_1 = require("./messages/verdict_request"); const uuid_1 = require("uuid"); const Verdict_1 = require("./Verdict"); const axios = __importStar(require("axios")); const CancellationToken_1 = require("./CancellationToken"); const VaasErrors_1 = require("./VaasErrors"); const vaas_verdict_1 = require("./messages/vaas_verdict"); const verdict_request_for_url_1 = require("./messages/verdict_request_for_url"); const verdict_request_for_stream_1 = require("./messages/verdict_request_for_stream"); const http_1 = __importDefault(require("http")); const https_1 = __importDefault(require("https")); const VAAS_URL = "wss://gateway.production.vaas.gdatasecurity.de"; exports.VAAS_URL = VAAS_URL; const defaultSerializer = new typescript_json_serializer_1.JsonSerializer(); // See https://advancedweb.hu/how-to-add-timeout-to-a-promise-in-javascript/ const timeout = (promise, timeoutInMs) => { let timer; return Promise.race([ promise, new Promise((_resolve, reject) => (timer = setTimeout(reject, timeoutInMs, new VaasErrors_1.VaasTimeoutError()))), ]).finally(() => clearTimeout(timer)); }; class Vaas { constructor(webSocketFactory = (url) => new isomorphic_ws_1.default.WebSocket(url)) { this.webSocketFactory = webSocketFactory; this.connection = null; this.defaultTimeoutHashReq = 2000; this.defaultTimeoutFileReq = 600000; this.defaultTimeoutUrlReq = 600000; this.defaultTimeoutStreamReq = 100000; this.debug = false; this.verdictPromises = new Map(); } static toHexString(byteArray) { return Array.from(byteArray, function (byte) { return ("0" + (byte & 0xff).toString(16)).slice(-2); }).join(""); } /** Get verdict for Readable Stream * @throws {VaasInvalidStateError} If connect() has not been called and awaited. Signifies caller error. * @throws {VaasAuthenticationError} Authentication failed. * @throws {VaasConnectionClosedError} Connection was closed. Call connect() to reconnect. * @throws {VaasTimeoutError} Timeout. Retry request. * @throws {VaasServerError} Stream could not be read properly in VaaS clouds */ forStream(stream_1) { return __awaiter(this, arguments, void 0, function* (stream, ct = CancellationToken_1.CancellationToken.fromMilliseconds(this.defaultTimeoutStreamReq)) { const request = this.forStreamRequest(stream).then((response) => response); return timeout(request, ct.timeout()); }); } /** Get verdict for URL * @throws {VaasInvalidStateError} If connect() has not been called and awaited. Signifies caller error. * @throws {VaasAuthenticationError} Authentication failed. * @throws {VaasConnectionClosedError} Connection was closed. Call connect() to reconnect. * @throws {VaasTimeoutError} Timeout. Retry request. */ forUrl(url_1) { return __awaiter(this, arguments, void 0, function* (url, ct = CancellationToken_1.CancellationToken.fromMilliseconds(this.defaultTimeoutUrlReq)) { const request = this.forUrlRequest(url).then((response) => response); return timeout(request, ct.timeout()); }); } /** Get verdict for a SHA256 * @throws {VaasInvalidStateError} If connect() has not been called and awaited. Signifies caller error. * @throws {VaasAuthenticationError} Authentication failed. * @throws {VaasConnectionClosedError} Connection was closed. Call connect() to reconnect. * @throws {VaasTimeoutError} Timeout. Retry request. */ forSha256(sha256_1) { return __awaiter(this, arguments, void 0, function* (sha256, ct = CancellationToken_1.CancellationToken.fromMilliseconds(this.defaultTimeoutHashReq)) { const request = this.forRequest(sha256).then((response) => response); return timeout(request, ct.timeout()); }); } /** Get verdict for list of SHA256 * @throws {VaasInvalidStateError} If connect() has not been called and awaited. Signifies caller error. * @throws {VaasAuthenticationError} Authentication failed. * @throws {VaasConnectionClosedError} Connection was closed. Call connect() to reconnect. * @throws {VaasTimeoutError} Timeout. Retry request. */ forSha256List(sha256List_1) { return __awaiter(this, arguments, void 0, function* (sha256List, ct = CancellationToken_1.CancellationToken.fromMilliseconds(this.defaultTimeoutHashReq)) { const promises = sha256List.map((sha256) => this.forSha256(sha256, ct)); return Promise.all(promises); }); } /** Get verdict for a file * @throws {VaasInvalidStateError} If connect() has not been called and awaited. Signifies caller error. * @throws {VaasAuthenticationError} Authentication failed. * @throws {VaasConnectionClosedError} Connection was closed. Call connect() to reconnect. * @throws {VaasTimeoutError} Timeout. Retry request. */ forFile(fileBuffer_1) { return __awaiter(this, arguments, void 0, function* (fileBuffer, ct = CancellationToken_1.CancellationToken.fromMilliseconds(this.defaultTimeoutFileReq)) { const request = this.forRequest(fileBuffer).then((response) => response); return timeout(request, ct.timeout()); }); } /** Get verdict for a list of files * @throws {VaasInvalidStateError} If connect() has not been called and awaited. Signifies caller error. * @throws {VaasAuthenticationError} Authentication failed. * @throws {VaasConnectionClosedError} Connection was closed. Call connect() to reconnect. * @throws {VaasTimeoutError} Timeout. Retry request. */ forFileList(fileBuffers_1) { return __awaiter(this, arguments, void 0, function* (fileBuffers, ct = CancellationToken_1.CancellationToken.fromMilliseconds(this.defaultTimeoutFileReq)) { const promises = fileBuffers.map((f) => this.forFile(f, ct)); return Promise.all(promises); }); } forRequest(sample) { return __awaiter(this, void 0, void 0, function* () { const ws = this.getAuthenticatedWebSocket(); return new Promise((resolve, reject) => { const guid = (0, uuid_1.v4)(); if (this.debug) console.debug("uuid", guid); this.verdictPromises.set(guid, { resolve: (verdictResponse) => __awaiter(this, void 0, void 0, function* () { if (verdictResponse.verdict === Verdict_1.Verdict.UNKNOWN && typeof sample !== "string") { yield this.upload(verdictResponse, sample, sample.length); return; } this.verdictPromises.delete(guid); resolve(new vaas_verdict_1.VaasVerdict(verdictResponse.sha256, verdictResponse.verdict, verdictResponse.detection, verdictResponse.file_type, verdictResponse.mime_type)); }), reject: (reason) => reject(reason), }); let hash = typeof sample === "string" ? sample : Vaas.toHexString(sha256.hash(sample)); const verdictReq = JSON.stringify(defaultSerializer.serialize(new verdict_request_1.VerdictRequest(hash, guid, this.connection.sessionId))); ws.send(verdictReq); }); }); } forUrlRequest(url) { return __awaiter(this, void 0, void 0, function* () { const ws = this.getAuthenticatedWebSocket(); return new Promise((resolve, reject) => { const guid = (0, uuid_1.v4)(); if (this.debug) console.debug("uuid", guid); this.verdictPromises.set(guid, { resolve: (verdictResponse) => __awaiter(this, void 0, void 0, function* () { this.verdictPromises.delete(guid); resolve(new vaas_verdict_1.VaasVerdict(verdictResponse.sha256, verdictResponse.verdict, verdictResponse.detection, verdictResponse.file_type, verdictResponse.mime_type)); }), reject: (reason) => reject(reason), }); const verdictReq = JSON.stringify(defaultSerializer.serialize(new verdict_request_for_url_1.VerdictRequestForUrl(url, guid, this.connection.sessionId))); ws.send(verdictReq); }); }); } forStreamRequest(stream) { return __awaiter(this, void 0, void 0, function* () { const ws = this.getAuthenticatedWebSocket(); return new Promise((resolve, reject) => { const guid = (0, uuid_1.v4)(); if (this.debug) console.debug("uuid", guid); let contentLength = 0; this.verdictPromises.set(guid, { resolve: (verdictResponse) => __awaiter(this, void 0, void 0, function* () { if (verdictResponse.verdict !== Verdict_1.Verdict.UNKNOWN) { throw new VaasErrors_1.VaasServerError("Server returned verdict without receiving content"); } if (verdictResponse.upload_token === undefined || verdictResponse.upload_token == "") { throw new VaasErrors_1.VaasServerError("No upload token set in response"); } if (verdictResponse.url === undefined || verdictResponse.url == "") { throw new VaasErrors_1.VaasServerError("No upload url set in response"); } this.verdictPromises.delete(guid); this.verdictPromises.set(guid, { resolve: (verdictResponse) => __awaiter(this, void 0, void 0, function* () { this.verdictPromises.delete(guid); resolve(new vaas_verdict_1.VaasVerdict(verdictResponse.sha256, verdictResponse.verdict, verdictResponse.detection, verdictResponse.file_type, verdictResponse.mime_type)); }), reject: (reason) => reject(reason), }); contentLength = stream.readableLength; yield this.upload(verdictResponse, stream, contentLength); return; }), reject: (reason) => reject(reason), }); const verdictReq = JSON.stringify(defaultSerializer.serialize(new verdict_request_for_stream_1.VerdictRequestForStream(guid, this.connection.sessionId))); ws.send(verdictReq); }); }); } /** Connect to VaaS * @throws {VaasAuthenticationError} Authentication failed. * @throws {VaasConnectionClosedError} Connection was closed. Call connect() to reconnect. */ connect(token, url = VAAS_URL) { return new Promise((resolve, reject) => { const ws = this.webSocketFactory(url); this.connection = { ws: ws }; this.closeEvent = undefined; this.authenticationError = undefined; this.pingTimeout = undefined; // ws library does not have auto-keepalive // https://github.com/websockets/ws/issues/767 if (ws.on !== undefined) { ws.on("ping", (payload) => { ws.pong(payload); }); ws.on("pong", () => { this.pingTimeout = setTimeout(() => ws.ping(), 10000); }); } ws.onopen = () => { try { this.authenticate(token); } catch (error) { reject(error); } }; ws.onclose = (event) => { if (this.pingTimeout) { clearTimeout(this.pingTimeout); this.pingTimeout = undefined; } if (!event.wasClean) { this.closeEvent = event; } const reason = new VaasErrors_1.VaasConnectionClosedError(event); if (this.verdictPromises.size > 0) { this.verdictPromises.forEach((c) => c.reject(reason)); this.verdictPromises.clear(); } reject(reason); }; ws.onmessage = (event) => __awaiter(this, void 0, void 0, function* () { const message = defaultSerializer.deserializeObject(event.data, message_1.Message); switch (message.kind) { case message_1.Kind.AuthResponse: const authResponse = defaultSerializer.deserializeObject(event.data, authentication_response_1.AuthenticationResponse); if (authResponse.success) { this.connection.sessionId = authResponse.session_id; resolve(); return; } this.authenticationError = authResponse; reject(new VaasErrors_1.VaasAuthenticationError()); break; case message_1.Kind.Error: reject(defaultSerializer.deserialize(event.data, websocket_error_1.WebsocketError).text); break; case message_1.Kind.VerdictResponse: const verdictResponse = defaultSerializer.deserialize(event.data, verdict_response_1.VerdictResponse); const promise = this.verdictPromises.get(verdictResponse.guid); if (promise) { yield promise.resolve(verdictResponse); } break; default: console.log(event.data); reject(new Error("Unknown message kind")); break; } }); }); } upload(verdictResponse, input, contentLength) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { const instance = axios.default.create({ baseURL: verdictResponse.url, // the maximum allowed time for the request timeout: 10 * 60 * 1000, headers: { Authorization: verdictResponse.upload_token, "Content-Type": "application/octet-stream", "Content-Length": contentLength.toString(), }, }); instance.defaults.httpAgent = new http_1.default.Agent({ keepAlive: true }); instance.defaults.httpsAgent = new https_1.default.Agent({ keepAlive: true }); yield instance .put("/", input) .then((response) => resolve(response)) .catch((error) => { if (error instanceof axios.AxiosError && error.response) { reject(new Error(`Upload failed with ${error.response.status} - Error ${error.response.data.message}`)); } else { throw error; } }); })); }); } close() { if (this.connection) { this.connection.ws.close(); this.authenticationError = undefined; } } authenticate(token) { const authReq = JSON.stringify(defaultSerializer.serialize(new authentication_request_1.AuthenticationRequest(token))); const ws = this.getConnectedWebSocket(); ws.send(authReq); if (ws.ping !== undefined) { ws.ping(); } } getConnectedWebSocket() { var _a; const ws = (_a = this.connection) === null || _a === void 0 ? void 0 : _a.ws; if (!ws) { throw new VaasErrors_1.VaasInvalidStateError("connect() was not called"); } if (ws.readyState === isomorphic_ws_1.default.WebSocket.CONNECTING) { throw new VaasErrors_1.VaasInvalidStateError("connect() was not awaited"); } if (ws.readyState !== isomorphic_ws_1.default.WebSocket.OPEN) { throw new VaasErrors_1.VaasConnectionClosedError(this.closeEvent); } return ws; } getAuthenticatedWebSocket() { var _a; const ws = this.getConnectedWebSocket(); if (!((_a = this.connection) === null || _a === void 0 ? void 0 : _a.sessionId)) { if (this.authenticationError) throw new VaasErrors_1.VaasAuthenticationError(); throw new VaasErrors_1.VaasInvalidStateError("Not yet authenticated - connect() was not awaited"); } return ws; } } exports.Vaas = Vaas; //# sourceMappingURL=Vaas.js.map