package sernet.verinice.encryption.impl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import javax.mail.MessagingException;
import javax.mail.internet.MimeBodyPart;
import org.bouncycastle.cms.CMSAlgorithm;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.RecipientId;
import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.RecipientInformationStore;
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientId;
import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipient;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.mail.smime.SMIMEEnveloped;
import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
import org.bouncycastle.mail.smime.SMIMEException;
import org.bouncycastle.mail.smime.SMIMEUtil;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PasswordFinder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.util.encoders.Base64;
import sernet.verinice.encryption.impl.util.CertificateUtils;
import sernet.verinice.encryption.impl.util.SMIMEDecryptedInputStream;
import sernet.verinice.encryption.impl.util.SMIMEEncryptedOutputStream;
import sernet.verinice.interfaces.encryption.EncryptionException;
/**
* Abstract utility class providing static methods for S/MIME based encryption.
*
*
* S/MIME stands for Secure/Multipurpose Internet Mail Extensions. As defined in
* RFC 2898, "S/MIME
* provides a consistent way to send and receive secure MIME data. Based on the
* popular Internet MIME standard, S/MIME provides the following cryptographic
* security services for electronic messaging applications: authentication,
* message integrity and non-repudiation of origin (using digital signatures),
* and data confidentiality (using encryption)"
.
*
*
*
* To encrypt a message (or other data) the public key certificate of the
* receiver is required. Public key certificates prove that that the public key
* it contains belongs to a certain identity (person, organisation, etc.). A
* standard for public key certificates is X.509. This standard specifies that
* the certificate content is definied in ASN.1.
* The two main certificate formats are DER and PEM. The DEM format is a DER
* (Distingushed Encoding Rules) encoded form of the ASN.1 certificate
* definition. PEM is a Base64 encoded form of the DEM format with additional
* ASCII header and footer, which include the encoded content.
*
*
*
* -----BEGIN CERTIFICATE-----
* Base64 encoded DER certificate content
* Base64 encoded DER certificate content
* Base64 encoded DER certificate content
* -----END CERTIFICATE-----
*
*
* @author Sebastian Engel
*
*/
public class SMIMEBasedEncryption {
/**
* Encrypts the given byte data with the given X.509 certificate file.
*
* Since encryption is realized through S/MIME, the public certificate of
* the "receiver" is required. The certificate is expected to be in DER or
* PEM format.
*
*
* @param unencryptedByteData
* an array of byte data to encrypt
* @param x509CertificateFile
* X.509 certificate file used to encrypt the data. The file is
* expected to be in DER or PEM format
* @return an array of bytes representing a MimeBodyPart that contains the
* encrypted content
* @throws IOException
*
* - if any of the given files does not exist
* - if any of the given files cannot be read
*
* @throws CertificateNotYetValidException
* if the certificate is not yet valid
* @throws CertificateExpiredException
* if the certificate is not valid anymore
* @throws CertificateException
*
* - if the given certificate file does not contain a
* certificate
* - if the certificate contained in the given file is not a
* X.509 certificate
*
* @throws EncryptionException
* if a problem occured during the encryption process
*/
public static byte[] encrypt(byte[] unencryptedByteData, File x509CertificateFile) throws IOException, CertificateNotYetValidException, CertificateExpiredException, CertificateException, EncryptionException {
byte[] encryptedMimeData = new byte[] {};
/*
* InputStream is =
* SMIMEBasedEncryption.class.getClassLoader().getResourceAsStream
* ("mailcap"); MailcapCommandMap mc = new MailcapCommandMap(is);
* CommandMap.setDefaultCommandMap(mc);
*/
;
X509Certificate x509Certificate = CertificateUtils.loadX509CertificateFromFile(x509CertificateFile);
try {
SMIMEEnvelopedGenerator generator = new SMIMEEnvelopedGenerator();
generator.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(x509Certificate).setProvider("SunPKCS11-verinice"));
unencryptedByteData = Base64.encode(unencryptedByteData);
MimeBodyPart unencryptedContent = SMIMEUtil.toMimeBodyPart(unencryptedByteData);
// Encrypt the byte data and make a MimeBodyPart from it
MimeBodyPart encryptedMimeBodyPart = generator.generate(
unencryptedContent,
new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES256_CBC).setProvider(BouncyCastleProvider.PROVIDER_NAME).build());
// Finally get the encoded bytes from the MimeMessage and return
// them
ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
encryptedMimeBodyPart.writeTo(byteOutStream);
encryptedMimeData = byteOutStream.toByteArray();
} catch (GeneralSecurityException e) {
throw new EncryptionException("There was a problem during the en- or decryption process. See the stacktrace for details.", e);
} catch (SMIMEException smimee) {
throw new EncryptionException("There was a problem during the en- or decryption process. See the stacktrace for details.", smimee);
} catch (MessagingException e) {
throw new EncryptionException("There was a problem during the en- or decryption process. See the stacktrace for details.", e);
} catch (IOException ioe) {
throw new EncryptionException("There was an IO problem during the en- or decryption process. See the stacktrace for details.", ioe);
} catch (CMSException e) {
throw new EncryptionException("There was an IO problem during the en- or decryption process. See the stacktrace for details.", e);
} catch (IllegalArgumentException e) {
throw new EncryptionException("There was an IO problem during the en- or decryption process. See the stacktrace for details.", e);
} catch (OperatorCreationException e) {
throw new EncryptionException("There was an IO problem during the en- or decryption process. See the stacktrace for details.", e);
}
return encryptedMimeData;
}
public static byte[] encrypt(byte[] unencryptedByteData, String keyAlias) throws IOException, CertificateNotYetValidException, CertificateExpiredException, CertificateException, EncryptionException {
byte[] encryptedMimeData = new byte[] {};
try {
KeyStore ks = KeyStore.getInstance("PKCS11", "SunPKCS11-verinice");
ks.load(null, null);
X509Certificate cert = (X509Certificate) ks.getCertificate(keyAlias);
SMIMEEnvelopedGenerator generator = new SMIMEEnvelopedGenerator();
generator.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(cert));
//.setProvider("SunPKCS11-verinice")
unencryptedByteData = Base64.encode(unencryptedByteData);
MimeBodyPart unencryptedContent = SMIMEUtil.toMimeBodyPart(unencryptedByteData);
// Encrypt the byte data and make a MimeBodyPart from it
MimeBodyPart encryptedMimeBodyPart = generator.generate(
unencryptedContent,
new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES256_CBC).setProvider(BouncyCastleProvider.PROVIDER_NAME).build());
// Finally get the encoded bytes from the MimeMessage and return
// them
ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
encryptedMimeBodyPart.writeTo(byteOutStream);
encryptedMimeData = byteOutStream.toByteArray();
} catch (GeneralSecurityException e) {
throw new EncryptionException("There was a problem during the en- or decryption process. See the stacktrace for details.", e);
} catch (SMIMEException smimee) {
throw new EncryptionException("There was a problem during the en- or decryption process. See the stacktrace for details.", smimee);
} catch (MessagingException e) {
throw new EncryptionException("There was a problem during the en- or decryption process. See the stacktrace for details.", e);
} catch (IOException ioe) {
throw new EncryptionException("There was an IO problem during the en- or decryption process. See the stacktrace for details.", ioe);
} catch (IllegalArgumentException e) {
throw new EncryptionException("There was a problem during the en- or decryption process. See the stacktrace for details.", e);
/*} catch (OperatorCreationException e) {
throw new EncryptionException("There was a problem during the en- or decryption process. See the stacktrace for details.", e);
*/} catch (CMSException e) {
throw new EncryptionException("There was a problem during the en- or decryption process. See the stacktrace for details.", e);
}
return encryptedMimeData;
}
public static OutputStream encrypt(OutputStream unencryptedDataStream, String keyAlias) throws IOException, CertificateNotYetValidException, CertificateExpiredException, CertificateException, EncryptionException {
return new SMIMEEncryptedOutputStream(unencryptedDataStream, keyAlias);
}
/**
* Decrypts the given byte data with the given receiver certificate and the
* private key
*
* @param encryptedByteData
* an array of byte data to decrypt
* @param x509CertificateFile
* X.509 certificate that was used to encrypt the data. The file
* is expected to be in DER or PEM format
* @param privateKeyPemFile
* .pem file that contains the private key used for decryption.
* This key must fit to the public key contained in the public
* certificate
* @return an array of bytes representing the unencrypted byte data.
* @throws IOException
*
* - if any of the given files does not exist
* - if any of the given files cannot be read
*
* @throws CertificateNotYetValidException
* if the certificate is not yet valid
* @throws CertificateExpiredException
* if the certificate is not valid anymore
* @throws CertificateException
*
* - if the given certificate file does not contain a
* certificate
* - if the certificate contained in the given file is not a
* X.509 certificate
*
* @throws EncryptionException
* if a problem occured during the encryption process
*/
public static byte[] decrypt(byte[] encryptedByteData, File x509CertificateFile, File privateKeyPemFile) throws IOException, CertificateNotYetValidException, CertificateExpiredException, CertificateException, EncryptionException {
return decrypt(encryptedByteData, x509CertificateFile, privateKeyPemFile, null);
}
/**
* Decrypts the given byte data with the given receiver certificate and the
* private key
*
* @param encryptedByteData
* an array of byte data to decrypt
* @param x509CertificateFile
* X.509 certificate that was used to encrypt the data. The file
* is expected to be in DER or PEM format
* @param privateKeyPemFile
* .pem file that contains the private key used for decryption.
* This key must fit to the public key contained in the public
* certificate
* @param privateKeyPassword
* password to encrypt private key
* @return an array of bytes representing the unencrypted byte data.
* @throws IOException
*
* - if any of the given files does not exist
* - if any of the given files cannot be read
*
* @throws CertificateNotYetValidException
* if the certificate is not yet valid
* @throws CertificateExpiredException
* if the certificate is not valid anymore
* @throws CertificateException
*
* - if the given certificate file does not contain a
* certificate
* - if the certificate contained in the given file is not a
* X.509 certificate
*
* @throws EncryptionException
* if a problem occured during the encryption process
*/
public static byte[] decrypt(byte[] encryptedByteData, File x509CertificateFile, File privateKeyPemFile, final String privateKeyPassword) throws IOException, CertificateNotYetValidException, CertificateExpiredException, CertificateException, EncryptionException {
byte[] decryptedByteData = new byte[] {};
// Get public key certificate
X509Certificate x509Certificate = CertificateUtils.loadX509CertificateFromFile(x509CertificateFile);
// The recipient's private key
FileReader fileReader = new FileReader(privateKeyPemFile);
PasswordFinder passwordFinder = new PasswordFinder() {
@Override
public char[] getPassword() {
return (privateKeyPassword != null) ? privateKeyPassword.toCharArray() : null;
}
};
PEMReader pemReader = null;
if (passwordFinder.getPassword() != null && passwordFinder.getPassword().length > 0) {
pemReader = new PEMReader(fileReader, passwordFinder);
} else {
pemReader = new PEMReader(fileReader);
}
KeyPair keyPair = (KeyPair) pemReader.readObject();
PrivateKey privateKey = keyPair.getPrivate();
try {
MimeBodyPart encryptedMimeBodyPart = new MimeBodyPart(new ByteArrayInputStream(encryptedByteData));
SMIMEEnveloped enveloped = new SMIMEEnveloped(encryptedMimeBodyPart);
// look for our recipient identifier
RecipientId recipientId = new JceKeyAgreeRecipientId(x509Certificate);
RecipientInformationStore recipients = enveloped.getRecipientInfos();
RecipientInformation recipientInfo = recipients.get(recipientId);
if (recipientInfo != null) {
JceKeyTransRecipient rec = new JceKeyTransEnvelopedRecipient(privateKey);
rec.setProvider("SunPKCS11-verinice");
rec.setContentProvider(BouncyCastleProvider.PROVIDER_NAME);
decryptedByteData = recipientInfo.getContent(rec);
}
} catch (MessagingException e) {
throw new EncryptionException("There was an IO problem during the en- or decryption process. See the stacktrace for details.", e);
} catch (CMSException e) {
throw new EncryptionException("There was an IO problem during the en- or decryption process. See the stacktrace for details.", e);
}
decryptedByteData = Base64.decode(decryptedByteData);
return decryptedByteData;
}
/**
* Encrypts the given OutputStream using the given X.509 certificate file.
*
* @param unencryptedDataStream
* @param x509CertificateFile
* X.509 certificate file used to encrypt the data. The file is
* expected to be in DER or PEM format
* @return the encrypted OutputStream
* @throws IOException
*
* - if any of the given files does not exist
* - if any of the given files cannot be read
*
* @throws CertificateNotYetValidException
* if the certificate is not yet valid
* @throws CertificateExpiredException
* if the certificate is not valid anymore
* @throws CertificateException
*
* - if the given certificate file does not contain a
* certificate
* - if the certificate contained in the given file is not a
* X.509 certificate
*
* @throws EncryptionException
* if a problem occured during the encryption process
*/
public static OutputStream encrypt(OutputStream unencryptedDataStream, File x509CertificateFile) throws IOException, CertificateNotYetValidException, CertificateExpiredException, CertificateException, EncryptionException {
return new SMIMEEncryptedOutputStream(unencryptedDataStream, x509CertificateFile);
}
/**
* Decrypts the given InputStream using the given X.509 certificate file
* that was used for encryption and the matching private key file.
*
* @param encryptedDataStream
* the InputStream to decrypt
* @param x509CertificateFile
* the X.509 public certificate that was used for encryption
* @param privateKeyFile
* the matching private key file needed for decryption
* @return the decrypted InputStream
* @throws IOException
*
* - if any of the given files does not exist
* - if any of the given files cannot be read
*
* @throws CertificateNotYetValidException
* if the certificate is not yet valid
* @throws CertificateExpiredException
* if the certificate is not valid anymore
* @throws CertificateException
*
* - if the given certificate file does not contain a
* certificate
* - if the certificate contained in the given file is not a
* X.509 certificate
*
* @throws EncryptionException
* if a problem occured during the encryption process
*/
public static InputStream decrypt(InputStream encryptedDataStream, File x509CertificateFile, File privateKeyFile) throws IOException, CertificateNotYetValidException, CertificateExpiredException, CertificateException, EncryptionException {
return new SMIMEDecryptedInputStream(encryptedDataStream, x509CertificateFile, privateKeyFile);
}
public static InputStream decrypt(InputStream encryptedDataStream, String keyAlias) throws IOException, CertificateNotYetValidException, CertificateExpiredException, CertificateException, EncryptionException {
return new SMIMEDecryptedInputStream(encryptedDataStream, keyAlias);
}
/**
* Decrypts the given InputStream using the given X.509 certificate file
* that was used for encryption and the matching private key file.
*
* @param encryptedDataStream
* the InputStream to decrypt
* @param x509CertificateFile
* the X.509 public certificate that was used for encryption
* @param privateKeyFile
* the matching private key file needed for decryption
* @param privateKeyPassword
* password to encrypt private key
* @return the decrypted InputStream
* @throws IOException
*
* - if any of the given files does not exist
* - if any of the given files cannot be read
*
* @throws CertificateNotYetValidException
* if the certificate is not yet valid
* @throws CertificateExpiredException
* if the certificate is not valid anymore
* @throws CertificateException
*
* - if the given certificate file does not contain a
* certificate
* - if the certificate contained in the given file is not a
* X.509 certificate
*
* @throws EncryptionException
* if a problem occured during the encryption process
*/
public static InputStream decrypt(InputStream encryptedDataStream, File x509CertificateFile, File privateKeyFile, final String privateKeyPassword) throws IOException, CertificateNotYetValidException, CertificateExpiredException, CertificateException, EncryptionException {
return new SMIMEDecryptedInputStream(encryptedDataStream, x509CertificateFile, privateKeyFile, privateKeyPassword);
}
public static byte[] decrypt(byte[] encryptedByteData, String keyAlias) throws IOException, CertificateNotYetValidException, CertificateExpiredException, CertificateException, EncryptionException {
byte[] decryptedByteData = new byte[] {};
try {
KeyStore ks = KeyStore.getInstance("PKCS11", "SunPKCS11-verinice");
ks.load(null, null);
Certificate cert = ks.getCertificate(keyAlias);
X509Certificate x509Certificate = (X509Certificate) cert;
PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, null);
MimeBodyPart encryptedMimeBodyPart = new MimeBodyPart(new ByteArrayInputStream(encryptedByteData));
SMIMEEnveloped enveloped = new SMIMEEnveloped(encryptedMimeBodyPart);
// look for our recipient identifier
RecipientId recipientId = new JceKeyTransRecipientId(x509Certificate);
RecipientInformationStore recipients = enveloped.getRecipientInfos();
RecipientInformation recipientInfo = recipients.get(recipientId);
if (recipientInfo != null) {
JceKeyTransRecipient rec = new JceKeyTransEnvelopedRecipient(privateKey);
rec.setProvider("SunPKCS11-verinice");
rec.setContentProvider(BouncyCastleProvider.PROVIDER_NAME);
decryptedByteData = recipientInfo.getContent(rec);
}
} catch (MessagingException e) {
throw new EncryptionException("There was an IO problem during the en- or decryption process. See the stacktrace for details.", e);
} catch (CMSException e) {
throw new EncryptionException("There was an IO problem during the en- or decryption process. See the stacktrace for details.", e);
} catch (GeneralSecurityException e) {
throw new EncryptionException("There was an IO problem during the en- or decryption process. See the stacktrace for details.", e);
}
decryptedByteData = Base64.decode(decryptedByteData);
return decryptedByteData;
}
}