Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/security/pkcs11/P11AEADCipher.java
38919 views
/*1* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package sun.security.pkcs11;2526import java.io.ByteArrayOutputStream;27import java.nio.ByteBuffer;28import java.util.Arrays;29import java.util.Locale;3031import java.security.*;32import java.security.spec.*;3334import javax.crypto.*;35import javax.crypto.spec.*;3637import sun.nio.ch.DirectBuffer;38import sun.security.jca.JCAUtil;39import sun.security.pkcs11.wrapper.*;40import static sun.security.pkcs11.wrapper.PKCS11Constants.*;4142/**43* P11 AEAD Cipher implementation class. This class currently supports44* AES with GCM mode.45*46* Note that AEAD modes do not use padding, so this class does not have47* its own padding impl. In addition, NSS CKM_AES_GCM only supports single-part48* encryption/decryption, thus the current impl uses PKCS#11 C_Encrypt/C_Decrypt49* calls and buffers data until doFinal is called.50*51* Note that PKCS#11 standard currently only supports GCM and CCM AEAD modes.52* There are no provisions for other AEAD modes yet.53*54* @since 1355*/56final class P11AEADCipher extends CipherSpi {5758// mode constant for GCM mode59private static final int MODE_GCM = 10;6061// default constants for GCM62private static final int GCM_DEFAULT_TAG_LEN = 16;63private static final int GCM_DEFAULT_IV_LEN = 16;6465private static final String ALGO = "AES";6667// token instance68private final Token token;6970// mechanism id71private final long mechanism;7273// mode, one of MODE_* above74private final int blockMode;7576// acceptable key size, -1 if more than 1 key sizes are accepted77private final int fixedKeySize;7879// associated session, if any80private Session session = null;8182// key, if init() was called83private P11Key p11Key = null;8485// flag indicating whether an operation is initialized86private boolean initialized = false;8788// falg indicating encrypt or decrypt mode89private boolean encrypt = true;9091// parameters92private byte[] iv = null;93private int tagLen = -1;94private SecureRandom random = JCAUtil.getSecureRandom();9596// dataBuffer is cleared upon doFinal calls97private ByteArrayOutputStream dataBuffer = new ByteArrayOutputStream();98// aadBuffer is cleared upon successful init calls99private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream();100private boolean updateCalled = false;101102private boolean requireReinit = false;103private P11Key lastEncKey = null;104private byte[] lastEncIv = null;105106P11AEADCipher(Token token, String algorithm, long mechanism)107throws PKCS11Exception, NoSuchAlgorithmException {108super();109this.token = token;110this.mechanism = mechanism;111112String[] algoParts = algorithm.split("/");113if (algoParts.length != 3) {114throw new ProviderException("Unsupported Transformation format: " +115algorithm);116}117if (!algoParts[0].startsWith("AES")) {118throw new ProviderException("Only support AES for AEAD cipher mode");119}120int index = algoParts[0].indexOf('_');121if (index != -1) {122// should be well-formed since we specify what we support123fixedKeySize = Integer.parseInt(algoParts[0].substring(index+1)) >> 3;124} else {125fixedKeySize = -1;126}127this.blockMode = parseMode(algoParts[1]);128if (!algoParts[2].equals("NoPadding")) {129throw new ProviderException("Only NoPadding is supported for AEAD cipher mode");130}131}132133protected void engineSetMode(String mode) throws NoSuchAlgorithmException {134// Disallow change of mode for now since currently it's explicitly135// defined in transformation strings136throw new NoSuchAlgorithmException("Unsupported mode " + mode);137}138139private int parseMode(String mode) throws NoSuchAlgorithmException {140mode = mode.toUpperCase(Locale.ENGLISH);141int result;142if (mode.equals("GCM")) {143result = MODE_GCM;144} else {145throw new NoSuchAlgorithmException("Unsupported mode " + mode);146}147return result;148}149150// see JCE spec151protected void engineSetPadding(String padding)152throws NoSuchPaddingException {153// Disallow change of padding for now since currently it's explicitly154// defined in transformation strings155throw new NoSuchPaddingException("Unsupported padding " + padding);156}157158// see JCE spec159protected int engineGetBlockSize() {160return 16; // constant; only AES is supported161}162163// see JCE spec164protected int engineGetOutputSize(int inputLen) {165return doFinalLength(inputLen);166}167168// see JCE spec169protected byte[] engineGetIV() {170return (iv == null) ? null : iv.clone();171}172173// see JCE spec174protected AlgorithmParameters engineGetParameters() {175if (encrypt && iv == null && tagLen == -1) {176switch (blockMode) {177case MODE_GCM:178iv = new byte[GCM_DEFAULT_IV_LEN];179tagLen = GCM_DEFAULT_TAG_LEN;180break;181default:182throw new ProviderException("Unsupported mode");183}184random.nextBytes(iv);185}186try {187AlgorithmParameterSpec spec;188String apAlgo;189switch (blockMode) {190case MODE_GCM:191apAlgo = "GCM";192spec = new GCMParameterSpec(tagLen << 3, iv);193break;194default:195throw new ProviderException("Unsupported mode");196}197AlgorithmParameters params =198AlgorithmParameters.getInstance(apAlgo);199params.init(spec);200return params;201} catch (GeneralSecurityException e) {202// NoSuchAlgorithmException, NoSuchProviderException203// InvalidParameterSpecException204throw new ProviderException("Could not encode parameters", e);205}206}207208// see JCE spec209protected void engineInit(int opmode, Key key, SecureRandom sr)210throws InvalidKeyException {211if (opmode == Cipher.DECRYPT_MODE) {212throw new InvalidKeyException("Parameters required for decryption");213}214updateCalled = false;215try {216implInit(opmode, key, null, -1, sr);217} catch (InvalidAlgorithmParameterException e) {218throw new InvalidKeyException("init() failed", e);219}220}221222// see JCE spec223protected void engineInit(int opmode, Key key,224AlgorithmParameterSpec params, SecureRandom sr)225throws InvalidKeyException, InvalidAlgorithmParameterException {226if (opmode == Cipher.DECRYPT_MODE && params == null) {227throw new InvalidAlgorithmParameterException228("Parameters required for decryption");229}230updateCalled = false;231byte[] ivValue = null;232int tagLen = -1;233if (params != null) {234switch (blockMode) {235case MODE_GCM:236if (!(params instanceof GCMParameterSpec)) {237throw new InvalidAlgorithmParameterException238("Only GCMParameterSpec is supported");239}240ivValue = ((GCMParameterSpec) params).getIV();241tagLen = ((GCMParameterSpec) params).getTLen() >> 3;242break;243default:244throw new ProviderException("Unsupported mode");245}246}247implInit(opmode, key, ivValue, tagLen, sr);248}249250// see JCE spec251protected void engineInit(int opmode, Key key, AlgorithmParameters params,252SecureRandom sr)253throws InvalidKeyException, InvalidAlgorithmParameterException {254if (opmode == Cipher.DECRYPT_MODE && params == null) {255throw new InvalidAlgorithmParameterException256("Parameters required for decryption");257}258updateCalled = false;259try {260AlgorithmParameterSpec paramSpec = null;261if (params != null) {262switch (blockMode) {263case MODE_GCM:264paramSpec =265params.getParameterSpec(GCMParameterSpec.class);266break;267default:268throw new ProviderException("Unsupported mode");269}270}271engineInit(opmode, key, paramSpec, sr);272} catch (InvalidParameterSpecException ex) {273throw new InvalidAlgorithmParameterException(ex);274}275}276277// actual init() implementation278private void implInit(int opmode, Key key, byte[] iv, int tagLen,279SecureRandom sr)280throws InvalidKeyException, InvalidAlgorithmParameterException {281reset(true);282if (fixedKeySize != -1 &&283((key instanceof P11Key) ? ((P11Key) key).length() >> 3 :284key.getEncoded().length) != fixedKeySize) {285throw new InvalidKeyException("Key size is invalid");286}287P11Key newKey = P11SecretKeyFactory.convertKey(token, key, ALGO);288switch (opmode) {289case Cipher.ENCRYPT_MODE:290encrypt = true;291requireReinit = Arrays.equals(iv, lastEncIv) &&292(newKey == lastEncKey);293if (requireReinit) {294throw new InvalidAlgorithmParameterException295("Cannot reuse iv for GCM encryption");296}297break;298case Cipher.DECRYPT_MODE:299encrypt = false;300requireReinit = false;301break;302default:303throw new InvalidAlgorithmParameterException304("Unsupported mode: " + opmode);305}306307// decryption without parameters is checked in all engineInit() calls308if (sr != null) {309this.random = sr;310}311if (iv == null && tagLen == -1) {312// generate default values313switch (blockMode) {314case MODE_GCM:315iv = new byte[GCM_DEFAULT_IV_LEN];316this.random.nextBytes(iv);317tagLen = GCM_DEFAULT_TAG_LEN;318break;319default:320throw new ProviderException("Unsupported mode");321}322}323this.iv = iv;324this.tagLen = tagLen;325this.p11Key = newKey;326try {327initialize();328} catch (PKCS11Exception e) {329throw new InvalidKeyException("Could not initialize cipher", e);330}331}332333private void cancelOperation() {334// cancel operation by finishing it; avoid killSession as some335// hardware vendors may require re-login336int bufLen = doFinalLength(0);337byte[] buffer = new byte[bufLen];338byte[] in = dataBuffer.toByteArray();339int inLen = in.length;340try {341if (encrypt) {342token.p11.C_Encrypt(session.id(), 0, in, 0, inLen,3430, buffer, 0, bufLen);344} else {345token.p11.C_Decrypt(session.id(), 0, in, 0, inLen,3460, buffer, 0, bufLen);347}348} catch (PKCS11Exception e) {349if (e.getErrorCode() == CKR_OPERATION_NOT_INITIALIZED) {350// Cancel Operation may be invoked after an error on a PKCS#11351// call. If the operation inside the token was already cancelled,352// do not fail here. This is part of a defensive mechanism for353// PKCS#11 libraries that do not strictly follow the standard.354return;355}356if (encrypt) {357throw new ProviderException("Cancel failed", e);358}359// ignore failure for decryption360}361}362363private void ensureInitialized() throws PKCS11Exception {364if (initialized && aadBuffer.size() > 0) {365// need to cancel first to avoid CKR_OPERATION_ACTIVE366reset(true);367}368if (!initialized) {369initialize();370}371}372373private void initialize() throws PKCS11Exception {374if (p11Key == null) {375throw new ProviderException(376"Operation cannot be performed without"377+ " calling engineInit first");378}379if (requireReinit) {380throw new IllegalStateException381("Must use either different key or iv for GCM encryption");382}383384token.ensureValid();385386byte[] aad = (aadBuffer.size() > 0? aadBuffer.toByteArray() : null);387388long p11KeyID = p11Key.getKeyID();389try {390if (session == null) {391session = token.getOpSession();392}393CK_MECHANISM mechWithParams;394switch (blockMode) {395case MODE_GCM:396mechWithParams = new CK_MECHANISM(mechanism,397new CK_GCM_PARAMS(tagLen << 3, iv, aad));398break;399default:400throw new ProviderException("Unsupported mode: " + blockMode);401}402if (encrypt) {403token.p11.C_EncryptInit(session.id(), mechWithParams,404p11KeyID);405} else {406token.p11.C_DecryptInit(session.id(), mechWithParams,407p11KeyID);408}409} catch (PKCS11Exception e) {410//e.printStackTrace();411p11Key.releaseKeyID();412session = token.releaseSession(session);413throw e;414} finally {415dataBuffer.reset();416aadBuffer.reset();417}418initialized = true;419}420421// if doFinal(inLen) is called, how big does the output buffer have to be?422private int doFinalLength(int inLen) {423if (inLen < 0) {424throw new ProviderException("Invalid negative input length");425}426427int result = inLen + dataBuffer.size();428if (encrypt) {429result += tagLen;430} else {431// PKCS11Exception: CKR_BUFFER_TOO_SMALL432//result -= tagLen;433}434return result;435}436437// reset the states to the pre-initialized values438private void reset(boolean doCancel) {439if (!initialized) {440return;441}442initialized = false;443444try {445if (session == null) {446return;447}448449if (doCancel && token.explicitCancel) {450cancelOperation();451}452} finally {453p11Key.releaseKeyID();454session = token.releaseSession(session);455dataBuffer.reset();456}457}458459// see JCE spec460protected byte[] engineUpdate(byte[] in, int inOfs, int inLen) {461updateCalled = true;462int n = implUpdate(in, inOfs, inLen);463return new byte[0];464}465466// see JCE spec467protected int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out,468int outOfs) throws ShortBufferException {469updateCalled = true;470implUpdate(in, inOfs, inLen);471return 0;472}473474// see JCE spec475@Override476protected int engineUpdate(ByteBuffer inBuffer, ByteBuffer outBuffer)477throws ShortBufferException {478updateCalled = true;479implUpdate(inBuffer);480return 0;481}482483// see JCE spec484@Override485protected synchronized void engineUpdateAAD(byte[] src, int srcOfs, int srcLen)486throws IllegalStateException {487if ((src == null) || (srcOfs < 0) || (srcOfs + srcLen > src.length)) {488throw new IllegalArgumentException("Invalid AAD");489}490if (requireReinit) {491throw new IllegalStateException492("Must use either different key or iv for GCM encryption");493}494if (p11Key == null) {495throw new IllegalStateException("Need to initialize Cipher first");496}497if (updateCalled) {498throw new IllegalStateException499("Update has been called; no more AAD data");500}501aadBuffer.write(src, srcOfs, srcLen);502}503504// see JCE spec505@Override506protected void engineUpdateAAD(ByteBuffer src)507throws IllegalStateException {508if (src == null) {509throw new IllegalArgumentException("Invalid AAD");510}511byte[] srcBytes = new byte[src.remaining()];512src.get(srcBytes);513engineUpdateAAD(srcBytes, 0, srcBytes.length);514}515516// see JCE spec517protected byte[] engineDoFinal(byte[] in, int inOfs, int inLen)518throws IllegalBlockSizeException, BadPaddingException {519int minOutLen = doFinalLength(inLen);520try {521byte[] out = new byte[minOutLen];522int n = engineDoFinal(in, inOfs, inLen, out, 0);523return P11Util.convert(out, 0, n);524} catch (ShortBufferException e) {525// convert since the output length is calculated by doFinalLength()526throw new ProviderException(e);527} finally {528updateCalled = false;529}530}531// see JCE spec532protected int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out,533int outOfs) throws ShortBufferException, IllegalBlockSizeException,534BadPaddingException {535try {536return implDoFinal(in, inOfs, inLen, out, outOfs, out.length - outOfs);537} finally {538updateCalled = false;539}540}541542// see JCE spec543@Override544protected int engineDoFinal(ByteBuffer inBuffer, ByteBuffer outBuffer)545throws ShortBufferException, IllegalBlockSizeException,546BadPaddingException {547try {548return implDoFinal(inBuffer, outBuffer);549} finally {550updateCalled = false;551}552}553554private int implUpdate(byte[] in, int inOfs, int inLen) {555if (inLen > 0) {556updateCalled = true;557try {558ensureInitialized();559} catch (PKCS11Exception e) {560//e.printStackTrace();561reset(false);562throw new ProviderException("update() failed", e);563}564dataBuffer.write(in, inOfs, inLen);565}566// always 0 as NSS only supports single-part encryption/decryption567return 0;568}569570private int implUpdate(ByteBuffer inBuf) {571int inLen = inBuf.remaining();572if (inLen > 0) {573try {574ensureInitialized();575} catch (PKCS11Exception e) {576reset(false);577throw new ProviderException("update() failed", e);578}579byte[] data = new byte[inLen];580inBuf.get(data);581dataBuffer.write(data, 0, data.length);582}583// always 0 as NSS only supports single-part encryption/decryption584return 0;585}586587private int implDoFinal(byte[] in, int inOfs, int inLen,588byte[] out, int outOfs, int outLen)589throws ShortBufferException, IllegalBlockSizeException,590BadPaddingException {591int requiredOutLen = doFinalLength(inLen);592if (outLen < requiredOutLen) {593throw new ShortBufferException();594}595boolean doCancel = true;596try {597ensureInitialized();598if (dataBuffer.size() > 0) {599if (in != null && inOfs > 0 && inLen > 0 &&600inOfs < (in.length - inLen)) {601dataBuffer.write(in, inOfs, inLen);602}603in = dataBuffer.toByteArray();604inOfs = 0;605inLen = in.length;606}607int k = 0;608if (encrypt) {609k = token.p11.C_Encrypt(session.id(), 0, in, inOfs, inLen,6100, out, outOfs, outLen);611doCancel = false;612} else {613// Special handling to match SunJCE provider behavior614if (inLen == 0) {615return 0;616}617k = token.p11.C_Decrypt(session.id(), 0, in, inOfs, inLen,6180, out, outOfs, outLen);619doCancel = false;620}621return k;622} catch (PKCS11Exception e) {623// As per the PKCS#11 standard, C_Encrypt and C_Decrypt may only624// keep the operation active on CKR_BUFFER_TOO_SMALL errors or625// successful calls to determine the output length. However,626// these cases are not expected here because the output length627// is checked in the OpenJDK side before making the PKCS#11 call.628// Thus, doCancel can safely be 'false'.629doCancel = false;630handleException(e);631throw new ProviderException("doFinal() failed", e);632} finally {633if (encrypt) {634lastEncKey = this.p11Key;635lastEncIv = this.iv;636requireReinit = true;637}638reset(doCancel);639}640}641642private int implDoFinal(ByteBuffer inBuffer, ByteBuffer outBuffer)643throws ShortBufferException, IllegalBlockSizeException,644BadPaddingException {645int outLen = outBuffer.remaining();646int inLen = inBuffer.remaining();647648int requiredOutLen = doFinalLength(inLen);649if (outLen < requiredOutLen) {650throw new ShortBufferException();651}652653boolean doCancel = true;654try {655ensureInitialized();656657long inAddr = 0;658byte[] in = null;659int inOfs = 0;660if (dataBuffer.size() > 0) {661if (inLen > 0) {662byte[] temp = new byte[inLen];663inBuffer.get(temp);664dataBuffer.write(temp, 0, temp.length);665}666in = dataBuffer.toByteArray();667inOfs = 0;668inLen = in.length;669} else {670if (inBuffer instanceof DirectBuffer) {671inAddr = ((DirectBuffer) inBuffer).address();672inOfs = inBuffer.position();673} else {674if (inBuffer.hasArray()) {675in = inBuffer.array();676inOfs = inBuffer.position() + inBuffer.arrayOffset();677} else {678in = new byte[inLen];679inBuffer.get(in);680}681}682}683long outAddr = 0;684byte[] outArray = null;685int outOfs = 0;686if (outBuffer instanceof DirectBuffer) {687outAddr = ((DirectBuffer) outBuffer).address();688outOfs = outBuffer.position();689} else {690if (outBuffer.hasArray()) {691outArray = outBuffer.array();692outOfs = outBuffer.position() + outBuffer.arrayOffset();693} else {694outArray = new byte[outLen];695}696}697698int k = 0;699if (encrypt) {700k = token.p11.C_Encrypt(session.id(), inAddr, in, inOfs, inLen,701outAddr, outArray, outOfs, outLen);702doCancel = false;703} else {704// Special handling to match SunJCE provider behavior705if (inLen == 0) {706return 0;707}708k = token.p11.C_Decrypt(session.id(), inAddr, in, inOfs, inLen,709outAddr, outArray, outOfs, outLen);710doCancel = false;711}712outBuffer.position(outBuffer.position() + k);713return k;714} catch (PKCS11Exception e) {715// As per the PKCS#11 standard, C_Encrypt and C_Decrypt may only716// keep the operation active on CKR_BUFFER_TOO_SMALL errors or717// successful calls to determine the output length. However,718// these cases are not expected here because the output length719// is checked in the OpenJDK side before making the PKCS#11 call.720// Thus, doCancel can safely be 'false'.721doCancel = false;722handleException(e);723throw new ProviderException("doFinal() failed", e);724} finally {725if (encrypt) {726lastEncKey = this.p11Key;727lastEncIv = this.iv;728requireReinit = true;729}730reset(doCancel);731}732}733734private void handleException(PKCS11Exception e)735throws ShortBufferException, IllegalBlockSizeException,736BadPaddingException {737long errorCode = e.getErrorCode();738if (errorCode == CKR_BUFFER_TOO_SMALL) {739throw (ShortBufferException)740(new ShortBufferException().initCause(e));741} else if (errorCode == CKR_DATA_LEN_RANGE ||742errorCode == CKR_ENCRYPTED_DATA_LEN_RANGE) {743throw (IllegalBlockSizeException)744(new IllegalBlockSizeException(e.toString()).initCause(e));745} else if (errorCode == CKR_ENCRYPTED_DATA_INVALID) {746throw (BadPaddingException)747(new BadPaddingException(e.toString()).initCause(e));748}749}750751// see JCE spec752protected byte[] engineWrap(Key key) throws IllegalBlockSizeException,753InvalidKeyException {754// XXX key wrapping755throw new UnsupportedOperationException("engineWrap()");756}757758// see JCE spec759protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,760int wrappedKeyType)761throws InvalidKeyException, NoSuchAlgorithmException {762// XXX key unwrapping763throw new UnsupportedOperationException("engineUnwrap()");764}765766// see JCE spec767@Override768protected int engineGetKeySize(Key key) throws InvalidKeyException {769int n = P11SecretKeyFactory.convertKey770(token, key, ALGO).length();771return n;772}773}774775776777