Home | History | Annotate | Download | only in crypto
      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      */
     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      */
     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      */
     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      */
    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      */
    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      */
    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 
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    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