feat: implement certificate validation and expiry detection (Phase 2.1)

Add comprehensive certificate metadata parsing and expiry warnings.

## Changes

### Types (src/types.ts)
- Add CertificateInfo interface with validity dates, expiry status, issuer/subject, fingerprint

### Crypto Module (src/lib/crypto.ts)
- Add parseCertificateInfo() to extract certificate metadata
- Add isCertificateExpiringSoon() helper (default 30 days threshold)
- Calculate SHA-256 fingerprint, parse DN fields, compute days until expiry

### SealingKeysView (src/components/SealingKeysView.tsx)
- Display certificate expiry information in table
- Show visual indicators: Expired (red), Expiring Soon (warning), Valid (normal)
- Display days remaining for expiring certificates

### EncryptDialog (src/components/EncryptDialog.tsx)
- Add expiry warning before encryption
- Warn if certificate expired or expiring within 30 days
- Show specific expiry date in warning message

## Features

- **Certificate Parsing:** Extract all metadata from X.509 certificates
- **Expiry Detection:** Automatic detection of expired/expiring certificates
- **Visual Indicators:** Color-coded chips for expiry status
- **Proactive Warnings:** Alert users before creating secrets with expiring certs
- **SHA-256 Fingerprint:** Unique certificate identification

## Verification

- TypeScript: 0 errors
- Linting: 0 errors
- Build: Success (343.95 kB, 94.58 kB gzipped)

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
2026-02-11 21:30:48 -05:00
parent 2e19dd05e6
commit cc08e15f6a
4 changed files with 169 additions and 35 deletions
+66
View File
@@ -14,6 +14,7 @@
import forge from 'node-forge';
import {
Base64String,
CertificateInfo,
Err,
Ok,
PEMCertificate,
@@ -154,3 +155,68 @@ export function validateCertificate(pemCert: PEMCertificate): boolean {
const result = parsePublicKeyFromCert(pemCert);
return result.ok;
}
/**
* Parse certificate and extract metadata
*
* Extracts validity dates, issuer/subject information, and calculates
* expiration status and fingerprint.
*
* @param pemCert PEM-encoded certificate string (branded type)
* @returns Result containing certificate information or error message
*/
export function parseCertificateInfo(pemCert: PEMCertificate): Result<CertificateInfo, string> {
try {
const cert = forge.pki.certificateFromPem(pemCert);
const now = new Date();
// Extract validity dates
const validFrom = cert.validity.notBefore;
const validTo = cert.validity.notAfter;
// Calculate expiration status
const isExpired = now > validTo;
const daysUntilExpiry = Math.floor((validTo.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
// Format issuer and subject
const formatDN = (attributes: forge.pki.CertificateField[]): string => {
return attributes.map(a => `${a.shortName}=${a.value}`).join(', ');
};
const issuer = formatDN(cert.issuer.attributes);
const subject = formatDN(cert.subject.attributes);
// Calculate SHA-256 fingerprint
const der = forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes();
const md = forge.md.sha256.create();
md.update(der);
const fingerprint = md.digest().toHex().toUpperCase();
// Get serial number
const serialNumber = cert.serialNumber;
return Ok({
validFrom,
validTo,
isExpired,
daysUntilExpiry,
issuer,
subject,
fingerprint,
serialNumber,
});
} catch (error) {
return Err(`Failed to parse certificate info: ${error}`);
}
}
/**
* Check if certificate will expire soon (within threshold)
*
* @param info Certificate information
* @param daysThreshold Number of days to consider "expiring soon" (default: 30)
* @returns true if certificate will expire within threshold days
*/
export function isCertificateExpiringSoon(info: CertificateInfo, daysThreshold = 30): boolean {
return !info.isExpired && info.daysUntilExpiry <= daysThreshold;
}