/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package javax.crypto; import java.nio.ByteBuffer; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Provider; import java.security.ProviderException; import java.security.Security; import java.security.spec.AlgorithmParameterSpec; import java.util.ArrayList; import org.apache.harmony.security.fortress.Engine; /** * This class provides the public API for <i>Message Authentication Code</i> * (MAC) algorithms. */ public class Mac implements Cloneable { // The service name. private static final String SERVICE = "Mac"; //Used to access common engine functionality private static final Engine ENGINE = new Engine(SERVICE); // Store used provider private Provider provider; // Provider that was requested during creation. private final Provider specifiedProvider; // Store used spi implementation private MacSpi spiImpl; // Store used algorithm name private final String algorithm; /** * Lock held while the SPI is initializing. */ private final Object initLock = new Object(); // Store Mac state (initialized or not initialized) private boolean isInitMac; /** * Creates a new {@code Mac} instance. * * @param macSpi * the implementation delegate. * @param provider * the implementation provider. * @param algorithm * the name of the MAC algorithm. */ protected Mac(MacSpi macSpi, Provider provider, String algorithm) { this.specifiedProvider = provider; this.algorithm = algorithm; this.spiImpl = macSpi; this.isInitMac = false; } /** * Returns the name of the MAC algorithm. * * @return the name of the MAC algorithm. */ public final String getAlgorithm() { return algorithm; } /** * Returns the provider of this {@code Mac} instance. * * @return the provider of this {@code Mac} instance. */ public final Provider getProvider() { getSpi(); return provider; } /** * Creates a new {@code Mac} instance that provides the specified MAC * algorithm. * * @param algorithm * the name of the requested MAC algorithm. * @return the new {@code Mac} instance. * @throws NoSuchAlgorithmException * if the specified algorithm is not available by any provider. * @throws NullPointerException * if {@code algorithm} is {@code null} (instead of * NoSuchAlgorithmException as in 1.4 release). */ public static final Mac getInstance(String algorithm) throws NoSuchAlgorithmException { return getMac(algorithm, null); } /** * Creates a new {@code Mac} instance that provides the specified MAC * algorithm from the specified provider. * * @param algorithm * the name of the requested MAC algorithm. * @param provider * the name of the provider that is providing the algorithm. * @return the new {@code Mac} instance. * @throws NoSuchAlgorithmException * if the specified algorithm is not provided by the specified * provider. * @throws NoSuchProviderException * if the specified provider is not available. * @throws IllegalArgumentException * if the specified provider name is {@code null} or empty. * @throws NullPointerException * if {@code algorithm} is {@code null} (instead of * NoSuchAlgorithmException as in 1.4 release). */ public static final Mac getInstance(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException { if (provider == null || provider.isEmpty()) { throw new IllegalArgumentException("Provider is null or empty"); } Provider impProvider = Security.getProvider(provider); if (impProvider == null) { throw new NoSuchProviderException(provider); } return getMac(algorithm, impProvider); } /** * Creates a new {@code Mac} instance that provides the specified MAC * algorithm from the specified provider. The {@code provider} supplied * does not have to be registered. * * @param algorithm * the name of the requested MAC algorithm. * @param provider * the provider that is providing the algorithm. * @return the new {@code Mac} instance. * @throws NoSuchAlgorithmException * if the specified algorithm is not provided by the specified * provider. * @throws IllegalArgumentException * if {@code provider} is {@code null}. * @throws NullPointerException * if {@code algorithm} is {@code null} (instead of * NoSuchAlgorithmException as in 1.4 release). */ public static final Mac getInstance(String algorithm, Provider provider) throws NoSuchAlgorithmException { if (provider == null) { throw new IllegalArgumentException("provider == null"); } return getMac(algorithm, provider); } private static Mac getMac(String algorithm, Provider provider) throws NoSuchAlgorithmException { if (algorithm == null) { throw new NullPointerException("algorithm == null"); } if (tryAlgorithm(null, provider, algorithm) == null) { if (provider == null) { throw new NoSuchAlgorithmException("No provider found for " + algorithm); } else { throw new NoSuchAlgorithmException("Provider " + provider.getName() + " does not provide " + algorithm); } } return new Mac(null, provider, algorithm); } private static Engine.SpiAndProvider tryAlgorithm(Key key, Provider provider, String algorithm) { if (provider != null) { Provider.Service service = provider.getService(SERVICE, algorithm); if (service == null) { return null; } return tryAlgorithmWithProvider(key, service); } ArrayList<Provider.Service> services = ENGINE.getServices(algorithm); if (services == null) { return null; } for (Provider.Service service : services) { Engine.SpiAndProvider sap = tryAlgorithmWithProvider(key, service); if (sap != null) { return sap; } } return null; } private static Engine.SpiAndProvider tryAlgorithmWithProvider(Key key, Provider.Service service) { try { if (key != null && !service.supportsParameter(key)) { return null; } Engine.SpiAndProvider sap = ENGINE.getInstance(service, null); if (sap.spi == null || sap.provider == null) { return null; } if (!(sap.spi instanceof MacSpi)) { return null; } return sap; } catch (NoSuchAlgorithmException ignored) { } return null; } /** * Makes sure a MacSpi that matches this type is selected. */ private MacSpi getSpi(Key key) { synchronized (initLock) { if (spiImpl != null && provider != null && key == null) { return spiImpl; } if (algorithm == null) { return null; } final Engine.SpiAndProvider sap = tryAlgorithm(key, specifiedProvider, algorithm); if (sap == null) { throw new ProviderException("No provider for " + getAlgorithm()); } /* * Set our Spi if we've never been initialized or if we have the Spi * specified and have a null provider. */ if (spiImpl == null || provider != null) { spiImpl = (MacSpi) sap.spi; } provider = sap.provider; return spiImpl; } } /** * Convenience call when the Key is not available. */ private MacSpi getSpi() { return getSpi(null); } /** * Returns the length of this MAC (in bytes). * * @return the length of this MAC (in bytes). */ public final int getMacLength() { return getSpi().engineGetMacLength(); } /** * Initializes this {@code Mac} instance with the specified key and * algorithm parameters. * * @param key * the key to initialize this algorithm. * @param params * the parameters for this algorithm. * @throws InvalidKeyException * if the specified key cannot be used to initialize this * algorithm, or it is null. * @throws InvalidAlgorithmParameterException * if the specified parameters cannot be used to initialize this * algorithm. */ public final void init(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, InvalidAlgorithmParameterException { if (key == null) { throw new InvalidKeyException("key == null"); } getSpi(key).engineInit(key, params); isInitMac = true; } /** * Initializes this {@code Mac} instance with the specified key. * * @param key * the key to initialize this algorithm. * @throws InvalidKeyException * if initialization fails because the provided key is {@code * null}. * @throws RuntimeException * if the specified key cannot be used to initialize this * algorithm. */ public final void init(Key key) throws InvalidKeyException { if (key == null) { throw new InvalidKeyException("key == null"); } try { getSpi(key).engineInit(key, null); isInitMac = true; } catch (InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } } /** * Updates this {@code Mac} instance with the specified byte. * * @param input * the byte * @throws IllegalStateException * if this MAC is not initialized. */ public final void update(byte input) throws IllegalStateException { if (!isInitMac) { throw new IllegalStateException(); } getSpi().engineUpdate(input); } /** * Updates this {@code Mac} instance with the data from the specified buffer * {@code input} from the specified {@code offset} and length {@code len}. * * @param input * the buffer. * @param offset * the offset in the buffer. * @param len * the length of the data in the buffer. * @throws IllegalStateException * if this MAC is not initialized. * @throws IllegalArgumentException * if {@code offset} and {@code len} do not specified a valid * chunk in {@code input} buffer. */ public final void update(byte[] input, int offset, int len) throws IllegalStateException { if (!isInitMac) { throw new IllegalStateException(); } if (input == null) { return; } if ((offset < 0) || (len < 0) || ((offset + len) > input.length)) { throw new IllegalArgumentException("Incorrect arguments." + " input.length=" + input.length + " offset=" + offset + ", len=" + len); } getSpi().engineUpdate(input, offset, len); } /** * Copies the buffer provided as input for further processing. * * @param input * the buffer. * @throws IllegalStateException * if this MAC is not initialized. */ public final void update(byte[] input) throws IllegalStateException { if (!isInitMac) { throw new IllegalStateException(); } if (input != null) { getSpi().engineUpdate(input, 0, input.length); } } /** * Updates this {@code Mac} instance with the data from the specified * buffer, starting at {@link ByteBuffer#position()}, including the next * {@link ByteBuffer#remaining()} bytes. * * @param input * the buffer. * @throws IllegalStateException * if this MAC is not initialized. */ public final void update(ByteBuffer input) { if (!isInitMac) { throw new IllegalStateException(); } if (input != null) { getSpi().engineUpdate(input); } else { throw new IllegalArgumentException("input == null"); } } /** * Computes the digest of this MAC based on the data previously specified in * {@link #update} calls. * <p> * This {@code Mac} instance is reverted to its initial state and can be * used to start the next MAC computation with the same parameters or * initialized with different parameters. * * @return the generated digest. * @throws IllegalStateException * if this MAC is not initialized. */ public final byte[] doFinal() throws IllegalStateException { if (!isInitMac) { throw new IllegalStateException(); } return getSpi().engineDoFinal(); } /** * Computes the digest of this MAC based on the data previously specified in * {@link #update} calls and stores the digest in the specified {@code * output} buffer at offset {@code outOffset}. * <p> * This {@code Mac} instance is reverted to its initial state and can be * used to start the next MAC computation with the same parameters or * initialized with different parameters. * * @param output * the output buffer * @param outOffset * the offset in the output buffer * @throws ShortBufferException * if the specified output buffer is either too small for the * digest to be stored, the specified output buffer is {@code * null}, or the specified offset is negative or past the length * of the output buffer. * @throws IllegalStateException * if this MAC is not initialized. */ public final void doFinal(byte[] output, int outOffset) throws ShortBufferException, IllegalStateException { if (!isInitMac) { throw new IllegalStateException(); } if (output == null) { throw new ShortBufferException("output == null"); } if ((outOffset < 0) || (outOffset >= output.length)) { throw new ShortBufferException("Incorrect outOffset: " + outOffset); } MacSpi spi = getSpi(); int t = spi.engineGetMacLength(); if (t > (output.length - outOffset)) { throw new ShortBufferException("Output buffer is short. Needed " + t + " bytes."); } byte[] result = spi.engineDoFinal(); System.arraycopy(result, 0, output, outOffset, result.length); } /** * Computes the digest of this MAC based on the data previously specified on * {@link #update} calls and on the final bytes specified by {@code input} * (or based on those bytes only). * <p> * This {@code Mac} instance is reverted to its initial state and can be * used to start the next MAC computation with the same parameters or * initialized with different parameters. * * @param input * the final bytes. * @return the generated digest. * @throws IllegalStateException * if this MAC is not initialized. */ public final byte[] doFinal(byte[] input) throws IllegalStateException { if (!isInitMac) { throw new IllegalStateException(); } MacSpi spi = getSpi(); if (input != null) { spi.engineUpdate(input, 0, input.length); } return spi.engineDoFinal(); } /** * Resets this {@code Mac} instance to its initial state. * <p> * This {@code Mac} instance is reverted to its initial state and can be * used to start the next MAC computation with the same parameters or * initialized with different parameters. */ public final void reset() { getSpi().engineReset(); } /** * Clones this {@code Mac} instance and the underlying implementation. * * @return the cloned instance. * @throws CloneNotSupportedException * if the underlying implementation does not support cloning. */ @Override public final Object clone() throws CloneNotSupportedException { MacSpi newSpiImpl = null; final MacSpi spi = getSpi(); if (spi != null) { newSpiImpl = (MacSpi) spi.clone(); } Mac mac = new Mac(newSpiImpl, this.provider, this.algorithm); mac.isInitMac = this.isInitMac; return mac; } }