Дэвид Лебланк - 19 смертных грехов, угрожающих безопасности программ
Выбор семейства шифров
Как и в случае выбора протокола, задать семейство шифров в языке высокого уровня сложно. В низкоуровневых языках такая возможность есть, но умолчания, на наш взгляд, оставляют желать лучшего. Например, в API Java Secure Sockets Extensions QSSE – защищенные сокеты в Java) в качестве симметричных шифров можно выбирать RC4, DES, 3DES и AES. Но первыми двумя лучше не пользоваться.
Вот полный перечень шифров, предлагаемых Sun, в порядке приоритета (в таком порядке Java будет пробовать их, если вы ничего не укажете):
□ SSL_RSA_WITH_RC4_12 8_MD5
□ SSL_RSA_WITH_RC4_12 8_SHA
□ TLS_RSA_WITH_AES_12 8_CBC_SHA
□ TLS_DHE_RSA_WITH_AES_12 8_CBC_SHA
□ TLS DHE DSS WITH AES 128 CBC SHA
□ SSL_RSA_WITH_3DES_EDE_CBC_SHA
□ SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
□ SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA
□ 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:certsserver.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). Ее сервер находится по адресу http://ocsp.verisign.com (если вы пользуетесь библиотекой, поддерживающей протокол OCSP).
Но обратимся к CRL–спискам. Во–первых, для сверки вы должны иметь много CRL–списков. Необходимо узнать адрес точки распространения CRL, которая (если существует) может быть доступна по протоколам HTTP или LDAP. Иногда адрес указан в сертификате, а иногда – нет. В табл. 10.1 приведен список известных точек распространения CRL, работающих по протоколу HTTP. Можете использовать этот список в случае, когда адрес отсутствует в самом сертификате.
Во–вторых, нужно решить, как часто загружать CRL–списки. Обычно УЦ регулярно обновляют списки отозванных сертификатов, даже если никаких новых записей в них не появилось. Мы рекомендуем загружать новую версию с точно такой же периодичностью, не позже чем через 24 ч после обновления.
В–третьих, необходимо контролировать, что загруженный CRL–список действительно опубликован соответствующим УЦ (для этого нужно проверить цифровую подпись).
И наконец, проверяя каждый предъявленный сертификат, следует убедиться, что ни один из сертификатов в цепочке доверия не внесен в имеющиеся CRL–списки. Если какой–то сертификат отозван, то соединение устанавливать нельзя.
В CRL заносятся просто идентификаторы сертификатов. Чтобы сверить сертификат с CRL–списком, нужно извлечь поле ID и посмотреть, есть ли оно в этом списке.
Таблица 10.1. Адреса точек распространения CRL–списков для популярных УЦ
Дополнительные защитные меры
В идеале, помимо описанных в этой главе проверок, надо бы проверять и другие критические расширения сертификата Х.509. При этом вы должны ясно понимать смысл всех критических расширений. Тогда вы не спутаете, например, сертификат для подписания кода с SSL–сертификатом. Вообще говоря, такие проверки могут представлять интерес, но обычно они не настолько важны, как может показаться.
Чтобы снизить риск кражи верительных грамот, после чего сертификат придется отозвать, можно рассмотреть возможность применения специального оборудования для ускорения работы с SSL. Большинство таких продуктов хранят секретные данные в аппаратуре и не показывают их компьютеру ни при каких обстоятельствах. Таким образом, хакер, взломавший вашу машину, ничего не добьется. Некоторые устройства оборудованы также средствами против физического вмешательства, так что даже физическая атака становится затруднительной.
И наконец, если вас пугает сложность дотошной проверки SSL–сертификатов, а ваше приложение общается только с небольшим числом серверов, то можете зашить в код все действительные сертификаты и сличать все байты. Такой подход на основе списков допустимых сертификатов можно дополнить собственной схемой PKI, но в долгосрочной перспективе это окажется более дорогостоящим и трудоемким делом, чем реализация корректной проверки с самого начала.
Другие ресурсы
□ RFC по протоколу HTTPS: www.ietf.org/rfc/rfc2818.txt
□ Документация по Java Secure Socket Extension (JSSE) API: java.sum.com/products/jsse
□ Документация по программированию SSL и TLS на базе библиотеки OpenSSL: www.openssl.org/docs/ssl/ssl.html
□ Информационный центр компании VeriSign по вопросам SSL: www.signiocom/products–services/security–services/ssl/ssl–information–center/