/** Version of the first TLS-only trust-plugin API. */ export const TRUST_API_VERSION = "0.1" as const; export type TrustApiVersion = typeof TRUST_API_VERSION; export type PluginMode = | "observer" | "advisor" | "decision-authority" | "veto-authority"; export type TlsValidation = "success" | "failure"; export interface CertificateFacts { readonly subject: string; readonly issuer?: string; readonly sha256: string; readonly spkiSha256?: string; readonly serialNumber?: string; readonly dnsNames?: readonly string[]; readonly validFrom?: string; readonly validUntil?: string; readonly isCa: boolean; readonly keyUsages: readonly CertificateKeyUsage[]; readonly selfSigned: boolean; } export type CertificateKeyUsage = | "digitalSignature" | "keyEncipherment" | "keyCertSign" | "crlSign"; export interface TlsFailure { readonly code: string; readonly certificateSha256?: string; readonly check: "identity" | "validity" | "signature" | "trust-anchor" | "local-policy"; readonly summary: string; } export interface TlsFacts { readonly schemaVersion: 0; readonly connectionId: string; readonly hostname: string; readonly port: number; readonly validation: TlsValidation; readonly errors: readonly string[]; readonly failure?: TlsFailure; readonly presentedChain: readonly CertificateFacts[]; readonly constructedChain: readonly CertificateFacts[]; readonly tls: Readonly>; } export type PluginEntry = EvidenceEntry | WarningEntry | VoteEntry; interface EntryBase { readonly code: string; readonly message: string; readonly data?: Readonly>; } export interface EvidenceEntry extends EntryBase { readonly kind: "evidence"; } export interface WarningEntry extends EntryBase { readonly kind: "warning"; } export interface VoteEntry extends EntryBase { readonly kind: "vote"; readonly data: Readonly<{ trusted: boolean } & Record>; } export interface JournalEntry extends EntryBase { readonly id: string; readonly pluginId: string; readonly pluginName: string; readonly kind: PluginEntry["kind"] | "resolution" | "error" | "abstention"; } export interface ReadonlyDecisionJournal { readonly entries: readonly JournalEntry[]; } export interface PluginContribution { readonly entries: readonly PluginEntry[]; } export type TrustScope = CertificateForHostScope | AuthorityForHostScope; export interface CertificateForHostScope { readonly kind: "certificate-for-host"; readonly hostname: string; readonly port: number; readonly certificateSha256: string; } export interface AuthorityForHostScope { readonly kind: "authority-for-host"; readonly hostname: string; readonly port: number | "any"; readonly authorityCertificateSha256: string; readonly includeSubdomains: boolean; } export type TrustLifetime = | { readonly kind: "connection" } | { readonly kind: "session" } | { readonly kind: "until"; readonly expiresAt: string } | { readonly kind: "persistent" }; export interface TrustedVerdict { readonly trusted: true; readonly scope: TrustScope; readonly lifetime: TrustLifetime; readonly reasonEntryIds: readonly string[]; readonly overriddenErrors: readonly string[]; } export interface NotTrustedVerdict { readonly trusted: false; readonly scope: TrustScope; readonly lifetime: TrustLifetime; readonly reasonEntryIds: readonly string[]; readonly overriddenErrors: readonly []; } export type TrustVerdict = TrustedVerdict | NotTrustedVerdict; export interface EvidenceContext { readonly facts: TlsFacts; readonly signal: AbortSignal; } export interface DecisionContext { readonly facts: TlsFacts; readonly journal: ReadonlyDecisionJournal; } export interface FailureDecisionContext extends DecisionContext { readonly ui: TrustDecisionUi; readonly signal: AbortSignal; } export interface TrustDecisionRequest { readonly title: string; readonly summary: string; readonly proposedScope: TrustScope; readonly evidenceEntryIds: readonly string[]; } export interface TrustDecisionUi { requestDecision(request: TrustDecisionRequest): Promise; } export interface TrustPluginHooks { collectEvidence?( context: EvidenceContext, ): PluginContribution | undefined | Promise; /** Fast and deliberately synchronous. Network and UI are unavailable. */ onBeforeTlsAccept?(context: DecisionContext): TrustVerdict | undefined; /** Failed navigation is investigated asynchronously before a fresh retry. */ onTlsFailure?( context: FailureDecisionContext, ): Promise; } export interface TrustPluginManifest { readonly manifestVersion: 1; readonly trustApiVersion: TrustApiVersion; readonly id: string; readonly name: string; readonly version: string; readonly supportedModes: readonly PluginMode[]; readonly capabilities: { readonly interactiveUi?: boolean; }; } export interface TrustPluginRegistration { readonly manifest: TrustPluginManifest; readonly hooks: TrustPluginHooks; } /** * Defines the module's single registration object. Browsec still validates the * installed package manifest and local capability grants before activation. */ export function defineTrustPlugin( registration: T, ): T { return Object.freeze(registration); } export function exactCertificateScope(facts: TlsFacts): CertificateForHostScope { const leaf = facts.presentedChain[0]; if (!leaf) throw new TypeError("TLS facts do not contain a leaf certificate"); return Object.freeze({ kind: "certificate-for-host", hostname: facts.hostname, port: facts.port, certificateSha256: leaf.sha256, }); }