Назад | Перейти на главную страницу

Очистка кеша сертификатов Java (принудительная перезагрузка сертификатов)

Здесь простой вопрос.

Одно приложение выдало мне это исключение при попытке доступа к веб-сайту с истекшим сертификатом: java.security.cert.CertificateExpiredException

Итак, я обновил сертифицированный компьютер с веб-сайта и перезапустил его. Когда я пытаюсь получить к нему доступ из Firefox или Chrome, он загружает новый сертификат (срок действия которого установлен где-то около 2040 года).

Проблема в том, что приложения Java, похоже, не обновляют этот сертификат, он застрял во внутреннем кэше. Я уже пробовал добавить его в keystore и установите параметры в свойствах приложения, например -Dcom.sun.net.ssl.checkRevocation=false. Независимо от того, что я делаю, это всегда вызывает у меня java.security.cert.CertificateExpiredException

Любые идеи?

Проблема, оказывается, связана с ошибка что заставляет HttpsUrlConnection не использовать SNI, когда используется настраиваемый верификатор имени хоста.

Обходной путь ошибки

javax.net.ssl.HttpsURLConnection connection = (javax.net.ssl.HttpsURLConnection) new java.net.URL(url).openConnection();
connection.setHostnameVerifier(... hostname verifier which indirectly causes a bug ...);

//the remaining code fixes the bug by forcing the use of SNI even with the custom hostname verifier
final javax.net.ssl.SSLSocketFactory originalSocketFactory = connection.getSSLSocketFactory();
connection.setSSLSocketFactory(new javax.net.ssl.SSLSocketFactory() {
  public String[] getDefaultCipherSuites() {
    return originalSocketFactory.getDefaultCipherSuites();
  }

  public String[] getSupportedCipherSuites() {
    return originalSocketFactory.getSupportedCipherSuites();
  }

  private java.net.Socket convertSocket(javax.net.ssl.SSLSocket socket, String host) {
    javax.net.ssl.SNIHostName serverName = new javax.net.ssl.SNIHostName(host);
    java.util.List<javax.net.ssl.SNIServerName> serverNames = new java.util.ArrayList<>(1);
    serverNames.add(serverName);
    javax.net.ssl.SSLParameters params = socket.getSSLParameters();
    params.setServerNames(serverNames);
    socket.setSSLParameters(params);
    return socket;
  }

  public java.net.Socket createSocket(java.net.Socket s, String host, int port, boolean autoClose) throws IOException {

    //host = new URL(url).getHost();
    javax.net.ssl.SSLSocket socket = (javax.net.ssl.SSLSocket) originalSocketFactory.createSocket(s,host,port,autoClose);
    return convertSocket(socket, host);
  }

  public java.net.Socket createSocket(java.net.InetAddress host, int port) throws IOException {
    //You may need convertSocket here, I didn't
    return originalSocketFactory.createSocket(host, port);
  }

  public java.net.Socket createSocket(java.net.InetAddress address, int port, java.net.InetAddress localAddress, int localPort) throws IOException {
    //You may need convertSocket here, I didn't
    return originalSocketFactory.createSocket(address, port, localAddress, localPort);
  }

  public java.net.Socket createSocket(String host, int port) throws IOException {
    //You may need convertSocket here, I didn't
    return originalSocketFactory.createSocket(host, port);
  }

  public java.net.Socket createSocket(String host, int port, java.net.InetAddress localHost, int localPort) throws IOException {

    //You may need convertSocket here, I didn't
    return originalSocketFactory.createSocket(host, port, localHost, localPort);
  }
});

Расположение хранилища ключей Java по умолчанию - это файл .keystore в вашем домашнем каталоге (системное свойство user.home), поэтому, если вы не укажете иное, именно здесь будет искать приложение Java.

Попробуйте запустить:

$ keytool -list -keystore ~/.keystore -storepass changeit -v

чтобы увидеть, есть ли там просроченный сертификат.

Если вы хотите указать другое хранилище ключей идентификации для использования, вы можете сделать это, используя следующие системные свойства:

javax.net.ssl.Keystore=/path/to/identity.jks
javax.net.ssl.keyStorePassword=mykeystorepassword

Я считаю, что Firefox использует NSS, и вы можете просмотреть его хранилище ключей с помощью утилиты certutil (из nss-tools или аналогичного пакета) - что-то вроде:

$ certutil -L -d sql:$HOME/.pki/nssdb

Вы должны иметь возможность использовать утилиту pk12util для извлечения ключа и сертификата в файл PKCS12, но вам, вероятно, лучше просто создать новый запрос на подпись сертификата с помощью утилиты keytool.

Обратите внимание, что отозванный сертификат - это не то же самое, что сертификат с истекшим сроком действия, поэтому ваш checkRevocation = false не работает. ЦС может отозвать сертификат в любое время, даже если срок его действия еще не истек, и это означает, что ему больше нельзя доверять.

Я столкнулся с этим вопросом, когда искал способ заставить Java распознавать недавно импортированные сертификаты.

Для меня причина, по которой сертификаты не обновлялись (в Windows), заключалась в том, что уже был запущен экземпляр Java (предположительно со старым кешем сертификатов).

Я просто убил старый процесс Java, и в следующий раз, когда он автоматически загрузился (из-за запущенных процессов Gradle), сертификаты были обновлены.