Skip to content

Embedded jetty server. Защищенный протокол HTTPS (часть 4)

Синопсис

В предыдущих постах серии embedded jetty server (часть 1, часть 2 и часть 3) мы поднимали встроенный jetty сервер. Как к любому веб серверу клиенты к нему коннектятся по протоколу http. Общение клиента с сервером по протоколу http чревата перехватом злоумышленником сообщения, которое передается от клиента к серверу или от сервера к клиенту, поскольку протокол http является открытым и все, что мы передаем с его помощью, может быть перехвачено и прочитано. Для того, чтобы злоумышленник не смог прочесть наше сообщение, мы его должны передавать не по открытому каналу, а по защищенному. Защищенный канал поднимается через протокол https, который является надстройкой над протоколом http, и который использует протокол SSL для шифрования.
В этом посте мы рассмотрим как устанавливается защищенное соединение между клиентом и сервером, стоит ли доверять серверу, с которым клиент устанавливает соединение, а поскольку это мы поднимаем сервак, то мы должны предоставить клиенту доказательства, что мы его не собираемся обманывать, то есть речь еще пойдет о сертификатах. Даже если соединение супер надежное, толку от этого будет мало для клиента если он устанавливает соединение с подставным ресурсом со злым умыслом, думая что ресурс надежный. Этот пост по большей части ориентирован на использование защищенного канала, а о том что такое сам защищенный канал мы рассматривали в статье Шифрование методом Диффи-Хеллмана

Готовый проект можно взять с gitHub по ссылке: https://github.com/dev-blogs/SecureJettyServer

Что такое сертификат и для чего он нужен

Допустим есть производитель продукции и ее потребитель. В самом общем понимании сертификат предназначен для подтверждения соответствия качества этого продукта специальной организацией, которая называется сертификационным удостоверяющим центром (УЦ) или Certificate Authority (CA). Эта организация должна быть независимой как от потребителя так и производителя. В нашем случае, под продуктом понимается контент, потребитель продукции это клиент, а производитель или поставщик это владелец сервера к которому клиент будет коннектится. По мимо всего прочего, удостоверяющий центр проверяет, что владелец сервера реально существует и является тем за кого он себя выдает, это исключает перехвата методом man in the middle attack.
Сертификат может быть получен двумя путями. Его можно либо изготовить самому, это будет само-подписанный (self-signed) сертификат, либо получить от удостоверяющего центра (УЦ) или Certificate Authority (CA). Вообще вся эта идея с сертификатами вытекает именно из второго случая, чтобы клиент удостоверился, что владелец сервера реально существует и не украдет его личные данные и сертификационный удостоверяющий центр (УЦ) ручается за это, а случае если будут нарушения со стороны владельца, то его сертификат может быть отозван. А вот первый случай с само-подписанным сертификатом должен настораживать клиента, другими словами клиент должен относится к само-подписанному сертификату, как вы относитесь к барыгам на рынке, которые обещают, что вас не кинут, и если случись, что, то обратиться будет не к кому, ибо никто за владельца ответственности не несет, сертификат ведь само-подписанный, и отозван быть не может, просто не кем. Поэтому когда браузер видит, что сертификат или само-подписанный или не соответствует владельцу ресурса, он матюкнется:
1
То есть браузер сертификату данного ресурса не доверяет. Тут вы можете добавить эту страницу в исключения, нажав на I Understand the Risks если точно знаете, что ресурс безопасный, например если вы его используете для тестов. Поэтому само-подписанный сертификат не стоит использовать в продакшене, он годится только для тестирования, а потом, когда ваш сервер будет перенесен на продакшен, там уже должен быть сертификат полученный от реального сертификационного удостоверяющего центра (УЦ) или Certificate Authority (CA).
В этом посте мы не будем заморачиваться с реальными сертификатами, так как мы просто хотим поднять защищенный канал между клиентом и сервером, а для этой задачи пойдет любой сертификат, в том числе само-подписанный, в этом случае сам канал будет шифроваться как положено, просто клиент не сможет убедиться в честности владельца сервера, но так как владельцем сервера являемся мы сами, то в этом случае аутентификацией владельца сервера можно пренебречь.

Создаем сертификат с помощью openSSL

Далее речь будет идти о само-подписанном (self-signed) сертификате. Для того, чтобы создать и подписать сертификат, можно воспользоваться либо тулой keytool либо OpenSSL. Я буду объяснять на примере тулы OpenSSL. И так для того, чтобы поднять защищенное соединение между клиентом и сервером, нам нужно для сервака сгенерировать сертификат:

openssl req -x509 -nodes -days X -newkey rsa:2048 -keyout cert.key -out cert.crt

Generating a 2048 bit RSA private key
....+++
...............................................+++
writing new private key to 'cert.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:AA
State or Province Name (full name) [Some-State]:dev-blogs
Locality Name (eg, city) []:dev-blogs
Organization Name (eg, company) [Internet Widgits Pty Ltd]:dev-blogs
Organizational Unit Name (eg, section) []:dev-blogs
Common Name (e.g. server FQDN or YOUR name) []:dev-blogs
Email Address []:zheka@dev-blogs.com

Здесь главное правильно ответить на вопрос Common Name (e.g. server FQDN or YOUR name) где нужно указать название нашего сервера для которого мы создаем сертификат.
После выполнения этой команды появятся два файла: cert.key и cert.crt — ключ и сертификат соответственно. Этот сертификат уже само-подписанный, то есть нам его не нужно отдавать ни в какие центры сертификации, чтобы нам его подписали ибо он уже подписанный нами. Выше мы уже выяснили, что это не годится для продакшена, где нужна полноценная подпись от реального центра сертификации, а для нашего случая ничего страшного, так как мы только рассматриваем пример.
Вот как выглядит ключ:

cat cert.key

-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/8rf9BTZTgvZj
E3sGG5/8MOnTOOwdzjvf6w2nj3yx7HsYdq99OfbFi8tWIM9gGCA7/1Po2YMC7G6c
Ehn4Evnh1MUvrlgmSbghUrcYfD0ZFTkGTaDVpz8aq85ZO/RAy3GsygFwbx4oCozq
rjAk7RVjCJQPc+R8CnVLXWkzmuwlqyLOZi0YbQFoi5E0svZhcrSHiH0BCv3MxTV2
HKIZ2MBCR3wxX/y1gScauztip/CyMaB5jwC5gegVx6tdb4olGtuTyYf11R4DxKvG
GuaTzh1N3qZ4jn1rQ5ssG8Bc/PjJ3SKhLc+nGhixbo8S/ev9ab6sbjJBlLIeRW4f
T7qQOPmNAgMBAAECggEAZ/ZmgLxZ+FFucasTbvN3a3hyrNyfxmT1OQpuktq8hwG2
ikF+Oy0x4z0cHMIYj8uBaf2YzP1CYEr+fbmwJxTgEtjTFEgxpks3V5UdZj2kOfms
sSVKf23eV+vGD77RRGbsYtHmhacphjZwrIs7uERQ2RrEVrpGfoiLjw5A9JpsXNmr
JwEa//cvbRw5aObu7OW++bi2o77sF0HFiLHiZc+b2eySkN1DAMaXwdTK3EFMAUfQ
QpUn/eprnP/d64vsxOcWZcKsJPuArxF68TSLukkvVSRWP56uv6utlF+rN7jxfHcz
c+7ofdZccqZuO8BtWD/ujStM2yvo2ee2hO280jfDAQKBgQDyLv04T/8KlARLHhye
2LskOEZoFd6jRUs5v8ovxayl/GRmeRBoSBLGu5ebmNFociV4a+pQIDguPqLpcK0w
fS7eMujiVD7CgDesoDPpnTWxyt5Y+ao6GtpqvIfAx8lJprVtOLKwEJ0kHWkEtGW1
CN+F/7KqYn1DD9tAvGkJcuTs1QKBgQDK5g7DCmsy+hAGhvpU2jVuCqi4r++3NAE/
UJtpGXr2Ql1PyAFuPxbYyA55Love8xw+IkJ4ue1I7qihiEvIBg4QjnM9jIfVsmKE
dzNXyUSh/UqmNF6mN9CLMTpXOnkG2ts0FlyR6Ip/Y5fpF3bbrJyioG4HkK0gqY6U
sALHCiPV2QKBgGN9kEMRAforgnQFg8ZxzLd9ctcRnOu/7L5lnr7vWxS5UdmTBH2C
lkwUBp1CM0zQD7qfwpt2EP5WdwClbFCOLLHVgjxwVXP7zmNZUNo43jUS3TL6JcdG
/tF4b+Bvcq6SENg0wZt4x8VtW5Y2Mc4kPgdDWveKDGF/Yxf+Drx1+budAoGBAI8Y
KRkM5AWTrnGsCCiHrF7rTYrTKfC+0JJiuvgobDv5Ge4GVFv4SpignvSAC001j5mS
d+bE2cAx8OPbR/bNrmWm0Ud7MOFZaXmax68F102XYWpOTOJVQtKn6UlTh7954lad
3rNYmTS2fJB7Z+wNlHptQqw7MJ50doYOjCbr09IhAoGBANFWangW1ZxHOTQBJ3Rg
Uqtg9YLoLiytQcfzDN8acgtR6z8cocQ3SNDWFMfAv/2IfM3Xm64K4Sgs7ssy+Td4
YEKTlzBzRIgEe0Bs7U2aZXXl4PZyBoxrl9O4Zw0odymMuBsXr3pUJ/45QRyErMAp
sgIeaJbcxMIu5OXOZ5Ahzf/R
-----END PRIVATE KEY-----

А вот как выглядит сертификат:

cat cert.crt

-----BEGIN CERTIFICATE-----
MIIDhTCCAm2gAwIBAgIJAPV8+//LhjRJMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
BAYTAlJVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNjAxMjQxMDAw
MTdaFw0xNjAyMjMxMDAwMTdaMFkxCzAJBgNVBAYTAlJVMRMwEQYDVQQIDApTb21l
LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV
BAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL/y
t/0FNlOC9mMTewYbn/ww6dM47B3OO9/rDaePfLHsexh2r3059sWLy1Ygz2AYIDv/
U+jZgwLsbpwSGfgS+eHUxS+uWCZJuCFStxh8PRkVOQZNoNWnPxqrzlk79EDLcazK
AXBvHigKjOquMCTtFWMIlA9z5HwKdUtdaTOa7CWrIs5mLRhtAWiLkTSy9mFytIeI
fQEK/czFNXYcohnYwEJHfDFf/LWBJxq7O2Kn8LIxoHmPALmB6BXHq11viiUa25PJ
h/XVHgPEq8Ya5pPOHU3epniOfWtDmywbwFz8+MndIqEtz6caGLFujxL96/1pvqxu
MkGUsh5Fbh9PupA4+Y0CAwEAAaNQME4wHQYDVR0OBBYEFDcCavC5e+91kV0eWFHx
Wm/WYvSjMB8GA1UdIwQYMBaAFDcCavC5e+91kV0eWFHxWm/WYvSjMAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAK5pCiC9eZZtHVyLT0rQIQQjECjfPO79
tN4N9YbgIGU06hmet/LsyR7uQfvt8oQ7FKW/8S5vBvolOxLX9iwAFok7b1cbSO3j
11UD5gmG3LcjfhxozlLfmoS8V2zxe0fJvpq9BLcsK95EPNHcvf5ps3PkeqZ33VvB
ECY5+5tahEOw3m3Kayd4GdE4ozu3JcvL0pjsM6uzum1nKo0dvDNp8CRM1SMlr54e
WqCju18+tn1vJcnarAg5ucIK8HWjVn5m1gAY9Nr/AnuS3gp0yQmYxczs6yN5vkIn
p6OgY2xz29gLpK4TekNo54bIrdM5spk2vjGp332daXOJOJLz4f3Gj/Q=
-----END CERTIFICATE-----

Разберем параметры команды.
req — означает, что сертификат надо подписать (Certificate Signing Request (CSR))
X509 — создать сертификат формата X.509
days — через сколько дней истекает сертификат

Сертификат готов, теперь все что нам осталось сделать это завернуть его вместе с ключом в контейнер keystore и всунуть в Jetty. Но прежде чем мы положим сертификат и его ключ в keystore сначала их нужно скомбинировать в формат PKCS12 который за тем загрузить в keystore.
Скомбинируем из файла ключа cert.key и из файла сертификата cert.crt в формата PKCS12 в файл jetty.pkcs12:

openssl pkcs12 -inkey cert.key -in cert.crt -export -out jetty.pkcs12

Enter Export Password:
Verifying - Enter Export Password:

И последнее что нам осталось сделать это загрузить файл формата PKCS12 в файл JSSE keystore. Для этого уже воспользуемся утилитой keytool:

keytool -importkeystore -srckeystore jetty.pkcs12 -srcstoretype PKCS12 -destkeystore keystore

Enter destination keystore password:  
Re-enter new password: 
Enter source keystore password:  
Entry for alias 1 successfully imported.
Import command completed:  1 entries successfully imported, 0 entries failed or cancelled

Структура проекта серверной части

JettySecureRestCalls
    ├──src
    │   └─main
    │      ├─java
    │      │  └─com
    │      │     └─dev-blogs
    │      │         ├─JettyWebSocket
    │      │         │   └─JettyStarter.java
    │      │         └─service
    │      │             ├─impl
    │      │             │   └─TestServiceImpl.java
    │      │             └─TestService.java
    │      └─webapp
    │         └─WEB-INF
    │            ├─spring
    │            │   └─spring-context.xml
    │            └─web.xml
    └──build.gradle

Java код

JettyStarter.java

package com.dev_blogs.JettyServer;

import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.WebAppContext;

import java.net.URL;
import java.security.ProtectionDomain;

public class JettyStarter {
    public static void main(String[] args) {
        Server server = new Server();

        // HTTP connector
        ServerConnector connector = new ServerConnector(server);
        connector.setPort(8080);

        // HTTPS configuration
        HttpConfiguration https = new HttpConfiguration();
        https.addCustomizer(new SecureRequestCustomizer());

        // Configuring SSL
        SslContextFactory sslContextFactory = new SslContextFactory();

        // Defining keystore path and passwords
        sslContextFactory.setKeyStorePath("/home/zheka/practice/ssl/2/keystore");
        sslContextFactory.setKeyStorePassword("123456");
        sslContextFactory.setKeyManagerPassword("123456");

        // Configuring the connector
        ServerConnector sslConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"), new HttpConnectionFactory(https));
        sslConnector.setPort(8443);

        // Setting HTTP and HTTPS connectors
        server.setConnectors(new Connector[]{connector, sslConnector});
        // --

        // add handler
        ResourceHandler resource_handler = new ResourceHandler();
        resource_handler.setDirectoriesListed(true);
        resource_handler.setWelcomeFiles(new String[]{"index.html"});
        resource_handler.setResourceBase(".");

        ProtectionDomain domain = JettyStarter.class.getProtectionDomain();
        URL location = domain.getCodeSource().getLocation();

        // add context
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        webapp.setWar(location.toExternalForm());

        HandlerList handlers = new HandlerList();
        // first element  is webSocket handler
        // second element is first handler,
        // third element is webContext
        handlers.setHandlers(new Handler[]{resource_handler, webapp});

        server.setHandler(handlers);

        try {
            server.start();
            server.join();
        } catch (Throwable t) {
            t.printStackTrace(System.err);
        }
    }
}

В строке 28 мы указали путь к файлу keystore где хранятся ключ и сертификат.

TestService.java

package com.dev_blogs.service;
 
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/test-service/")
public interface TestService {
 
    @GET
    @Path("/test")
    public String execute();
}

TestServiceImpl.java

package com.dev_blogs.service.impl;

import com.dev_blogs.service.TestService;

public class TestServiceImpl implements TestService {
 
    public String execute() {
        return "test Servlet";
    }
}

Билд скрипт build.gradle

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'war'
apply plugin: 'jetty'

repositories {
	mavenCentral()
}

List jetty_libraries = ['org.eclipse.jetty:jetty-webapp:9.1.3.v20140225',
	'org.eclipse.jetty:jetty-server:9.1.3.v20140225',
	'org.eclipse.jetty.websocket:websocket-server:9.1.3.v20140225'
]

configurations {
    embeddedJetty
}

dependencies {
	embeddedJetty jetty_libraries
}

dependencies {
	compile jetty_libraries
	compile 'org.springframework:spring-web:3.2.7.RELEASE'
	compile 'org.apache.cxf:cxf-rt-frontend-jaxrs:3.0.0-milestone1'
	testCompile 'junit:junit:4.11'
}

mainClassName = "com.dev_blogs.JettyServer.JettyStarter"

war {
	from {
		configurations.embeddedJetty.collect {
			project.zipTree(it)
		}
    }
	exclude "META-INF/*.SF", "META-INF/*.RSA", "about.html", "about_files/**", "readme.txt", "plugin.properties", "jetty-dir.css", "META-INF/maven/org.eclipse.jetty/*/pom.*"
	from "$buildDir/classes/main"
	manifest {
		attributes (
			'Main-Class': 'com.dev_blogs.JettyServer.JettyStarter',
			'Manifest-Version': '1.0',
			'Gradle-Version': 'Gradle 1.7'
		)
	}
}

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

./gradlew build

:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:startScripts UP-TO-DATE
:distTar UP-TO-DATE
:distZip UP-TO-DATE
:war UP-TO-DATE
:assemble UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build UP-TO-DATE

BUILD SUCCESSFUL

Gradle соберет проект и собранный варник положит в каталок /build/libs. Перейдем в этот каталог и запустим варник JettySecureRestCalls.war:

java -jar JettySecureRestCalls.war

2016-01-24 13:10:29.163:INFO::main: Logging initialized @153ms
2016-01-24 13:10:29.257:INFO:oejs.Server:main: jetty-9.1.z-SNAPSHOT
2016-01-24 13:10:45.891:INFO::main: Logging initialized @16884ms
2016-01-24 13:10:45.900:INFO:oejw.StandardDescriptorProcessor:main: NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet
2016-01-24 13:10:45.940:INFO:/:main: Initializing Spring root WebApplicationContext
янв 24, 2016 1:10:45 PM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization started
янв 24, 2016 1:10:46 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing Root WebApplicationContext: startup date [Sun Jan 24 13:10:46 EET 2016]; root of context hierarchy
янв 24, 2016 1:10:46 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from ServletContext resource [/WEB-INF/spring/spring-context.xml]
янв 24, 2016 1:10:46 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [META-INF/cxf/cxf.xml]
янв 24, 2016 1:10:46 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [META-INF/cxf/cxf-servlet.xml]
янв 24, 2016 1:10:46 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@7fad8c79: defining beans [cxf,org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor,org.apache.cxf.bus.spring.Jsr250BeanPostProcessor,org.apache.cxf.bus.spring.BusExtensionPostProcessor,serviceBean,restController]; root of factory hierarchy
янв 24, 2016 1:10:46 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be /
янв 24, 2016 1:10:46 PM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization completed in 619 ms
2016-01-24 13:10:46.575:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@51dcb805{/,file:/tmp/jetty-0.0.0.0-8080-JettySecureRestCalls.war-_-any-4512151673204580777.dir/webapp/,AVAILABLE}{file:/home/zheka/practice/ssl/3/JettySecureRestCalls/build/libs/JettySecureRestCalls.war}
2016-01-24 13:10:46.585:INFO:oejs.ServerConnector:main: Started ServerConnector@247d8ae{HTTP/1.1}{0.0.0.0:8080}
2016-01-24 13:10:46.716:INFO:oejs.ServerConnector:main: Started ServerConnector@6f204a1a{SSL-http/1.1}{0.0.0.0:8443}
2016-01-24 13:10:46.722:INFO:oejs.Server:main: Started @17715ms

Jetty сервер запущен, теперь можем его проверить браузером. Открываем фаирфокс и вводим в строку адрес: https://localhost:8443/rest/test-service/test.
Мы видим, что браузер не доверяет сертификату, но так как мы точно знаем, что это наш само-подписанный сертификат для сервера который запущен на нашей локальной машине, то мы можем добавить его в исключения.
2
Теперь протестируем наш защищенный сервер клиентским кодом, для этого создадим новый проект, который будет коннектится к нашему серверу через защищенный канал https.

Структура проекта клиентской части

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

SSLClient
    ├─main
    │   └─java
    │        └─src
    │            └─com
    │               └─dev-blogs
    │                   └─SSLClient.java
    └─pom.xml

SSLClient.java

package com.dev_blogs;

import javax.net.ssl.*;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

public class SSLClient {

    public static void disableCertificateValidation() {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }

                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
                    }

                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
                    }
                }};

        // Ignore differences between given hostname and certificate hostname
        HostnameVerifier hv = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        // Install the all-trusting trust manager
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            HttpsURLConnection.setDefaultHostnameVerifier(hv);
        } catch (Exception e) {
        }
    }

    public static void main(String[] args) {
        try {
            // This code was taken from http://stackoverflow.com/questions/875467/java-client-certificates-over-https-ssl
            disableCertificateValidation();
            //SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
            URL url = new URL("https://localhost:8443/rest/test-service/test");
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            //conn.setSSLSocketFactory(sslsocketfactory);
            InputStream inputstream = conn.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputstream));
            StringBuilder result = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                result.append(line);
            }
            System.out.println(result.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Метод disableCertificateValidation() который вызывается из строки 48 заставляет клиент игнорировать все подозрительные ресурсы. Поэтому прежде чем запустить клиентский код, давайте закомментируем строку 48, чтобы клиент проверял сертификат и запустим клиент.

Билд скрипт для клиента

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.dev_blogs</groupId>
  <artifactId>SSLClient</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>SSLClient</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.3.2</version>
      </plugin>
    </plugins>
  </build>
</project>

Теперь запустим клиент командой:


mvn install exec:java -Dexec.mainClass="com.dev_blogs.SSLClient"

В результате этого мы видим, что повалились эксцепшены:


javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
	at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1937)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1478)
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:212)
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:957)
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:892)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1050)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1363)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1391)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1375)
	at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1512)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1440)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
	at com.dev_blogs.SSLClient.main(SSLClient.java:52)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
	at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
	at sun.security.validator.Validator.validate(Validator.java:260)
	at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1460)
	... 18 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:145)
	at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:131)
	at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
	... 24 more

Вот что клиент выдает:


PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Это означает, что клиент завернул наш self-singed сертификат. Теперь, чтобы заставить клиента игнорировать подозрительные сертификаты рас комментируем строку 48 и запустим клиент еще раз:


mvn install exec:java -Dexec.mainClass="com.dev_blogs.SSLClient"

В результате получим вывод (его может быть плохо видно среди прочего мавенского мусора):


test Servlet

Теперь клиент отображает строку test Servlet которую он получил от сервера по зашифрованному каналу.

Линки

Полное руководство по переходу с HTTP на HTTPS
https://dzone.com/articles/adding-ssl-support-embedded
http://examples.javacodegeeks.com/enterprise-java/jetty/jetty-ssl-configuration-example
http://www.eclipse.org/jetty/documentation/9.2.1.v20140609/embedded-examples.html
http://www.smartjava.org/content/embedded-jetty-client-certificates
http://www.eclipse.org/jetty/documentation/9.2.6.v20141205/configuring-ssl.html

Поделиться в социальных сетях

Опубликовать в Google Plus
Опубликовать в LiveJournal
Опубликовать в Мой Мир
Опубликовать в Одноклассники
Опубликовать в Яндекс

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *