Added support for S/MIME auth-enveloped-data

master
M66B 2 weeks ago
parent 3379062acf
commit 0662e46ffb

@ -68,7 +68,6 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.CancellationSignal; import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.os.SystemClock; import android.os.SystemClock;
@ -153,14 +152,21 @@ import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1SequenceParser;
import org.bouncycastle.asn1.ASN1StreamParser;
import org.bouncycastle.asn1.cms.Attribute; import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.CMSAttributes; import org.bouncycastle.asn1.cms.CMSAttributes;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.cms.ContentInfoParser;
import org.bouncycastle.asn1.cms.Time; import org.bouncycastle.asn1.cms.Time;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSAuthEnvelopedDataParser;
import org.bouncycastle.cms.CMSEnvelopedDataParser; import org.bouncycastle.cms.CMSEnvelopedDataParser;
import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessable; import org.bouncycastle.cms.CMSProcessable;
@ -169,11 +175,13 @@ import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSTypedData; import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.PKIXRecipientId; import org.bouncycastle.cms.PKIXRecipientId;
import org.bouncycastle.cms.RecipientInformation; import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.RecipientInformationStore;
import org.bouncycastle.cms.SignerId; import org.bouncycastle.cms.SignerId;
import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore; import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.SignerInformationVerifier; import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.cms.jcajce.JceKeyTransAuthEnvelopedRecipient;
import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient; import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipient; import org.bouncycastle.cms.jcajce.JceKeyTransRecipient;
import org.bouncycastle.operator.DefaultAlgorithmNameFinder; import org.bouncycastle.operator.DefaultAlgorithmNameFinder;
@ -10603,17 +10611,46 @@ public class FragmentMessages extends FragmentBase
if (input == null) if (input == null)
throw new IllegalArgumentException("Encrypted message missing"); throw new IllegalArgumentException("Encrypted message missing");
// Do not trust only the smime-type MIME parameter: it is a hint and
// is not retained in EntityAttachment.type. Detect the actual CMS
// content type from the ContentInfo OID.
ASN1ObjectIdentifier contentType = getCmsContentType(input);
boolean authEnveloped = CMSObjectIdentifiers.authEnvelopedData.equals(contentType);
if (!authEnveloped && !CMSObjectIdentifiers.envelopedData.equals(contentType))
throw new CMSException("Unsupported encrypted CMS content type: " + contentType);
int count = -1; int count = -1;
boolean decoded = false; boolean decoded = false;
Throwable last = null; Throwable last = null;
while (!decoded) while (!decoded)
try (FileInputStream fis = new FileInputStream(input)) { try (FileInputStream fis = new FileInputStream(input)) {
// Create parser // AuthEnvelopedData uses an AEAD-capable parser and recipient.
CMSEnvelopedDataParser envelopedData = new CMSEnvelopedDataParser(fis); RecipientInformationStore recipientStore;
AlgorithmIdentifier contentAlgorithm;
String contentAlgorithmOid;
String parserClass;
JceKeyTransRecipient recipient;
if (authEnveloped) {
CMSAuthEnvelopedDataParser authData = new CMSAuthEnvelopedDataParser(fis);
recipientStore = authData.getRecipientInfos();
contentAlgorithm = authData.getEncryptionAlgOID();
contentAlgorithmOid = authData.getEncAlgOID();
parserClass = authData.getClass().getName();
recipient = new JceKeyTransAuthEnvelopedRecipient(privkey);
} else {
CMSEnvelopedDataParser envelopedData = new CMSEnvelopedDataParser(fis);
recipientStore = envelopedData.getRecipientInfos();
contentAlgorithm = envelopedData.getContentEncryptionAlgorithm();
contentAlgorithmOid = envelopedData.getEncryptionAlgOID();
parserClass = envelopedData.getClass().getName();
recipient = new JceKeyTransEnvelopedRecipient(privkey);
}
// Get recipient info // Get recipient info
JceKeyTransRecipient recipient = new JceKeyTransEnvelopedRecipient(privkey); Collection<RecipientInformation> recipients =
Collection<RecipientInformation> recipients = envelopedData.getRecipientInfos().getRecipients(); // KeyTransRecipientInformation recipientStore.getRecipients(); // KeyTransRecipientInformation
EntityLog.log(context, "s/mime private key" + EntityLog.log(context, "s/mime private key" +
" algo=" + privkey.getAlgorithm() + " algo=" + privkey.getAlgorithm() +
@ -10644,14 +10681,15 @@ public class FragmentMessages extends FragmentBase
String malgo; String malgo;
try { try {
DefaultAlgorithmNameFinder af = new DefaultAlgorithmNameFinder(); DefaultAlgorithmNameFinder af = new DefaultAlgorithmNameFinder();
malgo = af.getAlgorithmName(envelopedData.getContentEncryptionAlgorithm()); malgo = af.getAlgorithmName(contentAlgorithm);
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e(ex); Log.e(ex);
malgo = envelopedData.getEncryptionAlgOID(); malgo = contentAlgorithmOid;
} }
EntityLog.log(context, "s/mime message" + EntityLog.log(context, "s/mime message" +
" type=" + contentType +
" algo=" + malgo + " algo=" + malgo +
" class=" + envelopedData.getClass()); " class=" + parserClass);
// Find recipient // Find recipient
if (count < 0) { if (count < 0) {
@ -10938,6 +10976,20 @@ public class FragmentMessages extends FragmentBase
Log.unexpectedError(getParentFragmentManager(), ex); Log.unexpectedError(getParentFragmentManager(), ex);
} }
private ASN1ObjectIdentifier getCmsContentType(File file)
throws IOException, CMSException {
try (InputStream is =
new BufferedInputStream(new FileInputStream(file))) {
ASN1Encodable object = new ASN1StreamParser(is).readObject();
if (!(object instanceof ASN1SequenceParser))
throw new CMSException("Invalid CMS ContentInfo");
ContentInfoParser contentInfo =
new ContentInfoParser((ASN1SequenceParser) object);
return contentInfo.getContentType();
}
}
private void decodeMessage(Context context, InputStream is, EntityMessage message, Bundle args) throws MessagingException, IOException { private void decodeMessage(Context context, InputStream is, EntityMessage message, Bundle args) throws MessagingException, IOException {
int type = args.getInt("type"); int type = args.getInt("type");
String alias = args.getString("alias"); String alias = args.getString("alias");

@ -5271,6 +5271,10 @@ public class MessageHelper {
if ("enveloped-data".equalsIgnoreCase(smimeType)) { if ("enveloped-data".equalsIgnoreCase(smimeType)) {
getMessageParts(null, part, parts, EntityAttachment.SMIME_MESSAGE); getMessageParts(null, part, parts, EntityAttachment.SMIME_MESSAGE);
return parts; return parts;
} else if ("authEnveloped-data".equalsIgnoreCase(smimeType) ||
"auth-enveloped-data".equalsIgnoreCase(smimeType)) {
getMessageParts(null, part, parts, EntityAttachment.SMIME_MESSAGE);
return parts;
} else if ("signed-data".equalsIgnoreCase(smimeType)) { } else if ("signed-data".equalsIgnoreCase(smimeType)) {
getMessageParts(null, part, parts, EntityAttachment.SMIME_SIGNED_DATA); getMessageParts(null, part, parts, EntityAttachment.SMIME_SIGNED_DATA);
return parts; return parts;

Loading…
Cancel
Save