diff --git a/docs/schema/ClientPullChangesRequest.schema.json b/docs/schema/ClientPullChangesRequest.schema.json index c01277c..108e7f0 100644 --- a/docs/schema/ClientPullChangesRequest.schema.json +++ b/docs/schema/ClientPullChangesRequest.schema.json @@ -49,6 +49,9 @@ }, "kr": { "type": "number" + }, + "dh": { + "type": "string" } }, "additionalProperties": false, diff --git a/docs/schema/README.md b/docs/schema/README.md index 9708c1c..fdadbc4 100644 --- a/docs/schema/README.md +++ b/docs/schema/README.md @@ -216,6 +216,8 @@ * [ServerDeviceList](./serverdatastatus-definitions-serverdevicelist.md) – `https://timelimit.io/ServerDataStatus#/definitions/ServerDeviceList` +* [ServerDhKey](./serverdatastatus-definitions-serverdhkey.md) – `https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey` + * [ServerExtendedDeviceData](./serverdatastatus-definitions-serverextendeddevicedata.md) – `https://timelimit.io/ServerDataStatus#/definitions/ServerExtendedDeviceData` * [ServerInstalledAppsData](./serverdatastatus-definitions-serverinstalledappsdata.md) – `https://timelimit.io/ServerDataStatus#/definitions/ServerInstalledAppsData` diff --git a/docs/schema/ServerDataStatus.schema.json b/docs/schema/ServerDataStatus.schema.json index ffafea1..9166134 100644 --- a/docs/schema/ServerDataStatus.schema.json +++ b/docs/schema/ServerDataStatus.schema.json @@ -67,6 +67,9 @@ "$ref": "#/definitions/ServerKeyResponse" } }, + "dh": { + "$ref": "#/definitions/ServerDhKey" + }, "fullVersion": { "type": "number" }, @@ -913,6 +916,23 @@ "tempKey" ], "title": "ServerKeyResponse" + }, + "ServerDhKey": { + "type": "object", + "properties": { + "v": { + "type": "string" + }, + "k": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "k", + "v" + ], + "title": "ServerDhKey" } }, "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/docs/schema/clientpullchangesrequest-definitions-clientdatastatus-properties-dh.md b/docs/schema/clientpullchangesrequest-definitions-clientdatastatus-properties-dh.md new file mode 100644 index 0000000..695202c --- /dev/null +++ b/docs/schema/clientpullchangesrequest-definitions-clientdatastatus-properties-dh.md @@ -0,0 +1,15 @@ +# Untitled string in ClientPullChangesRequest Schema + +```txt +https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/dh +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :---------------------------------------------------------------------------------------------------- | +| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ClientPullChangesRequest.schema.json\*](ClientPullChangesRequest.schema.json "open original schema") | + +## dh Type + +`string` diff --git a/docs/schema/clientpullchangesrequest-definitions-clientdatastatus.md b/docs/schema/clientpullchangesrequest-definitions-clientdatastatus.md index fb9f4d1..d3bcd1c 100644 --- a/docs/schema/clientpullchangesrequest-definitions-clientdatastatus.md +++ b/docs/schema/clientpullchangesrequest-definitions-clientdatastatus.md @@ -26,6 +26,7 @@ https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus | [devicesDetail](#devicesdetail) | `object` | Optional | cannot be null | [ClientPullChangesRequest](clientpullchangesrequest-definitions-clientdatastatus-properties-devicesdetail.md "https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/devicesDetail") | | [kri](#kri) | `number` | Optional | cannot be null | [ClientPullChangesRequest](clientpullchangesrequest-definitions-clientdatastatus-properties-kri.md "https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/kri") | | [kr](#kr) | `number` | Optional | cannot be null | [ClientPullChangesRequest](clientpullchangesrequest-definitions-clientdatastatus-properties-kr.md "https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/kr") | +| [dh](#dh) | `string` | Optional | cannot be null | [ClientPullChangesRequest](clientpullchangesrequest-definitions-clientdatastatus-properties-dh.md "https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/dh") | ## devices @@ -170,3 +171,21 @@ https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus ### kr Type `number` + +## dh + + + +`dh` + +* is optional + +* Type: `string` + +* cannot be null + +* defined in: [ClientPullChangesRequest](clientpullchangesrequest-definitions-clientdatastatus-properties-dh.md "https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/dh") + +### dh Type + +`string` diff --git a/docs/schema/clientpullchangesrequest.md b/docs/schema/clientpullchangesrequest.md index 33020ac..17231f5 100644 --- a/docs/schema/clientpullchangesrequest.md +++ b/docs/schema/clientpullchangesrequest.md @@ -77,6 +77,7 @@ Reference this group by using | [devicesDetail](#devicesdetail) | `object` | Optional | cannot be null | [ClientPullChangesRequest](clientpullchangesrequest-definitions-clientdatastatus-properties-devicesdetail.md "https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/devicesDetail") | | [kri](#kri) | `number` | Optional | cannot be null | [ClientPullChangesRequest](clientpullchangesrequest-definitions-clientdatastatus-properties-kri.md "https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/kri") | | [kr](#kr) | `number` | Optional | cannot be null | [ClientPullChangesRequest](clientpullchangesrequest-definitions-clientdatastatus-properties-kr.md "https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/kr") | +| [dh](#dh) | `string` | Optional | cannot be null | [ClientPullChangesRequest](clientpullchangesrequest-definitions-clientdatastatus-properties-dh.md "https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/dh") | ### devices @@ -222,6 +223,24 @@ Reference this group by using `number` +### dh + + + +`dh` + +* is optional + +* Type: `string` + +* cannot be null + +* defined in: [ClientPullChangesRequest](clientpullchangesrequest-definitions-clientdatastatus-properties-dh.md "https://timelimit.io/ClientPullChangesRequest#/definitions/ClientDataStatus/properties/dh") + +#### dh Type + +`string` + ## Definitions group CategoryDataStatus Reference this group by using diff --git a/docs/schema/serverdatastatus-definitions-serverdhkey-properties-k.md b/docs/schema/serverdatastatus-definitions-serverdhkey-properties-k.md new file mode 100644 index 0000000..81d59f0 --- /dev/null +++ b/docs/schema/serverdatastatus-definitions-serverdhkey-properties-k.md @@ -0,0 +1,15 @@ +# Untitled string in ServerDataStatus Schema + +```txt +https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey/properties/k +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------ | +| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ServerDataStatus.schema.json\*](ServerDataStatus.schema.json "open original schema") | + +## k Type + +`string` diff --git a/docs/schema/serverdatastatus-definitions-serverdhkey-properties-v.md b/docs/schema/serverdatastatus-definitions-serverdhkey-properties-v.md new file mode 100644 index 0000000..f6942f9 --- /dev/null +++ b/docs/schema/serverdatastatus-definitions-serverdhkey-properties-v.md @@ -0,0 +1,15 @@ +# Untitled string in ServerDataStatus Schema + +```txt +https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey/properties/v +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------ | +| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ServerDataStatus.schema.json\*](ServerDataStatus.schema.json "open original schema") | + +## v Type + +`string` diff --git a/docs/schema/serverdatastatus-definitions-serverdhkey.md b/docs/schema/serverdatastatus-definitions-serverdhkey.md new file mode 100644 index 0000000..9ec7cae --- /dev/null +++ b/docs/schema/serverdatastatus-definitions-serverdhkey.md @@ -0,0 +1,58 @@ +# ServerDhKey Schema + +```txt +https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :----------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------ | +| Can be instantiated | No | Unknown status | No | Forbidden | Forbidden | none | [ServerDataStatus.schema.json\*](ServerDataStatus.schema.json "open original schema") | + +## ServerDhKey Type + +`object` ([ServerDhKey](serverdatastatus-definitions-serverdhkey.md)) + +# ServerDhKey Properties + +| Property | Type | Required | Nullable | Defined by | +| :------- | :------- | :------- | :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [v](#v) | `string` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdhkey-properties-v.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey/properties/v") | +| [k](#k) | `string` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdhkey-properties-k.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey/properties/k") | + +## v + + + +`v` + +* is required + +* Type: `string` + +* cannot be null + +* defined in: [ServerDataStatus](serverdatastatus-definitions-serverdhkey-properties-v.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey/properties/v") + +### v Type + +`string` + +## k + + + +`k` + +* is required + +* Type: `string` + +* cannot be null + +* defined in: [ServerDataStatus](serverdatastatus-definitions-serverdhkey-properties-k.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey/properties/k") + +### k Type + +`string` diff --git a/docs/schema/serverdatastatus-properties-dh-properties-k.md b/docs/schema/serverdatastatus-properties-dh-properties-k.md new file mode 100644 index 0000000..a242982 --- /dev/null +++ b/docs/schema/serverdatastatus-properties-dh-properties-k.md @@ -0,0 +1,15 @@ +# Untitled string in ServerDataStatus Schema + +```txt +https://timelimit.io/ServerDataStatus#/properties/dh/properties/k +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------ | +| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ServerDataStatus.schema.json\*](ServerDataStatus.schema.json "open original schema") | + +## k Type + +`string` diff --git a/docs/schema/serverdatastatus-properties-dh-properties-v.md b/docs/schema/serverdatastatus-properties-dh-properties-v.md new file mode 100644 index 0000000..d51ed91 --- /dev/null +++ b/docs/schema/serverdatastatus-properties-dh-properties-v.md @@ -0,0 +1,15 @@ +# Untitled string in ServerDataStatus Schema + +```txt +https://timelimit.io/ServerDataStatus#/properties/dh/properties/v +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------ | +| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [ServerDataStatus.schema.json\*](ServerDataStatus.schema.json "open original schema") | + +## v Type + +`string` diff --git a/docs/schema/serverdatastatus-properties-dh.md b/docs/schema/serverdatastatus-properties-dh.md new file mode 100644 index 0000000..c2cca49 --- /dev/null +++ b/docs/schema/serverdatastatus-properties-dh.md @@ -0,0 +1,58 @@ +# Untitled object in ServerDataStatus Schema + +```txt +https://timelimit.io/ServerDataStatus#/properties/dh +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :----------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------ | +| Can be instantiated | No | Unknown status | No | Forbidden | Forbidden | none | [ServerDataStatus.schema.json\*](ServerDataStatus.schema.json "open original schema") | + +## dh Type + +`object` ([Details](serverdatastatus-properties-dh.md)) + +# dh Properties + +| Property | Type | Required | Nullable | Defined by | +| :------- | :------- | :------- | :------------- | :------------------------------------------------------------------------------------------------------------------------------------- | +| [v](#v) | `string` | Required | cannot be null | [ServerDataStatus](serverdatastatus-properties-dh-properties-v.md "https://timelimit.io/ServerDataStatus#/properties/dh/properties/v") | +| [k](#k) | `string` | Required | cannot be null | [ServerDataStatus](serverdatastatus-properties-dh-properties-k.md "https://timelimit.io/ServerDataStatus#/properties/dh/properties/k") | + +## v + + + +`v` + +* is required + +* Type: `string` + +* cannot be null + +* defined in: [ServerDataStatus](serverdatastatus-properties-dh-properties-v.md "https://timelimit.io/ServerDataStatus#/properties/dh/properties/v") + +### v Type + +`string` + +## k + + + +`k` + +* is required + +* Type: `string` + +* cannot be null + +* defined in: [ServerDataStatus](serverdatastatus-properties-dh-properties-k.md "https://timelimit.io/ServerDataStatus#/properties/dh/properties/k") + +### k Type + +`string` diff --git a/docs/schema/serverdatastatus.md b/docs/schema/serverdatastatus.md index 3b2ede4..4ead767 100644 --- a/docs/schema/serverdatastatus.md +++ b/docs/schema/serverdatastatus.md @@ -30,6 +30,7 @@ https://timelimit.io/ServerDataStatus | [users](#users) | `object` | Optional | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serveruserlist.md "https://timelimit.io/ServerDataStatus#/properties/users") | | [krq](#krq) | `array` | Optional | cannot be null | [ServerDataStatus](serverdatastatus-properties-krq.md "https://timelimit.io/ServerDataStatus#/properties/krq") | | [kr](#kr) | `array` | Optional | cannot be null | [ServerDataStatus](serverdatastatus-properties-kr.md "https://timelimit.io/ServerDataStatus#/properties/kr") | +| [dh](#dh) | `object` | Optional | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdhkey.md "https://timelimit.io/ServerDataStatus#/properties/dh") | | [fullVersion](#fullversion) | `number` | Required | cannot be null | [ServerDataStatus](serverdatastatus-properties-fullversion.md "https://timelimit.io/ServerDataStatus#/properties/fullVersion") | | [message](#message) | `string` | Optional | cannot be null | [ServerDataStatus](serverdatastatus-properties-message.md "https://timelimit.io/ServerDataStatus#/properties/message") | | [apiLevel](#apilevel) | `number` | Required | cannot be null | [ServerDataStatus](serverdatastatus-properties-apilevel.md "https://timelimit.io/ServerDataStatus#/properties/apiLevel") | @@ -250,6 +251,24 @@ https://timelimit.io/ServerDataStatus `object[]` ([ServerKeyResponse](serverdatastatus-definitions-serverkeyresponse.md)) +## dh + + + +`dh` + +* is optional + +* Type: `object` ([ServerDhKey](serverdatastatus-definitions-serverdhkey.md)) + +* cannot be null + +* defined in: [ServerDataStatus](serverdatastatus-definitions-serverdhkey.md "https://timelimit.io/ServerDataStatus#/properties/dh") + +### dh Type + +`object` ([ServerDhKey](serverdatastatus-definitions-serverdhkey.md)) + ## fullVersion @@ -3372,3 +3391,52 @@ Reference this group by using #### signature Type `string` + +## Definitions group ServerDhKey + +Reference this group by using + +```json +{"$ref":"https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey"} +``` + +| Property | Type | Required | Nullable | Defined by | +| :------- | :------- | :------- | :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [v](#v) | `string` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdhkey-properties-v.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey/properties/v") | +| [k](#k) | `string` | Required | cannot be null | [ServerDataStatus](serverdatastatus-definitions-serverdhkey-properties-k.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey/properties/k") | + +### v + + + +`v` + +* is required + +* Type: `string` + +* cannot be null + +* defined in: [ServerDataStatus](serverdatastatus-definitions-serverdhkey-properties-v.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey/properties/v") + +#### v Type + +`string` + +### k + + + +`k` + +* is required + +* Type: `string` + +* cannot be null + +* defined in: [ServerDataStatus](serverdatastatus-definitions-serverdhkey-properties-k.md "https://timelimit.io/ServerDataStatus#/definitions/ServerDhKey/properties/k") + +#### k Type + +`string` diff --git a/src/api/sync.ts b/src/api/sync.ts index 224d892..6fee2b5 100644 --- a/src/api/sync.ts +++ b/src/api/sync.ts @@ -117,7 +117,8 @@ export const createSyncRouter = ({ database, websocket, connectedDevicesManager, familyId, deviceId, clientStatus: body.status, - transaction + transaction, + eventHandler }) }) @@ -128,6 +129,9 @@ export const createSyncRouter = ({ database, websocket, connectedDevicesManager, if (serverStatus.usedTimes) { eventHandler.countEvent('pullStatusRequest usedTimes') } if (serverStatus.rules) { eventHandler.countEvent('pullStatusRequest rules') } if (serverStatus.users) { eventHandler.countEvent('pullStatusRequest users') } + if (serverStatus.krq) { eventHandler.countEvent('pullStatusRequest pendingKeyRequests') } + if (serverStatus.kr) { eventHandler.countEvent('pullStatusRequest keyResponses') } + if (serverStatus.dh) { eventHandler.countEvent('pullStatusRequest dh') } res.json(serverStatus) } catch (ex) { diff --git a/src/api/validator.ts b/src/api/validator.ts index da15c92..4698c35 100644 --- a/src/api/validator.ts +++ b/src/api/validator.ts @@ -72,6 +72,9 @@ const definitions = { }, "kr": { "type": "number" + }, + "dh": { + "type": "string" } }, "additionalProperties": false, @@ -2705,6 +2708,22 @@ const definitions = { "srvSeq", "tempKey" ] + }, + "ServerDhKey": { + "type": "object", + "properties": { + "v": { + "type": "string" + }, + "k": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "k", + "v" + ] } } diff --git a/src/database/devicedhkey.ts b/src/database/devicedhkey.ts new file mode 100644 index 0000000..c81b15c --- /dev/null +++ b/src/database/devicedhkey.ts @@ -0,0 +1,81 @@ +/* + * server component for the TimeLimit App + * Copyright (C) 2019 - 2022 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import * as Sequelize from 'sequelize' +import { familyIdColumn, idWithinFamilyColumn, versionColumn, timestampColumn } from './columns' +import { SequelizeAttributes } from './types' + +export const config = { + generateNewKeyAfterAge: 1000 * 60 * 60 * 24, + generationTimeRounding: 1000 * 60 * 60, + expireDelay: 1000 * 60 * 60 * 2, + expireTimeRounding: 1000 * 60 * 15 +} + +export function calculateExpireTime(now: bigint): BigInt { + const expireBaseTime = now + BigInt(config.expireDelay) + const expireTime = expireBaseTime - expireBaseTime % BigInt(config.expireTimeRounding) + BigInt(config.expireTimeRounding) + + return expireTime +} + +export interface DeviceDhKeyAttributes { + familyId: string + deviceId: string + version: string + createdAt: string + expireAt: string | null + publicKey: Buffer + privateKey: Buffer +} + +export type DeviceDhKeyModel = Sequelize.Model & DeviceDhKeyAttributes +export type DeviceDhKeyModelStatic = typeof Sequelize.Model & { + new (values?: object, options?: Sequelize.BuildOptions): DeviceDhKeyModel; +} + +export const attributes: SequelizeAttributes = { + familyId: { + ...familyIdColumn, + primaryKey: true + }, + deviceId: { + ...idWithinFamilyColumn, + primaryKey: true + }, + version: { + ...versionColumn, + primaryKey: true + }, + createdAt: { + ...timestampColumn + }, + expireAt: { + ...timestampColumn, + allowNull: true + }, + publicKey: { + type: Sequelize.BLOB, + allowNull: false + }, + privateKey: { + type: Sequelize.BLOB, + allowNull: false + } +} + +export const createDeviceDhKey = (sequelize: Sequelize.Sequelize): DeviceDhKeyModelStatic => sequelize.define('DeviceDhKey', attributes) as DeviceDhKeyModelStatic diff --git a/src/database/main.ts b/src/database/main.ts index 95ed3b8..379cde8 100644 --- a/src/database/main.ts +++ b/src/database/main.ts @@ -27,6 +27,7 @@ import { CategoryTimeWarningModelStatic, createCategoryTimeWarningModel } from ' import { ChildTaskModelStatic, createChildTaskModel } from './childtask' import { ConfigModelStatic, createConfigModel } from './config' import { createDeviceModel, DeviceModelStatic } from './device' +import { createDeviceDhKey, DeviceDhKeyModelStatic } from './devicedhkey' import { createEncryptedAppListModel, EncryptedAppListModelStatic } from './encryptedapplist' import { createFamilyModel, FamilyModelStatic } from './family' import { createKeyRequestModel, KeyRequestModelStatic } from './keyrequest' @@ -55,6 +56,7 @@ export interface Database { childTask: ChildTaskModelStatic config: ConfigModelStatic device: DeviceModelStatic + deviceDhKey: DeviceDhKeyModelStatic encryptedAppList: EncryptedAppListModelStatic family: FamilyModelStatic keyRequest: KeyRequestModelStatic @@ -83,6 +85,7 @@ const createDatabase = (sequelize: Sequelize.Sequelize): Database => ({ categoryTimeWarning: createCategoryTimeWarningModel(sequelize), config: createConfigModel(sequelize), device: createDeviceModel(sequelize), + deviceDhKey: createDeviceDhKey(sequelize), encryptedAppList: createEncryptedAppListModel(sequelize), family: createFamilyModel(sequelize), keyRequest: createKeyRequestModel(sequelize), diff --git a/src/database/migration/migrations/20220913-create-device-dh-keys.ts b/src/database/migration/migrations/20220913-create-device-dh-keys.ts new file mode 100644 index 0000000..0929d3d --- /dev/null +++ b/src/database/migration/migrations/20220913-create-device-dh-keys.ts @@ -0,0 +1,70 @@ +/* + * server component for the TimeLimit App + * Copyright (C) 2019 - 2022 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { QueryInterface, Sequelize, Transaction } from 'sequelize' + +export async function up (queryInterface: QueryInterface, sequelize: Sequelize) { + await sequelize.transaction({ + type: Transaction.TYPES.EXCLUSIVE + }, async (transaction) => { + const dialect = sequelize.getDialect() + const isMysql = dialect === 'mysql' || dialect === 'mariadb' + const isPosgresql = dialect === 'postgres' + + if (isMysql) { + await sequelize.query( + 'CREATE TABLE `DeviceDhKeys` ' + + '(`familyId` VARCHAR(10) NOT NULL,' + + '`deviceId` VARCHAR(6) NOT NULL,' + + '`version` VARCHAR(4) NOT NULL,' + + '`createdAt` BIGINT NOT NULL, ' + + '`expireAt` BIGINT NULL, ' + + '`publicKey` BLOB NOT NULL, ' + + '`privateKey` BLOB NOT NULL, ' + + 'PRIMARY KEY (`familyId`, `deviceId`, `version`),' + + 'FOREIGN KEY (`familyId`, `deviceId`) REFERENCES `Devices` (`familyId`, `deviceId`) ON UPDATE CASCADE ON DELETE CASCADE' + + ')', + { transaction } + ) + } else { + await sequelize.query( + 'CREATE TABLE "DeviceDhKeys" ' + + '("familyId" VARCHAR(10) NOT NULL,' + + '"deviceId" VARCHAR(6) NOT NULL,' + + '"version" VARCHAR(4) NOT NULL,' + + '"createdAt" ' + (isPosgresql ? 'BIGINT' : 'LONG') + ' NOT NULL, ' + + '"expireAt" ' + (isPosgresql ? 'BIGINT' : 'LONG') + ' NULL, ' + + '"publicKey" ' + (isPosgresql ? 'BYTEA' : 'BLOB') + ' NOT NULL, ' + + '"privateKey" ' + (isPosgresql ? 'BYTEA' : 'BLOB') + ' NOT NULL, ' + + 'PRIMARY KEY ("familyId", "deviceId", "version"),' + + 'FOREIGN KEY ("familyId", "deviceId") REFERENCES "Devices" ("familyId", "deviceId") ON UPDATE CASCADE ON DELETE CASCADE' + + ')', + { transaction } + ) + } + + await queryInterface.addIndex('DeviceDhKeys', ['expireAt'], { transaction }) + }) +} + +export async function down (queryInterface: QueryInterface, sequelize: Sequelize) { + await sequelize.transaction({ + type: Transaction.TYPES.EXCLUSIVE + }, async (transaction) => { + await queryInterface.dropTable('DeviceDhKeys', { transaction }) + }) +} diff --git a/src/function/sync/get-server-data-status/dh-keys.ts b/src/function/sync/get-server-data-status/dh-keys.ts new file mode 100644 index 0000000..b1a87dd --- /dev/null +++ b/src/function/sync/get-server-data-status/dh-keys.ts @@ -0,0 +1,123 @@ +/* + * server component for the TimeLimit App + * Copyright (C) 2019 - 2022 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import * as Sequelize from 'sequelize' +import { Database } from '../../../database' +import { config, calculateExpireTime } from '../../../database/devicedhkey' +import { ServerDhKey } from '../../../object/serverdatastatus' +import { generateVersionId } from '../../../util/token' +import { EventHandler } from '../../../monitoring/eventhandler' +import { FamilyEntry } from './family-entry' +import { generateKeyPair } from 'crypto' +import { promisify } from 'util' + +const generateKeyPairAsync = promisify(generateKeyPair) + +export async function getDeviceDhKeys ({ + database, transaction, familyEntry, deviceId, lastVersionId, eventHandler +}: { + database: Database + transaction: Sequelize.Transaction + familyEntry: FamilyEntry + deviceId: string + lastVersionId: string | null + eventHandler: EventHandler +}): Promise { + const savedData = await database.deviceDhKey.findAll({ + where: { + familyId: familyEntry.familyId, + deviceId + }, + transaction + }) + + const now = BigInt(Date.now()) + const oldCurrentKey = savedData.find((item) => item.expireAt === null) + const needsNewKey = + oldCurrentKey === undefined || + BigInt(oldCurrentKey.createdAt) + BigInt(config.generateNewKeyAfterAge) <= now || + BigInt(oldCurrentKey.createdAt) > now + + if (needsNewKey) { + eventHandler.countEvent('getDeviceDhKeys:needsNewKey') + + const newVersion = generateVersionId() + const newKeypair = await generateKeyPairAsync( + 'ec', + { + namedCurve: 'prime256v1', + publicKeyEncoding: { + type: 'spki', + format: 'der' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'der' + } + } + ) + + if (savedData.length >= 8) { + eventHandler.countEvent('getDeviceDhKeys:gc') + + const minCreatedAtValue = savedData.map((item) => BigInt(item.createdAt)).sort()[0] + + await database.deviceDhKey.destroy({ + where: { + familyId: familyEntry.familyId, + deviceId, + createdAt: { + [Sequelize.Op.lte]: minCreatedAtValue.toString(10) + } + }, + transaction + }) + } + + await database.deviceDhKey.update({ + expireAt: calculateExpireTime(now).toString(10) + }, { + where: { + familyId: familyEntry.familyId, + deviceId, + expireAt: null + }, + transaction + }) + + await database.deviceDhKey.create({ + familyId: familyEntry.familyId, + deviceId, + version: newVersion, + createdAt: (now - now % BigInt(config.generationTimeRounding)).toString(10), + expireAt: null, + publicKey: newKeypair.publicKey, + privateKey: newKeypair.privateKey + }, { transaction }) + + return { + k: newKeypair.publicKey.toString('base64'), + v: newVersion + } + } else { + if (lastVersionId === oldCurrentKey.version) return null + else return { + k: oldCurrentKey.publicKey.toString('base64'), + v: oldCurrentKey.version + } + } +} diff --git a/src/function/sync/get-server-data-status/index.ts b/src/function/sync/get-server-data-status/index.ts index 95ffe67..6e3dfd2 100644 --- a/src/function/sync/get-server-data-status/index.ts +++ b/src/function/sync/get-server-data-status/index.ts @@ -21,6 +21,7 @@ import { Database } from '../../../database' import { getStatusMessage } from '../../../function/statusmessage' import { ClientDataStatus } from '../../../object/clientdatastatus' import { ServerDataStatus } from '../../../object/serverdatastatus' +import { EventHandler } from '../../../monitoring/eventhandler' import { getAppList } from './app-list' import { getCategoryAssignedApps, getCategoryBaseDatas, getCategoryDataToSync, @@ -28,23 +29,28 @@ import { } from './category' import { getDeviceDetailList } from './device-detail' import { getDeviceList } from './device-list' +import { getDeviceDhKeys } from './dh-keys' import { getFamilyEntry } from './family-entry' import { getUserList } from './user-list' import { getKeyRequests } from './key-requests' import { getKeyResponses } from './key-responses' export const generateServerDataStatus = async ({ - database, clientStatus, familyId, deviceId, transaction + database, clientStatus, familyId, deviceId, transaction, eventHandler }: { database: Database clientStatus: ClientDataStatus familyId: string deviceId: string transaction: Sequelize.Transaction + eventHandler: EventHandler }): Promise => { + const clientLevel = clientStatus.clientLevel || 0 + const familyEntry = await getFamilyEntry({ database, familyId, transaction }) - const doesClientSupportTasks = clientStatus.clientLevel !== undefined && clientStatus.clientLevel >= 3 - const doesClientSupportCryptoApps = clientStatus.clientLevel !== undefined && clientStatus.clientLevel >= 4 + const doesClientSupportTasks = clientLevel >= 3 + const doesClientSupportCryptoApps = clientLevel >= 4 + const doesClientSupportDh = clientLevel >= 5 const result: ServerDataStatus = { fullVersion: config.alwaysPro ? 1 : ( @@ -135,5 +141,16 @@ export const generateServerDataStatus = async ({ }) || undefined } + if (doesClientSupportDh) { + result.dh = await getDeviceDhKeys({ + database, + transaction, + familyEntry, + deviceId, + lastVersionId: clientStatus.dh || null, + eventHandler + }) || undefined + } + return result } diff --git a/src/object/clientdatastatus.ts b/src/object/clientdatastatus.ts index 531f17e..fdc7ea3 100644 --- a/src/object/clientdatastatus.ts +++ b/src/object/clientdatastatus.ts @@ -24,6 +24,7 @@ export interface ClientDataStatus { devicesDetail?: ClientDataStatusDevicesExtended kri?: number // last key request index kr?: number // last key response index + dh?: string // last Diffie Hellman key version } export type ClientDataStatusApps = {[key: string]: string} // installedAppsVersionsByDeviceId diff --git a/src/object/serverdatastatus.ts b/src/object/serverdatastatus.ts index 78a1437..b880eeb 100644 --- a/src/object/serverdatastatus.ts +++ b/src/object/serverdatastatus.ts @@ -34,6 +34,7 @@ export interface ServerDataStatus { users?: ServerUserList // newUserList krq?: Array // pendingKeyRequests kr?: Array // keyResponses + dh?: ServerDhKey // Diffie Hellman fullVersion: number // fullVersionUntil message?: string apiLevel: number @@ -252,3 +253,8 @@ export interface ServerKeyResponse { cryptKey: string, signature: string } + +export interface ServerDhKey { + v: string // version + k: string // key, base64 +} diff --git a/src/worker/delete-old-tokens.ts b/src/worker/delete-old-tokens.ts index 66d30df..ac2f901 100644 --- a/src/worker/delete-old-tokens.ts +++ b/src/worker/delete-old-tokens.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 - 2021 Jonas Lochmann + * Copyright (C) 2019 - 2022 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -71,4 +71,12 @@ async function deleteOldTokens ({ database }: { transaction }) }) + + await database.deviceDhKey.destroy({ + where: { + expireAt: { + [Sequelize.Op.lt]: Date.now().toString() + } + } + }) }