From bdeaeb47d0155b9f233274cff90334e8dd761aae Mon Sep 17 00:00:00 2001 From: Sean Coffey Date: Thu, 3 Jun 2021 06:45:06 +0000 Subject: [PATCH] 8240256: Better resource cleaning for SunPKCS11 Provider Reviewed-by: valeriep --- .../classes/sun/security/pkcs11/Config.java | 34 ++++- .../classes/sun/security/pkcs11/KeyCache.java | 6 +- .../classes/sun/security/pkcs11/P11Key.java | 46 +++--- .../classes/sun/security/pkcs11/Session.java | 64 ++++----- .../sun/security/pkcs11/SessionManager.java | 31 +++- .../sun/security/pkcs11/SunPKCS11.java | 94 +++++++++--- .../classes/sun/security/pkcs11/Token.java | 6 +- test/jdk/sun/security/pkcs11/PKCS11Test.java | 55 ++++--- .../pkcs11/Provider/MultipleLogins-nss.txt | 12 ++ .../pkcs11/Provider/MultipleLogins.java | 128 +++++++++++++++++ .../pkcs11/Provider/MultipleLogins.sh | 135 ++++++++++++++++++ 11 files changed, 505 insertions(+), 106 deletions(-) create mode 100644 test/jdk/sun/security/pkcs11/Provider/MultipleLogins-nss.txt create mode 100644 test/jdk/sun/security/pkcs11/Provider/MultipleLogins.java create mode 100644 test/jdk/sun/security/pkcs11/Provider/MultipleLogins.sh diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Config.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Config.java index bd3938a3bb6..6939c7be6f8 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Config.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Config.java @@ -144,7 +144,15 @@ final class Config { // how often to test for token insertion, if no token is present private int insertionCheckInterval = 2000; - // flag inidicating whether to omit the call to C_Initialize() + // short ms value to indicate how often native cleaner thread is called + private int resourceCleanerShortInterval = 2_000; + // long ms value to indicate how often native cleaner thread is called + private int resourceCleanerLongInterval = 60_000; + + // should Token be destroyed after logout() + private boolean destroyTokenAfterLogout; + + // flag indicating whether to omit the call to C_Initialize() // should be used only if we are running within a process that // has already called it (e.g. Plugin inside of Mozilla/NSS) private boolean omitInitialize = false; @@ -278,6 +286,18 @@ final class Config { return explicitCancel; } + boolean getDestroyTokenAfterLogout() { + return destroyTokenAfterLogout; + } + + int getResourceCleanerShortInterval() { + return resourceCleanerShortInterval; + } + + int getResourceCleanerLongInterval() { + return resourceCleanerLongInterval; + } + int getInsertionCheckInterval() { return insertionCheckInterval; } @@ -412,6 +432,18 @@ final class Config { if (insertionCheckInterval < 100) { throw excLine(word + " must be at least 100 ms"); } + } else if (word.equals("cleaner.shortInterval")) { + resourceCleanerShortInterval = parseIntegerEntry(word); + if (resourceCleanerShortInterval < 1_000) { + throw excLine(word + " must be at least 1000 ms"); + } + } else if (word.equals("cleaner.longInterval")) { + resourceCleanerLongInterval = parseIntegerEntry(word); + if (resourceCleanerLongInterval < 1_000) { + throw excLine(word + " must be at least 1000 ms"); + } + } else if (word.equals("destroyTokenAfterLogout")) { + destroyTokenAfterLogout = parseBooleanEntry(word); } else if (word.equals("showInfo")) { showInfo = parseBooleanEntry(word); } else if (word.equals("keyStoreCompatibilityMode")) { diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/KeyCache.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/KeyCache.java index 1687649e10e..7bfe427d070 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/KeyCache.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/KeyCache.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2011, 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 @@ -100,4 +100,8 @@ final class KeyCache { map.put(key, p11Key); } + synchronized void clear() { + strongCache.clear(); + cacheReference = null; + } } diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java index 227ab4f39a0..9b69072280e 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java @@ -48,7 +48,6 @@ import sun.security.pkcs11.wrapper.*; import static sun.security.pkcs11.TemplateManager.O_GENERATE; import static sun.security.pkcs11.wrapper.PKCS11Constants.*; -import sun.security.util.Debug; import sun.security.util.DerValue; import sun.security.util.Length; import sun.security.util.ECUtil; @@ -142,8 +141,8 @@ abstract class P11Key implements Key, Length { && tokenLabel[2] == 'S'); boolean extractKeyInfo = (!DISABLE_NATIVE_KEYS_EXTRACTION && isNSS && extractable && !tokenObject); - this.keyIDHolder = new NativeKeyHolder(this, keyID, session, extractKeyInfo, - tokenObject); + this.keyIDHolder = new NativeKeyHolder(this, keyID, session, + extractKeyInfo, tokenObject); } public long getKeyID() { @@ -166,6 +165,18 @@ abstract class P11Key implements Key, Length { return (b == null) ? null : b.clone(); } + // Called by the NativeResourceCleaner at specified intervals + // See NativeResourceCleaner for more information + static boolean drainRefQueue() { + boolean found = false; + SessionKeyRef next; + while ((next = (SessionKeyRef) SessionKeyRef.refQueue.poll()) != null) { + found = true; + next.dispose(); + } + return found; + } + abstract byte[] getEncodedInternal(); public boolean equals(Object obj) { @@ -882,7 +893,7 @@ abstract class P11Key implements Key, Length { return params; } public int hashCode() { - if (token.isValid() == false) { + if (!token.isValid()) { return 0; } fetchValues(); @@ -891,7 +902,7 @@ abstract class P11Key implements Key, Length { public boolean equals(Object obj) { if (this == obj) return true; // equals() should never throw exceptions - if (token.isValid() == false) { + if (!token.isValid()) { return false; } if (!(obj instanceof DHPrivateKey)) { @@ -1129,7 +1140,6 @@ abstract class P11Key implements Key, Length { } } } - final class NativeKeyHolder { private static long nativeKeyWrapperKeyID = 0; @@ -1254,6 +1264,7 @@ final class NativeKeyHolder { this.ref = new SessionKeyRef(p11Key, keyID, wrapperKeyUsed, keySession); } + this.nativeKeyInfo = ((ki == null || ki.length == 0)? null : ki); } @@ -1327,24 +1338,9 @@ final class NativeKeyHolder { * still use these keys during finalization such as SSLSocket. */ final class SessionKeyRef extends PhantomReference { - private static ReferenceQueue refQueue = - new ReferenceQueue(); + static ReferenceQueue refQueue = new ReferenceQueue<>(); private static Set refSet = - Collections.synchronizedSet(new HashSet()); - - static ReferenceQueue referenceQueue() { - return refQueue; - } - - private static void drainRefQueueBounded() { - while (true) { - SessionKeyRef next = (SessionKeyRef) refQueue.poll(); - if (next == null) { - break; - } - next.dispose(); - } - } + Collections.synchronizedSet(new HashSet<>()); // handle to the native key and the session it is generated under private long keyID; @@ -1355,13 +1351,13 @@ final class SessionKeyRef extends PhantomReference { Session session) { super(p11Key, refQueue); if (session == null) { - throw new ProviderException("key must be associated with a session"); + throw new ProviderException + ("key must be associated with a session"); } registerNativeKey(keyID, session); this.wrapperKeyUsed = wrapperKeyUsed; refSet.add(this); - drainRefQueueBounded(); } void registerNativeKey(long newKeyID, Session newSession) { diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Session.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Session.java index 5bf775dae71..dac4e5882c1 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Session.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Session.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2011, 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 @@ -82,10 +82,6 @@ final class Session implements Comparable { return currentTime - lastAccess < MAX_IDLE_TIME; } - long idInternal() { - return id; - } - long id() { if (token.isPresent(this.id) == false) { throw new ProviderException("Token has been removed"); @@ -112,15 +108,40 @@ final class Session implements Comparable { return createdObjects.get() != 0; } + // regular close which will not close sessions when there are objects(keys) + // still associated with them void close() { - if (hasObjects()) { + close(true); + } + + // forced close which will close sessions regardless if there are objects + // associated with them. Note that closing the sessions this way may + // lead to those associated objects(keys) un-usable. Thus should only be + // used for scenarios such as the token is about to be removed, etc. + void kill() { + close(false); + } + + private void close(boolean checkObjCtr) { + if (hasObjects() && checkObjCtr) { throw new ProviderException( - "Internal error: close session with active objects"); + "Internal error: close session with active objects"); } sessionRef.dispose(); } -} + // Called by the NativeResourceCleaner at specified intervals + // See NativeResourceCleaner for more information + static boolean drainRefQueue() { + boolean found = false; + SessionRef next; + while ((next = (SessionRef) SessionRef.refQueue.poll())!= null) { + found = true; + next.dispose(); + } + return found; + } +} /* * NOTE: Use PhantomReference here and not WeakReference * otherwise the sessions maybe closed before other objects @@ -129,27 +150,10 @@ final class Session implements Comparable { final class SessionRef extends PhantomReference implements Comparable { - private static ReferenceQueue refQueue = - new ReferenceQueue(); + static ReferenceQueue refQueue = new ReferenceQueue<>(); private static Set refList = - Collections.synchronizedSortedSet(new TreeSet()); - - static ReferenceQueue referenceQueue() { - return refQueue; - } - - static int totalCount() { - return refList.size(); - } - - private static void drainRefQueueBounded() { - while (true) { - SessionRef next = (SessionRef) refQueue.poll(); - if (next == null) break; - next.dispose(); - } - } + Collections.synchronizedSortedSet(new TreeSet<>()); // handle to the native session private long id; @@ -160,8 +164,6 @@ final class SessionRef extends PhantomReference this.id = id; this.token = token; refList.add(this); - // TBD: run at some interval and not every time? - drainRefQueueBounded(); } void dispose() { @@ -170,9 +172,7 @@ final class SessionRef extends PhantomReference if (token.isPresent(id)) { token.p11.C_CloseSession(id); } - } catch (PKCS11Exception e1) { - // ignore - } catch (ProviderException e2) { + } catch (PKCS11Exception | ProviderException e1) { // ignore } finally { this.clear(); diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SessionManager.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SessionManager.java index a95cf6de93d..bbb219bd3cf 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SessionManager.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SessionManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2016, 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 @@ -26,11 +26,9 @@ package sun.security.pkcs11; import java.util.*; - import java.security.ProviderException; import sun.security.util.Debug; - import sun.security.pkcs11.wrapper.*; import static sun.security.pkcs11.wrapper.PKCS11Constants.*; @@ -171,7 +169,9 @@ final class SessionManager { System.out.println("Killing session (" + location + ") active: " + activeSessions.get()); } - closeSession(session); + + session.kill(); + activeSessions.decrementAndGet(); return null; } @@ -179,7 +179,6 @@ final class SessionManager { if ((session == null) || (token.isValid() == false)) { return null; } - if (session.hasObjects()) { objSessions.release(session); } else { @@ -188,6 +187,11 @@ final class SessionManager { return null; } + void clearPools() { + objSessions.closeAll(); + opSessions.closeAll(); + } + void demoteObjSession(Session session) { if (token.isValid() == false) { return; @@ -196,6 +200,7 @@ final class SessionManager { System.out.println("Demoting session, active: " + activeSessions.get()); } + boolean present = objSessions.remove(session); if (present == false) { // session is currently in use @@ -238,6 +243,7 @@ final class SessionManager { private final SessionManager mgr; private final AbstractQueue pool; private final int SESSION_MAX = 5; + private volatile boolean closed = false; // Object session pools can contain unlimited sessions. // Operation session pools are limited and enforced by the queue. @@ -260,7 +266,7 @@ final class SessionManager { void release(Session session) { // Object session pools never return false, only Operation ones - if (!pool.offer(session)) { + if (closed || !pool.offer(session)) { mgr.closeSession(session); free(); } @@ -268,6 +274,9 @@ final class SessionManager { // Free any old operation session if this queue is full void free() { + // quick return path + if (pool.size() == 0) return; + int n = SESSION_MAX; int i = 0; Session oldestSession; @@ -291,6 +300,14 @@ final class SessionManager { } } + // empty out all sessions inside 'pool' and close them. + // however the Pool can still accept sessions + void closeAll() { + closed = true; + Session s; + while ((s = pool.poll()) != null) { + mgr.killSession(s); + } + } } - } diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java index 47ca4377b1e..8e257c0beea 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java @@ -38,9 +38,7 @@ import javax.security.auth.login.LoginException; import javax.security.auth.login.FailedLoginException; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.ConfirmationCallback; import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.TextOutputCallback; import com.sun.crypto.provider.ChaCha20Poly1305Parameters; @@ -88,6 +86,8 @@ public final class SunPKCS11 extends AuthProvider { private TokenPoller poller; + static NativeResourceCleaner cleaner; + Token getToken() { return token; } @@ -907,13 +907,19 @@ public final class SunPKCS11 extends AuthProvider { // background thread that periodically checks for token insertion // if no token is present. We need to do that in a separate thread because // the insertion check may block for quite a long time on some tokens. - private static class TokenPoller implements Runnable { + private static class TokenPoller extends Thread { private final SunPKCS11 provider; private volatile boolean enabled; + private TokenPoller(SunPKCS11 provider) { + super((ThreadGroup)null, "Poller-" + provider.getName()); + setContextClassLoader(null); + setDaemon(true); + setPriority(Thread.MIN_PRIORITY); this.provider = provider; enabled = true; } + @Override public void run() { int interval = provider.config.getInsertionCheckInterval(); while (enabled) { @@ -942,13 +948,8 @@ public final class SunPKCS11 extends AuthProvider { if (poller != null) { return; } - final TokenPoller poller = new TokenPoller(this); - Thread t = new Thread(null, poller, "Poller " + getName(), 0, false); - t.setContextClassLoader(null); - t.setDaemon(true); - t.setPriority(Thread.MIN_PRIORITY); - t.start(); - this.poller = poller; + poller = new TokenPoller(this); + poller.start(); } // destroy the poller thread, if active @@ -971,6 +972,56 @@ public final class SunPKCS11 extends AuthProvider { return (token != null) && token.isValid(); } + private class NativeResourceCleaner extends Thread { + private long sleepMillis = config.getResourceCleanerShortInterval(); + private int count = 0; + boolean keyRefFound, sessRefFound; + + private NativeResourceCleaner() { + super((ThreadGroup)null, "Cleanup-SunPKCS11"); + setContextClassLoader(null); + setDaemon(true); + setPriority(Thread.MIN_PRIORITY); + } + + /* + * The cleaner.shortInterval and cleaner.longInterval properties + * may be defined in the pkcs11 config file and are specified in milliseconds + * Minimum value is 1000ms. Default values : + * cleaner.shortInterval : 2000ms + * cleaner.longInterval : 60000ms + * + * The cleaner thread runs at cleaner.shortInterval intervals + * while P11Key or Session references continue to be found for cleaning. + * If 100 iterations occur with no references being found, then the interval + * period moves to cleaner.longInterval value. The cleaner thread moves back + * to short interval checking if a resource is found + */ + @Override + public void run() { + while (true) { + try { + sleep(sleepMillis); + } catch (InterruptedException ie) { + break; + } + keyRefFound = P11Key.drainRefQueue(); + sessRefFound = Session.drainRefQueue(); + if (!keyRefFound && !sessRefFound) { + count++; + if (count > 100) { + // no reference freed for some time + // increase the sleep time + sleepMillis = config.getResourceCleanerLongInterval(); + } + } else { + count = 0; + sleepMillis = config.getResourceCleanerShortInterval(); + } + } + } + } + // destroy the token. Called if we detect that it has been removed @SuppressWarnings("removal") synchronized void uninitToken(Token token) { @@ -987,7 +1038,10 @@ public final class SunPKCS11 extends AuthProvider { return null; } }); - createPoller(); + // keep polling for token insertion unless configured not to + if (removable && !config.getDestroyTokenAfterLogout()) { + createPoller(); + } } private static boolean isLegacy(CK_MECHANISM_INFO mechInfo) @@ -1135,6 +1189,10 @@ public final class SunPKCS11 extends AuthProvider { }); this.token = token; + if (cleaner == null) { + cleaner = new NativeResourceCleaner(); + cleaner.start(); + } } private static final class P11Service extends Service { @@ -1350,12 +1408,12 @@ public final class SunPKCS11 extends AuthProvider { ("authProvider." + this.getName())); } - if (hasValidToken() == false) { + if (!hasValidToken()) { throw new LoginException("No token present"); + } // see if a login is required - if ((token.tokenInfo.flags & CKF_LOGIN_REQUIRED) == 0) { if (debug != null) { debug.println("login operation not required for token - " + @@ -1467,7 +1525,6 @@ public final class SunPKCS11 extends AuthProvider { * this provider's getName method */ public void logout() throws LoginException { - if (!isConfigured()) { throw new IllegalStateException("Configuration is required"); } @@ -1494,10 +1551,13 @@ public final class SunPKCS11 extends AuthProvider { } try { - if (token.isLoggedInNow(null) == false) { + if (!token.isLoggedInNow(null)) { if (debug != null) { debug.println("user not logged in"); } + if (config.getDestroyTokenAfterLogout()) { + token.destroy(); + } return; } } catch (PKCS11Exception e) { @@ -1505,7 +1565,6 @@ public final class SunPKCS11 extends AuthProvider { } // perform token logout - Session session = null; try { session = token.getOpSession(); @@ -1526,6 +1585,9 @@ public final class SunPKCS11 extends AuthProvider { throw le; } finally { token.releaseSession(session); + if (config.getDestroyTokenAfterLogout()) { + token.destroy(); + } } } diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Token.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Token.java index 25b43aa68cc..9858a5faedf 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Token.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Token.java @@ -292,8 +292,12 @@ class Token implements Serializable { } void destroy() { - valid = false; + secretCache.clear(); + privateCache.clear(); + + sessionManager.clearPools(); provider.uninitToken(this); + valid = false; } Session getObjSession() throws PKCS11Exception { diff --git a/test/jdk/sun/security/pkcs11/PKCS11Test.java b/test/jdk/sun/security/pkcs11/PKCS11Test.java index d7ed22d0a2b..3f746c0d9f6 100644 --- a/test/jdk/sun/security/pkcs11/PKCS11Test.java +++ b/test/jdk/sun/security/pkcs11/PKCS11Test.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 @@ -21,15 +21,12 @@ * questions. */ - // common infrastructure for SunPKCS11 tests -import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -110,11 +107,9 @@ public abstract class PKCS11Test { // for quick checking for generic testing than many if-else statements. static double softoken3_version = -1; static double nss3_version = -1; - static Provider pkcs11; + static Provider pkcs11 = newPKCS11Provider(); - // Goes through ServiceLoader instead of Provider.getInstance() since it - // works on all platforms - static { + public static Provider newPKCS11Provider() { ServiceLoader sl = ServiceLoader.load(java.security.Provider.class); Iterator iter = sl.iterator(); Provider p = null; @@ -139,15 +134,20 @@ public abstract class PKCS11Test { ex.printStackTrace(); } } - pkcs11 = p; + return p; } - // Return a SunPKCS11 provider configured with the specified config file + // Return the static test SunPKCS11 provider configured with the specified config file static Provider getSunPKCS11(String config) throws Exception { - if (pkcs11 == null) { + return getSunPKCS11(config, pkcs11); + } + + // Return the Provider p configured with the specified config file + static Provider getSunPKCS11(String config, Provider p) throws Exception { + if (p == null) { throw new NoSuchProviderException("No PKCS11 provider available"); } - return pkcs11.configure(config); + return p.configure(config); } public abstract void main(Provider p) throws Exception; @@ -539,36 +539,45 @@ public abstract class PKCS11Test { nss_library = "nss3"; } + // Run NSS testing on a Provider p configured with test nss config public static void testNSS(PKCS11Test test) throws Exception { + String nssConfig = getNssConfig(); + if (nssConfig == null) { + // issue loading libraries + return; + } + Provider p = getSunPKCS11(nssConfig); + test.premain(p); + } + + public static String getNssConfig() throws Exception { String libdir = getNSSLibDir(); if (libdir == null) { - return; + return null; } - String base = getBase(); if (loadNSPR(libdir) == false) { - return; + return null; } + String base = getBase(); + String libfile = libdir + System.mapLibraryName(nss_library); String customDBdir = System.getProperty("CUSTOM_DB_DIR"); String dbdir = (customDBdir != null) ? - customDBdir : - base + SEP + "nss" + SEP + "db"; + customDBdir : + base + SEP + "nss" + SEP + "db"; // NSS always wants forward slashes for the config path dbdir = dbdir.replace('\\', '/'); String customConfig = System.getProperty("CUSTOM_P11_CONFIG"); String customConfigName = System.getProperty("CUSTOM_P11_CONFIG_NAME", "p11-nss.txt"); - String p11config = (customConfig != null) ? - customConfig : - base + SEP + "nss" + SEP + customConfigName; - System.setProperty("pkcs11test.nss.lib", libfile); System.setProperty("pkcs11test.nss.db", dbdir); - Provider p = getSunPKCS11(p11config); - test.premain(p); + return (customConfig != null) ? + customConfig : + base + SEP + "nss" + SEP + customConfigName; } // Generate a vector of supported elliptic curves of a given provider diff --git a/test/jdk/sun/security/pkcs11/Provider/MultipleLogins-nss.txt b/test/jdk/sun/security/pkcs11/Provider/MultipleLogins-nss.txt new file mode 100644 index 00000000000..a7acf0b3be8 --- /dev/null +++ b/test/jdk/sun/security/pkcs11/Provider/MultipleLogins-nss.txt @@ -0,0 +1,12 @@ +name = NSS + +slot = 2 + +library = ${pkcs11test.nss.lib} + +nssArgs = "configdir='${pkcs11test.nss.db}' certPrefix='' keyPrefix='' secmod='secmod.db' flags=readOnly" + +destroyTokenAfterLogout = true +cleaner.longInterval = 10000 +cleaner.shortInterval = 1000 + diff --git a/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.java b/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.java new file mode 100644 index 00000000000..dc3b657dc27 --- /dev/null +++ b/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.java @@ -0,0 +1,128 @@ +/* + * 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 + * 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. + */ + +import sun.security.pkcs11.SunPKCS11; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginException; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.security.KeyStore; +import java.security.Provider; +import java.security.Security; +import java.util.Iterator; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; + +import jdk.test.lib.util.ForceGC; + +public class MultipleLogins { + private static final String KS_TYPE = "PKCS11"; + private static final int NUM_PROVIDERS = 20; + private static final SunPKCS11[] providers = new SunPKCS11[NUM_PROVIDERS]; + + + public static void main(String[] args) throws Exception { + for (int i =0; i < NUM_PROVIDERS; i++) { + String nssConfig = PKCS11Test.getNssConfig(); + if (nssConfig == null) { + throw new RuntimeException("issue setting up config"); + } + providers[i] = + (SunPKCS11)PKCS11Test.newPKCS11Provider() + .configure(nssConfig); + Security.addProvider(providers[i]); + test(providers[i]); + } + + WeakReference[] weakRef = new WeakReference[NUM_PROVIDERS]; + for (int i =0; i < NUM_PROVIDERS; i++) { + weakRef[i] = new WeakReference<>(providers[i]); + providers[i].logout(); + + if (i == 0) { + // one provider stays for use with clean up thread + continue; + } + + try { + providers[i].login(new Subject(), new PasswordCallbackHandler()); + throw new RuntimeException("Expected LoginException"); + } catch (LoginException le) { + // expected + } + + Security.removeProvider(providers[i].getName()); + providers[i] = null; + + ForceGC gc = new ForceGC(); + int finalI = i; + gc.await(() -> weakRef[finalI].get() == null); + if (!weakRef[i].refersTo(null)) { + throw new RuntimeException("Expected SunPKCS11 Provider to be GC'ed.."); + } + } + } + + private static void test(SunPKCS11 p) throws Exception { + KeyStore ks = KeyStore.getInstance(KS_TYPE, p); + + p.setCallbackHandler(new PasswordCallbackHandler()); + try { + ks.load(null, (char[]) null); + } catch (IOException e) { + if (!e.getMessage().contains("load failed")) { + // we expect the keystore load to fail + throw new RuntimeException("unexpected exception", e); + } + } + + p.logout(); + + try { + ks.load(null, (char[]) null); + } catch (IOException e) { + if (e.getCause() instanceof LoginException && + e.getCause().getMessage().contains("No token present")) { + // expected + } else { + throw new RuntimeException("Token was present", e); + } + } + } + + public static class PasswordCallbackHandler implements CallbackHandler { + public void handle(Callback[] callbacks) + throws IOException, UnsupportedCallbackException { + if (!(callbacks[0] instanceof PasswordCallback)) { + throw new UnsupportedCallbackException(callbacks[0]); + } + PasswordCallback pc = (PasswordCallback)callbacks[0]; + pc.setPassword(null); + } + } +} diff --git a/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.sh b/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.sh new file mode 100644 index 00000000000..8077bbb0d4f --- /dev/null +++ b/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.sh @@ -0,0 +1,135 @@ +# +# Copyright (c) 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 +# 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 7777777 +# @summary +# @library /test/lib/ +# @build jdk.test.lib.util.ForceGC +# @run shell MultipleLogins.sh + +# set a few environment variables so that the shell-script can run stand-alone +# in the source directory + +# if running by hand on windows, change TESTSRC and TESTCLASSES to "." +if [ "${TESTSRC}" = "" ] ; then + TESTSRC=`pwd` +fi +if [ "${TESTCLASSES}" = "" ] ; then + TESTCLASSES=`pwd` +fi + +if [ "${TESTCLASSPATH}" = "" ] ; then + TESTCLASSPATH=`pwd` +fi + +if [ "${COMPILEJAVA}" = "" ]; then + COMPILEJAVA="${TESTJAVA}" +fi +echo TESTSRC=${TESTSRC} +echo TESTCLASSES=${TESTCLASSES} +echo TESTJAVA=${TESTJAVA} +echo COMPILEJAVA=${COMPILEJAVA} +echo "" + +# let java test exit if platform unsupported + +OS=`uname -s` +case "$OS" in + Linux ) + FS="/" + PS=":" + CP="${FS}bin${FS}cp" + CHMOD="${FS}bin${FS}chmod" + ;; + Darwin ) + FS="/" + PS=":" + CP="${FS}bin${FS}cp" + CHMOD="${FS}bin${FS}chmod" + ;; + AIX ) + FS="/" + PS=":" + CP="${FS}bin${FS}cp" + CHMOD="${FS}bin${FS}chmod" + ;; + Windows* ) + FS="\\" + PS=";" + CP="cp" + CHMOD="chmod" + ;; + CYGWIN* ) + FS="/" + PS=";" + CP="cp" + CHMOD="chmod" + # + # javac does not like /cygdrive produced by `pwd` + # + TESTSRC=`cygpath -d ${TESTSRC}` + ;; + * ) + echo "Unrecognized system!" + exit 1; + ;; +esac + +# first make cert/key DBs writable + +${CP} ${TESTSRC}${FS}..${FS}nss${FS}db${FS}cert8.db ${TESTCLASSES} +${CHMOD} +w ${TESTCLASSES}${FS}cert8.db + +${CP} ${TESTSRC}${FS}..${FS}nss${FS}db${FS}key3.db ${TESTCLASSES} +${CHMOD} +w ${TESTCLASSES}${FS}key3.db + +# compile test +${COMPILEJAVA}${FS}bin${FS}javac ${TESTJAVACOPTS} ${TESTTOOLVMOPTS} \ + -classpath ${TESTCLASSPATH} \ + -d ${TESTCLASSES} \ + --add-modules jdk.crypto.cryptoki \ + --add-exports jdk.crypto.cryptoki/sun.security.pkcs11=ALL-UNNAMED \ + ${TESTSRC}${FS}..${FS}..${FS}..${FS}..${FS}..${FS}lib${FS}jdk${FS}test${FS}lib${FS}artifacts${FS}*.java \ + ${TESTSRC}${FS}MultipleLogins.java \ + ${TESTSRC}${FS}..${FS}PKCS11Test.java + +# run test +${TESTJAVA}${FS}bin${FS}java ${TESTVMOPTS} \ + -classpath ${TESTCLASSPATH} \ + --add-modules jdk.crypto.cryptoki \ + --add-exports jdk.crypto.cryptoki/sun.security.pkcs11=ALL-UNNAMED \ + -DCUSTOM_DB_DIR=${TESTCLASSES} \ + -DCUSTOM_P11_CONFIG=${TESTSRC}${FS}MultipleLogins-nss.txt \ + -DNO_DEFAULT=true \ + -DNO_DEIMOS=true \ + -Dtest.src=${TESTSRC} \ + -Dtest.classes=${TESTCLASSES} \ + -Djava.security.debug=${DEBUG} \ + MultipleLogins + +# save error status +status=$? + +# return +exit $status