JDK-8134232 : KeyStore.load() throws an IOException with a wrong cause in case of wrong password
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.crypto:pkcs11
  • Affected Version: 9
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2015-08-21
  • Updated: 2016-11-18
  • Resolved: 2015-09-14
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u112Fixed 9 b83Fixed
Related Reports
Relates :  
Relates :  
Description
According to [1], KeyStore.load(InputStream, char[]) method should throw an IOException, and the cause of the IOException should be an UnrecoverableKeyException:

...
Throws:
IOException - if there is an I/O or format problem with the keystore data, if a password is required but not given, or if the given password was incorrect. If the error is due to a wrong password, the cause of the IOException should be an UnrecoverableKeyException
...

But in case of PKCS11 keystore it throws an IOException, and the cause is javax.security.auth.login.FailedLoginException:

java.io.IOException: load failed
	at sun.security.pkcs11.P11KeyStore.engineLoad(P11KeyStore.java:761)
	at java.security.KeyStore.load(KeyStore.java:1459)
	at LoadKeystore.main(LoadKeystore.java:73)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:504)
	at com.sun.javatest.regtest.agent.MainWrapper$MainThread.run(MainWrapper.java:92)
	at java.lang.Thread.run(Thread.java:746)
Caused by: javax.security.auth.login.FailedLoginException
	at sun.security.pkcs11.SunPKCS11.login(SunPKCS11.java:1253)
	at sun.security.pkcs11.P11KeyStore.login(P11KeyStore.java:847)
	at sun.security.pkcs11.P11KeyStore.engineLoad(P11KeyStore.java:751)
	... 8 more
Caused by: sun.security.pkcs11.wrapper.PKCS11Exception: CKR_PIN_INCORRECT
	at sun.security.pkcs11.wrapper.PKCS11.C_Login(Native Method)
	at sun.security.pkcs11.SunPKCS11.login(SunPKCS11.java:1241)
	... 10 more

Please use attached LoadKeystore.java test to reproduce the problem.

The implementation should follow the spec in all cases. Need to check other keystore types listed in [2].

[1] http://docs.oracle.com/javase/8/docs/api/java/security/KeyStore.html#load-java.io.InputStream-char:A-
[2] http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore
Comments
Code review: http://mail.openjdk.java.net/pipermail/security-dev/2015-September/012789.html
11-09-2015

DKS keystore throws an IOException, and there is an UnrecoverableKeyException in stack trace: java.io.IOException: java.security.KeyStoreException: KeyStore instantiation failed at sun.security.provider.DomainKeyStore.engineLoad(DomainKeyStore.java:747) at sun.security.provider.DomainKeyStore$DKS.engineLoad(DomainKeyStore.java:68) at java.security.KeyStore.load(KeyStore.java:1493) at DKSTest.main(DKSTest.java:83) Caused by: java.security.KeyStoreException: KeyStore instantiation failed at java.security.KeyStore$Builder$FileBuilder.getKeyStore(KeyStore.java:2109) at sun.security.provider.DomainKeyStore.engineLoad(DomainKeyStore.java:739) ... 3 more Caused by: java.io.IOException: Keystore was tampered with, or password was incorrect at sun.security.provider.JavaKeyStore.engineLoad(JavaKeyStore.java:780) at java.security.KeyStore.load(KeyStore.java:1459) at java.security.KeyStore$Builder$FileBuilder$1.run0(KeyStore.java:2098) at java.security.KeyStore$Builder$FileBuilder$1.run(KeyStore.java:2042) at java.security.KeyStore$Builder$FileBuilder$1.run(KeyStore.java:2039) at java.security.AccessController.doPrivileged(Native Method) at java.security.KeyStore$Builder$FileBuilder.getKeyStore(KeyStore.java:2105) ... 4 more Caused by: java.security.UnrecoverableKeyException: Password verification failed at sun.security.provider.JavaKeyStore.engineLoad(JavaKeyStore.java:778) ... 10 more
21-08-2015

JCEKS keystore doesn't use UnrecoverableKeyException as well: java.io.IOException: Keystore was tampered with, or password was incorrect at com.sun.crypto.provider.JceKeyStore.engineLoad(JceKeyStore.java:865) at java.security.KeyStore.load(KeyStore.java:1459) at LoadKeystore.runTest(LoadKeystore.java:58) at LoadKeystore.main(LoadKeystore.java:43) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:504) at com.sun.javatest.regtest.agent.MainWrapper$MainThread.run(MainWrapper.java:92) at java.lang.Thread.run(Thread.java:746)
21-08-2015

The patch above sets an UnrecoverableKeyException as a cause of IOException only if an error code is CKR_PIN_INCORRECT. I am not sure, but maybe some other error codes should be checked here, see PKCS11Constants.java: http://hg.openjdk.java.net/jdk9/dev/jdk/file/d99c2ffdd0f1/src/jdk.crypto.pkcs11/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java ... public static final long CKR_PIN_INCORRECT = 0x000000A0L; public static final long CKR_PIN_INVALID = 0x000000A1L; public static final long CKR_PIN_LEN_RANGE = 0x000000A2L; /* CKR_PIN_EXPIRED and CKR_PIN_LOCKED are new for v2.0 */ public static final long CKR_PIN_EXPIRED = 0x000000A3L; public static final long CKR_PIN_LOCKED = 0x000000A4L; ...
21-08-2015

sun.security.pkcs11.P11KeyStore uses JAAS framework for authentication. P11KeyStore.engineLoad() method wraps a LoginException into an IOException: http://hg.openjdk.java.net/jdk9/dev/jdk/file/d99c2ffdd0f1/src/jdk.crypto.pkcs11/share/classes/sun/security/pkcs11/P11KeyStore.java#l730 ... public synchronized void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException { ... try { if (password == null) { login(null); } else { login(new PasswordCallbackHandler(password)); } ... } catch (LoginException | KeyStoreException | PKCS11Exception e) { throw new IOException("load failed", e); } } ... To make it follow the spec, it can be fixed with the following patch: diff -r d99c2ffdd0f1 src/jdk.crypto.pkcs11/share/classes/sun/security/pkcs11/P11KeyStore.java --- a/src/jdk.crypto.pkcs11/share/classes/sun/security/pkcs11/P11KeyStore.java Thu Aug 20 12:29:24 2015 -0700 +++ b/src/jdk.crypto.pkcs11/share/classes/sun/security/pkcs11/P11KeyStore.java Fri Aug 21 09:38:00 2015 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2015, 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 @@ -62,6 +62,7 @@ import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.FailedLoginException; import sun.security.util.Debug; import sun.security.util.DerValue; @@ -750,6 +751,21 @@ } else { login(new PasswordCallbackHandler(password)); } + } catch(LoginException e) { + Throwable cause = e.getCause(); + if (cause instanceof PKCS11Exception) { + PKCS11Exception pe = (PKCS11Exception) cause; + if (pe.getErrorCode() == CKR_PIN_INCORRECT) { + // if password is wrong, the cause of the IOException + // should be an UnrecoverableKeyException + throw new IOException("load failed", + new UnrecoverableKeyException().initCause(e)); + } + } + throw new IOException("load failed", e); + } + + try { if (mapLabels() == true) { // CKA_LABELs are shared by multiple certs writeDisabled = true; @@ -757,7 +773,7 @@ if (debug != null) { dumpTokenMap(); } - } catch (LoginException | KeyStoreException | PKCS11Exception e) { + } catch (KeyStoreException | PKCS11Exception e) { throw new IOException("load failed", e); } }
21-08-2015