Подтвердить что ты не робот

PKCS12 Java Keystore из CA и сертификата пользователя в java

Недавно я был назначен ответственным за издевание продукта Apple (утилита настройки iPhone) на Java. Один из разделов, которые я немного застрял, - это часть Exchange ActiveSync. В нем вы можете выбрать сертификат из вашей брелка для использования в качестве учетных данных для своей учетной записи EAS. После некоторого исследования я обнаружил, что он фактически создает хранилище ключей PKCS12, вставляя закрытый ключ выбранного сертификата и кодируя его в XML. Пока что не очень. Если я создаю файл .p12 с Keychain Access, он загружает без проблем. Но я сталкиваюсь с проблемой, когда пытаюсь передать это на Java.

Скажем, я экспортирую один из тех сертификатов, который я использовал ранее с файлом .p12 в виде .cer файла (это то, что мы ожидаем получить в среде). Теперь, когда я загружаю его в Java, я получаю объект Certificate следующим образом:

KeyStore ks = java.security.KeyStore.getInstance("PKCS12");
ks.load(null, "somePassword".toCharArray());

CertificateFactory cf = CertificateFactory.getInstance("X.509", new BouncyCastleProvider());
java.security.cert.Certificate userCert  = cf.generateCertificate(new FileInputStream("/Users/me/Desktop/RecentlyExportedCert.cer"));

Но когда я пытаюсь...

ks.setCertificateEntry("SomeAlias", userCert);

Я получаю исключение...

java.security.KeyStoreException: TrustedCertEntry not supported

Итак, из сертификатов я перехожу к ключам. Но с этими сертификатами (я также получил сертификат CA), я могу получить доступ к открытому ключу, а не к частному. И если я попытаюсь добавить открытый ключ так...

java.security.cert.Certificate[] chain = {CACert};
ks.setKeyEntry("SomeAlias", userCert.getPublicKey().getEncoded(), chain);

Я получаю...

java.security.KeyStoreException: Private key is not stored as PKCS#8 EncryptedPrivateKeyInfo: java.io.IOException: DerValue.getOctetString, not an Octet String: 3

Итак, теперь я здесь. Кто-нибудь знает, как получить закрытый ключ из файла .cer в хранилище ключей PKCS12 на Java? Я даже на правильном пути?

Спасибо заранее!

4b9b3361

Ответ 1

Формат PKCS # 12 предназначен для хранения закрытого ключа, связанного с цепочкой сертификатов, и оба требуются (хотя вам может не понадобиться целая цепочка). Хотя тип PKCS12 keystore делает хорошую работу для сопоставления этого формата с Java KeyStore, по этой причине не все поддерживается.

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

То, что вы пытаетесь сделать во второй попытке (ks.setKeyEntry("SomeAlias", userCert.getPublicKey().getEncoded(), chain)), - это использовать открытый ключ вместо того, что должно быть закрытым ключом (см. KeyStore#setKeyEntry).

.cer Файл обычно относится только к сертификатам, а не к закрытым ключам (хотя, разумеется, расширение - это просто указание). Если вы экспортируете файл .cer из Keychain Access.app, вы не получите с ним закрытый ключ (что для формата экспорта .p12).

ИЗМЕНИТЬ о KeychainStore:

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

KeyStore ks = KeyStore.getInstance("KeychainStore", "Apple");
ks.load(null, "-".toCharArray());

Несколько примечаний для этого:

  • Любой непустой, непустой пароль будет использовать закрытый ключ (например, "-".toCharArray()), так как доступ будет вызван службой безопасности ОС (как в других приложениях).
  • Насколько мне известно, все еще есть ошибка, и он разрешает доступ только к одной паре частного ключа/сертификата (даже если в цепочке ключей присутствует несколько пар частных ключей/сертификатов)

Ответ 2

http://www.docjar.com/html/api/org/bouncycastle/jce/examples/PKCS12Example.java.html

Вот как добавить сертификат с ассоциированным закрытым ключом в хранилище ключей PKCS12. Когда вы используете аутентификацию клиента, хранилище ключей должно содержать также закрытый ключ, в этом случае вы используете KeyStore.getInstance( "PKCS12" ).

Если вы не используете аутентификацию клиента, но только аутентификация сервера (и закрытый ключ не будет добавлен в хранилище ключей, поскольку он принадлежит серверу), его лучше использовать KeyStore.getInstance( "JKS" ), чем вы можете добавить несколько сертификатов с псевдонимом к одному хранилищу ключей.

Когда вы используете PKCS12, насколько мне известно, вы можете добавить только 1 сертификат (вам нужно добавить целую цепочку сертификатов), связанную с закрытым ключом, который вы хотите использовать для этого сертификата.

Ответ 3

Я пару лет опаздываю на вечеринку, но мне потребовалось несколько часов, чтобы нормально работать, поэтому я подумал, что стоит опубликовать рабочее решение. Это решение использует 1) сертификат A.p12/PKCS12  и 2) ЦС не в TrustManager по умолчанию (и вы хотите добавить его программно, а не добавлять к TrustManager по умолчанию). 3) Нет сторонних криптографических библиотек, просто HttpClient, чтобы собрать все это вместе.

Я также добавил несколько полезных команд keytool и openssl в JavaDoc для работы с сертификатами, поскольку это искусство само по себе.

// Stitch it all together with HttpClient
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(getSSLSocketFactory()).build();


private SSLConnectionSocketFactory getSSLSocketFactory() {
    try {
        SSLContext sslContext = SSLContext.getInstance("TLS");

        KeyManager[] keyManager = getKeyManager("pkcs12", "path/to/cert.p12"), "p12_password"));
        TrustManager[] trustManager = getTrustManager("jks", "path/to/CA.truststore", "trust_store_password"));
        sslContext.init(keyManager, trustManager, new SecureRandom());

        return new SSLConnectionSocketFactory(sslContext);
    } catch (Exception e) {
        throw new RuntimeException("Unable to setup keystore and truststore", e);
    }
}

/**
 * Some useful commands for looking at the client certificate and private key:
 * keytool -keystore certificate.p12 -list -storetype pkcs12 -v
 * openssl pkcs12 -info -in certificate.p12
 */
private KeyManager[] getKeyManager(String keyStoreType, String keyStoreFile, String keyStorePassword) throws Exception {
    KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyStore.load(this.getClass().getClassLoader().getResourceAsStream(keyStoreFile), keyStorePassword.toCharArray());
    kmf.init(keyStore, keyStorePassword.toCharArray());

    return kmf.getKeyManagers();
}

/**
 * Depending on what format (pem / cer / p12) you have received the CA in, you will need to use a combination of openssl and keytool
 * to convert it to JKS format in order to be loaded into the truststore using the method below. 
 *
 * You could of course use keytool to import this into the JREs TrustStore - my situation mandated I create it on the fly.
 *
 * Useful command to look at the CA certificate:
 * keytool -keystore root_ca.truststore -list -storetype jks -v
 *
 */
private TrustManager[] getTrustManager(String trustStoreType, String trustStoreFile, String trustStorePassword) throws Exception {
    KeyStore trustStore = KeyStore.getInstance(trustStoreType);
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustStore.load(this.getClass().getClassLoader().getResourceAsStream(trustStoreFile), trustStorePassword.toCharArray());
    tmf.init(trustStore);

    return tmf.getTrustManagers();
}