| /* ==================================================================== |
| 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 java.io.IOException; |
| import java.io.InputStream; |
| import java.security.GeneralSecurityException; |
| import java.util.Map; |
| import java.util.function.Supplier; |
| |
| import javax.crypto.Cipher; |
| import javax.crypto.SecretKey; |
| |
| import org.apache.poi.EncryptedDocumentException; |
| import org.apache.poi.common.usermodel.GenericRecord; |
| import org.apache.poi.poifs.filesystem.DirectoryNode; |
| import org.apache.poi.poifs.filesystem.POIFSFileSystem; |
| import org.apache.poi.util.GenericRecordUtil; |
| |
| public abstract class Decryptor implements GenericRecord { |
| @SuppressWarnings({"squid:S2068"}) |
| public static final String DEFAULT_PASSWORD="VelvetSweatshop"; |
| public static final String DEFAULT_POIFS_ENTRY="EncryptedPackage"; |
| |
| protected EncryptionInfo encryptionInfo; |
| private SecretKey secretKey; |
| private byte[] verifier, integrityHmacKey, integrityHmacValue; |
| |
| protected Decryptor() {} |
| |
| protected Decryptor(Decryptor other) { |
| encryptionInfo = other.encryptionInfo; |
| // secretKey is immutable |
| secretKey = other.secretKey; |
| verifier = (other.verifier == null) ? null : other.verifier.clone(); |
| integrityHmacKey = (other.integrityHmacKey == null) ? null : other.integrityHmacKey.clone(); |
| integrityHmacValue = (other.integrityHmacValue == null) ? null : other.integrityHmacValue.clone(); |
| } |
| |
| /** |
| * Return a stream with decrypted data. |
| * <p> |
| * Use {@link #getLength()} to get the size of that data that can be safely read from the stream. |
| * Just reading to the end of the input stream is not sufficient because there are |
| * normally padding bytes that must be discarded |
| * </p> |
| * |
| * @param dir the node to read from |
| * @return decrypted stream |
| */ |
| public abstract InputStream getDataStream(DirectoryNode dir) |
| throws IOException, GeneralSecurityException; |
| |
| /** |
| * Wraps a stream for decryption<p> |
| * |
| * As we are handling streams and don't know the total length beforehand, |
| * it's the callers duty to care for the length of the entries. |
| * |
| * @param stream the stream to be wrapped |
| * @param initialPos initial/current byte position within the stream |
| * @return decrypted stream |
| */ |
| public InputStream getDataStream(InputStream stream, int size, int initialPos) |
| throws IOException, GeneralSecurityException { |
| throw new EncryptedDocumentException("this decryptor doesn't support reading from a stream"); |
| } |
| |
| /** |
| * Sets the chunk size of the data stream. |
| * Needs to be set before the data stream is requested. |
| * When not set, the implementation uses method specific default values |
| * |
| * @param chunkSize the chunk size, i.e. the block size with the same encryption key |
| */ |
| public void setChunkSize(int chunkSize) { |
| throw new EncryptedDocumentException("this decryptor doesn't support changing the chunk size"); |
| } |
| |
| /** |
| * Initializes a cipher object for a given block index for encryption |
| * |
| * @param cipher may be null, otherwise the given instance is reset to the new block index |
| * @param block the block index, e.g. the persist/slide id (hslf) |
| * @return a new cipher object, if cipher was null, otherwise the reinitialized cipher |
| * @throws GeneralSecurityException if the cipher can't be initialized |
| */ |
| public Cipher initCipherForBlock(Cipher cipher, int block) |
| throws GeneralSecurityException { |
| throw new EncryptedDocumentException("this decryptor doesn't support initCipherForBlock"); |
| } |
| |
| public abstract boolean verifyPassword(String password) |
| throws GeneralSecurityException; |
| |
| /** |
| * Returns the length of the encrypted data that can be safely read with |
| * {@link #getDataStream(org.apache.poi.poifs.filesystem.DirectoryNode)}. |
| * Just reading to the end of the input stream is not sufficient because there are |
| * normally padding bytes that must be discarded |
| * |
| * <p> |
| * The length variable is initialized in {@link #getDataStream(org.apache.poi.poifs.filesystem.DirectoryNode)}, |
| * an attempt to call getLength() prior to getDataStream() will result in IllegalStateException. |
| * </p> |
| * |
| * @return length of the encrypted data |
| * @throws IllegalStateException if {@link #getDataStream(org.apache.poi.poifs.filesystem.DirectoryNode)} |
| * was not called |
| */ |
| public abstract long getLength(); |
| |
| public static Decryptor getInstance(EncryptionInfo info) { |
| Decryptor d = info.getDecryptor(); |
| if (d == null) { |
| throw new EncryptedDocumentException("Unsupported version"); |
| } |
| return d; |
| } |
| |
| public InputStream getDataStream(POIFSFileSystem fs) throws IOException, GeneralSecurityException { |
| return getDataStream(fs.getRoot()); |
| } |
| |
| // for tests |
| public byte[] getVerifier() { |
| return verifier; |
| } |
| |
| public SecretKey getSecretKey() { |
| return secretKey; |
| } |
| |
| public byte[] getIntegrityHmacKey() { |
| return integrityHmacKey; |
| } |
| |
| @SuppressWarnings("unused") |
| public byte[] getIntegrityHmacValue() { |
| return integrityHmacValue; |
| } |
| |
| protected void setSecretKey(SecretKey secretKey) { |
| this.secretKey = secretKey; |
| } |
| |
| protected void setVerifier(byte[] verifier) { |
| this.verifier = (verifier == null) ? null : verifier.clone(); |
| } |
| |
| protected void setIntegrityHmacKey(byte[] integrityHmacKey) { |
| this.integrityHmacKey = (integrityHmacKey == null) ? null : integrityHmacKey.clone(); |
| } |
| |
| protected void setIntegrityHmacValue(byte[] integrityHmacValue) { |
| this.integrityHmacValue = (integrityHmacValue == null) ? null : integrityHmacValue.clone(); |
| } |
| |
| @SuppressWarnings("unused") |
| protected int getBlockSizeInBytes() { |
| return encryptionInfo.getHeader().getBlockSize(); |
| } |
| |
| protected int getKeySizeInBytes() { |
| return encryptionInfo.getHeader().getKeySize()/8; |
| } |
| |
| public EncryptionInfo getEncryptionInfo() { |
| return encryptionInfo; |
| } |
| |
| public void setEncryptionInfo(EncryptionInfo encryptionInfo) { |
| this.encryptionInfo = encryptionInfo; |
| } |
| |
| public abstract Decryptor copy(); |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties( |
| "secretKey", secretKey == null ? () -> null : secretKey::getEncoded, |
| "verifier", this::getVerifier, |
| "integrityHmacKey", this::getIntegrityHmacKey, |
| "integrityHmacValue", this::getIntegrityHmacValue |
| ); |
| } |
| } |