[Example]: Sending email with digital certificate
Maintainers usually reply within 2 days
@brmur is already working on this.
Since Feb 17, 2025.
Assessment
This issue has not been assessed yet.
Description
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.
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.");
}
}
})();
- Dominant language
- Java
- Stars
- 10.5k
- Forks
- 5.8k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 30
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from awsdocs/aws-doc-sdk-examples
-
[Enhancement]: Add Go (v2) example: Converse API with extended/adaptive thinking (Bedrock Runtime)May be free again @brmur claimed this 79 days ago, and no pull request is open. OpenGo-v2 New example request
awsdocs/aws-doc-sdk-examples#8093 · 1 assignee ·
Maintainers usually reply within 2 days
-
Modify ListObjectVersions to also show how do list versions of only a single objectMay be free again @brmur claimed this 163 days ago, and no pull request is open. OpenTask
awsdocs/aws-doc-sdk-examples#7914 · 1 assignee ·
Maintainers usually reply within 2 days
-
Create Rust DynamoDB GetItem single action exampleMay be free again @brmur claimed this 171 days ago, and no pull request is open. OpenNew example request Rust
awsdocs/aws-doc-sdk-examples#7913 · 1 assignee ·
Maintainers usually reply within 2 days
-
[Bug]: CloudWatch Logs Large Query Scenario - Offset BugMay be free again @rlhagerm claimed this 198 days ago, and no pull request is open. Open
awsdocs/aws-doc-sdk-examples#7873 · 1 assignee ·
Maintainers usually reply within 2 days
-
[Enhancement]: Update S3 Go code examples to use feature/s3/transfermanager instead of deprecated feature/s3/managerMay be free again @brmur claimed this 198 days ago, and no pull request is open. Open
awsdocs/aws-doc-sdk-examples#7872 · 3 comments · 1 assignee ·
Maintainers usually reply within 2 days
All issues in awsdocs/aws-doc-sdk-examples
Similar issues
-
面渣逆袭Spring事务传播机制勘误Open
Difficulty 1/5 Under an hour Newbie friendliness 80/100
itwanger/toBeBetterJavaer#218 ·
-
Markdown and Astro language servers do not start when the project path contains a non-ASCII character or a spacePossibly taken @RoiSoleil claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
eclipse-wildwebdeveloper/wildwebdeveloper#2277 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
johanhaleby/occurrent#1462 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
SasanLabs/VulnerableApp#912 ·
Maintainers usually reply within 1 day
-
area/build area/events area/storage area/storage/kafkasql area/storage/sql
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Apicurio/apicurio-registry#11079 ·
Maintainers usually reply within 1 day