mirror of
https://github.com/M66B/FairEmail.git
synced 2025-02-28 08:46:01 +00:00
S/MIME sign+encrypt
This commit is contained in:
parent
886ad067e4
commit
c28a786777
1 changed files with 100 additions and 71 deletions
|
@ -105,7 +105,6 @@ import org.bouncycastle.cms.CMSAlgorithm;
|
||||||
import org.bouncycastle.cms.CMSEnvelopedData;
|
import org.bouncycastle.cms.CMSEnvelopedData;
|
||||||
import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
|
import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
|
||||||
import org.bouncycastle.cms.CMSProcessableByteArray;
|
import org.bouncycastle.cms.CMSProcessableByteArray;
|
||||||
import org.bouncycastle.cms.CMSProcessableFile;
|
|
||||||
import org.bouncycastle.cms.CMSSignedData;
|
import org.bouncycastle.cms.CMSSignedData;
|
||||||
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
||||||
import org.bouncycastle.cms.CMSTypedData;
|
import org.bouncycastle.cms.CMSTypedData;
|
||||||
|
@ -154,6 +153,7 @@ import java.util.Objects;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.activation.DataHandler;
|
||||||
import javax.mail.Address;
|
import javax.mail.Address;
|
||||||
import javax.mail.BodyPart;
|
import javax.mail.BodyPart;
|
||||||
import javax.mail.MessageRemovedException;
|
import javax.mail.MessageRemovedException;
|
||||||
|
@ -166,7 +166,9 @@ import javax.mail.internet.ContentType;
|
||||||
import javax.mail.internet.InternetAddress;
|
import javax.mail.internet.InternetAddress;
|
||||||
import javax.mail.internet.MimeBodyPart;
|
import javax.mail.internet.MimeBodyPart;
|
||||||
import javax.mail.internet.MimeMessage;
|
import javax.mail.internet.MimeMessage;
|
||||||
|
import javax.mail.internet.MimeMultipart;
|
||||||
import javax.mail.internet.ParseException;
|
import javax.mail.internet.ParseException;
|
||||||
|
import javax.mail.util.ByteArrayDataSource;
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_CANCELED;
|
import static android.app.Activity.RESULT_CANCELED;
|
||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
|
@ -1881,7 +1883,7 @@ public class FragmentCompose extends FragmentBase {
|
||||||
attachments.remove(attachment);
|
attachments.remove(attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build message
|
// Build message to sign
|
||||||
Properties props = MessageHelper.getSessionProperties();
|
Properties props = MessageHelper.getSessionProperties();
|
||||||
Session isession = Session.getInstance(props, null);
|
Session isession = Session.getInstance(props, null);
|
||||||
MimeMessage imessage = new MimeMessage(isession);
|
MimeMessage imessage = new MimeMessage(isession);
|
||||||
|
@ -1906,19 +1908,19 @@ public class FragmentCompose extends FragmentBase {
|
||||||
};
|
};
|
||||||
bpContent.setContent(imessage.getContent(), imessage.getContentType());
|
bpContent.setContent(imessage.getContent(), imessage.getContentType());
|
||||||
|
|
||||||
|
if (alias == null)
|
||||||
|
throw new IllegalArgumentException("Key alias missing");
|
||||||
|
|
||||||
|
// Get private key
|
||||||
|
PrivateKey privkey = KeyChain.getPrivateKey(context, alias);
|
||||||
|
if (privkey == null)
|
||||||
|
throw new IllegalArgumentException("Private key missing");
|
||||||
|
X509Certificate[] chain = KeyChain.getCertificateChain(context, alias);
|
||||||
|
if (chain == null || chain.length == 0)
|
||||||
|
throw new IllegalArgumentException("Certificate missing");
|
||||||
|
|
||||||
|
// Build content
|
||||||
if (EntityMessage.SMIME_SIGNONLY.equals(type)) {
|
if (EntityMessage.SMIME_SIGNONLY.equals(type)) {
|
||||||
if (alias == null)
|
|
||||||
throw new IllegalArgumentException("Key alias missing");
|
|
||||||
|
|
||||||
// Get private key
|
|
||||||
PrivateKey privkey = KeyChain.getPrivateKey(context, alias);
|
|
||||||
if (privkey == null)
|
|
||||||
throw new IllegalArgumentException("Private key missing");
|
|
||||||
X509Certificate[] chain = KeyChain.getCertificateChain(context, alias);
|
|
||||||
if (chain == null || chain.length == 0)
|
|
||||||
throw new IllegalArgumentException("Certificate missing");
|
|
||||||
|
|
||||||
// Build content
|
|
||||||
EntityAttachment cattachment = new EntityAttachment();
|
EntityAttachment cattachment = new EntityAttachment();
|
||||||
cattachment.message = draft.id;
|
cattachment.message = draft.id;
|
||||||
cattachment.sequence = db.attachment().getAttachmentSequence(draft.id) + 1;
|
cattachment.sequence = db.attachment().getAttachmentSequence(draft.id) + 1;
|
||||||
|
@ -1934,24 +1936,30 @@ public class FragmentCompose extends FragmentBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
db.attachment().setDownloaded(cattachment.id, content.length());
|
db.attachment().setDownloaded(cattachment.id, content.length());
|
||||||
|
}
|
||||||
|
|
||||||
// Sign
|
// Sign
|
||||||
Store store = new JcaCertStore(Arrays.asList(chain[0]));
|
Store store = new JcaCertStore(Arrays.asList(chain[0]));
|
||||||
CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator();
|
CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator();
|
||||||
cmsGenerator.addCertificates(store);
|
cmsGenerator.addCertificates(store);
|
||||||
|
|
||||||
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA")
|
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA")
|
||||||
.build(privkey);
|
.build(privkey);
|
||||||
DigestCalculatorProvider digestCalculator = new JcaDigestCalculatorProviderBuilder()
|
DigestCalculatorProvider digestCalculator = new JcaDigestCalculatorProviderBuilder()
|
||||||
.build();
|
.build();
|
||||||
SignerInfoGenerator signerInfoGenerator = new JcaSignerInfoGeneratorBuilder(digestCalculator)
|
SignerInfoGenerator signerInfoGenerator = new JcaSignerInfoGeneratorBuilder(digestCalculator)
|
||||||
.build(contentSigner, chain[0]);
|
.build(contentSigner, chain[0]);
|
||||||
cmsGenerator.addSignerInfoGenerator(signerInfoGenerator);
|
cmsGenerator.addSignerInfoGenerator(signerInfoGenerator);
|
||||||
|
|
||||||
CMSTypedData cmsData = new CMSProcessableFile(content);
|
ByteArrayOutputStream osContent = new ByteArrayOutputStream();
|
||||||
CMSSignedData cmsSignedData = cmsGenerator.generate(cmsData, true);
|
bpContent.writeTo(osContent);
|
||||||
byte[] signedMessage = cmsSignedData.getEncoded();
|
|
||||||
|
|
||||||
|
CMSTypedData cmsData = new CMSProcessableByteArray(osContent.toByteArray());
|
||||||
|
CMSSignedData cmsSignedData = cmsGenerator.generate(cmsData, true);
|
||||||
|
byte[] signedMessage = cmsSignedData.getEncoded();
|
||||||
|
|
||||||
|
// Build signature
|
||||||
|
if (EntityMessage.SMIME_SIGNONLY.equals(type)) {
|
||||||
ContentType ct = new ContentType("application/pkcs7-signature");
|
ContentType ct = new ContentType("application/pkcs7-signature");
|
||||||
ct.setParameter("micalg", "sha-256");
|
ct.setParameter("micalg", "sha-256");
|
||||||
|
|
||||||
|
@ -1970,52 +1978,73 @@ public class FragmentCompose extends FragmentBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
db.attachment().setDownloaded(sattachment.id, file.length());
|
db.attachment().setDownloaded(sattachment.id, file.length());
|
||||||
} else if (EntityMessage.SMIME_SIGNENCRYPT.equals(draft.encrypt)) {
|
|
||||||
// Get recipient
|
|
||||||
if (draft.to == null || draft.to.length != 1)
|
|
||||||
throw new IllegalArgumentException(getString(R.string.title_to_missing));
|
|
||||||
String to = ((InternetAddress) draft.to[0]).getAddress();
|
|
||||||
|
|
||||||
// Get public key
|
return null;
|
||||||
List<EntityCertificate> c = db.certificate().getCertificateByEmail(to);
|
|
||||||
if (c == null || c.size() == 0)
|
|
||||||
throw new IllegalArgumentException("Certificate not found");
|
|
||||||
|
|
||||||
byte[] encoded = Base64.decode(c.get(0).data, Base64.NO_WRAP);
|
|
||||||
X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509")
|
|
||||||
.generateCertificate(new ByteArrayInputStream(encoded));
|
|
||||||
|
|
||||||
// Encrypt
|
|
||||||
CMSEnvelopedDataGenerator cmsEnvelopedDataGenerator = new CMSEnvelopedDataGenerator();
|
|
||||||
RecipientInfoGenerator gen = new JceKeyTransRecipientInfoGenerator(cert);
|
|
||||||
cmsEnvelopedDataGenerator.addRecipientInfoGenerator(gen);
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
bpContent.writeTo(bos);
|
|
||||||
CMSTypedData msg = new CMSProcessableByteArray(bos.toByteArray());
|
|
||||||
|
|
||||||
OutputEncryptor encryptor = new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC)
|
|
||||||
.build();
|
|
||||||
CMSEnvelopedData cmsEnvelopedData = cmsEnvelopedDataGenerator
|
|
||||||
.generate(msg, encryptor);
|
|
||||||
|
|
||||||
EntityAttachment attachment = new EntityAttachment();
|
|
||||||
attachment.message = draft.id;
|
|
||||||
attachment.sequence = db.attachment().getAttachmentSequence(draft.id) + 1;
|
|
||||||
attachment.name = "smime.p7m";
|
|
||||||
attachment.type = "application/pkcs7-mime";
|
|
||||||
attachment.disposition = Part.INLINE;
|
|
||||||
attachment.encryption = EntityAttachment.SMIME_MESSAGE;
|
|
||||||
attachment.id = db.attachment().insertAttachment(attachment);
|
|
||||||
|
|
||||||
File file = attachment.getFile(context);
|
|
||||||
try (OutputStream os = new FileOutputStream(file)) {
|
|
||||||
cmsEnvelopedData.toASN1Structure().encodeTo(os);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.attachment().setDownloaded(attachment.id, file.length());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get recipient
|
||||||
|
if (draft.to == null || draft.to.length != 1)
|
||||||
|
throw new IllegalArgumentException(getString(R.string.title_to_missing));
|
||||||
|
String to = ((InternetAddress) draft.to[0]).getAddress();
|
||||||
|
|
||||||
|
// Get public key
|
||||||
|
List<EntityCertificate> c = db.certificate().getCertificateByEmail(to);
|
||||||
|
if (c == null || c.size() == 0)
|
||||||
|
throw new IllegalArgumentException("Certificate not found");
|
||||||
|
|
||||||
|
byte[] encoded = Base64.decode(c.get(0).data, Base64.NO_WRAP);
|
||||||
|
X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509")
|
||||||
|
.generateCertificate(new ByteArrayInputStream(encoded));
|
||||||
|
|
||||||
|
// Build signature
|
||||||
|
BodyPart bpSignature = new MimeBodyPart();
|
||||||
|
bpSignature.setFileName("smime.p7s");
|
||||||
|
bpSignature.setDataHandler(new DataHandler(new ByteArrayDataSource(signedMessage, "application/pkcs7-signature")));
|
||||||
|
bpSignature.setDisposition(Part.INLINE);
|
||||||
|
|
||||||
|
// Build message
|
||||||
|
ContentType ct = new ContentType("multipart/signed");
|
||||||
|
ct.setParameter("micalg", "sha-256");
|
||||||
|
ct.setParameter("protocol", "application/pkcs7-signature");
|
||||||
|
ct.setParameter("smime-type", "signed-data");
|
||||||
|
String ctx = ct.toString();
|
||||||
|
int slash = ctx.indexOf("/");
|
||||||
|
Multipart multipart = new MimeMultipart(ctx.substring(slash + 1));
|
||||||
|
multipart.addBodyPart(bpContent);
|
||||||
|
multipart.addBodyPart(bpSignature);
|
||||||
|
imessage.setContent(multipart);
|
||||||
|
imessage.saveChanges();
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
CMSEnvelopedDataGenerator cmsEnvelopedDataGenerator = new CMSEnvelopedDataGenerator();
|
||||||
|
RecipientInfoGenerator gen = new JceKeyTransRecipientInfoGenerator(cert);
|
||||||
|
cmsEnvelopedDataGenerator.addRecipientInfoGenerator(gen);
|
||||||
|
|
||||||
|
ByteArrayOutputStream osMessage = new ByteArrayOutputStream();
|
||||||
|
imessage.writeTo(osMessage);
|
||||||
|
CMSTypedData msg = new CMSProcessableByteArray(osMessage.toByteArray());
|
||||||
|
|
||||||
|
OutputEncryptor encryptor = new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC)
|
||||||
|
.build();
|
||||||
|
CMSEnvelopedData cmsEnvelopedData = cmsEnvelopedDataGenerator
|
||||||
|
.generate(msg, encryptor);
|
||||||
|
|
||||||
|
EntityAttachment attachment = new EntityAttachment();
|
||||||
|
attachment.message = draft.id;
|
||||||
|
attachment.sequence = db.attachment().getAttachmentSequence(draft.id) + 1;
|
||||||
|
attachment.name = "smime.p7m";
|
||||||
|
attachment.type = "application/pkcs7-mime";
|
||||||
|
attachment.disposition = Part.INLINE;
|
||||||
|
attachment.encryption = EntityAttachment.SMIME_MESSAGE;
|
||||||
|
attachment.id = db.attachment().insertAttachment(attachment);
|
||||||
|
|
||||||
|
File encrypted = attachment.getFile(context);
|
||||||
|
try (OutputStream os = new FileOutputStream(encrypted)) {
|
||||||
|
cmsEnvelopedData.toASN1Structure().encodeTo(os);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.attachment().setDownloaded(attachment.id, encrypted.length());
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue