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: Certificate Enhancements #300

Open
wants to merge 1 commit into
base: rc-1.6.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 5 additions & 3 deletions 00_Base/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ export {
MessageTypeId,
OcppError,
} from './ocpp/rpc/message';
export {
ChargingStationSequenceType,
} from './ocpp/model/enums/requestIds';
export { ChargingStationSequenceType } from './ocpp/model/enums/requestIds';
export { IFileAccess } from './interfaces/fileAccess';

// Persistence Interfaces
Expand Down Expand Up @@ -248,3 +246,7 @@ export { UnauthorizedError } from './interfaces/api/exception/UnauthorizedError'
export { AuthorizationSecurity } from './interfaces/api/AuthorizationSecurity';
export { Ajv };
export declare type Constructable<T> = new (...args: any[]) => T;
export {
IMessageQuerystringSchema,
IMessageQuerystring,
} from './interfaces/api/MessageQuerystring';
6 changes: 5 additions & 1 deletion 00_Base/src/ocpp/persistence/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ export enum Namespace {
BootConfig = 'Boot',
Certificate = 'Certificate',
InstalledCertificate = 'InstalledCertificate',
InstallCertificateAttempt = 'InstallCertificateAttempt',
DeleteCertificateAttempt = 'DeleteCertificateAttempt',
CertificateChain = 'CertificateChain',
UploadExistingCertificate = 'UploadExistingCertificate',
RegenerateExistingCertificate = 'RegenerateExistingCertificate',
ChargingNeeds = 'ChargingNeeds',
ChargingProfile = 'ChargingProfile',
ChargingSchedule = 'ChargingSchedule',
Expand Down Expand Up @@ -50,5 +54,5 @@ export enum Namespace {
VariableMonitoringType = 'VariableMonitoring',
VariableMonitoringStatus = 'VariableMonitoringStatus',
VariableStatus = 'VariableStatus',
VariableType = 'Variable'
VariableType = 'Variable',
}
4 changes: 4 additions & 0 deletions 01_Data/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export {
VariableStatus,
Certificate,
InstalledCertificate,
InstallCertificateAttempt,
DeleteCertificateAttempt,
CountryNameEnumType,
TransactionEvent,
IdToken,
Expand All @@ -52,6 +54,8 @@ export {
SequelizeCallMessageRepository,
SequelizeCertificateRepository,
SequelizeInstalledCertificateRepository,
SequelizeInstallCertificateAttemptRepository,
SequelizeDeleteCertificateAttemptRepository,
SequelizeChargingProfileRepository,
SequelizeChargingStationSecurityInfoRepository,
SequelizeDeviceModelRepository,
Expand Down
3 changes: 3 additions & 0 deletions 01_Data/src/interfaces/dtos/RegenerateExistingCertificate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class RegenerateExistingCertificate {
installedCertificateId!: number;
}
6 changes: 6 additions & 0 deletions 01_Data/src/interfaces/dtos/UploadExistingCertificate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { GetCertificateIdUseEnumType } from '@citrineos/base';

export class UploadExistingCertificate {
certificate!: string;
certificateType!: GetCertificateIdUseEnumType;
}
4 changes: 3 additions & 1 deletion 01_Data/src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export { TariffQueryString, TariffQuerySchema } from './queries/Tariff';
export { ModelKeyQuerystring, ModelKeyQuerystringSchema } from './queries/Model';
export { NetworkProfileQuerystring, NetworkProfileQuerySchema, NetworkProfileDeleteQuerystring, NetworkProfileDeleteQuerySchema } from './queries/NetworkProfile';
export { UpdateTlsCertificateQueryString, TlsCertificateSchema, UpdateTlsCertificateQuerySchema } from './queries/TlsCertificate';
export { GenerateCertificateChainSchema, InstallRootCertificateSchema } from './queries/RootCertificate';
export { GenerateCertificateChainSchema, InstallRootCertificateSchema, UploadExistingCertificateSchema, RegenerateInstalledCertificateSchema } from './queries/RootCertificate';
export { CreateSubscriptionSchema } from './queries/Subscription';

// Data projection models
Expand All @@ -27,3 +27,5 @@ export { default as TariffSchema } from './projections/schemas/TariffSchema.json
export { TlsCertificatesRequest } from './dtos/TlsCertificatesRequest';
export { GenerateCertificateChainRequest } from './dtos/GenerateCertificateChainRequest';
export { InstallRootCertificateRequest } from './dtos/InstallRootCertificateRequest';
export { UploadExistingCertificate } from './dtos/UploadExistingCertificate';
export { RegenerateExistingCertificate } from './dtos/RegenerateExistingCertificate';
11 changes: 11 additions & 0 deletions 01_Data/src/interfaces/queries/RootCertificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,14 @@ export const InstallRootCertificateSchema = QuerySchema(
],
['stationId', 'certificateType', 'tenantId'],
);

export const UploadExistingCertificateSchema = QuerySchema(
'UploadExistingCertificateSchema',
[
['certificate', 'string'],
['certificateType', 'string'],
],
['certificate', 'certificateType'],
);

export const RegenerateInstalledCertificateSchema = QuerySchema('RegenerateInstalledCertificateSchema', [['installedCertificateId', 'number']], ['installedCertificateId']);
12 changes: 7 additions & 5 deletions 01_Data/src/interfaces/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
ChargingStationSequence,
type Component,
CompositeSchedule,
DeleteCertificateAttempt,
type EventData,
Evse,
type Location,
Expand All @@ -73,6 +74,7 @@ import { TariffQueryString } from './queries/Tariff';
import { LocalListVersion } from '../layers/sequelize/model/Authorization/LocalListVersion';
import { SendLocalList } from '../layers/sequelize/model/Authorization/SendLocalList';
import { InstalledCertificate } from '../layers/sequelize/model/Certificate/InstalledCertificate';
import { InstallCertificateAttempt } from '../layers/sequelize/model/Certificate/InstallCertificateAttempt';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super minor: InstallCertificateAttempt can be put together with DeleteCertificateAttempt at line 55.


export interface IAuthorizationRepository extends CrudRepository<AuthorizationData> {
createOrUpdateByQuerystring: (value: AuthorizationData, query: AuthorizationQuerystring) => Promise<Authorization | undefined>;
Expand Down Expand Up @@ -194,7 +196,9 @@ export interface ICertificateRepository extends CrudRepository<Certificate> {
createOrUpdateCertificate(certificate: Certificate): Promise<Certificate>;
}

export interface IInstalledCertificateRepository extends CrudRepository<InstalledCertificate> { }
export interface IInstalledCertificateRepository extends CrudRepository<InstalledCertificate> {}
export interface IInstallCertificateAttemptRepository extends CrudRepository<InstallCertificateAttempt> {}
export interface IDeleteCertificateAttemptRepository extends CrudRepository<DeleteCertificateAttempt> {}

export interface IChargingProfileRepository extends CrudRepository<ChargingProfile> {
createOrUpdateChargingProfile(chargingProfile: ChargingProfileType, stationId: string, evseId?: number | null, chargingLimitSource?: ChargingLimitSourceEnumType, isActive?: boolean): Promise<ChargingProfile>;
Expand All @@ -210,7 +214,7 @@ export interface IReservationRepository extends CrudRepository<Reservation> {
createOrUpdateReservation(reserveNowRequest: ReserveNowRequest, stationId: string, isActive?: boolean): Promise<Reservation | undefined>;
}

export interface ICallMessageRepository extends CrudRepository<CallMessage> { }
export interface ICallMessageRepository extends CrudRepository<CallMessage> {}

export interface IChargingStationSecurityInfoRepository extends CrudRepository<ChargingStationSecurityInfo> {
readChargingStationPublicKeyFileId(stationId: string): Promise<string>;
Expand All @@ -221,6 +225,4 @@ export interface IChargingStationSequenceRepository extends CrudRepository<Charg
getNextSequenceValue(stationId: string, type: ChargingStationSequenceType): Promise<number>;
}

export interface IServerNetworkProfileRepository extends CrudRepository<ServerNetworkProfile> {

}
export interface IServerNetworkProfileRepository extends CrudRepository<ServerNetworkProfile> {}
4 changes: 3 additions & 1 deletion 01_Data/src/layers/sequelize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export { ChargingStationSequence } from './model/ChargingStationSequence';
export { MessageInfo } from './model/MessageInfo';
export { Tariff } from './model/Tariff';
export { Subscription } from './model/Subscription';
export { Certificate, SignatureAlgorithmEnumType, CountryNameEnumType, InstalledCertificate } from './model/Certificate';
export { Certificate, SignatureAlgorithmEnumType, CountryNameEnumType, InstalledCertificate, InstallCertificateAttempt, DeleteCertificateAttempt } from './model/Certificate';
export { ChargingProfile, ChargingNeeds, ChargingSchedule, CompositeSchedule, SalesTariff } from './model/ChargingProfile';
export { CallMessage } from './model/CallMessage';
export { Reservation } from './model/Reservation';
Expand All @@ -35,6 +35,8 @@ export { SequelizeTariffRepository } from './repository/Tariff';
export { SequelizeSubscriptionRepository } from './repository/Subscription';
export { SequelizeCertificateRepository } from './repository/Certificate';
export { SequelizeInstalledCertificateRepository } from './repository/InstalledCertificate';
export { SequelizeInstallCertificateAttemptRepository } from './repository/InstallCertificateAttempt';
export { SequelizeDeleteCertificateAttemptRepository } from './repository/DeleteCertificateAttempt';
export { SequelizeChargingProfileRepository } from './repository/ChargingProfile';
export { SequelizeCallMessageRepository } from './repository/CallMessage';
export { SequelizeReservationRepository } from './repository/Reservation';
Expand Down
3 changes: 3 additions & 0 deletions 01_Data/src/layers/sequelize/model/Certificate/Certificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export class Certificate extends Model {
@Column(DataType.STRING)
declare certificateFileId?: string | null;

@Column(DataType.STRING)
declare certificateFileHash?: string | null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be unique?


@Column(DataType.STRING)
declare privateKeyFileId?: string | null;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { DeleteCertificateStatusEnumType, HashAlgorithmEnumType, Namespace } from '@citrineos/base';
import { BelongsTo, Column, DataType, ForeignKey, Model, Table } from 'sequelize-typescript';
import { ChargingStation } from '../Location';

@Table
export class DeleteCertificateAttempt extends Model {
static readonly MODEL_NAME: string = Namespace.DeleteCertificateAttempt;

@ForeignKey(() => ChargingStation)
@Column({
type: DataType.STRING(36),
allowNull: false,
})
declare stationId: string;

@BelongsTo(() => ChargingStation)
station?: ChargingStation;

@Column({
type: DataType.ENUM('SHA256', 'SHA384', 'SHA512'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just using values from the enum? The same idea for enum columns in InstallCertificateAttempt and InstalledCertificate .

Suggested change
type: DataType.ENUM('SHA256', 'SHA384', 'SHA512'),
type: DataType.ENUM(...Object.values(HashAlgorithmEnumType)),

allowNull: false,
})
declare hashAlgorithm: HashAlgorithmEnumType;

@Column(DataType.STRING)
declare issuerNameHash: string;

@Column(DataType.STRING)
declare issuerKeyHash: string;

@Column(DataType.STRING)
declare serialNumber: string;

@Column({
type: DataType.ENUM('Accepted', 'Failed', 'NotFound'),
allowNull: true,
})
declare status?: DeleteCertificateStatusEnumType | null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { type CustomDataType, GetCertificateIdUseEnumType, InstallCertificateStatusEnumType, Namespace } from '@citrineos/base';
import { BelongsTo, Column, DataType, ForeignKey, Model, Table } from 'sequelize-typescript';
import { ChargingStation } from '../Location';
import { Certificate } from './Certificate';

@Table
export class InstallCertificateAttempt extends Model {
static readonly MODEL_NAME: string = Namespace.InstallCertificateAttempt;

@ForeignKey(() => ChargingStation)
@Column({
type: DataType.STRING(36),
allowNull: false,
})
declare stationId: string;

@BelongsTo(() => ChargingStation)
station?: ChargingStation;

@Column({
type: DataType.ENUM('V2GRootCertificate', 'MORootCertificate', 'CSMSRootCertificate', 'V2GCertificateChain', 'ManufacturerRootCertificate'),
allowNull: false,
})
declare certificateType: GetCertificateIdUseEnumType;

@ForeignKey(() => Certificate)
@Column(DataType.INTEGER)
declare certificateId: number;

@BelongsTo(() => Certificate)
certificate?: Certificate;

@Column({
type: DataType.ENUM('Accepted', 'Rejected', 'Failed'),
allowNull: true,
})
declare status?: InstallCertificateStatusEnumType | null;

declare customData?: CustomDataType | null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be missed to remove?

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { CertificateHashDataType, GetCertificateIdUseEnumType, HashAlgorithmEnumType, Namespace } from '@citrineos/base';
import { Column, DataType, ForeignKey, Model, Table } from 'sequelize-typescript';
import { GetCertificateIdUseEnumType, HashAlgorithmEnumType, Namespace } from '@citrineos/base';
import { BelongsTo, Column, DataType, ForeignKey, Model, Table } from 'sequelize-typescript';
import { ChargingStation } from '../Location';
import { Certificate } from './Certificate';

@Table
export class InstalledCertificate extends Model implements CertificateHashDataType {
export class InstalledCertificate extends Model {
static readonly MODEL_NAME: string = Namespace.InstalledCertificate;

@ForeignKey(() => ChargingStation)
Expand All @@ -15,31 +16,38 @@ export class InstalledCertificate extends Model implements CertificateHashDataTy

@Column({
type: DataType.STRING,
allowNull: false,
allowNull: true,
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does these column changes from required to optional?

declare hashAlgorithm: HashAlgorithmEnumType;
declare hashAlgorithm?: HashAlgorithmEnumType | undefined;

@Column({
type: DataType.STRING,
allowNull: false,
allowNull: true,
})
declare issuerNameHash: string;
declare issuerNameHash?: string | undefined;

@Column({
type: DataType.STRING,
allowNull: false,
allowNull: true,
})
declare issuerKeyHash: string;
declare issuerKeyHash?: string | undefined;

@Column({
type: DataType.STRING,
allowNull: false,
allowNull: true,
})
declare serialNumber: string;
declare serialNumber?: string | undefined;

@Column({
type: DataType.ENUM('V2GRootCertificate', 'MORootCertificate', 'CSMSRootCertificate', 'V2GCertificateChain', 'ManufacturerRootCertificate'),
allowNull: false,
})
declare certificateType: GetCertificateIdUseEnumType;

@ForeignKey(() => Certificate)
@Column(DataType.INTEGER)
declare certificateId?: number | null;

@BelongsTo(() => Certificate)
certificate!: Certificate;
}
2 changes: 2 additions & 0 deletions 01_Data/src/layers/sequelize/model/Certificate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

export { Certificate } from './Certificate';
export { InstalledCertificate } from './InstalledCertificate';
export { InstallCertificateAttempt } from './InstallCertificateAttempt';
export { DeleteCertificateAttempt } from './DeleteCertificateAttempt';

export const enum SignatureAlgorithmEnumType {
RSA = 'SHA256withRSA',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Location } from './Location';
import { StatusNotification } from './StatusNotification';
import { ChargingStationNetworkProfile } from './ChargingStationNetworkProfile';
import { SetNetworkProfile } from './SetNetworkProfile';
import { InstalledCertificate } from '../Certificate';

/**
* Represents a charging station.
Expand All @@ -31,6 +32,9 @@ export class ChargingStation extends Model {
@HasMany(() => StatusNotification)
declare statusNotifications?: StatusNotificationRequest[];

@HasMany(() => InstalledCertificate)
declare installedCertificates?: InstalledCertificate[];

/**
* The business Location of the charging station. Optional in case a charging station is not yet in the field, or retired.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache 2.0

import { SequelizeRepository } from './Base';
import { SystemConfig } from '@citrineos/base';
import { Sequelize } from 'sequelize-typescript';
import { ILogObj, Logger } from 'tslog';
import { DeleteCertificateAttempt } from '../model/Certificate';
import { IDeleteCertificateAttemptRepository } from '../../../interfaces';

export class SequelizeDeleteCertificateAttemptRepository extends SequelizeRepository<DeleteCertificateAttempt> implements IDeleteCertificateAttemptRepository {
constructor(config: SystemConfig, logger?: Logger<ILogObj>, sequelizeInstance?: Sequelize) {
super(config, DeleteCertificateAttempt.MODEL_NAME, logger, sequelizeInstance);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache 2.0

import { SequelizeRepository } from './Base';
import { IInstallCertificateAttemptRepository } from '../../../interfaces';
import { SystemConfig } from '@citrineos/base';
import { Sequelize } from 'sequelize-typescript';
import { ILogObj, Logger } from 'tslog';
import { InstallCertificateAttempt } from '../model/Certificate/InstallCertificateAttempt';

export class SequelizeInstallCertificateAttemptRepository extends SequelizeRepository<InstallCertificateAttempt> implements IInstallCertificateAttemptRepository {
constructor(config: SystemConfig, logger?: Logger<ILogObj>, sequelizeInstance?: Sequelize) {
super(config, InstallCertificateAttempt.MODEL_NAME, logger, sequelizeInstance);
}
}
Loading
Loading