| /* | |
| * Copyright (c) 2021, 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.security.InvalidAlgorithmParameterException; | |
| import java.security.InvalidKeyException; | |
| import java.util.Arrays; | |
| import java.util.HexFormat; | |
| import javax.crypto.IllegalBlockSizeException; | |
| import static com.sun.crypto.provider.KWUtil.*; | |
| /** | |
| * This class implement the AES KeyWrap With Padding mode of operation 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"</a> | |
| * and represents AES cipher in KWP mode. | |
| * | |
| * @spec https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38F.pdf | |
| * Recommendation for Block Cipher Modes of Operation: Methods for Key Wrapping | |
| */ | |
| class AESKeyWrapPadded extends FeedbackCipher { | |
| // default integrity check value (icv) if iv is not supplied | |
| static final byte[] ICV2 = { // SEMI_BLKSIZE/2 long | |
| (byte) 0xA6, (byte) 0x59, (byte) 0x59, (byte) 0xA6, | |
| }; | |
| private static final byte[] PAD_BLK = new byte[SEMI_BLKSIZE - 1]; | |
| // set the first semi-block of dest with iv and inLen | |
| private static void setIvAndLen(byte[] dest, byte[] iv, int inLen) { | |
| assert(dest.length >= SEMI_BLKSIZE) : "buffer needs at least 8 bytes"; | |
| System.arraycopy(iv, 0, dest, 0, iv.length); | |
| dest[4] = (byte) ((inLen >>> 24) & 0xFF); | |
| dest[5] = (byte) ((inLen >>> 16) & 0xFF); | |
| dest[6] = (byte) ((inLen >>> 8) & 0xFF); | |
| dest[7] = (byte) (inLen & 0xFF); | |
| } | |
| // validate the recovered internal ivAndLen semi-block against iv and | |
| // return the recovered input length | |
| private static int validateIV(byte[] ivAndLen, byte[] iv) | |
| throws IllegalBlockSizeException { | |
| // check against iv and fail if not match | |
| int match = 0; | |
| for (int i = 0; i < ICV2.length; i++) { | |
| match |= (ivAndLen[i] ^ iv[i]); | |
| } | |
| if (match != 0) { | |
| throw new IllegalBlockSizeException("Integrity check failed"); | |
| } | |
| int outLen = ivAndLen[4]; | |
| for (int k = 5; k < SEMI_BLKSIZE; k++) { | |
| if (outLen != 0) { | |
| outLen <<= 8; | |
| } | |
| outLen |= ivAndLen[k] & 0xFF; | |
| } | |
| return outLen; | |
| } | |
| AESKeyWrapPadded() { | |
| super(new AESCrypt()); | |
| } | |
| /** | |
| * Gets the name of this feedback mode. | |
| * | |
| * @return the string <code>KW</code> | |
| */ | |
| @Override | |
| String getFeedback() { | |
| return "KWP"; | |
| } | |
| /** | |
| * Save the current content of this cipher. | |
| */ | |
| @Override | |
| void save() { | |
| throw new UnsupportedOperationException("save not supported"); | |
| } | |
| /** | |
| * Restores the content of this cipher to the previous saved one. | |
| */ | |
| @Override | |
| void restore() { | |
| throw new UnsupportedOperationException("restore not supported"); | |
| } | |
| /** | |
| * Initializes the cipher in the specified mode with the given key | |
| * and iv. | |
| * | |
| * @param decrypting flag indicating encryption or decryption | |
| * @param algorithm the algorithm name | |
| * @param key the key | |
| * @param iv the iv | |
| * | |
| * @exception InvalidKeyException if the given key is inappropriate for | |
| * initializing this cipher | |
| * @exception InvalidAlgorithmParameterException if the given iv is | |
| * non-null and not the right length | |
| */ | |
| @Override | |
| void init(boolean decrypting, String algorithm, byte[] key, byte[] iv) | |
| throws InvalidKeyException, InvalidAlgorithmParameterException { | |
| if (key == null) { | |
| throw new InvalidKeyException("Invalid null key"); | |
| } | |
| // allow setting an iv but if non-null, must equal to ICV2 | |
| if (iv != null && !Arrays.equals(iv, ICV2)) { | |
| HexFormat hf = HexFormat.of().withUpperCase(); | |
| throw new InvalidAlgorithmParameterException("Invalid IV, got 0x" + | |
| hf.formatHex(iv) + " instead of 0x" + hf.formatHex(ICV2)); | |
| } | |
| embeddedCipher.init(decrypting, algorithm, key); | |
| this.iv = ICV2; | |
| } | |
| /** | |
| * Resets the iv to its original value. | |
| * This is used when doFinal is called in the Cipher class, so that the | |
| * cipher can be reused (with its original iv). | |
| */ | |
| @Override | |
| void reset() { | |
| throw new UnsupportedOperationException("reset not supported"); | |
| } | |
| // no support for multipart encryption | |
| @Override | |
| int encrypt(byte[] pt, int ptOfs, int ptLen, byte[] ct, int ctOfs) { | |
| throw new UnsupportedOperationException("multipart not supported"); | |
| } | |
| // no support for multipart decryption | |
| @Override | |
| int decrypt(byte[] ct, int ctOfs, int ctLen, byte[] pt, int ptOfs) { | |
| throw new UnsupportedOperationException("multipart not supported"); | |
| } | |
| /** | |
| * Performs single-part encryption operation. | |
| * | |
| * <p>The input <code>pt</code>, starting at <code>0</code> | |
| * and ending at <code>ptLen-1</code>, is encrypted. | |
| * The result is stored in place into <code>pt</code>, starting at | |
| * <code>0</code>. | |
| * | |
| * <p>The subclass that implements Cipher should ensure that | |
| * <code>init</code> has been called before this method is called. | |
| * | |
| * @param pt the input buffer with the data to be encrypted | |
| * @param dummy1 the offset in <code>pt</code> which is always 0 | |
| * @param ptLen the length of the input data | |
| * @param dummy2 the output buffer for the encryption which is always pt | |
| * @param dummy3 the offset in the output buffer which is always 0 | |
| * @return the number of bytes placed into <code>pt</code> | |
| */ | |
| @Override | |
| int encryptFinal(byte[] pt, int dummy1, int ptLen, byte[] dummy2, | |
| int dummy3) throws IllegalBlockSizeException { | |
| int actualLen = ptLen - SEMI_BLKSIZE; | |
| if (actualLen < 1) { | |
| throw new IllegalBlockSizeException | |
| ("data should have at least 1 byte"); | |
| } | |
| if (ptLen % SEMI_BLKSIZE != 0) { | |
| int rem = SEMI_BLKSIZE - (ptLen % SEMI_BLKSIZE); | |
| System.arraycopy(PAD_BLK, 0, pt, ptLen, rem); | |
| ptLen += rem; | |
| } | |
| if (ptLen <= BLKSIZE) { | |
| // overwrite the first semi-block with iv and input length | |
| setIvAndLen(pt, iv, actualLen); | |
| embeddedCipher.encryptBlock(pt, 0, pt, 0); | |
| } else { | |
| byte[] ivAndLen = new byte[SEMI_BLKSIZE]; | |
| setIvAndLen(ivAndLen, iv, actualLen); | |
| ptLen = W(ivAndLen, pt, ptLen, embeddedCipher); | |
| } | |
| return ptLen; | |
| } | |
| /** | |
| * Performs single-part decryption operation. | |
| * | |
| * <p>The input <code>ct</code>, starting at <code>0</code> | |
| * and ending at <code>ctLen-1</code>, is decrypted. | |
| * The result is stored in place into <code>ct</code>, starting at | |
| * <code>0</code>. | |
| * | |
| * <p>The subclass that implements Cipher should ensure that | |
| * <code>init</code> has been called before this method is called. | |
| * | |
| * @param ct the input buffer with the data to be decrypted | |
| * @param dummy1 the offset in <code>ct</code> which is always 0 | |
| * @param ctLen the length of the input data | |
| * @param dummy2 the output buffer for the decryption which is always ct | |
| * @param dummy3 the offset in the output buffer which is always 0 | |
| * @return the number of bytes placed into <code>ct</code> | |
| */ | |
| @Override | |
| int decryptFinal(byte[] ct, int dummy1, int ctLen, byte[] dummy2, | |
| int dummy3) throws IllegalBlockSizeException { | |
| if (ctLen < BLKSIZE || ctLen % SEMI_BLKSIZE != 0) { | |
| throw new IllegalBlockSizeException | |
| ("data should be at least 16 bytes and multiples of 8"); | |
| } | |
| byte[] ivAndLen = new byte[SEMI_BLKSIZE]; | |
| if (ctLen == BLKSIZE) { | |
| embeddedCipher.decryptBlock(ct, 0, ct, 0); | |
| System.arraycopy(ct, 0, ivAndLen, 0, SEMI_BLKSIZE); | |
| System.arraycopy(ct, SEMI_BLKSIZE, ct, 0, SEMI_BLKSIZE); | |
| ctLen -= SEMI_BLKSIZE; | |
| } else { | |
| ctLen = W_INV(ct, ctLen, ivAndLen, embeddedCipher); | |
| } | |
| int outLen = validateIV(ivAndLen, this.iv); | |
| // check padding bytes | |
| int padLen = ctLen - outLen; | |
| if (padLen < 0 || padLen >= SEMI_BLKSIZE) { | |
| throw new IllegalBlockSizeException("Invalid KWP pad length " + | |
| padLen); | |
| } | |
| for (int k = padLen; k > 0; k--) { | |
| if (ct[ctLen - k] != 0) { | |
| throw new IllegalBlockSizeException("Invalid KWP pad value"); | |
| } | |
| } | |
| return outLen; | |
| } | |
| } |