blob: a44b4e37d18869a8069bf164edd7b3ac4cc205bd [file] [log] [blame]
/**
* 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.camel.converter.crypto;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.Key;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import static org.apache.camel.converter.crypto.HexUtils.byteArrayToHexString;
/**
* <code>HMACAccumulator</code> is used to build Hash Message Authentication
* Codes. It has two modes, one where all the data acquired is used to build the
* MAC and a second that assumes that the last n bytes of the acquired data will
* contain a MAC for all the data previous.
* <p>
* The first mode it used in an encryption phase to create a MAC for the
* encrypted data. The second mode is used in the decryption phase and
* recalculates the MAC taking into account that for cases where the encrypted
* data MAC has been appended. Internally the accumulator uses a circular buffer
* to simplify the housekeeping of avoiding the last n bytes.
* <p>
* It is assumed that the supplied buffersize is always greater than or equal to
* the mac length.
*/
public class HMACAccumulator {
protected OutputStream outputStream;
private CircularBuffer unprocessed;
private byte[] calculatedMac;
private Mac hmac;
private int maclength;
private byte[] appended;
HMACAccumulator() {
}
public HMACAccumulator(Key key, String macAlgorithm, String cryptoProvider, int buffersize) throws Exception {
hmac = cryptoProvider == null ? Mac.getInstance(macAlgorithm) : Mac.getInstance(macAlgorithm, cryptoProvider);
Key hmacKey = new SecretKeySpec(key.getEncoded(), macAlgorithm);
hmac.init(hmacKey);
maclength = hmac.getMacLength();
unprocessed = new CircularBuffer(buffersize + maclength);
}
/**
* Update buffer with MAC. Typically used in the encryption phase where no
* hmac is appended to the buffer.
*/
public void encryptUpdate(byte[] buffer, int read) {
hmac.update(buffer, 0, read);
}
/**
* Update buffer with MAC taking into account that a MAC is appended to the
* buffer and should be precluded from the MAC calculation.
*/
public void decryptUpdate(byte[] buffer, int read) throws IOException {
unprocessed.write(buffer, 0, read);
int safe = unprocessed.availableForRead() - maclength;
if (safe > 0) {
unprocessed.read(buffer, 0, safe);
hmac.update(buffer, 0, safe);
if (outputStream != null) {
outputStream.write(buffer, 0, safe);
}
}
}
public byte[] getCalculatedMac() {
if (calculatedMac == null) {
calculatedMac = hmac.doFinal();
}
return calculatedMac;
}
public byte[] getAppendedMac() {
if (appended == null) {
appended = new byte[maclength];
unprocessed.read(appended, 0, maclength);
}
return appended;
}
public void validate() {
byte[] actual = getCalculatedMac();
byte[] expected = getAppendedMac();
for (int x = 0; x < actual.length; x++) {
if (expected[x] != actual[x]) {
throw new IllegalStateException("Expected mac did not match actual mac\nexpected:"
+ byteArrayToHexString(expected) + "\n actual:" + byteArrayToHexString(actual));
}
}
}
public int getMaclength() {
return maclength;
}
public void attachStream(ByteArrayOutputStream outputStream) {
this.outputStream = outputStream;
}
static class CircularBuffer {
private byte[] buffer;
private int write;
private int read;
private int available;
public CircularBuffer(int bufferSize) {
buffer = new byte[bufferSize];
available = bufferSize;
}
public void write(byte[] data, int pos, int len) {
if (available >= len) {
if (write + len > buffer.length) {
int overlap = write + len % buffer.length;
System.arraycopy(data, 0, buffer, write, len - overlap);
System.arraycopy(data, len - overlap, buffer, 0, overlap);
} else {
System.arraycopy(data, pos, buffer, write, len);
}
write = (write + len) % buffer.length;
available -= len;
}
}
public int read(byte[] dest, int position, int len) {
if (dest.length - position >= len) {
if (buffer.length - available >= len) {
int overlap = (read + len) % buffer.length;
if (read > write) {
int x = buffer.length - read;
System.arraycopy(buffer, read, dest, position, buffer.length - read);
System.arraycopy(buffer, 0, dest, position + x, overlap);
} else {
System.arraycopy(buffer, read, dest, position, len);
}
read = (read + len) % buffer.length;
available += len;
return len;
}
}
return 0;
}
public boolean compareTo(byte[] compare, int pos, int len) {
boolean equal = false;
if (len <= availableForRead()) {
int x = 0;
while (equal && x < len) {
equal = compare[pos + x] != buffer[read + x % buffer.length];
}
}
return equal;
}
public int availableForRead() {
return buffer.length - available;
}
public int availableForWrite() {
return available;
}
public String show() {
StringBuilder b = new StringBuilder(HexUtils.byteArrayToHexString(buffer)).append("\n");
for (int x = read; --x >= 0;) {
b.append("--");
}
b.append("r");
b.append("\n");
for (int x = write; --x >= 0;) {
b.append("--");
}
b.append("w");
b.append("\n");
return b.toString();
}
}
}