com.joyent.manta.client.multipart.EncryptionState.java Source code

Java tutorial

Introduction

Here is the source code for com.joyent.manta.client.multipart.EncryptionState.java

Source

/*
 * Copyright (c) 2017, Joyent, Inc. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package com.joyent.manta.client.multipart;

import com.joyent.manta.client.crypto.EncryptionContext;
import com.joyent.manta.exception.MantaMultipartException;
import com.joyent.manta.util.HmacOutputStream;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.Validate;
import org.bouncycastle.crypto.macs.HMac;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Class that contains the state of the encryption streaming
 * ciphers used to encrypt multipart uploads.
 *
 * @author <a href="https://github.com/cburroughs/">Chris Burroughs</a>
 * @since 3.0.0
 */
public class EncryptionState {

    /**
     * Logger instance.
     */
    private static final transient Logger LOGGER = LoggerFactory.getLogger(EncryptionState.class);

    /**
     * Sentinel value for an EncryptionState which has not yet sent any parts.
     */
    static final int NOT_STARTED = -1;

    /**
     * Encryption cipher state object.
     */
    private final EncryptionContext encryptionContext;

    /**
     * Lock used to coordinate concurrent operations.
     */
    private final ReentrantLock lock;

    /**
     * The number of the last part processed.
     */
    private int lastPartNumber = NOT_STARTED;

    /**
     * The multipart stream that allows for attaching and detaching streams.
     */
    private transient MultipartOutputStream multipartStream = null;

    /**
     * The encrypting stream.
     */
    private transient OutputStream cipherStream = null;

    /**
     * Indicates if the multipart stream buffer has been flushed and
     * final cipher auth bytes written.
     */
    private boolean lastPartAuthWritten = false;

    /**
     * Zero argument constructor used for serialization.
     */
    private EncryptionState() {
        this.encryptionContext = null;
        this.lock = new ReentrantLock();
    }

    /**
     * <p>Creates a new multipart encryption state object.</p>
     *
     * <p>NOTE: This class is tightly bound to the lifecycle of the
     * encrypted MPU manager.  In particular the streams are now
     * instantiated as part of the constructor and must be set before
     * use.</p>
     *
     * @param encryptionContext encryption cipher state object
     */
    public EncryptionState(final EncryptionContext encryptionContext) {
        this.encryptionContext = encryptionContext;
        this.lock = new ReentrantLock();
    }

    EncryptionContext getEncryptionContext() {
        return encryptionContext;
    }

    ReentrantLock getLock() {
        return lock;
    }

    int getLastPartNumber() {
        return lastPartNumber;
    }

    MultipartOutputStream getMultipartStream() {
        return multipartStream;
    }

    OutputStream getCipherStream() {
        return cipherStream;
    }

    void setLastPartNumber(final int lastPartNumber) {
        this.lastPartNumber = lastPartNumber;
    }

    void setMultipartStream(final MultipartOutputStream multipartStream) {
        this.multipartStream = multipartStream;
    }

    void setCipherStream(final OutputStream cipherStream) {
        this.cipherStream = cipherStream;
    }

    boolean isLastPartAuthWritten() {
        return lastPartAuthWritten;
    }

    ByteArrayOutputStream remainderAndLastPartAuth() throws IOException {
        if (!getLock().isHeldByCurrentThread()) {
            throw new IllegalStateException("remainderAndLastPartAuth called without lock owned");
        }
        if (isLastPartAuthWritten()) {
            final String msg = "final CSE auth already written (complete called multiple times or "
                    + "parts below min size)";
            MantaMultipartException mme = new MantaMultipartException(new IllegalStateException(msg));
            mme.setContextValue("lastPartNumber", getLastPartNumber());
            throw mme;
        }
        ByteArrayOutputStream remainderStream = new ByteArrayOutputStream();
        getMultipartStream().setNext(remainderStream);
        getCipherStream().close();
        remainderStream.write(getMultipartStream().getRemainder());

        if (getCipherStream().getClass().equals(HmacOutputStream.class)) {
            HMac hmac = ((HmacOutputStream) getCipherStream()).getHmac();
            byte[] hmacBytes = new byte[hmac.getMacSize()];
            hmac.doFinal(hmacBytes, 0);

            final int hmacSize = encryptionContext.getCipherDetails().getAuthenticationTagOrHmacLengthInBytes();

            Validate.isTrue(hmacBytes.length == hmacSize,
                    "HMAC actual bytes doesn't equal the number of bytes expected");

            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("HMAC: {}", Hex.encodeHexString(hmacBytes));
            }
            remainderStream.write(hmacBytes);
        }
        lastPartAuthWritten = true;
        return remainderStream;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }

        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        final EncryptionState that = (EncryptionState) o;

        return lastPartNumber == that.lastPartNumber && lastPartAuthWritten == that.lastPartAuthWritten
                && Objects.equals(encryptionContext, that.encryptionContext);
    }

    @Override
    public int hashCode() {
        return Objects.hash(encryptionContext, lastPartNumber);
    }
}