mirror of https://github.com/apache/poi.git
Remove invalid agile certificate encryption
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1874351 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
a5e157c06f
commit
39ab347d99
|
@ -27,14 +27,11 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.security.spec.AlgorithmParameterSpec;
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.Mac;
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.RC2ParameterSpec;
|
import javax.crypto.spec.RC2ParameterSpec;
|
||||||
|
@ -49,7 +46,6 @@ import org.apache.poi.poifs.crypt.Decryptor;
|
||||||
import org.apache.poi.poifs.crypt.EncryptionHeader;
|
import org.apache.poi.poifs.crypt.EncryptionHeader;
|
||||||
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
||||||
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||||
import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier.AgileCertificateEntry;
|
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
@ -90,7 +86,7 @@ public class AgileDecryptor extends Decryptor {
|
||||||
|
|
||||||
byte[] pwHash = hashPassword(password, ver.getHashAlgorithm(), ver.getSalt(), ver.getSpinCount());
|
byte[] pwHash = hashPassword(password, ver.getHashAlgorithm(), ver.getSalt(), ver.getSpinCount());
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* encryptedVerifierHashInput: This attribute MUST be generated by using the following steps:
|
* encryptedVerifierHashInput: This attribute MUST be generated by using the following steps:
|
||||||
* 1. Generate a random array of bytes with the number of bytes used specified by the saltSize
|
* 1. Generate a random array of bytes with the number of bytes used specified by the saltSize
|
||||||
* attribute.
|
* attribute.
|
||||||
|
@ -108,7 +104,7 @@ public class AgileDecryptor extends Decryptor {
|
||||||
MessageDigest hashMD = getMessageDigest(ver.getHashAlgorithm());
|
MessageDigest hashMD = getMessageDigest(ver.getHashAlgorithm());
|
||||||
byte[] verifierHash = hashMD.digest(verfierInputEnc);
|
byte[] verifierHash = hashMD.digest(verfierInputEnc);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* encryptedVerifierHashValue: This attribute MUST be generated by using the following steps:
|
* encryptedVerifierHashValue: This attribute MUST be generated by using the following steps:
|
||||||
* 1. Obtain the hash value of the random array of bytes generated in step 1 of the steps for
|
* 1. Obtain the hash value of the random array of bytes generated in step 1 of the steps for
|
||||||
* encryptedVerifierHashInput.
|
* encryptedVerifierHashInput.
|
||||||
|
@ -123,7 +119,7 @@ public class AgileDecryptor extends Decryptor {
|
||||||
byte[] verifierHashDec = hashInput(ver, pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
|
byte[] verifierHashDec = hashInput(ver, pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
|
||||||
verifierHashDec = getBlock0(verifierHashDec, ver.getHashAlgorithm().hashSize);
|
verifierHashDec = getBlock0(verifierHashDec, ver.getHashAlgorithm().hashSize);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* encryptedKeyValue: This attribute MUST be generated by using the following steps:
|
* encryptedKeyValue: This attribute MUST be generated by using the following steps:
|
||||||
* 1. Generate a random array of bytes that is the same size as specified by the
|
* 1. Generate a random array of bytes that is the same size as specified by the
|
||||||
* Encryptor.KeyData.keyBits attribute of the parent element.
|
* Encryptor.KeyData.keyBits attribute of the parent element.
|
||||||
|
@ -140,7 +136,7 @@ public class AgileDecryptor extends Decryptor {
|
||||||
keyspec = getBlock0(keyspec, header.getKeySize()/8);
|
keyspec = getBlock0(keyspec, header.getKeySize()/8);
|
||||||
SecretKeySpec secretKey = new SecretKeySpec(keyspec, header.getCipherAlgorithm().jceId);
|
SecretKeySpec secretKey = new SecretKeySpec(keyspec, header.getCipherAlgorithm().jceId);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* 1. Obtain the intermediate key by decrypting the encryptedKeyValue from a KeyEncryptor
|
* 1. Obtain the intermediate key by decrypting the encryptedKeyValue from a KeyEncryptor
|
||||||
* contained within the KeyEncryptors sequence. Use this key for encryption operations in the
|
* contained within the KeyEncryptors sequence. Use this key for encryption operations in the
|
||||||
* remaining steps of this section.
|
* remaining steps of this section.
|
||||||
|
@ -159,7 +155,7 @@ public class AgileDecryptor extends Decryptor {
|
||||||
byte[] hmacKey = cipher.doFinal(header.getEncryptedHmacKey());
|
byte[] hmacKey = cipher.doFinal(header.getEncryptedHmacKey());
|
||||||
hmacKey = getBlock0(hmacKey, header.getHashAlgorithm().hashSize);
|
hmacKey = getBlock0(hmacKey, header.getHashAlgorithm().hashSize);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* 5. Generate an HMAC, as specified in [RFC2104], of the encrypted form of the data (message),
|
* 5. Generate an HMAC, as specified in [RFC2104], of the encrypted form of the data (message),
|
||||||
* which the DataIntegrity element will verify by using the Salt generated in step 2 as the key.
|
* which the DataIntegrity element will verify by using the Salt generated in step 2 as the key.
|
||||||
* Note that the entire EncryptedPackage stream (1), including the StreamSize field, MUST be
|
* Note that the entire EncryptedPackage stream (1), including the StreamSize field, MUST be
|
||||||
|
@ -183,69 +179,8 @@ public class AgileDecryptor extends Decryptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* instead of a password, it's also possible to decrypt via certificate.
|
|
||||||
* Warning: this code is experimental and hasn't been validated
|
|
||||||
*
|
|
||||||
* @see <a href="http://social.msdn.microsoft.com/Forums/en-US/cc9092bb-0c82-4b5b-ae21-abf643bdb37c/agile-encryption-with-certificates">Agile encryption with certificates</a>
|
|
||||||
*
|
|
||||||
* @param keyPair
|
|
||||||
* @param x509
|
|
||||||
* @return true, when the data can be successfully decrypted with the given private key
|
|
||||||
* @throws GeneralSecurityException
|
|
||||||
*/
|
|
||||||
public boolean verifyPassword(KeyPair keyPair, X509Certificate x509) throws GeneralSecurityException {
|
|
||||||
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
|
||||||
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
|
||||||
HashAlgorithm hashAlgo = header.getHashAlgorithm();
|
|
||||||
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
|
|
||||||
int blockSize = header.getBlockSize();
|
|
||||||
|
|
||||||
AgileCertificateEntry ace = null;
|
|
||||||
for (AgileCertificateEntry aceEntry : ver.getCertificates()) {
|
|
||||||
if (x509.equals(aceEntry.x509)) {
|
|
||||||
ace = aceEntry;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ace == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance("RSA");
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
|
|
||||||
byte[] keyspec = cipher.doFinal(ace.encryptedKey);
|
|
||||||
SecretKeySpec secretKey = new SecretKeySpec(keyspec, ver.getCipherAlgorithm().jceId);
|
|
||||||
|
|
||||||
Mac x509Hmac = CryptoFunctions.getMac(hashAlgo);
|
|
||||||
x509Hmac.init(secretKey);
|
|
||||||
byte[] certVerifier = x509Hmac.doFinal(ace.x509.getEncoded());
|
|
||||||
|
|
||||||
byte[] vec = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityKeyBlock, blockSize);
|
|
||||||
cipher = getCipher(secretKey, cipherAlgo, header.getChainingMode(), vec, Cipher.DECRYPT_MODE);
|
|
||||||
byte[] hmacKey = cipher.doFinal(header.getEncryptedHmacKey());
|
|
||||||
hmacKey = getBlock0(hmacKey, hashAlgo.hashSize);
|
|
||||||
|
|
||||||
vec = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityValueBlock, blockSize);
|
|
||||||
cipher = getCipher(secretKey, cipherAlgo, header.getChainingMode(), vec, Cipher.DECRYPT_MODE);
|
|
||||||
byte[] hmacValue = cipher.doFinal(header.getEncryptedHmacValue());
|
|
||||||
hmacValue = getBlock0(hmacValue, hashAlgo.hashSize);
|
|
||||||
|
|
||||||
|
|
||||||
if (Arrays.equals(ace.certVerifier, certVerifier)) {
|
|
||||||
setSecretKey(secretKey);
|
|
||||||
setIntegrityHmacKey(hmacKey);
|
|
||||||
setIntegrityHmacValue(hmacValue);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static int getNextBlockSize(int inputLen, int blockSize) {
|
protected static int getNextBlockSize(int inputLen, int blockSize) {
|
||||||
int fillSize;
|
return (int)Math.ceil(inputLen / (double)blockSize) * blockSize;
|
||||||
for (fillSize=blockSize; fillSize<inputLen; fillSize+=blockSize);
|
|
||||||
return fillSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ static byte[] hashInput(AgileEncryptionVerifier ver, byte[] pwHash, byte[] blockKey, byte[] inputKey, int cipherMode) {
|
/* package */ static byte[] hashInput(AgileEncryptionVerifier ver, byte[] pwHash, byte[] blockKey, byte[] inputKey, int cipherMode) {
|
||||||
|
|
|
@ -16,18 +16,11 @@
|
||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.poifs.crypt.agile;
|
package org.apache.poi.poifs.crypt.agile;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.microsoft.schemas.office.x2006.encryption.CTKeyEncryptor;
|
import com.microsoft.schemas.office.x2006.encryption.CTKeyEncryptor;
|
||||||
import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument;
|
import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument;
|
||||||
import com.microsoft.schemas.office.x2006.encryption.STCipherChaining;
|
import com.microsoft.schemas.office.x2006.encryption.STCipherChaining;
|
||||||
import com.microsoft.schemas.office.x2006.keyEncryptor.certificate.CTCertificateKeyEncryptor;
|
|
||||||
import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEncryptor;
|
import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEncryptor;
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
import org.apache.poi.poifs.crypt.ChainingMode;
|
import org.apache.poi.poifs.crypt.ChainingMode;
|
||||||
|
@ -40,24 +33,10 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||||
*/
|
*/
|
||||||
public class AgileEncryptionVerifier extends EncryptionVerifier {
|
public class AgileEncryptionVerifier extends EncryptionVerifier {
|
||||||
|
|
||||||
public static class AgileCertificateEntry {
|
|
||||||
X509Certificate x509;
|
|
||||||
byte[] encryptedKey;
|
|
||||||
byte[] certVerifier;
|
|
||||||
|
|
||||||
public AgileCertificateEntry() {}
|
|
||||||
|
|
||||||
public AgileCertificateEntry(AgileCertificateEntry other) {
|
|
||||||
x509 = other.x509;
|
|
||||||
encryptedKey = (other.encryptedKey == null) ? null : other.encryptedKey.clone();
|
|
||||||
certVerifier = (other.certVerifier == null) ? null : other.certVerifier.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<AgileCertificateEntry> certList = new ArrayList<>();
|
|
||||||
private int keyBits = -1;
|
private int keyBits = -1;
|
||||||
private int blockSize = -1;
|
private int blockSize = -1;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public AgileEncryptionVerifier(String descriptor) {
|
public AgileEncryptionVerifier(String descriptor) {
|
||||||
this(AgileEncryptionInfoBuilder.parseDescriptor(descriptor));
|
this(AgileEncryptionInfoBuilder.parseDescriptor(descriptor));
|
||||||
}
|
}
|
||||||
|
@ -114,24 +93,6 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
|
||||||
default:
|
default:
|
||||||
throw new EncryptedDocumentException("Unsupported chaining mode - "+ keyData.getCipherChaining());
|
throw new EncryptedDocumentException("Unsupported chaining mode - "+ keyData.getCipherChaining());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!encList.hasNext()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
|
||||||
while (encList.hasNext()) {
|
|
||||||
CTCertificateKeyEncryptor certKey = encList.next().getEncryptedCertificateKey();
|
|
||||||
AgileCertificateEntry ace = new AgileCertificateEntry();
|
|
||||||
ace.certVerifier = certKey.getCertVerifier();
|
|
||||||
ace.encryptedKey = certKey.getEncryptedKeyValue();
|
|
||||||
ace.x509 = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(certKey.getX509Certificate()));
|
|
||||||
certList.add(ace);
|
|
||||||
}
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new EncryptedDocumentException("can't parse X509 certificate", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AgileEncryptionVerifier(CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
|
public AgileEncryptionVerifier(CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
|
||||||
|
@ -147,7 +108,6 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
|
||||||
super(other);
|
super(other);
|
||||||
keyBits = other.keyBits;
|
keyBits = other.keyBits;
|
||||||
blockSize = other.blockSize;
|
blockSize = other.blockSize;
|
||||||
other.certList.stream().map(AgileCertificateEntry::new).forEach(certList::add);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -176,16 +136,6 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
|
||||||
super.setEncryptedKey(encryptedKey);
|
super.setEncryptedKey(encryptedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCertificate(X509Certificate x509) {
|
|
||||||
AgileCertificateEntry ace = new AgileCertificateEntry();
|
|
||||||
ace.x509 = x509;
|
|
||||||
certList.add(ace);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<AgileCertificateEntry> getCertificates() {
|
|
||||||
return certList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AgileEncryptionVerifier copy() {
|
public AgileEncryptionVerifier copy() {
|
||||||
return new AgileEncryptionVerifier(this);
|
return new AgileEncryptionVerifier(this);
|
||||||
|
|
|
@ -39,7 +39,6 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.CertificateEncodingException;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
@ -58,7 +57,6 @@ import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument;
|
||||||
import com.microsoft.schemas.office.x2006.encryption.STCipherAlgorithm;
|
import com.microsoft.schemas.office.x2006.encryption.STCipherAlgorithm;
|
||||||
import com.microsoft.schemas.office.x2006.encryption.STCipherChaining;
|
import com.microsoft.schemas.office.x2006.encryption.STCipherChaining;
|
||||||
import com.microsoft.schemas.office.x2006.encryption.STHashAlgorithm;
|
import com.microsoft.schemas.office.x2006.encryption.STHashAlgorithm;
|
||||||
import com.microsoft.schemas.office.x2006.keyEncryptor.certificate.CTCertificateKeyEncryptor;
|
|
||||||
import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEncryptor;
|
import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEncryptor;
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
|
import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
|
||||||
|
@ -67,8 +65,6 @@ import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
|
||||||
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
||||||
import org.apache.poi.poifs.crypt.Encryptor;
|
import org.apache.poi.poifs.crypt.Encryptor;
|
||||||
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||||
import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier.AgileCertificateEntry;
|
|
||||||
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
|
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
import org.apache.poi.util.IOUtils;
|
import org.apache.poi.util.IOUtils;
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
@ -127,7 +123,7 @@ public class AgileEncryptor extends Encryptor {
|
||||||
|
|
||||||
pwHash = hashPassword(password, ver.getHashAlgorithm(), verifierSalt, ver.getSpinCount());
|
pwHash = hashPassword(password, ver.getHashAlgorithm(), verifierSalt, ver.getSpinCount());
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* encryptedVerifierHashInput: This attribute MUST be generated by using the following steps:
|
* encryptedVerifierHashInput: This attribute MUST be generated by using the following steps:
|
||||||
* 1. Generate a random array of bytes with the number of bytes used specified by the saltSize
|
* 1. Generate a random array of bytes with the number of bytes used specified by the saltSize
|
||||||
* attribute.
|
* attribute.
|
||||||
|
@ -144,7 +140,7 @@ public class AgileEncryptor extends Encryptor {
|
||||||
ver.setEncryptedVerifier(encryptedVerifier);
|
ver.setEncryptedVerifier(encryptedVerifier);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* encryptedVerifierHashValue: This attribute MUST be generated by using the following steps:
|
* encryptedVerifierHashValue: This attribute MUST be generated by using the following steps:
|
||||||
* 1. Obtain the hash value of the random array of bytes generated in step 1 of the steps for
|
* 1. Obtain the hash value of the random array of bytes generated in step 1 of the steps for
|
||||||
* encryptedVerifierHashInput.
|
* encryptedVerifierHashInput.
|
||||||
|
@ -161,7 +157,7 @@ public class AgileEncryptor extends Encryptor {
|
||||||
byte[] encryptedVerifierHash = hashInput(ver, pwHash, kHashedVerifierBlock, hashedVerifier, Cipher.ENCRYPT_MODE);
|
byte[] encryptedVerifierHash = hashInput(ver, pwHash, kHashedVerifierBlock, hashedVerifier, Cipher.ENCRYPT_MODE);
|
||||||
ver.setEncryptedVerifierHash(encryptedVerifierHash);
|
ver.setEncryptedVerifierHash(encryptedVerifierHash);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* encryptedKeyValue: This attribute MUST be generated by using the following steps:
|
* encryptedKeyValue: This attribute MUST be generated by using the following steps:
|
||||||
* 1. Generate a random array of bytes that is the same size as specified by the
|
* 1. Generate a random array of bytes that is the same size as specified by the
|
||||||
* Encryptor.KeyData.keyBits attribute of the parent element.
|
* Encryptor.KeyData.keyBits attribute of the parent element.
|
||||||
|
@ -212,15 +208,6 @@ public class AgileEncryptor extends Encryptor {
|
||||||
byte[] hmacKey = getBlock0(this.integritySalt, getNextBlockSize(this.integritySalt.length, blockSize));
|
byte[] hmacKey = getBlock0(this.integritySalt, getNextBlockSize(this.integritySalt.length, blockSize));
|
||||||
byte[] encryptedHmacKey = cipher.doFinal(hmacKey);
|
byte[] encryptedHmacKey = cipher.doFinal(hmacKey);
|
||||||
header.setEncryptedHmacKey(encryptedHmacKey);
|
header.setEncryptedHmacKey(encryptedHmacKey);
|
||||||
|
|
||||||
cipher = Cipher.getInstance("RSA");
|
|
||||||
for (AgileCertificateEntry ace : ver.getCertificates()) {
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, ace.x509.getPublicKey());
|
|
||||||
ace.encryptedKey = cipher.doFinal(getSecretKey().getEncoded());
|
|
||||||
Mac x509Hmac = CryptoFunctions.getMac(header.getHashAlgorithm());
|
|
||||||
x509Hmac.init(getSecretKey());
|
|
||||||
ace.certVerifier = x509Hmac.doFinal(ace.x509.getEncoded());
|
|
||||||
}
|
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new EncryptedDocumentException(e);
|
throw new EncryptedDocumentException(e);
|
||||||
}
|
}
|
||||||
|
@ -276,8 +263,6 @@ public class AgileEncryptor extends Encryptor {
|
||||||
|
|
||||||
private final CTKeyEncryptor.Uri.Enum passwordUri =
|
private final CTKeyEncryptor.Uri.Enum passwordUri =
|
||||||
CTKeyEncryptor.Uri.HTTP_SCHEMAS_MICROSOFT_COM_OFFICE_2006_KEY_ENCRYPTOR_PASSWORD;
|
CTKeyEncryptor.Uri.HTTP_SCHEMAS_MICROSOFT_COM_OFFICE_2006_KEY_ENCRYPTOR_PASSWORD;
|
||||||
private final CTKeyEncryptor.Uri.Enum certificateUri =
|
|
||||||
CTKeyEncryptor.Uri.HTTP_SCHEMAS_MICROSOFT_COM_OFFICE_2006_KEY_ENCRYPTOR_CERTIFICATE;
|
|
||||||
|
|
||||||
protected EncryptionDocument createEncryptionDocument() {
|
protected EncryptionDocument createEncryptionDocument() {
|
||||||
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
|
@ -343,19 +328,6 @@ public class AgileEncryptor extends Encryptor {
|
||||||
hmacData.setEncryptedHmacKey(header.getEncryptedHmacKey());
|
hmacData.setEncryptedHmacKey(header.getEncryptedHmacKey());
|
||||||
hmacData.setEncryptedHmacValue(header.getEncryptedHmacValue());
|
hmacData.setEncryptedHmacValue(header.getEncryptedHmacValue());
|
||||||
|
|
||||||
for (AgileCertificateEntry ace : ver.getCertificates()) {
|
|
||||||
keyEnc = keyEncList.addNewKeyEncryptor();
|
|
||||||
keyEnc.setUri(certificateUri);
|
|
||||||
CTCertificateKeyEncryptor certData = keyEnc.addNewEncryptedCertificateKey();
|
|
||||||
try {
|
|
||||||
certData.setX509Certificate(ace.x509.getEncoded());
|
|
||||||
} catch (CertificateEncodingException e) {
|
|
||||||
throw new EncryptedDocumentException(e);
|
|
||||||
}
|
|
||||||
certData.setEncryptedKeyValue(ace.encryptedKey);
|
|
||||||
certData.setCertVerifier(ace.certVerifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ed;
|
return ed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,7 +344,6 @@ public class AgileEncryptor extends Encryptor {
|
||||||
xo.setCharacterEncoding("UTF-8");
|
xo.setCharacterEncoding("UTF-8");
|
||||||
Map<String,String> nsMap = new HashMap<>();
|
Map<String,String> nsMap = new HashMap<>();
|
||||||
nsMap.put(passwordUri.toString(),"p");
|
nsMap.put(passwordUri.toString(),"p");
|
||||||
nsMap.put(certificateUri.toString(), "c");
|
|
||||||
xo.setUseDefaultNamespace();
|
xo.setUseDefaultNamespace();
|
||||||
xo.setSaveSuggestedPrefixes(nsMap);
|
xo.setSaveSuggestedPrefixes(nsMap);
|
||||||
xo.setSaveNamespacesFirst();
|
xo.setSaveNamespacesFirst();
|
||||||
|
@ -391,31 +362,6 @@ public class AgileEncryptor extends Encryptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
DataSpaceMapUtils.addDefaultDataSpace(dir);
|
|
||||||
|
|
||||||
final EncryptionInfo info = getEncryptionInfo();
|
|
||||||
|
|
||||||
EncryptionRecord er = new EncryptionRecord(){
|
|
||||||
@Override
|
|
||||||
public void write(LittleEndianByteArrayOutputStream bos) {
|
|
||||||
// EncryptionVersionInfo (4 bytes): A Version structure (section 2.1.4), where
|
|
||||||
// Version.vMajor MUST be 0x0004 and Version.vMinor MUST be 0x0004
|
|
||||||
bos.writeShort(info.getVersionMajor());
|
|
||||||
bos.writeShort(info.getVersionMinor());
|
|
||||||
// Reserved (4 bytes): A value that MUST be 0x00000040
|
|
||||||
bos.writeInt(info.getEncryptionFlags());
|
|
||||||
|
|
||||||
EncryptionDocument ed = createEncryptionDocument();
|
|
||||||
marshallEncryptionDocument(ed, bos);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
createEncryptionEntry(dir, "EncryptionInfo", er);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 2.3.4.15 Data Encryption (Agile Encryption)
|
* 2.3.4.15 Data Encryption (Agile Encryption)
|
||||||
*
|
*
|
||||||
|
@ -451,8 +397,23 @@ public class AgileEncryptor extends Encryptor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
|
protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException {
|
||||||
AgileEncryptor.this.createEncryptionInfoEntry(dir, tmpFile);
|
DataSpaceMapUtils.addDefaultDataSpace(dir);
|
||||||
|
createEncryptionEntry(dir, "EncryptionInfo", this::marshallEncryptionRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void marshallEncryptionRecord(LittleEndianByteArrayOutputStream bos) {
|
||||||
|
final EncryptionInfo info = getEncryptionInfo();
|
||||||
|
|
||||||
|
// EncryptionVersionInfo (4 bytes): A Version structure (section 2.1.4), where
|
||||||
|
// Version.vMajor MUST be 0x0004 and Version.vMinor MUST be 0x0004
|
||||||
|
bos.writeShort(info.getVersionMajor());
|
||||||
|
bos.writeShort(info.getVersionMinor());
|
||||||
|
// Reserved (4 bytes): A value that MUST be 0x00000040
|
||||||
|
bos.writeInt(info.getEncryptionFlags());
|
||||||
|
|
||||||
|
EncryptionDocument ed = createEncryptionDocument();
|
||||||
|
marshallEncryptionDocument(ed, bos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ import org.junit.runners.Suite;
|
||||||
, TestDecryptor.class
|
, TestDecryptor.class
|
||||||
, TestEncryptor.class
|
, TestEncryptor.class
|
||||||
, TestAgileEncryptionParameters.class
|
, TestAgileEncryptionParameters.class
|
||||||
, TestCertificateEncryption.class
|
|
||||||
})
|
})
|
||||||
public final class AllPOIFSCryptoTests {
|
public final class AllPOIFSCryptoTests {
|
||||||
}
|
}
|
|
@ -1,197 +0,0 @@
|
||||||
/* ====================================================================
|
|
||||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
contributor license agreements. See the NOTICE file distributed with
|
|
||||||
this work for additional information regarding copyright ownership.
|
|
||||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
(the "License"); you may not use this file except in compliance with
|
|
||||||
the License. You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
==================================================================== */
|
|
||||||
package org.apache.poi.poifs.crypt;
|
|
||||||
|
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
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.PublicKey;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
|
|
||||||
import org.apache.poi.POIDataSamples;
|
|
||||||
import org.apache.poi.poifs.crypt.agile.AgileDecryptor;
|
|
||||||
import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier;
|
|
||||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
|
||||||
import org.apache.poi.util.IOUtils;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
/*
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import sun.security.x509.AlgorithmId;
|
|
||||||
import sun.security.x509.CertificateAlgorithmId;
|
|
||||||
import sun.security.x509.CertificateIssuerName;
|
|
||||||
import sun.security.x509.CertificateSerialNumber;
|
|
||||||
import sun.security.x509.CertificateSubjectName;
|
|
||||||
import sun.security.x509.CertificateValidity;
|
|
||||||
import sun.security.x509.CertificateVersion;
|
|
||||||
import sun.security.x509.CertificateX509Key;
|
|
||||||
import sun.security.x509.X500Name;
|
|
||||||
import sun.security.x509.X509CertImpl;
|
|
||||||
import sun.security.x509.X509CertInfo;
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see <a href="http://stackoverflow.com/questions/1615871/creating-an-x509-certificate-in-java-without-bouncycastle">creating a self-signed certificate</a>
|
|
||||||
*/
|
|
||||||
public class TestCertificateEncryption {
|
|
||||||
/**
|
|
||||||
* how many days from now the Certificate is valid for
|
|
||||||
*/
|
|
||||||
static final int days = 1000;
|
|
||||||
/**
|
|
||||||
* the signing algorithm, eg "SHA1withRSA"
|
|
||||||
*/
|
|
||||||
static final String algorithm = "SHA1withRSA";
|
|
||||||
static final String password = "foobaa";
|
|
||||||
static final String certAlias = "poitest";
|
|
||||||
/**
|
|
||||||
* the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
|
|
||||||
*/
|
|
||||||
static final String certDN = "CN=poitest";
|
|
||||||
// static final File pfxFile = TempFile.createTempFile("poitest", ".pfx");
|
|
||||||
static byte[] pfxFileBytes;
|
|
||||||
|
|
||||||
static class CertData {
|
|
||||||
KeyPair keypair;
|
|
||||||
X509Certificate x509;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a self-signed X.509 Certificate
|
|
||||||
*
|
|
||||||
* The keystore generation / loading is split, because normally the keystore would
|
|
||||||
* already exist.
|
|
||||||
*/
|
|
||||||
/* @BeforeClass
|
|
||||||
public static void initKeystore() throws GeneralSecurityException, IOException {
|
|
||||||
CertData certData = new CertData();
|
|
||||||
|
|
||||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
|
||||||
keyGen.initialize(1024);
|
|
||||||
certData.keypair = keyGen.generateKeyPair();
|
|
||||||
PrivateKey privkey = certData.keypair.getPrivate();
|
|
||||||
PublicKey publkey = certData.keypair.getPublic();
|
|
||||||
|
|
||||||
X509CertInfo info = new X509CertInfo();
|
|
||||||
Date from = new Date();
|
|
||||||
Date to = new Date(from.getTime() + days * 86400000l);
|
|
||||||
CertificateValidity interval = new CertificateValidity(from, to);
|
|
||||||
BigInteger sn = new BigInteger(64, new SecureRandom());
|
|
||||||
X500Name owner = new X500Name(certDN);
|
|
||||||
|
|
||||||
info.set(X509CertInfo.VALIDITY, interval);
|
|
||||||
info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
|
|
||||||
info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
|
|
||||||
info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
|
|
||||||
info.set(X509CertInfo.KEY, new CertificateX509Key(publkey));
|
|
||||||
info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
|
|
||||||
AlgorithmId algo = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
|
|
||||||
info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));
|
|
||||||
|
|
||||||
// Sign the cert to identify the algorithm that's used.
|
|
||||||
X509CertImpl cert = new X509CertImpl(info);
|
|
||||||
cert.sign(privkey, algorithm);
|
|
||||||
|
|
||||||
// Update the algorith, and resign.
|
|
||||||
algo = (AlgorithmId)cert.get(X509CertImpl.SIG_ALG);
|
|
||||||
info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo);
|
|
||||||
cert = new X509CertImpl(info);
|
|
||||||
cert.sign(privkey, algorithm);
|
|
||||||
certData.x509 = cert;
|
|
||||||
|
|
||||||
KeyStore keystore = KeyStore.getInstance("PKCS12");
|
|
||||||
keystore.load(null, password.toCharArray());
|
|
||||||
keystore.setKeyEntry(certAlias, certData.keypair.getPrivate(), password.toCharArray(), new Certificate[]{certData.x509});
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
keystore.store(bos, password.toCharArray());
|
|
||||||
pfxFileBytes = bos.toByteArray();
|
|
||||||
} */
|
|
||||||
|
|
||||||
public CertData loadKeystore()
|
|
||||||
throws GeneralSecurityException, IOException {
|
|
||||||
KeyStore keystore = KeyStore.getInstance("PKCS12");
|
|
||||||
|
|
||||||
// InputStream fis = new ByteArrayInputStream(pfxFileBytes);
|
|
||||||
InputStream fis = POIDataSamples.getPOIFSInstance().openResourceAsStream("poitest.pfx");
|
|
||||||
keystore.load(fis, password.toCharArray());
|
|
||||||
fis.close();
|
|
||||||
|
|
||||||
X509Certificate x509 = (X509Certificate)keystore.getCertificate(certAlias);
|
|
||||||
PrivateKey privateKey = (PrivateKey)keystore.getKey(certAlias, password.toCharArray());
|
|
||||||
PublicKey publicKey = x509.getPublicKey();
|
|
||||||
|
|
||||||
CertData certData = new CertData();
|
|
||||||
certData.keypair = new KeyPair(publicKey, privateKey);
|
|
||||||
certData.x509 = x509;
|
|
||||||
|
|
||||||
return certData;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCertificateEncryption() throws Exception {
|
|
||||||
POIFSFileSystem fs = new POIFSFileSystem();
|
|
||||||
EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile, CipherAlgorithm.aes128, HashAlgorithm.sha1, -1, -1, ChainingMode.cbc);
|
|
||||||
AgileEncryptionVerifier aev = (AgileEncryptionVerifier)info.getVerifier();
|
|
||||||
CertData certData = loadKeystore();
|
|
||||||
aev.addCertificate(certData.x509);
|
|
||||||
|
|
||||||
Encryptor enc = info.getEncryptor();
|
|
||||||
enc.confirmPassword("foobaa");
|
|
||||||
|
|
||||||
File file = POIDataSamples.getDocumentInstance().getFile("VariousPictures.docx");
|
|
||||||
InputStream fis = new FileInputStream(file);
|
|
||||||
byte[] byteExpected = IOUtils.toByteArray(fis);
|
|
||||||
fis.close();
|
|
||||||
|
|
||||||
OutputStream os = enc.getDataStream(fs);
|
|
||||||
IOUtils.copy(new ByteArrayInputStream(byteExpected), os);
|
|
||||||
os.close();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
fs.writeFilesystem(bos);
|
|
||||||
bos.close();
|
|
||||||
|
|
||||||
fs = new POIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));
|
|
||||||
info = new EncryptionInfo(fs);
|
|
||||||
AgileDecryptor agDec = (AgileDecryptor)info.getDecryptor();
|
|
||||||
boolean passed = agDec.verifyPassword(certData.keypair, certData.x509);
|
|
||||||
assertTrue("certificate verification failed", passed);
|
|
||||||
|
|
||||||
fis = agDec.getDataStream(fs);
|
|
||||||
byte[] byteActual = IOUtils.toByteArray(fis);
|
|
||||||
fis.close();
|
|
||||||
|
|
||||||
assertThat(byteExpected, equalTo(byteActual));
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue