Initial commit: CertTools SSL certificate toolkit

Made-with: Cursor
This commit is contained in:
Denis
2026-03-26 18:12:39 +03:00
commit b2f8cbdb0e
34 changed files with 6975 additions and 0 deletions

View File

@@ -0,0 +1,191 @@
import { useState } from 'react';
import { ChevronDown, ChevronRight, ShieldCheck, ShieldAlert, Clock, Building2, Globe } from 'lucide-react';
import { CopyButton } from './CopyButton';
import type { CertificateInfo as CertInfo } from '../types';
interface CertificateInfoProps {
cert: CertInfo;
index?: number;
defaultExpanded?: boolean;
}
function formatDate(iso: string): string {
return new Date(iso).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
}
function InfoRow({ label, value, mono }: { label: string; value: string | number; mono?: boolean }) {
return (
<div className="flex flex-col sm:flex-row sm:items-start gap-1 sm:gap-3 py-2 border-b border-slate-100 dark:border-slate-700/40 last:border-0">
<span className="text-xs font-medium text-slate-500 dark:text-slate-400 sm:w-40 shrink-0 uppercase tracking-wide">
{label}
</span>
<span className={`text-sm text-slate-800 dark:text-slate-200 break-all ${mono ? 'font-mono text-xs' : ''}`}>
{value}
</span>
</div>
);
}
export function CertificateInfoCard({ cert, index, defaultExpanded = true }: CertificateInfoProps) {
const [expanded, setExpanded] = useState(defaultExpanded);
const [showPem, setShowPem] = useState(false);
const roleLabel = !cert.isCA
? 'End Entity'
: cert.isSelfSigned
? 'Root CA'
: 'Intermediate CA';
const roleBadge = !cert.isCA ? 'badge-blue' : cert.isSelfSigned ? 'badge-amber' : 'badge-blue';
return (
<div className="card overflow-hidden">
<button
onClick={() => setExpanded(!expanded)}
className="w-full flex items-center gap-3 p-4 hover:bg-slate-50 dark:hover:bg-slate-700/30 transition-colors text-left"
>
{expanded ? (
<ChevronDown className="w-4 h-4 text-slate-400 shrink-0" />
) : (
<ChevronRight className="w-4 h-4 text-slate-400 shrink-0" />
)}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
{index !== undefined && (
<span className="text-xs font-mono text-slate-400">#{index + 1}</span>
)}
<h3 className="text-sm font-semibold text-slate-900 dark:text-white truncate">
{cert.subject.CN || 'Unknown CN'}
</h3>
<span className={roleBadge}>{roleLabel}</span>
{cert.isExpired ? (
<span className="badge-red">
<ShieldAlert className="w-3 h-3" /> Expired
</span>
) : cert.daysRemaining <= 30 ? (
<span className="badge-amber">
<Clock className="w-3 h-3" /> {cert.daysRemaining}d left
</span>
) : (
<span className="badge-green">
<ShieldCheck className="w-3 h-3" /> Valid
</span>
)}
</div>
{!expanded && (
<p className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">
Issued by {cert.issuer.CN || cert.issuer.O || 'Unknown'} &middot; Expires {formatDate(cert.validTo)}
</p>
)}
</div>
</button>
{expanded && (
<div className="border-t px-4 pb-4">
{/* Subject */}
<div className="mt-3">
<h4 className="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-1 flex items-center gap-1.5">
<Globe className="w-3.5 h-3.5" /> Subject
</h4>
<div className="bg-slate-50 dark:bg-slate-900/40 rounded-lg p-3">
{Object.entries(cert.subject).map(([key, val]) => (
<InfoRow key={key} label={key} value={val} />
))}
</div>
</div>
{/* Issuer */}
<div className="mt-3">
<h4 className="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-1 flex items-center gap-1.5">
<Building2 className="w-3.5 h-3.5" /> Issuer
</h4>
<div className="bg-slate-50 dark:bg-slate-900/40 rounded-lg p-3">
{Object.entries(cert.issuer).map(([key, val]) => (
<InfoRow key={key} label={key} value={val} />
))}
</div>
</div>
{/* Validity */}
<div className="mt-3 bg-slate-50 dark:bg-slate-900/40 rounded-lg p-3">
<InfoRow label="Valid From" value={formatDate(cert.validFrom)} />
<InfoRow label="Valid To" value={formatDate(cert.validTo)} />
<InfoRow
label="Days Remaining"
value={cert.isExpired ? `Expired ${Math.abs(cert.daysRemaining)} days ago` : `${cert.daysRemaining} days`}
/>
</div>
{/* SANs */}
{cert.sans.length > 0 && (
<div className="mt-3">
<h4 className="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-1">
Subject Alternative Names ({cert.sans.length})
</h4>
<div className="flex flex-wrap gap-1.5">
{cert.sans.map((san, i) => (
<span key={i} className="px-2 py-1 bg-slate-100 dark:bg-slate-700/50 rounded text-xs font-mono text-slate-700 dark:text-slate-300">
{san}
</span>
))}
</div>
</div>
)}
{/* Technical Details */}
<div className="mt-3 bg-slate-50 dark:bg-slate-900/40 rounded-lg p-3">
<InfoRow label="Serial Number" value={cert.serialNumber} mono />
<InfoRow label="Version" value={`v${cert.version}`} />
<InfoRow label="Signature" value={cert.signatureAlgorithm} />
<InfoRow label="Public Key" value={`${cert.publicKey.algorithm} ${cert.publicKey.bits} bits`} />
<InfoRow label="Self-signed" value={cert.isSelfSigned ? 'Yes' : 'No'} />
</div>
{/* Key Usage */}
{(cert.keyUsage.length > 0 || cert.extKeyUsage.length > 0) && (
<div className="mt-3 bg-slate-50 dark:bg-slate-900/40 rounded-lg p-3">
{cert.keyUsage.length > 0 && (
<InfoRow label="Key Usage" value={cert.keyUsage.join(', ')} />
)}
{cert.extKeyUsage.length > 0 && (
<InfoRow label="Ext Key Usage" value={cert.extKeyUsage.join(', ')} />
)}
</div>
)}
{/* Fingerprints */}
<div className="mt-3 bg-slate-50 dark:bg-slate-900/40 rounded-lg p-3">
<InfoRow label="SHA-256" value={cert.fingerprints.sha256} mono />
<InfoRow label="SHA-1" value={cert.fingerprints.sha1} mono />
</div>
{/* PEM */}
<div className="mt-3">
<button
onClick={() => setShowPem(!showPem)}
className="text-xs font-medium text-blue-600 dark:text-blue-400 hover:underline"
>
{showPem ? 'Hide' : 'Show'} PEM
</button>
{showPem && (
<div className="mt-2 relative">
<pre className="bg-slate-900 dark:bg-slate-950 text-green-400 p-4 rounded-lg text-xs overflow-x-auto whitespace-pre-wrap break-all">
{cert.pem}
</pre>
<div className="absolute top-2 right-2">
<CopyButton text={cert.pem} />
</div>
</div>
)}
</div>
</div>
)}
</div>
);
}