mirror of
https://github.com/documenso/documenso.git
synced 2026-09-29 08:14:31 +10:00
fix: default unset signing transport to local (#3309)
`/api/health` and `/api/certificate-status` reported the cert as available when `NEXT_PRIVATE_SIGNING_TRANSPORT` was unset, even though sealing defaults to the local P12 and fails if it is missing, unreadable, or expired.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { getCertificateStatus } from '@documenso/lib/server-only/cert/cert-status';
|
||||
|
||||
export const loader = () => {
|
||||
export const loader = async () => {
|
||||
try {
|
||||
const certStatus = getCertificateStatus();
|
||||
const certStatus = await getCertificateStatus();
|
||||
|
||||
return Response.json({
|
||||
isAvailable: certStatus.isAvailable,
|
||||
|
||||
@@ -22,7 +22,7 @@ export const loader = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const certStatus = getCertificateStatus();
|
||||
const certStatus = await getCertificateStatus();
|
||||
|
||||
if (certStatus.isAvailable) {
|
||||
checks.certificate = { status: 'ok' };
|
||||
|
||||
@@ -93,6 +93,8 @@ export const NEXT_PRIVATE_USE_PLAYWRIGHT_PDF = () => env('NEXT_PRIVATE_USE_PLAYW
|
||||
|
||||
export const NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY = () => env('NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY');
|
||||
|
||||
export const NEXT_PRIVATE_SIGNING_TRANSPORT = () => env('NEXT_PRIVATE_SIGNING_TRANSPORT') || 'local';
|
||||
|
||||
/**
|
||||
* Whether this Documenso instance is running in CSC (Cloud Signature Consortium) mode.
|
||||
*
|
||||
|
||||
@@ -1,26 +1,36 @@
|
||||
import * as fs from 'node:fs';
|
||||
import { X509Certificate } from 'node:crypto';
|
||||
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
import { createLocalSigner } from '@documenso/signing/transports/local';
|
||||
|
||||
export const getCertificateStatus = () => {
|
||||
if (env('NEXT_PRIVATE_SIGNING_TRANSPORT') !== 'local') {
|
||||
import { NEXT_PRIVATE_SIGNING_TRANSPORT } from '../../constants/app';
|
||||
|
||||
/**
|
||||
* Whether the local P12 opens with the configured passphrase and is in date.
|
||||
* Skips AIA so this stays offline. gcloud-hsm and csc always report available.
|
||||
*/
|
||||
export const getCertificateStatus = async () => {
|
||||
const transport = NEXT_PRIVATE_SIGNING_TRANSPORT();
|
||||
|
||||
// Cannot inspect a remote HSM or CSC provider from this process.
|
||||
if (transport === 'gcloud-hsm' || transport === 'csc') {
|
||||
return { isAvailable: true };
|
||||
}
|
||||
|
||||
if (env('NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS')) {
|
||||
return { isAvailable: true };
|
||||
// Anything else (typo, leftover `http`) would throw at seal time.
|
||||
if (transport !== 'local') {
|
||||
return { isAvailable: false };
|
||||
}
|
||||
|
||||
const defaultPath = env('NODE_ENV') === 'production' ? '/opt/documenso/cert.p12' : './example/cert.p12';
|
||||
|
||||
const filePath = env('NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH') || defaultPath;
|
||||
|
||||
try {
|
||||
fs.accessSync(filePath, fs.constants.F_OK | fs.constants.R_OK);
|
||||
const signer = await createLocalSigner({ buildChain: false });
|
||||
|
||||
const stats = fs.statSync(filePath);
|
||||
const certificate = new X509Certificate(Buffer.from(signer.certificate));
|
||||
|
||||
return { isAvailable: stats.size > 0 };
|
||||
const now = new Date();
|
||||
|
||||
const isWithinValidityPeriod = new Date(certificate.validFrom) <= now && now <= new Date(certificate.validTo);
|
||||
|
||||
return { isAvailable: isWithinValidityPeriod };
|
||||
} catch {
|
||||
return { isAvailable: false };
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
NEXT_PRIVATE_SIGNING_TRANSPORT,
|
||||
NEXT_PRIVATE_USE_LEGACY_SIGNING_SUBFILTER,
|
||||
NEXT_PUBLIC_SIGNING_CONTACT_INFO,
|
||||
NEXT_PUBLIC_WEBAPP_URL,
|
||||
} from '@documenso/lib/constants/app';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
import type { PDF, Signer } from '@libpdf/core';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
@@ -22,7 +22,7 @@ const getSigner = async () => {
|
||||
return signer;
|
||||
}
|
||||
|
||||
const transport = env('NEXT_PRIVATE_SIGNING_TRANSPORT') || 'local';
|
||||
const transport = NEXT_PRIVATE_SIGNING_TRANSPORT();
|
||||
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
signer = await match(transport)
|
||||
|
||||
@@ -22,10 +22,20 @@ const loadP12 = (): Uint8Array => {
|
||||
throw new Error('No certificate found for local signing');
|
||||
};
|
||||
|
||||
export const createLocalSigner = async () => {
|
||||
export type CreateLocalSignerOptions = {
|
||||
/**
|
||||
* Fetch missing intermediates via AIA. Leave on for sealing.
|
||||
* Turn off for health checks so they do not hit the network.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
buildChain?: boolean;
|
||||
};
|
||||
|
||||
export const createLocalSigner = async ({ buildChain = true }: CreateLocalSignerOptions = {}) => {
|
||||
const p12 = loadP12();
|
||||
|
||||
return await P12Signer.create(p12, env('NEXT_PRIVATE_SIGNING_PASSPHRASE') || '', {
|
||||
buildChain: true,
|
||||
buildChain,
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user