Как вручную настроить SSL Handshake при загрузке Spring - PullRequest
0 голосов
/ 20 апреля 2019

Я настраиваю сервер, который должен взаимодействовать с другими доверенными серверами, и я использую SSL Handshake при весенней загрузке с apache tomcat.

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

Свойства приложения одинаковы для второго сервера, только с разными KeyStore и Truststore.

Application.properties

server.ssl.key-store=classpath:booker.p12
server.ssl.key-store-password=pass
server.ssl.key-password=pass
server.ssl.key-alies=booker
server.ssl.trust-store=classpath:bookerTrust.p12
server.ssl.trust-store-password=pass
server.ssl.trust-store-type = PKCS12
server.ssl.client-auth=need
server.port=8111

Настройка RestTemplate в main.java

    @Bean
    public RestTemplate restTemplate() throws Exception {
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
        restTemplate.setErrorHandler(
                new DefaultResponseErrorHandler() {
                    @Override
                    protected boolean hasError(HttpStatus statusCode) {
                        return false;
                    }
                });

        return restTemplate;
    }

    private ClientHttpRequestFactory clientHttpRequestFactory() throws Exception {
        return new HttpComponentsClientHttpRequestFactory(httpClient());
    }

    private HttpClient httpClient() throws Exception {
        // Load our keystore and truststore containing certificates that we trust.
        SSLContext sslcontext =
                SSLContexts.custom().loadTrustMaterial(trustResource.getFile(), trustStorePassword.toCharArray())
                        .loadKeyMaterial(keyStore.getFile(), keyStorePassword.toCharArray(),
                                keyPassword.toCharArray()).build();
        SSLConnectionSocketFactory sslConnectionSocketFactory =
                new SSLConnectionSocketFactory(sslcontext, new HostCustomVerifer(trustResource,trustStorePassword,trustStoreType));
        return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
    }
}

Связь в контроллере:

@RestController
@RequestMapping(value = "/server1")
public class ClientController {

    @Autowired
    RestTemplate restTemplate;

    @RequestMapping(value = "/data", method = RequestMethod.GET)
    ResponseEntity<?> getMessage(ServletRequest request) {
    return ResponseEntity.ok("Server1 successfully called!");
    }


    @RequestMapping(value = "/sdata", method = RequestMethod.GET)
    public ResponseEntity<String> getMsData() {
        try {
            String msEndpoint = https://localhost:8111/api/server2/data";
            return new ResponseEntity<String>( restTemplate.getForObject(new URI(msEndpoint), String.class), HttpStatus.OK) ;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return new ResponseEntity<String>("Exception occurred.. so, returning default data", HttpStatus.BAD_GATEWAY);
    }


}

Я ожидаю, что этот сценарий потерпит неудачу, однако он будет успешным:

Сервер1: Доверительный RootCA, Сервер1, Сервер2

Сервер2: Доверительный RootCA, Сервер2

Сервер1 начинает связь с Сервером2 и каким-то образом преуспевает

Я ожидаю, что произойдет сбой, потому что Server2 не доверяет Server1

1 Ответ

0 голосов
/ 31 мая 2019

Однако это не лучшее решение, оно помогло мне прервать связь, если не доверять обоим.Я реализовал javax.net.ssl.HostnameVerifier и сделал свой CustomNameVerifier и поместил его в SSLConnectionSocketFactory при создании RestTemplate.Код, следующий за

Код компонента должен идти в main (с @SpringBootApplication)

@Bean
public RestTemplate template() throws Exception{

    RestTemplate restTemplate = new RestTemplate();

    KeyStore keyStore;
    KeyStore trustStore;
    HttpComponentsClientHttpRequestFactory requestFactory = null;
    try {
        keyStore = KeyStore.getInstance(keyStoreType);
        ClassPathResource classPathResource = new ClassPathResource(keyStoreLocation.getFilename());
        InputStream inputStream = classPathResource.getInputStream();
        keyStore.load(inputStream, keyStorePassword.toCharArray());

        trustStore = KeyStore.getInstance(trustStoreType);
        ClassPathResource classPathResourceTrust = new ClassPathResource(trustStoreLocation.getFilename());
        InputStream trustInput = classPathResourceTrust.getInputStream();
        trustStore.load(trustInput, trustStorePassword.toCharArray());

        javax.net.ssl.SSLContext sslContext = SSLContextBuilder.create()
                .loadKeyMaterial(keyStore, keyStorePassword.toCharArray())
                .loadTrustMaterial(trustStore, null).setProtocol("TLS")
                .build();
        SSLConnectionSocketFactory sslConnectionSocketFactory =
                new SSLConnectionSocketFactory(sslContext,
                new HostCustomVerifer(trustStoreLocation,trustStorePassword,trustStoreType));

        HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory)
                .setMaxConnTotal(Integer.valueOf(200))
                .setMaxConnPerRoute(Integer.valueOf(200))
                .build();

        requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        requestFactory.setReadTimeout(Integer.valueOf(10000));
        requestFactory.setConnectTimeout(Integer.valueOf(10000));

        restTemplate.setRequestFactory(requestFactory);
    } catch (Exception exception) {
        System.out.println("Exception Occured while creating restTemplate "+exception);
        exception.printStackTrace();
    }
    return restTemplate;
}

CustomNameVerifier выглядит следующим образом: (Даже если вы получаете другое сообщение об ошибке (это должен быть недоверенный сертификат)вы получаете за ошибку проверки имени.

public class HostCustomVerifer implements HostnameVerifier {

private String trustStorePassword;
private String trustResource;
private String trustType;

@Autowired
public HostCustomVerifer(@Value("${server.ssl.trust-store}") Resource trustResource,@Value("${server.ssl.trust-store-password}") String trustStorePassword,@Value("${server.ssl.trust-store-type}") String trustType) {
    this.trustStorePassword = trustStorePassword;
    this.trustResource = trustResource.getFilename();
    this.trustType = trustType;
}

@Override
public boolean verify(String hostName, SSLSession sslSession) {
    try {
        X509Certificate list[] =  (X509Certificate[]) sslSession.getPeerCertificates();
        int size = list.length;
        KeyStore trustStore = KeyStore.getInstance(trustType);
        ClassPathResource classPathResourceTrust = new ClassPathResource(trustResource);
        InputStream trustInput = classPathResourceTrust.getInputStream();
        trustStore.load(trustInput, trustStorePassword.toCharArray());  
        for (X509Certificate x509Certificate : list) {
            ArrayList<String> content = Collections.list(trustStore.aliases());
            for (String alias : content) {
                X509Certificate cert = (X509Certificate) trustStore.getCertificate(alias);
                boolean isSelfSigned = cert.getIssuerDN().equals(cert.getSubjectDN());
                if(cert.equals(x509Certificate) && (!isSelfSigned || size==1))
                    return true;
            }
        }
    } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
        e.printStackTrace();
    }
    return false;
}

}

Если кто-то вдруг наткнулся на эту тему и нашел лучший способ, вы можете сообщить мне.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...