001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.converter.crypto;
018
019 import java.io.ByteArrayOutputStream;
020 import java.io.IOException;
021 import java.io.OutputStream;
022 import java.security.Key;
023 import javax.crypto.Mac;
024 import javax.crypto.spec.SecretKeySpec;
025
026 import static org.apache.camel.converter.crypto.HexUtils.byteArrayToHexString;
027
028 /**
029 * <code>HMACAccumulator</code> is used to build Hash Message Authentication
030 * Codes. It has two modes, one where all the data acquired is used to build the
031 * MAC and a second that assumes that the last n bytes of the acquired data will
032 * contain a MAC for all the data previous.
033 * <p>
034 * The first mode it used in an encryption phase to create a MAC for the
035 * encrypted data. The second mode is used in the decryption phase and
036 * recalculates the MAC taking into account that for cases where the encrypted
037 * data MAC has been appended. Internally the accumulator uses a circular buffer
038 * to simplify the housekeeping of avoiding the last n bytes.
039 * <p>
040 * It is assumed that the supplied buffersize is always greater than or equal to
041 * the mac length.
042 */
043 public class HMACAccumulator {
044
045 protected OutputStream outputStream;
046 private CircularBuffer unprocessed;
047 private byte[] calculatedMac;
048 private Mac hmac;
049 private int maclength;
050 private byte[] appended;
051
052 HMACAccumulator() {
053 }
054
055 public HMACAccumulator(Key key, String macAlgorithm, String cryptoProvider, int buffersize) throws Exception {
056 hmac = cryptoProvider == null ? Mac.getInstance(macAlgorithm) : Mac.getInstance(macAlgorithm, cryptoProvider);
057 Key hmacKey = new SecretKeySpec(key.getEncoded(), macAlgorithm);
058 hmac.init(hmacKey);
059 maclength = hmac.getMacLength();
060 unprocessed = new CircularBuffer(buffersize + maclength);
061 }
062
063 /**
064 * Update buffer with MAC. Typically used in the encryption phase where no
065 * hmac is appended to the buffer.
066 */
067 public void encryptUpdate(byte[] buffer, int read) {
068 hmac.update(buffer, 0, read);
069 }
070
071 /**
072 * Update buffer with MAC taking into account that a MAC is appended to the
073 * buffer and should be precluded from the MAC calculation.
074 */
075 public void decryptUpdate(byte[] buffer, int read) throws IOException {
076 unprocessed.write(buffer, 0, read);
077 int safe = unprocessed.availableForRead() - maclength;
078 if (safe > 0) {
079 unprocessed.read(buffer, 0, safe);
080 hmac.update(buffer, 0, safe);
081 if (outputStream != null) {
082 outputStream.write(buffer, 0, safe);
083 }
084 }
085 }
086
087 public byte[] getCalculatedMac() {
088 if (calculatedMac == null) {
089 calculatedMac = hmac.doFinal();
090 }
091 return calculatedMac;
092 }
093
094 public byte[] getAppendedMac() {
095 if (appended == null) {
096 appended = new byte[maclength];
097 unprocessed.read(appended, 0, maclength);
098 }
099 return appended;
100 }
101
102 public void validate() {
103 byte[] actual = getCalculatedMac();
104 byte[] expected = getAppendedMac();
105 for (int x = 0; x < actual.length; x++) {
106 if (expected[x] != actual[x]) {
107 throw new IllegalStateException("Expected mac did not match actual mac\nexpected:"
108 + byteArrayToHexString(expected) + "\n actual:" + byteArrayToHexString(actual));
109 }
110 }
111 }
112
113 public int getMaclength() {
114 return maclength;
115 }
116
117 public void attachStream(ByteArrayOutputStream outputStream) {
118 this.outputStream = outputStream;
119 }
120
121 static class CircularBuffer {
122 private byte[] buffer;
123 private int write;
124 private int read;
125 private int available;
126
127 public CircularBuffer(int bufferSize) {
128 buffer = new byte[bufferSize];
129 available = bufferSize;
130 }
131
132 public void write(byte[] data, int pos, int len) {
133 if (available >= len) {
134 if (write + len > buffer.length) {
135 int overlap = write + len % buffer.length;
136 System.arraycopy(data, 0, buffer, write, len - overlap);
137 System.arraycopy(data, len - overlap, buffer, 0, overlap);
138 } else {
139 System.arraycopy(data, pos, buffer, write, len);
140 }
141 write = (write + len) % buffer.length;
142 available -= len;
143 }
144 }
145
146 public int read(byte[] dest, int position, int len) {
147 if (dest.length - position >= len) {
148 if (buffer.length - available >= len) {
149 int overlap = (read + len) % buffer.length;
150 if (read > write) {
151 int x = buffer.length - read;
152 System.arraycopy(buffer, read, dest, position, buffer.length - read);
153 System.arraycopy(buffer, 0, dest, position + x, overlap);
154 } else {
155 System.arraycopy(buffer, read, dest, position, len);
156 }
157 read = (read + len) % buffer.length;
158 available += len;
159 return len;
160 }
161 }
162 return 0;
163 }
164
165 public boolean compareTo(byte[] compare, int pos, int len) {
166 boolean equal = false;
167 if (len <= availableForRead()) {
168 int x = 0;
169 while (equal && x < len) {
170 equal = compare[pos + x] != buffer[read + x % buffer.length];
171 }
172 }
173 return equal;
174 }
175
176 public int availableForRead() {
177 return buffer.length - available;
178 }
179
180 public int availableForWrite() {
181 return available;
182 }
183
184 public String show() {
185 StringBuilder b = new StringBuilder(HexUtils.byteArrayToHexString(buffer)).append("\n");
186 for (int x = read; --x >= 0;) {
187 b.append("--");
188 }
189 b.append("r");
190 b.append("\n");
191 for (int x = write; --x >= 0;) {
192 b.append("--");
193 }
194 b.append("w");
195 b.append("\n");
196 return b.toString();
197 }
198 }
199
200 }