diff --git a/core/java/src/net/i2p/crypto/CertUtil.java b/core/java/src/net/i2p/crypto/CertUtil.java
index 239b057edb3bdf9d680eca8e64ce4f1336032040..fe588fce375d44738e31379f4bf0f7c923dd8ee4 100644
--- a/core/java/src/net/i2p/crypto/CertUtil.java
+++ b/core/java/src/net/i2p/crypto/CertUtil.java
@@ -1,11 +1,16 @@
 package net.i2p.crypto;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 import java.util.Locale;
@@ -134,4 +139,40 @@ public class CertUtil {
         Log l = ctx.logManager().getLog(CertUtil.class);
         l.log(level, msg, t);
     }
+
+    /**
+     *  Get the Java public key from a X.509 certificate file.
+     *  Throws if the certificate is invalid (e.g. expired).
+     *
+     *  @return non-null, throws on all errors including certificate invalid
+     *  @since 0.9.24 moved from SU3File private method
+     */
+    public static PublicKey loadKey(File kd) throws IOException, GeneralSecurityException {
+        return loadCert(kd).getPublicKey();
+    }
+
+    /**
+     *  Get the certificate from a X.509 certificate file.
+     *  Throws if the certificate is invalid (e.g. expired).
+     *
+     *  @return non-null, throws on all errors including certificate invalid
+     *  @since 0.9.24 adapted from SU3File private method
+     */
+    public static X509Certificate loadCert(File kd) throws IOException, GeneralSecurityException {
+        InputStream fis = null;
+        try {
+            fis = new FileInputStream(kd);
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
+            cert.checkValidity();
+            return cert;
+        } catch (IllegalArgumentException iae) {
+            // java 1.8.0_40-b10, openSUSE
+            // Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has wrong 4-byte ending unit
+            // at java.util.Base64$Decoder.decode0(Base64.java:704)
+            throw new GeneralSecurityException("cert error", iae);
+        } finally {
+            try { if (fis != null) fis.close(); } catch (IOException foo) {}
+        }
+    }
 }
diff --git a/core/java/src/net/i2p/crypto/DirKeyRing.java b/core/java/src/net/i2p/crypto/DirKeyRing.java
index cc840b36e796d730de755622f0e3a10e198fbf34..899f41847dec24d1d05fa92df2891aa646a52a40 100644
--- a/core/java/src/net/i2p/crypto/DirKeyRing.java
+++ b/core/java/src/net/i2p/crypto/DirKeyRing.java
@@ -35,6 +35,8 @@ class DirKeyRing implements KeyRing {
      *  and have a CN == keyName.
      *
      *  CN check unsupported on Android.
+     *
+     *  @return null if file doesn't exist, throws on all other errors
      */
     public PublicKey getKey(String keyName, String scope, SigType type)
                             throws GeneralSecurityException, IOException {
@@ -47,26 +49,15 @@ class DirKeyRing implements KeyRing {
         File kd = new File(sd, fileName + ".crt");
         if (!kd.exists())
             return null;
-        InputStream fis = null;
-        try {
-            fis = new FileInputStream(kd);
-            CertificateFactory cf = CertificateFactory.getInstance("X.509");
-            X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
-            cert.checkValidity();
-            if (!SystemVersion.isAndroid()) {
-                // getSubjectValue() unsupported on Android.
-                // Any cert problems will be caught in non-Android testing.
-                String cn = CertUtil.getSubjectValue(cert, "CN");
-                if (!keyName.equals(cn))
-                    throw new GeneralSecurityException("CN mismatch: " + cn);
-            }
-            return cert.getPublicKey();
-        } catch (IllegalArgumentException iae) {
-            // java 1.8.0_40-b10, openSUSE
-            throw new GeneralSecurityException("Bad cert", iae);
-        } finally {
-            try { if (fis != null) fis.close(); } catch (IOException foo) {}
+        X509Certificate cert = CertUtil.loadCert(kd);
+        if (!SystemVersion.isAndroid()) {
+            // getSubjectValue() unsupported on Android.
+            // Any cert problems will be caught in non-Android testing.
+            String cn = CertUtil.getSubjectValue(cert, "CN");
+            if (!keyName.equals(cn))
+                throw new GeneralSecurityException("CN mismatch: " + cn);
         }
+        return cert.getPublicKey();
     }
 
     /**
diff --git a/core/java/src/net/i2p/crypto/KeyStoreUtil.java b/core/java/src/net/i2p/crypto/KeyStoreUtil.java
index 7c79fa274f35c55e3b3f68d9b95167438fe9f85a..fe65a7afb057fc8fe625ed0dbaeebbae6fb2d894 100644
--- a/core/java/src/net/i2p/crypto/KeyStoreUtil.java
+++ b/core/java/src/net/i2p/crypto/KeyStoreUtil.java
@@ -12,7 +12,6 @@ import java.security.PrivateKey;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.Enumeration;
 import java.util.Locale;
@@ -317,46 +316,32 @@ public class KeyStoreUtil {
      *  @since 0.8.2, moved from SSLEepGet in 0.9.9
      */
     public static boolean addCert(File file, String alias, KeyStore ks) {
-        InputStream fis = null;
         try {
-            fis = new FileInputStream(file);
-            CertificateFactory cf = CertificateFactory.getInstance("X.509");
-            X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
+            X509Certificate cert = CertUtil.loadCert(file);
             info("Read X509 Certificate from " + file.getAbsolutePath() +
                           " Issuer: " + cert.getIssuerX500Principal() +
                           " Serial: " + cert.getSerialNumber().toString(16) +
                           "; Valid From: " + cert.getNotBefore() +
                           " To: " + cert.getNotAfter());
-            try {
-                cert.checkValidity();
-            } catch (CertificateExpiredException cee) {
-                String s = "Rejecting expired X509 Certificate: " + file.getAbsolutePath();
-                // Android often has old system certs
-                if (SystemVersion.isAndroid())
-                    warn(s, cee);
-                else
-                    error(s, cee);
-                return false;
-            } catch (CertificateNotYetValidException cnyve) {
-                error("Rejecting X509 Certificate not yet valid: " + file.getAbsolutePath(), cnyve);
-                return false;
-            }
             ks.setCertificateEntry(alias, cert);
             info("Now trusting X509 Certificate, Issuer: " + cert.getIssuerX500Principal());
+        } catch (CertificateExpiredException cee) {
+            String s = "Rejecting expired X509 Certificate: " + file.getAbsolutePath();
+            // Android often has old system certs
+            if (SystemVersion.isAndroid())
+                warn(s, cee);
+            else
+                error(s, cee);
+            return false;
+        } catch (CertificateNotYetValidException cnyve) {
+            error("Rejecting X509 Certificate not yet valid: " + file.getAbsolutePath(), cnyve);
+            return false;
         } catch (GeneralSecurityException gse) {
             error("Error reading X509 Certificate: " + file.getAbsolutePath(), gse);
             return false;
         } catch (IOException ioe) {
             error("Error reading X509 Certificate: " + file.getAbsolutePath(), ioe);
             return false;
-        } catch (IllegalArgumentException iae) {
-            // java 1.8.0_40-b10, openSUSE
-            // Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has wrong 4-byte ending unit
-            // at java.util.Base64$Decoder.decode0(Base64.java:704)
-            error("Error reading X509 Certificate: " + file.getAbsolutePath(), iae);
-            return false;
-        } finally {
-            try { if (fis != null) fis.close(); } catch (IOException foo) {}
         }
         return true;
     }
diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java
index f8753aee50d3a55454e49c10c35aa0bdaa837be7..3c8154c6eeab3e9f87aed62b0f813e46d9f2e196 100644
--- a/core/java/src/net/i2p/crypto/SU3File.java
+++ b/core/java/src/net/i2p/crypto/SU3File.java
@@ -15,8 +15,6 @@ import java.security.GeneralSecurityException;
 import java.security.MessageDigest;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumSet;
@@ -978,24 +976,12 @@ public class SU3File {
      *  @since 0.9.15
      */
     private static PublicKey loadKey(File kd) throws IOException {
-        InputStream fis = null;
         try {
-            fis = new FileInputStream(kd);
-            CertificateFactory cf = CertificateFactory.getInstance("X.509");
-            X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
-            cert.checkValidity();
-            return cert.getPublicKey();
+            return CertUtil.loadKey(kd);
         } catch (GeneralSecurityException gse) {
             IOException ioe = new IOException("cert error");
             ioe.initCause(gse);
             throw ioe;
-        } catch (IllegalArgumentException iae) {
-            // java 1.8.0_40-b10, openSUSE
-            IOException ioe = new IOException("cert error");
-            ioe.initCause(iae);
-            throw ioe;
-        } finally {
-            try { if (fis != null) fis.close(); } catch (IOException foo) {}
         }
     }
 }