mirror of
https://github.com/documenso/documenso.git
synced 2025-11-14 00:32:43 +10:00
chore: use rust based cms signing
This commit is contained in:
26
.env.example
26
.env.example
@ -22,10 +22,23 @@ NEXT_PRIVATE_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documen
|
|||||||
# Defines the URL to use for the database when running migrations and other commands that won't work with a connection pool.
|
# Defines the URL to use for the database when running migrations and other commands that won't work with a connection pool.
|
||||||
NEXT_PRIVATE_DIRECT_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documenso"
|
NEXT_PRIVATE_DIRECT_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documenso"
|
||||||
|
|
||||||
# [[E2E Tests]]
|
# [[SIGNING]]
|
||||||
E2E_TEST_AUTHENTICATE_USERNAME="Test User"
|
# The transport to use for document signing. Available options: local (default) | gcloud-hsm
|
||||||
E2E_TEST_AUTHENTICATE_USER_EMAIL="testuser@mail.com"
|
NEXT_PRIVATE_SIGNING_TRANSPORT="local"
|
||||||
E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_Password123"
|
# OPTIONAL: The passphrase to use for the local file-based signing transport.
|
||||||
|
NEXT_PRIVATE_SIGNING_PASSPHRASE=
|
||||||
|
# OPTIONAL: The local file path to the .p12 file to use for the local signing transport.
|
||||||
|
NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=
|
||||||
|
# OPTIONAL: The base64-encoded contents of the .p12 file to use for the local signing transport.
|
||||||
|
NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS=
|
||||||
|
# OPTIONAL: The path to the Google Cloud HSM key to use for the gcloud-hsm signing transport.
|
||||||
|
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH=
|
||||||
|
# OPTIONAL: The path to the Google Cloud HSM public certificate file to use for the gcloud-hsm signing transport.
|
||||||
|
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH=
|
||||||
|
# OPTIONAL: The base64-encoded contents of the Google Cloud HSM public certificate file to use for the gcloud-hsm signing transport.
|
||||||
|
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS=
|
||||||
|
# OPTIONAL: The path to the Google Cloud Credentials file to use for the gcloud-hsm signing transport.
|
||||||
|
NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS=
|
||||||
|
|
||||||
# [[STORAGE]]
|
# [[STORAGE]]
|
||||||
# OPTIONAL: Defines the storage transport to use. Available options: database (default) | s3
|
# OPTIONAL: Defines the storage transport to use. Available options: database (default) | s3
|
||||||
@ -90,6 +103,11 @@ NEXT_PUBLIC_FEATURE_BILLING_ENABLED=
|
|||||||
# OPTIONAL: Leave blank to allow users to signup through /signup page.
|
# OPTIONAL: Leave blank to allow users to signup through /signup page.
|
||||||
NEXT_PUBLIC_DISABLE_SIGNUP=
|
NEXT_PUBLIC_DISABLE_SIGNUP=
|
||||||
|
|
||||||
|
# [[E2E Tests]]
|
||||||
|
E2E_TEST_AUTHENTICATE_USERNAME="Test User"
|
||||||
|
E2E_TEST_AUTHENTICATE_USER_EMAIL="testuser@mail.com"
|
||||||
|
E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_Password123"
|
||||||
|
|
||||||
# This is only required for the marketing site
|
# This is only required for the marketing site
|
||||||
# [[REDIS]]
|
# [[REDIS]]
|
||||||
NEXT_PRIVATE_REDIS_URL=
|
NEXT_PRIVATE_REDIS_URL=
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const FONT_CAVEAT_BYTES = fs.readFileSync(
|
|||||||
const config = {
|
const config = {
|
||||||
experimental: {
|
experimental: {
|
||||||
outputFileTracingRoot: path.join(__dirname, '../../'),
|
outputFileTracingRoot: path.join(__dirname, '../../'),
|
||||||
serverComponentsExternalPackages: ['@node-rs/bcrypt'],
|
serverComponentsExternalPackages: ['@node-rs/bcrypt', '@documenso/pdf-sign'],
|
||||||
serverActions: {
|
serverActions: {
|
||||||
bodySizeLimit: '50mb',
|
bodySizeLimit: '50mb',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const config = {
|
|||||||
output: process.env.DOCKER_OUTPUT ? 'standalone' : undefined,
|
output: process.env.DOCKER_OUTPUT ? 'standalone' : undefined,
|
||||||
experimental: {
|
experimental: {
|
||||||
outputFileTracingRoot: path.join(__dirname, '../../'),
|
outputFileTracingRoot: path.join(__dirname, '../../'),
|
||||||
serverComponentsExternalPackages: ['@node-rs/bcrypt'],
|
serverComponentsExternalPackages: ['@node-rs/bcrypt', '@documenso/pdf-sign'],
|
||||||
serverActions: {
|
serverActions: {
|
||||||
bodySizeLimit: '50mb',
|
bodySizeLimit: '50mb',
|
||||||
},
|
},
|
||||||
|
|||||||
2129
package-lock.json
generated
2129
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -48,6 +48,7 @@
|
|||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@documenso/pdf-sign": "^0.1.0",
|
||||||
"next-runtime-env": "^3.2.0"
|
"next-runtime-env": "^3.2.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
|
|||||||
3
packages/signing/constants/byte-range.ts
Normal file
3
packages/signing/constants/byte-range.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// We use stars as a placeholder since it's easy to find and replace,
|
||||||
|
// the length of the placeholder is to support larger pdf files
|
||||||
|
export const BYTE_RANGE_PLACEHOLDER = '**********';
|
||||||
@ -1,4 +1,3 @@
|
|||||||
import signer from 'node-signpdf';
|
|
||||||
import {
|
import {
|
||||||
PDFArray,
|
PDFArray,
|
||||||
PDFDocument,
|
PDFDocument,
|
||||||
@ -9,6 +8,8 @@ import {
|
|||||||
rectangle,
|
rectangle,
|
||||||
} from 'pdf-lib';
|
} from 'pdf-lib';
|
||||||
|
|
||||||
|
import { BYTE_RANGE_PLACEHOLDER } from '../constants/byte-range';
|
||||||
|
|
||||||
export type AddSigningPlaceholderOptions = {
|
export type AddSigningPlaceholderOptions = {
|
||||||
pdf: Buffer;
|
pdf: Buffer;
|
||||||
};
|
};
|
||||||
@ -20,9 +21,9 @@ export const addSigningPlaceholder = async ({ pdf }: AddSigningPlaceholderOption
|
|||||||
const byteRange = PDFArray.withContext(doc.context);
|
const byteRange = PDFArray.withContext(doc.context);
|
||||||
|
|
||||||
byteRange.push(PDFNumber.of(0));
|
byteRange.push(PDFNumber.of(0));
|
||||||
byteRange.push(PDFName.of(signer.byteRangePlaceholder));
|
byteRange.push(PDFName.of(BYTE_RANGE_PLACEHOLDER));
|
||||||
byteRange.push(PDFName.of(signer.byteRangePlaceholder));
|
byteRange.push(PDFName.of(BYTE_RANGE_PLACEHOLDER));
|
||||||
byteRange.push(PDFName.of(signer.byteRangePlaceholder));
|
byteRange.push(PDFName.of(BYTE_RANGE_PLACEHOLDER));
|
||||||
|
|
||||||
const signature = doc.context.obj({
|
const signature = doc.context.obj({
|
||||||
Type: 'Sig',
|
Type: 'Sig',
|
||||||
72
packages/signing/helpers/update-signing-placeholder.test.ts
Normal file
72
packages/signing/helpers/update-signing-placeholder.test.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { updateSigningPlaceholder } from './update-signing-placeholder';
|
||||||
|
|
||||||
|
describe('updateSigningPlaceholder', () => {
|
||||||
|
const pdf = Buffer.from(`
|
||||||
|
20 0 obj
|
||||||
|
<<
|
||||||
|
/Type /Sig
|
||||||
|
/Filter /Adobe.PPKLite
|
||||||
|
/SubFilter /adbe.pkcs7.detached
|
||||||
|
/ByteRange [ 0 /********** /********** /********** ]
|
||||||
|
/Contents <0000000000000000000000000000000000000000000000000000000>
|
||||||
|
/Reason (Signed by Documenso)
|
||||||
|
/M (D:20210101000000Z)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
`);
|
||||||
|
|
||||||
|
it('should not throw an error', () => {
|
||||||
|
expect(() => updateSigningPlaceholder({ pdf })).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not modify the original PDF', () => {
|
||||||
|
const result = updateSigningPlaceholder({ pdf });
|
||||||
|
|
||||||
|
expect(result.pdf).not.toEqual(pdf);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a PDF with the same length as the original', () => {
|
||||||
|
const result = updateSigningPlaceholder({ pdf });
|
||||||
|
|
||||||
|
expect(result.pdf).toHaveLength(pdf.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the byte range and return it', () => {
|
||||||
|
const result = updateSigningPlaceholder({ pdf });
|
||||||
|
|
||||||
|
expect(result.byteRange).toEqual([0, 184, 241, 92]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only update the last signature in the PDF', () => {
|
||||||
|
const pdf = Buffer.from(`
|
||||||
|
20 0 obj
|
||||||
|
<<
|
||||||
|
/Type /Sig
|
||||||
|
/Filter /Adobe.PPKLite
|
||||||
|
/SubFilter /adbe.pkcs7.detached
|
||||||
|
/ByteRange [ 0 /********** /********** /********** ]
|
||||||
|
/Contents <0000000000000000000000000000000000000000000000000000000>
|
||||||
|
/Reason (Signed by Documenso)
|
||||||
|
/M (D:20210101000000Z)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
21 0 obj
|
||||||
|
<<
|
||||||
|
/Type /Sig
|
||||||
|
/Filter /Adobe.PPKLite
|
||||||
|
/SubFilter /adbe.pkcs7.detached
|
||||||
|
/ByteRange [ 0 /********** /********** /********** ]
|
||||||
|
/Contents <0000000000000000000000000000000000000000000000000000000>
|
||||||
|
/Reason (Signed by Documenso)
|
||||||
|
/M (D:20210101000000Z)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
`);
|
||||||
|
|
||||||
|
const result = updateSigningPlaceholder({ pdf });
|
||||||
|
|
||||||
|
expect(result.byteRange).toEqual([0, 512, 569, 92]);
|
||||||
|
});
|
||||||
|
});
|
||||||
39
packages/signing/helpers/update-signing-placeholder.ts
Normal file
39
packages/signing/helpers/update-signing-placeholder.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
export type UpdateSigningPlaceholderOptions = {
|
||||||
|
pdf: Buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateSigningPlaceholder = ({ pdf }: UpdateSigningPlaceholderOptions) => {
|
||||||
|
const length = pdf.length;
|
||||||
|
|
||||||
|
const byteRangePos = pdf.lastIndexOf('/ByteRange');
|
||||||
|
const byteRangeStart = pdf.indexOf('[', byteRangePos);
|
||||||
|
const byteRangeEnd = pdf.indexOf(']', byteRangePos);
|
||||||
|
|
||||||
|
const byteRangeSlice = pdf.subarray(byteRangeStart, byteRangeEnd + 1);
|
||||||
|
|
||||||
|
const signaturePos = pdf.indexOf('/Contents', byteRangeEnd);
|
||||||
|
const signatureStart = pdf.indexOf('<', signaturePos);
|
||||||
|
const signatureEnd = pdf.indexOf('>', signaturePos);
|
||||||
|
|
||||||
|
const signatureSlice = pdf.subarray(signatureStart, signatureEnd + 1);
|
||||||
|
|
||||||
|
const byteRange = [0, 0, 0, 0];
|
||||||
|
|
||||||
|
byteRange[1] = signatureStart;
|
||||||
|
byteRange[2] = byteRange[1] + signatureSlice.length;
|
||||||
|
byteRange[3] = length - byteRange[2];
|
||||||
|
|
||||||
|
const newByteRange = `[${byteRange.join(' ')}]`.padEnd(byteRangeSlice.length, ' ');
|
||||||
|
|
||||||
|
const updatedPdf = Buffer.concat([
|
||||||
|
pdf.subarray(0, byteRangeStart),
|
||||||
|
Buffer.from(newByteRange),
|
||||||
|
pdf.subarray(byteRangeEnd + 1),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (updatedPdf.length !== length) {
|
||||||
|
throw new Error('Updated PDF length does not match original length');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { pdf: updatedPdf, byteRange };
|
||||||
|
};
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
|
import { signWithGoogleCloudHSM } from './transports/google-cloud-hsm';
|
||||||
import { signWithLocalCert } from './transports/local-cert';
|
import { signWithLocalCert } from './transports/local-cert';
|
||||||
|
|
||||||
export type SignOptions = {
|
export type SignOptions = {
|
||||||
@ -11,6 +12,7 @@ export const signPdf = async ({ pdf }: SignOptions) => {
|
|||||||
|
|
||||||
return await match(transport)
|
return await match(transport)
|
||||||
.with('local', async () => signWithLocalCert({ pdf }))
|
.with('local', async () => signWithLocalCert({ pdf }))
|
||||||
|
.with('gcloud-hsm', async () => signWithGoogleCloudHSM({ pdf }))
|
||||||
.otherwise(() => {
|
.otherwise(() => {
|
||||||
throw new Error(`Unsupported signing transport: ${transport}`);
|
throw new Error(`Unsupported signing transport: ${transport}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,15 +9,15 @@
|
|||||||
"index.ts"
|
"index.ts"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/tsconfig": "*",
|
"@documenso/tsconfig": "*",
|
||||||
"node-forge": "^1.3.1",
|
"@documenso/pdf-sign": "^0.1.0",
|
||||||
"node-signpdf": "^2.0.0",
|
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"ts-pattern": "^5.0.5"
|
"ts-pattern": "^5.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node-forge": "^1.3.4"
|
"vitest": "^1.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
packages/signing/transports/google-cloud-hsm.ts
Normal file
79
packages/signing/transports/google-cloud-hsm.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
import { signWithGCloud } from '@documenso/pdf-sign';
|
||||||
|
|
||||||
|
import { addSigningPlaceholder } from '../helpers/add-signing-placeholder';
|
||||||
|
import { updateSigningPlaceholder } from '../helpers/update-signing-placeholder';
|
||||||
|
|
||||||
|
export type SignWithGoogleCloudHSMOptions = {
|
||||||
|
pdf: Buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const signWithGoogleCloudHSM = async ({ pdf }: SignWithGoogleCloudHSMOptions) => {
|
||||||
|
const keyPath = process.env.NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH;
|
||||||
|
|
||||||
|
if (!keyPath) {
|
||||||
|
throw new Error('No certificate path provided for Google Cloud HSM signing');
|
||||||
|
}
|
||||||
|
|
||||||
|
// To handle hosting in serverless environments like Vercel we can supply the base64 encoded
|
||||||
|
// application credentials as an environment variable and write it to a file if it doesn't exist
|
||||||
|
if (
|
||||||
|
process.env.GOOGLE_APPLICATION_CREDENTIALS &&
|
||||||
|
process.env.NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS
|
||||||
|
) {
|
||||||
|
if (!fs.existsSync(process.env.GOOGLE_APPLICATION_CREDENTIALS)) {
|
||||||
|
fs.writeFileSync(
|
||||||
|
process.env.GOOGLE_APPLICATION_CREDENTIALS,
|
||||||
|
Buffer.from(
|
||||||
|
process.env.NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS,
|
||||||
|
'base64',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pdf: pdfWithPlaceholder, byteRange } = updateSigningPlaceholder({
|
||||||
|
pdf: await addSigningPlaceholder({ pdf }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const pdfWithoutSignature = Buffer.concat([
|
||||||
|
pdfWithPlaceholder.subarray(0, byteRange[1]),
|
||||||
|
pdfWithPlaceholder.subarray(byteRange[2]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const signatureLength = byteRange[2] - byteRange[1];
|
||||||
|
|
||||||
|
let cert: Buffer | null = null;
|
||||||
|
|
||||||
|
if (process.env.NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS) {
|
||||||
|
cert = Buffer.from(
|
||||||
|
process.env.NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS,
|
||||||
|
'base64',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cert) {
|
||||||
|
cert = Buffer.from(
|
||||||
|
fs.readFileSync(
|
||||||
|
process.env.NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH || './example/cert.crt',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const signature = signWithGCloud({
|
||||||
|
keyPath,
|
||||||
|
cert,
|
||||||
|
content: pdfWithoutSignature,
|
||||||
|
});
|
||||||
|
|
||||||
|
const signatureAsHex = signature.toString('hex');
|
||||||
|
|
||||||
|
const signedPdf = Buffer.concat([
|
||||||
|
pdfWithPlaceholder.subarray(0, byteRange[1]),
|
||||||
|
Buffer.from(`<${signatureAsHex.padEnd(signatureLength - 2, '0')}>`),
|
||||||
|
pdfWithPlaceholder.subarray(byteRange[2]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return signedPdf;
|
||||||
|
};
|
||||||
@ -1,32 +1,51 @@
|
|||||||
import signer from 'node-signpdf';
|
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
|
||||||
import { addSigningPlaceholder } from '../helpers/addSigningPlaceholder';
|
import { signWithP12 } from '@documenso/pdf-sign';
|
||||||
|
|
||||||
|
import { addSigningPlaceholder } from '../helpers/add-signing-placeholder';
|
||||||
|
import { updateSigningPlaceholder } from '../helpers/update-signing-placeholder';
|
||||||
|
|
||||||
export type SignWithLocalCertOptions = {
|
export type SignWithLocalCertOptions = {
|
||||||
pdf: Buffer;
|
pdf: Buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const signWithLocalCert = async ({ pdf }: SignWithLocalCertOptions) => {
|
export const signWithLocalCert = async ({ pdf }: SignWithLocalCertOptions) => {
|
||||||
const pdfWithPlaceholder = await addSigningPlaceholder({ pdf });
|
const { pdf: pdfWithPlaceholder, byteRange } = updateSigningPlaceholder({
|
||||||
|
pdf: await addSigningPlaceholder({ pdf }),
|
||||||
|
});
|
||||||
|
|
||||||
let p12Cert: Buffer | null = null;
|
const pdfWithoutSignature = Buffer.concat([
|
||||||
|
pdfWithPlaceholder.subarray(0, byteRange[1]),
|
||||||
|
pdfWithPlaceholder.subarray(byteRange[2]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const signatureLength = byteRange[2] - byteRange[1];
|
||||||
|
|
||||||
|
let cert: Buffer | null = null;
|
||||||
|
|
||||||
if (process.env.NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS) {
|
if (process.env.NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS) {
|
||||||
p12Cert = Buffer.from(process.env.NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS, 'base64');
|
cert = Buffer.from(process.env.NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS, 'base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!p12Cert) {
|
if (!cert) {
|
||||||
p12Cert = Buffer.from(
|
cert = Buffer.from(
|
||||||
fs.readFileSync(process.env.NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH || './example/cert.p12'),
|
fs.readFileSync(process.env.NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH || './example/cert.p12'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NEXT_PRIVATE_SIGNING_PASSPHRASE) {
|
const signature = signWithP12({
|
||||||
return signer.sign(pdfWithPlaceholder, p12Cert, {
|
cert,
|
||||||
passphrase: process.env.NEXT_PRIVATE_SIGNING_PASSPHRASE,
|
content: pdfWithoutSignature,
|
||||||
|
password: process.env.NEXT_PRIVATE_SIGNING_PASSPHRASE || undefined,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return signer.sign(pdfWithPlaceholder, p12Cert);
|
const signatureAsHex = signature.toString('hex');
|
||||||
|
|
||||||
|
const signedPdf = Buffer.concat([
|
||||||
|
pdfWithPlaceholder.subarray(0, byteRange[1]),
|
||||||
|
Buffer.from(`<${signatureAsHex.padEnd(signatureLength - 2, '0')}>`),
|
||||||
|
pdfWithPlaceholder.subarray(byteRange[2]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return signedPdf;
|
||||||
};
|
};
|
||||||
|
|||||||
4
packages/tsconfig/process-env.d.ts
vendored
4
packages/tsconfig/process-env.d.ts
vendored
@ -30,6 +30,10 @@ declare namespace NodeJS {
|
|||||||
NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH?: string;
|
NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH?: string;
|
||||||
NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS?: string;
|
NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS?: string;
|
||||||
NEXT_PRIVATE_SIGNING_LOCAL_FILE_ENCODING?: string;
|
NEXT_PRIVATE_SIGNING_LOCAL_FILE_ENCODING?: string;
|
||||||
|
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH?: string;
|
||||||
|
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH?: string;
|
||||||
|
NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS?: string;
|
||||||
|
NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS?: string;
|
||||||
|
|
||||||
NEXT_PRIVATE_SMTP_TRANSPORT?: 'mailchannels' | 'resend' | 'smtp-auth' | 'smtp-api';
|
NEXT_PRIVATE_SMTP_TRANSPORT?: 'mailchannels' | 'resend' | 'smtp-auth' | 'smtp-api';
|
||||||
|
|
||||||
|
|||||||
@ -48,6 +48,14 @@
|
|||||||
"NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT",
|
"NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT",
|
||||||
"NEXT_PRIVATE_DATABASE_URL",
|
"NEXT_PRIVATE_DATABASE_URL",
|
||||||
"NEXT_PRIVATE_DIRECT_DATABASE_URL",
|
"NEXT_PRIVATE_DIRECT_DATABASE_URL",
|
||||||
|
"NEXT_PRIVATE_SIGNING_TRANSPORT",
|
||||||
|
"NEXT_PRIVATE_SIGNING_PASSPHRASE",
|
||||||
|
"NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH",
|
||||||
|
"NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS",
|
||||||
|
"NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH",
|
||||||
|
"NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH",
|
||||||
|
"NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS",
|
||||||
|
"NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS",
|
||||||
"NEXT_PRIVATE_GOOGLE_CLIENT_ID",
|
"NEXT_PRIVATE_GOOGLE_CLIENT_ID",
|
||||||
"NEXT_PRIVATE_GOOGLE_CLIENT_SECRET",
|
"NEXT_PRIVATE_GOOGLE_CLIENT_SECRET",
|
||||||
"NEXT_PUBLIC_UPLOAD_TRANSPORT",
|
"NEXT_PUBLIC_UPLOAD_TRANSPORT",
|
||||||
@ -93,6 +101,7 @@
|
|||||||
"DATABASE_URL_UNPOOLED",
|
"DATABASE_URL_UNPOOLED",
|
||||||
"POSTGRES_PRISMA_URL",
|
"POSTGRES_PRISMA_URL",
|
||||||
"POSTGRES_URL_NON_POOLING",
|
"POSTGRES_URL_NON_POOLING",
|
||||||
|
"GOOGLE_APPLICATION_CREDENTIALS",
|
||||||
"E2E_TEST_AUTHENTICATE_USERNAME",
|
"E2E_TEST_AUTHENTICATE_USERNAME",
|
||||||
"E2E_TEST_AUTHENTICATE_USER_EMAIL",
|
"E2E_TEST_AUTHENTICATE_USER_EMAIL",
|
||||||
"E2E_TEST_AUTHENTICATE_USER_PASSWORD"
|
"E2E_TEST_AUTHENTICATE_USER_PASSWORD"
|
||||||
|
|||||||
Reference in New Issue
Block a user