All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.metaeffekt.artifact.analysis.flow.ng.crypt.EncryptedZipProvider Maven / Gradle / Ivy

/*
 * Copyright 2021-2024 the original author or authors.
 *
 * Licensed 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 com.metaeffekt.artifact.analysis.flow.ng.crypt;

import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.metaeffekt.artifact.analysis.flow.ng.DecryptableKeyslot;
import com.metaeffekt.artifact.analysis.flow.ng.crypt.param.ProviderParameters;
import com.metaeffekt.artifact.analysis.flow.ng.exception.DecryptionImpossibleException;
import com.metaeffekt.artifact.analysis.flow.ng.exception.SelfcheckFailedException;
import com.metaeffekt.artifact.analysis.flow.ng.keyholder.UserKeysForConsumer;
import com.metaeffekt.artifact.analysis.flow.ng.keyholder.UserKeysStorage;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.AEADBadTagException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.security.*;
import java.util.zip.ZipInputStream;

import static com.metaeffekt.artifact.analysis.flow.ng.EncryptedArchiveConstants.KEYSLOT_ENTRY_NAME;
import static com.metaeffekt.artifact.analysis.flow.ng.StreamReadingUtils.skipToEntry;

/**
 * Assists in reading encrypted archives, hiding low-level annoyances.
 */
public class EncryptedZipProvider {
    private static final Logger LOG = LoggerFactory.getLogger(EncryptedZipProvider.class);

    private final ProviderParameters param;

    public EncryptedZipProvider(ProviderParameters param) {
        this.param = param;
    }

    /**
     * Reads keyslots from the stream to find a content key.
     * 
* This method may close the stream once it's done. * @param keyslotsFileInputStream the input stream to read keyslots from * @return returns the found content key (or throws otherwise) * @throws IOException throws if reading failed * @throws InvalidCipherTextException throws if decryption of the content key failed */ private Key getDecryptedContentKey(InputStream keyslotsFileInputStream) throws IOException, InvalidCipherTextException { // read user keys UserKeysForConsumer userKeys; // try to read the keyfile twice (once with selfcheck to warn the user of integrity errors, then try anyway) try { userKeys = UserKeysStorage.readUserKeysForConsumer(param.getUserKeysFile(), param.getPassword(), true); } catch (SelfcheckFailedException e) { LOG.warn("Keyfile's selfcheck failed: possibly corrupt at [{}]", param.getUserKeysFile().getPath()); userKeys = UserKeysStorage.readUserKeysForConsumer(param.getUserKeysFile(), param.getPassword(), false); } // read keyslots, trying to find the correct one try (final BufferedInputStream bufferedInputStream = new BufferedInputStream(keyslotsFileInputStream, 16384); final Reader reader = new InputStreamReader(bufferedInputStream, StandardCharsets.UTF_8); final MappingIterator mappingIterator = new ObjectMapper() .readerFor(DecryptableKeyslot.class) .readValues(reader) ) { while (mappingIterator.hasNext()) { DecryptableKeyslot toCheck = mappingIterator.next(); if (toCheck.checkHmac(userKeys)) { // decrypt keyslot getting content key byte[] contentKey = toCheck.getContentKeyUsing(userKeys); return new SecretKeySpec(contentKey, param.getContentAlgorithmParam().getAlgorithm() + param.getContentAlgorithmParam().getMode()); } } } return null; } /** * Processes the keyslots file, taken from ProviderParameters. * @param param parameters for processing * @return returns the content key (if found). * @throws IOException throws on io errors * @throws InvalidCipherTextException throws if a used cipher is not supported by the environment */ private Key processKeyslotsFile(ProviderParameters param) throws IOException, InvalidCipherTextException { // iterate key slots, trying to find the correct one for decryption try (InputStream inputStream = Files.newInputStream(param.getEncryptedZipPackage().toPath()); ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { // err if we can't find the entry if (!skipToEntry(zipInputStream, KEYSLOT_ENTRY_NAME)) { throw new RuntimeException("Zip package incompatible: could not find keyslots file"); } // should now have selected the keyslots entry, stream should be ready return getDecryptedContentKey(zipInputStream); } } public InputStream getEntryStream(String entryName) throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, InvalidCipherTextException { Key contentKey = processKeyslotsFile(param); if (contentKey == null) { throw new DecryptionImpossibleException("No content key found for the specified user key."); } try (final InputStream fileIn = Files.newInputStream(param.getEncryptedZipPackage().toPath(), StandardOpenOption.READ); final ZipInputStream zipInputStream = new ZipInputStream(fileIn)) { if (!skipToEntry(zipInputStream, entryName)) { throw new FileNotFoundException("Zip package incompatible: could not find requested entry"); } DecryptedEntryInputStream.ensureAuthenticated(zipInputStream, param.getContentAlgorithmParam(), contentKey); } catch (AEADBadTagException e) { throw new RuntimeException(e); } // and then stream it final InputStream fileIn = Files.newInputStream(param.getEncryptedZipPackage().toPath(), StandardOpenOption.READ); final ZipInputStream zipInputStream = new ZipInputStream(fileIn); if (!skipToEntry(zipInputStream, entryName)) { throw new FileNotFoundException("Zip package incompatible: could not find requested entry"); } //return DecryptedEntryInputStream.createEntryDecryptionInputStream(zipInputStream, contentKey, true); return new ByteArrayInputStream(readEntryCompletely(entryName)); } /** * Tries to find and read a zip entry and decrypt it. * @param entryName the name of the entry to read. Usually in * {@link com.metaeffekt.artifact.analysis.flow.ng.EncryptedArchiveConstants EncryptedArchiveConstants}. * @return raw decrypted bytes. * @throws IOException throws on error * @throws InvalidCipherTextException throws on error * @throws InvalidAlgorithmParameterException throws on error * @throws NoSuchPaddingException throws on error * @throws NoSuchAlgorithmException throws on error * @throws NoSuchProviderException throws on error * @throws InvalidKeyException throws on error */ public byte[] readEntryCompletely(String entryName) throws IOException, InvalidCipherTextException, InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException { Key contentKey = processKeyslotsFile(param); try (final InputStream fileIn = Files.newInputStream(param.getEncryptedZipPackage().toPath(), StandardOpenOption.READ); final ZipInputStream zipInputStream = new ZipInputStream(fileIn)) { if (!skipToEntry(zipInputStream, entryName)) { throw new FileNotFoundException("Zip package incompatible: could not find requested entry"); } byte[] byteArray; try (final DecryptedEntryInputStream decryptedInputStream = DecryptedEntryInputStream.createEntryDecryptionInputStream( zipInputStream, param.getContentAlgorithmParam(), contentKey )) { // decrypt the entire file and dump it to the user // we do this in one go since we're using a streaming concept under the hood. // this means that authentication tag mismatches could otherwise not be found until after parsing // faulty decrypted data. // the alternative would be to change the API to read an entry fully, authenticate and then stream it. byteArray = IOUtils.toByteArray(decryptedInputStream); } return byteArray; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy