diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11AEADCipher.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11AEADCipher.java index 82d0dc164f4..7913d755d4e 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11AEADCipher.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11AEADCipher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2021, 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 @@ -350,6 +350,13 @@ final class P11AEADCipher extends CipherSpi { 0, buffer, 0, bufLen); } } catch (PKCS11Exception e) { + if (e.getErrorCode() == 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; + } if (encrypt) { throw new ProviderException("Cancel failed", e); } @@ -616,6 +623,12 @@ final class P11AEADCipher extends CipherSpi { } return 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; handleException(e); throw new ProviderException("doFinal() failed", e); @@ -702,6 +715,12 @@ final class P11AEADCipher extends CipherSpi { outBuffer.position(outBuffer.position() + k); return 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; handleException(e); throw new ProviderException("doFinal() failed", e); diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java index 470a888cd84..362d46733dc 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2021, 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 @@ -439,6 +439,13 @@ final class P11Cipher extends CipherSpi { token.p11.C_DecryptFinal(session.id(), 0, buffer, 0, bufLen); } } catch (PKCS11Exception e) { + if (e.getErrorCode() == 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; + } if (encrypt) { throw new ProviderException("Cancel failed", e); } @@ -628,7 +635,11 @@ final class P11Cipher extends CipherSpi { throw (ShortBufferException) (new ShortBufferException().initCause(e)); } - reset(false); + // Some implementations such as the NSS Software Token do not + // cancel the operation upon a C_EncryptUpdate/C_DecryptUpdate + // failure (as required by the PKCS#11 standard). See JDK-8258833 + // for further information. + reset(true); throw new ProviderException("update() failed", e); } } @@ -746,7 +757,11 @@ final class P11Cipher extends CipherSpi { throw (ShortBufferException) (new ShortBufferException().initCause(e)); } - reset(false); + // Some implementations such as the NSS Software Token do not + // cancel the operation upon a C_EncryptUpdate/C_DecryptUpdate + // failure (as required by the PKCS#11 standard). See JDK-8258833 + // for further information. + reset(true); throw new ProviderException("update() failed", e); } } @@ -770,9 +785,14 @@ final class P11Cipher extends CipherSpi { 0, padBuffer, 0, actualPadLen, 0, out, outOfs, outLen); } + // Some implementations such as the NSS Software Token do not + // cancel the operation upon a C_EncryptUpdate failure (as + // required by the PKCS#11 standard). Cancel is not needed + // only after this point. See JDK-8258833 for further + // information. + doCancel = false; k += token.p11.C_EncryptFinal(session.id(), 0, out, (outOfs + k), (outLen - k)); - doCancel = false; } else { // Special handling to match SunJCE provider behavior if (bytesBuffered == 0 && padBufferLen == 0) { @@ -784,22 +804,26 @@ final class P11Cipher extends CipherSpi { padBuffer, 0, padBufferLen, 0, padBuffer, 0, padBuffer.length); } + // Some implementations such as the NSS Software Token do not + // cancel the operation upon a C_DecryptUpdate failure (as + // required by the PKCS#11 standard). Cancel is not needed + // only after this point. See JDK-8258833 for further + // information. + doCancel = false; k += token.p11.C_DecryptFinal(session.id(), 0, padBuffer, k, padBuffer.length - k); - doCancel = false; int actualPadLen = paddingObj.unpad(padBuffer, k); k -= actualPadLen; System.arraycopy(padBuffer, 0, out, outOfs, k); } else { + doCancel = false; k = token.p11.C_DecryptFinal(session.id(), 0, out, outOfs, outLen); - doCancel = false; } } return k; } catch (PKCS11Exception e) { - doCancel = false; handleException(e); throw new ProviderException("doFinal() failed", e); } finally { @@ -845,9 +869,14 @@ final class P11Cipher extends CipherSpi { 0, padBuffer, 0, actualPadLen, outAddr, outArray, outOfs, outLen); } + // Some implementations such as the NSS Software Token do not + // cancel the operation upon a C_EncryptUpdate failure (as + // required by the PKCS#11 standard). Cancel is not needed + // only after this point. See JDK-8258833 for further + // information. + doCancel = false; k += token.p11.C_EncryptFinal(session.id(), outAddr, outArray, (outOfs + k), (outLen - k)); - doCancel = false; } else { // Special handling to match SunJCE provider behavior if (bytesBuffered == 0 && padBufferLen == 0) { @@ -861,18 +890,23 @@ final class P11Cipher extends CipherSpi { 0, padBuffer, 0, padBuffer.length); padBufferLen = 0; } + // Some implementations such as the NSS Software Token do not + // cancel the operation upon a C_DecryptUpdate failure (as + // required by the PKCS#11 standard). Cancel is not needed + // only after this point. See JDK-8258833 for further + // information. + doCancel = false; k += token.p11.C_DecryptFinal(session.id(), 0, padBuffer, k, padBuffer.length - k); - doCancel = false; int actualPadLen = paddingObj.unpad(padBuffer, k); k -= actualPadLen; outArray = padBuffer; outOfs = 0; } else { + doCancel = false; k = token.p11.C_DecryptFinal(session.id(), outAddr, outArray, outOfs, outLen); - doCancel = false; } } if ((!encrypt && paddingObj != null) || @@ -884,7 +918,6 @@ final class P11Cipher extends CipherSpi { } return k; } catch (PKCS11Exception e) { - doCancel = false; handleException(e); throw new ProviderException("doFinal() failed", e); } finally { diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Mac.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Mac.java index 0671ce9dc40..29b26651c39 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Mac.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Mac.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2021, 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 @@ -151,6 +151,13 @@ final class P11Mac extends MacSpi { try { token.p11.C_SignFinal(session.id(), 0); } catch (PKCS11Exception e) { + if (e.getErrorCode() == 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; + } throw new ProviderException("Cancel failed", e); } } @@ -213,6 +220,12 @@ final class P11Mac extends MacSpi { ensureInitialized(); return token.p11.C_SignFinal(session.id(), 0); } catch (PKCS11Exception e) { + // As per the PKCS#11 standard, C_SignFinal may only + // keep the operation active on CKR_BUFFER_TOO_SMALL errors or + // successful calls to determine the output length. However, + // these cases are handled at OpenJDK's libj2pkcs11 native + // library. Thus, P11Mac::reset can be called with a 'false' + // doCancel argument from here. throw new ProviderException("doFinal() failed", e); } finally { reset(false); diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11PSSSignature.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11PSSSignature.java index 14b6110dfb3..82214352668 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11PSSSignature.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11PSSSignature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2021, 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 @@ -298,6 +298,13 @@ final class P11PSSSignature extends SignatureSpi { } } } catch (PKCS11Exception e) { + if (e.getErrorCode() == 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; + } if (mode == M_SIGN) { throw new ProviderException("cancel failed", e); } @@ -662,6 +669,11 @@ final class P11PSSSignature extends SignatureSpi { doCancel = false; return signature; } catch (PKCS11Exception pe) { + // As per the PKCS#11 standard, C_Sign and C_SignFinal may only + // keep the operation active on CKR_BUFFER_TOO_SMALL errors or + // successful calls to determine the output length. However, + // these cases are handled at OpenJDK's libj2pkcs11 native + // library. Thus, doCancel can safely be 'false' here. doCancel = false; throw new ProviderException(pe); } catch (ProviderException e) { diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Signature.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Signature.java index c8616e26e49..f1366b46a81 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Signature.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Signature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2021, 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 @@ -314,6 +314,13 @@ final class P11Signature extends SignatureSpi { } } } catch (PKCS11Exception e) { + if (e.getErrorCode() == 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; + } if (mode == M_VERIFY) { long errorCode = e.getErrorCode(); if ((errorCode == CKR_SIGNATURE_INVALID) || @@ -654,6 +661,11 @@ final class P11Signature extends SignatureSpi { } } } catch (PKCS11Exception pe) { + // As per the PKCS#11 standard, C_Sign and C_SignFinal may only + // keep the operation active on CKR_BUFFER_TOO_SMALL errors or + // successful calls to determine the output length. However, + // these cases are handled at OpenJDK's libj2pkcs11 native + // library. Thus, doCancel can safely be 'false' here. doCancel = false; throw new ProviderException(pe); } catch (SignatureException | ProviderException e) { diff --git a/test/jdk/sun/security/pkcs11/Cipher/CancelMultipart.java b/test/jdk/sun/security/pkcs11/Cipher/CancelMultipart.java new file mode 100644 index 00000000000..28f3699050c --- /dev/null +++ b/test/jdk/sun/security/pkcs11/Cipher/CancelMultipart.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2021, Red Hat, Inc. + * 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. + * + * 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. + */ + +/* + * @test + * @bug 8258833 + * @library /test/lib .. + * @modules jdk.crypto.cryptoki/sun.security.pkcs11:open + * @run main/othervm CancelMultipart + */ + +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.security.Key; +import java.security.Provider; +import java.security.ProviderException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.spec.SecretKeySpec; + +public class CancelMultipart extends PKCS11Test { + + private static Provider provider; + private static Key key; + + static { + key = new SecretKeySpec(new byte[16], "AES"); + } + + private static class SessionLeaker { + private LeakOperation op; + private LeakInputType type; + + SessionLeaker(LeakOperation op, LeakInputType type) { + this.op = op; + this.type = type; + } + + private void leakAndTry() throws Exception { + Cipher cipher = op.getCipher(); + try { + type.doOperation(cipher, + (op instanceof LeakDecrypt ? + LeakInputType.DECRYPT_MODE : + null)); + throw new Exception("PKCS11Exception expected, invalid block" + + "size"); + } catch (ProviderException | IllegalBlockSizeException e) { + // Exception expected - session returned to the SessionManager + // should be cancelled. That's what will be tested now. + } + + tryCipherInit(); + } + } + + private static interface LeakOperation { + Cipher getCipher() throws Exception; + } + + private static interface LeakInputType { + static int DECRYPT_MODE = 1; + void doOperation(Cipher cipher, int mode) throws Exception; + } + + private static class LeakDecrypt implements LeakOperation { + public Cipher getCipher() throws Exception { + Cipher cipher = Cipher.getInstance( + "AES/ECB/PKCS5Padding", provider); + cipher.init(Cipher.DECRYPT_MODE, key); + return cipher; + } + } + + private static class LeakByteBuffer implements LeakInputType { + public void doOperation(Cipher cipher, int mode) throws Exception { + if (mode == DECRYPT_MODE) { + cipher.update(ByteBuffer.allocate(1), ByteBuffer.allocate(1)); + cipher.doFinal(ByteBuffer.allocate(0), ByteBuffer.allocate(1)); + } + } + } + + private static class LeakByteArray implements LeakInputType { + public void doOperation(Cipher cipher, int mode) throws Exception { + if (mode == DECRYPT_MODE) { + cipher.update(new byte[1]); + cipher.doFinal(new byte[1], 0, 0); + } + } + } + + public static void main(String[] args) throws Exception { + main(new CancelMultipart(), args); + } + + @Override + public void main(Provider p) throws Exception { + init(p); + + // Try multiple paths: + + executeTest(new SessionLeaker(new LeakDecrypt(), new LeakByteArray()), + "P11Cipher::implDoFinal(byte[], int, int)"); + + executeTest(new SessionLeaker(new LeakDecrypt(), new LeakByteBuffer()), + "P11Cipher::implDoFinal(ByteBuffer)"); + + System.out.println("TEST PASS - OK"); + } + + private static void executeTest(SessionLeaker sl, String testName) + throws Exception { + try { + sl.leakAndTry(); + System.out.println(testName + ": OK"); + } catch (Exception e) { + System.out.println(testName + ": FAILED"); + throw e; + } + } + + private static void init(Provider p) throws Exception { + provider = p; + + // The max number of sessions is 2 because, in addition to the + // operation (i.e. PKCS11::getNativeKeyInfo), a session to hold + // the P11Key object is needed. + setMaxSessions(2); + } + + /* + * This method is intended to generate pression on the number of sessions + * to be used from the NSS Software Token, so sessions with (potentially) + * active operations are reused. + */ + private static void setMaxSessions(int maxSessions) throws Exception { + Field tokenField = Class.forName("sun.security.pkcs11.SunPKCS11") + .getDeclaredField("token"); + tokenField.setAccessible(true); + Field sessionManagerField = Class.forName("sun.security.pkcs11.Token") + .getDeclaredField("sessionManager"); + sessionManagerField.setAccessible(true); + Field maxSessionsField = Class.forName("sun.security.pkcs11.SessionManager") + .getDeclaredField("maxSessions"); + maxSessionsField.setAccessible(true); + Object sessionManagerObj = sessionManagerField.get( + tokenField.get(provider)); + maxSessionsField.setInt(sessionManagerObj, maxSessions); + } + + private static void tryCipherInit() throws Exception { + Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", provider); + + // A CKR_OPERATION_ACTIVE error may be thrown if a session was + // returned to the Session Manager with an active operation, and + // we try to initialize the Cipher using it. + // + // Given that the maximum number of sessions was forced to 2, we know + // that the session to be used here was already used in a previous + // (failed) operation. Thus, the test asserts that the operation was + // properly cancelled. + cipher.init(Cipher.ENCRYPT_MODE, key); + + // If initialization passes, finish gracefully so other paths can + // be tested under the current maximum number of sessions. + cipher.doFinal(new byte[16], 0, 0); + } +}