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;
29
30import org.apache.harmony.crypto.internal.nls.Messages;
31import org.apache.harmony.security.fortress.Engine;
32
33
34/**
35 * This class provides the public API for <i>Message Authentication Code</i>
36 * (MAC) algorithms.
37 *
38 * @since Android 1.0
39 */
40public class Mac implements Cloneable {
41
42    //Used to access common engine functionality
43    private static final Engine engine = new Engine("Mac"); //$NON-NLS-1$
44
45    // Store used provider
46    private final Provider provider;
47
48    // Store used spi implementation
49    private final MacSpi spiImpl;
50
51    // Store used algorithm name
52    private final String algorithm;
53
54    // Store Mac state (initialized or not initialized)
55    private boolean isInitMac;
56
57    /**
58     * Creates a new {@code Mac} instance.
59     *
60     * @param macSpi
61     *            the implementation delegate.
62     * @param provider
63     *            the implementation provider.
64     * @param algorithm
65     *            the name of the MAC algorithm.
66     * @since Android 1.0
67     */
68    protected Mac(MacSpi macSpi, Provider provider, String algorithm) {
69        this.provider = provider;
70        this.algorithm = algorithm;
71        this.spiImpl = macSpi;
72        this.isInitMac = false;
73    }
74
75    /**
76     * Returns the name of the MAC algorithm.
77     *
78     * @return the name of the MAC algorithm.
79     * @since Android 1.0
80     */
81    public final String getAlgorithm() {
82        return algorithm;
83    }
84
85    /**
86     * Returns the provider of this {@code Mac} instance.
87     *
88     * @return the provider of this {@code Mac} instance.
89     * @since Android 1.0
90     */
91    public final Provider getProvider() {
92        return provider;
93    }
94
95    /**
96     * Creates a new {@code Mac} instance that provides the specified MAC
97     * algorithm.
98     *
99     * @param algorithm
100     *            the name of the requested MAC algorithm.
101     * @return the new {@code Mac} instance.
102     * @throws NoSuchAlgorithmException
103     *             if the specified algorithm is not available by any provider.
104     * @throws NullPointerException
105     *             if {@code algorithm} is {@code null}.
106     * @since Android 1.0
107     */
108    public static final Mac getInstance(String algorithm)
109            throws NoSuchAlgorithmException {
110        if (algorithm == null) {
111            throw new NullPointerException(Messages.getString("crypto.02")); //$NON-NLS-1$
112        }
113        synchronized (engine) {
114            engine.getInstance(algorithm, null);
115            return new Mac((MacSpi) engine.spi, engine.provider, algorithm);
116        }
117    }
118
119    /**
120     * Creates a new {@code Mac} instance that provides the specified MAC
121     * algorithm from the specified provider.
122     *
123     * @param algorithm
124     *            the name of the requested MAC algorithm.
125     * @param provider
126     *            the name of the provider that is providing the algorithm.
127     * @return the new {@code Mac} instance.
128     * @throws NoSuchAlgorithmException
129     *             if the specified algorithm is not provided by the specified
130     *             provider.
131     * @throws NoSuchProviderException
132     *             if the specified provider is not available.
133     * @throws IllegalArgumentException
134     *             if the specified provider name is {@code null} or empty.
135     * @throws NullPointerException
136     *             if {@code algorithm} is {@code null}
137     * @since Android 1.0.
138     */
139    public static final Mac getInstance(String algorithm, String provider)
140            throws NoSuchAlgorithmException, NoSuchProviderException {
141        if ((provider == null) || (provider.length() == 0)) {
142            throw new IllegalArgumentException(Messages.getString("crypto.03")); //$NON-NLS-1$
143        }
144        Provider impProvider = Security.getProvider(provider);
145        if (impProvider == null) {
146            throw new NoSuchProviderException(provider);
147        }
148        return getInstance(algorithm, impProvider);
149    }
150
151    /**
152     * Creates a new {@code Mac} instance that provides the specified MAC
153     * algorithm from the specified provider.
154     *
155     * @param algorithm
156     *            the name of the requested MAC algorithm.
157     * @param provider
158     *            the provider that is providing the algorithm.
159     * @return the new {@code Mac} instance.
160     * @throws NoSuchAlgorithmException
161     *             if the specified algorithm is not provided by the specified
162     *             provider.
163     * @throws IllegalArgumentException
164     *             if {@code provider} is {@code null}.
165     * @throws NullPointerException
166     *             if {@code algorithm} is {@code null}.
167     * @since Android 1.0
168     */
169    public static final Mac getInstance(String algorithm, Provider provider)
170            throws NoSuchAlgorithmException {
171        if (provider == null) {
172            throw new IllegalArgumentException(Messages.getString("crypto.04")); //$NON-NLS-1$
173        }
174        if (algorithm == null) {
175            throw new NullPointerException(Messages.getString("crypto.02")); //$NON-NLS-1$
176        }
177        synchronized (engine) {
178            engine.getInstance(algorithm, provider, null);
179            return new Mac((MacSpi) engine.spi, provider, algorithm);
180        }
181    }
182
183    /**
184     * Returns the length of this MAC (in bytes).
185     *
186     * @return the length of this MAC (in bytes).
187     */
188    public final int getMacLength() {
189        return spiImpl.engineGetMacLength();
190    }
191
192    /**
193     * Initializes this {@code Mac} instance with the specified key and
194     * algorithm parameters.
195     *
196     * @param key
197     *            the key to initialize this algorithm.
198     * @param params
199     *            the parameters for this algorithm.
200     * @throws InvalidKeyException
201     *             if the specified key cannot be used to initialize this
202     *             algorithm, or it is null.
203     * @throws InvalidAlgorithmParameterException
204     *             if the specified parameters cannot be used to initialize this
205     *             algorithm.
206     * @since Android 1.0
207     */
208    public final void init(Key key, AlgorithmParameterSpec params)
209            throws InvalidKeyException, InvalidAlgorithmParameterException {
210        if (key == null) {
211            throw new InvalidKeyException(Messages.getString("crypto.05")); //$NON-NLS-1$
212        }
213        spiImpl.engineInit(key, params);
214        isInitMac = true;
215    }
216
217    /**
218     * Initializes this {@code Mac} instance with the specified key.
219     *
220     * @param key
221     *            the key to initialize this algorithm.
222     * @throws InvalidKeyException
223     *             if initialization fails because the provided key is {@code
224     *             null}.
225     * @throws RuntimeException
226     *             if the specified key cannot be used to initialize this
227     *             algorithm.
228     * @since Android 1.0
229     */
230    public final void init(Key key) throws InvalidKeyException {
231        if (key == null) {
232            throw new InvalidKeyException(Messages.getString("crypto.05")); //$NON-NLS-1$
233        }
234        try {
235            spiImpl.engineInit(key, null);
236            isInitMac = true;
237        } catch (InvalidAlgorithmParameterException e) {
238            throw new RuntimeException(e);
239        }
240    }
241
242    /**
243     * Updates this {@code Mac} instance with the specified byte.
244     *
245     * @param input
246     *            the byte
247     * @throws IllegalStateException
248     *             if this MAC is not initialized.
249     * @since Android 1.0
250     */
251    public final void update(byte input) throws IllegalStateException {
252        if (!isInitMac) {
253            throw new IllegalStateException(Messages.getString("crypto.01"));
254        }
255        spiImpl.engineUpdate(input);
256    }
257
258    /**
259     * Updates this {@code Mac} instance with the data from the specified buffer
260     * {@code input} from the specified {@code offset} and length {@code len}.
261     *
262     * @param input
263     *            the buffer.
264     * @param offset
265     *            the offset in the buffer.
266     * @param len
267     *            the length of the data in the buffer.
268     * @throws IllegalStateException
269     *             if this MAC is not initialized.
270     * @throws IllegalArgumentException
271     *             if {@code offset} and {@code len} do not specified a valid
272     *             chunk in {@code input} buffer.
273     * @since Android 1.0
274     */
275    public final void update(byte[] input, int offset, int len)
276            throws IllegalStateException {
277        if (!isInitMac) {
278            throw new IllegalStateException(Messages.getString("crypto.01"));
279        }
280        if (input == null) {
281            return;
282        }
283        if ((offset < 0) || (len < 0) || ((offset + len) > input.length)) {
284            throw new IllegalArgumentException(Messages.getString("crypto.06")); //$NON-NLS-1$
285        }
286        spiImpl.engineUpdate(input, offset, len);
287    }
288
289    /**
290     * Copies the buffer provided as input for further processing.
291     *
292     * @param input
293     *            the buffer.
294     * @throws IllegalStateException
295     *             if this MAC is not initialized.
296     * @since Android 1.0
297     */
298    public final void update(byte[] input) throws IllegalStateException {
299        if (!isInitMac) {
300            throw new IllegalStateException(Messages.getString("crypto.01"));
301        }
302        if (input != null) {
303            spiImpl.engineUpdate(input, 0, input.length);
304        }
305    }
306
307    /**
308     * Updates this {@code Mac} instance with the data from the specified
309     * buffer, starting at {@link ByteBuffer#position()}, including the next
310     * {@link ByteBuffer#remaining()} bytes.
311     *
312     * @param input
313     *            the buffer.
314     * @throws IllegalStateException
315     *             if this MAC is not initialized.
316     * @since Android 1.0
317     */
318    public final void update(ByteBuffer input) {
319        if (!isInitMac) {
320            throw new IllegalStateException(Messages.getString("crypto.01"));
321        }
322        if (input != null) {
323            spiImpl.engineUpdate(input);
324        } else {
325            throw new IllegalArgumentException(Messages.getString("crypto.07")); //$NON-NLS-1$
326        }
327    }
328
329    /**
330     * Computes the digest of this MAC based on the data previously specified in
331     * {@link #update} calls.
332     * <p>
333     * This {@code Mac} instance is reverted to its initial state and can be
334     * used to start the next MAC computation with the same parameters or
335     * initialized with different parameters.
336     * </p>
337     *
338     * @return the generated digest.
339     * @throws IllegalStateException
340     *             if this MAC is not initialized.
341     * @since Android 1.0
342     */
343    public final byte[] doFinal() throws IllegalStateException {
344        if (!isInitMac) {
345            throw new IllegalStateException(Messages.getString("crypto.01"));
346        }
347        return spiImpl.engineDoFinal();
348    }
349
350    /**
351     * Computes the digest of this MAC based on the data previously specified in
352     * {@link #update} calls and stores the digest in the specified {@code
353     * output} buffer at offset {@code outOffset}.
354     * <p>
355     * This {@code Mac} instance is reverted to its initial state and can be
356     * used to start the next MAC computation with the same parameters or
357     * initialized with different parameters.
358     * </p>
359     *
360     * @param output
361     *            the output buffer
362     * @param outOffset
363     *            the offset in the output buffer
364     * @throws ShortBufferException
365     *             if the specified output buffer is either too small for the
366     *             digest to be stored, the specified output buffer is {@code
367     *             null}, or the specified offset is negative or past the length
368     *             of the output buffer.
369     * @throws IllegalStateException
370     *             if this MAC is not initialized.
371     * @since Android 1.0
372     */
373    public final void doFinal(byte[] output, int outOffset)
374            throws ShortBufferException, IllegalStateException {
375        if (!isInitMac) {
376            throw new IllegalStateException(Messages.getString("crypto.01"));
377        }
378        if (output == null) {
379            throw new ShortBufferException(Messages.getString("crypto.08")); //$NON-NLS-1$
380        }
381        if ((outOffset < 0) || (outOffset >= output.length)) {
382            throw new ShortBufferException(Messages.getString("crypto.09", //$NON-NLS-1$
383                    Integer.toString(outOffset)));
384        }
385        int t = spiImpl.engineGetMacLength();
386        if (t > (output.length - outOffset)) {
387            throw new ShortBufferException(
388                    Messages.getString("crypto.0A", //$NON-NLS-1$
389                            Integer.toString(t)));
390        }
391        byte[] result = spiImpl.engineDoFinal();
392        System.arraycopy(result, 0, output, outOffset, result.length);
393
394    }
395
396    /**
397     * Computes the digest of this MAC based on the data previously specified on
398     * {@link #update} calls and on the final bytes specified by {@code input}
399     * (or based on those bytes only).
400     * <p>
401     * This {@code Mac} instance is reverted to its initial state and can be
402     * used to start the next MAC computation with the same parameters or
403     * initialized with different parameters.
404     * </p>
405     *
406     * @param input
407     *            the final bytes.
408     * @return the generated digest.
409     * @throws IllegalStateException
410     *             if this MAC is not initialized.
411     * @since Android 1.0
412     */
413    public final byte[] doFinal(byte[] input) throws IllegalStateException {
414        if (!isInitMac) {
415            throw new IllegalStateException(Messages.getString("crypto.0B")); //$NON-NLS-1$
416        }
417        if (input != null) {
418            spiImpl.engineUpdate(input, 0, input.length);
419        }
420        return spiImpl.engineDoFinal();
421    }
422
423    /**
424     * Resets this {@code Mac} instance to its initial state.
425     * <p>
426     * This {@code Mac} instance is reverted to its initial state and can be
427     * used to start the next MAC computation with the same parameters or
428     * initialized with different parameters.
429     * </p>
430     *
431     * @since Android 1.0
432     */
433    public final void reset() {
434        spiImpl.engineReset();
435    }
436
437    /**
438     * Clones this {@code Mac} instance and the underlying implementation.
439     *
440     * @return the cloned instance.
441     * @throws CloneNotSupportedException
442     *             if the underlying implementation does not support cloning.
443     * @since Android 1.0
444     */
445    @Override
446    public final Object clone() throws CloneNotSupportedException {
447        MacSpi newSpiImpl = (MacSpi)spiImpl.clone();
448        Mac mac = new Mac(newSpiImpl, this.provider, this.algorithm);
449        mac.isInitMac = this.isInitMac;
450        return mac;
451    }
452}
453