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 
18 package javax.crypto;
19 
20 import java.nio.ByteBuffer;
21 import java.security.InvalidAlgorithmParameterException;
22 import java.security.InvalidKeyException;
23 import java.security.Key;
24 import java.security.NoSuchAlgorithmException;
25 import java.security.NoSuchProviderException;
26 import java.security.Provider;
27 import java.security.ProviderException;
28 import java.security.Security;
29 import java.security.spec.AlgorithmParameterSpec;
30 import java.util.ArrayList;
31 import 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 public class Mac implements Cloneable {
39 
40     // The service name.
41     private static final String SERVICE = "Mac";
42 
43     //Used to access common engine functionality
44     private static final Engine ENGINE = new Engine(SERVICE);
45 
46     // Store used provider
47     private Provider provider;
48 
49     // Provider that was requested during creation.
50     private final Provider specifiedProvider;
51 
52     // Store used spi implementation
53     private MacSpi spiImpl;
54 
55     // Store used algorithm name
56     private final String algorithm;
57 
58     /**
59      * Lock held while the SPI is initializing.
60      */
61     private final Object initLock = new Object();
62 
63     // Store Mac state (initialized or not initialized)
64     private boolean isInitMac;
65 
66     /**
67      * Creates a new {@code Mac} instance.
68      *
69      * @param macSpi
70      *            the implementation delegate.
71      * @param provider
72      *            the implementation provider.
73      * @param algorithm
74      *            the name of the MAC algorithm.
75      */
Mac(MacSpi macSpi, Provider provider, String algorithm)76     protected Mac(MacSpi macSpi, Provider provider, String algorithm) {
77         this.specifiedProvider = provider;
78         this.algorithm = algorithm;
79         this.spiImpl = macSpi;
80         this.isInitMac = false;
81     }
82 
83     /**
84      * Returns the name of the MAC algorithm.
85      *
86      * @return the name of the MAC algorithm.
87      */
getAlgorithm()88     public final String getAlgorithm() {
89         return algorithm;
90     }
91 
92     /**
93      * Returns the provider of this {@code Mac} instance.
94      *
95      * @return the provider of this {@code Mac} instance.
96      */
getProvider()97     public final Provider getProvider() {
98         getSpi();
99         return provider;
100     }
101 
102     /**
103      * Creates a new {@code Mac} instance that provides the specified MAC
104      * algorithm.
105      *
106      * @param algorithm
107      *            the name of the requested MAC algorithm.
108      * @return the new {@code Mac} instance.
109      * @throws NoSuchAlgorithmException
110      *             if the specified algorithm is not available by any provider.
111      * @throws NullPointerException
112      *             if {@code algorithm} is {@code null} (instead of
113      *             NoSuchAlgorithmException as in 1.4 release).
114      */
getInstance(String algorithm)115     public static final Mac getInstance(String algorithm)
116             throws NoSuchAlgorithmException {
117         return getMac(algorithm, null);
118     }
119 
120     /**
121      * Creates a new {@code Mac} instance that provides the specified MAC
122      * algorithm from the specified provider.
123      *
124      * @param algorithm
125      *            the name of the requested MAC algorithm.
126      * @param provider
127      *            the name of the provider that is providing the algorithm.
128      * @return the new {@code Mac} instance.
129      * @throws NoSuchAlgorithmException
130      *             if the specified algorithm is not provided by the specified
131      *             provider.
132      * @throws NoSuchProviderException
133      *             if the specified provider is not available.
134      * @throws IllegalArgumentException
135      *             if the specified provider name is {@code null} or empty.
136      * @throws NullPointerException
137      *             if {@code algorithm} is {@code null} (instead of
138      *             NoSuchAlgorithmException as in 1.4 release).
139      */
getInstance(String algorithm, String provider)140     public static final Mac getInstance(String algorithm, String provider)
141             throws NoSuchAlgorithmException, NoSuchProviderException {
142         if (provider == null || provider.isEmpty()) {
143             throw new IllegalArgumentException("Provider is null or empty");
144         }
145         Provider impProvider = Security.getProvider(provider);
146         if (impProvider == null) {
147             throw new NoSuchProviderException(provider);
148         }
149         return getMac(algorithm, impProvider);
150     }
151 
152     /**
153      * Creates a new {@code Mac} instance that provides the specified MAC
154      * algorithm from the specified provider. The {@code provider} supplied
155      * does not have to be registered.
156      *
157      * @param algorithm
158      *            the name of the requested MAC algorithm.
159      * @param provider
160      *            the provider that is providing the algorithm.
161      * @return the new {@code Mac} instance.
162      * @throws NoSuchAlgorithmException
163      *             if the specified algorithm is not provided by the specified
164      *             provider.
165      * @throws IllegalArgumentException
166      *             if {@code provider} is {@code null}.
167      * @throws NullPointerException
168      *             if {@code algorithm} is {@code null} (instead of
169      *             NoSuchAlgorithmException as in 1.4 release).
170      */
getInstance(String algorithm, Provider provider)171     public static final Mac getInstance(String algorithm, Provider provider)
172             throws NoSuchAlgorithmException {
173         if (provider == null) {
174             throw new IllegalArgumentException("provider == null");
175         }
176         return getMac(algorithm, provider);
177     }
178 
getMac(String algorithm, Provider provider)179     private static Mac getMac(String algorithm, Provider provider)
180             throws NoSuchAlgorithmException {
181         if (algorithm == null) {
182             throw new NullPointerException("algorithm == null");
183         }
184 
185         if (tryAlgorithm(null, provider, algorithm) == null) {
186             if (provider == null) {
187                 throw new NoSuchAlgorithmException("No provider found for " + algorithm);
188             } else {
189                 throw new NoSuchAlgorithmException("Provider " + provider.getName()
190                         + " does not provide " + algorithm);
191             }
192         }
193         return new Mac(null, provider, algorithm);
194     }
195 
tryAlgorithm(Key key, Provider provider, String algorithm)196     private static Engine.SpiAndProvider tryAlgorithm(Key key, Provider provider, String algorithm) {
197         if (provider != null) {
198             Provider.Service service = provider.getService(SERVICE, algorithm);
199             if (service == null) {
200                 return null;
201             }
202             return tryAlgorithmWithProvider(key, service);
203         }
204         ArrayList<Provider.Service> services = ENGINE.getServices(algorithm);
205         if (services == null) {
206             return null;
207         }
208         for (Provider.Service service : services) {
209             Engine.SpiAndProvider sap = tryAlgorithmWithProvider(key, service);
210             if (sap != null) {
211                 return sap;
212             }
213         }
214         return null;
215     }
216 
tryAlgorithmWithProvider(Key key, Provider.Service service)217     private static Engine.SpiAndProvider tryAlgorithmWithProvider(Key key, Provider.Service service) {
218         try {
219             if (key != null && !service.supportsParameter(key)) {
220                 return null;
221             }
222 
223             Engine.SpiAndProvider sap = ENGINE.getInstance(service, null);
224             if (sap.spi == null || sap.provider == null) {
225                 return null;
226             }
227             if (!(sap.spi instanceof MacSpi)) {
228                 return null;
229             }
230             return sap;
231         } catch (NoSuchAlgorithmException ignored) {
232         }
233         return null;
234     }
235 
236     /**
237      * Makes sure a MacSpi that matches this type is selected.
238      */
getSpi(Key key)239     private MacSpi getSpi(Key key) {
240         synchronized (initLock) {
241             if (spiImpl != null && provider != null && key == null) {
242                 return spiImpl;
243             }
244 
245             if (algorithm == null) {
246                 return null;
247             }
248 
249             final Engine.SpiAndProvider sap = tryAlgorithm(key, specifiedProvider, algorithm);
250             if (sap == null) {
251                 throw new ProviderException("No provider for " + getAlgorithm());
252             }
253 
254             /*
255              * Set our Spi if we've never been initialized or if we have the Spi
256              * specified and have a null provider.
257              */
258             if (spiImpl == null || provider != null) {
259                 spiImpl = (MacSpi) sap.spi;
260             }
261             provider = sap.provider;
262 
263             return spiImpl;
264         }
265     }
266 
267     /**
268      * Convenience call when the Key is not available.
269      */
getSpi()270     private MacSpi getSpi() {
271         return getSpi(null);
272     }
273 
274     /**
275      * Returns the length of this MAC (in bytes).
276      *
277      * @return the length of this MAC (in bytes).
278      */
getMacLength()279     public final int getMacLength() {
280         return getSpi().engineGetMacLength();
281     }
282 
283     /**
284      * Initializes this {@code Mac} instance with the specified key and
285      * algorithm parameters.
286      *
287      * @param key
288      *            the key to initialize this algorithm.
289      * @param params
290      *            the parameters for this algorithm.
291      * @throws InvalidKeyException
292      *             if the specified key cannot be used to initialize this
293      *             algorithm, or it is null.
294      * @throws InvalidAlgorithmParameterException
295      *             if the specified parameters cannot be used to initialize this
296      *             algorithm.
297      */
init(Key key, AlgorithmParameterSpec params)298     public final void init(Key key, AlgorithmParameterSpec params)
299             throws InvalidKeyException, InvalidAlgorithmParameterException {
300         if (key == null) {
301             throw new InvalidKeyException("key == null");
302         }
303         getSpi(key).engineInit(key, params);
304         isInitMac = true;
305     }
306 
307     /**
308      * Initializes this {@code Mac} instance with the specified key.
309      *
310      * @param key
311      *            the key to initialize this algorithm.
312      * @throws InvalidKeyException
313      *             if initialization fails because the provided key is {@code
314      *             null}.
315      * @throws RuntimeException
316      *             if the specified key cannot be used to initialize this
317      *             algorithm.
318      */
init(Key key)319     public final void init(Key key) throws InvalidKeyException {
320         if (key == null) {
321             throw new InvalidKeyException("key == null");
322         }
323         try {
324             getSpi(key).engineInit(key, null);
325             isInitMac = true;
326         } catch (InvalidAlgorithmParameterException e) {
327             throw new RuntimeException(e);
328         }
329     }
330 
331     /**
332      * Updates this {@code Mac} instance with the specified byte.
333      *
334      * @param input
335      *            the byte
336      * @throws IllegalStateException
337      *             if this MAC is not initialized.
338      */
update(byte input)339     public final void update(byte input) throws IllegalStateException {
340         if (!isInitMac) {
341             throw new IllegalStateException();
342         }
343         getSpi().engineUpdate(input);
344     }
345 
346     /**
347      * Updates this {@code Mac} instance with the data from the specified buffer
348      * {@code input} from the specified {@code offset} and length {@code len}.
349      *
350      * @param input
351      *            the buffer.
352      * @param offset
353      *            the offset in the buffer.
354      * @param len
355      *            the length of the data in the buffer.
356      * @throws IllegalStateException
357      *             if this MAC is not initialized.
358      * @throws IllegalArgumentException
359      *             if {@code offset} and {@code len} do not specified a valid
360      *             chunk in {@code input} buffer.
361      */
update(byte[] input, int offset, int len)362     public final void update(byte[] input, int offset, int len) throws IllegalStateException {
363         if (!isInitMac) {
364             throw new IllegalStateException();
365         }
366         if (input == null) {
367             return;
368         }
369         if ((offset < 0) || (len < 0) || ((offset + len) > input.length)) {
370             throw new IllegalArgumentException("Incorrect arguments."
371                                                + " input.length=" + input.length
372                                                + " offset=" + offset + ", len=" + len);
373         }
374         getSpi().engineUpdate(input, offset, len);
375     }
376 
377     /**
378      * Copies the buffer provided as input for further processing.
379      *
380      * @param input
381      *            the buffer.
382      * @throws IllegalStateException
383      *             if this MAC is not initialized.
384      */
update(byte[] input)385     public final void update(byte[] input) throws IllegalStateException {
386         if (!isInitMac) {
387             throw new IllegalStateException();
388         }
389         if (input != null) {
390             getSpi().engineUpdate(input, 0, input.length);
391         }
392     }
393 
394     /**
395      * Updates this {@code Mac} instance with the data from the specified
396      * buffer, starting at {@link ByteBuffer#position()}, including the next
397      * {@link ByteBuffer#remaining()} bytes.
398      *
399      * @param input
400      *            the buffer.
401      * @throws IllegalStateException
402      *             if this MAC is not initialized.
403      */
update(ByteBuffer input)404     public final void update(ByteBuffer input) {
405         if (!isInitMac) {
406             throw new IllegalStateException();
407         }
408         if (input != null) {
409             getSpi().engineUpdate(input);
410         } else {
411             throw new IllegalArgumentException("input == null");
412         }
413     }
414 
415     /**
416      * Computes the digest of this MAC based on the data previously specified in
417      * {@link #update} calls.
418      * <p>
419      * This {@code Mac} instance is reverted to its initial state and can be
420      * used to start the next MAC computation with the same parameters or
421      * initialized with different parameters.
422      *
423      * @return the generated digest.
424      * @throws IllegalStateException
425      *             if this MAC is not initialized.
426      */
doFinal()427     public final byte[] doFinal() throws IllegalStateException {
428         if (!isInitMac) {
429             throw new IllegalStateException();
430         }
431         return getSpi().engineDoFinal();
432     }
433 
434     /**
435      * Computes the digest of this MAC based on the data previously specified in
436      * {@link #update} calls and stores the digest in the specified {@code
437      * output} buffer at offset {@code outOffset}.
438      * <p>
439      * This {@code Mac} instance is reverted to its initial state and can be
440      * used to start the next MAC computation with the same parameters or
441      * initialized with different parameters.
442      *
443      * @param output
444      *            the output buffer
445      * @param outOffset
446      *            the offset in the output buffer
447      * @throws ShortBufferException
448      *             if the specified output buffer is either too small for the
449      *             digest to be stored, the specified output buffer is {@code
450      *             null}, or the specified offset is negative or past the length
451      *             of the output buffer.
452      * @throws IllegalStateException
453      *             if this MAC is not initialized.
454      */
doFinal(byte[] output, int outOffset)455     public final void doFinal(byte[] output, int outOffset)
456             throws ShortBufferException, IllegalStateException {
457         if (!isInitMac) {
458             throw new IllegalStateException();
459         }
460         if (output == null) {
461             throw new ShortBufferException("output == null");
462         }
463         if ((outOffset < 0) || (outOffset >= output.length)) {
464             throw new ShortBufferException("Incorrect outOffset: " + outOffset);
465         }
466         MacSpi spi = getSpi();
467         int t = spi.engineGetMacLength();
468         if (t > (output.length - outOffset)) {
469             throw new ShortBufferException("Output buffer is short. Needed " + t + " bytes.");
470         }
471         byte[] result = spi.engineDoFinal();
472         System.arraycopy(result, 0, output, outOffset, result.length);
473 
474     }
475 
476     /**
477      * Computes the digest of this MAC based on the data previously specified on
478      * {@link #update} calls and on the final bytes specified by {@code input}
479      * (or based on those bytes only).
480      * <p>
481      * This {@code Mac} instance is reverted to its initial state and can be
482      * used to start the next MAC computation with the same parameters or
483      * initialized with different parameters.
484      *
485      * @param input
486      *            the final bytes.
487      * @return the generated digest.
488      * @throws IllegalStateException
489      *             if this MAC is not initialized.
490      */
doFinal(byte[] input)491     public final byte[] doFinal(byte[] input) throws IllegalStateException {
492         if (!isInitMac) {
493             throw new IllegalStateException();
494         }
495         MacSpi spi = getSpi();
496         if (input != null) {
497             spi.engineUpdate(input, 0, input.length);
498         }
499         return spi.engineDoFinal();
500     }
501 
502     /**
503      * Resets this {@code Mac} instance to its initial state.
504      * <p>
505      * This {@code Mac} instance is reverted to its initial state and can be
506      * used to start the next MAC computation with the same parameters or
507      * initialized with different parameters.
508      */
reset()509     public final void reset() {
510         getSpi().engineReset();
511     }
512 
513     /**
514      * Clones this {@code Mac} instance and the underlying implementation.
515      *
516      * @return the cloned instance.
517      * @throws CloneNotSupportedException
518      *             if the underlying implementation does not support cloning.
519      */
520     @Override
clone()521     public final Object clone() throws CloneNotSupportedException {
522         MacSpi newSpiImpl = null;
523         final MacSpi spi = getSpi();
524         if (spi != null) {
525             newSpiImpl = (MacSpi) spi.clone();
526         }
527         Mac mac = new Mac(newSpiImpl, this.provider, this.algorithm);
528         mac.isInitMac = this.isInitMac;
529         return mac;
530     }
531 }
532