espace-paie-odentas/lambda-odentas-pades-sign/index.js

115 lines
4 KiB
JavaScript

import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import { KMSClient, SignCommand } from '@aws-sdk/client-kms';
import * as pades from './helpers/pades.js';
import crypto from 'node:crypto';
const region = process.env.REGION || 'eu-west-3';
const s3 = new S3Client({ region });
const kms = new KMSClient({ region });
const BUCKET = process.env.BUCKET || 'odentas-sign';
const SIGNED_PREFIX = process.env.SIGNED_PREFIX || 'signed-pades/';
const CHAIN_S3_KEY = process.env.SIGNER_CHAIN_S3_KEY || 'certs/chain.pem';
const KMS_KEY_ID = process.env.KMS_KEY_ID || '';
export const handler = async (event) => {
try {
const requestRef = event.requestRef || `REQ-${Date.now()}`;
const sourceKey = event.sourceKey || event.pdfS3Key;
if (!sourceKey) throw new Error('sourceKey manquant');
console.log('[START] requestRef:', requestRef, 'sourceKey:', sourceKey);
// 1. Télécharger PDF source
const getPdf = await s3.send(new GetObjectCommand({ Bucket: BUCKET, Key: sourceKey }));
const inputPdf = await streamToBuffer(getPdf.Body);
console.log('[PDF] Downloaded, size:', inputPdf.length, 'bytes');
// 2. Télécharger chain.pem
const getChain = await s3.send(new GetObjectCommand({ Bucket: BUCKET, Key: CHAIN_S3_KEY }));
const chainPem = await streamToBuffer(getChain.Body);
console.log('[CHAIN] Downloaded, size:', chainPem.length, 'bytes');
// 3. Préparer le PDF avec les vraies valeurs ByteRange (calculées en 2 passes)
const {
pdfWithRevision,
byteRange,
contentsPlaceholder,
signingTime
} = await pades.preparePdfWithPlaceholder(inputPdf);
console.log('[PREPARE] PDF with revision ready, size:', pdfWithRevision.length, 'bytes');
console.log('[PREPARE] Signing time:', signingTime);
console.log('[PREPARE] ByteRange:', byteRange);
// 4. Calculer le digest des SignedAttributes (ByteRange déjà correct dans le PDF)
const {
signedAttrs,
signedAttrsDigest,
pdfDigest
} = pades.buildSignedAttributesDigest(pdfWithRevision, byteRange, signingTime, chainPem.toString('utf-8'));
// 5. Signer avec KMS
if (!KMS_KEY_ID) throw new Error('KMS_KEY_ID non défini');
const signResp = await kms.send(new SignCommand({
KeyId: KMS_KEY_ID,
Message: Buffer.from(signedAttrsDigest),
MessageType: 'DIGEST',
SigningAlgorithm: 'RSASSA_PSS_SHA_256'
}));
const signatureBytes = Buffer.from(signResp.Signature);
console.log('[KMS] Signature length:', signatureBytes.length, 'bytes');
// 6. Construire le CMS SignedData
const cmsDer = await pades.buildCmsSignedData(signedAttrs, signatureBytes, chainPem);
const cmsHex = cmsDer.toString('hex');
console.log('[CMS] Built, hex length:', cmsHex.length);
// 7. Finaliser le PDF avec la signature (remplacer UNIQUEMENT /Contents)
const finalPdf = pades.finalizePdfWithCms(
pdfWithRevision,
byteRange,
cmsHex
);
const finalSha256 = crypto.createHash('sha256').update(finalPdf).digest('hex');
console.log('[FINAL] PDF size:', finalPdf.length, 'bytes, SHA256:', finalSha256);
// 8. Upload vers S3
const signedKey = `${SIGNED_PREFIX}${requestRef}.pdf`;
await s3.send(new PutObjectCommand({
Bucket: BUCKET,
Key: signedKey,
Body: finalPdf,
ContentType: 'application/pdf',
Metadata: {
requestRef,
pades: 'BES',
sha256: finalSha256,
}
}));
console.log('[SUCCESS] Signed PDF uploaded to:', signedKey);
return {
statusCode: 200,
body: JSON.stringify({
status: 'signed',
requestRef,
signed_pdf_s3_key: signedKey,
sha256: finalSha256
})
};
} catch (err) {
console.error('[ERROR]', err);
return {
statusCode: 500,
body: JSON.stringify({ error: String(err), stack: err.stack })
};
}
};
async function streamToBuffer(stream) {
const chunks = [];
for await (const chunk of stream) chunks.push(chunk);
return Buffer.concat(chunks);
}