Bonjour,

Je suis actuellement en train de développe une application JAVA qui doit se connecté sur un site web utilisant une authentification par certificat, via SSL.

Le serveur est configuré afin de vérifié le certificat client. de cette manière :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
<VirtualHost <mon_ip>:443>
 
    [...]
 
    SSLEngine on
    SSLCertificateFile /etc/apache2/ssl.crt/<mon_certifcat_serveur>.cer
    SSLCertificateKeyFile /etc/apache2/ssl.key/<mon_certifcat_serveur_cle_prive>.key
    SSLCertificateChainFile /etc/apache2/ssl.crt/<mon_certifcat_serveur_racine>.cer
 
    <Location />
        SSLVerifyClient require
        SSLVerifyDepth 5
    </Location>
</VirtualHost>
Pour me connecter, je fais ceci
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
 
public class HttpsTest {
 
    public static void testSLLConection(File cert, String pass){
        try {
 
            InputStream certifinput = new FileInputStream(cert);
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(certifinput, pass.toCharArray());
 
            System.out.println( "init Stores..." );
 
            KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" );
            kmf.init( ks, pass.toCharArray());
 
            /*KeyStore jks = KeyStore.getInstance( "Windows-ROOT" );
            jks.load( null );
            TrustManagerFactory tmf = TrustManagerFactory.getInstance( "SunX509" );
            tmf.init( jks );
            */
 
            /*
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init((KeyStore)null);
            */
 
            /*
            TrustManager[] myTMs = new TrustManager [] {
                          new X509TrustStoreManager() };
            */
 
            TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                    @Override
                    public void checkClientTrusted( java.security.cert.X509Certificate[] certs, String authType ) {
                    }
                    @Override
                    public void checkServerTrusted( java.security.cert.X509Certificate[] certs, String authType ) {
                    }
                }
            };
 
            //"TLSv1", "SSLv3"
            SSLContext sslClientContext = SSLContext.getInstance("SSLv3");
            sslClientContext.init( kmf.getKeyManagers(), trustAllCerts, null );
 
            /* */
            SslContextedSecureProtocolSocketFactory secureProtocolSocketFactory = new SslContextedSecureProtocolSocketFactory(sslClientContext, false);
 
 
            Protocol.registerProtocol("https", new Protocol("https", (ProtocolSocketFactory)secureProtocolSocketFactory, 443));
 
            HttpClient httpClient = new HttpClient();
            httpClient.getParams().setParameter("http.tcp.nodelay", false);
            httpClient.getParams().setParameter("http.useragent", System.getProperty("http.agent")+" Java/"+System.getProperty("java.version"));
 
            GetMethod method = new GetMethod("<url_de_mon_site_en_https>");
            method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false));
            String reponse = "";
            int statusCode = -1;
            try {
                statusCode = httpClient.executeMethod(method);
                InputStream  in = method.getResponseBodyAsStream();
                StringWriter writer = new StringWriter();
                InputStreamReader streamReader = new InputStreamReader(in);
                //le buffer permet le readline
                BufferedReader buffer = new BufferedReader(streamReader);
                String line="";
                while ( null!=(line=buffer.readLine())){
                    writer.write(line);
                }
                reponse = writer.toString();
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                System.out.print("response = " +HttpStatus.getStatusText(statusCode)+" - "+reponse);
                method.releaseConnection();
            }
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}
voici le code de la classe SslContextedSecureProtocolSocketFactory :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
 
public class SslContextedSecureProtocolSocketFactory implements
        SecureProtocolSocketFactory {
    private SSLContext sslContext;
 
    /** Host name verify flag. */
    private boolean verifyHostname = true;
 
    /**
     * Constructor for SslContextedSecureProtocolSocketFactory.
     * 
     * @param sslContext
     *            The SSLContext to use for building the SSLSocketFactory. If
     *            this is null, then the default SSLSocketFactory is used.
     * @param verifyHostname
     *            The host name verification flag. If set to <code>true</code>
     *            the SSL sessions server host name will be compared to the host
     *            name returned in the server certificates "Common Name" field
     *            of the "SubjectDN" entry. If these names do not match a
     *            Exception is thrown to indicate this. Enabling host name
     *            verification will help to prevent from man-in-the-middle
     *            attacks. If set to <code>false</code> host name verification
     *            is turned off.
     * 
     *            Code sample:
     * 
     *            <blockquote> Protocol stricthttps = new Protocol( "https", new
     *            SslContextedSecureProtocolSocketFactory(sslContext,true),
     *            443);
     * 
     *            HttpClient client = new HttpClient();
     *            client.getHostConfiguration().setHost("localhost", 443,
     *            stricthttps); </blockquote>
     * 
     */
    public SslContextedSecureProtocolSocketFactory(SSLContext sslContext,
            boolean verifyHostname) {
        this.sslContext = sslContext;
        this.verifyHostname = verifyHostname;
    }
 
    /**
     * Constructor for SslContextedSecureProtocolSocketFactory. Host name
     * verification will be enabled by default.
     * 
     * @param sslContext
     *            The SSLContext to use for building the SSLSocketFactory. If
     *            this is null, then the default SSLSocketFactory is used.
     */
    public SslContextedSecureProtocolSocketFactory(SSLContext sslContext) {
        this(sslContext, true);
    }
 
    /**
     * Constructor for SslContextedSecureProtocolSocketFactory. The default
     * SSLSocketFactory will be used by default.
     * 
     * @param verifyHostname
     *            The host name verification flag. If set to <code>true</code>
     *            the SSL sessions server host name will be compared to the host
     *            name returned in the server certificates "Common Name" field
     *            of the "SubjectDN" entry. If these names do not match a
     *            Exception is thrown to indicate this. Enabling host name
     *            verification will help to prevent from man-in-the-middle
     *            attacks. If set to <code>false</code> host name verification
     *            is turned off.
     */
    public SslContextedSecureProtocolSocketFactory(boolean verifyHostname) {
        this(null, verifyHostname);
    }
 
    /**
     * Constructor for SslContextedSecureProtocolSocketFactory. By default, the
     * default SSLSocketFactory will be used and host name verification will be
     * enabled.
     */
    public SslContextedSecureProtocolSocketFactory() {
        this(null, true);
    }
 
    /**
     * Set the host name verification flag.
     * 
     * @param verifyHostname
     *            The host name verification flag. If set to <code>true</code>
     *            the SSL sessions server host name will be compared to the host
     *            name returned in the server certificates "Common Name" field
     *            of the "SubjectDN" entry. If these names do not match a
     *            Exception is thrown to indicate this. Enabling host name
     *            verification will help to prevent from man-in-the-middle
     *            attacks. If set to <code>false</code> host name verification
     *            is turned off.
     */
    public synchronized void setHostnameVerification(boolean verifyHostname) {
        this.verifyHostname = verifyHostname;
    }
 
    /**
     * Gets the status of the host name verification flag.
     * 
     * @return Host name verification flag. Either <code>true</code> if host
     *         name verification is turned on, or <code>false</code> if host
     *         name verification is turned off.
     */
    public synchronized boolean getHostnameVerification() {
        return verifyHostname;
    }
 
    /**
     * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
     */
    public Socket createSocket(String host, int port, InetAddress clientHost,
            int clientPort) throws IOException, UnknownHostException {
        SSLSocketFactory sf = (SSLSocketFactory) getSslSocketFactory();
        SSLSocket sslSocket = (SSLSocket) sf.createSocket(host, port,
                clientHost, clientPort);
        verifyHostname(sslSocket);
 
        return sslSocket;
    }
 
    /**
     * Attempts to get a new socket connection to the given host within the
     * given time limit.
     * <p>
     * This method employs several techniques to circumvent the limitations of
     * older JREs that do not support connect timeout. When running in JRE 1.4
     * or above reflection is used to call Socket#connect(SocketAddress
     * endpoint, int timeout) method. When executing in older JREs a controller
     * thread is executed. The controller thread attempts to create a new socket
     * within the given limit of time. If socket constructor does not return
     * until the timeout expires, the controller terminates and throws an
     * {@link ConnectTimeoutException}
     * </p>
     * 
     * @param host
     *            the host name/IP
     * @param port
     *            the port on the host
     * @param clientHost
     *            the local host name/IP to bind the socket to
     * @param clientPort
     *            the port on the local machine
     * @param params
     *            {@link HttpConnectionParams Http connection parameters}
     * 
     * @return Socket a new socket
     * 
     * @throws IOException
     *             if an I/O error occurs while creating the socket
     * @throws UnknownHostException
     *             if the IP address of the host cannot be determined
     */
    public Socket createSocket(final String host, final int port,
            final InetAddress localAddress, final int localPort,
            final HttpConnectionParams params) throws IOException,
            UnknownHostException, ConnectTimeoutException {
        if (params == null) {
            throw new IllegalArgumentException("Parameters may not be null");
        }
        int timeout = params.getConnectionTimeout();
        Socket socket = null;
 
        SocketFactory socketfactory = getSslSocketFactory();
        if (timeout == 0) {
            socket = socketfactory.createSocket(host, port, localAddress,
                    localPort);
        } else {
            socket = socketfactory.createSocket();
            SocketAddress localaddr = new InetSocketAddress(localAddress,
                    localPort);
            SocketAddress remoteaddr = new InetSocketAddress(host, port);
            socket.bind(localaddr);
            socket.connect(remoteaddr, timeout);
        }
        verifyHostname((SSLSocket) socket);
        return socket;
    }
 
    /**
     * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
     */
    public Socket createSocket(String host, int port) throws IOException,
            UnknownHostException {
        SSLSocketFactory sf = (SSLSocketFactory) getSslSocketFactory();
        SSLSocket sslSocket = (SSLSocket) sf.createSocket(host, port);
        verifyHostname(sslSocket);
 
        return sslSocket;
    }
 
    /**
     * @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
     */
    public Socket createSocket(Socket socket, String host, int port,
            boolean autoClose) throws IOException, UnknownHostException {
        SSLSocketFactory sf = (SSLSocketFactory) getSslSocketFactory();
        SSLSocket sslSocket = (SSLSocket) sf.createSocket(socket, host, port,
                autoClose);
        verifyHostname(sslSocket);
 
        return sslSocket;
    }
 
    /**
     * Describe <code>verifyHostname</code> method here.
     * 
     * @param socket
     *            a <code>SSLSocket</code> value
     * @exception SSLPeerUnverifiedException
     *                If there are problems obtaining the server certificates
     *                from the SSL session, or the server host name does not
     *                match with the "Common Name" in the server certificates
     *                SubjectDN.
     * @exception UnknownHostException
     *                If we are not able to resolve the SSL sessions returned
     *                server host name.
     */
    private void verifyHostname(SSLSocket socket)
            throws SSLPeerUnverifiedException, UnknownHostException {
        synchronized (this) {
            if (!verifyHostname)
                return;
        }
 
        SSLSession session = socket.getSession();
        String hostname = session.getPeerHost();
        try {
            InetAddress.getByName(hostname);
        } catch (UnknownHostException uhe) {
            throw new UnknownHostException("Could not resolve SSL sessions "
                    + "server hostname: " + hostname);
        }
 
        X509Certificate[] certs = (X509Certificate[]) session
                .getPeerCertificates();
        if (certs == null || certs.length == 0)
            throw new SSLPeerUnverifiedException(
                    "No server certificates found!");
 
        X500Principal subjectDN = certs[0].getSubjectX500Principal();
 
        // get the common names from the first cert
        List<String> cns = getCNs(subjectDN);
        boolean foundHostName = false;
        for (String cn : cns) {
            if (hostname.equalsIgnoreCase(cn)) {
                foundHostName = true;
                break;
            }
        }
        if (!foundHostName) {
            throw new SSLPeerUnverifiedException(
                    "HTTPS hostname invalid: expected '" + hostname
                            + "', received '" + cns + "'");
        }
    }
 
    /**
     * Parses a X.500 distinguished name for the values of the "Common Name"
     * fields. This is done a bit sloppy right now and should probably be done a
     * bit more according to <code>RFC 2253</code>.
     * 
     * @param subjectDN
     *            an X.500 Principal from an X.509 certificate.
     * @return the values of the "Common Name" fields.
     */
    private List<String> getCNs(X500Principal subjectDN) {
        List<String> cns = new ArrayList<String>();
 
        StringTokenizer st = new StringTokenizer(subjectDN.getName(), ",");
        while (st.hasMoreTokens()) {
            String cnField = st.nextToken();
            if (cnField.startsWith("CN=")) {
                cns.add(cnField.substring(3));
            }
        }
        return cns;
    }
 
    /**
     * Returns the SSLSocketFactory to use to create the sockets. If the
     * sslContext is non-null, this is built from the sslContext; otherwise,
     * this is the default SSLSocketFactory.
     * 
     * @return the SSLSocketFactory to use to create the sockets.
     */
    protected SSLSocketFactory getSslSocketFactory() {
        SSLSocketFactory sslSocketFactory = null;
        synchronized (this) {
            if (this.sslContext != null) {
                sslSocketFactory = this.sslContext.getSocketFactory();
            }
        }
        if (sslSocketFactory == null) {
            sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
        }
        return sslSocketFactory;
    }
 
    /**
     * Sets the SSLContext to use.
     * 
     * @param sslContext
     *            SSLContext to use.
     */
    public synchronized void setSSLContext(SSLContext sslContext) {
        this.sslContext = sslContext;
    }
}
A priori la connexion semble fonctionné. Cependant dans les logs appache du serveur je vois ma tentative de connexion se soldé par une erreur 403, et je n'ai aucune erreur du coté Java (aucune exception) et aucune réponse non plus... Ce que je trouve relativement troublant.

Je me demande donc, si j'utilise la bonne méthode ?

Cordialement.