210 lines
5.8 KiB
TypeScript
210 lines
5.8 KiB
TypeScript
/** 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 unsupportedCriticalExtensions?: readonly string[];
|
|
readonly selfSigned: boolean;
|
|
readonly derBase64?: string;
|
|
readonly der?: Readonly<Record<string, unknown>>;
|
|
}
|
|
|
|
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<Record<string, unknown>>;
|
|
}
|
|
|
|
export type PluginEntry = EvidenceEntry | WarningEntry | VoteEntry;
|
|
|
|
interface EntryBase {
|
|
readonly code: string;
|
|
readonly message: string;
|
|
readonly data?: Readonly<Record<string, unknown>>;
|
|
}
|
|
|
|
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<string, unknown>>;
|
|
}
|
|
|
|
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<boolean | undefined>;
|
|
}
|
|
|
|
export interface TrustPluginHooks {
|
|
collectEvidence?(
|
|
context: EvidenceContext,
|
|
): PluginContribution | undefined | Promise<PluginContribution | undefined>;
|
|
|
|
/** 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<TrustVerdict | undefined>;
|
|
}
|
|
|
|
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<const T extends TrustPluginRegistration>(
|
|
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,
|
|
});
|
|
}
|