(Несколько правок после ряда комментариев.)
setWantClientAuth
используется для запроса аутентификации сертификата клиента, но сохраняет соединение, если аутентификация не предоставлена. setNeedClientAuth
используется для запроса и требования аутентификации сертификата клиента: соединение будет прервано, если не будет представлен подходящий сертификат клиента.
Дополнительную информацию по этой теме можно найти в разделе "Сертификат клиента" спецификации TLS а>. Немного истории:
В версии 1.2 говорится: «Если подходящий сертификат недоступен, клиент ДОЛЖЕН отправить сообщение сертификата, не содержащее сертификатов.». До этого это было просто «СЛЕДУЕТ», но в этом случае клиенты Sun JSSE все равно отправляют пустой список.
- #P5# <блочная цитата> #P6# блочная цитата> #P7# #P8#
Сначала я подумал, что ваш клиент не отправлял свой сертификат, когда вы использовали «необходимость». Действительно, большинство клиентов вообще не будут отправлять клиентский сертификат, если они не могут найти клиентский сертификат, выданный одним из издателей, перечисленных в именах ЦС, отправленных сервером во время его запроса (или если клиент не может построить цепочку самостоятельно, что является распространенной проблемой). По умолчанию JSSE использует центры сертификации в хранилище доверенных сертификатов для создания этого списка. По этой причине ваш клиент, вероятно, вообще не отправит сертификат клиента, если подходящего эмитента нет в хранилище доверенных сертификатов сервера.
Проверить, отправлен ли сертификат клиента, можно с помощью Wireshark. Если вы не используете порт, обычно используемый с SSL/TLS, вам нужно щелкнуть правой кнопкой мыши пакет и выбрать «Декодировать как... -> Транспорт -> SSL».
Там вы должны увидеть сообщение Certificate Request
, пришедшее с сервера. (По какой-то причине, когда я использую хранилище доверенных сертификатов JRE по умолчанию с Wireshark, это сообщение отображается как «Зашифрованное сообщение рукопожатия» сразу после сообщения «Обмен ключами сервера». Однако оно не зашифровано: вы можете четко видеть число имен ЦС, если посмотреть на ASCII-рендеринг пакета в нижней панели.Возможно, это потому, что это сообщение слишком длинное, я не уверен.) С более коротким списком, например, хранилище доверия с одним ЦС , Wireshark правильно расшифрует это как сообщение Certificate Request
, и вы должны увидеть список принятых ЦС в разделе «Отличительные имена».
Вы также должны увидеть сообщение Certificate
от клиента (конечно, не от сервера). Если сервер запрашивает (с желанием или необходимостью) сертификат, вы все равно всегда должны видеть это сообщение от клиента.
Предполагая, что у вас есть доступ к тестовому центру сертификации (с клиентским сертификатом, выданным этим центром сертификации), вы можете провести следующие эксперименты.
Если вы настроили хранилище доверенных сертификатов с этим тестовым сертификатом ЦС, используйте setWantClientAuth(true)
, клиент отправит свой сертификат клиента, и соединение будет продолжено. Затем сервер может получить сертификат клиента от SSLSession
, как и ожидалось.
Если вы используете хранилище доверенных сертификатов по умолчанию (которое не содержит вашего тестового сертификата ЦС), используйте setWantClientAuth(true)
, DN ЦС не будет в Certificate Request
. Клиент отправит сообщение Certificate
, но список сертификатов будет пустым (Certificates Length: 0
в Wireshark). Здесь клиент на самом деле не отправляет сертификат клиента, даже если его хранилище ключей настроено на это, просто потому, что он не может найти подходящее совпадение. Соединение продолжится (вы можете получить исключение, если попытаетесь прочитать сертификат узла из SSLSession
на сервере, но это не фатально). Это вариант использования setWantClientAuth(true)
; setNeedClientAuth(true)
немедленно прервал бы соединение.
Ради этого эксперимента вы можете подделать список DN, отправляемых сервером на Java.
KeyManagerFactory kmf = //... Initialise a KMF with your server's keystore
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null); // Use the default trust store
TrustManager[] trustManagers = tmf.getTrustManagers();
final X509TrustManager origTrustManager = (X509TrustManager) trustManagers[0];
final X509Certificate caCert = // Load your test CA certificate here.
X509TrustManager fakeTrustManager = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// Key the behaviour of the default trust manager.
origTrustManager.checkClientTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// Key the behaviour of the default trust manager.
origTrustManager.checkServerTrusted(chain, authType);
}
public X509Certificate[] getAcceptedIssuers() {
// This is only used for sending the list of acceptable CA DNs.
return new X509Certificate[] { caCert };
}
};
trustManagers = new X509TrustManager[] { fakeTrustManager };
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), trustManagers, null);
В этом случае сообщение Certificate Request
, отправленное сервером, должно содержать DN вашего тестового ЦС. Однако на самом деле диспетчер доверия не доверяет этому ЦС, который по-прежнему использует значения по умолчанию.
Клиент отправит свой сертификат, но сервер отклонит его, заявив «javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: проверка пути PKIX не удалась», и это завершит соединение. Это, по крайней мере, реализация с использованием провайдера SunJSSE, с использованием менеджеров доверия PKIX или SunX509. Это также соответствует спецификации JSSE. менеджера доверия: "Основная обязанность TrustManager — определить, следует ли доверять представленным учетным данным для аутентификации. Если учетным данным нельзя доверять, соединение будет разорвано."
Ключевым моментом здесь является то, что если вы можете получить клиентский сертификат от SSLSession
, этот сертификат должен быть аутентифицирован менеджером доверия (под этим я подразумеваю SSLSession
, который вы получаете после завершения рукопожатия, а SSLSocket.getSession()
нет). тот, который вы получаете во время рукопожатия с использованием getHandshakeSession()
, представленного в Java 7).
Вы, кажется, указываете в комментариях, что используете другого поставщика JSSE, и что ваш клиент все равно отправлял сертификат клиента, независимо от того, находился ли сертификат CA в хранилище доверенных сертификатов сервера, потому что у вас также был другой другой сертификат CA в вашем доверии хранить с тем же DN субъекта. Предполагая, что эти два сертификата ЦС имеют разные ключи (иначе они фактически были бы одним и тем же ЦС), это было бы довольно серьезной ошибкой: приложения, использующие аутентификацию по сертификату клиента, имеют право ожидать, что сертификат клиента был проверен диспетчером доверия ( как указано в справочном руководстве JSSE). Если вы используете Apache Tomcat, после получения сертификата клиента удаленное подключение считается аутентифицированным с помощью этого сертификата. Если в этот момент сервлет сможет использовать клиентский сертификат, который не может быть проверен, то аутентификация на самом деле не была выполнена, что было бы серьезным недостатком.
person
Bruno
schedule
14.02.2013