带有TLS / SSL的Java服务相互认证简介


在开发应用程序的过程中,越来越多地出现授权和身份验证以及一般的信息安全方面的问题,每个人都有不同的狂热态度来解决这些问题。鉴于最近几年我的活动领域是金融部门的软件开发,特别是风险计算系统,我无法绕过这一事实,特别是考虑到相关的教育。因此,在本文的框架中,我决定涵盖这一主题,并告诉我在设置应用程序过程中必须面对的问题。


介绍


. , - - nginx apache, dmz. . , -. - , - , GUI . - , LDAP , .


RADIUS, Kerberos OAuth/OpenID . - , base64, - JsonWebToken, . , , , , , , .


SSL- — UAT . Java JKS, .


, — , . , , — .


, , , .





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