mirror of https://github.com/apache/poi.git
Bug 60320 - issue opening password protected xlsx
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1767399 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
61c1b722e7
commit
ff6f6bb7c4
|
@ -210,6 +210,7 @@ public class TestAllFiles {
|
||||||
//EXPECTED_FAILURES.add("poifs/extenxls_pwd123.xlsx");
|
//EXPECTED_FAILURES.add("poifs/extenxls_pwd123.xlsx");
|
||||||
//EXPECTED_FAILURES.add("poifs/protected_agile.docx");
|
//EXPECTED_FAILURES.add("poifs/protected_agile.docx");
|
||||||
EXPECTED_FAILURES.add("spreadsheet/58616.xlsx");
|
EXPECTED_FAILURES.add("spreadsheet/58616.xlsx");
|
||||||
|
EXPECTED_FAILURES.add("poifs/60320-protected.xlsx");
|
||||||
|
|
||||||
// TODO: fails XMLExportTest, is this ok?
|
// TODO: fails XMLExportTest, is this ok?
|
||||||
EXPECTED_FAILURES.add("spreadsheet/CustomXMLMapping-singleattributenamespace.xlsx");
|
EXPECTED_FAILURES.add("spreadsheet/CustomXMLMapping-singleattributenamespace.xlsx");
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.poifs.crypt;
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
|
import org.apache.poi.util.Removal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads and processes OOXML Encryption Headers
|
* Reads and processes OOXML Encryption Headers
|
||||||
* The constants are largely based on ZIP constants.
|
* The constants are largely based on ZIP constants.
|
||||||
|
@ -82,8 +85,19 @@ public abstract class EncryptionHeader implements Cloneable {
|
||||||
|
|
||||||
protected void setCipherAlgorithm(CipherAlgorithm cipherAlgorithm) {
|
protected void setCipherAlgorithm(CipherAlgorithm cipherAlgorithm) {
|
||||||
this.cipherAlgorithm = cipherAlgorithm;
|
this.cipherAlgorithm = cipherAlgorithm;
|
||||||
|
if (cipherAlgorithm.allowedKeySize.length == 1) {
|
||||||
|
setKeySize(cipherAlgorithm.defaultKeySize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashAlgorithm getHashAlgorithm() {
|
||||||
|
return hashAlgorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated POI 3.16 beta 1. use {@link #getHashAlgorithm()}
|
||||||
|
*/
|
||||||
|
@Removal(version="3.18")
|
||||||
public HashAlgorithm getHashAlgorithmEx() {
|
public HashAlgorithm getHashAlgorithmEx() {
|
||||||
return hashAlgorithm;
|
return hashAlgorithm;
|
||||||
}
|
}
|
||||||
|
@ -96,8 +110,21 @@ public abstract class EncryptionHeader implements Cloneable {
|
||||||
return keyBits;
|
return keyBits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the keySize (in bits). Before calling this method, make sure
|
||||||
|
* to set the cipherAlgorithm, as the amount of keyBits gets validated against
|
||||||
|
* the list of allowed keyBits of the corresponding cipherAlgorithm
|
||||||
|
*
|
||||||
|
* @param keyBits
|
||||||
|
*/
|
||||||
protected void setKeySize(int keyBits) {
|
protected void setKeySize(int keyBits) {
|
||||||
this.keyBits = keyBits;
|
this.keyBits = keyBits;
|
||||||
|
for (int allowedBits : getCipherAlgorithm().allowedKeySize) {
|
||||||
|
if (allowedBits == keyBits) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for cipher "+getCipherAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBlockSize() {
|
public int getBlockSize() {
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.poifs.crypt;
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
|
import org.apache.poi.util.Removal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used when checking if a key is valid for a document
|
* Used when checking if a key is valid for a document
|
||||||
*/
|
*/
|
||||||
|
@ -48,10 +50,17 @@ public abstract class EncryptionVerifier implements Cloneable {
|
||||||
return spinCount;
|
return spinCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated POI 3.16 beta 1. use {@link #getChainingMode()}
|
||||||
|
*/
|
||||||
|
@Removal(version="3.18")
|
||||||
public int getCipherMode() {
|
public int getCipherMode() {
|
||||||
return chainingMode.ecmaId;
|
return chainingMode.ecmaId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated POI 3.16 beta 1. use {@link #getCipherAlgorithm()}
|
||||||
|
*/
|
||||||
public int getAlgorithm() {
|
public int getAlgorithm() {
|
||||||
return cipherAlgorithm.ecmaId;
|
return cipherAlgorithm.ecmaId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,7 +108,7 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
|
||||||
bos.writeInt(getFlags());
|
bos.writeInt(getFlags());
|
||||||
bos.writeInt(0); // size extra
|
bos.writeInt(0); // size extra
|
||||||
bos.writeInt(getCipherAlgorithm().ecmaId);
|
bos.writeInt(getCipherAlgorithm().ecmaId);
|
||||||
bos.writeInt(getHashAlgorithmEx().ecmaId);
|
bos.writeInt(getHashAlgorithm().ecmaId);
|
||||||
bos.writeInt(getKeySize());
|
bos.writeInt(getKeySize());
|
||||||
bos.writeInt(getCipherProvider().ecmaId);
|
bos.writeInt(getCipherProvider().ecmaId);
|
||||||
bos.writeInt(0); // reserved1
|
bos.writeInt(0); // reserved1
|
||||||
|
|
|
@ -56,7 +56,7 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
|
||||||
setCipherAlgorithm(header.getCipherAlgorithm());
|
setCipherAlgorithm(header.getCipherAlgorithm());
|
||||||
setChainingMode(header.getChainingMode());
|
setChainingMode(header.getChainingMode());
|
||||||
setEncryptedKey(null);
|
setEncryptedKey(null);
|
||||||
setHashAlgorithm(header.getHashAlgorithmEx());
|
setHashAlgorithm(header.getHashAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected StandardEncryptionVerifier(CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
|
protected StandardEncryptionVerifier(CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
|
||||||
|
|
|
@ -40,13 +40,13 @@ import javax.crypto.spec.RC2ParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
|
import org.apache.poi.poifs.crypt.ChainingMode;
|
||||||
import org.apache.poi.poifs.crypt.ChunkedCipherInputStream;
|
import org.apache.poi.poifs.crypt.ChunkedCipherInputStream;
|
||||||
import org.apache.poi.poifs.crypt.CipherAlgorithm;
|
import org.apache.poi.poifs.crypt.CipherAlgorithm;
|
||||||
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
||||||
import org.apache.poi.poifs.crypt.Decryptor;
|
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.EncryptionVerifier;
|
|
||||||
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.agile.AgileEncryptionVerifier.AgileCertificateEntry;
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
|
@ -93,10 +93,8 @@ public class AgileDecryptor extends Decryptor implements Cloneable {
|
||||||
public boolean verifyPassword(String password) throws GeneralSecurityException {
|
public boolean verifyPassword(String password) throws GeneralSecurityException {
|
||||||
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
||||||
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
|
|
||||||
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
|
|
||||||
int blockSize = header.getBlockSize();
|
int blockSize = header.getBlockSize();
|
||||||
int keySize = header.getKeySize()/8;
|
|
||||||
|
|
||||||
byte[] pwHash = hashPassword(password, ver.getHashAlgorithm(), ver.getSalt(), ver.getSpinCount());
|
byte[] pwHash = hashPassword(password, ver.getHashAlgorithm(), ver.getSalt(), ver.getSpinCount());
|
||||||
|
|
||||||
|
@ -113,9 +111,9 @@ public class AgileDecryptor extends Decryptor implements Cloneable {
|
||||||
* blockSize bytes.
|
* blockSize bytes.
|
||||||
* 4. Use base64 to encode the result of step 3.
|
* 4. Use base64 to encode the result of step 3.
|
||||||
*/
|
*/
|
||||||
byte verfierInputEnc[] = hashInput(getEncryptionInfo(), pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE);
|
byte verfierInputEnc[] = hashInput(ver, pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE);
|
||||||
setVerifier(verfierInputEnc);
|
setVerifier(verfierInputEnc);
|
||||||
MessageDigest hashMD = getMessageDigest(hashAlgo);
|
MessageDigest hashMD = getMessageDigest(ver.getHashAlgorithm());
|
||||||
byte[] verifierHash = hashMD.digest(verfierInputEnc);
|
byte[] verifierHash = hashMD.digest(verfierInputEnc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -130,8 +128,8 @@ public class AgileDecryptor extends Decryptor implements Cloneable {
|
||||||
* blockSize bytes, pad the hash value with 0x00 to an integral multiple of blockSize bytes.
|
* blockSize bytes, pad the hash value with 0x00 to an integral multiple of blockSize bytes.
|
||||||
* 4. Use base64 to encode the result of step 3.
|
* 4. Use base64 to encode the result of step 3.
|
||||||
*/
|
*/
|
||||||
byte verifierHashDec[] = hashInput(getEncryptionInfo(), pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
|
byte verifierHashDec[] = hashInput(ver, pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
|
||||||
verifierHashDec = getBlock0(verifierHashDec, hashAlgo.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:
|
||||||
|
@ -146,9 +144,9 @@ public class AgileDecryptor extends Decryptor implements Cloneable {
|
||||||
* blockSize bytes.
|
* blockSize bytes.
|
||||||
* 4. Use base64 to encode the result of step 3.
|
* 4. Use base64 to encode the result of step 3.
|
||||||
*/
|
*/
|
||||||
byte keyspec[] = hashInput(getEncryptionInfo(), pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE);
|
byte keyspec[] = hashInput(ver, pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE);
|
||||||
keyspec = getBlock0(keyspec, keySize);
|
keyspec = getBlock0(keyspec, header.getKeySize()/8);
|
||||||
SecretKeySpec secretKey = new SecretKeySpec(keyspec, ver.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
|
||||||
|
@ -163,10 +161,11 @@ public class AgileDecryptor extends Decryptor implements Cloneable {
|
||||||
* array with 0x00 to the next integral multiple of blockSize bytes.
|
* array with 0x00 to the next integral multiple of blockSize bytes.
|
||||||
* 4. Assign the encryptedHmacKey attribute to the base64-encoded form of the result of step 3.
|
* 4. Assign the encryptedHmacKey attribute to the base64-encoded form of the result of step 3.
|
||||||
*/
|
*/
|
||||||
byte vec[] = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityKeyBlock, blockSize);
|
byte vec[] = CryptoFunctions.generateIv(header.getHashAlgorithm(), header.getKeySalt(), kIntegrityKeyBlock, blockSize);
|
||||||
Cipher cipher = getCipher(secretKey, cipherAlgo, ver.getChainingMode(), vec, Cipher.DECRYPT_MODE);
|
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
|
||||||
|
Cipher cipher = getCipher(secretKey, cipherAlgo, header.getChainingMode(), vec, Cipher.DECRYPT_MODE);
|
||||||
byte hmacKey[] = cipher.doFinal(header.getEncryptedHmacKey());
|
byte hmacKey[] = cipher.doFinal(header.getEncryptedHmacKey());
|
||||||
hmacKey = getBlock0(hmacKey, hashAlgo.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),
|
||||||
|
@ -177,10 +176,10 @@ public class AgileDecryptor extends Decryptor implements Cloneable {
|
||||||
* 0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, and 0x33.
|
* 0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, and 0x33.
|
||||||
* 7. Assign the encryptedHmacValue attribute to the base64-encoded form of the result of step 6.
|
* 7. Assign the encryptedHmacValue attribute to the base64-encoded form of the result of step 6.
|
||||||
*/
|
*/
|
||||||
vec = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityValueBlock, blockSize);
|
vec = CryptoFunctions.generateIv(header.getHashAlgorithm(), header.getKeySalt(), kIntegrityValueBlock, blockSize);
|
||||||
cipher = getCipher(secretKey, cipherAlgo, ver.getChainingMode(), vec, Cipher.DECRYPT_MODE);
|
cipher = getCipher(secretKey, cipherAlgo, ver.getChainingMode(), vec, Cipher.DECRYPT_MODE);
|
||||||
byte hmacValue[] = cipher.doFinal(header.getEncryptedHmacValue());
|
byte hmacValue[] = cipher.doFinal(header.getEncryptedHmacValue());
|
||||||
hmacValue = getBlock0(hmacValue, hashAlgo.hashSize);
|
hmacValue = getBlock0(hmacValue, header.getHashAlgorithm().hashSize);
|
||||||
|
|
||||||
if (Arrays.equals(verifierHashDec, verifierHash)) {
|
if (Arrays.equals(verifierHashDec, verifierHash)) {
|
||||||
setSecretKey(secretKey);
|
setSecretKey(secretKey);
|
||||||
|
@ -206,7 +205,7 @@ public class AgileDecryptor extends Decryptor implements Cloneable {
|
||||||
public boolean verifyPassword(KeyPair keyPair, X509Certificate x509) throws GeneralSecurityException {
|
public boolean verifyPassword(KeyPair keyPair, X509Certificate x509) throws GeneralSecurityException {
|
||||||
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
||||||
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
|
HashAlgorithm hashAlgo = header.getHashAlgorithm();
|
||||||
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
|
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
|
||||||
int blockSize = header.getBlockSize();
|
int blockSize = header.getBlockSize();
|
||||||
|
|
||||||
|
@ -231,12 +230,12 @@ public class AgileDecryptor extends Decryptor implements Cloneable {
|
||||||
byte certVerifier[] = x509Hmac.doFinal(ace.x509.getEncoded());
|
byte certVerifier[] = x509Hmac.doFinal(ace.x509.getEncoded());
|
||||||
|
|
||||||
byte vec[] = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityKeyBlock, blockSize);
|
byte vec[] = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityKeyBlock, blockSize);
|
||||||
cipher = getCipher(secretKey, cipherAlgo, ver.getChainingMode(), vec, Cipher.DECRYPT_MODE);
|
cipher = getCipher(secretKey, cipherAlgo, header.getChainingMode(), vec, Cipher.DECRYPT_MODE);
|
||||||
byte hmacKey[] = cipher.doFinal(header.getEncryptedHmacKey());
|
byte hmacKey[] = cipher.doFinal(header.getEncryptedHmacKey());
|
||||||
hmacKey = getBlock0(hmacKey, hashAlgo.hashSize);
|
hmacKey = getBlock0(hmacKey, hashAlgo.hashSize);
|
||||||
|
|
||||||
vec = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityValueBlock, blockSize);
|
vec = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityValueBlock, blockSize);
|
||||||
cipher = getCipher(secretKey, cipherAlgo, ver.getChainingMode(), vec, Cipher.DECRYPT_MODE);
|
cipher = getCipher(secretKey, cipherAlgo, header.getChainingMode(), vec, Cipher.DECRYPT_MODE);
|
||||||
byte hmacValue[] = cipher.doFinal(header.getEncryptedHmacValue());
|
byte hmacValue[] = cipher.doFinal(header.getEncryptedHmacValue());
|
||||||
hmacValue = getBlock0(hmacValue, hashAlgo.hashSize);
|
hmacValue = getBlock0(hmacValue, hashAlgo.hashSize);
|
||||||
|
|
||||||
|
@ -257,18 +256,17 @@ public class AgileDecryptor extends Decryptor implements Cloneable {
|
||||||
return fillSize;
|
return fillSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static byte[] hashInput(EncryptionInfo encryptionInfo, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
|
/* package */ static byte[] hashInput(AgileEncryptionVerifier ver, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
|
||||||
EncryptionVerifier ver = encryptionInfo.getVerifier();
|
CipherAlgorithm cipherAlgo = ver.getCipherAlgorithm();
|
||||||
AgileDecryptor dec = (AgileDecryptor)encryptionInfo.getDecryptor();
|
ChainingMode chainMode = ver.getChainingMode();
|
||||||
int keySize = dec.getKeySizeInBytes();
|
int keySize = ver.getKeySize()/8;
|
||||||
int blockSize = dec.getBlockSizeInBytes();
|
int blockSize = ver.getBlockSize();
|
||||||
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
||||||
byte[] salt = ver.getSalt();
|
|
||||||
|
|
||||||
byte intermedKey[] = generateKey(pwHash, hashAlgo, blockKey, keySize);
|
byte intermedKey[] = generateKey(pwHash, hashAlgo, blockKey, keySize);
|
||||||
SecretKey skey = new SecretKeySpec(intermedKey, ver.getCipherAlgorithm().jceId);
|
SecretKey skey = new SecretKeySpec(intermedKey, cipherAlgo.jceId);
|
||||||
byte[] iv = generateIv(hashAlgo, salt, null, blockSize);
|
byte[] iv = generateIv(hashAlgo, ver.getSalt(), null, blockSize);
|
||||||
Cipher cipher = getCipher(skey, ver.getCipherAlgorithm(), ver.getChainingMode(), iv, cipherMode);
|
Cipher cipher = getCipher(skey, cipherAlgo, chainMode, iv, cipherMode);
|
||||||
byte[] hashFinal;
|
byte[] hashFinal;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -281,7 +279,6 @@ public class AgileDecryptor extends Decryptor implements Cloneable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
|
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
|
||||||
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
|
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
|
||||||
_length = dis.readLong();
|
_length = dis.readLong();
|
||||||
|
@ -307,7 +304,7 @@ public class AgileDecryptor extends Decryptor implements Cloneable {
|
||||||
|
|
||||||
byte[] blockKey = new byte[4];
|
byte[] blockKey = new byte[4];
|
||||||
LittleEndian.putInt(blockKey, 0, block);
|
LittleEndian.putInt(blockKey, 0, block);
|
||||||
byte[] iv = generateIv(header.getHashAlgorithmEx(), header.getKeySalt(), blockKey, header.getBlockSize());
|
byte[] iv = generateIv(header.getHashAlgorithm(), header.getKeySalt(), blockKey, header.getBlockSize());
|
||||||
|
|
||||||
AlgorithmParameterSpec aps;
|
AlgorithmParameterSpec aps;
|
||||||
if (header.getCipherAlgorithm() == CipherAlgorithm.rc2) {
|
if (header.getCipherAlgorithm() == CipherAlgorithm.rc2) {
|
||||||
|
|
|
@ -45,18 +45,18 @@ public class AgileEncryptionHeader extends EncryptionHeader implements Cloneable
|
||||||
throw new EncryptedDocumentException("Unable to parse keyData");
|
throw new EncryptedDocumentException("Unable to parse keyData");
|
||||||
}
|
}
|
||||||
|
|
||||||
setKeySize((int)keyData.getKeyBits());
|
|
||||||
setFlags(0);
|
|
||||||
setSizeExtra(0);
|
|
||||||
setCspName(null);
|
|
||||||
setBlockSize(keyData.getBlockSize());
|
|
||||||
|
|
||||||
int keyBits = (int)keyData.getKeyBits();
|
int keyBits = (int)keyData.getKeyBits();
|
||||||
|
|
||||||
CipherAlgorithm ca = CipherAlgorithm.fromXmlId(keyData.getCipherAlgorithm().toString(), keyBits);
|
CipherAlgorithm ca = CipherAlgorithm.fromXmlId(keyData.getCipherAlgorithm().toString(), keyBits);
|
||||||
setCipherAlgorithm(ca);
|
setCipherAlgorithm(ca);
|
||||||
setCipherProvider(ca.provider);
|
setCipherProvider(ca.provider);
|
||||||
|
|
||||||
|
setKeySize(keyBits);
|
||||||
|
setFlags(0);
|
||||||
|
setSizeExtra(0);
|
||||||
|
setCspName(null);
|
||||||
|
setBlockSize(keyData.getBlockSize());
|
||||||
|
|
||||||
switch (keyData.getCipherChaining().intValue()) {
|
switch (keyData.getCipherChaining().intValue()) {
|
||||||
case STCipherChaining.INT_CHAINING_MODE_CBC:
|
case STCipherChaining.INT_CHAINING_MODE_CBC:
|
||||||
setChainingMode(ChainingMode.cbc);
|
setChainingMode(ChainingMode.cbc);
|
||||||
|
@ -73,7 +73,7 @@ public class AgileEncryptionHeader extends EncryptionHeader implements Cloneable
|
||||||
HashAlgorithm ha = HashAlgorithm.fromEcmaId(keyData.getHashAlgorithm().toString());
|
HashAlgorithm ha = HashAlgorithm.fromEcmaId(keyData.getHashAlgorithm().toString());
|
||||||
setHashAlgorithm(ha);
|
setHashAlgorithm(ha);
|
||||||
|
|
||||||
if (getHashAlgorithmEx().hashSize != hashSize) {
|
if (getHashAlgorithm().hashSize != hashSize) {
|
||||||
throw new EncryptedDocumentException("Unsupported hash algorithm: " +
|
throw new EncryptedDocumentException("Unsupported hash algorithm: " +
|
||||||
keyData.getHashAlgorithm() + " @ " + hashSize + " bytes");
|
keyData.getHashAlgorithm() + " @ " + hashSize + " bytes");
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.security.GeneralSecurityException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -48,6 +49,8 @@ public class AgileEncryptionVerifier extends EncryptionVerifier implements Clone
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<AgileCertificateEntry> certList = new ArrayList<AgileCertificateEntry>();
|
private List<AgileCertificateEntry> certList = new ArrayList<AgileCertificateEntry>();
|
||||||
|
private int keyBits = -1;
|
||||||
|
private int blockSize = -1;
|
||||||
|
|
||||||
public AgileEncryptionVerifier(String descriptor) {
|
public AgileEncryptionVerifier(String descriptor) {
|
||||||
this(AgileEncryptionInfoBuilder.parseDescriptor(descriptor));
|
this(AgileEncryptionInfoBuilder.parseDescriptor(descriptor));
|
||||||
|
@ -66,10 +69,14 @@ public class AgileEncryptionVerifier extends EncryptionVerifier implements Clone
|
||||||
}
|
}
|
||||||
|
|
||||||
int keyBits = (int)keyData.getKeyBits();
|
int keyBits = (int)keyData.getKeyBits();
|
||||||
|
|
||||||
CipherAlgorithm ca = CipherAlgorithm.fromXmlId(keyData.getCipherAlgorithm().toString(), keyBits);
|
CipherAlgorithm ca = CipherAlgorithm.fromXmlId(keyData.getCipherAlgorithm().toString(), keyBits);
|
||||||
setCipherAlgorithm(ca);
|
setCipherAlgorithm(ca);
|
||||||
|
|
||||||
|
setKeySize(keyBits);
|
||||||
|
|
||||||
|
int blockSize = keyData.getBlockSize();
|
||||||
|
setBlockSize(blockSize);
|
||||||
|
|
||||||
int hashSize = keyData.getHashSize();
|
int hashSize = keyData.getHashSize();
|
||||||
|
|
||||||
HashAlgorithm ha = HashAlgorithm.fromEcmaId(keyData.getHashAlgorithm().toString());
|
HashAlgorithm ha = HashAlgorithm.fromEcmaId(keyData.getHashAlgorithm().toString());
|
||||||
|
@ -125,6 +132,8 @@ public class AgileEncryptionVerifier extends EncryptionVerifier implements Clone
|
||||||
setCipherAlgorithm(cipherAlgorithm);
|
setCipherAlgorithm(cipherAlgorithm);
|
||||||
setHashAlgorithm(hashAlgorithm);
|
setHashAlgorithm(hashAlgorithm);
|
||||||
setChainingMode(chainingMode);
|
setChainingMode(chainingMode);
|
||||||
|
setKeySize(keyBits);
|
||||||
|
setBlockSize(blockSize);
|
||||||
setSpinCount(100000); // TODO: use parameter
|
setSpinCount(100000); // TODO: use parameter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,4 +180,60 @@ public class AgileEncryptionVerifier extends EncryptionVerifier implements Clone
|
||||||
other.certList = new ArrayList<AgileCertificateEntry>(certList);
|
other.certList = new ArrayList<AgileCertificateEntry>(certList);
|
||||||
return other;
|
return other;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The keysize (in bits) of the verifier data. This usually equals the keysize of the header,
|
||||||
|
* but only on a few exceptions, like files generated by Office for Mac, can be
|
||||||
|
* different.
|
||||||
|
*
|
||||||
|
* @return the keysize (in bits) of the verifier.
|
||||||
|
*/
|
||||||
|
public int getKeySize() {
|
||||||
|
return keyBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The blockSize (in bytes) of the verifier data.
|
||||||
|
* This usually equals the blocksize of the header.
|
||||||
|
*
|
||||||
|
* @return the blockSize (in bytes) of the verifier,
|
||||||
|
*/
|
||||||
|
public int getBlockSize() {
|
||||||
|
return blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the keysize (in bits) of the verifier
|
||||||
|
*
|
||||||
|
* @param keyBits the keysize (in bits)
|
||||||
|
*/
|
||||||
|
protected void setKeySize(int keyBits) {
|
||||||
|
this.keyBits = keyBits;
|
||||||
|
for (int allowedBits : getCipherAlgorithm().allowedKeySize) {
|
||||||
|
if (allowedBits == keyBits) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for cipher "+getCipherAlgorithm());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the blockSize (in bytes) of the verifier
|
||||||
|
*
|
||||||
|
* @param blockSize the blockSize (in bytes)
|
||||||
|
*/
|
||||||
|
protected void setBlockSize(int blockSize) {
|
||||||
|
this.blockSize = blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setCipherAlgorithm(CipherAlgorithm cipherAlgorithm) {
|
||||||
|
super.setCipherAlgorithm(cipherAlgorithm);
|
||||||
|
if (cipherAlgorithm.allowedKeySize.length == 1) {
|
||||||
|
setKeySize(cipherAlgorithm.defaultKeySize);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,9 +86,10 @@ public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
public void confirmPassword(String password) {
|
public void confirmPassword(String password) {
|
||||||
// see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
|
// see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
|
||||||
Random r = new SecureRandom();
|
Random r = new SecureRandom();
|
||||||
int blockSize = getEncryptionInfo().getHeader().getBlockSize();
|
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
||||||
int keySize = getEncryptionInfo().getHeader().getKeySize()/8;
|
int blockSize = header.getBlockSize();
|
||||||
int hashSize = getEncryptionInfo().getHeader().getHashAlgorithmEx().hashSize;
|
int keySize = header.getKeySize()/8;
|
||||||
|
int hashSize = header.getHashAlgorithm().hashSize;
|
||||||
|
|
||||||
byte[] newVerifierSalt = new byte[blockSize]
|
byte[] newVerifierSalt = new byte[blockSize]
|
||||||
, newVerifier = new byte[blockSize]
|
, newVerifier = new byte[blockSize]
|
||||||
|
@ -107,14 +108,14 @@ public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
@Override
|
@Override
|
||||||
public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {
|
public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {
|
||||||
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
||||||
ver.setSalt(verifierSalt);
|
|
||||||
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
||||||
|
|
||||||
|
ver.setSalt(verifierSalt);
|
||||||
header.setKeySalt(keySalt);
|
header.setKeySalt(keySalt);
|
||||||
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
|
||||||
|
|
||||||
int blockSize = header.getBlockSize();
|
int blockSize = header.getBlockSize();
|
||||||
|
|
||||||
pwHash = hashPassword(password, hashAlgo, 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:
|
||||||
|
@ -129,7 +130,7 @@ public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
* blockSize bytes.
|
* blockSize bytes.
|
||||||
* 4. Use base64 to encode the result of step 3.
|
* 4. Use base64 to encode the result of step 3.
|
||||||
*/
|
*/
|
||||||
byte encryptedVerifier[] = hashInput(getEncryptionInfo(), pwHash, kVerifierInputBlock, verifier, Cipher.ENCRYPT_MODE);
|
byte encryptedVerifier[] = hashInput(ver, pwHash, kVerifierInputBlock, verifier, Cipher.ENCRYPT_MODE);
|
||||||
ver.setEncryptedVerifier(encryptedVerifier);
|
ver.setEncryptedVerifier(encryptedVerifier);
|
||||||
|
|
||||||
|
|
||||||
|
@ -145,9 +146,9 @@ public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
* blockSize bytes, pad the hash value with 0x00 to an integral multiple of blockSize bytes.
|
* blockSize bytes, pad the hash value with 0x00 to an integral multiple of blockSize bytes.
|
||||||
* 4. Use base64 to encode the result of step 3.
|
* 4. Use base64 to encode the result of step 3.
|
||||||
*/
|
*/
|
||||||
MessageDigest hashMD = getMessageDigest(hashAlgo);
|
MessageDigest hashMD = getMessageDigest(ver.getHashAlgorithm());
|
||||||
byte[] hashedVerifier = hashMD.digest(verifier);
|
byte[] hashedVerifier = hashMD.digest(verifier);
|
||||||
byte encryptedVerifierHash[] = hashInput(getEncryptionInfo(), pwHash, kHashedVerifierBlock, hashedVerifier, Cipher.ENCRYPT_MODE);
|
byte encryptedVerifierHash[] = hashInput(ver, pwHash, kHashedVerifierBlock, hashedVerifier, Cipher.ENCRYPT_MODE);
|
||||||
ver.setEncryptedVerifierHash(encryptedVerifierHash);
|
ver.setEncryptedVerifierHash(encryptedVerifierHash);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -163,10 +164,10 @@ public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
* blockSize bytes.
|
* blockSize bytes.
|
||||||
* 4. Use base64 to encode the result of step 3.
|
* 4. Use base64 to encode the result of step 3.
|
||||||
*/
|
*/
|
||||||
byte encryptedKey[] = hashInput(getEncryptionInfo(), pwHash, kCryptoKeyBlock, keySpec, Cipher.ENCRYPT_MODE);
|
byte encryptedKey[] = hashInput(ver, pwHash, kCryptoKeyBlock, keySpec, Cipher.ENCRYPT_MODE);
|
||||||
ver.setEncryptedKey(encryptedKey);
|
ver.setEncryptedKey(encryptedKey);
|
||||||
|
|
||||||
SecretKey secretKey = new SecretKeySpec(keySpec, ver.getCipherAlgorithm().jceId);
|
SecretKey secretKey = new SecretKeySpec(keySpec, header.getCipherAlgorithm().jceId);
|
||||||
setSecretKey(secretKey);
|
setSecretKey(secretKey);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -196,17 +197,17 @@ public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
this.integritySalt = integritySalt.clone();
|
this.integritySalt = integritySalt.clone();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte vec[] = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityKeyBlock, header.getBlockSize());
|
byte vec[] = CryptoFunctions.generateIv(header.getHashAlgorithm(), header.getKeySalt(), kIntegrityKeyBlock, header.getBlockSize());
|
||||||
Cipher cipher = getCipher(secretKey, ver.getCipherAlgorithm(), ver.getChainingMode(), vec, Cipher.ENCRYPT_MODE);
|
Cipher cipher = getCipher(secretKey, header.getCipherAlgorithm(), header.getChainingMode(), vec, Cipher.ENCRYPT_MODE);
|
||||||
byte filledSalt[] = getBlock0(this.integritySalt, getNextBlockSize(this.integritySalt.length, blockSize));
|
byte hmacKey[] = getBlock0(this.integritySalt, getNextBlockSize(this.integritySalt.length, blockSize));
|
||||||
byte encryptedHmacKey[] = cipher.doFinal(filledSalt);
|
byte encryptedHmacKey[] = cipher.doFinal(hmacKey);
|
||||||
header.setEncryptedHmacKey(encryptedHmacKey);
|
header.setEncryptedHmacKey(encryptedHmacKey);
|
||||||
|
|
||||||
cipher = Cipher.getInstance("RSA");
|
cipher = Cipher.getInstance("RSA");
|
||||||
for (AgileCertificateEntry ace : ver.getCertificates()) {
|
for (AgileCertificateEntry ace : ver.getCertificates()) {
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, ace.x509.getPublicKey());
|
cipher.init(Cipher.ENCRYPT_MODE, ace.x509.getPublicKey());
|
||||||
ace.encryptedKey = cipher.doFinal(getSecretKey().getEncoded());
|
ace.encryptedKey = cipher.doFinal(getSecretKey().getEncoded());
|
||||||
Mac x509Hmac = CryptoFunctions.getMac(hashAlgo);
|
Mac x509Hmac = CryptoFunctions.getMac(header.getHashAlgorithm());
|
||||||
x509Hmac.init(getSecretKey());
|
x509Hmac.init(getSecretKey());
|
||||||
ace.certVerifier = x509Hmac.doFinal(ace.x509.getEncoded());
|
ace.certVerifier = x509Hmac.doFinal(ace.x509.getEncoded());
|
||||||
}
|
}
|
||||||
|
@ -236,10 +237,12 @@ public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
// as the integrity hmac needs to contain the StreamSize,
|
// as the integrity hmac needs to contain the StreamSize,
|
||||||
// it's not possible to calculate it on-the-fly while buffering
|
// it's not possible to calculate it on-the-fly while buffering
|
||||||
// TODO: add stream size parameter to getDataStream()
|
// TODO: add stream size parameter to getDataStream()
|
||||||
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
|
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
||||||
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
int blockSize = header.getBlockSize();
|
||||||
|
HashAlgorithm hashAlgo = header.getHashAlgorithm();
|
||||||
Mac integrityMD = CryptoFunctions.getMac(hashAlgo);
|
Mac integrityMD = CryptoFunctions.getMac(hashAlgo);
|
||||||
integrityMD.init(new SecretKeySpec(integritySalt, hashAlgo.jceHmacId));
|
byte hmacKey[] = getBlock0(this.integritySalt, getNextBlockSize(this.integritySalt.length, blockSize));
|
||||||
|
integrityMD.init(new SecretKeySpec(hmacKey, hashAlgo.jceHmacId));
|
||||||
|
|
||||||
byte buf[] = new byte[1024];
|
byte buf[] = new byte[1024];
|
||||||
LittleEndian.putLong(buf, 0, oleStreamSize);
|
LittleEndian.putLong(buf, 0, oleStreamSize);
|
||||||
|
@ -256,12 +259,10 @@ public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
}
|
}
|
||||||
|
|
||||||
byte hmacValue[] = integrityMD.doFinal();
|
byte hmacValue[] = integrityMD.doFinal();
|
||||||
|
|
||||||
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
|
|
||||||
int blockSize = header.getBlockSize();
|
|
||||||
byte iv[] = CryptoFunctions.generateIv(header.getHashAlgorithmEx(), header.getKeySalt(), kIntegrityValueBlock, blockSize);
|
|
||||||
Cipher cipher = CryptoFunctions.getCipher(getSecretKey(), header.getCipherAlgorithm(), header.getChainingMode(), iv, Cipher.ENCRYPT_MODE);
|
|
||||||
byte hmacValueFilled[] = getBlock0(hmacValue, getNextBlockSize(hmacValue.length, blockSize));
|
byte hmacValueFilled[] = getBlock0(hmacValue, getNextBlockSize(hmacValue.length, blockSize));
|
||||||
|
|
||||||
|
byte iv[] = CryptoFunctions.generateIv(header.getHashAlgorithm(), header.getKeySalt(), kIntegrityValueBlock, blockSize);
|
||||||
|
Cipher cipher = CryptoFunctions.getCipher(getSecretKey(), header.getCipherAlgorithm(), header.getChainingMode(), iv, Cipher.ENCRYPT_MODE);
|
||||||
byte encryptedHmacValue[] = cipher.doFinal(hmacValueFilled);
|
byte encryptedHmacValue[] = cipher.doFinal(hmacValueFilled);
|
||||||
|
|
||||||
header.setEncryptedHmacValue(encryptedHmacValue);
|
header.setEncryptedHmacValue(encryptedHmacValue);
|
||||||
|
@ -288,18 +289,21 @@ public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
keyPass.setSpinCount(ver.getSpinCount());
|
keyPass.setSpinCount(ver.getSpinCount());
|
||||||
|
|
||||||
keyData.setSaltSize(header.getBlockSize());
|
keyData.setSaltSize(header.getBlockSize());
|
||||||
keyPass.setSaltSize(header.getBlockSize());
|
keyPass.setSaltSize(ver.getBlockSize());
|
||||||
|
|
||||||
keyData.setBlockSize(header.getBlockSize());
|
keyData.setBlockSize(header.getBlockSize());
|
||||||
keyPass.setBlockSize(header.getBlockSize());
|
keyPass.setBlockSize(ver.getBlockSize());
|
||||||
|
|
||||||
keyData.setKeyBits(header.getKeySize());
|
keyData.setKeyBits(header.getKeySize());
|
||||||
keyPass.setKeyBits(header.getKeySize());
|
keyPass.setKeyBits(ver.getKeySize());
|
||||||
|
|
||||||
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
|
keyData.setHashSize(header.getHashAlgorithm().hashSize);
|
||||||
keyData.setHashSize(hashAlgo.hashSize);
|
keyPass.setHashSize(ver.getHashAlgorithm().hashSize);
|
||||||
keyPass.setHashSize(hashAlgo.hashSize);
|
|
||||||
|
|
||||||
|
// header and verifier have to have the same cipher algorithm
|
||||||
|
if (!header.getCipherAlgorithm().xmlId.equals(ver.getCipherAlgorithm().xmlId)) {
|
||||||
|
throw new EncryptedDocumentException("Cipher algorithm of header and verifier have to match");
|
||||||
|
}
|
||||||
STCipherAlgorithm.Enum xmlCipherAlgo = STCipherAlgorithm.Enum.forString(header.getCipherAlgorithm().xmlId);
|
STCipherAlgorithm.Enum xmlCipherAlgo = STCipherAlgorithm.Enum.forString(header.getCipherAlgorithm().xmlId);
|
||||||
if (xmlCipherAlgo == null) {
|
if (xmlCipherAlgo == null) {
|
||||||
throw new EncryptedDocumentException("CipherAlgorithm "+header.getCipherAlgorithm()+" not supported.");
|
throw new EncryptedDocumentException("CipherAlgorithm "+header.getCipherAlgorithm()+" not supported.");
|
||||||
|
@ -320,12 +324,8 @@ public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
throw new EncryptedDocumentException("ChainingMode "+header.getChainingMode()+" not supported.");
|
throw new EncryptedDocumentException("ChainingMode "+header.getChainingMode()+" not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
STHashAlgorithm.Enum xmlHashAlgo = STHashAlgorithm.Enum.forString(hashAlgo.ecmaString);
|
keyData.setHashAlgorithm(mapHashAlgorithm(header.getHashAlgorithm()));
|
||||||
if (xmlHashAlgo == null) {
|
keyPass.setHashAlgorithm(mapHashAlgorithm(ver.getHashAlgorithm()));
|
||||||
throw new EncryptedDocumentException("HashAlgorithm "+hashAlgo+" not supported.");
|
|
||||||
}
|
|
||||||
keyData.setHashAlgorithm(xmlHashAlgo);
|
|
||||||
keyPass.setHashAlgorithm(xmlHashAlgo);
|
|
||||||
|
|
||||||
keyData.setSaltValue(header.getKeySalt());
|
keyData.setSaltValue(header.getKeySalt());
|
||||||
keyPass.setSaltValue(ver.getSalt());
|
keyPass.setSaltValue(ver.getSalt());
|
||||||
|
@ -353,6 +353,14 @@ public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
return ed;
|
return ed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static STHashAlgorithm.Enum mapHashAlgorithm(HashAlgorithm hashAlgo) {
|
||||||
|
STHashAlgorithm.Enum xmlHashAlgo = STHashAlgorithm.Enum.forString(hashAlgo.ecmaString);
|
||||||
|
if (xmlHashAlgo == null) {
|
||||||
|
throw new EncryptedDocumentException("HashAlgorithm "+hashAlgo+" not supported.");
|
||||||
|
}
|
||||||
|
return xmlHashAlgo;
|
||||||
|
}
|
||||||
|
|
||||||
protected void marshallEncryptionDocument(EncryptionDocument ed, LittleEndianByteArrayOutputStream os) {
|
protected void marshallEncryptionDocument(EncryptionDocument ed, LittleEndianByteArrayOutputStream os) {
|
||||||
XmlOptions xo = new XmlOptions();
|
XmlOptions xo = new XmlOptions();
|
||||||
xo.setCharacterEncoding("UTF-8");
|
xo.setCharacterEncoding("UTF-8");
|
||||||
|
@ -371,7 +379,7 @@ public class AgileEncryptor extends Encryptor implements Cloneable {
|
||||||
try {
|
try {
|
||||||
bos.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n".getBytes("UTF-8"));
|
bos.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n".getBytes("UTF-8"));
|
||||||
ed.save(bos, xo);
|
ed.save(bos, xo);
|
||||||
os.write(bos.toByteArray());
|
bos.writeTo(os);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new EncryptedDocumentException("error marshalling encryption info document", e);
|
throw new EncryptedDocumentException("error marshalling encryption info document", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,10 +37,6 @@ import org.apache.poi.util.IOUtils;
|
||||||
import org.apache.poi.xssf.XSSFTestDataSamples;
|
import org.apache.poi.xssf.XSSFTestDataSamples;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Maxim Valyanskiy
|
|
||||||
* @author Gary King
|
|
||||||
*/
|
|
||||||
public class TestDecryptor {
|
public class TestDecryptor {
|
||||||
@Test
|
@Test
|
||||||
public void passwordVerification() throws IOException, GeneralSecurityException {
|
public void passwordVerification() throws IOException, GeneralSecurityException {
|
||||||
|
@ -162,4 +158,22 @@ public class TestDecryptor {
|
||||||
//dec.verifyPassword(null);
|
//dec.verifyPassword(null);
|
||||||
dec.getDataStream(pfs);
|
dec.getDataStream(pfs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void bug60320() throws IOException, GeneralSecurityException {
|
||||||
|
InputStream is = POIDataSamples.getPOIFSInstance().openResourceAsStream("60320-protected.xlsx");
|
||||||
|
POIFSFileSystem fs = new POIFSFileSystem(is);
|
||||||
|
is.close();
|
||||||
|
|
||||||
|
EncryptionInfo info = new EncryptionInfo(fs);
|
||||||
|
|
||||||
|
Decryptor d = Decryptor.getInstance(info);
|
||||||
|
|
||||||
|
boolean b = d.verifyPassword("Test001!!");
|
||||||
|
assertTrue(b);
|
||||||
|
|
||||||
|
zipOk(fs.getRoot(), d);
|
||||||
|
|
||||||
|
fs.close();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -35,7 +35,7 @@ public class TestEncryptionInfo {
|
||||||
assertEquals(2, info.getVersionMinor());
|
assertEquals(2, info.getVersionMinor());
|
||||||
|
|
||||||
assertEquals(CipherAlgorithm.aes128, info.getHeader().getCipherAlgorithm());
|
assertEquals(CipherAlgorithm.aes128, info.getHeader().getCipherAlgorithm());
|
||||||
assertEquals(HashAlgorithm.sha1, info.getHeader().getHashAlgorithmEx());
|
assertEquals(HashAlgorithm.sha1, info.getHeader().getHashAlgorithm());
|
||||||
assertEquals(128, info.getHeader().getKeySize());
|
assertEquals(128, info.getHeader().getKeySize());
|
||||||
assertEquals(32, info.getVerifier().getEncryptedVerifierHash().length);
|
assertEquals(32, info.getVerifier().getEncryptedVerifierHash().length);
|
||||||
assertEquals(CipherProvider.aes, info.getHeader().getCipherProvider());
|
assertEquals(CipherProvider.aes, info.getHeader().getCipherProvider());
|
||||||
|
@ -54,7 +54,7 @@ public class TestEncryptionInfo {
|
||||||
assertEquals(4, info.getVersionMinor());
|
assertEquals(4, info.getVersionMinor());
|
||||||
|
|
||||||
assertEquals(CipherAlgorithm.aes256, info.getHeader().getCipherAlgorithm());
|
assertEquals(CipherAlgorithm.aes256, info.getHeader().getCipherAlgorithm());
|
||||||
assertEquals(HashAlgorithm.sha512, info.getHeader().getHashAlgorithmEx());
|
assertEquals(HashAlgorithm.sha512, info.getHeader().getHashAlgorithm());
|
||||||
assertEquals(256, info.getHeader().getKeySize());
|
assertEquals(256, info.getHeader().getKeySize());
|
||||||
assertEquals(64, info.getVerifier().getEncryptedVerifierHash().length);
|
assertEquals(64, info.getVerifier().getEncryptedVerifierHash().length);
|
||||||
assertEquals(CipherProvider.aes, info.getHeader().getCipherProvider());
|
assertEquals(CipherProvider.aes, info.getHeader().getCipherProvider());
|
||||||
|
|
|
@ -36,7 +36,9 @@ import javax.crypto.Cipher;
|
||||||
import org.apache.poi.POIDataSamples;
|
import org.apache.poi.POIDataSamples;
|
||||||
import org.apache.poi.openxml4j.opc.ContentTypes;
|
import org.apache.poi.openxml4j.opc.ContentTypes;
|
||||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||||
|
import org.apache.poi.poifs.crypt.agile.AgileDecryptor;
|
||||||
import org.apache.poi.poifs.crypt.agile.AgileEncryptionHeader;
|
import org.apache.poi.poifs.crypt.agile.AgileEncryptionHeader;
|
||||||
|
import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier;
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
import org.apache.poi.poifs.filesystem.DocumentNode;
|
import org.apache.poi.poifs.filesystem.DocumentNode;
|
||||||
import org.apache.poi.poifs.filesystem.Entry;
|
import org.apache.poi.poifs.filesystem.Entry;
|
||||||
|
@ -379,4 +381,142 @@ public class TestEncryptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* this test simulates the generation of bugs 60320 sample file
|
||||||
|
* as the padding bytes of the EncryptedPackage stream are random or in POIs case PKCS5-padded
|
||||||
|
* one would need to mock those bytes to get the same hmacValues - see diff below
|
||||||
|
*
|
||||||
|
* this use-case is experimental - for the time being the setters of the encryption classes
|
||||||
|
* are spreaded between two packages and are protected - so you would need to violate
|
||||||
|
* the packages rules and provide a helper class in the *poifs.crypt package-namespace.
|
||||||
|
* the default way of defining the encryption settings is via the EncryptionInfo class
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void bug60320CustomEncrypt() throws Exception {
|
||||||
|
// --- src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java (revision 1766745)
|
||||||
|
// +++ src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java (working copy)
|
||||||
|
// @@ -208,6 +208,13 @@
|
||||||
|
// protected int invokeCipher(int posInChunk, boolean doFinal) throws GeneralSecurityException {
|
||||||
|
// byte plain[] = (_plainByteFlags.isEmpty()) ? null : _chunk.clone();
|
||||||
|
//
|
||||||
|
// + if (posInChunk < 4096) {
|
||||||
|
// + _cipher.update(_chunk, 0, posInChunk, _chunk);
|
||||||
|
// + byte bla[] = { (byte)0x7A,(byte)0x0F,(byte)0x27,(byte)0xF0,(byte)0x17,(byte)0x6E,(byte)0x77,(byte)0x05,(byte)0xB9,(byte)0xDA,(byte)0x49,(byte)0xF9,(byte)0xD7,(byte)0x8E,(byte)0x03,(byte)0x1D };
|
||||||
|
// + System.arraycopy(bla, 0, _chunk, posInChunk-2, bla.length);
|
||||||
|
// + return posInChunk-2+bla.length;
|
||||||
|
// + }
|
||||||
|
// +
|
||||||
|
// int ciLen = (doFinal)
|
||||||
|
// ? _cipher.doFinal(_chunk, 0, posInChunk, _chunk)
|
||||||
|
// : _cipher.update(_chunk, 0, posInChunk, _chunk);
|
||||||
|
//
|
||||||
|
// --- src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java (revision 1766745)
|
||||||
|
// +++ src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java (working copy)
|
||||||
|
//
|
||||||
|
// @@ -300,7 +297,7 @@
|
||||||
|
// protected static Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk, EncryptionInfo encryptionInfo, SecretKey skey, int encryptionMode)
|
||||||
|
// throws GeneralSecurityException {
|
||||||
|
// EncryptionHeader header = encryptionInfo.getHeader();
|
||||||
|
// - String padding = (lastChunk ? "PKCS5Padding" : "NoPadding");
|
||||||
|
// + String padding = "NoPadding"; // (lastChunk ? "PKCS5Padding" : "NoPadding");
|
||||||
|
// if (existing == null || !existing.getAlgorithm().endsWith(padding)) {
|
||||||
|
// existing = getCipher(skey, header.getCipherAlgorithm(), header.getChainingMode(), header.getKeySalt(), encryptionMode, padding);
|
||||||
|
// }
|
||||||
|
|
||||||
|
InputStream is = POIDataSamples.getPOIFSInstance().openResourceAsStream("60320-protected.xlsx");
|
||||||
|
POIFSFileSystem fsOrig = new POIFSFileSystem(is);
|
||||||
|
is.close();
|
||||||
|
EncryptionInfo infoOrig = new EncryptionInfo(fsOrig);
|
||||||
|
Decryptor decOrig = infoOrig.getDecryptor();
|
||||||
|
boolean b = decOrig.verifyPassword("Test001!!");
|
||||||
|
assertTrue(b);
|
||||||
|
InputStream decIn = decOrig.getDataStream(fsOrig);
|
||||||
|
byte[] zipInput = IOUtils.toByteArray(decIn);
|
||||||
|
decIn.close();
|
||||||
|
|
||||||
|
InputStream epOrig = fsOrig.getRoot().createDocumentInputStream("EncryptedPackage");
|
||||||
|
// ignore the 16 padding bytes
|
||||||
|
byte[] epOrigBytes = IOUtils.toByteArray(epOrig, 9400);
|
||||||
|
epOrig.close();
|
||||||
|
|
||||||
|
EncryptionInfo eiNew = new EncryptionInfo(EncryptionMode.agile);
|
||||||
|
AgileEncryptionHeader aehHeader = (AgileEncryptionHeader)eiNew.getHeader();
|
||||||
|
aehHeader.setCipherAlgorithm(CipherAlgorithm.aes128);
|
||||||
|
aehHeader.setHashAlgorithm(HashAlgorithm.sha1);
|
||||||
|
AgileEncryptionVerifier aehVerifier = (AgileEncryptionVerifier)eiNew.getVerifier();
|
||||||
|
|
||||||
|
// this cast might look strange - if the setters would be public, it will become obsolete
|
||||||
|
// see http://stackoverflow.com/questions/5637650/overriding-protected-methods-in-java
|
||||||
|
((EncryptionVerifier)aehVerifier).setCipherAlgorithm(CipherAlgorithm.aes256);
|
||||||
|
aehVerifier.setHashAlgorithm(HashAlgorithm.sha512);
|
||||||
|
|
||||||
|
Encryptor enc = eiNew.getEncryptor();
|
||||||
|
enc.confirmPassword("Test001!!",
|
||||||
|
infoOrig.getDecryptor().getSecretKey().getEncoded(),
|
||||||
|
infoOrig.getHeader().getKeySalt(),
|
||||||
|
infoOrig.getDecryptor().getVerifier(),
|
||||||
|
infoOrig.getVerifier().getSalt(),
|
||||||
|
infoOrig.getDecryptor().getIntegrityHmacKey()
|
||||||
|
);
|
||||||
|
NPOIFSFileSystem fsNew = new NPOIFSFileSystem();
|
||||||
|
OutputStream os = enc.getDataStream(fsNew);
|
||||||
|
os.write(zipInput);
|
||||||
|
os.close();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
fsNew.writeFilesystem(bos);
|
||||||
|
fsNew.close();
|
||||||
|
|
||||||
|
NPOIFSFileSystem fsReload = new NPOIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));
|
||||||
|
InputStream epReload = fsReload.getRoot().createDocumentInputStream("EncryptedPackage");
|
||||||
|
byte[] epNewBytes = IOUtils.toByteArray(epReload, 9400);
|
||||||
|
epReload.close();
|
||||||
|
|
||||||
|
assertArrayEquals(epOrigBytes, epNewBytes);
|
||||||
|
|
||||||
|
EncryptionInfo infoReload = new EncryptionInfo(fsOrig);
|
||||||
|
Decryptor decReload = infoReload.getDecryptor();
|
||||||
|
b = decReload.verifyPassword("Test001!!");
|
||||||
|
assertTrue(b);
|
||||||
|
|
||||||
|
AgileEncryptionHeader aehOrig = (AgileEncryptionHeader)infoOrig.getHeader();
|
||||||
|
AgileEncryptionHeader aehReload = (AgileEncryptionHeader)infoReload.getHeader();
|
||||||
|
assertEquals(aehOrig.getBlockSize(), aehReload.getBlockSize());
|
||||||
|
assertEquals(aehOrig.getChainingMode(), aehReload.getChainingMode());
|
||||||
|
assertEquals(aehOrig.getCipherAlgorithm(), aehReload.getCipherAlgorithm());
|
||||||
|
assertEquals(aehOrig.getCipherProvider(), aehReload.getCipherProvider());
|
||||||
|
assertEquals(aehOrig.getCspName(), aehReload.getCspName());
|
||||||
|
assertArrayEquals(aehOrig.getEncryptedHmacKey(), aehReload.getEncryptedHmacKey());
|
||||||
|
// this only works, when the paddings are mocked to be the same ...
|
||||||
|
// assertArrayEquals(aehOrig.getEncryptedHmacValue(), aehReload.getEncryptedHmacValue());
|
||||||
|
assertEquals(aehOrig.getFlags(), aehReload.getFlags());
|
||||||
|
assertEquals(aehOrig.getHashAlgorithm(), aehReload.getHashAlgorithm());
|
||||||
|
assertArrayEquals(aehOrig.getKeySalt(), aehReload.getKeySalt());
|
||||||
|
assertEquals(aehOrig.getKeySize(), aehReload.getKeySize());
|
||||||
|
|
||||||
|
AgileEncryptionVerifier aevOrig = (AgileEncryptionVerifier)infoOrig.getVerifier();
|
||||||
|
AgileEncryptionVerifier aevReload = (AgileEncryptionVerifier)infoReload.getVerifier();
|
||||||
|
assertEquals(aevOrig.getBlockSize(), aevReload.getBlockSize());
|
||||||
|
assertEquals(aevOrig.getChainingMode(), aevReload.getChainingMode());
|
||||||
|
assertEquals(aevOrig.getCipherAlgorithm(), aevReload.getCipherAlgorithm());
|
||||||
|
assertArrayEquals(aevOrig.getEncryptedKey(), aevReload.getEncryptedKey());
|
||||||
|
assertArrayEquals(aevOrig.getEncryptedVerifier(), aevReload.getEncryptedVerifier());
|
||||||
|
assertArrayEquals(aevOrig.getEncryptedVerifierHash(), aevReload.getEncryptedVerifierHash());
|
||||||
|
assertEquals(aevOrig.getHashAlgorithm(), aevReload.getHashAlgorithm());
|
||||||
|
assertEquals(aevOrig.getKeySize(), aevReload.getKeySize());
|
||||||
|
assertArrayEquals(aevOrig.getSalt(), aevReload.getSalt());
|
||||||
|
assertEquals(aevOrig.getSpinCount(), aevReload.getSpinCount());
|
||||||
|
|
||||||
|
AgileDecryptor adOrig = (AgileDecryptor)infoOrig.getDecryptor();
|
||||||
|
AgileDecryptor adReload = (AgileDecryptor)infoReload.getDecryptor();
|
||||||
|
|
||||||
|
assertArrayEquals(adOrig.getIntegrityHmacKey(), adReload.getIntegrityHmacKey());
|
||||||
|
// doesn't work without mocking ... see above
|
||||||
|
// assertArrayEquals(adOrig.getIntegrityHmacValue(), adReload.getIntegrityHmacValue());
|
||||||
|
assertArrayEquals(adOrig.getSecretKey().getEncoded(), adReload.getSecretKey().getEncoded());
|
||||||
|
assertArrayEquals(adOrig.getVerifier(), adReload.getVerifier());
|
||||||
|
|
||||||
|
fsReload.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue