Skip to content

Commit 04e4235

Browse files
feat(smime): add S/MIME companion-app API and integration
Adds support for delegating S/MIME crypto operations to a separate companion app over an AIDL service, paralleling the existing OpenPGP / OpenKeychain integration. The reference provider is CipherMail (com.ciphermail.android); other providers can implement the same API. API surface (new module `plugins/smime-api/smime-api/`) - ISmimeService AIDL — execute(Intent, ParcelFileDescriptor, int) + createOutputPipe(int) for streaming bulk MIME data. - SmimeApi helper (sync + async execution wrappers, pipe management). - SmimeServiceConnection (bind lifecycle helper). - Parcelables: SmimeError, SmimeSignatureResult, SmimeDecryptionResult, SmimeCertificateInfo. All carry PARCELABLE_VERSION = 1. - Actions: CHECK_PERMISSION, DECRYPT_VERIFY, SIGN_AND_ENCRYPT, GET_CERTIFICATES, IMPORT_CERTIFICATE. Receive-side integration - SmimeCryptoHelper (parallels MessageCryptoHelper for OpenPGP): detects S/MIME parts, binds to the provider, dispatches DECRYPT_VERIFY, surfaces RESULT_CODE_USER_INTERACTION_REQUIRED via PendingIntent so the host can launch the provider's passphrase dialog. - MessageCryptoStructureDetector.isSmimePart and helpers — detect application/pkcs7-mime and PKCS#7 multipart/signed. - CryptoResultAnnotation: new S/MIME fields and createSmime* factories. - MessageCryptoDisplayStatus: S/MIME signature/encryption mappings to the existing display-status badges. - MessageLoaderHelper, MessageCryptoPresenter, MessageViewInfoExtractor wired through. Send-side integration - SmimeMessageBuilder (parallels PgpMessageBuilder): binds to the provider on a background thread, calls SIGN_AND_ENCRYPT, returns the wrapped MIME message for SMTP transport. Drafts bypass crypto. - MessageCompose: S/MIME branch in createMessageBuilder(), checked before PGP. - RecipientPresenter.asyncUpdateSmimeCertStatus: calls GET_CERTIFICATES on recipient changes; drives the compose lock-icon state (green = all certs present, red = missing). Per-account configuration - LegacyAccount / LegacyAccountDto: smimeProvider field + isSmimeProviderConfigured. - LegacyAccountStorageHandler + DefaultLegacyAccountDataMapper: persist smimeProvider. - AccountSettingsFragment: S/MIME PreferenceScreen + provider picker. - SmimeAppSelectDialog: enumerates installed providers via SmimeApi.SERVICE_INTENT and lets the user choose. Binding always uses setPackage(account.smimeProvider) to avoid intent-filter interception. Manifest plumbing - app-k9mail and app-thunderbird AndroidManifest: <queries> for ISmimeService discovery on Android 11+. - legacy/ui/legacy AndroidManifest: register SmimeAppSelectDialog. Cross-process passphrase handshake - When the provider's keystore is locked it returns RESULT_CODE_USER_INTERACTION_REQUIRED with an immutable PendingIntent for its passphrase activity. Thunderbird launches via startIntentSenderForResult and retries on RESULT_OK. No inline prompting, no IPC timeouts. Send/receive UX and per-identity signing - User-controllable Sign / Encrypt toggles with per-account defaults; S/MIME-enabled is persisted separately and a message is never sent silently unencrypted. - Encrypt-implies-sign enforced (no encrypt without a signature). - Per-identity signing: EXTRA_FROM carries the composing account's address on SIGN_AND_ENCRYPT so the provider signs with the matching certificate. - Separate signed / encrypted status icons and an "open in provider" action to view any encrypted or signed message in CipherMail. - Provider-unavailable handling: never lose a message; gate composer close on save completion; offer Drafts-folder assignment. - Send failures surface in a dismissible dialog rather than a Toast. - Set the intent extras class loader before reading Parcelable extras. - Test: RecipientPresenter.onSmimeCertCheckResult result branches.
1 parent 8c473ed commit 04e4235

43 files changed

Lines changed: 2763 additions & 23 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app-k9mail/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
android:theme="@style/Theme.K9.DayNight.Dialog.Translucent"
2020
/>
2121

22+
<activity
23+
android:name="com.fsck.k9.ui.settings.account.SmimeAppSelectDialog"
24+
android:configChanges="locale"
25+
android:theme="@style/Theme.K9.DayNight.Dialog.Translucent"
26+
/>
27+
2228
<activity
2329
android:name="com.fsck.k9.ui.notification.DeleteConfirmationActivity"
2430
android:excludeFromRecents="true"

app-thunderbird/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
android:theme="@style/Theme.Thunderbird.DayNight.Dialog.Translucent"
1919
/>
2020

21+
<activity
22+
android:name="com.fsck.k9.ui.settings.account.SmimeAppSelectDialog"
23+
android:configChanges="locale"
24+
android:theme="@style/Theme.Thunderbird.DayNight.Dialog.Translucent"
25+
/>
26+
2127
<activity
2228
android:name="com.fsck.k9.ui.notification.DeleteConfirmationActivity"
2329
android:excludeFromRecents="true"

core/android/account/src/main/kotlin/net/thunderbird/core/android/account/LegacyAccount.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,19 @@ data class LegacyAccount(
8686
val isStripSignature: Boolean = false,
8787
val isSyncRemoteDeletions: Boolean = false,
8888
val openPgpProvider: String? = null,
89+
/**
90+
* Package name of the installed S/MIME provider app (e.g.
91+
* `"com.ciphermail.android"`) selected for this account, or `null` if
92+
* S/MIME is not enabled. Used to target `ISmimeService` binds with an
93+
* explicit `setPackage()`, avoiding intent-filter ambiguity when more
94+
* than one provider is installed.
95+
*/
96+
val smimeProvider: String? = null,
97+
/** Whether S/MIME is turned on for this account; see LegacyAccountDto.smimeEnabled. */
98+
val smimeEnabled: Boolean = false,
99+
/** Last S/MIME sign/encrypt choice in the composer; see LegacyAccountDto. */
100+
val smimeSign: Boolean = true,
101+
val smimeEncrypt: Boolean = true,
89102
val openPgpKey: Long = 0,
90103
val autocryptPreferEncryptMutual: Boolean = false,
91104
val isOpenPgpHideSignOnly: Boolean = false,

core/android/account/src/main/kotlin/net/thunderbird/core/android/account/LegacyAccountDto.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,53 @@ open class LegacyAccountDto(
320320
field = value?.takeIf { it.isNotEmpty() }
321321
}
322322

323+
/**
324+
* Package name of the S/MIME provider app this account uses (e.g.
325+
* `"com.ciphermail.android"`), or `null` if S/MIME is not enabled.
326+
*
327+
* Setting an empty string is normalised to `null` so callers can
328+
* unset the provider by writing the result of a possibly-empty
329+
* EditText without first checking for emptiness.
330+
*/
331+
@get:Synchronized
332+
@set:Synchronized
333+
var smimeProvider: String? = null
334+
set(value) {
335+
field = value?.takeIf { it.isNotEmpty() }
336+
}
337+
338+
/**
339+
* Whether the user has turned S/MIME on for this account. Independent of
340+
* [smimeProvider]: S/MIME can be enabled while no provider app is installed
341+
* (provider unresolved). In that state outgoing mail is blocked at send
342+
* time rather than silently sent unencrypted.
343+
*/
344+
@get:Synchronized
345+
@set:Synchronized
346+
var smimeEnabled: Boolean = false
347+
348+
/**
349+
* Per-account memory of the user's last S/MIME sign / encrypt choice in the
350+
* composer. Defaults to true (sign and encrypt). When both are false an
351+
* S/MIME-enabled account sends a plain message.
352+
*/
353+
@get:Synchronized
354+
@set:Synchronized
355+
var smimeSign: Boolean = true
356+
357+
@get:Synchronized
358+
@set:Synchronized
359+
var smimeEncrypt: Boolean = true
360+
361+
/**
362+
* True iff S/MIME is turned on for this account (see [smimeEnabled]).
363+
* Note: enabled does not imply a provider is installed/resolved — callers
364+
* that actually perform crypto must additionally check [smimeProvider] and
365+
* that its package is installed.
366+
*/
367+
val isSmimeProviderConfigured: Boolean
368+
get() = smimeEnabled
369+
323370
@get:Synchronized
324371
@set:Synchronized
325372
var openPgpKey: Long = 0

feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/LegacyAccountStorageHandler.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@ class LegacyAccountStorageHandler(
226226
replaceIdentities(loadIdentities(data.id, storage))
227227

228228
openPgpProvider = storage.getStringOrDefault(keyGen.create("openPgpProvider"), "")
229+
smimeProvider = storage.getStringOrDefault(keyGen.create("smimeProvider"), "")
230+
// Default true for legacy accounts that already had a provider set,
231+
// so upgrading keeps S/MIME enabled.
232+
smimeEnabled = storage.getBoolean(keyGen.create("smimeEnabled"), smimeProvider != null)
233+
smimeSign = storage.getBoolean(keyGen.create("smimeSign"), true)
234+
smimeEncrypt = storage.getBoolean(keyGen.create("smimeEncrypt"), true)
229235
openPgpKey = storage.getLong(keyGen.create("cryptoKey"), AccountDefaultsProvider.Companion.NO_OPENPGP_KEY)
230236
isOpenPgpHideSignOnly = storage.getBoolean(keyGen.create("openPgpHideSignOnly"), true)
231237
isOpenPgpEncryptSubject = storage.getBoolean(keyGen.create("openPgpEncryptSubject"), true)
@@ -395,6 +401,10 @@ class LegacyAccountStorageHandler(
395401
editor.putBoolean(keyGen.create("openPgpEncryptSubject"), isOpenPgpEncryptSubject)
396402
editor.putBoolean(keyGen.create("openPgpEncryptAllDrafts"), isOpenPgpEncryptAllDrafts)
397403
editor.putString(keyGen.create("openPgpProvider"), openPgpProvider)
404+
editor.putString(keyGen.create("smimeProvider"), smimeProvider)
405+
editor.putBoolean(keyGen.create("smimeEnabled"), smimeEnabled)
406+
editor.putBoolean(keyGen.create("smimeSign"), smimeSign)
407+
editor.putBoolean(keyGen.create("smimeEncrypt"), smimeEncrypt)
398408
editor.putBoolean(keyGen.create("autocryptMutualMode"), autocryptPreferEncryptMutual)
399409
editor.putBoolean(keyGen.create("remoteSearchFullText"), isRemoteSearchFullText)
400410
editor.putInt(keyGen.create("remoteSearchNumResults"), remoteSearchNumResults)
@@ -507,6 +517,10 @@ class LegacyAccountStorageHandler(
507517
editor.remove(keyGen.create("cryptoKey"))
508518
editor.remove(keyGen.create("cryptoSupportSignOnly"))
509519
editor.remove(keyGen.create("openPgpProvider"))
520+
editor.remove(keyGen.create("smimeProvider"))
521+
editor.remove(keyGen.create("smimeEnabled"))
522+
editor.remove(keyGen.create("smimeSign"))
523+
editor.remove(keyGen.create("smimeEncrypt"))
510524
editor.remove(keyGen.create("openPgpHideSignOnly"))
511525
editor.remove(keyGen.create("openPgpEncryptSubject"))
512526
editor.remove(keyGen.create("openPgpEncryptAllDrafts"))

feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultLegacyAccountDataMapper.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ internal class DefaultLegacyAccountDataMapper : LegacyAccountDataMapper {
8181
isStripSignature = dto.isStripSignature,
8282
isSyncRemoteDeletions = dto.isSyncRemoteDeletions,
8383
openPgpProvider = dto.openPgpProvider,
84+
smimeProvider = dto.smimeProvider,
85+
smimeEnabled = dto.smimeEnabled,
86+
smimeSign = dto.smimeSign,
87+
smimeEncrypt = dto.smimeEncrypt,
8488
openPgpKey = dto.openPgpKey,
8589
autocryptPreferEncryptMutual = dto.autocryptPreferEncryptMutual,
8690
isOpenPgpHideSignOnly = dto.isOpenPgpHideSignOnly,
@@ -187,6 +191,10 @@ internal class DefaultLegacyAccountDataMapper : LegacyAccountDataMapper {
187191
isStripSignature = domain.isStripSignature
188192
isSyncRemoteDeletions = domain.isSyncRemoteDeletions
189193
openPgpProvider = domain.openPgpProvider
194+
smimeProvider = domain.smimeProvider
195+
smimeEnabled = domain.smimeEnabled
196+
smimeSign = domain.smimeSign
197+
smimeEncrypt = domain.smimeEncrypt
190198
openPgpKey = domain.openPgpKey
191199
autocryptPreferEncryptMutual = domain.autocryptPreferEncryptMutual
192200
isOpenPgpHideSignOnly = domain.isOpenPgpHideSignOnly

legacy/common/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@
263263
android:name="com.fsck.k9.provider.RawMessageProvider"
264264
android:authorities="${applicationId}.rawmessageprovider"
265265
android:exported="false"
266+
android:grantUriPermissions="true"
266267
>
267268

268269
<meta-data

legacy/core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
implementation(projects.feature.notification.api)
3030

3131
implementation(projects.plugins.openpgpApiLib.openpgpApi)
32+
implementation(projects.plugins.smimeApi.smimeApi)
3233
implementation(projects.feature.telemetry.api)
3334
implementation(projects.core.featureflag)
3435
implementation(projects.core.logging.implComposite)

legacy/core/src/main/java/com/fsck/k9/crypto/MessageCryptoStructureDetector.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public class MessageCryptoStructureDetector {
3636
// APPLICATION/PGP is a special case which occurs from mutt. see http://www.mutt.org/doc/PGP-Notes.txt
3737
private static final String APPLICATION_PGP = "application/pgp";
3838

39+
private static final String APPLICATION_PKCS7_MIME = "application/pkcs7-mime";
40+
private static final String APPLICATION_X_PKCS7_MIME = "application/x-pkcs7-mime";
41+
private static final String APPLICATION_PKCS7_SIGNATURE = "application/pkcs7-signature";
42+
private static final String APPLICATION_X_PKCS7_SIGNATURE = "application/x-pkcs7-signature";
43+
3944
private static final String PGP_INLINE_START_MARKER = "-----BEGIN PGP MESSAGE-----";
4045
private static final String PGP_INLINE_SIGNED_START_MARKER = "-----BEGIN PGP SIGNED MESSAGE-----";
4146
private static final int TEXT_LENGTH_FOR_INLINE_CHECK = 36;
@@ -211,7 +216,43 @@ public static byte[] getSignatureData(Part part) throws IOException, MessagingEx
211216
}
212217

213218
private static boolean isPartEncryptedOrSigned(Part part) {
214-
return isPartMultipartEncrypted(part) || isPartMultipartSigned(part) || isPartPgpInlineEncryptedOrSigned(part);
219+
return isPartMultipartEncrypted(part) || isPartMultipartSigned(part) ||
220+
isPartPgpInlineEncryptedOrSigned(part) || isSmimePart(part);
221+
}
222+
223+
/**
224+
* @return true if {@code part} is an S/MIME part — either an opaque
225+
* {@code application/pkcs7-mime} blob (encrypted, or
226+
* CMS-wrapped signed data) or a {@code multipart/signed} part
227+
* declaring the PKCS#7 detached-signature protocol.
228+
*/
229+
public static boolean isSmimePart(Part part) {
230+
return isSmimeEncryptedOrSignedData(part) || isSmimeSignedMultipart(part);
231+
}
232+
233+
/**
234+
* @return true for {@code application/pkcs7-mime} (RFC 8551) or its
235+
* legacy {@code x-pkcs7-mime} alias. Covers both encrypted
236+
* envelopedData and signed CMS data.
237+
*/
238+
public static boolean isSmimeEncryptedOrSignedData(Part part) {
239+
return isSameMimeType(part.getMimeType(), APPLICATION_PKCS7_MIME) ||
240+
isSameMimeType(part.getMimeType(), APPLICATION_X_PKCS7_MIME);
241+
}
242+
243+
/**
244+
* @return true for a {@code multipart/signed} part whose
245+
* {@code protocol} parameter is
246+
* {@code application/pkcs7-signature} (or its {@code x-} alias),
247+
* i.e. an S/MIME detached signature.
248+
*/
249+
public static boolean isSmimeSignedMultipart(Part part) {
250+
if (!isSameMimeType(part.getMimeType(), MULTIPART_SIGNED)) {
251+
return false;
252+
}
253+
String protocol = MimeUtility.getHeaderParameter(part.getContentType(), PROTOCOL_PARAMETER);
254+
return isSameMimeType(protocol, APPLICATION_PKCS7_SIGNATURE) ||
255+
isSameMimeType(protocol, APPLICATION_X_PKCS7_SIGNATURE);
215256
}
216257

217258
private static boolean isPartMultipartSigned(Part part) {

legacy/core/src/main/java/com/fsck/k9/mailstore/CryptoResultAnnotation.java

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import androidx.annotation.NonNull;
66
import androidx.annotation.Nullable;
77

8+
import com.ciphermail.smime.api.SmimeDecryptionResult;
9+
import com.ciphermail.smime.api.SmimeError;
10+
import com.ciphermail.smime.api.SmimeSignatureResult;
811
import com.fsck.k9.mail.internet.MimeBodyPart;
912

1013
import org.openintents.openpgp.OpenPgpDecryptionResult;
@@ -23,6 +26,11 @@ public final class CryptoResultAnnotation {
2326
private final PendingIntent openPgpInsecureWarningPendingIntent;
2427
private final boolean overrideCryptoWarning;
2528

29+
@Nullable private final SmimeDecryptionResult smimeDecryptionResult;
30+
@Nullable private final SmimeSignatureResult smimeSignatureResult;
31+
@Nullable private final SmimeError smimeError;
32+
@Nullable private final PendingIntent smimePendingIntent;
33+
2634
private final CryptoResultAnnotation encapsulatedResult;
2735

2836
private CryptoResultAnnotation(@NonNull CryptoError errorType, MimeBodyPart replacementData,
@@ -42,6 +50,33 @@ private CryptoResultAnnotation(@NonNull CryptoError errorType, MimeBodyPart repl
4250
this.openPgpInsecureWarningPendingIntent = openPgpInsecureWarningPendingIntent;
4351
this.overrideCryptoWarning = overrideCryptoWarning;
4452

53+
this.smimeDecryptionResult = null;
54+
this.smimeSignatureResult = null;
55+
this.smimeError = null;
56+
this.smimePendingIntent = null;
57+
this.encapsulatedResult = null;
58+
}
59+
60+
private CryptoResultAnnotation(@NonNull CryptoError errorType, MimeBodyPart replacementData,
61+
SmimeDecryptionResult smimeDecryptionResult,
62+
SmimeSignatureResult smimeSignatureResult,
63+
PendingIntent smimePendingIntent,
64+
SmimeError smimeError,
65+
boolean overrideCryptoWarning) {
66+
this.errorType = errorType;
67+
this.replacementData = replacementData;
68+
69+
this.smimeDecryptionResult = smimeDecryptionResult;
70+
this.smimeSignatureResult = smimeSignatureResult;
71+
this.smimePendingIntent = smimePendingIntent;
72+
this.smimeError = smimeError;
73+
this.overrideCryptoWarning = overrideCryptoWarning;
74+
75+
this.openPgpDecryptionResult = null;
76+
this.openPgpSignatureResult = null;
77+
this.openPgpPendingIntent = null;
78+
this.openPgpInsecureWarningPendingIntent = null;
79+
this.openPgpError = null;
4580
this.encapsulatedResult = null;
4681
}
4782

@@ -60,6 +95,11 @@ private CryptoResultAnnotation(CryptoResultAnnotation annotation, CryptoResultAn
6095
this.openPgpError = annotation.openPgpError;
6196
this.overrideCryptoWarning = annotation.overrideCryptoWarning;
6297

98+
this.smimeDecryptionResult = annotation.smimeDecryptionResult;
99+
this.smimeSignatureResult = annotation.smimeSignatureResult;
100+
this.smimePendingIntent = annotation.smimePendingIntent;
101+
this.smimeError = annotation.smimeError;
102+
63103
this.encapsulatedResult = encapsulatedResult;
64104
}
65105

@@ -95,6 +135,53 @@ public static CryptoResultAnnotation createOpenPgpEncryptionErrorAnnotation(Open
95135
CryptoError.OPENPGP_ENCRYPTED_API_ERROR, null, null, null, null, null, error, false);
96136
}
97137

138+
/**
139+
* Build a success annotation for an S/MIME decrypt/verify operation.
140+
*
141+
* @param smimeDecryptionResult outcome of decryption (may indicate the
142+
* part was signed-only or plain).
143+
* @param smimeSignatureResult outcome of signature verification.
144+
* @param smimePendingIntent follow-up interaction (e.g. view signer
145+
* certificate); nullable.
146+
* @param replacementData the decrypted inner MIME part to
147+
* substitute for the wrapped one; nullable
148+
* for sign-only inputs.
149+
* @param overrideCryptoWarning {@code true} to suppress the "encrypted
150+
* content not verified" badge.
151+
*/
152+
public static CryptoResultAnnotation createSmimeResultAnnotation(
153+
SmimeDecryptionResult smimeDecryptionResult,
154+
SmimeSignatureResult smimeSignatureResult,
155+
@Nullable PendingIntent smimePendingIntent,
156+
@Nullable MimeBodyPart replacementData,
157+
boolean overrideCryptoWarning) {
158+
return new CryptoResultAnnotation(CryptoError.SMIME_OK, replacementData,
159+
smimeDecryptionResult, smimeSignatureResult, smimePendingIntent, null,
160+
overrideCryptoWarning);
161+
}
162+
163+
/**
164+
* Build an error annotation for a failed S/MIME decryption. The
165+
* message view will show an "encrypted content unavailable" badge with
166+
* the {@link SmimeError} as the cause.
167+
*/
168+
public static CryptoResultAnnotation createSmimeEncryptionErrorAnnotation(SmimeError error) {
169+
return new CryptoResultAnnotation(CryptoError.SMIME_ENCRYPTED_API_ERROR, null,
170+
(SmimeDecryptionResult) null, null, null, error, false);
171+
}
172+
173+
/**
174+
* Build an error annotation for a failed S/MIME signature verification.
175+
* Unlike encryption errors, {@code replacementData} can still carry the
176+
* inner MIME body so the user can read the (now-untrusted) message
177+
* content alongside the signature-error badge.
178+
*/
179+
public static CryptoResultAnnotation createSmimeSignatureErrorAnnotation(
180+
SmimeError error, @Nullable MimeBodyPart replacementData) {
181+
return new CryptoResultAnnotation(CryptoError.SMIME_SIGNED_API_ERROR, replacementData,
182+
(SmimeDecryptionResult) null, null, null, error, false);
183+
}
184+
98185
public boolean isOpenPgpResult() {
99186
return openPgpDecryptionResult != null && openPgpSignatureResult != null;
100187
}
@@ -162,6 +249,30 @@ public MimeBodyPart getReplacementData() {
162249
return replacementData;
163250
}
164251

252+
public boolean isSmimeResult() {
253+
return smimeDecryptionResult != null && smimeSignatureResult != null;
254+
}
255+
256+
@Nullable
257+
public SmimeDecryptionResult getSmimeDecryptionResult() {
258+
return smimeDecryptionResult;
259+
}
260+
261+
@Nullable
262+
public SmimeSignatureResult getSmimeSignatureResult() {
263+
return smimeSignatureResult;
264+
}
265+
266+
@Nullable
267+
public SmimeError getSmimeError() {
268+
return smimeError;
269+
}
270+
271+
@Nullable
272+
public PendingIntent getSmimePendingIntent() {
273+
return smimePendingIntent;
274+
}
275+
165276
public boolean isOverrideSecurityWarning() {
166277
return overrideCryptoWarning;
167278
}
@@ -189,5 +300,9 @@ public enum CryptoError {
189300
SIGNED_BUT_UNSUPPORTED,
190301
ENCRYPTED_BUT_UNSUPPORTED,
191302
OPENPGP_ENCRYPTED_NO_PROVIDER,
303+
SMIME_OK,
304+
SMIME_SIGNED_API_ERROR,
305+
SMIME_ENCRYPTED_API_ERROR,
306+
SMIME_ENCRYPTED_NO_PROVIDER,
192307
}
193308
}

0 commit comments

Comments
 (0)