Mercurial > hg > openjdk > jdk8u > jdk
changeset 14353:27b6d9a2b8a3
8258833: Cancel multi-part cipher operations in SunPKCS11 after failures
Reviewed-by: valeriep, phh, andrew
author | mbalao |
---|---|
date | Mon, 25 Jan 2021 15:01:59 -0300 |
parents | fa5b43603cf2 |
children | 39e8ea32b425 |
files | src/share/classes/sun/security/pkcs11/P11AEADCipher.java src/share/classes/sun/security/pkcs11/P11Cipher.java src/share/classes/sun/security/pkcs11/P11Mac.java src/share/classes/sun/security/pkcs11/P11PSSSignature.java src/share/classes/sun/security/pkcs11/P11Signature.java test/sun/security/pkcs11/Cipher/CancelMultipart.java |
diffstat | 6 files changed, 292 insertions(+), 15 deletions(-) [+] |
line wrap: on
line diff
--- a/src/share/classes/sun/security/pkcs11/P11AEADCipher.java Thu Apr 30 14:04:39 2015 +0400 +++ b/src/share/classes/sun/security/pkcs11/P11AEADCipher.java Mon Jan 25 15:01:59 2021 -0300 @@ -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 @@ -347,6 +347,13 @@ 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); } @@ -614,6 +621,12 @@ } 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); @@ -700,6 +713,12 @@ 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);
--- a/src/share/classes/sun/security/pkcs11/P11Cipher.java Thu Apr 30 14:04:39 2015 +0400 +++ b/src/share/classes/sun/security/pkcs11/P11Cipher.java Mon Jan 25 15:01:59 2021 -0300 @@ -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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } return k; } catch (PKCS11Exception e) { - doCancel = false; handleException(e); throw new ProviderException("doFinal() failed", e); } finally {
--- a/src/share/classes/sun/security/pkcs11/P11Mac.java Thu Apr 30 14:04:39 2015 +0400 +++ b/src/share/classes/sun/security/pkcs11/P11Mac.java Mon Jan 25 15:01:59 2021 -0300 @@ -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 @@ -144,6 +144,13 @@ 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); } } @@ -206,6 +213,12 @@ 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);
--- a/src/share/classes/sun/security/pkcs11/P11PSSSignature.java Thu Apr 30 14:04:39 2015 +0400 +++ b/src/share/classes/sun/security/pkcs11/P11PSSSignature.java Mon Jan 25 15:01:59 2021 -0300 @@ -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 @@ -271,6 +271,13 @@ } } } 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); } @@ -594,6 +601,11 @@ 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) {
--- a/src/share/classes/sun/security/pkcs11/P11Signature.java Thu Apr 30 14:04:39 2015 +0400 +++ b/src/share/classes/sun/security/pkcs11/P11Signature.java Mon Jan 25 15:01:59 2021 -0300 @@ -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 @@ -297,6 +297,13 @@ } } } 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) || @@ -637,6 +644,11 @@ return signature; } } catch (PKCS11Exception e) { + // 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(e); } finally {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/sun/security/pkcs11/Cipher/CancelMultipart.java Mon Jan 25 15:01:59 2021 -0300 @@ -0,0 +1,188 @@ +/* + * 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 /lib/security .. + * @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); + } +}