Pengantar otentikasi timbal balik dari layanan di Jawa dengan TLS / SSL


Masalah otorisasi dan otentikasi dan, secara umum, aspek keamanan informasi semakin sering muncul dalam proses pengembangan aplikasi, dan masing-masing dengan tingkat fanatisme yang berbeda mendekati masalah ini. Mengingat fakta bahwa beberapa tahun terakhir bidang kegiatan saya telah mengembangkan perangkat lunak di sektor keuangan, khususnya, sistem perhitungan risiko, saya tidak bisa melewati ini, terutama mengingat pendidikan yang relevan. Oleh karena itu, dalam kerangka artikel ini saya memutuskan untuk membahas topik ini dan memberi tahu apa yang harus saya hadapi dalam proses mengatur aplikasi kami.


pengantar


. , - - 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.


KeystoreTrustStore
( )( CA root)
SSL
keystoretrustStore
javax.net.ssl.keyStore keystorejavax.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, , .


bouncyastle, 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 , , .

Source: https://habr.com/ru/post/undefined/


All Articles