Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature - Replacing Bcrypt with NodeJS built in PBKDF2 #315

Open
wants to merge 7 commits into
base: rc-1.6.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions 01_Data/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@
"author": "S44",
"license": "Apache-2.0",
"devDependencies": {
"@types/bcrypt": "5.0.1",
"@types/uuid": "9.0.1",
"eslint": "8.48.0",
"typescript": "5.0.4"
},
"dependencies": {
"@citrineos/base": "1.6.0",
"@types/sequelize": "4.28.20",
"bcrypt": "5.1.1",
"pg": "8.11.3",
"pg-hstore": "2.3.4",
"sequelize-typescript": "2.1.6",
Expand All @@ -36,4 +34,4 @@
"engines": {
"node": ">=18"
}
}
}
1 change: 1 addition & 0 deletions 01_Data/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,4 @@ export {
SequelizeChargingStationSequenceRepository,
} from './layers/sequelize'; // TODO ensure all needed modules are properly exported
export { RepositoryStore } from './layers/sequelize/repository/RepositoryStore';
export { CryptoUtils } from './util/CryptoUtils';
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

import { AttributeEnumType, ComponentType, type CustomDataType, DataEnumType, EVSEType, MutabilityEnumType, Namespace, type VariableAttributeType, VariableType } from '@citrineos/base';
import { BelongsTo, Column, DataType, ForeignKey, HasMany, Index, Model, Table } from 'sequelize-typescript';
import * as bcrypt from 'bcrypt';
import { Variable } from './Variable';
import { Component } from './Component';
import { Evse } from './Evse';
import { Boot } from '../Boot';
import { VariableStatus } from './VariableStatus';
import { ChargingStation } from '../Location';
import { CryptoUtils } from '../../../../util/CryptoUtils';

@Table
export class VariableAttribute extends Model implements VariableAttributeType {
Expand Down Expand Up @@ -48,12 +48,12 @@ export class VariableAttribute extends Model implements VariableAttributeType {
@Column({
// TODO: Make this configurable? also used in VariableStatus model
type: DataType.STRING(4000),
set(valueString) {
set(valueString: string) {
if (valueString) {
const valueType = (this as VariableAttribute).dataType;
switch (valueType) {
case DataEnumType.passwordString:
valueString = bcrypt.hashSync(valueString as string, 10);
valueString = CryptoUtils.getSaltedHash(valueString);
break;
default:
// Do nothing
Expand Down
26 changes: 26 additions & 0 deletions 01_Data/src/util/CryptoUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { pbkdf2Sync, randomBytes } from 'crypto';

export class CryptoUtils {
/**
* Generates a salted hash for a given password.
* @param password The plain text password.
* @returns A string containing the salt and hash separated by a colon.
*/
static getSaltedHash(password: string): string {
const salt = randomBytes(16).toString('hex');
const hash = pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex');
return `${salt}:${hash}`;
thanaParis marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Validates if an input password matches a stored salted hash.
* @param storedValue The stored value containing salt and hash separated by a colon.
* @param inputPassword The input password to validate.
* @returns A boolean indicating if the password matches.
*/
static isHashMatch(storedValue: string, inputPassword: string): boolean {
const [salt, originalHash] = storedValue.split(':');
const hash = pbkdf2Sync(inputPassword, salt, 1000, 64, 'sha512').toString('hex');
return hash === originalHash;
}
}
1 change: 0 additions & 1 deletion 02_Util/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"license": "Apache-2.0",
"devDependencies": {
"@types/amqplib": "0.10.2",
"@types/bcrypt": "5.0.2",
"@types/deasync-promise": "1.0.0",
"@types/json-schema-faker": "0.5.4",
"@types/jsrsasign": "10.5.14",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { AttributeEnumType } from '@citrineos/base';
import { AttributeEnumType, AuthenticationOptions } from '@citrineos/base';
import { ILogObj, Logger } from 'tslog';
import { IDeviceModelRepository } from '@citrineos/data';
import { CryptoUtils, IDeviceModelRepository } from '@citrineos/data';
import { IncomingMessage } from 'http';
import * as bcrypt from 'bcrypt';
import { extractBasicCredentials } from '../../util/RequestOperations';
import { AuthenticatorFilter } from './AuthenticatorFilter';
import { AuthenticationOptions } from '@citrineos/base';
import { UpgradeAuthenticationError } from './errors/AuthenticationError';

/**
Expand Down Expand Up @@ -33,7 +31,9 @@ export class BasicAuthenticationFilter extends AuthenticatorFilter {
): Promise<void> {
const { username, password } = extractBasicCredentials(request);
if (!username || !password) {
throw new UpgradeAuthenticationError('Auth header missing or incorrectly formatted');
throw new UpgradeAuthenticationError(
'Auth header missing or incorrectly formatted',
);
}

if (
Expand All @@ -56,7 +56,7 @@ export class BasicAuthenticationFilter extends AuthenticatorFilter {
if (r && r[0]) {
const hashedPassword = r[0].value;
if (hashedPassword) {
return bcrypt.compare(password, hashedPassword);
return CryptoUtils.isHashMatch(hashedPassword, password);
}
}
this._logger.warn('Has no password', username);
Expand Down
2 changes: 1 addition & 1 deletion Server/deploy.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ WORKDIR /usr/local/apps/citrineos

COPY . .
RUN npm run install-all && npm run build
RUN npm rebuild bcrypt --build-from-source && npm rebuild deasync --build-from-source
RUN npm rebuild deasync --build-from-source

# The final stage, which copies built files and prepares the run environment
# Using a slim image to reduce the final image size
Expand Down
2 changes: 1 addition & 1 deletion Server/local.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ WORKDIR /usr/local/apps/citrineos

COPY . .
RUN npm run install-all && npm run build
RUN npm rebuild bcrypt --build-from-source && npm rebuild deasync --build-from-source
RUN npm rebuild deasync --build-from-source

# The final stage, which copies built files and prepares the run environment
# Using a slim image to reduce the final image size
Expand Down
3 changes: 1 addition & 2 deletions Server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"license": "Apache-2.0",
"devDependencies": {
"@types/amqplib": "0.10.1",
"@types/bcrypt": "5.0.2",
"@types/deasync-promise": "1.0.0",
"@types/uuid": "9.0.1",
"@types/ws": "8.5.4",
Expand Down Expand Up @@ -62,4 +61,4 @@
"bufferutil": "4.0.8",
"utf-8-validate": "6.0.3"
}
}
}
Loading