8240256: Better resource cleaning for SunPKCS11 Provider

Reviewed-by: valeriep
This commit is contained in:
Sean Coffey 2021-06-03 06:45:06 +00:00
parent 06f87cf441
commit bdeaeb47d0
11 changed files with 505 additions and 106 deletions

View File

@ -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")) {

View File

@ -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;
}
}

View File

@ -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<P11Key> {
private static ReferenceQueue<P11Key> refQueue =
new ReferenceQueue<P11Key>();
static ReferenceQueue<P11Key> refQueue = new ReferenceQueue<>();
private static Set<SessionKeyRef> refSet =
Collections.synchronizedSet(new HashSet<SessionKeyRef>());
static ReferenceQueue<P11Key> 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<P11Key> {
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) {

View File

@ -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<Session> {
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<Session> {
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<Session> {
final class SessionRef extends PhantomReference<Session>
implements Comparable<SessionRef> {
private static ReferenceQueue<Session> refQueue =
new ReferenceQueue<Session>();
static ReferenceQueue<Session> refQueue = new ReferenceQueue<>();
private static Set<SessionRef> refList =
Collections.synchronizedSortedSet(new TreeSet<SessionRef>());
static ReferenceQueue<Session> 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<Session>
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<Session>
if (token.isPresent(id)) {
token.p11.C_CloseSession(id);
}
} catch (PKCS11Exception e1) {
// ignore
} catch (ProviderException e2) {
} catch (PKCS11Exception | ProviderException e1) {
// ignore
} finally {
this.clear();

View File

@ -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<Session> 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);
}
}
}
}

View File

@ -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 <code>getName</code> 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();
}
}
}

View File

@ -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 {

View File

@ -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<Provider> 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

View File

@ -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

View File

@ -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<SunPKCS11>[] 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);
}
}
}

View File

@ -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