| /** |
| * 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(); |
| } |
| } |
| |
| } |