From d1e6b3157eb8f03d32d284a4a4500dabd00aab03 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Mon, 28 Oct 2024 14:10:00 +0100 Subject: [PATCH] feat: key pinning --- packages/transport/src/core/Transport.ts | 25 +++++++++++++++++++ .../transport/src/core/TransportCoreErrors.ts | 6 +++++ 2 files changed, 31 insertions(+) diff --git a/packages/transport/src/core/Transport.ts b/packages/transport/src/core/Transport.ts index f37b49a8d..d37839587 100644 --- a/packages/transport/src/core/Transport.ts +++ b/packages/transport/src/core/Transport.ts @@ -6,6 +6,7 @@ import { SodiumWrapper } from "@nmshd/crypto"; import { AgentOptions } from "http"; import { AgentOptions as HTTPSAgentOptions } from "https"; import _ from "lodash"; +import { PeerCertificate, checkServerIdentity } from "tls"; import { ICorrelator } from "./ICorrelator"; import { TransportCoreErrors } from "./TransportCoreErrors"; import { TransportError } from "./TransportError"; @@ -31,6 +32,7 @@ export interface IConfig { datawalletEnabled: boolean; httpAgentOptions: AgentOptions; httpsAgentOptions: HTTPSAgentOptions; + pinnedPublicKeys?: Record; } export interface IConfigOverwrite { @@ -48,6 +50,7 @@ export interface IConfigOverwrite { datawalletEnabled?: boolean; httpAgentOptions?: AgentOptions; httpsAgentOptions?: HTTPSAgentOptions; + pinnedPublicKeys?: Record; } export class Transport { @@ -89,9 +92,31 @@ export class Transport { loggerFactory: ILoggerFactory = new SimpleLoggerFactory(), public readonly correlator?: ICorrelator ) { + if (customConfig.pinnedPublicKeys && customConfig.httpsAgentOptions?.checkServerIdentity) { + throw TransportCoreErrors.general.conflictingServerIdentityChecks().logWith(log); + } + this.databaseConnection = databaseConnection; this._config = _.defaultsDeep({}, customConfig, Transport.defaultConfig); + if (this._config.pinnedPublicKeys) { + this._config.httpsAgentOptions = { + ...this._config.httpsAgentOptions, + checkServerIdentity: (host: string, certificate: PeerCertificate) => { + const error = checkServerIdentity(host, certificate); + if (error) { + return error; + } + + const subject = certificate.subject.CN; + if (!(subject in this._config.pinnedPublicKeys!)) return; + if (this._config.pinnedPublicKeys![subject].includes(certificate.pubkey!.toString("base64"))) return; + + return new Error(`Certificate verification error: The public key of ${certificate.subject.CN} doesn't match a pinned public key`); + } + }; + } + TransportLoggerFactory.init(loggerFactory); log = TransportLoggerFactory.getLogger(Transport); diff --git a/packages/transport/src/core/TransportCoreErrors.ts b/packages/transport/src/core/TransportCoreErrors.ts index e866bb1e1..5520a786b 100644 --- a/packages/transport/src/core/TransportCoreErrors.ts +++ b/packages/transport/src/core/TransportCoreErrors.ts @@ -160,6 +160,12 @@ class Tokens { } class General { + public conflictingServerIdentityChecks() { + return new CoreError( + "error.transport.general.conflictingServerIdentityChecks", + "Both httpsAgentOptions.checkServerIdentity and pinnedPublicKeys were configured. Because pinnedPublicKeys works by setting checkServerIdentity, you may only use one of them." + ); + } public baseUrlNotSet() { return new CoreError("error.transport.general.baseUrlNotSet", "The baseUrl was not set."); }