| /* | |
| * Copyright (c) 2021, 2025, 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 sun.security.pkcs11; | |
| import java.io.ByteArrayOutputStream; | |
| import java.nio.ByteBuffer; | |
| import java.util.Arrays; | |
| import java.util.Locale; | |
| import java.security.*; | |
| import java.security.spec.*; | |
| import javax.crypto.*; | |
| import javax.crypto.spec.*; | |
| import java.util.HexFormat; | |
| import jdk.internal.access.JavaNioAccess; | |
| import jdk.internal.access.SharedSecrets; | |
| import sun.nio.ch.DirectBuffer; | |
| import sun.security.jca.JCAUtil; | |
| import sun.security.pkcs11.wrapper.*; | |
| import static sun.security.pkcs11.wrapper.PKCS11Constants.*; | |
| import static sun.security.pkcs11.wrapper.PKCS11Exception.RV.*; | |
| import static sun.security.pkcs11.TemplateManager.*; | |
| /** | |
| * P11 KeyWrap Cipher implementation class for native impl which only support | |
| * single part encryption/decryption through C_Encrypt/C_Decrypt() and | |
| * key wrap/unwrap through C_WrapKey/C_UnwrapKey() calls. | |
| * This class currently supports only AES cipher in KW and KWP modes. | |
| * | |
| * For multi-part encryption/decryption, this class has to buffer data until | |
| * doFinal() is called. | |
| * | |
| * @since 18 | |
| */ | |
| final class P11KeyWrapCipher extends CipherSpi { | |
| private static final JavaNioAccess NIO_ACCESS = SharedSecrets.getJavaNioAccess(); | |
| private static final int BLK_SIZE = 8; | |
| // supported mode and padding with AES cipher | |
| private enum KeyWrapType { | |
| KW_NOPADDING("KW", "NOPADDING"), | |
| KW_PKCS5PADDING("KW", "PKCS5PADDING"), | |
| KWP_NOPADDING("KWP", "NOPADDING"); | |
| private final String mode; | |
| private final String padding; | |
| private final byte[] defIv; | |
| KeyWrapType(String mode, String padding) { | |
| this.mode = mode; | |
| this.padding = padding; | |
| if (mode.equalsIgnoreCase("KW")) { | |
| this.defIv = new byte[] { | |
| (byte)0xA6, (byte)0xA6, (byte)0xA6, (byte)0xA6, | |
| (byte)0xA6, (byte)0xA6, (byte)0xA6, (byte)0xA6 | |
| }; | |
| } else { | |
| this.defIv = new byte[] { | |
| (byte)0xA6, (byte)0x59, (byte)0x59, (byte)0xA6 | |
| }; | |
| } | |
| } | |
| } | |
| // token instance | |
| private final Token token; | |
| // mechanism id | |
| private final long mechanism; | |
| // type of this KeyWrap cipher, one of Transformation enum above | |
| private final KeyWrapType type; | |
| // acceptable key size in bytes, -1 if more than 1 key sizes are accepted | |
| private final int fixedKeySize; | |
| // associated session, if any | |
| private Session session = null; | |
| // key, if init() was called | |
| private P11Key p11Key = null; | |
| // flag indicating whether an operation is initialized | |
| private boolean initialized = false; | |
| private int opmode = Cipher.ENCRYPT_MODE; | |
| // parameters | |
| private byte[] iv = null; | |
| private SecureRandom random = JCAUtil.getSecureRandom(); | |
| // dataBuffer for storing enc/dec data; cleared upon doFinal calls | |
| private final ByteArrayOutputStream dataBuffer = new ByteArrayOutputStream(); | |
| P11KeyWrapCipher(Token token, String algorithm, long mechanism) | |
| throws PKCS11Exception, NoSuchAlgorithmException { | |
| super(); | |
| this.token = token; | |
| this.mechanism = mechanism; | |
| // javax.crypto.Cipher ensures algoParts.length == 3 | |
| String[] algoParts = algorithm.split("/"); | |
| if (algoParts[0].startsWith("AES")) { | |
| int index = algoParts[0].indexOf('_'); | |
| fixedKeySize = (index == -1 ? -1 : | |
| // should be well-formed since we specify what we support | |
| Integer.parseInt(algoParts[0].substring(index+1)) >> 3); | |
| try { | |
| this.type = KeyWrapType.valueOf(algoParts[1].toUpperCase() + | |
| "_" + algoParts[2].toUpperCase()); | |
| } catch (IllegalArgumentException iae) { | |
| throw new NoSuchAlgorithmException("Unsupported algorithm " + | |
| algorithm); | |
| } | |
| } else { | |
| throw new NoSuchAlgorithmException("Unsupported algorithm " + | |
| algorithm); | |
| } | |
| } | |
| @Override | |
| protected void engineSetMode(String mode) throws NoSuchAlgorithmException { | |
| if (!mode.toUpperCase(Locale.ENGLISH).equals(type.mode)) { | |
| throw new NoSuchAlgorithmException("Unsupported mode " + mode); | |
| } | |
| } | |
| // see JCE spec | |
| @Override | |
| protected void engineSetPadding(String padding) | |
| throws NoSuchPaddingException { | |
| if (!padding.toUpperCase(Locale.ENGLISH).equals(type.padding)) { | |
| throw new NoSuchPaddingException("Unsupported padding " + padding); | |
| } | |
| } | |
| // see JCE spec | |
| @Override | |
| protected int engineGetBlockSize() { | |
| return BLK_SIZE; | |
| } | |
| // see JCE spec | |
| @Override | |
| protected int engineGetOutputSize(int inputLen) { | |
| return doFinalLength(inputLen); | |
| } | |
| // see JCE spec | |
| @Override | |
| protected byte[] engineGetIV() { | |
| return (iv == null) ? null : iv.clone(); | |
| } | |
| // see JCE spec | |
| protected AlgorithmParameters engineGetParameters() { | |
| // KW and KWP uses but not require parameters, return the default | |
| // IV when no IV is supplied by caller | |
| byte[] iv = (this.iv == null ? type.defIv : this.iv); | |
| AlgorithmParameterSpec spec = new IvParameterSpec(iv); | |
| try { | |
| AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); | |
| params.init(spec); | |
| return params; | |
| } catch (GeneralSecurityException e) { | |
| // NoSuchAlgorithmException, NoSuchProviderException | |
| // InvalidParameterSpecException | |
| throw new ProviderException("Could not encode parameters", e); | |
| } | |
| } | |
| // see JCE spec | |
| protected void engineInit(int opmode, Key key, SecureRandom sr) | |
| throws InvalidKeyException { | |
| try { | |
| implInit(opmode, key, null, sr); | |
| } catch (InvalidAlgorithmParameterException e) { | |
| throw new InvalidKeyException("init() failed", e); | |
| } | |
| } | |
| // see JCE spec | |
| protected void engineInit(int opmode, Key key, | |
| AlgorithmParameterSpec params, SecureRandom sr) | |
| throws InvalidKeyException, InvalidAlgorithmParameterException { | |
| if (params != null && !(params instanceof IvParameterSpec)) { | |
| throw new InvalidAlgorithmParameterException | |
| ("Only IvParameterSpec is supported"); | |
| } | |
| byte[] ivValue = (params == null ? null : | |
| ((IvParameterSpec)params).getIV()); | |
| implInit(opmode, key, ivValue, sr); | |
| } | |
| // see JCE spec | |
| protected void engineInit(int opmode, Key key, AlgorithmParameters params, | |
| SecureRandom sr) | |
| throws InvalidKeyException, InvalidAlgorithmParameterException { | |
| AlgorithmParameterSpec paramSpec = null; | |
| if (params != null) { | |
| try { | |
| paramSpec = params.getParameterSpec(IvParameterSpec.class); | |
| } catch (InvalidParameterSpecException ex) { | |
| throw new InvalidAlgorithmParameterException(ex); | |
| } | |
| } | |
| engineInit(opmode, key, paramSpec, sr); | |
| } | |
| // actual init() implementation | |
| private void implInit(int opmode, Key key, byte[] iv, SecureRandom sr) | |
| throws InvalidKeyException, InvalidAlgorithmParameterException { | |
| reset(true); | |
| if (fixedKeySize != -1) { | |
| int keySize; | |
| if (key instanceof P11Key) { | |
| keySize = ((P11Key) key).length() >> 3; | |
| } else { | |
| byte[] encoding = key.getEncoded(); | |
| Arrays.fill(encoding, (byte) 0); | |
| keySize = encoding.length; | |
| } | |
| if (keySize != fixedKeySize) { | |
| throw new InvalidKeyException("Key size is invalid"); | |
| } | |
| } | |
| P11Key newKey = P11SecretKeyFactory.convertKey(token, key, "AES"); | |
| this.opmode = opmode; | |
| if (iv == null) { | |
| iv = type.defIv; | |
| } else { | |
| if (type == KeyWrapType.KWP_NOPADDING && | |
| !Arrays.equals(iv, type.defIv)) { | |
| throw new InvalidAlgorithmParameterException | |
| ("For KWP mode, IV must has value 0x" + | |
| HexFormat.of().withUpperCase().formatHex(type.defIv)); | |
| } else if (iv.length != type.defIv.length) { | |
| throw new InvalidAlgorithmParameterException | |
| ("Wrong IV length, expected " + type.defIv.length + | |
| " but got " + iv.length); | |
| } | |
| } | |
| this.iv = iv; | |
| this.p11Key = newKey; | |
| if (sr != null) { | |
| this.random = sr; | |
| } | |
| try { | |
| initialize(); | |
| } catch (PKCS11Exception e) { | |
| if (e.match(CKR_MECHANISM_PARAM_INVALID)) { | |
| throw new InvalidAlgorithmParameterException("Bad params", e); | |
| } | |
| throw new InvalidKeyException("Could not initialize cipher", e); | |
| } | |
| } | |
| private void cancelOperation() { | |
| token.ensureValid(); | |
| if (P11Util.trySessionCancel(token, session, | |
| (opmode == Cipher.ENCRYPT_MODE ? CKF_ENCRYPT : CKF_DECRYPT))) { | |
| return; | |
| } | |
| // cancel by finishing operations; avoid killSession as some | |
| // hardware vendors may require re-login | |
| byte[] in = dataBuffer.toByteArray(); | |
| int inLen = in.length; | |
| int bufLen = doFinalLength(0); | |
| byte[] buffer = new byte[bufLen]; | |
| try { | |
| if (opmode == Cipher.ENCRYPT_MODE) { | |
| token.p11.C_Encrypt(session.id(), 0, in, 0, inLen, | |
| 0, buffer, 0, bufLen); | |
| } else if (opmode == Cipher.DECRYPT_MODE) { | |
| token.p11.C_Decrypt(session.id(), 0, in, 0, inLen, | |
| 0, buffer, 0, bufLen); | |
| } | |
| } catch (PKCS11Exception e) { | |
| if (e.match(CKR_OPERATION_NOT_INITIALIZED)) { | |
| // Cancel Operation may be invoked after an error on a PKCS#11 | |
| // call. If the operation inside the token was already | |
| // cancelled, do not fail here. This is part of a defensive | |
| // mechanism for PKCS#11 libraries that do not strictly follow | |
| // the standard. | |
| return; | |
| } | |
| // ignore failure for en/decryption since it's likely to fail | |
| // due to the minimum length requirement | |
| } | |
| } | |
| private void ensureInitialized() throws PKCS11Exception { | |
| if (!initialized) { | |
| initialize(); | |
| } | |
| } | |
| private void initialize() throws PKCS11Exception { | |
| if (p11Key == null) { | |
| throw new ProviderException("Operation cannot be performed without" | |
| + " calling engineInit first"); | |
| } | |
| token.ensureValid(); | |
| dataBuffer.reset(); | |
| if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.DECRYPT_MODE) { | |
| long p11KeyID = p11Key.getKeyID(); | |
| try { | |
| CK_MECHANISM mechWithParams = new CK_MECHANISM(mechanism, iv); | |
| if (session == null) { | |
| session = token.getOpSession(); | |
| } | |
| switch (opmode) { | |
| case Cipher.ENCRYPT_MODE -> token.p11.C_EncryptInit(session.id(), mechWithParams, | |
| p11KeyID); | |
| case Cipher.DECRYPT_MODE -> token.p11.C_DecryptInit(session.id(), mechWithParams, | |
| p11KeyID); | |
| } | |
| } catch (PKCS11Exception e) { | |
| session = token.releaseSession(session); | |
| throw e; | |
| } finally { | |
| p11Key.releaseKeyID(); | |
| } | |
| } | |
| initialized = true; | |
| } | |
| // if doFinal(inLen) is called, how big does the output buffer have to be? | |
| private int doFinalLength(int inLen) { | |
| if (inLen < 0) { | |
| throw new ProviderException("Invalid negative input length"); | |
| } | |
| int result = inLen + dataBuffer.size(); | |
| boolean encrypt = (opmode == Cipher.ENCRYPT_MODE || | |
| opmode == Cipher.WRAP_MODE); | |
| if (encrypt) { | |
| if (type == KeyWrapType.KW_PKCS5PADDING) { | |
| // add potential pad length, i.e. 1-8 | |
| result += (BLK_SIZE - (result & (BLK_SIZE - 1))); | |
| } else if (type == KeyWrapType.KWP_NOPADDING && | |
| (result & (BLK_SIZE - 1)) != 0) { | |
| // add potential pad length, i.e. 0-7 | |
| result += (BLK_SIZE - (result & (BLK_SIZE - 1))); | |
| } | |
| result += BLK_SIZE; // add the leading block including the ICV | |
| } else { | |
| result -= BLK_SIZE; // minus the leading block including the ICV | |
| } | |
| return (Math.max(result, 0)); | |
| } | |
| // reset the states to the pre-initialized values | |
| // set initialized to false, cancel operation, release session, and | |
| // reset dataBuffer | |
| private void reset(boolean doCancel) { | |
| if (!initialized) { | |
| return; | |
| } | |
| initialized = false; | |
| try { | |
| if (session == null) { | |
| return; | |
| } | |
| if (doCancel && token.explicitCancel) { | |
| cancelOperation(); | |
| } | |
| } finally { | |
| session = token.releaseSession(session); | |
| dataBuffer.reset(); | |
| } | |
| } | |
| // see JCE spec | |
| protected byte[] engineUpdate(byte[] in, int inOfs, int inLen) { | |
| int n = implUpdate(in, inOfs, inLen); | |
| return new byte[0]; | |
| } | |
| // see JCE spec | |
| protected int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out, | |
| int outOfs) throws ShortBufferException { | |
| implUpdate(in, inOfs, inLen); | |
| return 0; | |
| } | |
| // see JCE spec | |
| @Override | |
| protected int engineUpdate(ByteBuffer inBuffer, ByteBuffer outBuffer) | |
| throws ShortBufferException { | |
| implUpdate(inBuffer); | |
| return 0; | |
| } | |
| // see JCE spec | |
| protected byte[] engineDoFinal(byte[] in, int inOfs, int inLen) | |
| throws IllegalBlockSizeException, BadPaddingException { | |
| int maxOutLen = doFinalLength(inLen); | |
| try { | |
| byte[] out = new byte[maxOutLen]; | |
| int n = engineDoFinal(in, inOfs, inLen, out, 0); | |
| return P11Util.convert(out, 0, n); | |
| } catch (ShortBufferException e) { | |
| // convert since the output length is calculated by doFinalLength() | |
| throw new ProviderException(e); | |
| } | |
| } | |
| // see JCE spec | |
| protected int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out, | |
| int outOfs) throws ShortBufferException, IllegalBlockSizeException, | |
| BadPaddingException { | |
| return implDoFinal(in, inOfs, inLen, out, outOfs, out.length - outOfs); | |
| } | |
| // see JCE spec | |
| @Override | |
| protected int engineDoFinal(ByteBuffer inBuffer, ByteBuffer outBuffer) | |
| throws ShortBufferException, IllegalBlockSizeException, | |
| BadPaddingException { | |
| return implDoFinal(inBuffer, outBuffer); | |
| } | |
| private int implUpdate(byte[] in, int inOfs, int inLen) { | |
| if (inLen > 0) { | |
| try { | |
| ensureInitialized(); | |
| } catch (PKCS11Exception e) { | |
| reset(false); | |
| throw new ProviderException("update() failed", e); | |
| } | |
| dataBuffer.write(in, inOfs, inLen); | |
| } | |
| // always 0 as NSS only supports single-part encryption/decryption | |
| return 0; | |
| } | |
| private int implUpdate(ByteBuffer inBuf) { | |
| int inLen = inBuf.remaining(); | |
| if (inLen > 0) { | |
| try { | |
| ensureInitialized(); | |
| } catch (PKCS11Exception e) { | |
| reset(false); | |
| throw new ProviderException("update() failed", e); | |
| } | |
| byte[] data = new byte[inLen]; | |
| inBuf.get(data); | |
| dataBuffer.write(data, 0, data.length); | |
| } | |
| // always 0 as NSS only supports single-part encryption/decryption | |
| return 0; | |
| } | |
| private int implDoFinal(byte[] in, int inOfs, int inLen, | |
| byte[] out, int outOfs, int outLen) | |
| throws ShortBufferException, IllegalBlockSizeException, | |
| BadPaddingException { | |
| int requiredOutLen = doFinalLength(inLen); | |
| if (outLen < requiredOutLen) { | |
| throw new ShortBufferException(); | |
| } | |
| boolean doCancel = true; | |
| int k = 0; | |
| try { | |
| ensureInitialized(); | |
| if (dataBuffer.size() > 0) { | |
| if (in != null && inLen > 0) { | |
| dataBuffer.write(in, inOfs, inLen); | |
| } | |
| in = dataBuffer.toByteArray(); | |
| inOfs = 0; | |
| inLen = in.length; | |
| } | |
| if (opmode == Cipher.ENCRYPT_MODE) { | |
| k = token.p11.C_Encrypt(session.id(), 0, in, inOfs, inLen, | |
| 0, out, outOfs, outLen); | |
| doCancel = false; | |
| } else { | |
| // Special handling to match SunJCE provider behavior | |
| if (inLen == 0) { | |
| return 0; | |
| } | |
| k = token.p11.C_Decrypt(session.id(), 0, in, inOfs, inLen, | |
| 0, out, outOfs, outLen); | |
| doCancel = false; | |
| } | |
| } catch (PKCS11Exception e) { | |
| // As per the PKCS#11 standard, C_Encrypt and C_Decrypt may only | |
| // keep the operation active on CKR_BUFFER_TOO_SMALL errors or | |
| // successful calls to determine the output length. However, | |
| // these cases are not expected here because the output length | |
| // is checked in the OpenJDK side before making the PKCS#11 call. | |
| // Thus, doCancel can safely be 'false'. | |
| doCancel = false; | |
| handleEncException("doFinal() failed", e); | |
| } finally { | |
| reset(doCancel); | |
| } | |
| return k; | |
| } | |
| private int implDoFinal(ByteBuffer inBuffer, ByteBuffer outBuffer) | |
| throws ShortBufferException, IllegalBlockSizeException, | |
| BadPaddingException { | |
| int outLen = outBuffer.remaining(); | |
| int inLen = inBuffer.remaining(); | |
| int requiredOutLen = doFinalLength(inLen); | |
| if (outLen < requiredOutLen) { | |
| throw new ShortBufferException(); | |
| } | |
| boolean doCancel = true; | |
| int k = 0; | |
| NIO_ACCESS.acquireSession(inBuffer); | |
| try { | |
| NIO_ACCESS.acquireSession(outBuffer); | |
| try { | |
| try { | |
| ensureInitialized(); | |
| long inAddr = 0; | |
| byte[] in = null; | |
| int inOfs = 0; | |
| if (dataBuffer.size() > 0) { | |
| if (inBuffer != null && inLen > 0) { | |
| byte[] temp = new byte[inLen]; | |
| inBuffer.get(temp); | |
| dataBuffer.write(temp, 0, temp.length); | |
| } | |
| in = dataBuffer.toByteArray(); | |
| inOfs = 0; | |
| inLen = in.length; | |
| } else { | |
| if (inBuffer instanceof DirectBuffer) { | |
| inAddr = NIO_ACCESS.getBufferAddress(inBuffer); | |
| inOfs = inBuffer.position(); | |
| } else { | |
| if (inBuffer.hasArray()) { | |
| in = inBuffer.array(); | |
| inOfs = inBuffer.position() + inBuffer.arrayOffset(); | |
| } else { | |
| in = new byte[inLen]; | |
| inBuffer.get(in); | |
| } | |
| } | |
| } | |
| long outAddr = 0; | |
| byte[] outArray = null; | |
| int outOfs = 0; | |
| if (outBuffer instanceof DirectBuffer) { | |
| outAddr = NIO_ACCESS.getBufferAddress(outBuffer); | |
| outOfs = outBuffer.position(); | |
| } else { | |
| if (outBuffer.hasArray()) { | |
| outArray = outBuffer.array(); | |
| outOfs = outBuffer.position() + outBuffer.arrayOffset(); | |
| } else { | |
| outArray = new byte[outLen]; | |
| } | |
| } | |
| if (opmode == Cipher.ENCRYPT_MODE) { | |
| k = token.p11.C_Encrypt(session.id(), inAddr, in, inOfs, inLen, | |
| outAddr, outArray, outOfs, outLen); | |
| doCancel = false; | |
| } else { | |
| // Special handling to match SunJCE provider behavior | |
| if (inLen == 0) { | |
| return 0; | |
| } | |
| k = token.p11.C_Decrypt(session.id(), inAddr, in, inOfs, inLen, | |
| outAddr, outArray, outOfs, outLen); | |
| doCancel = false; | |
| } | |
| inBuffer.position(inBuffer.limit()); | |
| outBuffer.position(outBuffer.position() + k); | |
| } catch (PKCS11Exception e) { | |
| // As per the PKCS#11 standard, C_Encrypt and C_Decrypt may only | |
| // keep the operation active on CKR_BUFFER_TOO_SMALL errors or | |
| // successful calls to determine the output length. However, | |
| // these cases are not expected here because the output length | |
| // is checked in the OpenJDK side before making the PKCS#11 call. | |
| // Thus, doCancel can safely be 'false'. | |
| doCancel = false; | |
| handleEncException("doFinal() failed", e); | |
| } finally { | |
| reset(doCancel); | |
| } | |
| } finally { | |
| NIO_ACCESS.releaseSession(outBuffer); | |
| } | |
| } finally { | |
| NIO_ACCESS.releaseSession(inBuffer); | |
| } | |
| return k; | |
| } | |
| private void handleEncException(String msg, PKCS11Exception e) | |
| throws IllegalBlockSizeException, ShortBufferException, | |
| ProviderException { | |
| if (e.match(CKR_DATA_LEN_RANGE) || | |
| e.match(CKR_ENCRYPTED_DATA_LEN_RANGE)) { | |
| throw (IllegalBlockSizeException) | |
| (new IllegalBlockSizeException(msg).initCause(e)); | |
| } else if (e.match(CKR_BUFFER_TOO_SMALL)) { | |
| throw (ShortBufferException) | |
| (new ShortBufferException(msg).initCause(e)); | |
| } else { | |
| throw new ProviderException(msg, e); | |
| } | |
| } | |
| // see JCE spec | |
| protected byte[] engineWrap(Key tbwKey) throws IllegalBlockSizeException, | |
| InvalidKeyException { | |
| try { | |
| ensureInitialized(); | |
| } catch (PKCS11Exception e) { | |
| reset(false); | |
| throw new ProviderException("wrap() failed", e); | |
| } | |
| // convert the specified key into P11Key handle | |
| P11Key tbwP11Key = null; | |
| if (!(tbwKey instanceof P11Key)) { | |
| try { | |
| tbwP11Key = (tbwKey instanceof SecretKey ? | |
| P11SecretKeyFactory.convertKey(token, tbwKey, | |
| tbwKey.getAlgorithm()) : | |
| P11KeyFactory.convertKey(token, tbwKey, | |
| tbwKey.getAlgorithm())); | |
| } catch (ProviderException pe) { | |
| throw new InvalidKeyException("Cannot convert to PKCS11 key", | |
| pe); | |
| } catch (InvalidKeyException ike) { | |
| // could be algorithms NOT supported by PKCS11 library | |
| // try single part encryption instead | |
| } | |
| } else { | |
| tbwP11Key = (P11Key) tbwKey; | |
| } | |
| long p11KeyID = 0; | |
| try { | |
| if (session == null) { | |
| session = token.getOpSession(); | |
| } | |
| p11KeyID = p11Key.getKeyID(); | |
| CK_MECHANISM mechWithParams = new CK_MECHANISM(mechanism, iv); | |
| if (tbwP11Key != null) { | |
| long tbwP11KeyID = tbwP11Key.getKeyID(); | |
| try { | |
| return token.p11.C_WrapKey(session.id(), mechWithParams, | |
| p11KeyID, tbwP11KeyID); | |
| } finally { | |
| tbwP11Key.releaseKeyID(); | |
| } | |
| } else { | |
| byte[] in = tbwKey.getEncoded(); | |
| try { | |
| token.p11.C_EncryptInit(session.id(), mechWithParams, | |
| p11KeyID); | |
| int bufLen = doFinalLength(in.length); | |
| byte[] buffer = new byte[bufLen]; | |
| token.p11.C_Encrypt(session.id(), 0, in, 0, in.length, | |
| 0, buffer, 0, bufLen); | |
| return buffer; | |
| } finally { | |
| Arrays.fill(in, (byte)0); | |
| } | |
| } | |
| } catch (PKCS11Exception e) { | |
| String msg = "wrap() failed"; | |
| if (e.match(CKR_KEY_SIZE_RANGE) || e.match(CKR_DATA_LEN_RANGE)) { | |
| throw (IllegalBlockSizeException) | |
| (new IllegalBlockSizeException(msg).initCause(e)); | |
| } else if (e.match(CKR_KEY_NOT_WRAPPABLE) || | |
| e.match(CKR_KEY_UNEXTRACTABLE) || | |
| e.match(CKR_KEY_HANDLE_INVALID)) { | |
| throw new InvalidKeyException(msg, e); | |
| } else if (e.match(CKR_MECHANISM_INVALID)) { | |
| throw new UnsupportedOperationException(msg, e); | |
| } else { | |
| throw new ProviderException(msg, e); | |
| } | |
| } finally { | |
| if (p11KeyID != 0) p11Key.releaseKeyID(); | |
| reset(false); | |
| } | |
| } | |
| // see JCE spec | |
| protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgo, | |
| int wrappedKeyType) | |
| throws InvalidKeyException, NoSuchAlgorithmException { | |
| try { | |
| ensureInitialized(); | |
| } catch (PKCS11Exception e) { | |
| reset(false); | |
| throw new ProviderException("unwrap() failed", e); | |
| } | |
| long keyClass; | |
| long keyType; | |
| switch (wrappedKeyType) { | |
| case Cipher.PRIVATE_KEY -> { | |
| keyClass = CKO_PRIVATE_KEY; | |
| keyType = P11KeyFactory.getPKCS11KeyType(wrappedKeyAlgo); | |
| } | |
| case Cipher.SECRET_KEY -> { | |
| keyClass = CKO_SECRET_KEY; | |
| keyType = P11SecretKeyFactory.getPKCS11KeyType(wrappedKeyAlgo); | |
| } | |
| case Cipher.PUBLIC_KEY -> throw new UnsupportedOperationException | |
| ("cannot unwrap public keys"); | |
| default -> // should never happen | |
| throw new AssertionError(); | |
| } | |
| CK_ATTRIBUTE[] attributes; | |
| try { | |
| attributes = new CK_ATTRIBUTE[] { | |
| new CK_ATTRIBUTE(CKA_CLASS, keyClass), | |
| new CK_ATTRIBUTE(CKA_KEY_TYPE, keyType), | |
| }; | |
| attributes = token.getAttributes | |
| (O_IMPORT, keyClass, keyType, attributes); | |
| } catch (PKCS11Exception e) { | |
| reset(false); | |
| throw new ProviderException("unwrap() failed", e); | |
| } | |
| CK_MECHANISM mechParams = new CK_MECHANISM(mechanism, iv); | |
| long p11KeyID = 0; | |
| try { | |
| if (session == null) { | |
| session = token.getOpSession(); | |
| } | |
| p11KeyID = p11Key.getKeyID(); | |
| long unwrappedKeyID = token.p11.C_UnwrapKey(session.id(), | |
| mechParams, p11KeyID, wrappedKey, attributes); | |
| return (switch(wrappedKeyType) { | |
| case Cipher.PRIVATE_KEY -> P11Key.privateKey | |
| (session, unwrappedKeyID, wrappedKeyAlgo, -1, attributes); | |
| case Cipher.SECRET_KEY -> P11Key.secretKey | |
| (session, unwrappedKeyID, wrappedKeyAlgo, -1, attributes); | |
| default -> null; | |
| }); | |
| } catch (PKCS11Exception e) { | |
| String msg = "unwrap() failed"; | |
| if (e.match(CKR_UNWRAPPING_KEY_SIZE_RANGE) || | |
| e.match(CKR_WRAPPED_KEY_INVALID) || | |
| e.match(CKR_WRAPPED_KEY_LEN_RANGE) || | |
| e.match(CKR_UNWRAPPING_KEY_HANDLE_INVALID) || | |
| e.match(CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT)) { | |
| throw new InvalidKeyException(msg, e); | |
| } else if (e.match(CKR_MECHANISM_INVALID)) { | |
| throw new UnsupportedOperationException(msg, e); | |
| } else { | |
| throw new ProviderException(msg, e); | |
| } | |
| } finally { | |
| if (p11KeyID != 0) p11Key.releaseKeyID(); | |
| reset(false); | |
| } | |
| } | |
| // see JCE spec | |
| @Override | |
| protected int engineGetKeySize(Key key) throws InvalidKeyException { | |
| return P11SecretKeyFactory.convertKey(token, key, "AES").length(); | |
| } | |
| } |