| /* | |
| * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package com.sun.crypto.provider; | |
| import java.util.Arrays; | |
| import java.security.*; | |
| import java.security.spec.*; | |
| import javax.crypto.*; | |
| import javax.crypto.spec.*; | |
| import static com.sun.crypto.provider.KWUtil.*; | |
| /** | |
| * This class is the impl class for AES KeyWrap algorithms as defined in | |
| * <a href=https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38F.pdf> | |
| * "Recommendation for Block Cipher Modes of Operation: Methods for Key Wrapping" | |
| * | |
| * @spec https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38F.pdf | |
| * Recommendation for Block Cipher Modes of Operation: Methods for Key Wrapping | |
| */ | |
| abstract class KeyWrapCipher extends CipherSpi { | |
| // for AESWrap + AES/KW/NoPadding | |
| public static final class AES_KW_NoPadding extends KeyWrapCipher { | |
| public AES_KW_NoPadding() { | |
| super(new AESKeyWrap(), null, -1); | |
| } | |
| } | |
| // for AESWrap_128 + AES_128/KW/NoPadding | |
| public static final class AES128_KW_NoPadding extends KeyWrapCipher { | |
| public AES128_KW_NoPadding() { | |
| super(new AESKeyWrap(), null, 16); | |
| } | |
| } | |
| // for AESWrap_192 + AES_192/KW/NoPadding | |
| public static final class AES192_KW_NoPadding extends KeyWrapCipher { | |
| public AES192_KW_NoPadding() { | |
| super(new AESKeyWrap(), null, 24); | |
| } | |
| } | |
| // for AESWrap_256 + AES_256/KW/NoPadding | |
| public static final class AES256_KW_NoPadding extends KeyWrapCipher { | |
| public AES256_KW_NoPadding() { | |
| super(new AESKeyWrap(), null, 32); | |
| } | |
| } | |
| // for AES/KW/NoPadding | |
| public static final class AES_KW_PKCS5Padding extends KeyWrapCipher { | |
| public AES_KW_PKCS5Padding() { | |
| super(new AESKeyWrap(), new PKCS5Padding(8), -1); | |
| } | |
| } | |
| // for AES_128/KW/NoPadding | |
| public static final class AES128_KW_PKCS5Padding extends KeyWrapCipher { | |
| public AES128_KW_PKCS5Padding() { | |
| super(new AESKeyWrap(), new PKCS5Padding(8), 16); | |
| } | |
| } | |
| // for AES_192/KW/NoPadding | |
| public static final class AES192_KW_PKCS5Padding extends KeyWrapCipher { | |
| public AES192_KW_PKCS5Padding() { | |
| super(new AESKeyWrap(), new PKCS5Padding(8), 24); | |
| } | |
| } | |
| // for AES_256/KW/NoPadding | |
| public static final class AES256_KW_PKCS5Padding extends KeyWrapCipher { | |
| public AES256_KW_PKCS5Padding() { | |
| super(new AESKeyWrap(), new PKCS5Padding(8), 32); | |
| } | |
| } | |
| // for AES/KWP/NoPadding | |
| public static final class AES_KWP_NoPadding extends KeyWrapCipher { | |
| public AES_KWP_NoPadding() { | |
| super(new AESKeyWrapPadded(), null, -1); | |
| } | |
| } | |
| // for AES_128/KWP/NoPadding | |
| public static final class AES128_KWP_NoPadding extends KeyWrapCipher { | |
| public AES128_KWP_NoPadding() { | |
| super(new AESKeyWrapPadded(), null, 16); | |
| } | |
| } | |
| // for AES_192/KWP/NoPadding | |
| public static final class AES192_KWP_NoPadding extends KeyWrapCipher { | |
| public AES192_KWP_NoPadding() { | |
| super(new AESKeyWrapPadded(), null, 24); | |
| } | |
| } | |
| // for AES_256/KWP/NoPadding | |
| public static final class AES256_KWP_NoPadding extends KeyWrapCipher { | |
| public AES256_KWP_NoPadding() { | |
| super(new AESKeyWrapPadded(), null, 32); | |
| } | |
| } | |
| // validate the key algorithm/encoding and then returns the key bytes | |
| // which callers should erase after use | |
| private static byte[] checkKey(Key key, int fixedKeySize) | |
| throws InvalidKeyException { | |
| byte[] keyBytes = key.getEncoded(); | |
| if (keyBytes == null) { | |
| throw new InvalidKeyException("Null key"); | |
| } | |
| int keyLen = keyBytes.length; | |
| if (!key.getAlgorithm().equalsIgnoreCase("AES") || | |
| !AESCrypt.isKeySizeValid(keyLen) || | |
| (fixedKeySize != -1 && fixedKeySize != keyLen)) { | |
| throw new InvalidKeyException("Invalid key length: " + | |
| keyLen + " bytes"); | |
| } | |
| return keyBytes; | |
| } | |
| // store the specified bytes, e.g. in[inOfs...(inOfs+inLen-1)] into | |
| // 'dataBuf' starting at 'dataIdx'. | |
| // NOTE: if 'in' is null, this method will ensure that 'dataBuf' has enough | |
| // capacity for 'inLen' bytes but will NOT copy bytes from 'in'. | |
| private void store(byte[] in, int inOfs, int inLen) { | |
| // In NIST SP 800-38F, KWP input size is limited to be no longer | |
| // than 2^32 bytes. Otherwise, the length cannot be encoded in 32 bits | |
| // However, given the current spec requirement that recovered text | |
| // can only be returned after successful tag verification, we are | |
| // bound by limiting the data size to the size limit of java byte array, | |
| // e.g. Integer.MAX_VALUE, since all data are returned by doFinal(). | |
| int remain = Integer.MAX_VALUE - dataIdx; | |
| if (inLen > remain) { | |
| throw new ProviderException("SunJCE provider can only take " + | |
| remain + " more bytes"); | |
| } | |
| // resize 'dataBuf' to the smallest (n * BLKSIZE) + SEMI_BLKSIZE) | |
| if (dataBuf == null || dataBuf.length - dataIdx < inLen) { | |
| int newSize = Math.addExact(dataIdx, inLen); | |
| int lastBlk = (dataIdx + inLen - SEMI_BLKSIZE) % BLKSIZE; | |
| if (lastBlk != 0 || padding != null) { | |
| newSize = Math.addExact(newSize, BLKSIZE - lastBlk); | |
| } | |
| byte[] temp = new byte[newSize]; | |
| if (dataBuf != null && dataIdx > 0) { | |
| System.arraycopy(dataBuf, 0, temp, 0, dataIdx); | |
| } | |
| dataBuf = temp; | |
| } | |
| if (in != null) { | |
| System.arraycopy(in, inOfs, dataBuf, dataIdx, inLen); | |
| dataIdx += inLen; | |
| } | |
| } | |
| // internal cipher object which does the real work. | |
| // AESKeyWrap for KW, AESKeyWrapPadded for KWP | |
| private final FeedbackCipher cipher; | |
| // internal padding object; null if NoPadding | |
| private final Padding padding; | |
| // encrypt/wrap or decrypt/unwrap? | |
| private int opmode = -1; // must be set by init(..) | |
| /* | |
| * needed to support oids which associates a fixed key size | |
| * to the cipher object. | |
| */ | |
| private final int fixedKeySize; // in bytes, -1 if no restriction | |
| // internal data buffer for encrypt, decrypt calls | |
| // must use store() to store data into 'dataBuf' as it will resize if needed | |
| private byte[] dataBuf; | |
| private int dataIdx; | |
| /** | |
| * Creates an instance of KeyWrap cipher using the specified | |
| * symmetric cipher whose block size must be 128-bit, and | |
| * the supported mode and padding scheme. | |
| */ | |
| public KeyWrapCipher(FeedbackCipher cipher, Padding padding, int keySize) { | |
| this.cipher = cipher; | |
| this.padding = padding; | |
| this.fixedKeySize = keySize; | |
| this.dataBuf = null; | |
| this.dataIdx = 0; | |
| } | |
| /** | |
| * Sets the mode of this cipher. Must match the mode specified in | |
| * the constructor. | |
| * | |
| * @param mode the cipher mode | |
| * | |
| * @exception NoSuchAlgorithmException if the requested cipher mode | |
| * does not match the supported mode | |
| */ | |
| @Override | |
| protected void engineSetMode(String mode) throws NoSuchAlgorithmException { | |
| if (mode != null && !cipher.getFeedback().equalsIgnoreCase(mode)) { | |
| throw new NoSuchAlgorithmException(mode + " cannot be used"); | |
| } | |
| } | |
| /** | |
| * Sets the padding mechanism of this cipher. The specified padding | |
| * scheme should match what this cipher is configured with. | |
| * | |
| * @param padding the padding mechanism | |
| * | |
| * @exception NoSuchPaddingException if the requested padding mechanism | |
| * does not match the supported padding scheme | |
| */ | |
| @Override | |
| protected void engineSetPadding(String padding) | |
| throws NoSuchPaddingException { | |
| if ((this.padding == null && !"NoPadding".equalsIgnoreCase(padding)) || | |
| this.padding instanceof PKCS5Padding && | |
| !"PKCS5Padding".equalsIgnoreCase(padding)) { | |
| throw new NoSuchPaddingException("Unsupported padding " + padding); | |
| } | |
| } | |
| /** | |
| * @return the block size (in bytes) | |
| */ | |
| @Override | |
| protected int engineGetBlockSize() { | |
| return 8; | |
| } | |
| /** | |
| * Returns the length in bytes that an output buffer would need to be | |
| * given the input length <code>inLen</code> (in bytes). | |
| * | |
| * <p>The actual output length of the next <code>update</code> or | |
| * <code>doFinal</code> call may be smaller than the length returned | |
| * by this method. | |
| * | |
| * @param inLen the input length (in bytes) | |
| * | |
| * @return the required output buffer size (in bytes) | |
| */ | |
| protected int engineGetOutputSize(int inLen) { | |
| int result; | |
| if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE) { | |
| result = (dataIdx > 0? | |
| Math.addExact(inLen, dataIdx - SEMI_BLKSIZE) : inLen); | |
| // calculate padding length based on plaintext length | |
| int padLen = 0; | |
| if (padding != null) { | |
| padLen = padding.padLength(result); | |
| } else if (cipher instanceof AESKeyWrapPadded) { | |
| int n = result % SEMI_BLKSIZE; | |
| if (n != 0) { | |
| padLen = SEMI_BLKSIZE - n; | |
| } | |
| } | |
| // then add the first semi-block and padLen to result | |
| result = Math.addExact(result, SEMI_BLKSIZE + padLen); | |
| } else { | |
| result = inLen - SEMI_BLKSIZE; | |
| if (dataIdx > 0) { | |
| result = Math.addExact(result, dataIdx); | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Returns the initialization vector (IV) in a new buffer. | |
| * | |
| * @return the user-specified iv, or null if the underlying algorithm does | |
| * not use an IV, or if the IV has not yet been set. | |
| */ | |
| @Override | |
| protected byte[] engineGetIV() { | |
| byte[] iv = cipher.getIV(); | |
| return (iv == null? null : iv.clone()); | |
| } | |
| // actual impl for various engineInit(...) methods | |
| private void implInit(int opmode, Key key, byte[] iv, SecureRandom random) | |
| throws InvalidKeyException, InvalidAlgorithmParameterException { | |
| byte[] keyBytes = checkKey(key, fixedKeySize); | |
| this.opmode = opmode; | |
| boolean decrypting = (opmode == Cipher.DECRYPT_MODE || | |
| opmode == Cipher.UNWRAP_MODE); | |
| try { | |
| cipher.init(decrypting, key.getAlgorithm(), keyBytes, iv); | |
| dataBuf = null; | |
| dataIdx = 0; | |
| } finally { | |
| Arrays.fill(keyBytes, (byte) 0); | |
| } | |
| } | |
| /** | |
| * Initializes this cipher with a key and a source of randomness. | |
| * | |
| * @param opmode the operation mode of this cipher. | |
| * @param key the secret key. | |
| * @param random the source of randomness. | |
| * | |
| * @exception InvalidKeyException if the given key is inappropriate for | |
| * initializing this cipher. | |
| */ | |
| @Override | |
| protected void engineInit(int opmode, Key key, SecureRandom random) | |
| throws InvalidKeyException { | |
| try { | |
| implInit(opmode, key, null, random); | |
| } catch (InvalidAlgorithmParameterException iae) { | |
| // should never happen | |
| throw new AssertionError(iae); | |
| } | |
| } | |
| /** | |
| * Initializes this cipher with a key, a set of algorithm parameters, | |
| * and a source of randomness. | |
| * | |
| * @param opmode the operation mode of this cipher. | |
| * @param key the secret key. | |
| * @param params the algorithm parameters; if not null, must be of type | |
| * IvParameterSpec | |
| * @param random the source of randomness. | |
| * | |
| * @exception InvalidKeyException if the given key is inappropriate for | |
| * initializing this cipher | |
| * @exception InvalidAlgorithmParameterException if the given algorithm | |
| * parameters is invalid. | |
| */ | |
| @Override | |
| protected void engineInit(int opmode, Key key, | |
| AlgorithmParameterSpec params, SecureRandom random) | |
| throws InvalidKeyException, InvalidAlgorithmParameterException { | |
| if (params != null && !(params instanceof IvParameterSpec)) { | |
| throw new InvalidAlgorithmParameterException( | |
| "Only IvParameterSpec is accepted"); | |
| } | |
| byte[] iv = (params == null? null : ((IvParameterSpec)params).getIV()); | |
| implInit(opmode, key, iv, random); | |
| } | |
| /** | |
| * Initializes this cipher with a key, a set of algorithm parameters, | |
| * and a source of randomness. | |
| * | |
| * @param opmode the operation mode of this cipher. | |
| * @param key the secret key. | |
| * @param params the algorithm parameters; if not null, must be able to | |
| * be converted to IvParameterSpec. | |
| * @param random the source of randomness. | |
| * | |
| * @exception InvalidKeyException if the given key is inappropriate. | |
| * @exception InvalidAlgorithmParameterException if the given algorithm | |
| * parameters is invalid. | |
| */ | |
| @Override | |
| protected void engineInit(int opmode, Key key, AlgorithmParameters params, | |
| SecureRandom random) throws InvalidKeyException, | |
| InvalidAlgorithmParameterException { | |
| byte[] iv = null; | |
| if (params != null) { | |
| try { | |
| IvParameterSpec spec = | |
| params.getParameterSpec(IvParameterSpec.class); | |
| iv = spec.getIV(); | |
| } catch (InvalidParameterSpecException ispe) { | |
| throw new InvalidAlgorithmParameterException( | |
| "Only IvParameterSpec is accepted"); | |
| } | |
| } | |
| try { | |
| implInit(opmode, key, iv, random); | |
| } catch (IllegalArgumentException iae) { | |
| throw new InvalidAlgorithmParameterException(iae.getMessage()); | |
| } | |
| } | |
| /** | |
| * See CipherSpi.engineUpdate(...) - buffers data internally as | |
| * only single part operation is supported. | |
| * | |
| * @param in the input buffer. | |
| * @param inOffset the offset in <code>in</code> where the input | |
| * starts. | |
| * @param inLen the input length. | |
| * | |
| * @return null. | |
| */ | |
| @Override | |
| protected byte[] engineUpdate(byte[] in, int inOffset, int inLen) { | |
| if (opmode != Cipher.ENCRYPT_MODE && opmode != Cipher.DECRYPT_MODE) { | |
| throw new IllegalStateException | |
| ("Cipher not initialized for update"); | |
| } | |
| implUpdate(in, inOffset, inLen); | |
| return null; | |
| } | |
| /** | |
| * See CipherSpi.engineUpdate(...) - buffers data internally as | |
| * only single part operation is supported. | |
| * | |
| * @param in the input buffer. | |
| * @param inOffset the offset in <code>in</code> where the input | |
| * starts. | |
| * @param inLen the input length. | |
| * @param out the buffer for the result. | |
| * @param outOffset the offset in <code>out</code> where the result | |
| * is stored. | |
| * | |
| * @return n/a. | |
| * | |
| * @exception IllegalStateException upon invocation of this method. | |
| */ | |
| @Override | |
| protected int engineUpdate(byte[] in, int inOffset, int inLen, | |
| byte[] out, int outOffset) throws ShortBufferException { | |
| if (opmode != Cipher.ENCRYPT_MODE && opmode != Cipher.DECRYPT_MODE) { | |
| throw new IllegalStateException | |
| ("Cipher not initialized for update"); | |
| } | |
| implUpdate(in, inOffset, inLen); | |
| return 0; | |
| } | |
| // actual impl for various engineUpdate(...) methods | |
| private void implUpdate(byte[] in, int inOfs, int inLen) { | |
| if (inLen <= 0) return; | |
| if (opmode == Cipher.ENCRYPT_MODE && dataIdx == 0) { | |
| // the first semi-block is for iv, store data after it | |
| dataIdx = SEMI_BLKSIZE; | |
| } | |
| store(in, inOfs, inLen); | |
| } | |
| /** | |
| * See CipherSpi.engineDoFinal(...) | |
| * | |
| * @param input the input buffer | |
| * @param inputOffset the offset in <code>in</code> where the input | |
| * starts | |
| * @param inputLen the input length. | |
| * | |
| * @return n/a. | |
| * | |
| * @exception IllegalStateException upon invocation of this method. | |
| */ | |
| @Override | |
| protected byte[] engineDoFinal(byte[] in, int inOfs, int inLen) | |
| throws IllegalBlockSizeException, BadPaddingException { | |
| int estOutLen = engineGetOutputSize(inLen); | |
| byte[] out = new byte[estOutLen]; | |
| try { | |
| int outLen = engineDoFinal(in, inOfs, inLen, out, 0); | |
| if (outLen < estOutLen) { | |
| try { | |
| return Arrays.copyOf(out, outLen); | |
| } finally { | |
| Arrays.fill(out, (byte)0); | |
| } | |
| } else { | |
| return out; | |
| } | |
| } catch (ShortBufferException sbe) { | |
| // should never happen | |
| throw new AssertionError(sbe); | |
| } | |
| } | |
| /** | |
| * See CipherSpi.doFinal(...) | |
| * | |
| * @param in the input buffer. | |
| * @param inOffset the offset in <code>in</code> where the input | |
| * starts. | |
| * @param inLen the input length. | |
| * @param out the buffer for the result. | |
| * @param outOffset the ofset in <code>out</code> where the result | |
| * is stored. | |
| * | |
| * @return n/a. | |
| * | |
| * @exception IllegalStateException upon invocation of this method. | |
| */ | |
| protected int engineDoFinal(byte[] in, int inOfs, int inLen, | |
| byte[] out, int outOfs) throws IllegalBlockSizeException, | |
| ShortBufferException, BadPaddingException { | |
| if (opmode != Cipher.ENCRYPT_MODE && opmode != Cipher.DECRYPT_MODE) { | |
| throw new IllegalStateException | |
| ("Cipher not initialized for doFinal"); | |
| } | |
| int estOutLen = engineGetOutputSize(inLen); | |
| if (out.length - outOfs < estOutLen) { | |
| throw new ShortBufferException("Need at least " + estOutLen); | |
| } | |
| try { | |
| // cannot write out the result for decryption due to verification | |
| // requirement | |
| if (outOfs == 0 && opmode == Cipher.ENCRYPT_MODE) { | |
| return implDoFinal(in, inOfs, inLen, out); | |
| } else { | |
| // use 'dataBuf' as output buffer and then copy into 'out' | |
| // make sure 'dataBuf' is large enough | |
| store(null, 0, inLen); | |
| int outLen = implDoFinal(in, inOfs, inLen, dataBuf); | |
| if (outLen > estOutLen) { | |
| throw new AssertionError | |
| ("Actual output length exceeds estimated length"); | |
| } | |
| System.arraycopy(dataBuf, 0, out, outOfs, outLen); | |
| return outLen; | |
| } | |
| } finally { | |
| if (dataBuf != null) { | |
| Arrays.fill(dataBuf, (byte)0); | |
| } | |
| dataBuf = null; | |
| dataIdx = 0; | |
| } | |
| } | |
| // actual impl for various engineDoFinal(...) methods. | |
| // prepare 'out' buffer with the buffered bytes in 'dataBuf', | |
| // and the to-be-processed bytes in 'in', then perform single-part | |
| // encryption/decryption over 'out' buffer | |
| private int implDoFinal(byte[] in, int inOfs, int inLen, byte[] out) | |
| throws IllegalBlockSizeException, BadPaddingException, | |
| ShortBufferException { | |
| int len = (out == dataBuf? dataIdx : 0); | |
| // copy over the buffered bytes if out != dataBuf | |
| if (out != dataBuf && dataIdx > 0) { | |
| System.arraycopy(dataBuf, 0, out, 0, dataIdx); | |
| len = dataIdx; | |
| } | |
| if (opmode == Cipher.ENCRYPT_MODE && len == 0) { | |
| len = SEMI_BLKSIZE; // reserve space for the ICV if encryption | |
| } | |
| if (inLen > 0) { | |
| System.arraycopy(in, inOfs, out, len, inLen); | |
| len += inLen; | |
| } | |
| try { | |
| return (opmode == Cipher.ENCRYPT_MODE ? | |
| helperEncrypt(out, len) : helperDecrypt(out, len)); | |
| } finally { | |
| if (dataBuf != null && dataBuf != out) { | |
| Arrays.fill(dataBuf, (byte)0); | |
| } | |
| } | |
| } | |
| // helper routine for in-place encryption. | |
| // 'inBuf' = semi-block | plain text | extra bytes if padding is used | |
| // 'inLen' = semi-block length + plain text length | |
| private int helperEncrypt(byte[] inBuf, int inLen) | |
| throws IllegalBlockSizeException, ShortBufferException { | |
| // pad data if padding is used | |
| if (padding != null) { | |
| int paddingLen = padding.padLength(inLen - SEMI_BLKSIZE); | |
| if (inLen + paddingLen > inBuf.length) { | |
| throw new AssertionError("encrypt buffer too small"); | |
| } | |
| try { | |
| padding.padWithLen(inBuf, inLen, paddingLen); | |
| inLen += paddingLen; | |
| } catch (ShortBufferException sbe) { | |
| // should never happen | |
| throw new AssertionError(sbe); | |
| } | |
| } | |
| return cipher.encryptFinal(inBuf, 0, inLen, null, 0); | |
| } | |
| // helper routine for in-place decryption. | |
| // 'inBuf' = cipher text | |
| // 'inLen' = cipher text length | |
| private int helperDecrypt(byte[] inBuf, int inLen) | |
| throws IllegalBlockSizeException, BadPaddingException, | |
| ShortBufferException { | |
| int outLen = cipher.decryptFinal(inBuf, 0, inLen, null, 0); | |
| // unpad data if padding is used | |
| if (padding != null) { | |
| int padIdx = padding.unpad(inBuf, 0, outLen); | |
| if (padIdx <= 0) { | |
| throw new BadPaddingException("Bad Padding: " + padIdx); | |
| } | |
| outLen = padIdx; | |
| } | |
| return outLen; | |
| } | |
| /** | |
| * Returns the parameters used with this cipher. | |
| * | |
| * @return AlgorithmParameters object containing IV, or null if this cipher | |
| * does not use any parameters. | |
| */ | |
| @Override | |
| protected AlgorithmParameters engineGetParameters() { | |
| AlgorithmParameters params; | |
| byte[] iv = cipher.getIV(); | |
| if (iv == null) { | |
| iv = (cipher instanceof AESKeyWrap? | |
| AESKeyWrap.ICV1 : AESKeyWrapPadded.ICV2); | |
| } | |
| try { | |
| params = AlgorithmParameters.getInstance("AES"); | |
| params.init(new IvParameterSpec(iv)); | |
| } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) { | |
| // should never happen | |
| throw new AssertionError(); | |
| } | |
| return params; | |
| } | |
| /** | |
| * Returns the key size of the given key object in number of bits. | |
| * | |
| * @param key the key object. | |
| * | |
| * @return the "effective" key size of the given key object. | |
| * | |
| * @exception InvalidKeyException if <code>key</code> is invalid. | |
| */ | |
| protected int engineGetKeySize(Key key) throws InvalidKeyException { | |
| byte[] keyBytes = checkKey(key, fixedKeySize); | |
| // only need length; erase immediately | |
| Arrays.fill(keyBytes, (byte) 0); | |
| return Math.multiplyExact(keyBytes.length, 8); | |
| } | |
| /** | |
| * Wrap a key. | |
| * | |
| * @param key the key to be wrapped. | |
| * | |
| * @return the wrapped key. | |
| * | |
| * @exception IllegalBlockSizeException if this cipher is a block | |
| * cipher, no padding has been requested, and the length of the | |
| * encoding of the key to be wrapped is not a | |
| * multiple of the block size. | |
| * | |
| * @exception InvalidKeyException if it is impossible or unsafe to | |
| * wrap the key with this cipher (e.g., a hardware protected key is | |
| * being passed to a software only cipher). | |
| */ | |
| @Override | |
| protected byte[] engineWrap(Key key) | |
| throws IllegalBlockSizeException, InvalidKeyException { | |
| if (opmode != Cipher.WRAP_MODE) { | |
| throw new IllegalStateException("Cipher not initialized for wrap"); | |
| } | |
| byte[] encoded = key.getEncoded(); | |
| if ((encoded == null) || (encoded.length == 0)) { | |
| throw new InvalidKeyException("Cannot get an encoding of " + | |
| "the key to be wrapped"); | |
| } | |
| // output size is known, allocate output buffer | |
| byte[] out = new byte[engineGetOutputSize(encoded.length)]; | |
| // reserve the first semi-block and do not write data | |
| int len = SEMI_BLKSIZE; | |
| System.arraycopy(encoded, 0, out, len, encoded.length); | |
| len += encoded.length; | |
| // discard key data | |
| Arrays.fill(encoded, (byte) 0); | |
| try { | |
| int outLen = helperEncrypt(out, len); | |
| if (outLen != out.length) { | |
| throw new AssertionError("Wrong output buffer size"); | |
| } | |
| return out; | |
| } catch (ShortBufferException sbe) { | |
| // should never happen | |
| throw new AssertionError(); | |
| } | |
| } | |
| /** | |
| * Unwrap a previously wrapped key. | |
| * | |
| * @param wrappedKey the key to be unwrapped. | |
| * | |
| * @param wrappedKeyAlgorithm the algorithm the wrapped key is for. | |
| * | |
| * @param wrappedKeyType the type of the wrapped key. | |
| * This is one of <code>Cipher.SECRET_KEY</code>, | |
| * <code>Cipher.PRIVATE_KEY</code>, or <code>Cipher.PUBLIC_KEY</code>. | |
| * | |
| * @return the unwrapped key. | |
| * | |
| * @exception NoSuchAlgorithmException if no installed providers | |
| * can create keys of type <code>wrappedKeyType</code> for the | |
| * <code>wrappedKeyAlgorithm</code>. | |
| * | |
| * @exception InvalidKeyException if <code>wrappedKey</code> does not | |
| * represent a wrapped key of type <code>wrappedKeyType</code> for | |
| * the <code>wrappedKeyAlgorithm</code>. | |
| */ | |
| @Override | |
| protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, | |
| int wrappedKeyType) throws InvalidKeyException, | |
| NoSuchAlgorithmException { | |
| if (opmode != Cipher.UNWRAP_MODE) { | |
| throw new IllegalStateException | |
| ("Cipher not initialized for unwrap"); | |
| } | |
| byte[] buf = wrappedKey.clone(); | |
| try { | |
| int outLen = helperDecrypt(buf, buf.length); | |
| return ConstructKeys.constructKey(buf, 0, outLen, | |
| wrappedKeyAlgorithm, wrappedKeyType); | |
| } catch (ShortBufferException sbe) { | |
| // should never happen | |
| throw new AssertionError(); | |
| } catch (IllegalBlockSizeException | BadPaddingException e) { | |
| throw new InvalidKeyException(e); | |
| } finally { | |
| Arrays.fill(buf, (byte) 0); | |
| } | |
| } | |
| } |