| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| package org.apache.poi.poifs.crypt; |
| |
| import static org.apache.poi.poifs.crypt.EncryptionMode.agile; |
| import static org.apache.poi.poifs.crypt.EncryptionMode.binaryRC4; |
| import static org.apache.poi.poifs.crypt.EncryptionMode.cryptoAPI; |
| import static org.apache.poi.poifs.crypt.EncryptionMode.standard; |
| import static org.apache.poi.poifs.crypt.EncryptionMode.xor; |
| |
| import java.io.IOException; |
| |
| import org.apache.poi.EncryptedDocumentException; |
| import org.apache.poi.poifs.filesystem.DirectoryNode; |
| import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; |
| import org.apache.poi.poifs.filesystem.OPOIFSFileSystem; |
| import org.apache.poi.poifs.filesystem.POIFSFileSystem; |
| import org.apache.poi.util.BitField; |
| import org.apache.poi.util.BitFieldFactory; |
| import org.apache.poi.util.LittleEndianInput; |
| |
| /** |
| * This class may require {@code poi-ooxml} to be on the classpath to load |
| * some {@link EncryptionMode}s. |
| * @see #getBuilder(EncryptionMode) |
| */ |
| public class EncryptionInfo implements Cloneable { |
| private final EncryptionMode encryptionMode; |
| private final int versionMajor; |
| private final int versionMinor; |
| private final int encryptionFlags; |
| |
| private EncryptionHeader header; |
| private EncryptionVerifier verifier; |
| private Decryptor decryptor; |
| private Encryptor encryptor; |
| |
| /** |
| * A flag that specifies whether CryptoAPI RC4 or ECMA-376 encryption |
| * ECMA-376 is used. It MUST be 1 unless flagExternal is 1. If flagExternal is 1, it MUST be 0. |
| */ |
| public static final BitField flagCryptoAPI = BitFieldFactory.getInstance(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. |
| */ |
| public static final BitField flagDocProps = BitFieldFactory.getInstance(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. |
| */ |
| public static final BitField flagExternal = BitFieldFactory.getInstance(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. |
| */ |
| public static final BitField flagAES = BitFieldFactory.getInstance(0x20); |
| |
| |
| /** |
| * Opens for decryption |
| */ |
| public EncryptionInfo(POIFSFileSystem fs) throws IOException { |
| this(fs.getRoot()); |
| } |
| |
| /** |
| * Opens for decryption |
| */ |
| public EncryptionInfo(OPOIFSFileSystem fs) throws IOException { |
| this(fs.getRoot()); |
| } |
| |
| /** |
| * Opens for decryption |
| */ |
| public EncryptionInfo(NPOIFSFileSystem fs) throws IOException { |
| this(fs.getRoot()); |
| } |
| |
| /** |
| * Opens for decryption |
| */ |
| public EncryptionInfo(DirectoryNode dir) throws IOException { |
| this(dir.createDocumentInputStream("EncryptionInfo"), null); |
| } |
| |
| public EncryptionInfo(LittleEndianInput dis, EncryptionMode preferredEncryptionMode) throws IOException { |
| if (preferredEncryptionMode == xor) { |
| versionMajor = xor.versionMajor; |
| versionMinor = xor.versionMinor; |
| } else { |
| versionMajor = dis.readUShort(); |
| versionMinor = dis.readUShort(); |
| } |
| |
| if ( versionMajor == xor.versionMajor |
| && versionMinor == xor.versionMinor) { |
| encryptionMode = xor; |
| encryptionFlags = -1; |
| } else if ( versionMajor == binaryRC4.versionMajor |
| && versionMinor == binaryRC4.versionMinor) { |
| encryptionMode = binaryRC4; |
| encryptionFlags = -1; |
| } else if ( |
| 2 <= versionMajor && versionMajor <= 4 |
| && versionMinor == 2) { |
| encryptionMode = (preferredEncryptionMode == cryptoAPI) ? cryptoAPI : standard; |
| encryptionFlags = dis.readInt(); |
| } else if ( |
| versionMajor == agile.versionMajor |
| && versionMinor == agile.versionMinor){ |
| encryptionMode = agile; |
| encryptionFlags = dis.readInt(); |
| } else { |
| encryptionFlags = dis.readInt(); |
| throw new EncryptedDocumentException( |
| "Unknown encryption: version major: "+versionMajor+ |
| " / version minor: "+versionMinor+ |
| " / fCrypto: "+flagCryptoAPI.isSet(encryptionFlags)+ |
| " / fExternal: "+flagExternal.isSet(encryptionFlags)+ |
| " / fDocProps: "+flagDocProps.isSet(encryptionFlags)+ |
| " / fAES: "+flagAES.isSet(encryptionFlags)); |
| } |
| |
| EncryptionInfoBuilder eib; |
| try { |
| eib = getBuilder(encryptionMode); |
| } catch (Exception e) { |
| throw new IOException(e); |
| } |
| |
| eib.initialize(this, dis); |
| } |
| |
| /** |
| * Prepares for encryption, using the given Encryption Mode, and |
| * all other parameters as default. |
| * @see #EncryptionInfo(EncryptionMode, CipherAlgorithm, HashAlgorithm, int, int, ChainingMode) |
| */ |
| public EncryptionInfo(EncryptionMode encryptionMode) { |
| this(encryptionMode, null, null, -1, -1, null); |
| } |
| |
| /** |
| * Constructs an EncryptionInfo from scratch |
| * |
| * @param encryptionMode see {@link EncryptionMode} for values, {@link EncryptionMode#cryptoAPI} is for |
| * internal use only, as it's record based |
| * @param cipherAlgorithm |
| * @param hashAlgorithm |
| * @param keyBits |
| * @param blockSize |
| * @param chainingMode |
| * |
| * @throws EncryptedDocumentException if the given parameters mismatch, e.g. only certain combinations |
| * of keyBits, blockSize are allowed for a given {@link CipherAlgorithm} |
| */ |
| public EncryptionInfo( |
| EncryptionMode encryptionMode |
| , CipherAlgorithm cipherAlgorithm |
| , HashAlgorithm hashAlgorithm |
| , int keyBits |
| , int blockSize |
| , ChainingMode chainingMode |
| ) { |
| this.encryptionMode = encryptionMode; |
| versionMajor = encryptionMode.versionMajor; |
| versionMinor = encryptionMode.versionMinor; |
| encryptionFlags = encryptionMode.encryptionFlags; |
| |
| EncryptionInfoBuilder eib; |
| try { |
| eib = getBuilder(encryptionMode); |
| } catch (Exception e) { |
| throw new EncryptedDocumentException(e); |
| } |
| |
| eib.initialize(this, cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode); |
| } |
| |
| /** |
| * This method loads the builder class with reflection, which may generate |
| * a {@code ClassNotFoundException} if the class is not on the classpath. |
| * For example, {@link org.apache.poi.poifs.crypt.agile.AgileEncryptionInfoBuilder} |
| * is contained in the {@code poi-ooxml} package since the class makes use of some OOXML |
| * classes rather than using the {@code poi} package and plain XML DOM calls. |
| * As such, you may need to include {@code poi-ooxml} and {@code poi-ooxml-schemas} to load |
| * some encryption mode builders. See bug #60021 for more information. |
| * https://bz.apache.org/bugzilla/show_bug.cgi?id=60021 |
| * |
| * @param encryptionMode the encryption mode |
| * @return an encryption info builder |
| * @throws ClassNotFoundException |
| * @throws IllegalAccessException |
| * @throws InstantiationException |
| */ |
| protected static EncryptionInfoBuilder getBuilder(EncryptionMode encryptionMode) |
| throws ClassNotFoundException, IllegalAccessException, InstantiationException { |
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
| EncryptionInfoBuilder eib; |
| eib = (EncryptionInfoBuilder)cl.loadClass(encryptionMode.builder).newInstance(); |
| return eib; |
| } |
| |
| public int getVersionMajor() { |
| return versionMajor; |
| } |
| |
| public int getVersionMinor() { |
| return versionMinor; |
| } |
| |
| public int getEncryptionFlags() { |
| return encryptionFlags; |
| } |
| |
| public EncryptionHeader getHeader() { |
| return header; |
| } |
| |
| public EncryptionVerifier getVerifier() { |
| return verifier; |
| } |
| |
| public Decryptor getDecryptor() { |
| return decryptor; |
| } |
| |
| public Encryptor getEncryptor() { |
| return encryptor; |
| } |
| |
| public void setHeader(EncryptionHeader header) { |
| this.header = header; |
| } |
| |
| public void setVerifier(EncryptionVerifier verifier) { |
| this.verifier = verifier; |
| } |
| |
| public void setDecryptor(Decryptor decryptor) { |
| this.decryptor = decryptor; |
| } |
| |
| public void setEncryptor(Encryptor encryptor) { |
| this.encryptor = encryptor; |
| } |
| |
| public EncryptionMode getEncryptionMode() { |
| return encryptionMode; |
| } |
| |
| @Override |
| public EncryptionInfo clone() throws CloneNotSupportedException { |
| EncryptionInfo other = (EncryptionInfo)super.clone(); |
| other.header = header.clone(); |
| other.verifier = verifier.clone(); |
| other.decryptor = decryptor.clone(); |
| other.decryptor.setEncryptionInfo(other); |
| other.encryptor = encryptor.clone(); |
| other.encryptor.setEncryptionInfo(other); |
| return other; |
| } |
| } |