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": "Автоимпорт сертификатов подписантов",