Введение во взаимную аутентификацию сервисов на Java c TLS/SSL


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


    Введение


    Если говорить о формате настройки сертификатов для безопасной передачи данных. Как правило, данные действия производят на каком-либо веб-сервере типа nginx или apache, стоящем на входе во внутреннюю сеть компании и dmz. Благодаря ему можно разделить защищенную внутреннюю сеть и внешнюю сеть интернет. Далее, внутри доверенной сети каждый поступает по-разному. Кто-то считает, что все внутренние сервисы могут взаимодействовать друг с другом без каких-то ограничений, и контроль пользователей управляется уже в GUI посредством логина и пароля для конкретного приложения с разграничением ролей в рамках приложения. Кто-то идет дальше, подключая LDAP и используя логин, пароль пользователя из общего хранилища.


    Существуют различные протоколы и технологии типа RADIUS, Kerberos или OAuth/OpenID для работы с вопросами аутентификации. Кто-то использует схемы с базовой аунтефикацией, передавая логин и пароль в base64, кто-то использует JsonWebToken, еще существует возможность использования сертификатов для проверки не только сервера и клиента. В результате получается ситуация, что мы формируем защищенное соединение клиента и сервера, в котором шифруем передаваемые данные и доверяем не только серверу, с которого эти данные забираем, но и знаем о том, кто именно забирает эти данные с нашего сервера, так как он предоставляет клиентский сертификат.


    В рамках моей работы в ТехЦентре Дойче Банка мы в обязательном порядке для всех межсервисных взаимодействий используем SSL-сертификаты — даже в UAT окружении. В Java используем JKS, как более привычный контейнер сертификатов и паролей для этой системы.


    Причины, почему мы так делаем — большое количество регулирующих органов по всему миру, перед которыми мы должны отчитываться. С одной стороны, это добавляет надежности, но с другой — создает некоторые сложности в разработке и тестировании систем.


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


    Терминология


    • SSL (англ. Secure Sockets Layer — уровень защищённых сокетов) — криптографический протокол, использующий асимметричную криптографию для аутентификации ключей обмена, симметричное шифрование для сохранения конфиденциальности, коды аутентификации сообщений для целостности сообщений. Третья версия протокола описана в рабочем предложении RFC-6101. В последующем в SSL была обнаружена уязвимость CVE-2014-3566 в связи на базе. В третьей версии протокола было разработано новое рабочее предложение RFC-5246 протокола, получившего название TLS.
    • TLS (англ. Transport Layer Security — Протокол защиты транспортного уровня) — криптографический протокол, развивающие идеи SSLv3 и закрывающий имеющиеся там уязвимости.

    Формат сертификатов


    В рамках работы с сертификатами обычно используется контейнер PKCS 12 для хранения ключей и сертификатов, но в рамках Java, в дополнение широко используется проприетарный формат JKS (Java KeyStore). Для работы с хранилищем JDK поставляется с консольной утилитой keytool.


    Помимо команды, позволяющей создать ключи вместе с keystore, которая выглядит следующим образом:


    keytool -genkey -alias example.com -keyalg RSA -keystore keystore.jks  -keysize 2048

    Есть ряд других команд под катом, которые могут быть полезны в работе с JKS и просто с ключами и сертификатами в Java


    Примеры полезных команды утилиты keytool
    • Создание запроса сертификата (CSR) для существующего Java keystore
      keytool -certreq -alias example.com -keystore keystore.jks -file example.com.csr
    • Загрузка корневого или промежуточного CA сертификата
      keytool -import -trustcacerts -alias root -file Thawte.crt -keystore keystore.jks
    • Импорт доверенного сертификата
      keytool -import -trustcacerts -alias example.com -file example.com.crt -keystore keystore.jks
    • Генерация самоподписанного сертификата и keystore
      keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -validity 360 -keysize 2048
    • Просмотр сертификата
      keytool -printcert -v -file example.com.crt
    • Проверка списка сертификатов в keystore
      keytool -list -v -keystore keystore.jks
    • Проверка конкретного сертификата по алиасу в keystore
      keytool -list -v -keystore keystore.jks -alias example.com
    • Удаление сертификата из keystore
      keytool -delete -alias example.com -keystore keystore.jks
    • Изменение пароля для keystore
      keytool -storepasswd -new new_storepass -keystore keystore.jks
    • Экспорт сертификата из keystore
      keytool -export -alias example.com -file example.com.crt -keystore keystore.jks
    • Список доверенный корневых сертификатов
      keytool -list -v -keystore $JAVA_HOME/jre/lib/security/cacerts
    • Добавление нового корневого сертификата в trustStore
      keytool -import -trustcacerts -file /path/to/ca/ca.pem -alias CA_ALIAS -keystore $JAVA_HOME/jre/lib/security/cacerts

    KeyStore & TrustStore


    Говоря о JKS, стоит отметить, что данные файлы могут использоваться как KeyStore так и TrustStore. Это два различных типа хранилищ, которые находятся в JKS файлах. Одно из них (KeyStore) содержит более чувствительную информацию типа приватного ключа, и поэтому требует пароля для доступа к этой информации. В противовес чему TrustStore хранит информацию о доверенных сертификатах, которые конечно же присутствуют в операционной системе. Например, для Linux систем мы сможем их найти в /usr/local/share/ca-certificates/


    Но так же эти сертификаты идут в поставке Java в файле cacerts, который по умолчанию расположен в директории java.home\lib\security и имеет пароль по умолчанию changeit.


    Данная информация может быть полезна в тех случаях, когда установка JDK/JRE осуществляется в компании централизовано из одного источника, и имеется возможность добавления туда своих доверенных сертификатов компании для prod/uat окружения.


    Ниже приведена таблица с некоторыми различиями KeyStore & TrustStore.


    Keystore TrustStore
    Хранятся ваши приватные ключи и сертификаты (клиентские или серверные) Хранятся доверенные сертификаты (корневые самоподписанные CA root)
    Необходим для настойки SSL на сервере Необходим для успешного подключения к серверу на клиентской стороне
    Клиент будет хранить свой приватный ключ и сертификат в keystore Сервер будет валидировать клиента при двусторонней аутентификации на основании сертификатов в trustStore
    javax.net.ssl.keyStore используется для работы с keystore javax.net.ssl.trustStore используется для работы с trustStore

    Подключение SSL к NettyServer


    При создании нового проекта, подразумевающего взаимодействие клиента и сервера бинарными данными(protobuf) через защищенные вебсокеты (wss), возник вопрос подключения SSL в netty сервер. Как оказалось, это не представляет особых проблем, достаточно в билдере сетевого интерфейса добавить метод .withSslContext(), в который необходимо передать созданный контекст.


    NettyServer.builder()
            .addHttpListener(
            NetworkInterfaceBuilder.forPort(serviceUri.getPort())
                .withSslContext(sslContextFactory.getSslContext()),
            PathHandler.path()
                .addExactPath(serviceUri.getPath(), createServiceHandler()
        )
        .build();

    Для того что бы сформировать серверный и клиентский SSL-контекст можно использовать один единственный билдер — SslContextBuilder и его методы — forServer и forClient. У этого билдера надо заполнить ряд обязательных полей, такие как trustManager и keyManager. Эти менеджеры мы можем получить из соответствующий фабрик — TrustManagerFactory и KeyManagerFactory.


     SslContextBuilder.forServer(getKeyManagerFactory())
            .trustManager(getTrustManagerFactory())
            .build();

    Синтаксис данных фабрик практически аналогичен с разницей в том, что для trustManager-а мы используем только пароль для самого jks файла,


    private TrustManagerFactory getTrustManagerFactory() throws Exception {
        final TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        final KeyStore keyStore = KeyStore.getInstance("JKS");
        final InputStream trustStoreFile = getTrustStoreFile();
        keyStore.load(trustStoreFile, trustStorePassword.toCharArray());
        tmFactory.init(keyStore);
        return tmFactory;
    }

    а для KeyStore при инициализации нам необходимо дополнительно передать пароль от самого ключа.


    private KeyManagerFactory getKeyManagerFactory() throws Exception {
        final KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        final KeyStore keyStore = KeyStore.getInstance("JKS");
        final InputStream keyStoreFile = getKeyStoreFile();
        keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
        kmFactory.init(keyStore, keyPassword.toCharArray());
        return kmFactory;
    }

    И в целом это все, что необходимо для добавления SSL в NettyServer.


    Подключение SSL в gRPC / RSocket


    Если говорить о двунаправленном обмене бинарными данными, современных тенденциях к написанию реактивных приложений, стоит отметить gRPC и RSocket для создания подобных приложений. Но поскольку в основании этих протоколов можно использовать Netty как транспорт, логика конфигурирования останется. Поэтому я не буду уделять этому много внимания.


    Подключение SSL в Spring Boot для RestController


    Но в мире Java разработки Spring стал де факто стандартом для DI. А вместе с внедрением зависимости люди используют и другие технологии, которые удобно собрать в одном Spring Boot приложении, не расходуя множество времени на конфигурирование всего зоопарка технологий. Конечно же, это приводит к избыточности зависимостей и к увеличению времени загрузки, но упрощает разработку. Куда проще написать аннотацию RestController, чем самому разбираться с тем, как корректно хендлить запросы через сервлеты. А для того, чтобы перенаправить всё взаимодействие через сервлеты в защищённый канал с использованием сертификатов, в Spring Boot есть два пути проcтой и более сложный для кастомных решений.
    В первом случае достаточно воспользоваться набором пропертей


    server.ssl.key-store-type=JKS
    server.ssl.key-store=classpath:cert.jks
    server.ssl.key-store-password=changeit
    server.ssl.key-alias=key
    trust.store=classpath:cert.jks
    trust.store.password=changeit

    И все будет сделано за вас. Либо, если требуется более кастомная конфигурация, поднятие коннекторов на разных портах с нестандартными настройками и так далее, тогда путь лежит в сторону использования интерфейса WebServerFactoryCustomizer, имплементации которого существуют для всех основных контейнеров будь то Jetty, Tomcat или Undertow.


    Поскольку это функциональный интерфейс, его довольно просто можно описать через lambda c параметром типа Connector. Для него мы можем выставить флаг setSecure(true), затем заполнить необходимые параметры для ProtocolHandler-а, выставив ему пути до jks c keystore и trustStore и соответствующие пароли к ним. Например, для tomcat код будет выглядеть подобным образом:


    @Bean
    WebServerFactoryCustomizer<ConfigurableWebServerFactory> containerCustomizer() throws Exception {
        TomcatConnectorCustomizer customizer;
        String absoluteKeystoreFile = keystoreFile.getAbsolutePath();
        String absoluteTruststoreFile = truststoreFile.getAbsolutePath();
        boolean sslEnabled = checkSslSettings(absoluteKeystoreFile, absoluteTruststoreFile, keystorePass);
        if (sslEnabled) {
            customizer = (connector) -> {
                connector.setPort(port);
                connector.setSecure(true);
                connector.setScheme("https");
                Http11NioProtocol proto = (Http11NioProtocol) connector.getProtocolHandler();
                proto.setSSLEnabled(true);
                proto.setClientAuth(clientAuth);
                proto.setKeystoreFile(absoluteKeystoreFile);
                proto.setKeystorePass(keystorePass);
                proto.setTruststoreFile(absoluteTruststoreFile);
                proto.setTruststorePass(truststorePass);
                proto.setKeystoreType(keyStoreType);
                proto.setKeyAlias(keyAlias);
                proto.setCiphers(chiphers);
            };
        } else {
            customizer = (connector) -> {
                connector.setPort(port);
                connector.setSecure(false);
                if (sslEnabled) {
                    log.error("Key- or Trust-store file — {} or {} — is not found, or keystore password is missing, REST service on port {} is disabled", absoluteKeystoreFile, absoluteTruststoreFile, port);
                    ((Http11NioProtocol) connector.getProtocolHandler()).setMaxConnections(0);
                }
            };
        }
    
        return (ConfigurableWebServerFactory factory) -> {
            ConfigurableTomcatWebServerFactory tomcatWebServerFactory = (ConfigurableTomcatWebServerFactory) factory;
            tomcatWebServerFactory.addConnectorCustomizers(customizer);
        };

    И после этого мы отдаем на откуп «магии» спринга перехват всех веб-запросов к нашему сервису, для того чтобы обеспечить безопасность соединения.


    Тестирование TLS/SSL


    Для того чтобы провести тестирование реализованного безопасного подключения имеется возможность создать keystore программно, используя классы из пакета java.security.* Это даст возможность тестировать различное поведение системы в случае разных ситуаций типа истекших сертификатов, проверки корректной валидации доверенных сертификатов и так далее.


    Чтобы грамотно проверить работоспобность придется пройти по всем составным частям jks и воссоздать программно внутри KeyStore пару ключей KeyPair, свой сертификат X509Certificate, цепочку родительских сертификатов, подпись и доверенные корневые сертификаты.


    Для упрощения этой задачи можно воспользоваться библиотекой bouncyСastle, которая предоставляет ряд дополнительный возможностей в дополнение к стандартным классам в Java, посвященным криптографии из Java Cryptography Architecture (JCA) и Java Cryptography Extension (JCE).


    Некоторые аспекты работы с этой библиотекой присутствуют для kotlin и Java в зеркале их репозитория на github (https://github.com/bcgit/bc-java и https://github.com/bcgit/bc-kotlin).


    На верхнем уровне абстракции создание keyStore для целей тестирования может выглядеть следующим образом:


    KeyStore generateKeyStore(String password) { 
        X509Certificate2 ca = X509Certificate2Builder()
            .setSubject("CN=CA")
            .build()
    
         X509Certificate2 int = X509Certificate2Builder()
            .setIssuer(ca)
            .setSubject("CN=Intermediate")
            .build()
    
        X509Certificate2 cert = X509Certificate2Builder()
            .setIssuer(int)
            .setSubject("CN=Child")
            .setIntermediate(false)
            .build()
    
        return KeyStoreBuilder()
            .addTrustedCertificate("test-ca", ca)
            .addPrivateKey("test-pk", cert.keyPair, password, asList(cert, int, ca))
            .build()
    }

    Здесь мы, соответственно, можем увидеть наш доверительный корневой сертификат CA, сертификат cert, который выпущен промежуточным звеном, и нашу пару ключей (приватный и публичный), которые хранятся вместе с сертификатом в поле KeyPair keyPair класса X509Certificate2, расширяющем X509Certificate.


    Благодаря билдерам подобного вида, мы легко сможем собрать различные тесткейсы для покрытия всех возможностей подключения к нашей системе.


    Соответственно, остается нюанс в непосредственном написании двух билдеров — X509Certificate2Builder и KeyStoreBuilder. Конечно же в java существует java.security.KeyStore.Builder, но он весьма общего плана и имеет единственный ценный метод — newInstance, а хочется чего-то более явного для добавления доверенных сертификатов и приватных ключей. По этой причине был написан свой билдер.


    Свой билдер использует в конечном итоге метод setEntry класса KeyStore для единообразного добавления сущностей доверенных сертификатов и приватных ключей, используя различные имплементации типа Entry (TrustedCertificateEntry или PrivateKeyEntry).


    И поскольку KeyStore#setEntry имеет сигнатуру setEntry(String alias, Entry entry, ProtectionParameter protParam) с 3 параметрами, мы их можем объединить в один класс item и в итоге в методе KeyStoreBuilder#build() останется лишь следующий код:


    public KeyStore build() {
        try {
            KeyStore keyStore = KeyStore.getInstance("JKS", "SUN");
            keyStore.load(null, null);
            for (Item it : entries.values()) {
                keyStore.setEntry(it.alias, it.entry, it.parameter);
            }
            return keyStore;
        } catch (IOException | GeneralSecurityException e) {
            throw new RuntimeException(e.getMessage(), e);
        }

    А при добавлении сущностей в entries мы будем использовать сигнатуру аналогичную KeyStore#setEntry, но публичным интерфейсом, использующим этот setEntry будут более понятные методы addPrivateKey


    public KeyStoreBuilder addPrivateKey(String alias, KeyPair pair, String password, List<X509Certificate> chain) {
        addEntry(alias,
                 new KeyStore.PrivateKeyEntry(pair.getPrivate(), chain),
                 new KeyStore.PasswordProtection(password.toCharArray()));
        return this;
     }

    и метод addTrustedCertificate,


    public KeyStoreBuilder addTrustedCertificate(String alias, X509Certificate cert) {
        addEntry(alias, new KeyStore.TrustedCertificateEntry(cert), null);
        return this;
    }

    которые мы использовали выше при генерации keyStore.


    C билдером X509 сертификата дела обстоят чуть сложнее, поскольку основная часть логики там будет сосредоточена в методе build(). Чтобы не загромождать статью болейрплейт кодом сеттеров, которые просто устанавливают значения полей билдера, я сразу перейду к реализации метода build() для X509Certificate2, опустив методы, связанные с установкой значений в билдер, и использую вместо них локальные переменные:


    public X509Certificate2 build() {
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    
        final X500Name subject = new X500Name(this.subject);
        final KeyPair subjectKeyPair = newKeyPair(subjectKeyStrength);
        final boolean selfSigned = this.issuer == null;
        final X500Name issuer = selfSigned ? subject : new X500Name(this.issuer.getSubjectDN().getName());
        final KeyPair issuerKeyPair = selfSigned ? subjectKeyPair : this.issuer.getKeyPair();
    
        // Create x509 certificate
        final Date notBefore = new Date();
        final Date notAfter = new Date(notBefore.getTime() + 20L * 365 * 24 * 60 * 60 * 1000);
        final BigInteger serialNumber = BigInteger.valueOf(SERIALS.incrementAndGet());
        final SubjectPublicKeyInfo subjectKeyInfo = SubjectPublicKeyInfo.getInstance(subjectKeyPair.getPublic().getEncoded());
        final X509v3CertificateBuilder builder = new X509v3CertificateBuilder(
            issuer, serialNumber, notBefore, notAfter, subject, subjectKeyInfo);
    
        // Get the certificate back
        final AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withRSA");
        final AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
    
        try {
            final BcX509ExtensionUtils extensionUtils = new BcX509ExtensionUtils();
            builder.addExtension(
                new ASN1ObjectIdentifier("2.5.29.14"), // Subject Key Identifier
                false,
                extensionUtils.createSubjectKeyIdentifier(
                    SubjectPublicKeyInfo.getInstance(subjectKeyPair.getPublic().getEncoded()))
            );
            builder.addExtension(
                new ASN1ObjectIdentifier("2.5.29.35"), // Authority Key Identifier
                false,
                extensionUtils.createAuthorityKeyIdentifier(
                    SubjectPublicKeyInfo.getInstance(issuerKeyPair.getPublic().getEncoded()))
            );
            builder.addExtension(
                new ASN1ObjectIdentifier("2.5.29.19"), // Basic Constraints
                false,
                new BasicConstraints(intermediate)); 
            AsymmetricKeyParameter privateKey = PrivateKeyFactory.createKey(issuerKeyPair.getPrivate().getEncoded());
            ContentSigner signer = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
            .build(privateKey);
            X509Certificate cert = new JcaX509CertificateConverter()
                .setProvider("BC")
                .getCertificate(builder.build(signer));
    
            return new X509Certificate2(cert, subjectKeyPair);
        } catch (IOException | OperatorCreationException | CertificateException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    
    private static KeyPair newKeyPair(int subjectKeyStrength) {
        try {
            if (subjectKeyStrength <= 0) {
                subjectKeyStrength = DEFAULT_KEY_STRENGTH; // 2048 
            }
    
            // Create the public/private rsa key pair
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA", "BC");
            keyPairGen.initialize(subjectKeyStrength, SecureRandom.getInstance("SHA1PRNG"));
            return keyPairGen.generateKeyPair();
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    Все недостающие в стандартной библиотеке классы импортированы из bouncycastle.
    В начале работы необходимо проинициализировать провайдер bouncycastle, если это еще не было сделано ранее.


    Пара ключей генерируется с использованием java.security.KeyPairGenerator, который позволяет создать ключи с заданный алгоритмом и хеш-функцией. В данном примере был использован RSA c SHA1PRNG.


    Далее мы объявляем поля, необходимые для сертификата, такие как даты начала и окончания, эмитент и серийный номер.


    Затем мы добавляем расширения для сертификата, описывающие субъект и указание корневого сертификата, подписавшего его. В конце концов, получаем сертификат.


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


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


    В результате, например, для проверки безопасного соединения в Spring boot приложении без использования стандартного пути с пропертями достаточно будет создать шаблонное приложение с вебом, например, через Spring Initializr и


    добавить в главный класс следующий код
    @SpringBootApplication
    @RestController
    public class Server {
        static String certFile = System.getProperty("user.home") + "/cert.jks";
        static String defaultPassword = "changeit";
    
        @GetMapping("/hello")
        public String hello() {
            System.out.println("request /hello");
            return "hello";
        }
    
        @Bean
        WebServerFactoryCustomizer<ConfigurableWebServerFactory> containerCustomizer() {
            TomcatConnectorCustomizer customizer = (connector) -> {
                connector.setPort(8080);
                connector.setSecure(true);
                connector.setScheme("https");
                Http11NioProtocol proto = (Http11NioProtocol) connector.getProtocolHandler();
                proto.setSSLEnabled(true);
                proto.setClientAuth("true");
                proto.setKeystoreFile(certFile);
                proto.setKeystorePass(defaultPassword);
                proto.setTruststoreFile(certFile);
                proto.setTruststorePass(defaultPassword);
                proto.setKeystoreType("JKS");
            };
    
            return (ConfigurableWebServerFactory factory) -> {
                ConfigurableTomcatWebServerFactory tomcatWebServerFactory = (ConfigurableTomcatWebServerFactory) factory;
                tomcatWebServerFactory.addConnectorCustomizers(customizer);
            };
        }
    
        public static void main(String[] args) {
            SpringApplication.run(Server.class, args);
        }
    }
    
    class Client {
        RestTemplate restTemplate() throws Exception {
            SSLContext sslContext = new SSLContextBuilder()
                    .loadTrustMaterial(new URL(Server.certFile), Server.defaultPassword.toCharArray())
                    .build();
            SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
            HttpClient httpClient = HttpClients.custom()
                    .setSSLSocketFactory(socketFactory)
                    .build();
            HttpComponentsClientHttpRequestFactory factory =
                    new HttpComponentsClientHttpRequestFactory(httpClient);
            return new RestTemplate(factory);
        }
    
        public static void main(String[] args) {
            String response = new RestTemplate().getForObject("https://localhost:8080/hello", String.class);
            System.out.println("received " + response);
        }
    }

    В таком примере используется уже готовый cert.jks файл, если надо будет создать его на лету можно воспользоваться примерами, которые я приводил выше и доработать для себя так как будет удобно.

    Технологический Центр Дойче Банка
    Компания

    Похожие публикации

    Комментарии 7

      0
      В рамках моей работы в ТехЦентре Дойче Банка мы в обязательном порядке для всех межсервисных взаимодействий используем SSL-сертификаты — даже в UAT окружении.

      Конечно как генерить сертификаты программно, это может быть и интересно программеру, который с этим никогда не сталкивался. Хотя на мой взгляд банальная задача, если понимаешь структуру и назначение сертификатов.


      Но почему то не упомянуты типичные проблемы сопровождения этого зоопарка TLS ключей на все сервисы и автоматизации управление/поддержки этой инфраструктуры (обновления/хранения/генерации новых). Когда сервисов мало — это можно и вручную (хотя чревато человеческим фактором).
      Вот про автоматизацию бы с удовольствием почитал.


      С проблемой настроек всякой маршрутизации не сталкивались? (стандартный HostnameVerifier в JDK)
      А с тем что TLS добавляет от 50 до 100 ms — с такой проблемой не сталкивались?
      казалось бы мелочь… но… (например СБП дает 3 сек на валидацию перевода. И приходится за каждый 10 ms бороться)

        0
        >> мой взгляд банальная задача
        Я поэтому и писал, что это введение. Разумеется тем, кто знаком с этой сферой в статье будет мало новой информации. Но большинство разработчиков, на мой взгляд, не так частно занимаются подобными вещами, поэтому им это может оказаться интересным.

        >> не упомянуты типичные проблемы сопровождения этого зоопарка TLS ключей на все сервисы
        Использование certbot, certnanny, настройки своего PKI и все подобное несколько выходит за рамки статьи о использовании сертификатов в java, но спасибо за идею, я подумаю о том, что б рассмотреть это отдельно.

        С проблемами маршрутиризации не приходилось сталкиваться, не могу ни чего сказать к сожалению по этому вопросу.

        Накладные расходы безусловно присутствуют, но тут речь идет о системах расчета рисков в которых я работаю, где конечно же скорость тоже критична, но в меньшей мере чем в системах выставления заявок или HTF, не знаю как решается там этот вопрос, но мы задержки подобного рода в TLS можем себе позволить.
          0
          С проблемами маршрутиризации не приходилось сталкиваться, не могу ни чего сказать к сожалению по этому вопросу.

          Стандартная реализация HostnameVerifier проверяет соответствие имени хоста в сертификате клиента входящему соединению. Из за этого возникают типичные проблемы TLS handshake между клиентом и сервером при использования прокси (между двумя внутренними сетями, например)


          Типичное решение вида
          ((HttpsURLConnection) conn).setHostnameVerifier((String hostname, SSLSession session) -> true);
          это решение программиста вида: "на отвяжись от меня с этими проблемами". Нифига не безопасно, но быстро.


          Правильного и одновременно "легкого" решения, я, например, не знаю. Каждый раз индивидуально приходится. То же хотел бы услышать чужой опыт.

            0
            поддерживаю, хотелось бы тоже этот момент разузнать, а то был случай, тоже возился с этим HostnameVerifier
        0
        Использование certbot, certnanny

        Это скорее инструменты. К тому же это все же инструменты для Let's Encrypt провайдера сертификатов
        Использовать Let's Encrypt для служебных сертификатов в инфраструктуре с аутентификацией по TLS сертификату клиента — это… "странно".


        Для себя проще свои написать, чем пытаться эти адаптировать под другой CA (свой или еще какой).


        То же бы интересно чужой опыт узнать по автоматизации.
        И от типичной проблемы "ой, мы забыли обновить сертификат (год прошел и вообще забыли про существование) и сервис неожиданно встал — это не спасает.


        Дальше вариантов масса, от бумажного ежедневника, до спец самописной учетной системы


        То же бы с удовольствием про чужой опыт в этом прочитал.

          0
          Да, разумеется что Let's Encrypt имеет смысл использовать лишь для личных проектов. Я привел его просто в качестве примера инструмента для управления сертификатами.
          Просто настройкой инфраструктуры для управлением сертификатами, управлением своим внутренним CA и т.п. в основном занимаются другие люди.

          Ну и плюс по понятным причинам я не могу раскрывать всех деталей в данном вопросе. Но если говорить в общем — управление сертификатами производиться сейчас в автоматическом режиме, как раз с использованием своих самописных решений и ряда внешних утилит например того же certnanny
          0

          Java из коробки поддерживает PKCS12 — нет никаких проблем ни при чтении этого формата, ни при создании (но не через keytool).
          И, согласно JEP 229 начиная с JDK9 в качестве дефолтного формата для кейстора/трастстора выбран PKCS12.


          А почему всё-таки был выбран формат JKS? Какой от этого профит?

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое