Add generating dh keys

This commit is contained in:
Jonas Lochmann
2022-09-12 02:00:00 +02:00
parent 1414e41672
commit f725a7bda3
23 changed files with 659 additions and 5 deletions
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<ServerDhKey | null> {
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
}
}
}
@@ -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<ServerDataStatus> => {
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
}