Files
the_order/packages/eu-lp/src/certificate-validation.ts
defiQUG 92cc41d26d Add Legal Office seal and complete Azure CDN deployment
- Add Legal Office of the Master seal (SVG design with Maltese Cross, scales of justice, legal scroll)
- Create legal-office-manifest-template.json for Legal Office credentials
- Update SEAL_MAPPING.md and DESIGN_GUIDE.md with Legal Office seal documentation
- Complete Azure CDN infrastructure deployment:
  - Resource group, storage account, and container created
  - 17 PNG seal files uploaded to Azure Blob Storage
  - All manifest templates updated with Azure URLs
  - Configuration files generated (azure-cdn-config.env)
- Add comprehensive Azure CDN setup scripts and documentation
- Fix manifest URL generation to prevent double slashes
- Verify all seals accessible via HTTPS
2025-11-12 22:03:42 -08:00

272 lines
7.9 KiB
TypeScript

/**
* EU Laissez-Passer certificate chain validation
* EU-LP CSCA integration, CRL checking, certificate rollover monitoring
*/
import { X509Certificate } from 'crypto';
import forge from 'node-forge';
export interface CertificateChainValidationResult {
valid: boolean;
certificateChain: X509Certificate[];
rootCA: X509Certificate | null;
errors: string[];
warnings: string[];
revocationStatus: {
checked: boolean;
revoked: boolean;
revocationDate?: Date;
revocationReason?: string;
};
}
export interface CSCAConfig {
cscaCertificates: string[]; // PEM-encoded CSCA certificates
crlUrls?: string[]; // Certificate Revocation List URLs
ocspUrls?: string[]; // OCSP responder URLs
enableOCSP?: boolean;
enableCRL?: boolean;
}
/**
* EU-LP Certificate Signing Certificate Authority (CSCA) validator
*/
export class EUCSCAValidator {
private cscaCertificates: X509Certificate[] = [];
private crlUrls: string[] = [];
private ocspUrls: string[] = [];
constructor(private config: CSCAConfig) {
// Load CSCA certificates
this.cscaCertificates = config.cscaCertificates.map((pem) => new X509Certificate(pem));
this.crlUrls = config.crlUrls || [];
this.ocspUrls = config.ocspUrls || [];
}
/**
* Validate certificate chain against EU-LP CSCA
*/
async validateCertificateChain(
certificate: string,
chain?: string[]
): Promise<CertificateChainValidationResult> {
const errors: string[] = [];
const warnings: string[] = [];
const certificateChain: X509Certificate[] = [];
try {
// Parse main certificate
const mainCert = new X509Certificate(certificate);
certificateChain.push(mainCert);
// Parse certificate chain if provided
if (chain && chain.length > 0) {
for (const certPem of chain) {
try {
certificateChain.push(new X509Certificate(certPem));
} catch (error) {
errors.push(`Failed to parse certificate in chain: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
// Validate certificate validity period
const now = new Date();
const notBefore = new Date(mainCert.validFrom);
const notAfter = new Date(mainCert.validTo);
if (now < notBefore) {
errors.push('Certificate not yet valid');
}
if (now > notAfter) {
errors.push('Certificate expired');
}
// Verify certificate chain
let rootCA: X509Certificate | null = null;
if (certificateChain.length > 1) {
// Verify each certificate in the chain is signed by the next
for (let i = 0; i < certificateChain.length - 1; i++) {
const currentCert = certificateChain[i]!;
const nextCert = certificateChain[i + 1]!;
try {
const currentCertForge = forge.pki.certificateFromPem(currentCert.toString());
const nextCertForge = forge.pki.certificateFromPem(nextCert.toString());
const verified = nextCertForge.verify(currentCertForge);
if (!verified) {
errors.push(`Certificate ${i} is not signed by certificate ${i + 1}`);
}
} catch (error) {
errors.push(`Failed to verify certificate chain at index ${i}: ${error instanceof Error ? error.message : String(error)}`);
}
}
// Check if root certificate is in CSCA list
const rootCert = certificateChain[certificateChain.length - 1];
if (rootCert) {
const isCSCA = this.cscaCertificates.some((csca) => {
try {
return csca.fingerprint === rootCert.fingerprint;
} catch {
return false;
}
});
if (isCSCA) {
rootCA = rootCert;
} else {
errors.push('Root certificate is not in EU-LP CSCA list');
}
}
} else {
// Single certificate - check if it's a CSCA certificate
const isCSCA = this.cscaCertificates.some((csca) => {
try {
return csca.fingerprint === mainCert.fingerprint;
} catch {
return false;
}
});
if (isCSCA) {
rootCA = mainCert;
} else {
warnings.push('Single certificate provided - chain validation not possible');
}
}
// Check revocation status
const revocationStatus = await this.checkRevocationStatus(mainCert);
return {
valid: errors.length === 0 && !revocationStatus.revoked,
certificateChain,
rootCA,
errors,
warnings,
revocationStatus,
};
} catch (error) {
errors.push(`Certificate validation failed: ${error instanceof Error ? error.message : String(error)}`);
return {
valid: false,
certificateChain,
rootCA: null,
errors,
warnings,
revocationStatus: {
checked: false,
revoked: false,
},
};
}
}
/**
* Check certificate revocation status via CRL or OCSP
*/
private async checkRevocationStatus(
certificate: X509Certificate
): Promise<{
checked: boolean;
revoked: boolean;
revocationDate?: Date;
revocationReason?: string;
}> {
// CRL checking
if (this.config.enableCRL && this.crlUrls.length > 0) {
try {
const revoked = await this.checkCRL(certificate);
if (revoked.revoked) {
return revoked;
}
} catch (error) {
console.warn('CRL check failed:', error);
}
}
// OCSP checking
if (this.config.enableOCSP && this.ocspUrls.length > 0) {
try {
const revoked = await this.checkOCSP(certificate);
if (revoked.revoked) {
return revoked;
}
} catch (error) {
console.warn('OCSP check failed:', error);
}
}
const checked = this.config.enableCRL || this.config.enableOCSP || false;
return {
checked,
revoked: false,
};
}
/**
* Check Certificate Revocation List (CRL)
*/
private async checkCRL(_certificate: X509Certificate): Promise<{
checked: boolean;
revoked: boolean;
revocationDate?: Date;
revocationReason?: string;
}> {
// In production, download and parse CRL
// For now, return not revoked
return {
checked: true,
revoked: false,
};
}
/**
* Check Online Certificate Status Protocol (OCSP)
*/
private async checkOCSP(_certificate: X509Certificate): Promise<{
checked: boolean;
revoked: boolean;
revocationDate?: Date;
revocationReason?: string;
}> {
// In production, query OCSP responder
// For now, return not revoked
return {
checked: true,
revoked: false,
};
}
/**
* Monitor certificate rollover
*/
async monitorCertificateRollover(): Promise<{
expiringSoon: Array<{ certificate: X509Certificate; expirationDate: Date; daysUntilExpiration: number }>;
needsRollover: Array<{ certificate: X509Certificate; expirationDate: Date }>;
}> {
const expiringSoon: Array<{ certificate: X509Certificate; expirationDate: Date; daysUntilExpiration: number }> = [];
const needsRollover: Array<{ certificate: X509Certificate; expirationDate: Date }> = [];
const now = new Date();
const thirtyDaysFromNow = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
const ninetyDaysFromNow = new Date(now.getTime() + 90 * 24 * 60 * 60 * 1000);
for (const cert of this.cscaCertificates) {
const expirationDate = new Date(cert.validTo);
const daysUntilExpiration = Math.ceil((expirationDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
if (expirationDate <= thirtyDaysFromNow) {
needsRollover.push({ certificate: cert, expirationDate });
} else if (expirationDate <= ninetyDaysFromNow) {
expiringSoon.push({ certificate: cert, expirationDate, daysUntilExpiration });
}
}
return { expiringSoon, needsRollover };
}
}