/*
 * 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.nifi.pgp.util;

import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;

/**
 * Pretty Good Privacy Operation Utilities
 */
public class PGPOperationUtils {
    private static final boolean NESTED_SIGNATURE_DISABLED = false;

    private static final int BUFFER_SIZE = 2048;

    private static final long MODIFIED_MILLISECONDS = 86400000;

    private static final Date MODIFIED = new Date(MODIFIED_MILLISECONDS);

    private static final String FILE_NAME = String.class.getSimpleName();

    private static final char FILE_TYPE = PGPLiteralDataGenerator.BINARY;

    /**
     * Get data signed using one-pass signature generator
     *
     * @param contents Byte array contents to be signed
     * @param privateKey Private Key used for signing
     * @return Signed byte array
     * @throws PGPException Thrown when signature initialization failed
     * @throws IOException Thrown when signature generation failed
     */
    public static byte[] getOnePassSignedData(final byte[] contents, final PGPPrivateKey privateKey) throws IOException, PGPException {
        final PGPSignatureGenerator signatureGenerator = getSignatureGenerator(privateKey);

        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        final PGPOnePassSignature onePassSignature = signatureGenerator.generateOnePassVersion(NESTED_SIGNATURE_DISABLED);
        onePassSignature.encode(outputStream);

        outputStream.write(contents);
        signatureGenerator.update(contents);

        final PGPSignature signature = signatureGenerator.generate();
        signature.encode(outputStream);
        return outputStream.toByteArray();
    }

    /**
     * Get data signed using one-pass signature generator wrapping literal data
     *
     * @param contents Byte array contents to be signed
     * @param privateKey Private Key used for signing
     * @return Signed byte array
     * @throws PGPException Thrown when signature initialization failed
     * @throws IOException Thrown when signature generation failed
     */
    public static byte[] getOnePassSignedLiteralData(final byte[] contents, final PGPPrivateKey privateKey) throws IOException, PGPException {
        final PGPSignatureGenerator signatureGenerator = getSignatureGenerator(privateKey);

        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        final PGPOnePassSignature onePassSignature = signatureGenerator.generateOnePassVersion(NESTED_SIGNATURE_DISABLED);
        onePassSignature.encode(outputStream);

        final PGPLiteralDataGenerator generator = new PGPLiteralDataGenerator();
        final byte[] buffer = new byte[BUFFER_SIZE];
        try (final OutputStream literalStream = generator.open(outputStream, FILE_TYPE, FILE_NAME, MODIFIED, buffer)) {
            literalStream.write(contents);
            signatureGenerator.update(contents);
        }

        final PGPSignature signature = signatureGenerator.generate();
        signature.encode(outputStream);
        return outputStream.toByteArray();
    }

    private static PGPSignatureGenerator getSignatureGenerator(final PGPPrivateKey privateKey) throws PGPException {
        final PGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(privateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512);
        final PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
        signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
        return signatureGenerator;
    }
}
