/* * server component for the TimeLimit App * Copyright (C) 2019 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 { createDecipheriv, createHash } from 'crypto' import { assertIsHexString } from '../util/hexstring' import { assertIdWithinFamily } from '../util/token' import { ParentAction } from './basetypes' export class ChangeParentPasswordAction extends ParentAction { readonly parentUserId: string readonly newPasswordFirstHash: string readonly newPasswordSecondSalt: string readonly newPasswordSecondHashEncrypted: string readonly integrity: string constructor ({ parentUserId, newPasswordFirstHash, newPasswordSecondSalt, newPasswordSecondHashEncrypted, integrity }: { parentUserId: string newPasswordFirstHash: string newPasswordSecondSalt: string newPasswordSecondHashEncrypted: string integrity: string }) { super() assertIdWithinFamily(parentUserId) if ( (!parentUserId) || (!newPasswordFirstHash) || (!newPasswordSecondSalt) || (!newPasswordSecondHashEncrypted) || (!integrity) ) { throw new Error('missing required parameter for change parent password') } if (integrity.length !== 128) { throw new Error('wrong length of integrity data') } assertIsHexString(newPasswordSecondHashEncrypted) assertIsHexString(integrity) this.parentUserId = parentUserId this.newPasswordFirstHash = newPasswordFirstHash this.newPasswordSecondSalt = newPasswordSecondSalt this.newPasswordSecondHashEncrypted = newPasswordSecondHashEncrypted this.integrity = integrity } serialize = (): SerializedChangeParentPasswordAction => ({ type: 'CHANGE_PARENT_PASSWORD', userId: this.parentUserId, hash: this.newPasswordFirstHash, secondSalt: this.newPasswordSecondSalt, secondHashEncrypted: this.newPasswordSecondHashEncrypted, integrity: this.integrity }) static parse = ({ userId, hash, secondSalt, secondHashEncrypted, integrity }: SerializedChangeParentPasswordAction) => ( new ChangeParentPasswordAction({ parentUserId: userId, newPasswordFirstHash: hash, newPasswordSecondSalt: secondSalt, newPasswordSecondHashEncrypted: secondHashEncrypted, integrity }) ) assertIntegrityValid ({ oldPasswordSecondHash }: {oldPasswordSecondHash: string}) { const integrityData = oldPasswordSecondHash + this.parentUserId + this.newPasswordFirstHash + this.newPasswordSecondSalt + this.newPasswordSecondHashEncrypted const expected = createHash('sha512').update(integrityData).digest('hex') if (expected !== this.integrity) { throw new Error('invalid integrity for change parent password action') } } decryptSecondHash ({ oldPasswordSecondHash }: {oldPasswordSecondHash: string}) { if (this.newPasswordSecondHashEncrypted.length <= 70) { throw new Error('wrong length of the new password') } const ivHex = this.newPasswordSecondHashEncrypted.substring(0, 32) const salt = this.newPasswordSecondHashEncrypted.substring(32, 64) const encryptedData = this.newPasswordSecondHashEncrypted.substring(64) const keyData = oldPasswordSecondHash + salt const key = createHash('sha512').update(keyData).digest().slice(0, 16) const decipher = createDecipheriv('aes-128-ctr', key, Buffer.from(ivHex, 'hex')) decipher.setAutoPadding(false) const decryptedSecondHash = decipher.update(Buffer.from(encryptedData, 'hex')).toString() + decipher.final().toString() return decryptedSecondHash } } export interface SerializedChangeParentPasswordAction { type: 'CHANGE_PARENT_PASSWORD' userId: string hash: string secondSalt: string secondHashEncrypted: string integrity: string }