1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package javax.crypto;
19
20import java.nio.ByteBuffer;
21import java.security.InvalidAlgorithmParameterException;
22import java.security.InvalidKeyException;
23import java.security.Key;
24import java.security.NoSuchAlgorithmException;
25import java.security.NoSuchProviderException;
26import java.security.Provider;
27import java.security.Security;
28import java.security.spec.AlgorithmParameterSpec;
29import org.apache.harmony.security.fortress.Engine;
30
31
32/**
33 * This class provides the public API for <i>Message Authentication Code</i>
34 * (MAC) algorithms.
35 */
36public class Mac implements Cloneable {
37
38    //Used to access common engine functionality
39    private static final Engine ENGINE = new Engine("Mac");
40
41    // Store used provider
42    private final Provider provider;
43
44    // Store used spi implementation
45    private final MacSpi spiImpl;
46
47    // Store used algorithm name
48    private final String algorithm;
49
50    // Store Mac state (initialized or not initialized)
51    private boolean isInitMac;
52
53    /**
54     * Creates a new {@code Mac} instance.
55     *
56     * @param macSpi
57     *            the implementation delegate.
58     * @param provider
59     *            the implementation provider.
60     * @param algorithm
61     *            the name of the MAC algorithm.
62     */
63    protected Mac(MacSpi macSpi, Provider provider, String algorithm) {
64        this.provider = provider;
65        this.algorithm = algorithm;
66        this.spiImpl = macSpi;
67        this.isInitMac = false;
68    }
69
70    /**
71     * Returns the name of the MAC algorithm.
72     *
73     * @return the name of the MAC algorithm.
74     */
75    public final String getAlgorithm() {
76        return algorithm;
77    }
78
79    /**
80     * Returns the provider of this {@code Mac} instance.
81     *
82     * @return the provider of this {@code Mac} instance.
83     */
84    public final Provider getProvider() {
85        return provider;
86    }
87
88    /**
89     * Creates a new {@code Mac} instance that provides the specified MAC
90     * algorithm.
91     *
92     * @param algorithm
93     *            the name of the requested MAC algorithm.
94     * @return the new {@code Mac} instance.
95     * @throws NoSuchAlgorithmException
96     *             if the specified algorithm is not available by any provider.
97     * @throws NullPointerException
98     *             if {@code algorithm} is {@code null} (instead of
99     *             NoSuchAlgorithmException as in 1.4 release).
100     */
101    public static final Mac getInstance(String algorithm)
102            throws NoSuchAlgorithmException {
103        if (algorithm == null) {
104            throw new NullPointerException("algorithm == null");
105        }
106        Engine.SpiAndProvider sap = ENGINE.getInstance(algorithm, null);
107        return new Mac((MacSpi) sap.spi, sap.provider, algorithm);
108    }
109
110    /**
111     * Creates a new {@code Mac} instance that provides the specified MAC
112     * algorithm from the specified provider.
113     *
114     * @param algorithm
115     *            the name of the requested MAC algorithm.
116     * @param provider
117     *            the name of the provider that is providing the algorithm.
118     * @return the new {@code Mac} instance.
119     * @throws NoSuchAlgorithmException
120     *             if the specified algorithm is not provided by the specified
121     *             provider.
122     * @throws NoSuchProviderException
123     *             if the specified provider is not available.
124     * @throws IllegalArgumentException
125     *             if the specified provider name is {@code null} or empty.
126     * @throws NullPointerException
127     *             if {@code algorithm} is {@code null} (instead of
128     *             NoSuchAlgorithmException as in 1.4 release).
129     */
130    public static final Mac getInstance(String algorithm, String provider)
131            throws NoSuchAlgorithmException, NoSuchProviderException {
132        if (provider == null || provider.isEmpty()) {
133            throw new IllegalArgumentException("Provider is null or empty");
134        }
135        Provider impProvider = Security.getProvider(provider);
136        if (impProvider == null) {
137            throw new NoSuchProviderException(provider);
138        }
139        return getInstance(algorithm, impProvider);
140    }
141
142    /**
143     * Creates a new {@code Mac} instance that provides the specified MAC
144     * algorithm from the specified provider.
145     *
146     * @param algorithm
147     *            the name of the requested MAC algorithm.
148     * @param provider
149     *            the provider that is providing the algorithm.
150     * @return the new {@code Mac} instance.
151     * @throws NoSuchAlgorithmException
152     *             if the specified algorithm is not provided by the specified
153     *             provider.
154     * @throws IllegalArgumentException
155     *             if {@code provider} is {@code null}.
156     * @throws NullPointerException
157     *             if {@code algorithm} is {@code null} (instead of
158     *             NoSuchAlgorithmException as in 1.4 release).
159     */
160    public static final Mac getInstance(String algorithm, Provider provider)
161            throws NoSuchAlgorithmException {
162        if (provider == null) {
163            throw new IllegalArgumentException("provider == null");
164        }
165        if (algorithm == null) {
166            throw new NullPointerException("algorithm == null");
167        }
168        Object spi = ENGINE.getInstance(algorithm, provider, null);
169        return new Mac((MacSpi) spi, provider, algorithm);
170    }
171
172    /**
173     * Returns the length of this MAC (in bytes).
174     *
175     * @return the length of this MAC (in bytes).
176     */
177    public final int getMacLength() {
178        return spiImpl.engineGetMacLength();
179    }
180
181    /**
182     * Initializes this {@code Mac} instance with the specified key and
183     * algorithm parameters.
184     *
185     * @param key
186     *            the key to initialize this algorithm.
187     * @param params
188     *            the parameters for this algorithm.
189     * @throws InvalidKeyException
190     *             if the specified key cannot be used to initialize this
191     *             algorithm, or it is null.
192     * @throws InvalidAlgorithmParameterException
193     *             if the specified parameters cannot be used to initialize this
194     *             algorithm.
195     */
196    public final void init(Key key, AlgorithmParameterSpec params)
197            throws InvalidKeyException, InvalidAlgorithmParameterException {
198        if (key == null) {
199            throw new InvalidKeyException("key == null");
200        }
201        spiImpl.engineInit(key, params);
202        isInitMac = true;
203    }
204
205    /**
206     * Initializes this {@code Mac} instance with the specified key.
207     *
208     * @param key
209     *            the key to initialize this algorithm.
210     * @throws InvalidKeyException
211     *             if initialization fails because the provided key is {@code
212     *             null}.
213     * @throws RuntimeException
214     *             if the specified key cannot be used to initialize this
215     *             algorithm.
216     */
217    public final void init(Key key) throws InvalidKeyException {
218        if (key == null) {
219            throw new InvalidKeyException("key == null");
220        }
221        try {
222            spiImpl.engineInit(key, null);
223            isInitMac = true;
224        } catch (InvalidAlgorithmParameterException e) {
225            throw new RuntimeException(e);
226        }
227    }
228
229    /**
230     * Updates this {@code Mac} instance with the specified byte.
231     *
232     * @param input
233     *            the byte
234     * @throws IllegalStateException
235     *             if this MAC is not initialized.
236     */
237    public final void update(byte input) throws IllegalStateException {
238        if (!isInitMac) {
239            throw new IllegalStateException();
240        }
241        spiImpl.engineUpdate(input);
242    }
243
244    /**
245     * Updates this {@code Mac} instance with the data from the specified buffer
246     * {@code input} from the specified {@code offset} and length {@code len}.
247     *
248     * @param input
249     *            the buffer.
250     * @param offset
251     *            the offset in the buffer.
252     * @param len
253     *            the length of the data in the buffer.
254     * @throws IllegalStateException
255     *             if this MAC is not initialized.
256     * @throws IllegalArgumentException
257     *             if {@code offset} and {@code len} do not specified a valid
258     *             chunk in {@code input} buffer.
259     */
260    public final void update(byte[] input, int offset, int len) throws IllegalStateException {
261        if (!isInitMac) {
262            throw new IllegalStateException();
263        }
264        if (input == null) {
265            return;
266        }
267        if ((offset < 0) || (len < 0) || ((offset + len) > input.length)) {
268            throw new IllegalArgumentException("Incorrect arguments");
269        }
270        spiImpl.engineUpdate(input, offset, len);
271    }
272
273    /**
274     * Copies the buffer provided as input for further processing.
275     *
276     * @param input
277     *            the buffer.
278     * @throws IllegalStateException
279     *             if this MAC is not initialized.
280     */
281    public final void update(byte[] input) throws IllegalStateException {
282        if (!isInitMac) {
283            throw new IllegalStateException();
284        }
285        if (input != null) {
286            spiImpl.engineUpdate(input, 0, input.length);
287        }
288    }
289
290    /**
291     * Updates this {@code Mac} instance with the data from the specified
292     * buffer, starting at {@link ByteBuffer#position()}, including the next
293     * {@link ByteBuffer#remaining()} bytes.
294     *
295     * @param input
296     *            the buffer.
297     * @throws IllegalStateException
298     *             if this MAC is not initialized.
299     */
300    public final void update(ByteBuffer input) {
301        if (!isInitMac) {
302            throw new IllegalStateException();
303        }
304        if (input != null) {
305            spiImpl.engineUpdate(input);
306        } else {
307            throw new IllegalArgumentException("input == null");
308        }
309    }
310
311    /**
312     * Computes the digest of this MAC based on the data previously specified in
313     * {@link #update} calls.
314     * <p>
315     * This {@code Mac} instance is reverted to its initial state and can be
316     * used to start the next MAC computation with the same parameters or
317     * initialized with different parameters.
318     *
319     * @return the generated digest.
320     * @throws IllegalStateException
321     *             if this MAC is not initialized.
322     */
323    public final byte[] doFinal() throws IllegalStateException {
324        if (!isInitMac) {
325            throw new IllegalStateException();
326        }
327        return spiImpl.engineDoFinal();
328    }
329
330    /**
331     * Computes the digest of this MAC based on the data previously specified in
332     * {@link #update} calls and stores the digest in the specified {@code
333     * output} buffer at offset {@code outOffset}.
334     * <p>
335     * This {@code Mac} instance is reverted to its initial state and can be
336     * used to start the next MAC computation with the same parameters or
337     * initialized with different parameters.
338     *
339     * @param output
340     *            the output buffer
341     * @param outOffset
342     *            the offset in the output buffer
343     * @throws ShortBufferException
344     *             if the specified output buffer is either too small for the
345     *             digest to be stored, the specified output buffer is {@code
346     *             null}, or the specified offset is negative or past the length
347     *             of the output buffer.
348     * @throws IllegalStateException
349     *             if this MAC is not initialized.
350     */
351    public final void doFinal(byte[] output, int outOffset)
352            throws ShortBufferException, IllegalStateException {
353        if (!isInitMac) {
354            throw new IllegalStateException();
355        }
356        if (output == null) {
357            throw new ShortBufferException("output == null");
358        }
359        if ((outOffset < 0) || (outOffset >= output.length)) {
360            throw new ShortBufferException("Incorrect outOffset: " + outOffset);
361        }
362        int t = spiImpl.engineGetMacLength();
363        if (t > (output.length - outOffset)) {
364            throw new ShortBufferException("Output buffer is short. Needed " + t + " bytes.");
365        }
366        byte[] result = spiImpl.engineDoFinal();
367        System.arraycopy(result, 0, output, outOffset, result.length);
368
369    }
370
371    /**
372     * Computes the digest of this MAC based on the data previously specified on
373     * {@link #update} calls and on the final bytes specified by {@code input}
374     * (or based on those bytes only).
375     * <p>
376     * This {@code Mac} instance is reverted to its initial state and can be
377     * used to start the next MAC computation with the same parameters or
378     * initialized with different parameters.
379     *
380     * @param input
381     *            the final bytes.
382     * @return the generated digest.
383     * @throws IllegalStateException
384     *             if this MAC is not initialized.
385     */
386    public final byte[] doFinal(byte[] input) throws IllegalStateException {
387        if (!isInitMac) {
388            throw new IllegalStateException();
389        }
390        if (input != null) {
391            spiImpl.engineUpdate(input, 0, input.length);
392        }
393        return spiImpl.engineDoFinal();
394    }
395
396    /**
397     * Resets this {@code Mac} instance to its initial state.
398     * <p>
399     * This {@code Mac} instance is reverted to its initial state and can be
400     * used to start the next MAC computation with the same parameters or
401     * initialized with different parameters.
402     */
403    public final void reset() {
404        spiImpl.engineReset();
405    }
406
407    /**
408     * Clones this {@code Mac} instance and the underlying implementation.
409     *
410     * @return the cloned instance.
411     * @throws CloneNotSupportedException
412     *             if the underlying implementation does not support cloning.
413     */
414    @Override
415    public final Object clone() throws CloneNotSupportedException {
416        MacSpi newSpiImpl = (MacSpi)spiImpl.clone();
417        Mac mac = new Mac(newSpiImpl, this.provider, this.algorithm);
418        mac.isInitMac = this.isInitMac;
419        return mac;
420    }
421}
422