From 7102add1943978aeb0b4df9a73d2d2bdf3ae1399 Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Tue, 31 Mar 2026 15:13:48 +0200
Subject: [PATCH] fix: add self-signed certificate detection and update status
messages for S/MIME signatures
---
components/email/smime-status-banner.tsx | 8 +++++++-
lib/smime/smime-verify.ts | 6 ++++++
lib/smime/types.ts | 2 ++
locales/de/common.json | 1 +
locales/en/common.json | 1 +
locales/es/common.json | 1 +
locales/fr/common.json | 1 +
locales/it/common.json | 1 +
locales/ja/common.json | 1 +
locales/nl/common.json | 1 +
locales/pt/common.json | 1 +
locales/ru/common.json | 1 +
12 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/components/email/smime-status-banner.tsx b/components/email/smime-status-banner.tsx
index 4ce318bc..688f1ea4 100644
--- a/components/email/smime-status-banner.tsx
+++ b/components/email/smime-status-banner.tsx
@@ -55,7 +55,13 @@ export function SmimeStatusBanner({ status, onUnlockKey, className }: SmimeStatu
// Signature status
if (status.isSigned) {
if (status.signatureValid === true) {
- if (status.signerEmailMatch === false) {
+ if (status.selfSigned) {
+ items.push({
+ icon: ,
+ text: t('status_signed_self_signed'),
+ variant: 'warning',
+ });
+ } else if (status.signerEmailMatch === false) {
items.push({
icon: ,
text: t('status_signed_mismatch'),
diff --git a/lib/smime/smime-verify.ts b/lib/smime/smime-verify.ts
index 8b6adc53..30cb8e14 100644
--- a/lib/smime/smime-verify.ts
+++ b/lib/smime/smime-verify.ts
@@ -108,6 +108,11 @@ export async function smimeVerify(
signerEmailMatch = fromHeader.toLowerCase() === signerEmail.toLowerCase();
}
+ // Detect self-signed certificates (issuer === subject)
+ const issuerDer = new Uint8Array(signerCert.issuer.toSchema().toBER(false));
+ const subjectDer = new Uint8Array(signerCert.subject.toSchema().toBER(false));
+ const selfSigned = arraysEqual(issuerDer, subjectDer);
+
return {
mimeBytes: innerContent,
status: {
@@ -117,6 +122,7 @@ export async function smimeVerify(
signatureError,
signerCert: signerPublicCert,
signerEmailMatch,
+ selfSigned,
},
};
}
diff --git a/lib/smime/types.ts b/lib/smime/types.ts
index 0282a196..a3e50f3a 100644
--- a/lib/smime/types.ts
+++ b/lib/smime/types.ts
@@ -55,6 +55,8 @@ export interface SmimeStatus {
signatureError?: string;
signerCert?: SmimePublicCert;
signerEmailMatch?: boolean;
+ /** True when the signer certificate is self-signed (not chained to a trusted CA). */
+ selfSigned?: boolean;
decryptionSuccess?: boolean;
decryptionError?: string;
unsupportedReason?: string;
diff --git a/locales/de/common.json b/locales/de/common.json
index 1d2fbc58..a5251303 100644
--- a/locales/de/common.json
+++ b/locales/de/common.json
@@ -2384,6 +2384,7 @@
"status_signed_valid": "Signatur überprüft",
"status_signed_invalid": "Signaturprüfung fehlgeschlagen",
"status_signed_expired_cert": "Mit einem abgelaufenen Zertifikat signiert",
+ "status_signed_self_signed": "Signatur gültig, aber das Zertifikat ist selbstsigniert (nicht vertrauenswürdig)",
"status_signed_mismatch": "Signatur ist gültig, aber der Unterzeichner stimmt nicht mit dem Absender überein",
"status_unsupported": "Nicht unterstütztes S/MIME-Format",
"auto_import_signer_certs": "Unterzeichnerzertifikate automatisch importieren",
diff --git a/locales/en/common.json b/locales/en/common.json
index 207d8bcb..fad55283 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -2391,6 +2391,7 @@
"status_signed_valid": "Signature verified",
"status_signed_invalid": "Signature verification failed",
"status_signed_expired_cert": "Signed with an expired certificate",
+ "status_signed_self_signed": "Signature valid, but certificate is self-signed (not trusted)",
"status_signed_mismatch": "Signature valid, but signer does not match sender",
"status_unsupported": "Unsupported S/MIME format",
"auto_import_signer_certs": "Auto-import signer certificates",
diff --git a/locales/es/common.json b/locales/es/common.json
index 14b27732..efdafc3e 100644
--- a/locales/es/common.json
+++ b/locales/es/common.json
@@ -2384,6 +2384,7 @@
"status_signed_valid": "Firma verificada",
"status_signed_invalid": "La verificación de la firma falló",
"status_signed_expired_cert": "Firmado con un certificado caducado",
+ "status_signed_self_signed": "Firma válida, pero el certificado es autofirmado (no confiable)",
"status_signed_mismatch": "La firma es válida, pero el firmante no coincide con el remitente",
"status_unsupported": "Formato S/MIME no compatible",
"auto_import_signer_certs": "Importar automáticamente certificados de firmantes",
diff --git a/locales/fr/common.json b/locales/fr/common.json
index 8dade1f3..9b07a50f 100644
--- a/locales/fr/common.json
+++ b/locales/fr/common.json
@@ -2384,6 +2384,7 @@
"status_signed_valid": "Signature vérifiée",
"status_signed_invalid": "Échec de la vérification de la signature",
"status_signed_expired_cert": "Signé avec un certificat expiré",
+ "status_signed_self_signed": "Signature valide, mais le certificat est auto-signé (non fiable)",
"status_signed_mismatch": "La signature est valide, mais le signataire ne correspond pas à l'expéditeur",
"status_unsupported": "Format S/MIME non pris en charge",
"auto_import_signer_certs": "Importer automatiquement les certificats des signataires",
diff --git a/locales/it/common.json b/locales/it/common.json
index cfa75d64..32439d5d 100644
--- a/locales/it/common.json
+++ b/locales/it/common.json
@@ -2384,6 +2384,7 @@
"status_signed_valid": "Firma verificata",
"status_signed_invalid": "Verifica della firma non riuscita",
"status_signed_expired_cert": "Firmato con un certificato scaduto",
+ "status_signed_self_signed": "Firma valida, ma il certificato è autofirmato (non attendibile)",
"status_signed_mismatch": "La firma è valida, ma il firmatario non corrisponde al mittente",
"status_unsupported": "Formato S/MIME non supportato",
"auto_import_signer_certs": "Importa automaticamente i certificati dei firmatari",
diff --git a/locales/ja/common.json b/locales/ja/common.json
index bde71a2f..8bf12c9e 100644
--- a/locales/ja/common.json
+++ b/locales/ja/common.json
@@ -2384,6 +2384,7 @@
"status_signed_valid": "署名を確認しました",
"status_signed_invalid": "署名の検証に失敗しました",
"status_signed_expired_cert": "期限切れの証明書で署名されています",
+ "status_signed_self_signed": "署名は有効ですが、証明書は自己署名です(信頼されていません)",
"status_signed_mismatch": "署名は有効ですが、署名者が送信者と一致しません",
"status_unsupported": "未対応の S/MIME 形式です",
"auto_import_signer_certs": "署名者証明書を自動インポート",
diff --git a/locales/nl/common.json b/locales/nl/common.json
index ea718189..630d5e41 100644
--- a/locales/nl/common.json
+++ b/locales/nl/common.json
@@ -2384,6 +2384,7 @@
"status_signed_valid": "Handtekening geverifieerd",
"status_signed_invalid": "Verificatie van de handtekening is mislukt",
"status_signed_expired_cert": "Ondertekend met een verlopen certificaat",
+ "status_signed_self_signed": "Handtekening geldig, maar het certificaat is zelfondertekend (niet vertrouwd)",
"status_signed_mismatch": "Handtekening is geldig, maar de ondertekenaar komt niet overeen met de afzender",
"status_unsupported": "Niet-ondersteund S/MIME-formaat",
"auto_import_signer_certs": "Ondertekenaarcertificaten automatisch importeren",
diff --git a/locales/pt/common.json b/locales/pt/common.json
index a669b734..3dd6d2fd 100644
--- a/locales/pt/common.json
+++ b/locales/pt/common.json
@@ -2384,6 +2384,7 @@
"status_signed_valid": "Assinatura verificada",
"status_signed_invalid": "A verificação da assinatura falhou",
"status_signed_expired_cert": "Assinada com um certificado expirado",
+ "status_signed_self_signed": "Assinatura válida, mas o certificado é autoassinado (não confiável)",
"status_signed_mismatch": "A assinatura é válida, mas o signatário não corresponde ao remetente",
"status_unsupported": "Formato S/MIME não suportado",
"auto_import_signer_certs": "Importar automaticamente certificados de signatários",
diff --git a/locales/ru/common.json b/locales/ru/common.json
index a81c5e4a..65245347 100644
--- a/locales/ru/common.json
+++ b/locales/ru/common.json
@@ -2384,6 +2384,7 @@
"status_signed_valid": "Подпись проверена",
"status_signed_invalid": "Проверка подписи не удалась",
"status_signed_expired_cert": "Подписано просроченным сертификатом",
+ "status_signed_self_signed": "Подпись действительна, но сертификат самоподписанный (не доверенный)",
"status_signed_mismatch": "Подпись действительна, но подписант не совпадает с отправителем",
"status_unsupported": "Неподдерживаемый формат S/MIME",
"auto_import_signer_certs": "Автоимпорт сертификатов подписантов",