GordianMacInputStream.java
/*
* GordianKnot: Security Suite
* Copyright 2012-2026. Tony Washer
*
* 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 io.github.tonywasher.joceanus.gordianknot.impl.core.stream;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMac;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
import org.bouncycastle.util.Arrays;
import java.io.InputStream;
/**
* Input stream MAC implementation.
*/
class GordianMacInputStream
extends GordianInputStream {
/**
* The MAC.
*/
private final GordianMac theMac;
/**
* The expected result.
*/
private final byte[] theExpected;
/**
* The expected digest.
*/
private byte[] theDigest;
/**
* Have we closed the stream?.
*/
private boolean haveClosed;
/**
* Constructor.
*
* @param pMac the MAC
* @param pExpected the expected result
* @param pInput the underlying input stream
*/
GordianMacInputStream(final GordianMac pMac,
final byte[] pExpected,
final InputStream pInput) {
/* Initialise underlying class */
super(pInput);
/* Store parameters */
theMac = pMac;
theExpected = Arrays.copyOf(pExpected, pExpected.length);
/* Create processed buffer */
setProcessedBuffer(new GordianMacBuffer(this));
}
/**
* Obtain the Mac.
*
* @return the Mac
*/
public GordianMac getMac() {
return theMac;
}
/**
* Set the expected digest.
*
* @param pExpected the expected digest
* @throws GordianException on error
*/
void setExpectedDigest(final byte[] pExpected) throws GordianException {
/* Set the expected client */
theDigest = pExpected;
/* If we are late reporting the digest, then check result now */
if (haveClosed) {
checkResult();
}
}
/**
* Check result.
*
* @throws GordianException on error
*/
void checkResult() throws GordianException {
/*
* If we are reading a small file, we may end up closing the input file before the digest stream is created, and
* therefore the digest has not yet been reported. If this is the case, defer processing until the digest is reported
*/
if (theDigest == null) {
/* Just note that we have closed and return */
haveClosed = true;
return;
}
/* Record the digest */
theMac.update(theDigest);
/* Calculate MAC */
final byte[] myResult = theMac.finish();
/* Check valid MAC */
if (!Arrays.areEqual(myResult, theExpected)) {
throw new GordianDataException("Invalid MAC");
}
}
/**
* Buffer to hold the processed data prior to returning it to the caller.
*/
private static final class GordianMacBuffer
extends GordianProcessedBuffer {
/**
* The InputStream.
*/
private final GordianMacInputStream theStream;
/**
* The MAC.
*/
private final GordianMac theMac;
/**
* Constructor.
*
* @param pStream the input stream
*/
GordianMacBuffer(final GordianMacInputStream pStream) {
theStream = pStream;
theMac = theStream.getMac();
}
@Override
public int processBytes(final byte[] pBuffer,
final int pLength) throws GordianException {
/* If we have EOF from the input stream */
if (pLength == -1) {
/* Record the fact and reset the read length to zero */
setEOFSeen();
theStream.checkResult();
return -1;
}
/* Update the MAC */
theMac.update(pBuffer, 0, pLength);
/* Set up buffer variables */
setBuffer(pBuffer, pLength);
return pLength;
}
}
}