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    }