| /* ==================================================================== |
| 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.ByteArrayInputStream; |
| import java.security.GeneralSecurityException; |
| import java.security.cert.CertificateFactory; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| 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 com.microsoft.schemas.office.x2006.encryption.CTKeyEncryptor; |
| import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument; |
| import com.microsoft.schemas.office.x2006.encryption.STCipherChaining; |
| import com.microsoft.schemas.office.x2006.keyEncryptor.certificate.CTCertificateKeyEncryptor; |
| import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEncryptor; |
| |
| /** |
| * Used when checking if a key is valid for a document |
| */ |
| public class AgileEncryptionVerifier extends EncryptionVerifier implements Cloneable { |
| |
| public static class AgileCertificateEntry { |
| X509Certificate x509; |
| byte encryptedKey[]; |
| byte certVerifier[]; |
| } |
| |
| private List<AgileCertificateEntry> certList = new ArrayList<AgileCertificateEntry>(); |
| private int keyBits = -1; |
| private int blockSize = -1; |
| |
| public AgileEncryptionVerifier(String descriptor) { |
| this(AgileEncryptionInfoBuilder.parseDescriptor(descriptor)); |
| } |
| |
| protected AgileEncryptionVerifier(EncryptionDocument ed) { |
| Iterator<CTKeyEncryptor> encList = ed.getEncryption().getKeyEncryptors().getKeyEncryptorList().iterator(); |
| CTPasswordKeyEncryptor keyData; |
| try { |
| keyData = encList.next().getEncryptedPasswordKey(); |
| if (keyData == null) { |
| throw new NullPointerException("encryptedKey not set"); |
| } |
| } catch (Exception e) { |
| throw new EncryptedDocumentException("Unable to parse keyData", e); |
| } |
| |
| int kb = (int)keyData.getKeyBits(); |
| CipherAlgorithm ca = CipherAlgorithm.fromXmlId(keyData.getCipherAlgorithm().toString(), kb); |
| setCipherAlgorithm(ca); |
| |
| setKeySize(kb); |
| |
| int blockSize = keyData.getBlockSize(); |
| setBlockSize(blockSize); |
| |
| int hashSize = keyData.getHashSize(); |
| |
| HashAlgorithm ha = HashAlgorithm.fromEcmaId(keyData.getHashAlgorithm().toString()); |
| setHashAlgorithm(ha); |
| |
| if (getHashAlgorithm().hashSize != hashSize) { |
| throw new EncryptedDocumentException("Unsupported hash algorithm: " + |
| keyData.getHashAlgorithm() + " @ " + hashSize + " bytes"); |
| } |
| |
| setSpinCount(keyData.getSpinCount()); |
| setEncryptedVerifier(keyData.getEncryptedVerifierHashInput()); |
| setSalt(keyData.getSaltValue()); |
| setEncryptedKey(keyData.getEncryptedKeyValue()); |
| setEncryptedVerifierHash(keyData.getEncryptedVerifierHashValue()); |
| |
| int saltSize = keyData.getSaltSize(); |
| if (saltSize != getSalt().length) { |
| throw new EncryptedDocumentException("Invalid salt size"); |
| } |
| |
| 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()); |
| } |
| |
| if (!encList.hasNext()) { |
| return; |
| } |
| |
| try { |
| CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
| while (encList.hasNext()) { |
| CTCertificateKeyEncryptor certKey = encList.next().getEncryptedCertificateKey(); |
| AgileCertificateEntry ace = new AgileCertificateEntry(); |
| ace.certVerifier = certKey.getCertVerifier(); |
| ace.encryptedKey = certKey.getEncryptedKeyValue(); |
| ace.x509 = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(certKey.getX509Certificate())); |
| certList.add(ace); |
| } |
| } catch (GeneralSecurityException e) { |
| throw new EncryptedDocumentException("can't parse X509 certificate", e); |
| } |
| } |
| |
| public AgileEncryptionVerifier(CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) { |
| setCipherAlgorithm(cipherAlgorithm); |
| setHashAlgorithm(hashAlgorithm); |
| setChainingMode(chainingMode); |
| setKeySize(keyBits); |
| setBlockSize(blockSize); |
| setSpinCount(100000); // TODO: use parameter |
| } |
| |
| @Override |
| protected void setSalt(byte salt[]) { |
| if (salt == null || salt.length != getCipherAlgorithm().blockSize) { |
| throw new EncryptedDocumentException("invalid verifier salt"); |
| } |
| super.setSalt(salt); |
| } |
| |
| // make method visible for this package |
| @Override |
| protected void setEncryptedVerifier(byte encryptedVerifier[]) { |
| super.setEncryptedVerifier(encryptedVerifier); |
| } |
| |
| // make method visible for this package |
| @Override |
| protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) { |
| super.setEncryptedVerifierHash(encryptedVerifierHash); |
| } |
| |
| // make method visible for this package |
| @Override |
| protected void setEncryptedKey(byte[] encryptedKey) { |
| super.setEncryptedKey(encryptedKey); |
| } |
| |
| public void addCertificate(X509Certificate x509) { |
| AgileCertificateEntry ace = new AgileCertificateEntry(); |
| ace.x509 = x509; |
| certList.add(ace); |
| } |
| |
| public List<AgileCertificateEntry> getCertificates() { |
| return certList; |
| } |
| |
| @Override |
| public AgileEncryptionVerifier clone() throws CloneNotSupportedException { |
| AgileEncryptionVerifier other = (AgileEncryptionVerifier)super.clone(); |
| // TODO: deep copy of certList |
| other.certList = new ArrayList<AgileCertificateEntry>(certList); |
| 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 final void setCipherAlgorithm(CipherAlgorithm cipherAlgorithm) { |
| super.setCipherAlgorithm(cipherAlgorithm); |
| if (cipherAlgorithm.allowedKeySize.length == 1) { |
| setKeySize(cipherAlgorithm.defaultKeySize); |
| } |
| } |
| } |