@ -109,11 +109,19 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar ;
import com.google.android.material.snackbar.Snackbar ;
import com.sun.mail.util.FolderClosedIOException ;
import com.sun.mail.util.FolderClosedIOException ;
import org.bouncycastle.cert.X509CertificateHolder ;
import org.bouncycastle.cms.CMSEnvelopedData ;
import org.bouncycastle.cms.CMSEnvelopedData ;
import org.bouncycastle.cms.CMSProcessable ;
import org.bouncycastle.cms.CMSProcessableFile ;
import org.bouncycastle.cms.CMSSignedData ;
import org.bouncycastle.cms.KeyTransRecipientInformation ;
import org.bouncycastle.cms.KeyTransRecipientInformation ;
import org.bouncycastle.cms.RecipientInformation ;
import org.bouncycastle.cms.RecipientInformation ;
import org.bouncycastle.cms.SignerInformation ;
import org.bouncycastle.cms.SignerInformationStore ;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder ;
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.util.Store ;
import org.jsoup.nodes.Document ;
import org.jsoup.nodes.Document ;
import org.jsoup.nodes.Element ;
import org.jsoup.nodes.Element ;
import org.openintents.openpgp.OpenPgpError ;
import org.openintents.openpgp.OpenPgpError ;
@ -131,6 +139,9 @@ import java.io.InputStream;
import java.io.OutputStream ;
import java.io.OutputStream ;
import java.nio.charset.StandardCharsets ;
import java.nio.charset.StandardCharsets ;
import java.security.PrivateKey ;
import java.security.PrivateKey ;
import java.security.PublicKey ;
import java.security.cert.CertificateException ;
import java.security.cert.X509Certificate ;
import java.text.Collator ;
import java.text.Collator ;
import java.text.DateFormat ;
import java.text.DateFormat ;
import java.text.NumberFormat ;
import java.text.NumberFormat ;
@ -3904,7 +3915,8 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
boolean auto = intent . getBooleanExtra ( "auto" , false ) ;
boolean auto = intent . getBooleanExtra ( "auto" , false ) ;
int type = intent . getIntExtra ( "type" , EntityMessage . ENCRYPT_NONE ) ;
int type = intent . getIntExtra ( "type" , EntityMessage . ENCRYPT_NONE ) ;
if ( EntityMessage . SMIME_SIGNENCRYPT . equals ( type ) ) {
if ( EntityMessage . SMIME_SIGNONLY . equals ( type ) | |
EntityMessage . SMIME_SIGNENCRYPT . equals ( type ) ) {
final Bundle args = new Bundle ( ) ;
final Bundle args = new Bundle ( ) ;
args . putLong ( "id" , id ) ;
args . putLong ( "id" , id ) ;
args . putInt ( "type" , type ) ;
args . putInt ( "type" , type ) ;
@ -4170,6 +4182,9 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
out = new FileOutputStream ( plain ) ;
out = new FileOutputStream ( plain ) ;
} else if ( EntityAttachment . PGP_SIGNATURE . equals ( attachment . encryption ) ) {
} else if ( EntityAttachment . PGP_SIGNATURE . equals ( attachment . encryption ) ) {
if ( ! attachment . available )
throw new IllegalArgumentException ( context . getString ( R . string . title_attachments_missing ) ) ;
File file = attachment . getFile ( context ) ;
File file = attachment . getFile ( context ) ;
byte [ ] signature = new byte [ ( int ) file . length ( ) ] ;
byte [ ] signature = new byte [ ( int ) file . length ( ) ] ;
try ( FileInputStream fis = new FileInputStream ( file ) ) {
try ( FileInputStream fis = new FileInputStream ( file ) ) {
@ -4341,7 +4356,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
}
}
private void onSmime ( Bundle args ) {
private void onSmime ( Bundle args ) {
new SimpleTask () {
new SimpleTask <Boolean > () {
@Override
@Override
protected Boolean onExecute ( Context context , Bundle args ) throws Throwable {
protected Boolean onExecute ( Context context , Bundle args ) throws Throwable {
long id = args . getLong ( "id" ) ;
long id = args . getLong ( "id" ) ;
@ -4351,75 +4366,142 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
if ( alias = = null )
if ( alias = = null )
throw new IllegalArgumentException ( "Key alias missing" ) ;
throw new IllegalArgumentException ( "Key alias missing" ) ;
PrivateKey pk = KeyChain . getPrivateKey ( context , alias ) ;
if ( pk = = null )
throw new IllegalArgumentException ( "Private key missing" ) ;
DB db = DB . getInstance ( context ) ;
DB db = DB . getInstance ( context ) ;
File input = null ;
if ( EntityMessage . SMIME_SIGNONLY . equals ( type ) ) {
List < EntityAttachment > attachments = db . attachment ( ) . getAttachments ( id ) ;
// Check public key
for ( EntityAttachment attachment : attachments )
X509Certificate [ ] chain = KeyChain . getCertificateChain ( context , alias ) ;
if ( EntityAttachment . SMIME_MESSAGE . equals ( attachment . encryption ) ) {
if ( chain = = null | | chain . length = = 0 )
input = attachment . getFile ( context ) ;
throw new IllegalArgumentException ( "Public key missing" ) ;
break ;
}
// Get content/signature
if ( input = = null )
File content = null ;
throw new IllegalArgumentException ( "Encrypted message missing" ) ;
File signature = null ;
List < EntityAttachment > attachments = db . attachment ( ) . getAttachments ( id ) ;
FileInputStream fis = new FileInputStream ( input ) ;
for ( EntityAttachment attachment : attachments )
CMSEnvelopedData envelopedData = new CMSEnvelopedData ( fis ) ;
if ( EntityAttachment . SMIME_SIGNATURE . equals ( attachment . encryption ) )
signature = attachment . getFile ( context ) ;
else if ( EntityAttachment . SMIME_CONTENT . equals ( attachment . encryption ) )
content = attachment . getFile ( context ) ;
if ( content = = null )
throw new IllegalArgumentException ( "Signed content missing" ) ;
if ( signature = = null )
throw new IllegalArgumentException ( "Signature missing" ) ;
// Build signed data
CMSProcessable signedContent = new CMSProcessableFile ( content ) ;
FileInputStream fis = new FileInputStream ( signature ) ;
CMSSignedData signedData = new CMSSignedData ( signedContent , fis ) ;
// Check signature
Store store = signedData . getCertificates ( ) ;
SignerInformationStore signerInfos = signedData . getSignerInfos ( ) ;
for ( SignerInformation signer : signerInfos . getSigners ( ) )
for ( Object cert : store . getMatches ( signer . getSID ( ) ) ) {
X509CertificateHolder certHolder = ( X509CertificateHolder ) cert ;
if ( signer . verify ( new JcaSimpleSignerInfoVerifierBuilder ( ) . build ( certHolder ) ) ) {
// Check validity
Date now = new Date ( ) ;
boolean valid ;
try {
chain [ 0 ] . checkValidity ( now ) ;
valid = certHolder . isValidOn ( now ) ;
} catch ( CertificateException ignored ) {
valid = false ;
}
Collection < RecipientInformation > recipients = envelopedData . getRecipientInfos ( ) . getRecipients ( ) ;
// Check public key
KeyTransRecipientInformation recipientInfo = ( KeyTransRecipientInformation ) recipients . iterator ( ) . next ( ) ;
PublicKey pubkey = chain [ 0 ] . getPublicKey ( ) ;
JceKeyTransRecipient recipient = new JceKeyTransEnvelopedRecipient ( pk ) ;
if ( valid & &
InputStream is = recipientInfo . getContentStream ( recipient ) . getContentStream ( ) ;
signer . verify ( new JcaSimpleSignerInfoVerifierBuilder ( ) . build ( pubkey ) ) )
return true ;
else
return null ;
}
}
// Decode message
return false ;
Properties props = MessageHelper . getSessionProperties ( ) ;
} else {
Session isession = Session . getInstance ( props , null ) ;
// Check private key
MimeMessage imessage = new MimeMessage ( isession , is ) ;
PrivateKey privkey = KeyChain . getPrivateKey ( context , alias ) ;
MessageHelper helper = new MessageHelper ( imessage ) ;
if ( privkey = = null )
MessageHelper . MessageParts parts = helper . getMessageParts ( context ) ;
throw new IllegalArgumentException ( "Private key missing" ) ;
// Get encrypted message
File input = null ;
List < EntityAttachment > attachments = db . attachment ( ) . getAttachments ( id ) ;
for ( EntityAttachment attachment : attachments )
if ( EntityAttachment . SMIME_MESSAGE . equals ( attachment . encryption ) ) {
input = attachment . getFile ( context ) ;
break ;
}
if ( input = = null )
throw new IllegalArgumentException ( "Encrypted message missing" ) ;
try {
// Build enveloped data
db . beginTransaction ( ) ;
FileInputStream fis = new FileInputStream ( input ) ;
CMSEnvelopedData envelopedData = new CMSEnvelopedData ( fis ) ;
// Write decrypted body
// Decrypt message
String html = parts . getHtml ( context ) ;
Collection < RecipientInformation > recipients = envelopedData . getRecipientInfos ( ) . getRecipients ( ) ;
Helper . writeText ( EntityMessage . getFile ( context , id ) , html ) ;
KeyTransRecipientInformation recipientInfo = ( KeyTransRecipientInformation ) recipients . iterator ( ) . next ( ) ;
JceKeyTransRecipient recipient = new JceKeyTransEnvelopedRecipient ( privkey ) ;
InputStream is = recipientInfo . getContentStream ( recipient ) . getContentStream ( ) ;
// Decode message
Properties props = MessageHelper . getSessionProperties ( ) ;
Session isession = Session . getInstance ( props , null ) ;
MimeMessage imessage = new MimeMessage ( isession , is ) ;
MessageHelper helper = new MessageHelper ( imessage ) ;
MessageHelper . MessageParts parts = helper . getMessageParts ( context ) ;
// Remove existing attachments
try {
db . attachment ( ) . deleteAttachments ( id ) ;
db . beginTransaction ( ) ;
// Add decrypted attachments
// Write decrypted body
List < EntityAttachment > remotes = parts . getAttachments ( ) ;
String html = parts . getHtml ( context ) ;
for ( int index = 0 ; index < remotes . size ( ) ; index + + ) {
Helper . writeText ( EntityMessage . getFile ( context , id ) , html ) ;
EntityAttachment remote = remotes . get ( index ) ;
remote . message = id ;
// Remove existing attachments
remote . sequence = index + 1 ;
db . attachment ( ) . deleteAttachments ( id ) ;
remote . id = db . attachment ( ) . insertAttachment ( remote ) ;
try {
// Add decrypted attachments
parts . downloadAttachment ( context , index , remote ) ;
List < EntityAttachment > remotes = parts . getAttachments ( ) ;
} catch ( Throwable ex ) {
for ( int index = 0 ; index < remotes . size ( ) ; index + + ) {
Log . e ( ex ) ;
EntityAttachment remote = remotes . get ( index ) ;
remote . message = id ;
remote . sequence = index + 1 ;
remote . id = db . attachment ( ) . insertAttachment ( remote ) ;
try {
parts . downloadAttachment ( context , index , remote ) ;
} catch ( Throwable ex ) {
Log . e ( ex ) ;
}
}
}
}
db . message ( ) . setMessageEncrypt ( id , parts . getEncryption ( ) ) ;
db . message ( ) . setMessageEncrypt ( id , parts . getEncryption ( ) ) ;
db . message ( ) . setMessageStored ( id , new Date ( ) . getTime ( ) ) ;
db . message ( ) . setMessageStored ( id , new Date ( ) . getTime ( ) ) ;
db . setTransactionSuccessful ( ) ;
db . setTransactionSuccessful ( ) ;
} finally {
} finally {
db . endTransaction ( ) ;
db . endTransaction ( ) ;
}
}
return true ;
return null ;
}
}
}
@Override
@Override
protected void onExecuted ( Bundle args , Object data ) {
protected void onExecuted ( Bundle args , Boolean result ) {
int type = args . getInt ( "type" ) ;
int type = args . getInt ( "type" ) ;
if ( EntityMessage . SMIME_SIGNONLY . equals ( type ) )
if ( result = = null )
Snackbar . make ( view , R . string . title_signature_unconfirmed , Snackbar . LENGTH_LONG ) . show ( ) ;
else if ( result )
Snackbar . make ( view , R . string . title_signature_valid , Snackbar . LENGTH_LONG ) . show ( ) ;
else
Snackbar . make ( view , R . string . title_signature_invalid , Snackbar . LENGTH_LONG ) . show ( ) ;
}
}
@Override
@Override