Skip to content

[Example]: Sending email with digital certificate #7239

Open
@FabioEduardoBatista

Description

@FabioEduardoBatista

Background story

Could you provide an example that inserts a s/mime digital certificate through a pfx, pem, p12 file that digitally signs the body of the email, ensuring that upon delivery the email client recognizes it as valid and trustworthy?

The difficulty is in understanding why when adding the body of the email the result becomes invalid.
The code below is an example that is working but without the body of the email.

What does this example accomplish?

The expected result is that the email client displays the signer and also the icon that guarantees that the digital certificate is valid and trustworthy.
The same process can be done by adding the certificate to Outlook and shooting through it.

Image

Which AWS service(s)?

AWS SES

Which AWS SDKs or tools?

  • All languages
  • .NET
  • C++
  • Go (v2)
  • Java
  • Java (v2)
  • JavaScript
  • JavaScript (v3)
  • Kotlin
  • PHP
  • Python
  • Ruby
  • Rust
  • Swift
  • Not applicable

Are there existing code examples to leverage?

No response

Do you have any reference code?

const fs = require("fs");
const dotenv = require("dotenv");
const { SESClient, SendRawEmailCommand } = require("@aws-sdk/client-ses");
const forge = require("node-forge");
const { execSync } = require("child_process");

dotenv.config();

// 🔥 Configuração do SES AWS
const sesClient = new SESClient({
    region: process.env.AWS_REGION,
    credentials: {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
    },
});

// 🔥 Função para assinar e-mail corretamente
async function signEmail(emailContent, p12Buffer, password) {
    try {
        // 🔹 Garantir que todas as quebras de linha sejam CRLF (\r\n)
        emailContent = emailContent.replace(/\r?\n/g, "\r\n");

        // 🔹 Carregar certificado P12
        const p12Asn1 = forge.asn1.fromDer(forge.util.createBuffer(p12Buffer).getBytes());
        const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, password);

        // 🔹 Extração da chave privada e certificados
        let privateKey, certificates = [];
        for (const safeContent of p12.safeContents) {
            for (const safeBag of safeContent.safeBags) {
                if (safeBag.type === forge.pki.oids.certBag) {
                    certificates.push(safeBag.cert);
                } else if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
                    privateKey = safeBag.key;
                }
            }
        }

        // Verificação da extração
        if (!privateKey || certificates.length === 0) {
            console.error("🚨 Chave privada ou certificado não encontrados no PFX.");
            return null;
        }

        // 🔹 Criar assinatura PKCS#7 (detached)
        const p7 = forge.pkcs7.createSignedData();
        p7.content = forge.util.createBuffer(emailContent, "utf8"); // 🔥 Assina apenas o corpo

        p7.addCertificate(certificates[0]); // Certificado principal
        for (let i = 1; i < certificates.length; i++) {
            p7.addCertificate(certificates[i]); // Certificados intermediários
        }
        p7.addSigner({
            key: privateKey,
            certificate: certificates[0],
            digestAlgorithm: forge.pki.oids.sha256,
            authenticatedAttributes: [
                { type: forge.pki.oids.contentType, value: forge.pki.oids.data },
                { type: forge.pki.oids.messageDigest },
                { type: forge.pki.oids.signingTime, value: new Date() },
            ],
        });

        p7.sign();
        p7.detached = true; // 🔥 Garantindo assinatura separada

        // 🔹 Gerar assinatura DER sem modificações
        const signatureDER = forge.asn1.toDer(p7.toAsn1()).getBytes();
        const signatureBase64 = Buffer.from(signatureDER, "binary").toString("base64");

        // 🔹 Criar e-mail multipart S/MIME assinado corretamente
        const signedEmail =
            "From: [email protected]\r\n" +
            "To: [email protected]\r\n" +
            "Subject: E-mail Assinado\r\n" +
            "MIME-Version: 1.0\r\n" +
            "Content-Type: application/pkcs7-mime; smime-type=signed-data; name=\"smime.p7m\"\r\n" +
            "Content-Disposition: attachment; filename=\"smime.p7m\"\r\n" +
            "Content-Transfer-Encoding: base64\r\n\r\n" +
            signatureBase64.match(/.{1,64}/g).join("\r\n") + "\r\n";

        return {
            signedEmail,
            signatureDER,
            certificate: certificates.length > 0 ? certificates[0] : null, // 🔥 Verificação de segurança
        };
    } catch (error) {
        console.error("🚨 Erro ao assinar o e-mail:", error);
        return null;
    }
}

// 🔥 Função para verificar a assinatura PKCS#7 com OpenSSL
function verifySignature(emailContent, signatureDER) {
    try {
        // 🔹 Salva o conteúdo original e a assinatura em arquivos temporários
        fs.writeFileSync("email.txt", emailContent, "utf8");
        fs.writeFileSync("signature.p7s", Buffer.from(signatureDER, "binary"));

        // 🔹 Comando OpenSSL para validar a assinatura
        const command = `openssl smime -verify -in signature.p7s -inform DER -content email.txt -noverify`;
        const output = execSync(command).toString();

        // 🔹 Se não houver erro, a assinatura é válida
        console.log("✅ Assinatura válida:", output);
        return true;
    } catch (error) {
        console.error("❌ Assinatura inválida:", error.toString());
        return false;
    }
}

// 🔥 Função para enviar o e-mail assinado via AWS SES
async function sendSignedEmail(signedEmail) {
    try {
        const params = {
            RawMessage: { Data: signedEmail },
            Source: "email do remetente",
            Destinations: ["email do destinatario"],
        };
        const command = new SendRawEmailCommand(params);
        const response = await sesClient.send(command);
        console.log("✅ E-mail assinado enviado com sucesso!", response);
    } catch (error) {
        console.error("🚨 Erro ao enviar e-mail assinado:", error);
    }
}

// 🔥 Função para salvar o e-mail no formato .eml
function saveEmailAsEml(signedEmail) {
    try {
        const emailPath = "./email.eml";
        fs.writeFileSync(emailPath, signedEmail, "utf8");
        console.log(`✅ E-mail assinado salvo como ${emailPath}`);
    } catch (error) {
        console.error("🚨 Erro ao salvar o e-mail como .eml:", error);
    }
}

// 🔥 Executando tudo
(async () => {
    const emailContent = "Este é um e-mail assinado digitalmente!";
    const p12Buffer = fs.readFileSync("./caminho do certificado digital");
    const password = "senha do certificado digital";

    const result = await signEmail(emailContent, p12Buffer, password);
    if (result) {
        const { signedEmail, signatureDER, certificates } = result;

        // 🔥 Verificar assinatura antes de enviar
        if (verifySignature(emailContent, signatureDER)) {
            // 🔥 Salvar e-mail assinado como .eml antes de enviar
            saveEmailAsEml(signedEmail);

            // 🔥 Enviar o e-mail assinado
            await sendSignedEmail(signedEmail);
        } else {
            console.error("❌ Erro: Assinatura corrompida! O e-mail não foi enviado.");
        }
    }
})();

Metadata

Metadata

Assignees

Labels

Javascript-v3This issue relates to the AWS SDK for Javascript V3New example requestNew example requests.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions