19 смертных грехов, угрожающих безопасности программ
Шрифт:
□ SSL_RSA_WITH_DES_CBC_SHA
□ SSL_DHE_RSA_WITH_DES_CBC_SHA
□ SSL_DHE_DSS_WITH_DES_CBC_SHA
□ SSL_RSA_EXPORT_WITH_RC4_4 0_MD5
□ SSL_RSA_EXPORT_WITH_DES4 0_CBC_SHA
□ SSL_DHE_RSA_EXPORT_WITH_DES4 0_CBC_SHA
□ SSL_DHE_DSS_EXPORT_WITH_DES4 0_CBC_SHA
Первые два семейства шифров нежелательны из соображений долгосрочной безопасности, но именно они, скорее всего, и будут использованы! Мы рекомендуем выбирать любое из последующих трех семейств, поскольку AES считается самым лучшим из современных криптографических алгоритмов. (Вопроса о выборе алгоритма открытого ключа и кода аутентификации сообщений (MAC) мы здесь не касаемся.) Чтобы принять только три указанных алгоритма, надо написать такой код:
private void useSaneCiperSuites(SSLSocket s) {
s.setEnabledCipherSuites({"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA"});
}
Проверка
Разные API в разной степени поддерживают базовую проверку сертификата. Некоторые по умолчанию проверяют дату и цепочку доверия, в других вообще не реализовано ни то, ни другое. Большинство же находятся где–то посередине, например включают средства проверки, но не выполняют ее по умолчанию.
Обычно (хотя и не всегда) для выполнения проверки нужно получить ссылку на сертификат сервера (часто его называют сертификатом «партнера» (peer certificate)). Например, в Java до инициализации SSL–соединения можно зарегистрировать объект–слушатель HandShakeCompletedListener для объекта SSLSocket. Слушатель должен реализовать такой метод:
public void handshakeCompleted(HandShakeCompletedEvent event);
Получив объект, описывающий событие, вы можете далее написать:
event.getPeerCertificates ;
В результате будет возвращен массив объектов типа javasecuritycert.Certi–ficate. Certificate – это базовый класс, фактический тип полученных объектов обычно представлен производным классом java.security.cert.X509Extension, хотя иногда встречаются и устаревшие сертификаты (типа java.security.cert.X509, которому наследует X509Extension).
Первым в массиве идет сертификат партнера, а за ним – сертификаты удостоверяющих центров по цепочке вплоть до корневого. При вызове этого метода Java API выполняет некоторые проверки сертификатов с целью убедиться в поддержке выбранного семейства шифров, но цепочка доверия не контролируется. Выбрав такой подход, вы должны самостоятельно произвести все проверки, используя открытый ключ (п+1)-го сертификата для контроля n–го, а дойдя до корневого сертификата, сравнить его со списком известных корневых УЦ. (В Java есть и другие способы проверки сертификатов, но они не менее сложны.) Например, чтобы проверить сертификат партнера, когда уже установлено, что вторым в массиве идет доверенный сертификат, нужно сделать следующее:
try {
((X509Extension)(certificate[0])).verify(certificate[1].getPublicKey);
} catch (Exception e) {
/* Проверка сертификата завершилась неудачно. */
}
Отметим, что здесь не проверяется корректность даты каждого сертификата. Это можно было бы сделать так:
try {
((X509Extension)(certificate[0])).checkValidity;
} catch (Exception e) {
/* Проверка сертификата завершилась неудачно. */
}
В каркасе .NET имеются аналогичные средства:
X509Certificate2 cert = new X509Certificate2(@"c:\certs\server.cer");
X509Chain chain = new X509Chain;
chain.Build(cert);
if (chain.ChainStatus.Length > 0) {
// Были ошибки
}
Проверка имени хоста
Предпочтительный способ проверить имя хоста – воспользоваться полем dnsName из расширения subjectAltName, если оно имеется и заполнено. Но часто имя хоста записывается в поле DN. API
для проверки этих полей варьируются в широких пределах.В JavaJSSE в предположении, что мы имеем дело с сертификатом X509Exten–sion, можно следующим образом проверить значение subjectAltName, а в случае неудачи обратиться к полю DN:
private Boolean validateHost(X509Extension cert) {
String s = "";
String EXPECTED_HOST = "www.example.com";
try {
/* 2.5.29.17 – это OID, стандартное числовое представление имени
расширения */
s = new String(cert.getExtensions("2.5.29.17"));
if (s.equals(EXPECTED_HOST)) {
return true;
}
else { /* если расширение есть, но не соответствует
* ожидаемому значению, не будем проверять поле DN,
* которое НЕ ДОЛЖНО иметь другое значение. */
return false;
}
} catch (Exception e) {} /* Такого расширения нет, проверим DN */
if (cert.getSubjectDN.getName.equals(EXPECTED_HOST)) {
return true;
} else {
return false;
}
}
В каркасе .NET имя хоста проверяется автоматически при вызове метода SslStream. AuthenticateAsClient.
Проверка отзыва сертификата
Самым популярным способом проверки факта отзыва сертификата (если вообще можно говорить о популярности столь нечасто применяемой методики) по–прежнему остается сверка с CRL–списком. Следовало бы рекомендовать протокол OCSP, но УЦ не торопятся с его поддержкой. Компания VeriSign поддерживает его, пожалуй, лучше других, она готова отвечать на запрос о статусе каждого когда–либо выпущенного ей сертификата (включая также сертификаты, выпущенные компаниями RSA и Thawte). Ее сервер находится по адресу(если вы пользуетесь библиотекой, поддерживающей протокол OCSP).
Но обратимся к CRL–спискам. Во–первых, для сверки вы должны иметь много CRL–списков. Необходимо узнать адрес точки распространения CRL, которая (если существует) может быть доступна по протоколам HTTP или LDAP. Иногда адрес указан в сертификате, а иногда – нет. В табл. 10.1 приведен список известных точек распространения CRL, работающих по протоколу HTTP. Можете использовать этот список в случае, когда адрес отсутствует в самом сертификате.
Во–вторых, нужно решить, как часто загружать CRL–списки. Обычно УЦ регулярно обновляют списки отозванных сертификатов, даже если никаких новых записей в них не появилось. Мы рекомендуем загружать новую версию с точно такой же периодичностью, не позже чем через 24 ч после обновления.
В–третьих, необходимо контролировать, что загруженный CRL–список действительно опубликован соответствующим УЦ (для этого нужно проверить цифровую подпись).
И наконец, проверяя каждый предъявленный сертификат, следует убедиться, что ни один из сертификатов в цепочке доверия не внесен в имеющиеся CRL–списки. Если какой–то сертификат отозван, то соединение устанавливать нельзя.
В CRL заносятся просто идентификаторы сертификатов. Чтобы сверить сертификат с CRL–списком, нужно извлечь поле ID и посмотреть, есть ли оно в этом списке.
Таблица 10.1. Адреса точек распространения CRL–списков для популярных УЦ
Дополнительные защитные меры
В идеале, помимо описанных в этой главе проверок, надо бы проверять и другие критические расширения сертификата Х.509. При этом вы должны ясно понимать смысл всех критических расширений. Тогда вы не спутаете, например, сертификат для подписания кода с SSL–сертификатом. Вообще говоря, такие проверки могут представлять интерес, но обычно они не настолько важны, как может показаться.