Using Java to read Oracle Wallet files

A while ago, i was given the task to write a program to check for the expiration of client certificates. Since the program was supposed to be workable on Windows, Linux, Solaris, and AIX, i decided to not use the openssl libraries, instead, i wrote a java program. Now, when i started to make it productive, the guy actually in charge told me “great thing, and we actually have a few PEM certificates, but most of them are Oracle Wallets”.

Bummer.

Some information about how to use wallets are in a white paper by oracle, but that focuses on using wallets with the oracle client and doesn’t tell much about how to read wallet files.

As Mike Sidoti suggests in a blog post, oracle Wallets are PKCS12 files, and you can read both of them using the Java KeyStore api, provided you have Oracle’s PKI provider installed. That was a good place to start, but there were still some problems.

1. Needed libraries.

Mike’s post suggests using something like

Security.addProvider(new OraclePKIProvider());
KeyStore keyStore=KeyStore.getInstance("SSO", "OraclePKI");
keyStore.load(new FileInputStream(certFile), null);

which compiles well if you have the $ORACLE_HOME/jlib/oraclepki.jar file in your library path. However, that code threw all kinds of exceptions on me when i ran it. Turns out that, when executing this code, you need osdt_core.jar and osdt_cert.jar as well. (The exceptions will include the names of some classes that are missing, but some of those classes are in osdt_core_fips.jar as well. Took me two hours until i replaced osdt_core_fips.jar with osdt_core.jar, since you get decryption errors, not ClassNotFound exceptions, when you use the wrong one).
To give oracle some credit, section 2.2.1 of the oracle white paper mentions that. Seems Oracle 10 used ojpse.jar instead of the osdt_* jars, but Oracle 11 and 12 both use the osdt_* jars.

2. Don’t copy wallet files!

This shouldn’t be a big deal, but it can cost  you some time if you do it wrong. If you copy .sso files, the copy won’t (always) be usable. So while my IDE is on a windows system, and the cwallet.sso was on an AIX box where i can’t access it from windows, it might have been tempting to copy the cwallet.sso over to be able to test from within the IDE. But you will get decrpytion errors in that case, just like you should – after all, the point of .sso wallets is that you can’t just copy them. So i just used scp to copy my software over to AIX after each build and run it there.

A note of caution here: while copied .sso files might not be directly usable in all cases, they still contain all credentials needed to login. I have a hunch that oracle just takes your machine name, user name, and maybe one or two other factors, creates a secret key from them, and encrypts your original file with that key. I doubt that a determined individual would need much longer that a week to write a wallet file decryptor. So you should protect your wallet files just as if they were text files containing a plaintext password.

3. Oracle’s wallet objects are undocumented.

There is documentation for the keystore api, and it works on wallets opened with the above piece of code as well. You can enumerate the elements of an .sso file like this:

String certFile="/path/to/cwallet.sso";
Security.addProvider(new OraclePKIProvider());
KeyStore keyStore=KeyStore.getInstance("SSO", "OraclePKI");
keyStore.load(new FileInputStream(certFile), null);

Enumeration alias=keyStore.aliases();
while (alias.hasMoreElements()) {
    String aliasName=alias.nextElement();
    if (keyStore.isCertificateEntry(aliasName)) System.out.print("(cert) "); else System.out.print(" ");
    if (keyStore.isKeyEntry (aliasName)) System.out.print("(key ) "); else System.out.print(" ");
    System.out.println(aliasName);
    if (keyStore.isKeyEntry (aliasName)) {
        Key key=keyStore.getKey(aliasName, null);
        System.out.println("\t\t"+key.getAlgorithm()+" - "+key.getFormat()+"\t"+key.getClass().getCanonicalName());
    }
    if (keyStore.isCertificateEntry(aliasName)) {
        Certificate cert=keyStore.getCertificate(aliasName);
        System.out.println("\t\t"+cert.getType()+"\t"+cert.getClass().getCanonicalName());
    }
}

Unfortunately, the class names printed out are oracle.security.pki.OraclePKIRSAPrivateKey and oracle.security.pki.OraclePKIX509CertImpl. I wasn’t able to find documentation on these. But there the oracle.security.pki.OraclePKIX509CertImpl has getSubjectDN() (with getName()), getNotBefore() and getNotAfter() methods, so the following works:

oracle.security.pki.OraclePKIX509CertImpl x509=(oracle.security.pki.OraclePKIX509CertImpl)cert;
System.out.println("\t\tissued to "+x509.getSubjectDN().getName());
System.out.println("\t\tfrom "+x509.getNotBefore()+ " to "+x509.getNotAfter());

where getNotBefore() and getNotAfter() return Date objects, so you can compare them easily.

4. How on earth do i get the user certificate?

If you run the above code on your cwallet.sso file, you’ll find it enumerates various Certificate entries (the various intermediate trust CAs), and a Key entry (whose Subject is the user you’re trying to authenticate). The OraclePKIRSAPrivateKey holds some methods to get the factors and modulus, and a method to get the (optional) certificate request associated to it. But there’s no method to retrieve the certificate. And you can’t get the expiration date from the request, since that’s the CA’s decision. So, how can you check when your certificate expires?

Turns out that KeyStore.isCertificateEntry lies to you when you deal with user certificates. If the alias myusername has a key, a certificate request, and a certificate, in the wallet, then KeyStore.isKeyEntry("myusername") will return true, as it should. But KeyStore.isCertificateEntry("myusername") will return false, even with the certificate present.

Fortunately, keyStore.getCertificate("myusername") will still return the certificate. Unfortunately, this cost me an afternoon and most part of the evening to figure out, until i decided to load up the oraclepki.jar in jd-gui.

So to check for the expiration of your user, just run the above code for certificates for keys as well:

String certFile="/path/to/cwallet.sso";
Security.addProvider(new OraclePKIProvider());
KeyStore keyStore=KeyStore.getInstance("SSO", "OraclePKI");
keyStore.load(new FileInputStream(certFile), null);

Enumeration alias=keyStore.aliases();
while (alias.hasMoreElements()) {
    Certificate cert=null;
    String aliasName=alias.nextElement();
    if (keyStore.isCertificateEntry(aliasName)) System.out.print("(cert)  "); else System.out.print("        ");
    if (keyStore.isKeyEntry        (aliasName)) System.out.print("(key )  "); else System.out.print("        ");
    System.out.println(aliasName);
    if (keyStore.isKeyEntry        (aliasName)) {
        Key key=keyStore.getKey(aliasName, null);
        System.out.println("\t\t"+key.getAlgorithm()+" - "+key.getFormat()+"\t"+key.getClass().getCanonicalName());
        cert=keyStore.getCertificate(aliasName);
    }
    if (keyStore.isCertificateEntry(aliasName)) {
        cert=keyStore.getCertificate(aliasName);
        System.out.println("\t\t"+cert.getType()+"\t"+cert.getClass().getCanonicalName());
    }

    if (cert != null && cert.getClass().getCanonicalName().equals("oracle.security.pki.OraclePKIX509CertImpl")) {
        oracle.security.pki.OraclePKIX509CertImpl x509=(oracle.security.pki.OraclePKIX509CertImpl)cert;
        System.out.println("\t\tissued to "+x509.getSubjectDN().getName());
        System.out.println("\t\tfrom "+x509.getNotBefore()+ " to "+x509.getNotAfter());
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *