(См. важные примечания к редактированию в конце.)
Мы изучали карты CAC / PIV для нашего веб-приложения уже несколько дней и нашли некоторую полезную информацию, но нам все еще не хватает ее для наших конкретных нужд. Мы используем Spring Boot, Java и сервер Tomcat 9/10. По сути, наш вопрос: каков отраслевой стандарт для реализации аутентификации по карте CAC/PIV в веб-приложении Spring Boot, Java на сервере Tomcat? (Большинству источников, которые мы нашли, более 10 лет, а остальным 5-9 лет, поэтому нам просто нужна была актуальная информация по этой теме.)
В нашем веб-приложении будет два способа входа:
(Метод, необходимый для каждого пользователя, частично определяется в зависимости от его роли.)
У нас было несколько идей:
Мы нашли еще несколько человек, которые предлагают использовать настройку clientAuth = "true"
на нашем сервере Tomcat или аналогичную. [1 ] [ 2 ] [ 3] Это было бы идеально, поскольку кажется, что это самый простой способ реализовать это, но поскольку нам нужны 2 метода, упомянутых выше, мы не хотите, чтобы приложение всегда требовало значок.
Примечание: кажется, есть возможность сказать clientAuth = "want"
, которая запросит сертификат пользователя, но не откажет пользователю, который не предъявит сертификат. Мне еще нужно узнать, как это работает. (Использовать "want"
не рекомендуется, так как в некоторых случаях это может выдавать предупреждения.)
Еще одно замечание: использование clientAuth = "true"
или "want"
может оказаться успешным для некоторых, но на самом деле оно не дает нам нужного метода. По сути, он будет запрашивать сертификат с момента загрузки сервера. Если установлено значение "true"
, сервер не разрешит доступ к приложению, если он не примет предоставленный сертификат или не доверяет ему. Если установлено значение "want"
, сервер будет искать сертификат, но если ни один из них не найден (или ни один из них не является доверенным), он не будет хранить их, а просто позволит пользователю получить доступ к приложению как обычно. Однако после этого запросить сертификат (любыми настройками сервера) невозможно.
Чтобы обойти эту проблему, мы подумали, что могли бы создать своего рода «шлюзовое» приложение, единственной целью которого является выполнение аутентификации по бейджу. (Может быть, что-то вроде этого веб-сайта max.gov [4] или других сайтов департаментов, ссылки на которые есть на этой странице. Эта реализация на max.gov — это именно то, что нам нужно: возможность чтения бейджей или какой-либо другой метод.)
Нам удалось получить сертификат сервера, чтобы использовать его при тестировании этой идеи! Мы всё настроили и я разобрался с конфигурацией server.xml
:
<!-- default connector to redirect to https port 8443 -->
<Connector executor = "tomcatThreadPool" port = "8080" protocol = "HTTP/1.1"
connectionTimeout = "20000" redirectPort = "8443" maxParameterCount = "1000" />
<!-- connector structure for https -->
<Connector
protocol = "org.apache.coyote.http11.Http11NioProtocol"
port = "8444" maxThreads = "150" SSLEnabled = "true"
maxParameterCount = "1000" secure = "true" scheme = "https" >
<SSLHostConfig
sslProtocol = "TLSv1.2"
certificateVerification = "true" // true requires a cert, or else it fails
truststoreFile = "C:/Program Files/Java/jdk-17/lib/security/cacerts"
truststorePassword = "changeit">
<Certificate
certificateKeystoreFile = "path/to/keystore.jks"
certificateKeystorePassword = "password" type = "RSA" />
</SSLHostConfig>
</Connector>
Я все еще учусь, как заставить это работать с требованиями нашего приложения. (См. мой похожий вопрос [33] о том, как мы заставили часть этого процесса работать. Ссылка [ 3] отлично объяснила процесс/идею.)
Используйте библиотеку с открытым исходным кодом из GitHub. Мы нашли несколько источников, которые рекомендуют это:
Мы читали о некоторых случаях использования Java-апплетов, но это были старые обсуждения. Кроме того, вам, возможно, придется поработать с подписью апплета, а апплеты считаются несколько небезопасными.
Изучая более подробно реализацию аутентификации карт CAC/PIV в Login.gov, я понял, что ответ, данный по ссылке [11], на самом деле касается того, как выполняется аутентификация карт: «Вам будет гораздо легче разобраться в этом и найти информацию». если вы забудете, что пользователи используют смарт-карту или CAC, и просто начнете с идеи, что вы собираетесь использовать клиентские сертификаты для аутентификации».
Это обсуждение [22] может дать дополнительную информацию.
По сути, я узнал, что вам необходимо иметь URL-адрес https для вашего веб-приложения, чтобы ваш сервер мог устанавливать соединение SSL/TLS - это единственный способ запросить сертификат пользователя. Если вы это получите, теоретически вам просто нужно будет запросить сертификат X509 пользователя примерно так:
X509Certificate[] x509certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
if (x509certs != null && x509certs.length > 0) {
X509Certificate x509cert = x509certs[0];
try {
x509cert.checkValidity();
} catch (Exception e) {
System.out.println("error checking cert or cert is not valid.");
}
} else {
System.out.println("certs list is null or empty.");
}
(Убедитесь, что вы изменили javax
на jakarta
, если вы используете Tomcat 10 или более позднюю версию.)
Кроме того, те, кто создает приложение для государственного учреждения, могут связаться с командой Login.gov на их сайте [17]. Если у вас или вашего агентства есть адрес электронной почты .gov, вы сможете создать учетную запись разработчика и использовать их систему для аутентификации пользователя.
Карты CAC, PIV и другие считаются смарт-картами. Компьютеры обмениваются данными со смарт-картами через APDU (блоки данных протокола приложений) [18] . Есть несколько библиотек с открытым исходным кодом, которые могут работать, и у Oracle также есть кое-что: Java Card [19], Smart Card IO [20]. API Java Card в основном предназначен для создания и изменения карточек и обычно использует апплеты, что для нас не очень хороший выбор.
Используя ввод-вывод смарт-карты, я могу, по крайней мере, проверить, присутствует ли карта, и прочитать ATR карты:
try {
// get all terminals
TerminalFactory factory = TerminalFactory.getDefault();
List<CardTerminal> terminals = factory.terminals().list();
System.out.println("Terminals: " + terminals);
// get the first terminal
CardTerminal cdTerminal = terminals.get(0);
try {
if (cdTerminal.isCardPresent()) {
// establish a connection with the card
Card card = cdTerminal.connect("*"); // don't limit the type, just in case your card is different
System.out.println("card: " + card);
ATR atr = card.getATR();
System.out.println("ATR: " + atr.getBytes().toString());
card.disconnect(false);
} else {
System.out.println("No card inserted!");
}
} catch (CardException e) {
System.out.println("Failed to determine if card is present.");
}
} catch (CardException e1) {
System.out.println("Failed in smart card io.");
}
Я надеялся использовать метод transmitControlCommand() [21] для получения сертификата, но сложно найти информацию о том, что означают эти два параметра и какие параметры для них доступны. (См. [24 ], [ 25 ], [ 26)].
Я изучаю это, но у кого-то есть репозиторий GitHub [27], который использует эту библиотеку, и я пытаюсь узнать, как они ее используют.
В этой библиотеке есть несколько полезных функций, но, к сожалению, есть два минуса:
В этом обсуждении [23] и других, которые я видел, рекомендуется API Sun PKCS#11, но, очевидно, он устарел. Существуют возможные альтернативы (Bouncy Castle и IAIK PKCS#11 [28]), но все они требуют некоторой дополнительной собственной библиотеки PKCS#11.
Мне нравится обсуждение здесь [11], но оно более теоретическое, без каких-либо примеров кода. В любом случае, если кто-то еще ищет способ выполнить аутентификацию по бейджу, ответ дает лучшую основу для понимания того, как на самом деле работает аутентификация по бейджу!
Эти двое поднимают несколько хороших вопросов [12 ] [ 13], но ответов на них нет.
Некоторые используют node.js
или React, например [это 14], но я слышал, что использовать это в приложении Spring Boot — не самое лучшее решение. Теперь я немного изучаю это, чтобы увидеть, что я могу использовать (особенно для проверки наличия карты).
Существует также Руководство разработчика [15] для устройства чтения карт, которое используется конечными пользователями этого приложения, но я пока не нашел там ничего полезного.
Мы также подумали об использовании доступной библиотеки от Okta, поскольку теперь у них есть возможность использовать методы аутентификации PIV-карты [16]. Однако похоже, что Okta — это платная услуга, и для нас это не вариант.
Я нашел рекомендацию попробовать API более низкого уровня, например PC/SC [29], но для этого также требуются команды APDU (которые могут зависеть от карты).
14 мая 2024 г.: Мы больше не будем использовать Spring Boot Framework, поскольку потребуется время для преобразования старого кода JDBC в новый способ с использованием bean-компонента jdbcTemplate
. Мы также рассматриваем Tomcat 9 и 10 (для двух разных приложений с одинаковыми требованиями к входу в систему).
30 мая 2024 г.: Я провел дополнительные исследования и нашел множество хороших идей, но пока ни одна из них не оказалась полностью успешной. Я добавил примечания выше.
13.06.2024: (Разные изменения выше.) Я нашел простой способ прочитать сертификаты со смарт-карты! К сожалению, некоторые части у меня работают не так, как описано в источниках: у меня не появляется всплывающее окно выбора сертификата. ТАК вопросы, которые меня вдохновили, были этот [30] и этот [31], поэтому обязательно ознакомьтесь с ними, если мой вопрос вообще оказался для вас полезным!
Я сделал следующее:
// there are various types and providers, but these were the ones that worked
KeyStore keyStore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
// load local certificates (the certificates visible in your browser)
keyStore.load(null);
// get all the names of the available certificates
Enumeration<String> aliases = keyStore.aliases();
Теперь я могу просмотреть Enumeration
, чтобы увидеть доступные сертификаты! Затем вы можете получить доступ к каждому из них, например
String als;
Certificate cert;
while (aliases.hasMoreElements()) {
als = aliases.nextElement();
System.out.println("Alias: " + als);
cert = keyStore.getCertificate(als); // get the certificate
// check the certificate
certs = keyStore.getCertificateChain(als); // get the certificate chain
// check the certificate chain
}
Есть несколько методов класса Certificate
, которые вы можете использовать для проверки; например, cert.getPublicKey()
, cert.getType()
и cert.verify()
(убедитесь, что вы используете открытый ключ эмитента для проверки [32]). Если cert instanceof X509Certificate
, то способов гораздо больше!
Даже несмотря на этот прогресс, я до сих пор не нашел способа проверить PIN-код карты; это дает мне только общедоступную информацию из сертификата на карте.
24.06.2024: Я добавил несколько примечаний к идее №1, которая кажется лучшим вариантом!
Ссылки на ссылки:
(Я буду продолжать обновлять этот вопрос по мере продолжения исследования.)
Короче говоря, идеи №1 и №3 – это решение проблемы.
Мой вопрос и ответ здесь также показывают некоторые важные части решения. Еще раз отмечу, что ссылка 11 в моем вопросе выше очень важна.
Настройте файл server.xml
, как описано в моем ответе здесь, и используйте что-то вроде кода из идеи №3 выше:
X509Certificate[] x509certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
if (x509certs != null && x509certs.length > 0) {
X509Certificate x509cert = x509certs[0];
try {
x509cert.checkValidity();
} catch (Exception e) {
System.out.println("error checking cert or cert is not valid.");
}
} else {
System.out.println("certs list is null or empty.");
}
Примечание: браузер предлагает пользователю выбрать свой сертификат и ввести свой, а не веб-приложение. Затем сервер получает сертификат (цепочку) от браузера.