001/* Mac.java -- The message authentication code interface.
002   Copyright (C) 2004  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.crypto;
040
041import gnu.java.security.Engine;
042
043import java.lang.reflect.InvocationTargetException;
044import java.nio.ByteBuffer;
045import java.security.InvalidAlgorithmParameterException;
046import java.security.InvalidKeyException;
047import java.security.Key;
048import java.security.NoSuchAlgorithmException;
049import java.security.NoSuchProviderException;
050import java.security.Provider;
051import java.security.Security;
052import java.security.spec.AlgorithmParameterSpec;
053
054/**
055 * This class implements a "message authentication code" (MAC), a method
056 * to ensure the integrity of data transmitted between two parties who
057 * share a common secret key.
058 *
059 * <p>The best way to describe a MAC is as a <i>keyed one-way hash
060 * function</i>, which looks like:
061 *
062 * <blockquote><p><code>D = MAC(K, M)</code></blockquote>
063 *
064 * <p>where <code>K</code> is the key, <code>M</code> is the message,
065 * and <code>D</code> is the resulting digest. One party will usually
066 * send the concatenation <code>M || D</code> to the other party, who
067 * will then verify <code>D</code> by computing <code>D'</code> in a
068 * similar fashion. If <code>D == D'</code>, then the message is assumed
069 * to be authentic.
070 *
071 * @author Casey Marshall (csm@gnu.org)
072 */
073public class Mac implements Cloneable
074{
075
076  // Fields.
077  // ------------------------------------------------------------------------
078
079  private static final String SERVICE = "Mac";
080
081  /** The underlying MAC implementation. */
082  private MacSpi macSpi;
083
084  /** The provider we got our implementation from. */
085  private Provider provider;
086
087  /** The name of the algorithm. */
088  private String algorithm;
089
090  /** Whether or not we've been initialized. */
091  private boolean virgin;
092
093  // Constructor.
094  // ------------------------------------------------------------------------
095
096  /**
097   * Creates a new Mac instance.
098   *
099   * @param macSpi    The underlying MAC implementation.
100   * @param provider  The provider of this implementation.
101   * @param algorithm The name of this MAC algorithm.
102   */
103  protected Mac(MacSpi macSpi, Provider provider, String algorithm)
104  {
105    this.macSpi = macSpi;
106    this.provider = provider;
107    this.algorithm = algorithm;
108    virgin = true;
109  }
110
111  /**
112   * Create an instance of the named algorithm from the first provider with an
113   * appropriate implementation.
114   * 
115   * @param algorithm The name of the algorithm.
116   * @return An appropriate Mac instance, if the specified algorithm is
117   *         implemented by a provider.
118   * @throws NoSuchAlgorithmException If no implementation of the named
119   *           algorithm is installed.
120   * @throws IllegalArgumentException if <code>algorithm</code> is
121   *           <code>null</code> or is an empty string.
122   */
123  public static final Mac getInstance(String algorithm)
124      throws NoSuchAlgorithmException
125  {
126    Provider[] p = Security.getProviders();
127    NoSuchAlgorithmException lastException = null;
128    for (int i = 0; i < p.length; i++)
129      try
130        {
131          return getInstance(algorithm, p[i]);
132        }
133      catch (NoSuchAlgorithmException x)
134        {
135          lastException = x;
136        }
137      if (lastException != null)
138        throw lastException;
139      throw new NoSuchAlgorithmException(algorithm);
140  }
141
142  /**
143   * Create an instance of the named algorithm from the named provider.
144   * 
145   * @param algorithm The name of the algorithm.
146   * @param provider The name of the provider.
147   * @return An appropriate Mac instance, if the specified algorithm is
148   *         implemented by the named provider.
149   * @throws NoSuchAlgorithmException If the named provider has no
150   *           implementation of the algorithm.
151   * @throws NoSuchProviderException If the named provider does not exist.
152   * @throws IllegalArgumentException if either <code>algorithm</code> or
153   *           <code>provider</code> is <code>null</code>, or if
154   *           <code>algorithm</code> is an empty string.
155   */
156  public static final Mac getInstance(String algorithm, String provider)
157      throws NoSuchAlgorithmException, NoSuchProviderException
158  {
159    if (provider == null)
160      throw new IllegalArgumentException("provider MUST NOT be null");
161    Provider p = Security.getProvider(provider);
162    if (p == null)
163      throw new NoSuchProviderException(provider);
164    return getInstance(algorithm, p);
165  }
166
167  /**
168   * Create an instance of the named algorithm from a provider.
169   * 
170   * @param algorithm The name of the algorithm.
171   * @param provider The provider.
172   * @return An appropriate Mac instance, if the specified algorithm is
173   *         implemented by the provider.
174   * @throws NoSuchAlgorithmException If the provider has no implementation of
175   *           the algorithm.
176   * @throws IllegalArgumentException if either <code>algorithm</code> or
177   *           <code>provider</code> is <code>null</code>, or if
178   *           <code>algorithm</code> is an empty string.
179   */
180  public static final Mac getInstance(String algorithm, Provider provider)
181      throws NoSuchAlgorithmException
182  {
183    StringBuilder sb = new StringBuilder("Mac algorithm [")
184        .append(algorithm).append("] from provider[")
185        .append(provider).append("] could not be created");
186    Throwable cause;
187    try
188      {
189        Object spi = Engine.getInstance(SERVICE, algorithm, provider);
190        return new Mac((MacSpi) spi, provider, algorithm);
191      }
192    catch (InvocationTargetException x)
193      {
194        cause = x.getCause();
195        if (cause instanceof NoSuchAlgorithmException)
196          throw (NoSuchAlgorithmException) cause;
197        if (cause == null)
198          cause = x;
199      }
200    catch (ClassCastException x)
201      {
202        cause = x;
203      }
204    NoSuchAlgorithmException x = new NoSuchAlgorithmException(sb.toString());
205    x.initCause(cause);
206    throw x;
207  }
208
209  /**
210   * Finishes the computation of a MAC and returns the digest.
211   *
212   * <p>After this method succeeds, it may be used again as just after a
213   * call to <code>init</code>, and can compute another MAC using the
214   * same key and parameters.
215   *
216   * @return The message authentication code.
217   * @throws java.lang.IllegalStateException If this instnace has not
218   *         been initialized.
219   */
220  public final byte[] doFinal() throws IllegalStateException
221  {
222    if (virgin)
223      {
224        throw new IllegalStateException("not initialized");
225      }
226    byte[] digest = macSpi.engineDoFinal();
227    reset();
228    return digest;
229  }
230
231  /**
232   * Finishes the computation of a MAC with a final byte array (or
233   * computes a MAC over those bytes only) and returns the digest.
234   *
235   * <p>After this method succeeds, it may be used again as just after a
236   * call to <code>init</code>, and can compute another MAC using the
237   * same key and parameters.
238   *
239   * @param input The bytes to add.
240   * @return The message authentication code.
241   * @throws java.lang.IllegalStateException If this instnace has not
242   *         been initialized.
243   */
244  public final byte[] doFinal(byte[] input) throws IllegalStateException
245  {
246    update(input);
247    byte[] digest = macSpi.engineDoFinal();
248    reset();
249    return digest;
250  }
251
252  /**
253   * Finishes the computation of a MAC and places the result into the
254   * given array.
255   *
256   * <p>After this method succeeds, it may be used again as just after a
257   * call to <code>init</code>, and can compute another MAC using the
258   * same key and parameters.
259   *
260   * @param output    The destination for the result.
261   * @param outOffset The index in the output array to start.
262   * @return The message authentication code.
263   * @throws java.lang.IllegalStateException If this instnace has not
264   *         been initialized.
265   * @throws javax.crypto.ShortBufferException If <code>output</code> is
266   *         not large enough to hold the result.
267   */
268  public final void doFinal(byte[] output, int outOffset)
269  throws IllegalStateException, ShortBufferException
270  {
271    if (virgin)
272      {
273        throw new IllegalStateException("not initialized");
274      }
275    if (output.length - outOffset < getMacLength())
276      {
277        throw new ShortBufferException();
278      }
279    byte[] mac = macSpi.engineDoFinal();
280    System.arraycopy(mac, 0, output, outOffset, getMacLength());
281    reset();
282  }
283
284  /**
285   * Returns the name of this MAC algorithm.
286   *
287   * @return The MAC name.
288   */
289  public final String getAlgorithm()
290  {
291    return algorithm;
292  }
293
294  /**
295   * Get the size of the MAC. This is the size of the array returned by
296   * {@link #doFinal()} and {@link #doFinal(byte[])}, and the minimum
297   * number of bytes that must be available in the byte array passed to
298   * {@link #doFinal(byte[],int)}.
299   *
300   * @return The MAC length.
301   */
302  public final int getMacLength()
303  {
304    return macSpi.engineGetMacLength();
305  }
306
307  /**
308   * Get the provider of the underlying implementation.
309   *
310   * @return The provider.
311   */
312  public final Provider getProvider()
313  {
314    return provider;
315  }
316
317  /**
318   * Initialize this MAC with a key and no parameters.
319   *
320   * @param key The key to initialize this instance with.
321   * @throws java.security.InvalidKeyException If the key is
322   *         unacceptable.
323   */
324  public final void init(Key key) throws InvalidKeyException
325  {
326    try
327      {
328        init(key, null);
329      }
330    catch (InvalidAlgorithmParameterException iape)
331      {
332        throw new IllegalArgumentException(algorithm + " needs parameters");
333      }
334  }
335
336  /**
337   * Initialize this MAC with a key and parameters.
338   *
339   * @param key    The key to initialize this instance with.
340   * @param params The algorithm-specific parameters.
341   * @throws java.security.InvalidAlgorithmParameterException If the
342   *         algorithm parameters are unacceptable.
343   * @throws java.security.InvalidKeyException If the key is
344   *         unacceptable.
345   */
346  public final void init(Key key, AlgorithmParameterSpec params)
347    throws InvalidAlgorithmParameterException, InvalidKeyException
348  {
349    macSpi.engineInit(key, params);
350    virgin = false;                      // w00t!
351  }
352
353  /**
354   * Reset this instance. A call to this method returns this instance
355   * back to the state it was in just after it was initialized.
356   */
357  public final void reset()
358  {
359    macSpi.engineReset();
360  }
361
362  /**
363   * Update the computation with a single byte.
364   *
365   * @param input The next byte.
366   * @throws java.lang.IllegalStateException If this instance has not
367   *         been initialized.
368   */
369  public final void update(byte input) throws IllegalStateException
370  {
371    if (virgin)
372      {
373        throw new IllegalStateException("not initialized");
374      }
375    macSpi.engineUpdate(input);
376  }
377
378  /**
379   * Update the computation with a byte array.
380   *
381   * @param input The next bytes.
382   * @throws java.lang.IllegalStateException If this instance has not
383   *         been initialized.
384   */
385  public final void update(byte[] input) throws IllegalStateException
386  {
387    update(input, 0, input.length);
388  }
389
390  /**
391   * Update the computation with a portion of a byte array.
392   *
393   * @param input  The next bytes.
394   * @param offset The index in <code>input</code> to start.
395   * @param length The number of bytes to update.
396   * @throws java.lang.IllegalStateException If this instance has not
397   *         been initialized.
398   */
399  public final void update(byte[] input, int offset, int length)
400    throws IllegalStateException
401  {
402    if (virgin)
403      {
404        throw new IllegalStateException("not initialized");
405      }
406    macSpi.engineUpdate(input, offset, length);
407  }
408
409  /**
410   * Update this MAC with the remaining bytes in the given buffer
411   * @param buffer The input buffer.
412   * @since 1.5
413   */
414  public final void update (final ByteBuffer buffer)
415  {
416    if (virgin)
417      throw new IllegalStateException ("not initialized");
418    macSpi.engineUpdate(buffer);
419  }
420  
421  /**
422   * Clone this instance, if the underlying implementation supports it.
423   *
424   * @return A clone of this instance.
425   * @throws java.lang.CloneNotSupportedException If the underlying
426   *         implementation is not cloneable.
427   */
428  public final Object clone() throws CloneNotSupportedException
429  {
430    Mac result = new Mac((MacSpi) macSpi.clone(), provider, algorithm);
431    result.virgin = virgin;
432    return result;
433  }
434}