src/crypto/key/entities/key-chain.entity.ts
KeyChainEntity represents a complete signing key setup.
This unified model encapsulates:
External consumers (e.g., issuance config) reference this entity by its id.
The embedded root CA (if present) signs the signing key's certificates.
This eliminates orphaned keys and simplifies lifecycle management.
Properties |
|
| activeCertificate |
Type : string
|
Decorators :
@IsString()
|
|
Certificate for the active signing key in PEM format. Either CA-signed (if rootKey exists) or self-signed. |
| Optional certValidityDays |
Type : number
|
Decorators :
@IsNumber()
|
|
Certificate validity in days when generating new certificates. |
| createdAt |
Type : Date
|
Decorators :
@CreateDateColumn()
|
|
The timestamp when the key chain was created. |
| Optional description |
Type : string
|
Decorators :
@IsString()
|
|
Human-readable description of the key chain. |
| Optional externalKeyId |
Type : string
|
Decorators :
@IsString()
|
|
External key identifier for cloud KMS providers. This field stores the provider-specific key reference for the active signing key. |
| id |
Type : string
|
Decorators :
@IsString()
|
|
Unique identifier for the key chain. This is the ID referenced by other entities (e.g., issuance config's signingKeyId). |
| kmsProvider |
Type : string
|
Decorators :
@IsString()
|
|
The KMS provider used for this key chain. References a configured KMS provider name. |
| Optional lastRotatedAt |
Type : Date
|
Decorators :
@Column({nullable: true})
|
|
Timestamp of when the key was last rotated. |
| Optional previousCertificate |
Type : string
|
Decorators :
@IsString()
|
|
Certificate for the previous signing key in PEM format. |
| Optional previousKeyExpiry |
Type : Date
|
Decorators :
@Column({nullable: true})
|
|
Expiry date for the previous key. After this date, the previous key should be deleted. |
| Optional rootCertificate |
Type : string
|
Decorators :
@IsString()
|
|
Root CA certificate in PEM format. Self-signed certificate for the root CA key. |
| rotationEnabled |
Type : boolean
|
Decorators :
@IsBoolean()
|
|
Whether automatic key rotation is enabled. |
| Optional rotationIntervalDays |
Type : number
|
Decorators :
@IsNumber()
|
|
Rotation interval in days. Key material will be rotated after this many days. |
| tenant |
Type : TenantEntity
|
Decorators :
@ManyToOne(undefined, {cascade: true, onDelete: 'CASCADE'})
|
|
The tenant that owns this key chain. |
| tenantId |
Type : string
|
Decorators :
@Column('varchar', {primary: true})
|
|
Tenant ID for the key chain. |
| updatedAt |
Type : Date
|
Decorators :
@UpdateDateColumn()
|
|
The timestamp when the key chain was last updated. |
| usage |
Type : KeyUsage
|
Decorators :
@IsEnum(KeyUsage)
|
|
The usage type of the keys (sign or encrypt). |
| usageType |
Type : KeyUsageType
|
Decorators :
@IsEnum(KeyUsageType)
|
|
The purpose/role of this key chain in the system. |
import {
IsBoolean,
IsEnum,
IsNumber,
IsOptional,
IsString,
} from "class-validator";
import { JWK } from "jose";
import {
Column,
CreateDateColumn,
Entity,
ManyToOne,
UpdateDateColumn,
} from "typeorm";
import { TenantEntity } from "../../../auth/tenant/entitites/tenant.entity";
import { EncryptedJsonTransformer } from "../../../shared/utils/encryption";
/**
* Key usage types for cryptographic operations.
*/
export enum KeyUsage {
Sign = "sign",
Encrypt = "encrypt",
}
/**
* Key usage types for different purposes in the system.
*/
export enum KeyUsageType {
/** Used for OAuth/OIDC access token signing and authentication */
Access = "access",
/** Used for credential/attestation signing (SD-JWT VC, mDOC, etc.) */
Attestation = "attestation",
/** Used for trust list signing */
TrustList = "trustList",
/** Used for status list (credential revocation) signing */
StatusList = "statusList",
/** Used for encryption (JWE) */
Encrypt = "encrypt",
}
/**
* KeyChainEntity represents a complete signing key setup.
*
* This unified model encapsulates:
* - An optional root CA key (for internal certificate chains)
* - An active signing key with its certificate
* - A previous key (for grace period after rotation)
* - Rotation policy
*
* External consumers (e.g., issuance config) reference this entity by its `id`.
* The embedded root CA (if present) signs the signing key's certificates.
* This eliminates orphaned keys and simplifies lifecycle management.
*/
@Entity("key_chain")
export class KeyChainEntity {
/**
* Unique identifier for the key chain.
* This is the ID referenced by other entities (e.g., issuance config's signingKeyId).
*/
@IsString()
@Column("varchar", { primary: true })
id!: string;
/**
* Tenant ID for the key chain.
*/
@Column("varchar", { primary: true })
tenantId!: string;
/**
* The tenant that owns this key chain.
*/
@ManyToOne(() => TenantEntity, { cascade: true, onDelete: "CASCADE" })
tenant!: TenantEntity;
/**
* Human-readable description of the key chain.
*/
@IsString()
@IsOptional()
@Column("varchar", { nullable: true })
description?: string;
/**
* The purpose/role of this key chain in the system.
*/
@IsEnum(KeyUsageType)
@Column("varchar")
usageType!: KeyUsageType;
/**
* The usage type of the keys (sign or encrypt).
*/
@IsEnum(KeyUsage)
@Column("varchar", { default: "sign" })
usage!: KeyUsage;
/**
* The KMS provider used for this key chain.
* References a configured KMS provider name.
*/
@IsString()
@Column("varchar", { default: "db" })
kmsProvider!: string;
/**
* External key identifier for cloud KMS providers.
* This field stores the provider-specific key reference for the active signing key.
*/
@IsString()
@IsOptional()
@Column("varchar", { nullable: true })
externalKeyId?: string;
// ─────────────────────────────────────────────────────────
// ROOT CA (optional - for internal certificate chains)
// ─────────────────────────────────────────────────────────
/**
* Root CA key material (JWK).
* Encrypted at rest using AES-256-GCM.
* Null for standalone keys that use self-signed or externally issued certificates.
*/
@Column("text", {
transformer: EncryptedJsonTransformer,
nullable: true,
})
rootKey?: JWK;
/**
* Root CA certificate in PEM format.
* Self-signed certificate for the root CA key.
*/
@IsString()
@IsOptional()
@Column("text", { nullable: true })
rootCertificate?: string;
// ─────────────────────────────────────────────────────────
// ACTIVE SIGNING KEY
// ─────────────────────────────────────────────────────────
/**
* Active signing key material (JWK).
* Encrypted at rest using AES-256-GCM.
* This is the key used for signing operations.
*/
@Column("text", { transformer: EncryptedJsonTransformer })
activeKey!: JWK;
/**
* Certificate for the active signing key in PEM format.
* Either CA-signed (if rootKey exists) or self-signed.
*/
@IsString()
@Column("text")
activeCertificate!: string;
// ─────────────────────────────────────────────────────────
// ROTATION
// ─────────────────────────────────────────────────────────
/**
* Whether automatic key rotation is enabled.
*/
@IsBoolean()
@Column("boolean", { default: false })
rotationEnabled!: boolean;
/**
* Rotation interval in days. Key material will be rotated after this many days.
*/
@IsNumber()
@IsOptional()
@Column("int", { nullable: true })
rotationIntervalDays?: number;
/**
* Certificate validity in days when generating new certificates.
*/
@IsNumber()
@IsOptional()
@Column("int", { nullable: true })
certValidityDays?: number;
/**
* Timestamp of when the key was last rotated.
*/
@Column({ nullable: true })
lastRotatedAt?: Date;
// ─────────────────────────────────────────────────────────
// PREVIOUS KEY (grace period after rotation)
// ─────────────────────────────────────────────────────────
/**
* Previous signing key material (JWK).
* Encrypted at rest using AES-256-GCM.
* Kept for a grace period after rotation to allow validation of existing signatures.
*/
@Column("text", {
transformer: EncryptedJsonTransformer,
nullable: true,
})
previousKey?: JWK;
/**
* Certificate for the previous signing key in PEM format.
*/
@IsString()
@IsOptional()
@Column("text", { nullable: true })
previousCertificate?: string;
/**
* Expiry date for the previous key.
* After this date, the previous key should be deleted.
*/
@Column({ nullable: true })
previousKeyExpiry?: Date;
// ─────────────────────────────────────────────────────────
// TIMESTAMPS
// ─────────────────────────────────────────────────────────
/**
* The timestamp when the key chain was created.
*/
@CreateDateColumn()
createdAt!: Date;
/**
* The timestamp when the key chain was last updated.
*/
@UpdateDateColumn()
updatedAt!: Date;
// ─────────────────────────────────────────────────────────
// HELPER METHODS
// ─────────────────────────────────────────────────────────
/**
* Returns true if this key chain has an internal CA (root key).
*/
hasInternalCa(): boolean {
return this.rootKey != null && this.rootCertificate != null;
}
/**
* Returns all public keys (current and previous) for JWKS.
*/
getPublicKeys(): JWK[] {
const keys: JWK[] = [];
if (this.activeKey) {
keys.push(this.getPublicJwk(this.activeKey));
}
if (this.previousKey) {
keys.push(this.getPublicJwk(this.previousKey));
}
return keys;
}
private getPublicJwk(jwk: JWK): JWK {
// Remove private key components
const { d, p, q, dp, dq, qi, k, ...publicJwk } = jwk as Record<
string,
unknown
>;
return publicJwk as JWK;
}
}