You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by ki...@apache.org on 2013/12/25 00:13:22 UTC

svn commit: r1553336 [2/4] - in /poi: site/src/documentation/content/xdocs/ trunk/ trunk/src/java/org/apache/poi/ trunk/src/java/org/apache/poi/poifs/crypt/ trunk/src/java/org/apache/poi/poifs/crypt/standard/ trunk/src/ooxml/java/org/apache/poi/ trunk/...

Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,66 @@
+/* ====================================================================
+   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 org.apache.poi.EncryptedDocumentException;
+
+public enum HashAlgorithm {
+    none     (         "", 0x0000,           "",  0,               "", false),
+    sha1     (    "SHA-1", 0x8004,       "SHA1", 20,       "HmacSHA1", false),
+    sha256   (  "SHA-256", 0x800C,     "SHA256", 32,     "HmacSHA256", false),
+    sha384   (  "SHA-384", 0x800D,     "SHA384", 48,     "HmacSHA384", false),
+    sha512   (  "SHA-512", 0x800E,     "SHA512", 64,     "HmacSHA512", false),
+    /* only for agile encryption */
+    md5      (      "MD5",     -1,        "MD5", 16,        "HmacMD5", false),
+    // although sunjc2 supports md2, hmac-md2 is only supported by bouncycastle
+    md2      (      "MD2",     -1,        "MD2", 16,       "Hmac-MD2", true),
+    ripemd128("RipeMD128",     -1, "RIPEMD-128", 16, "HMac-RipeMD128", true),
+    ripemd160("RipeMD160",     -1, "RIPEMD-160", 20, "HMac-RipeMD160", true),
+    whirlpool("Whirlpool",     -1,  "WHIRLPOOL", 64, "HMac-Whirlpool", true),
+    ;
+
+    public final String jceId;
+    public final int ecmaId;
+    public final String ecmaString;
+    public final int hashSize;
+    public final String jceHmacId;
+    public final boolean needsBouncyCastle;
+    
+    HashAlgorithm(String jceId, int ecmaId, String ecmaString, int hashSize, String jceHmacId, boolean needsBouncyCastle) {
+        this.jceId = jceId;
+        this.ecmaId = ecmaId;
+        this.ecmaString = ecmaString;
+        this.hashSize = hashSize;
+        this.jceHmacId = jceHmacId;
+        this.needsBouncyCastle = needsBouncyCastle;
+    }
+    
+    public static HashAlgorithm fromEcmaId(int ecmaId) {
+        for (HashAlgorithm ha : values()) {
+            if (ha.ecmaId == ecmaId) return ha;
+        }
+        throw new EncryptedDocumentException("hash algorithm not found");
+    }    
+    
+    public static HashAlgorithm fromEcmaId(String ecmaString) {
+        for (HashAlgorithm ha : values()) {
+            if (ha.ecmaString.equals(ecmaString)) return ha;
+        }
+        throw new EncryptedDocumentException("hash algorithm not found");
+    }
+}
\ No newline at end of file

Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/package.html
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/package.html?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/package.html (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/package.html Tue Dec 24 23:13:21 2013
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<!--
+   ====================================================================
+   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.
+   ====================================================================
+-->
+<html>
+<head>
+</head>
+<body bgcolor="white">
+
+<p>Implementation of the <a href="http://msdn.microsoft.com/en-us/library/dd952186(v=office.12).aspx">ECMA-376 Document Encryption</a></p>
+<p>The implementation is split into three packages:</p>
+<ul>
+<li>This package contains common functions for both current implemented cipher modes.</li>
+<li>the {@link org.apache.poi.poifs.crypt.standard standard} package is part of the base poi jar and contains classes for the standard encryption ...</li>
+<li>the {@link org.apache.poi.poifs.crypt.agile agile} package is part of the poi ooxml jar and the provides agile encryption support.</li>
+</ul>
+
+<h2>Related Documentation</h2>
+
+Some implementations informations can be found under:
+<ul>
+<li><a href="http://poi.apache.org/encryption.html">Apache POI - Encryption support</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+@see org.apache.poi.poifs.crypt.standard
+@see org.apache.poi.poifs.crypt.agile
+</body>
+</html>

Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/EncryptionRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/EncryptionRecord.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/EncryptionRecord.java (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/EncryptionRecord.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,23 @@
+/* ====================================================================
+   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.standard;
+
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+
+public interface EncryptionRecord {
+    void write(LittleEndianByteArrayOutputStream os);
+}

Copied: poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java (from r1541009, poi/trunk/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java)
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java?p2=poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java&p1=poi/trunk/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java&r1=1541009&r2=1553336&rev=1553336&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java Tue Dec 24 23:13:21 2013
@@ -14,7 +14,9 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-package org.apache.poi.poifs.crypt;
+package org.apache.poi.poifs.crypt.standard;
+
+import static org.apache.poi.poifs.crypt.CryptoFunctions.hashPassword;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -28,70 +30,90 @@ import javax.crypto.CipherInputStream;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
 
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.crypt.EncryptionHeader;
+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.filesystem.DirectoryNode;
 import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.util.BoundedInputStream;
 import org.apache.poi.util.LittleEndian;
 
 /**
  */
-public class EcmaDecryptor extends Decryptor {
-    private final EncryptionInfo info;
-    private byte[] passwordHash;
+public class StandardDecryptor extends Decryptor {
     private long _length = -1;
 
-    public EcmaDecryptor(EncryptionInfo info) {
-        this.info = info;
+    protected StandardDecryptor(EncryptionInfo info) {
+        super(info);
     }
 
-    private byte[] generateKey(int block) throws NoSuchAlgorithmException {
-        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-
-        sha1.update(passwordHash);
-        byte[] blockValue = new byte[4];
-        LittleEndian.putInt(blockValue, 0, block);
-        byte[] finalHash = sha1.digest(blockValue);
-
-        int requiredKeyLength = info.getHeader().getKeySize()/8;
-
-        byte[] buff = new byte[64];
-
-        Arrays.fill(buff, (byte) 0x36);
-
-        for (int i=0; i<finalHash.length; i++) {
-            buff[i] = (byte) (buff[i] ^ finalHash[i]);
+    public boolean verifyPassword(String password) {
+        EncryptionVerifier ver = info.getVerifier();
+        SecretKey skey = generateSecretKey(password, ver, getKeySizeInBytes());
+        Cipher cipher = getCipher(skey);
+
+        try {
+            byte encryptedVerifier[] = ver.getEncryptedVerifier();
+            byte verifier[] = cipher.doFinal(encryptedVerifier);
+            setVerifier(verifier);
+            MessageDigest sha1 = MessageDigest.getInstance(ver.getHashAlgorithm().jceId);
+            byte[] calcVerifierHash = sha1.digest(verifier);
+            byte encryptedVerifierHash[] = ver.getEncryptedVerifierHash();
+            byte decryptedVerifierHash[] = cipher.doFinal(encryptedVerifierHash);
+            byte[] verifierHash = truncateOrPad(decryptedVerifierHash, calcVerifierHash.length);
+    
+            if (Arrays.equals(calcVerifierHash, verifierHash)) {
+                setSecretKey(skey);
+                return true;
+            } else {
+                return false;
+            }
+        } catch (GeneralSecurityException e) {
+            throw new EncryptedDocumentException(e);
         }
-
-        sha1.reset();
-        byte[] x1 = sha1.digest(buff);
-
-        Arrays.fill(buff, (byte) 0x5c);
-        for (int i=0; i<finalHash.length; i++) {
-            buff[i] = (byte) (buff[i] ^ finalHash[i]);
-        }
-
-        sha1.reset();
-        byte[] x2 = sha1.digest(buff);
+    }
+    
+    protected static SecretKey generateSecretKey(String password, EncryptionVerifier ver, int keySize) {
+        HashAlgorithm hashAlgo = ver.getHashAlgorithm();
+
+        byte pwHash[] = hashPassword(password, hashAlgo, ver.getSalt(), ver.getSpinCount());
+
+        byte[] blockKey = new byte[4];
+        LittleEndian.putInt(blockKey, 0, 0);
+
+        byte[] finalHash = CryptoFunctions.generateKey(pwHash, hashAlgo, blockKey, hashAlgo.hashSize);
+        byte x1[] = fillAndXor(finalHash, (byte) 0x36);
+        byte x2[] = fillAndXor(finalHash, (byte) 0x5c);
 
         byte[] x3 = new byte[x1.length + x2.length];
         System.arraycopy(x1, 0, x3, 0, x1.length);
         System.arraycopy(x2, 0, x3, x1.length, x2.length);
+        
+        byte[] key = truncateOrPad(x3, keySize);
 
-        return truncateOrPad(x3, requiredKeyLength);
+        SecretKey skey = new SecretKeySpec(key, ver.getCipherAlgorithm().jceId);
+        return skey;
     }
 
-    public boolean verifyPassword(String password) throws GeneralSecurityException {
-        passwordHash = hashPassword(info, password);
-
-        Cipher cipher = getCipher();
-
-        byte[] verifier = cipher.doFinal(info.getVerifier().getVerifier());
-
-        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-        byte[] calcVerifierHash = sha1.digest(verifier);
+    protected static byte[] fillAndXor(byte hash[], byte fillByte) {
+        byte[] buff = new byte[64];
+        Arrays.fill(buff, fillByte);
 
-        byte[] verifierHash = truncateOrPad(cipher.doFinal(info.getVerifier().getVerifierHash()), calcVerifierHash.length);
+        for (int i=0; i<hash.length; i++) {
+            buff[i] = (byte) (buff[i] ^ hash[i]);
+        }
 
-        return Arrays.equals(calcVerifierHash, verifierHash);
+        try {
+            MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+            return sha1.digest(buff);
+        } catch (NoSuchAlgorithmException e) {
+            throw new EncryptedDocumentException("hash algo not supported", e);
+        }
     }
 
     /**
@@ -99,7 +121,7 @@ public class EcmaDecryptor extends Decry
      *  truncated or zero padded as needed.
      * Behaves like Arrays.copyOf in Java 1.6
      */
-    private byte[] truncateOrPad(byte[] source, int length) {
+    protected static byte[] truncateOrPad(byte[] source, int length) {
        byte[] result = new byte[length];
        System.arraycopy(source, 0, result, 0, Math.min(length, source.length));
        if(length > source.length) {
@@ -110,25 +132,27 @@ public class EcmaDecryptor extends Decry
        return result;
     }
 
-    private Cipher getCipher() throws GeneralSecurityException {
-        byte[] key = generateKey(0);
-        Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
-        SecretKey skey = new SecretKeySpec(key, "AES");
-        cipher.init(Cipher.DECRYPT_MODE, skey);
-
-        return cipher;
+    private Cipher getCipher(SecretKey key) {
+        EncryptionHeader em = info.getHeader();
+        ChainingMode cm = em.getChainingMode();
+        assert(cm == ChainingMode.ecb);
+        return CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), cm, null, Cipher.DECRYPT_MODE);
     }
 
-    public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
+    public InputStream getDataStream(DirectoryNode dir) throws IOException {
         DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage");
 
         _length = dis.readLong();
 
-        return new CipherInputStream(dis, getCipher());
+        return new BoundedInputStream(new CipherInputStream(dis, getCipher(getSecretKey())), _length);
     }
 
     public long getLength(){
-        if(_length == -1) throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called");
+        if(_length == -1) throw new IllegalStateException("Decryptor.getDataStream() was not called");
         return _length;
     }
+
+    protected int getKeySizeInBytes() {
+        return info.getHeader().getKeySize()/8;
+    }
 }

Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,116 @@
+/* ====================================================================
+   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.standard;
+
+import static org.apache.poi.poifs.crypt.CryptoFunctions.getUtf16LeString;
+
+import java.io.IOException;
+
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.CipherProvider;
+import org.apache.poi.poifs.crypt.EncryptionHeader;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianOutput;
+
+public class StandardEncryptionHeader extends EncryptionHeader implements EncryptionRecord {
+    // A flag that specifies whether CryptoAPI RC4 or ECMA-376 encryption 
+    // [ECMA-376] is used. It MUST be 1 unless fExternal is 1. If fExternal is 1, it MUST be 0.
+    private static BitField flagsCryptoAPI = new BitField(0x04);
+
+    // A value that MUST be 0 if document properties are encrypted. The 
+    // encryption of document properties is specified in section 2.3.5.4 [MS-OFFCRYPTO].
+    @SuppressWarnings("unused")
+    private static BitField flagsDocProps = new BitField(0x08);
+    
+    // A value that MUST be 1 if extensible encryption is used,. If this value is 1, 
+    // the value of every other field in this structure MUST be 0.
+    @SuppressWarnings("unused")
+    private static BitField flagsExternal = new BitField(0x10);
+    
+    // A value that MUST be 1 if the protected content is an ECMA-376 document 
+    // [ECMA-376]. If the fAES bit is 1, the fCryptoAPI bit MUST also be 1.
+    private static BitField flagsAES = new BitField(0x20);
+    
+    protected StandardEncryptionHeader(DocumentInputStream is) throws IOException {
+        setFlags(is.readInt());
+        setSizeExtra(is.readInt());
+        setCipherAlgorithm(CipherAlgorithm.fromEcmaId(is.readInt()));
+        setHashAlgorithm(HashAlgorithm.fromEcmaId(is.readInt()));
+        setKeySize(is.readInt());
+        setBlockSize(getKeySize());
+        setCipherProvider(CipherProvider.fromEcmaId(is.readInt()));
+
+        is.readLong(); // skip reserved
+
+        // CSPName may not always be specified
+        // In some cases, the salt value of the EncryptionVerifier is the next chunk of data
+        is.mark(LittleEndianConsts.INT_SIZE+1);
+        int checkForSalt = is.readInt();
+        is.reset();
+        
+        if (checkForSalt == 16) {
+            setCspName("");
+        } else {
+            StringBuilder builder = new StringBuilder();
+            while (true) {
+                char c = (char) is.readShort();
+                if (c == 0) break;
+                builder.append(c);
+            }
+            setCspName(builder.toString());
+        }
+        
+        setChainingMode(ChainingMode.ecb);
+        setKeySalt(null);
+    }
+
+    protected StandardEncryptionHeader(CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
+        setCipherAlgorithm(cipherAlgorithm);
+        setHashAlgorithm(hashAlgorithm);
+        setKeySize(keyBits);
+        setBlockSize(blockSize);
+        setCipherProvider(cipherAlgorithm.provider);
+        setFlags(flagsCryptoAPI.setBoolean(0, true)
+                | flagsAES.setBoolean(0, cipherAlgorithm.provider == CipherProvider.aes));
+        // see http://msdn.microsoft.com/en-us/library/windows/desktop/bb931357(v=vs.85).aspx for a full list
+        // setCspName("Microsoft Enhanced RSA and AES Cryptographic Provider");
+    }
+    
+    public void write(LittleEndianByteArrayOutputStream bos) {
+        int startIdx = bos.getWriteIndex();
+        LittleEndianOutput sizeOutput = bos.createDelayedOutput(LittleEndianConsts.INT_SIZE);
+        bos.writeInt(getFlags());
+        bos.writeInt(0); // size extra
+        bos.writeInt(getCipherAlgorithm().ecmaId);
+        bos.writeInt(getHashAlgorithmEx().ecmaId);
+        bos.writeInt(getKeySize());
+        bos.writeInt(getCipherProvider().ecmaId);
+        bos.writeInt(0); // reserved1
+        bos.writeInt(0); // reserved2
+        if (getCspName() != null) {
+            bos.write(getUtf16LeString(getCspName()));
+            bos.writeShort(0);
+        }
+        int headerSize = bos.getWriteIndex()-startIdx-LittleEndianConsts.INT_SIZE;
+        sizeOutput.writeInt(headerSize);        
+    }
+}

Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,106 @@
+/* ====================================================================
+   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.standard;
+
+import java.io.IOException;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+
+public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder {
+    
+    EncryptionInfo info;
+    StandardEncryptionHeader header;
+    StandardEncryptionVerifier verifier;
+    StandardDecryptor decryptor;
+    StandardEncryptor encryptor;
+
+    public void initialize(EncryptionInfo info, DocumentInputStream dis) throws IOException {
+        this.info = info;
+        
+        @SuppressWarnings("unused")
+        int hSize = dis.readInt();
+        header = new StandardEncryptionHeader(dis);
+        verifier = new StandardEncryptionVerifier(dis, header);
+
+        if (info.getVersionMinor() == 2 && (info.getVersionMajor() == 3 || info.getVersionMajor() == 4)) {
+            decryptor = new StandardDecryptor(info);
+        }
+    }
+    
+    public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
+        this.info = info;
+
+        if (cipherAlgorithm == null) {
+            cipherAlgorithm = CipherAlgorithm.rc4;
+        }
+        if (hashAlgorithm == null) {
+            hashAlgorithm = HashAlgorithm.sha1;
+        }
+        if (hashAlgorithm != HashAlgorithm.sha1) {
+            throw new EncryptedDocumentException("Standard encryption only supports SHA-1.");
+        }
+        if (chainingMode == null) {
+            chainingMode = ChainingMode.ecb;
+        }
+        if (chainingMode != ChainingMode.ecb) {
+            throw new EncryptedDocumentException("Standard encryption only supports ECB chaining.");
+        }
+        if (keyBits == -1) {
+            keyBits = cipherAlgorithm.defaultKeySize;
+        }
+        if (blockSize == -1) {
+            blockSize = cipherAlgorithm.blockSize;
+        }
+        boolean found = false;
+        for (int ks : cipherAlgorithm.allowedKeySize) {
+            found |= (ks == keyBits);
+        }
+        if (!found) {
+            throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString());
+        }
+        header = new StandardEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
+        verifier = new StandardEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
+        decryptor = new StandardDecryptor(info);
+        encryptor = new StandardEncryptor(this);
+    }
+
+    public StandardEncryptionHeader getHeader() {
+        return header;
+    }
+
+    public StandardEncryptionVerifier getVerifier() {
+        return verifier;
+    }
+
+    public StandardDecryptor getDecryptor() {
+        return decryptor;
+    }
+
+    public StandardEncryptor getEncryptor() {
+        return encryptor;
+    }
+    
+    public EncryptionInfo getEncryptionInfo() {
+        return info;
+    }
+}

Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,112 @@
+/* ====================================================================
+   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.standard;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.EncryptionVerifier;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+
+/**
+ * Used when checking if a key is valid for a document 
+ */
+public class StandardEncryptionVerifier extends EncryptionVerifier implements EncryptionRecord {
+    private static final int SPIN_COUNT = 50000;
+    private final int verifierHashSize;
+    
+    protected StandardEncryptionVerifier(DocumentInputStream is, StandardEncryptionHeader header) {
+        int saltSize = is.readInt();
+
+        if (saltSize!=16) {
+            throw new RuntimeException("Salt size != 16 !?");
+        }
+
+        byte salt[] = new byte[16];
+        is.readFully(salt);
+        setSalt(salt);
+        
+        byte encryptedVerifier[] = new byte[16];
+        is.readFully(encryptedVerifier);
+        setEncryptedVerifier(encryptedVerifier);
+
+        verifierHashSize = is.readInt();
+
+        byte encryptedVerifierHash[] = new byte[header.getCipherAlgorithm().encryptedVerifierHashLength];
+        is.readFully(encryptedVerifierHash);
+        setEncryptedVerifierHash(encryptedVerifierHash);
+
+        setSpinCount(SPIN_COUNT);
+        setCipherAlgorithm(CipherAlgorithm.aes128);
+        setChainingMode(ChainingMode.ecb);
+        setEncryptedKey(null);
+        setHashAlgorithm(HashAlgorithm.sha1); 
+    }
+    
+    protected StandardEncryptionVerifier(CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
+        setCipherAlgorithm(cipherAlgorithm);
+        setHashAlgorithm(hashAlgorithm);
+        setChainingMode(chainingMode);
+        setSpinCount(SPIN_COUNT);
+        verifierHashSize = hashAlgorithm.hashSize;
+    }
+
+    // make method visible for this package
+    protected void setSalt(byte salt[]) {
+        if (salt == null || salt.length != 16) {
+            throw new EncryptedDocumentException("invalid verifier salt");
+        }
+        super.setSalt(salt);
+    }
+    
+    // make method visible for this package
+    protected void setEncryptedVerifier(byte encryptedVerifier[]) {
+        super.setEncryptedVerifier(encryptedVerifier);
+    }
+
+    // make method visible for this package
+    protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
+        super.setEncryptedVerifierHash(encryptedVerifierHash);
+    }
+    
+    public void write(LittleEndianByteArrayOutputStream bos) {
+        // see [MS-OFFCRYPTO] - 2.3.4.9
+        byte salt[] = getSalt();
+        assert(salt.length == 16);
+        bos.writeInt(salt.length); // salt size
+        bos.write(salt);
+        
+        // The resulting Verifier value MUST be an array of 16 bytes.
+        byte encryptedVerifier[] = getEncryptedVerifier(); 
+        assert(encryptedVerifier.length == 16);
+        bos.write(encryptedVerifier);
+
+        // The number of bytes used by the encrypted Verifier hash MUST be 32.
+        // The number of bytes used by the decrypted Verifier hash is given by
+        // the VerifierHashSize field, which MUST be 20
+        byte encryptedVerifierHash[] = getEncryptedVerifierHash(); 
+        assert(encryptedVerifierHash.length == 32);
+        bos.writeInt(20);
+        bos.write(encryptedVerifierHash);
+    }
+
+    protected int getVerifierHashSize() {
+        return verifierHashSize;
+    }
+}

Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,218 @@
+/* ====================================================================
+   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.standard;
+
+import static org.apache.poi.poifs.crypt.DataSpaceMapUtils.createEncryptionEntry;
+import static org.apache.poi.poifs.crypt.standard.StandardDecryptor.generateSecretKey;
+import static org.apache.poi.poifs.crypt.standard.StandardDecryptor.truncateOrPad;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.Random;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKey;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionVerifier;
+import org.apache.poi.poifs.crypt.Encryptor;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.POIFSWriterEvent;
+import org.apache.poi.poifs.filesystem.POIFSWriterListener;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianOutputStream;
+import org.apache.poi.util.TempFile;
+
+public class StandardEncryptor extends Encryptor {
+    private final StandardEncryptionInfoBuilder builder;
+    
+    protected StandardEncryptor(StandardEncryptionInfoBuilder builder) {
+        this.builder = builder;
+    }    
+
+    public void confirmPassword(String password) {
+        // see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
+        Random r = new SecureRandom();
+        byte[] salt = new byte[16], verifier = new byte[16];
+        r.nextBytes(salt);
+        r.nextBytes(verifier);
+        
+        confirmPassword(password, null, null, salt, verifier, null);
+    }
+    
+    
+    /**
+     * Fills the fields of verifier and header with the calculated hashes based
+     * on the password and a random salt
+     * 
+     * see [MS-OFFCRYPTO] - 2.3.4.7 ECMA-376 Document Encryption Key Generation
+     */
+    public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {
+        StandardEncryptionVerifier ver = builder.getVerifier();
+
+        ver.setSalt(verifierSalt);
+        SecretKey secretKey = generateSecretKey(password, ver, getKeySizeInBytes());
+        setSecretKey(secretKey);
+        Cipher cipher = getCipher(secretKey, null);
+        
+        try {
+            byte encryptedVerifier[] = cipher.doFinal(verifier); 
+            MessageDigest hashAlgo = MessageDigest.getInstance(ver.getHashAlgorithm().jceId);
+            byte calcVerifierHash[] = hashAlgo.digest(verifier);
+            
+            // 2.3.3 EncryptionVerifier ...
+            // An array of bytes that contains the encrypted form of the 
+            // hash of the randomly generated Verifier value. The length of the array MUST be the size of 
+            // the encryption block size multiplied by the number of blocks needed to encrypt the hash of the 
+            // Verifier. If the encryption algorithm is RC4, the length MUST be 20 bytes. If the encryption 
+            // algorithm is AES, the length MUST be 32 bytes. After decrypting the EncryptedVerifierHash
+            // field, only the first VerifierHashSize bytes MUST be used.
+            int encVerHashSize = ver.getCipherAlgorithm().encryptedVerifierHashLength; 
+            byte encryptedVerifierHash[] = cipher.doFinal(truncateOrPad(calcVerifierHash, encVerHashSize));
+    
+            ver.setEncryptedVerifier(encryptedVerifier);
+            ver.setEncryptedVerifierHash(encryptedVerifierHash);
+        } catch (GeneralSecurityException e) {
+            throw new EncryptedDocumentException("Password confirmation failed", e);
+        }
+        
+    }
+
+    private Cipher getCipher(SecretKey key, String padding) {
+        EncryptionVerifier ver = builder.getVerifier();
+        return CryptoFunctions.getCipher(key, ver.getCipherAlgorithm(), ver.getChainingMode(), null, Cipher.ENCRYPT_MODE, padding);
+    }
+    
+    public OutputStream getDataStream(final DirectoryNode dir)
+    throws IOException, GeneralSecurityException {
+        createEncryptionInfoEntry(dir);
+        DataSpaceMapUtils.addDefaultDataSpace(dir);
+        OutputStream countStream = new StandardCipherOutputStream(dir);
+        return countStream;
+    }
+    
+    protected class StandardCipherOutputStream extends FilterOutputStream implements POIFSWriterListener {
+        protected long countBytes;
+        protected final File fileOut;
+        protected final DirectoryNode dir;
+        
+        protected StandardCipherOutputStream(DirectoryNode dir) throws IOException {
+            super(null);
+
+            this.dir = dir;
+            fileOut = TempFile.createTempFile("encrypted_package", "crypt");
+            FileOutputStream rawStream = new FileOutputStream(fileOut);
+
+            // although not documented, we need the same padding as with agile encryption
+            // and instead of calculating the missing bytes for the block size ourselves
+            // we leave it up to the CipherOutputStream, which generates/saves them on close()
+            // ... we can't use "NoPadding" here
+            //
+            // see also [MS-OFFCRYPT] - 2.3.4.15
+            // The final data block MUST be padded to the next integral multiple of the
+            // KeyData.blockSize value. Any padding bytes can be used. Note that the StreamSize
+            // field of the EncryptedPackage field specifies the number of bytes of 
+            // unencrypted data as specified in section 2.3.4.4.
+            CipherOutputStream cryptStream = new CipherOutputStream(rawStream, getCipher(getSecretKey(), "PKCS5Padding"));
+            
+            this.out = cryptStream;
+        }
+        
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException {
+            out.write(b, off, len);
+            countBytes += len;
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            out.write(b);
+            countBytes++;
+        }
+    
+        public void close() throws IOException {
+            // the CipherOutputStream adds the padding bytes on close()
+            super.close(); 
+            writeToPOIFS();
+        }
+        
+        void writeToPOIFS() throws IOException {
+            int oleStreamSize = (int)(fileOut.length()+LittleEndianConsts.LONG_SIZE);
+            dir.createDocument("EncryptedPackage", oleStreamSize, this);
+            // TODO: any properties???
+        }
+    
+        public void processPOIFSWriterEvent(POIFSWriterEvent event) {
+            try {
+                LittleEndianOutputStream leos = new LittleEndianOutputStream(event.getStream());
+
+                // StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data 
+                // encrypted within the EncryptedData field, not including the size of the StreamSize field. 
+                // Note that the actual size of the \EncryptedPackage stream (1) can be larger than this 
+                // value, depending on the block size of the chosen encryption algorithm
+                leos.writeLong(countBytes);
+
+                FileInputStream fis = new FileInputStream(fileOut);
+                IOUtils.copy(fis, leos);
+                fis.close();
+                fileOut.delete();
+
+                leos.close();
+            } catch (IOException e) {
+                throw new EncryptedDocumentException(e);
+            }
+        }
+    }
+    
+    protected int getKeySizeInBytes() {
+        return builder.getHeader().getKeySize()/8;
+    }
+    
+    protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
+        final EncryptionInfo info = builder.getEncryptionInfo();
+        final StandardEncryptionHeader header = builder.getHeader();
+        final StandardEncryptionVerifier verifier = builder.getVerifier();
+        
+        EncryptionRecord er = new EncryptionRecord(){
+            public void write(LittleEndianByteArrayOutputStream bos) {
+                bos.writeShort(info.getVersionMajor());
+                bos.writeShort(info.getVersionMinor());
+                bos.writeInt(info.getEncryptionFlags());
+                header.write(bos);
+                verifier.write(bos);
+            }
+        };
+        
+        createEncryptionEntry(dir, "EncryptionInfo", er);
+        
+        // TODO: any properties???
+    }
+}

Modified: poi/trunk/src/ooxml/java/org/apache/poi/POIXMLException.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/POIXMLException.java?rev=1553336&r1=1553335&r2=1553336&view=diff
==============================================================================
--- poi/trunk/src/ooxml/java/org/apache/poi/POIXMLException.java (original)
+++ poi/trunk/src/ooxml/java/org/apache/poi/POIXMLException.java Tue Dec 24 23:13:21 2013
@@ -21,6 +21,7 @@ package org.apache.poi;
  *
  * @author Yegor Kozlov
  */
+@SuppressWarnings("serial")
 public final class POIXMLException extends RuntimeException{
     /**
      * Create a new <code>POIXMLException</code> with no

Copied: poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java (from r1544367, poi/trunk/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java)
URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java?p2=poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java&p1=poi/trunk/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java&r1=1544367&r2=1553336&rev=1553336&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java (original)
+++ poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java Tue Dec 24 23:13:21 2013
@@ -14,37 +14,55 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-package org.apache.poi.poifs.crypt;
+package org.apache.poi.poifs.crypt.agile;
+
+import static org.apache.poi.poifs.crypt.CryptoFunctions.generateIv;
+import static org.apache.poi.poifs.crypt.CryptoFunctions.generateKey;
+import static org.apache.poi.poifs.crypt.CryptoFunctions.getBlock0;
+import static org.apache.poi.poifs.crypt.CryptoFunctions.getCipher;
+import static org.apache.poi.poifs.crypt.CryptoFunctions.getMessageDigest;
+import static org.apache.poi.poifs.crypt.CryptoFunctions.hashPassword;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.GeneralSecurityException;
+import java.security.KeyPair;
 import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
 import java.util.Arrays;
 
 import javax.crypto.Cipher;
+import javax.crypto.Mac;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
 import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.crypt.EncryptionHeader;
+import org.apache.poi.poifs.crypt.EncryptionVerifier;
+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.DocumentInputStream;
 import org.apache.poi.util.LittleEndian;
 
 /**
- * 
+ * Decryptor implementation for Agile Encryption
  */
 public class AgileDecryptor extends Decryptor {
+    private final AgileEncryptionInfoBuilder builder;
+    
 
-    private final EncryptionInfo _info;
-    private SecretKey _secretKey;
     private long _length = -1;
 
-    private static final byte[] kVerifierInputBlock;
-    private static final byte[] kHashedVerifierBlock;
-    private static final byte[] kCryptoKeyBlock;
+    protected static final byte[] kVerifierInputBlock;
+    protected static final byte[] kHashedVerifierBlock;
+    protected static final byte[] kCryptoKeyBlock;
+    protected static final byte[] kIntegrityKeyBlock;
+    protected static final byte[] kIntegrityValueBlock;
 
     static {
         kVerifierInputBlock =
@@ -56,50 +74,215 @@ public class AgileDecryptor extends Decr
         kCryptoKeyBlock =
             new byte[] { (byte)0x14, (byte)0x6e, (byte)0x0b, (byte)0xe7,
                          (byte)0xab, (byte)0xac, (byte)0xd0, (byte)0xd6 };
+        kIntegrityKeyBlock =
+            new byte[] { (byte)0x5f, (byte)0xb2, (byte)0xad, (byte)0x01, 
+                         (byte)0x0c, (byte)0xb9, (byte)0xe1, (byte)0xf6 };
+        kIntegrityValueBlock =
+            new byte[] { (byte)0xa0, (byte)0x67, (byte)0x7f, (byte)0x02,
+                         (byte)0xb2, (byte)0x2c, (byte)0x84, (byte)0x33 };
     }
 
+    protected AgileDecryptor(AgileEncryptionInfoBuilder builder) {
+        super(builder.getInfo());
+        this.builder = builder; 
+    }
+    
+    /**
+     * set decryption password
+     */
     public boolean verifyPassword(String password) throws GeneralSecurityException {
-        EncryptionVerifier verifier = _info.getVerifier();
-        byte[] salt = verifier.getSalt();
-
-        byte[] pwHash = hashPassword(_info, password);
-        byte[] iv = generateIv(salt, null);
+        AgileEncryptionVerifier ver = builder.getVerifier();
+        AgileEncryptionHeader header = builder.getHeader(); 
+        HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
+        CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
+        int blockSize = header.getBlockSize();
+        int keySize = header.getKeySize()/8;
+
+        byte[] pwHash = hashPassword(password, ver.getHashAlgorithm(), ver.getSalt(), ver.getSpinCount());
+
+        /**
+         * 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
+         *    attribute.
+         * 2. Generate an encryption key as specified in section 2.3.4.11 by using the user-supplied password,
+         *    the binary byte array used to create the saltValue attribute, and a blockKey byte array
+         *    consisting of the following bytes: 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, and 0x79.
+         * 3. Encrypt the random array of bytes generated in step 1 by using the binary form of the saltValue
+         *    attribute as an initialization vector as specified in section 2.3.4.12. If the array of bytes is not an
+         *    integral multiple of blockSize bytes, pad the array with 0x00 to the next integral multiple of
+         *    blockSize bytes.
+         * 4. Use base64 to encode the result of step 3.
+         */
+        byte verfierInputEnc[] = hashInput(builder, pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE);
+        setVerifier(verfierInputEnc);
+        MessageDigest hashMD = getMessageDigest(hashAlgo);
+        byte[] verifierHash = hashMD.digest(verfierInputEnc);
+
+        /**
+         * 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
+         *    encryptedVerifierHashInput.
+         * 2. Generate an encryption key as specified in section 2.3.4.11 by using the user-supplied password,
+         *    the binary byte array used to create the saltValue attribute, and a blockKey byte array
+         *    consisting of the following bytes: 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, and 0x4e.
+         * 3. Encrypt the hash value obtained in step 1 by using the binary form of the saltValue attribute as
+         *    an initialization vector as specified in section 2.3.4.12. If hashSize is not an integral multiple of
+         *    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.
+         */
+        byte verifierHashDec[] = hashInput(builder, pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
+        verifierHashDec = getBlock0(verifierHashDec, hashAlgo.hashSize);
+        
+        /**
+         * 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
+         *    Encryptor.KeyData.keyBits attribute of the parent element.
+         * 2. Generate an encryption key as specified in section 2.3.4.11, using the user-supplied password,
+         *    the binary byte array used to create the saltValue attribute, and a blockKey byte array
+         *    consisting of the following bytes: 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, and 0xd6.
+         * 3. Encrypt the random array of bytes generated in step 1 by using the binary form of the saltValue
+         *    attribute as an initialization vector as specified in section 2.3.4.12. If the array of bytes is not an
+         *    integral multiple of blockSize bytes, pad the array with 0x00 to an integral multiple of
+         *    blockSize bytes.
+         * 4. Use base64 to encode the result of step 3.
+         */
+        byte keyspec[] = hashInput(builder, pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE);
+        keyspec = getBlock0(keyspec, keySize);
+        SecretKeySpec secretKey = new SecretKeySpec(keyspec, ver.getCipherAlgorithm().jceId);
+
+        /**
+         * 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
+         *    remaining steps of this section.
+         * 2. Generate a random array of bytes, known as Salt, of the same length as the value of the
+         *    KeyData.hashSize attribute.
+         * 3. Encrypt the random array of bytes generated in step 2 by using the binary form of the
+         *    KeyData.saltValue attribute and a blockKey byte array consisting of the following bytes: 0x5f,
+         *    0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, and 0xf6 used to form an initialization vector as specified in
+         *    section 2.3.4.12. If the array of bytes is not an integral multiple of blockSize bytes, pad the
+         *    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.
+         */
+        byte vec[] = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityKeyBlock, blockSize); 
+        Cipher cipher = getCipher(secretKey, cipherAlgo, ver.getChainingMode(), vec, Cipher.DECRYPT_MODE);
+        byte hmacKey[] = cipher.doFinal(header.getEncryptedHmacKey());
+        hmacKey = getBlock0(hmacKey, hashAlgo.hashSize);
+
+        /**
+         * 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.
+         *    Note that the entire EncryptedPackage stream (1), including the StreamSize field, MUST be
+         *    used as the message.
+         * 6. Encrypt the HMAC as in step 3 by using a blockKey byte array consisting of the following bytes:
+         *    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.
+         */
+        vec = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityValueBlock, blockSize);
+        cipher = getCipher(secretKey, cipherAlgo, ver.getChainingMode(), vec, Cipher.DECRYPT_MODE);
+        byte hmacValue[] = cipher.doFinal(header.getEncryptedHmacValue());
+        hmacValue = getBlock0(hmacValue, hashAlgo.hashSize);
+        
+        if (Arrays.equals(verifierHashDec, verifierHash)) {
+            setSecretKey(secretKey);
+            setIntegrityHmacKey(hmacKey);
+            setIntegrityHmacValue(hmacValue);
+            return true;
+        } else {
+            return false;
+        }
+    }
 
-        SecretKey skey;
-        skey = new SecretKeySpec(generateKey(pwHash, kVerifierInputBlock), "AES");
-        Cipher cipher = getCipher(skey, iv);
-        byte[] verifierHashInput = cipher.doFinal(verifier.getVerifier());
-
-        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-        byte[] trimmed = new byte[salt.length];
-        System.arraycopy(verifierHashInput, 0, trimmed, 0, trimmed.length);
-        byte[] hashedVerifier = sha1.digest(trimmed);
-
-        skey = new SecretKeySpec(generateKey(pwHash, kHashedVerifierBlock), "AES");
-        iv = generateIv(salt, null);
-        cipher = getCipher(skey, iv);
-        byte[] verifierHash = cipher.doFinal(verifier.getVerifierHash());
-        trimmed = new byte[hashedVerifier.length];
-        System.arraycopy(verifierHash, 0, trimmed, 0, trimmed.length);
-
-        if (Arrays.equals(trimmed, hashedVerifier)) {
-            skey = new SecretKeySpec(generateKey(pwHash, kCryptoKeyBlock), "AES");
-            iv = generateIv(salt, null);
-            cipher = getCipher(skey, iv);
-            byte[] inter = cipher.doFinal(verifier.getEncryptedKey());
-            byte[] keyspec = new byte[getKeySizeInBytes()];
-            System.arraycopy(inter, 0, keyspec, 0, keyspec.length);
-            _secretKey = new SecretKeySpec(keyspec, "AES");
+    /**
+     * instead of a password, it's also possible to decrypt via certificate.
+     * Warning: this code is experimental and hasn't been validated
+     * 
+     * {@linkplain http://social.msdn.microsoft.com/Forums/en-US/cc9092bb-0c82-4b5b-ae21-abf643bdb37c/agile-encryption-with-certificates}
+     *
+     * @param keyPair
+     * @param x509
+     * @return
+     * @throws GeneralSecurityException
+     */
+    public boolean verifyPassword(KeyPair keyPair, X509Certificate x509) throws GeneralSecurityException {
+        AgileEncryptionVerifier ver = builder.getVerifier();
+        AgileEncryptionHeader header = builder.getHeader();
+        HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
+        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, ver.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, ver.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) {
+        int fillSize;
+        for (fillSize=blockSize; fillSize<inputLen; fillSize+=blockSize);
+        return fillSize;
+    }
+
+    protected static byte[] hashInput(AgileEncryptionInfoBuilder builder, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
+        EncryptionVerifier ver = builder.getVerifier();
+        int keySize = builder.getDecryptor().getKeySizeInBytes();
+        int blockSize = builder.getDecryptor().getBlockSizeInBytes();
+        HashAlgorithm hashAlgo = ver.getHashAlgorithm();
+        byte[] salt = ver.getSalt();
+
+        byte intermedKey[] = generateKey(pwHash, hashAlgo, blockKey, keySize);
+        SecretKey skey = new SecretKeySpec(intermedKey, ver.getCipherAlgorithm().jceId);
+        byte[] iv = generateIv(hashAlgo, salt, null, blockSize);
+        Cipher cipher = getCipher(skey, ver.getCipherAlgorithm(), ver.getChainingMode(), iv, cipherMode);
+        byte[] hashFinal;
+        
+        try {
+            inputKey = getBlock0(inputKey, getNextBlockSize(inputKey.length, blockSize));
+            hashFinal = cipher.doFinal(inputKey);
+            return hashFinal;
+        } catch (GeneralSecurityException e) {
+            throw new EncryptedDocumentException(e);
+        }
+    }
+
     public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
         DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage");
         _length = dis.readLong();
-        return new ChunkedCipherInputStream(dis, _length);
+        
+        ChunkedCipherInputStream cipherStream = new ChunkedCipherInputStream(dis, _length);
+        return cipherStream;
     }
 
     public long getLength(){
@@ -107,23 +290,35 @@ public class AgileDecryptor extends Decr
         return _length;
     }
 
-    protected AgileDecryptor(EncryptionInfo info) {
-        _info = info;
-    }
-
+    /**
+     * 2.3.4.15 Data Encryption (Agile Encryption)
+     * 
+     * The EncryptedPackage stream (1) MUST be encrypted in 4096-byte segments to facilitate nearly
+     * random access while allowing CBC modes to be used in the encryption process.
+     * The initialization vector for the encryption process MUST be obtained by using the zero-based
+     * segment number as a blockKey and the binary form of the KeyData.saltValue as specified in
+     * section 2.3.4.12. The block number MUST be represented as a 32-bit unsigned integer.
+     * Data blocks MUST then be encrypted by using the initialization vector and the intermediate key
+     * obtained by decrypting the encryptedKeyValue from a KeyEncryptor contained within the
+     * KeyEncryptors sequence as specified in section 2.3.4.10. The final data block MUST be padded to
+     * the next integral multiple of the KeyData.blockSize value. Any padding bytes can be used. Note
+     * that the StreamSize field of the EncryptedPackage field specifies the number of bytes of
+     * unencrypted data as specified in section 2.3.4.4.
+     */
     private class ChunkedCipherInputStream extends InputStream {
         private int _lastIndex = 0;
         private long _pos = 0;
         private final long _size;
-        private final DocumentInputStream _stream;
+        private final InputStream _stream;
         private byte[] _chunk;
         private Cipher _cipher;
 
         public ChunkedCipherInputStream(DocumentInputStream stream, long size)
             throws GeneralSecurityException {
+            EncryptionHeader header = info.getHeader();
             _size = size;
             _stream = stream;
-            _cipher = getCipher(_secretKey, _info.getHeader().getKeySalt());
+            _cipher = getCipher(getSecretKey(), header.getCipherAlgorithm(), header.getChainingMode(), header.getKeySalt(), Cipher.DECRYPT_MODE);
         }
 
         public int read() throws IOException {
@@ -139,6 +334,8 @@ public class AgileDecryptor extends Decr
 
         public int read(byte[] b, int off, int len) throws IOException {
             int total = 0;
+            
+            if (available() <= 0) return -1;
 
             while (len > 0) {
                 if (_chunk == null) {
@@ -149,7 +346,11 @@ public class AgileDecryptor extends Decr
                     }
                 }
                 int count = (int)(4096L - (_pos & 0xfff));
-                count = Math.min(available(), Math.min(count, len));
+                int avail = available();
+                if (avail == 0) {
+                    return total;
+                }
+                count = Math.min(avail, Math.min(count, len));
                 System.arraycopy(_chunk, (int)(_pos & 0xfff), b, off, count);
                 off += count;
                 len -= count;
@@ -180,89 +381,24 @@ public class AgileDecryptor extends Decr
             int index = (int)(_pos >> 12);
             byte[] blockKey = new byte[4];
             LittleEndian.putInt(blockKey, 0, index);
-            byte[] iv = generateIv(_info.getHeader().getKeySalt(), blockKey);
-            _cipher.init(Cipher.DECRYPT_MODE, _secretKey, new IvParameterSpec(iv));
+            EncryptionHeader header = info.getHeader();
+            byte[] iv = generateIv(header.getHashAlgorithmEx(), header.getKeySalt(), blockKey, getBlockSizeInBytes());
+            _cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), new IvParameterSpec(iv));
             if (_lastIndex != index)
                 _stream.skip((index - _lastIndex) << 12);
 
             byte[] block = new byte[Math.min(_stream.available(), 4096)];
-            _stream.readFully(block);
+            _stream.read(block);
             _lastIndex = index + 1;
             return _cipher.doFinal(block);
         }
     }
 
-    private Cipher getCipher(SecretKey key, byte[] vec)
-        throws GeneralSecurityException {
-        
-        String name = null;
-        String chain = null;
-
-    	EncryptionVerifier verifier = _info.getVerifier();
-        
-        switch (verifier.getAlgorithm()) {
-          case EncryptionHeader.ALGORITHM_AES_128:
-          case EncryptionHeader.ALGORITHM_AES_192:
-          case EncryptionHeader.ALGORITHM_AES_256:
-             name = "AES";
-             break;
-          default:
-             throw new EncryptedDocumentException("Unsupported algorithm");
-        }
-
-        // Ensure the JCE policies files allow for this sized key
-        if (Cipher.getMaxAllowedKeyLength(name) < _info.getHeader().getKeySize()) {
-            throw new EncryptedDocumentException("Export Restrictions in place - please install JCE Unlimited Strength Jurisdiction Policy files");
-        }
-        
-        switch (verifier.getCipherMode()) {
-          case EncryptionHeader.MODE_CBC: 
-              chain = "CBC"; 
-              break;
-          case EncryptionHeader.MODE_CFB:
-              chain = "CFB";
-              break;
-          default: 
-              throw new EncryptedDocumentException("Unsupported chain mode");
-        }
-        
-        Cipher cipher = Cipher.getInstance(name + "/" + chain + "/NoPadding");
-        IvParameterSpec iv = new IvParameterSpec(vec);
-        cipher.init(Cipher.DECRYPT_MODE, key, iv);
-        return cipher;
-    }
-
-    private byte[] getBlock(byte[] hash, int size) {
-        byte[] result = new byte[size];
-        Arrays.fill(result, (byte)0x36);
-        System.arraycopy(hash, 0, result, 0, Math.min(result.length, hash.length));
-        return result;
-    }
-
-    private byte[] generateKey(byte[] hash, byte[] blockKey) throws NoSuchAlgorithmException {
-        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-        sha1.update(hash);
-        byte[] key = sha1.digest(blockKey);
-        return getBlock(key, getKeySizeInBytes());
-    }
-
-    protected byte[] generateIv(byte[] salt, byte[] blockKey)
-        throws NoSuchAlgorithmException {
-
-
-        if (blockKey == null)
-            return getBlock(salt, getBlockSizeInBytes());
-
-        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-        sha1.update(salt);
-        return getBlock(sha1.digest(blockKey), getBlockSizeInBytes());
-    }
-    
     protected int getBlockSizeInBytes() {
-    	return _info.getHeader().getBlockSize();
+    	return info.getHeader().getBlockSize();
     }
     
     protected int getKeySizeInBytes() {
-    	return _info.getHeader().getKeySize()/8;
+    	return info.getHeader().getKeySize()/8;
     }
 }

Added: poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java (added)
+++ poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,130 @@
+/* ====================================================================
+   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.agile;
+
+import java.io.IOException;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.EncryptionHeader;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.xmlbeans.XmlException;
+
+import com.microsoft.schemas.office.x2006.encryption.CTDataIntegrity;
+import com.microsoft.schemas.office.x2006.encryption.CTKeyData;
+import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument;
+import com.microsoft.schemas.office.x2006.encryption.STCipherChaining;
+
+public class AgileEncryptionHeader extends EncryptionHeader {
+    private byte encryptedHmacKey[], encryptedHmacValue[];
+    
+    public AgileEncryptionHeader(String descriptor) throws IOException {
+        EncryptionDocument ed;
+        try {
+            ed = EncryptionDocument.Factory.parse(descriptor);
+        } catch (XmlException e) {
+            throw new EncryptedDocumentException("Unable to parse encryption descriptor", e);
+        }
+        
+        CTKeyData keyData;
+        try {
+            keyData = ed.getEncryption().getKeyData();
+            if (keyData == null) {
+                throw new NullPointerException("keyData not set");
+            }
+        } catch (Exception e) {
+            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();
+        
+        CipherAlgorithm ca = CipherAlgorithm.fromXmlId(keyData.getCipherAlgorithm().toString(), keyBits);
+        setCipherAlgorithm(ca);
+        setCipherProvider(ca.provider);
+
+        switch (keyData.getCipherChaining().intValue()) {
+        case STCipherChaining.INT_CHAINING_MODE_CBC:
+            setChainingMode(ChainingMode.cbc);
+            break;
+        case STCipherChaining.INT_CHAINING_MODE_CFB:
+            setChainingMode(ChainingMode.cfb);
+            break;
+        default:
+            throw new EncryptedDocumentException("Unsupported chaining mode - "+keyData.getCipherChaining().toString());
+        }
+    
+        int hashSize = keyData.getHashSize();
+        
+        HashAlgorithm ha = HashAlgorithm.fromEcmaId(keyData.getHashAlgorithm().toString());
+        setHashAlgorithm(ha);
+
+        if (getHashAlgorithmEx().hashSize != hashSize) {
+            throw new EncryptedDocumentException("Unsupported hash algorithm: " + 
+                    keyData.getHashAlgorithm() + " @ " + hashSize + " bytes");
+        }
+
+        int saltLength = keyData.getSaltSize();
+        setKeySalt(keyData.getSaltValue());
+        if (getKeySalt().length != saltLength) {
+            throw new EncryptedDocumentException("Invalid salt length");
+        }
+        
+        CTDataIntegrity di = ed.getEncryption().getDataIntegrity();
+        setEncryptedHmacKey(di.getEncryptedHmacKey());
+        setEncryptedHmacValue(di.getEncryptedHmacValue());
+    }
+    
+    
+    public AgileEncryptionHeader(CipherAlgorithm algorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
+        setCipherAlgorithm(algorithm);
+        setHashAlgorithm(hashAlgorithm);
+        setKeySize(keyBits);
+        setBlockSize(blockSize);
+        setChainingMode(chainingMode);
+    }
+
+    // make method visible for this package
+    protected void setKeySalt(byte salt[]) {
+        if (salt == null || salt.length != getBlockSize()) {
+            throw new EncryptedDocumentException("invalid verifier salt");
+        }
+        super.setKeySalt(salt);
+    }
+
+    public byte[] getEncryptedHmacKey() {
+        return encryptedHmacKey;
+    }
+
+    protected void setEncryptedHmacKey(byte[] encryptedHmacKey) {
+        this.encryptedHmacKey = encryptedHmacKey;
+    }
+
+    public byte[] getEncryptedHmacValue() {
+        return encryptedHmacValue;
+    }
+
+    protected void setEncryptedHmacValue(byte[] encryptedHmacValue) {
+        this.encryptedHmacValue = encryptedHmacValue;
+    }
+}

Added: poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java (added)
+++ poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,111 @@
+/* ====================================================================
+   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.agile;
+
+import java.io.IOException;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+
+public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder {
+    
+    EncryptionInfo info;
+    AgileEncryptionHeader header;
+    AgileEncryptionVerifier verifier;
+    AgileDecryptor decryptor;
+    AgileEncryptor encryptor;
+
+    public void initialize(EncryptionInfo info, DocumentInputStream dis) throws IOException {
+        this.info = info;
+        
+        StringBuilder builder = new StringBuilder();
+        byte[] xmlDescriptor = new byte[dis.available()];
+        dis.read(xmlDescriptor);
+        for (byte b : xmlDescriptor)
+            builder.append((char)b);
+        String descriptor = builder.toString();
+        header = new AgileEncryptionHeader(descriptor);
+        verifier = new AgileEncryptionVerifier(descriptor);
+        if (info.getVersionMajor() == 4 && info.getVersionMinor() == 4) {
+            decryptor = new AgileDecryptor(this);
+        }
+    }
+
+    public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
+        this.info = info;
+
+        if (cipherAlgorithm == null) {
+            cipherAlgorithm = CipherAlgorithm.aes128;
+        }
+        if (cipherAlgorithm == CipherAlgorithm.rc4) {
+            throw new EncryptedDocumentException("RC4 must not be used with agile encryption.");
+        }
+        if (hashAlgorithm == null) {
+            hashAlgorithm = HashAlgorithm.sha1;
+        }
+        if (chainingMode == null) {
+            chainingMode = ChainingMode.cbc;
+        }
+        if (!(chainingMode == ChainingMode.cbc || chainingMode == ChainingMode.cfb)) {
+            throw new EncryptedDocumentException("Agile encryption only supports CBC/CFB chaining.");
+        }
+        if (keyBits == -1) {
+            keyBits = cipherAlgorithm.defaultKeySize;
+        }
+        if (blockSize == -1) {
+            blockSize = cipherAlgorithm.blockSize;
+        }
+        boolean found = false;
+        for (int ks : cipherAlgorithm.allowedKeySize) {
+            found |= (ks == keyBits);
+        }
+        if (!found) {
+            throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString());
+        }
+        header = new AgileEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
+        verifier = new AgileEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
+        decryptor = new AgileDecryptor(this);
+        encryptor = new AgileEncryptor(this);
+    }
+    
+    public AgileEncryptionHeader getHeader() {
+        return header;
+    }
+
+    public AgileEncryptionVerifier getVerifier() {
+        return verifier;
+    }
+
+    public AgileDecryptor getDecryptor() {
+        return decryptor;
+    }
+
+    public AgileEncryptor getEncryptor() {
+        return encryptor;
+    }
+
+    protected EncryptionInfo getInfo() {
+        return info;
+    }
+    
+    
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org