Статьи

Проверка прозрачности сертификатов в Java

Таким образом, у меня возникла наивная идея, что было бы легко выполнять проверку прозрачности сертификатов как часть каждого запроса в дополнение к проверкам достоверности сертификатов (в Java).

С потерей половины выходных я могу засвидетельствовать, что это не так тривиально. Но что такое прозрачность сертификата ? Короче говоря — это общедоступный журнал всех сертификатов TLS в мире (которые все еще называются сертификатами SSL, даже если SSL устарел). Вы можете проверить, опубликован ли журнал в этом журнале, а если нет, то что-то подозрительно, так как CA должны помещать все свои выпущенные сертификаты в журнал. Существуют и другие варианты использования, например, регистрация уведомлений о новых сертификатах для ваших доменов для обнаружения потенциально угнанных панелей администратора DNS или CA ( Facebook предлагает такой инструмент бесплатно ).

Я хотел сделать первое: каждый запрос приложения Java проверять сертификат другой стороны в журнале прозрачности сертификата. Кажется, что это не доступно из коробки (если это, я не мог найти это. В одном обсуждении о JEP 244 кажется, что обсуждалось расширение TLS, связанное с прозрачностью сертификата, но я не мог найти, является ли это поддерживается в конце).

Сначала я подумал, что вы можете просто получить сертификат и проверить его включение в журнал по отпечатку пальца сертификата. Это было бы слишком просто — журналы для проверки хешем, однако это не отпечаток сертификата, а подписанная метка времени сертификата — подпись, выданная журналом до включения. Процитируем CT RFC :


SCT (подписанная временная метка сертификата) — это обещание журнала включить сертификат в дерево Merkle.

Дерево Merkle — это очень крутая структура данных, которая позволяет внешним участникам быть уверенными, что что-то находится в журнале, предоставляя «доказательство включения», которое намного короче всего журнала (таким образом, экономя большую пропускную способность). На самом деле, именно из-за крутизны деревьев Merkle меня заинтересовала прозрачность сертификатов (так как мы используем деревья Merkle в моей текущей компании, ориентированной на журналы).

Итак, чтобы проверить включение , нужно как-то получить SCT. Сначала я думал, что это будет возможно с Java-библиотекой Certificate Transparency , но вы не можете. Получив его, вы можете использовать его для проверки в журнале, но получить его сложнее. (Примечание: для проверки на стороне сервера можно запросить журнал через HTTP; однако браузеры используют DNS-запросы для сохранения анонимности пользователей).

Получение SCT может быть выполнено тремя способами , в зависимости от того, что сервер и / или журнал и / или CA выбрали для поддержки: SCT может быть включен в сертификат или может быть предоставлен как расширение TLS во время подтверждения связи TLS. или может быть включен в ответ сшивания TLS, снова во время рукопожатия. К сожалению, в нескольких сертификатах, которые я проверял, в них не было SCT, поэтому мне пришлось перейти на более низкий уровень и отладить рукопожатие TLS.

Я включил вывод TLS hadnshake подробный , и о чудо — там ничего не было. Google включает SCT как расширение TLS (согласно Qualys), но вывод Java ничего не сказал об этом.

К счастью (?) Google выпустила Conscrypt — провайдер безопасности Java, основанный на форке Google OpenSSL. Все стало грязно … но я пошел на это, включил Conscrypt и зарегистрировал его в качестве поставщика безопасности. Мне пришлось установить соединение, используя Conscrypt TrustManager (инициализированный со всеми доверенными сертификатами в JDK):

01
02
03
04
05
06
07
08
09
10
11
12
13
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(new FileInputStream(System.getenv("JAVA_HOME") + "/lib/security/cacerts"), "changeit".toCharArray());
ctx.init(null,new TrustManager[] {new TrustManagerImpl(trustStore,
    null, null, null, logStore, null,
    new StrictCTPolicy())}, new SecureRandom());
         
 
URL url = new URL("https://google.com");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(ctx.getSocketFactory());
conn.connect();
conn.getInputStream();
conn.disconnect();

И, конечно, изначально это не сработало, потому что Conscrypt не предоставляет реализации некоторых необходимых интерфейсов ядра — классов CTLogStore и CTPolicy. CTLogStore на самом деле является важным битом, который содержит информацию обо всех известных журналах (я все еще нахожу странным называть «поставщик журналов» просто «log», но это принятая терминология). Существует список известных журналов в форме JSON , что здорово, за исключением того, что мне потребовалось некоторое время, чтобы выяснить (с внешней помощью), что именно являются этими открытыми ключами. Что они — RSA, ECC? Как они закодированы? Вы не можете найти это ни в RFC, ни в документации. Здесь видно, что это «DER-кодирование структуры SubjectPublicKeyInfo ASN.1». Тьфу.

Надувной замок на помощь. Мои отношения с BouncyCastle — любовь-ненависть. Я ненавижу, насколько это не интуитивно понятно и насколько запутаны его API, но мне нравится, что в нем есть (почти) все, что связано с криптографией, что вам может понадобиться. Через некоторое время, потраченное на попытки выяснить, как именно преобразовать этот открытый ключ в объект PublicKey, я обнаружил, что с помощью PublicKeyFactory.createKey(Base64.getDecoder().decode(base64Key)); дает параметры любого используемого алгоритма — он может возвращать ключевые параметры эллиптической кривой или ключевые параметры RSA. Вам просто нужно обернуть их в другой класс и передать на другой завод (типичный BouncyCastle), и ура, у вас есть открытый ключ.

Конечно, теперь Conscrypt от Google снова не работал, потому что после преобразований закодированная версия publicKey не была идентична исходным байтам, и поэтому вычисление идентификатора журнала было неправильным. Но я исправил это, подумав, и наконец, это сработало — журнал прозрачности сертификата был запрошен, и сертификат был показан как действительный и правильно включен в журнал.

Весь код можно найти здесь . И да, он использует несколько провайдеров безопасности, несколько странных API BouncyCastle и некоторые простые реализации, которые отсутствуют в провайдере Google. Известные сертификаты могут быть кэшированы, так что повторные вызовы в журнал не выполняются, но это выходит за рамки моего эксперимента.

Прозрачность сертификатов кажется основой Интернета в наши дни. И все же, с ним так сложно и трудно работать.

Почему тип открытого ключа в списке не задокументирован (они должны по крайней мере поместить OID рядом с открытым ключом, потому что, как выясняется, не все журналы используют эллиптические кривые — два из них используют RSA). Вероятно, есть хорошее объяснение, но зачем включать SCT в журнал, а не отпечаток пальца сертификата? Почему бы тогда не предписать включение SCT в сертификат, что не потребовало бы дополнительной настройки серверов и клиентов, в отличие от включения его в квитирование TLS, которое требует обновлений?

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

И, наконец, если у Java нет простого способа сделать это со всеми доступными крипто библиотеками, мне интересно, как обстоят дела с другими языками. Поддерживают ли они прозрачность сертификатов или нуждаются в обновлении?

И, возможно, мы все хороши, потому что браузеры поддерживают это, но браузеры не единственные, кто делает HTTP-запросы. Вызовы API — это большой случай использования, и если их можно будет захватить, ущерб может быть даже больше, чем фишинг отдельных пользователей. Поэтому я думаю, что больше усилий следует приложить к двум вещам:
1. улучшение RFC и 2. улучшение экосистемы программирования. Я надеюсь, что этот пост внесет хоть немного.

Опубликовано на Java Code Geeks с разрешения Божидара Божанова, партнера нашей программы JCG . См. Оригинальную статью здесь: Проверка прозрачности сертификата в Java

Мнения, высказанные участниками Java Code Geeks, являются их собственными.