File

src/crypto/key/entities/key-chain.entity.ts

Description

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.

Index

Properties

Properties

activeCertificate
Type : string
Decorators :
@IsString()
@Column('text')

Certificate for the active signing key in PEM format. Either CA-signed (if rootKey exists) or self-signed.

activeKey
Type : JWK
Decorators :
@Column('text', {transformer: EncryptedJsonTransformer})

Active signing key material (JWK). Encrypted at rest using AES-256-GCM. This is the key used for signing operations.

Optional certValidityDays
Type : number
Decorators :
@IsNumber()
@IsOptional()
@Column('int', {nullable: true})

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()
@IsOptional()
@Column('varchar', {nullable: true})

Human-readable description of the key chain.

Optional externalKeyId
Type : string
Decorators :
@IsString()
@IsOptional()
@Column('varchar', {nullable: true})

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()
@Column('varchar', {primary: true})

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()
@Column('varchar', {default: 'db'})

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()
@IsOptional()
@Column('text', {nullable: true})

Certificate for the previous signing key in PEM format.

Optional previousKey
Type : JWK
Decorators :
@Column('text', {transformer: EncryptedJsonTransformer, nullable: true})

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.

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()
@IsOptional()
@Column('text', {nullable: true})

Root CA certificate in PEM format. Self-signed certificate for the root CA key.

Optional rootKey
Type : JWK
Decorators :
@Column('text', {transformer: EncryptedJsonTransformer, nullable: true})

Root CA key material (JWK). Encrypted at rest using AES-256-GCM. Null for standalone keys that use self-signed or externally issued certificates.

rotationEnabled
Type : boolean
Decorators :
@IsBoolean()
@Column('boolean', {default: false})

Whether automatic key rotation is enabled.

Optional rotationIntervalDays
Type : number
Decorators :
@IsNumber()
@IsOptional()
@Column('int', {nullable: true})

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)
@Column('varchar', {default: 'sign'})

The usage type of the keys (sign or encrypt).

usageType
Type : KeyUsageType
Decorators :
@IsEnum(KeyUsageType)
@Column('varchar')

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;
    }
}

results matching ""

    No results matching ""