OpenSSLSessionImpl.java revision 32b2c95c350002f67c8b3e65777161feda766b72
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.apache.harmony.xnet.provider.jsse;
18
19import java.io.IOException;
20import java.security.AccessControlContext;
21import java.security.AccessController;
22import java.security.Principal;
23import java.security.cert.Certificate;
24import java.security.cert.CertificateEncodingException;
25import java.security.cert.X509Certificate;
26import java.util.ArrayList;
27import java.util.Map;
28import java.util.Set;
29import javax.net.ssl.SSLPeerUnverifiedException;
30import javax.net.ssl.SSLPermission;
31import javax.net.ssl.SSLSession;
32import javax.net.ssl.SSLSessionBindingEvent;
33import javax.net.ssl.SSLSessionBindingListener;
34import javax.net.ssl.SSLSessionContext;
35import javax.security.cert.CertificateException;
36import libcore.util.Objects;
37import org.apache.harmony.luni.util.TwoKeyHashMap;
38
39/**
40 * Implementation of the class OpenSSLSessionImpl
41 * based on OpenSSL.
42 */
43public class OpenSSLSessionImpl implements SSLSession {
44
45    private long creationTime = 0;
46    long lastAccessedTime = 0;
47    final X509Certificate[] localCertificates;
48    final X509Certificate[] peerCertificates;
49
50    private boolean isValid = true;
51    private TwoKeyHashMap values = new TwoKeyHashMap<String, AccessControlContext, Object>();
52    private volatile javax.security.cert.X509Certificate[] peerCertificateChain;
53    protected int sslSessionNativePointer;
54    private String peerHost;
55    private int peerPort = -1;
56    private String cipherSuite;
57    private String protocol;
58    private String compressionMethod;
59    private AbstractSessionContext sessionContext;
60    private byte[] id;
61
62    /**
63     * Class constructor creates an SSL session context given the appropriate
64     * SSL parameters.
65     *
66     * @param session the Identifier for SSL session
67     * @param sslParameters the SSL parameters like ciphers' suites etc.
68     */
69    protected OpenSSLSessionImpl(int sslSessionNativePointer, X509Certificate[] localCertificates,
70            X509Certificate[] peerCertificates, String peerHost, int peerPort,
71            AbstractSessionContext sessionContext) {
72        this.sslSessionNativePointer = sslSessionNativePointer;
73        this.localCertificates = localCertificates;
74        this.peerCertificates = peerCertificates;
75        this.peerHost = peerHost;
76        this.peerPort = peerPort;
77        this.sessionContext = sessionContext;
78    }
79
80    /**
81     * Constructs a session from a byte[] containing DER data. This
82     * allows loading the saved session.
83     * @throws IOException
84     */
85    OpenSSLSessionImpl(byte[] derData,
86            String peerHost, int peerPort,
87            X509Certificate[] peerCertificates,
88            AbstractSessionContext sessionContext)
89            throws IOException {
90        this(NativeCrypto.d2i_SSL_SESSION(derData),
91             null,
92             peerCertificates,
93             peerHost,
94             peerPort,
95             sessionContext);
96        // TODO move this check into native code so we can throw an error with more information
97        if (this.sslSessionNativePointer == 0) {
98            throw new IOException("Invalid session data");
99        }
100    }
101
102    /**
103     * Gets the identifier of the actual SSL session
104     * @return array of sessions' identifiers.
105     */
106    public byte[] getId() {
107        if (id == null) {
108            resetId();
109        }
110        return id;
111    }
112
113    /**
114     * Reset the id field to the current value found in the native
115     * SSL_SESSION. It can change during the lifetime of the session
116     * because while a session is created during initial handshake,
117     * with handshake_cutthrough, the SSL_do_handshake may return
118     * before we have read the session ticket from the server side and
119     * therefore have computed no id based on the SHA of the ticket.
120     */
121    void resetId() {
122        id = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);
123    }
124
125    /**
126     * Get the session object in DER format. This allows saving the session
127     * data or sharing it with other processes.
128     */
129    byte[] getEncoded() {
130        return NativeCrypto.i2d_SSL_SESSION(sslSessionNativePointer);
131    }
132
133    /**
134     * Gets the creation time of the SSL session.
135     * @return the session's creation time in milliseconds since the epoch
136     */
137    public long getCreationTime() {
138        if (creationTime == 0) {
139            creationTime = NativeCrypto.SSL_SESSION_get_time(sslSessionNativePointer);
140        }
141        return creationTime;
142    }
143
144    /**
145     * Returns the last time this concrete SSL session was accessed. Accessing
146     * here is to mean that a new connection with the same SSL context data was
147     * established.
148     *
149     * @return the session's last access time in milliseconds since the epoch
150     */
151    public long getLastAccessedTime() {
152        return (lastAccessedTime == 0) ? getCreationTime() : lastAccessedTime;
153    }
154
155    /**
156     * Returns the largest buffer size for the application's data bound to this
157     * concrete SSL session.
158     * @return the largest buffer size
159     */
160    public int getApplicationBufferSize() {
161        return SSLRecordProtocol.MAX_DATA_LENGTH;
162    }
163
164    /**
165     * Returns the largest SSL/TLS packet size one can expect for this concrete
166     * SSL session.
167     * @return the largest packet size
168     */
169    public int getPacketBufferSize() {
170        return SSLRecordProtocol.MAX_SSL_PACKET_SIZE;
171    }
172
173    /**
174     * Returns the principal (subject) of this concrete SSL session used in the
175     * handshaking phase of the connection.
176     * @return a X509 certificate or null if no principal was defined
177     */
178    public Principal getLocalPrincipal() {
179        if (localCertificates != null && localCertificates.length > 0) {
180            return localCertificates[0].getSubjectX500Principal();
181        } else {
182            return null;
183        }
184    }
185
186    /**
187     * Returns the certificate(s) of the principal (subject) of this concrete SSL
188     * session used in the handshaking phase of the connection. The OpenSSL
189     * native method supports only RSA certificates.
190     * @return an array of certificates (the local one first and then eventually
191     *         that of the certification authority) or null if no certificate
192     *         were used during the handshaking phase.
193     */
194    public Certificate[] getLocalCertificates() {
195        return localCertificates;
196    }
197
198    /**
199     * Returns the certificate(s) of the peer in this SSL session
200     * used in the handshaking phase of the connection.
201     * Please notice hat this method is superseded by
202     * <code>getPeerCertificates()</code>.
203     * @return an array of X509 certificates (the peer's one first and then
204     *         eventually that of the certification authority) or null if no
205     *         certificate were used during the SSL connection.
206     * @throws <code>SSLPeerUnverifiedCertificateException</code> if either a
207     *         not X509 certificate was used (i.e. Kerberos certificates) or the
208     *         peer could not be verified.
209     */
210    public javax.security.cert.X509Certificate[] getPeerCertificateChain()
211            throws SSLPeerUnverifiedException {
212        checkPeerCertificatesPresent();
213        javax.security.cert.X509Certificate[] result = peerCertificateChain;
214        if (result == null) {
215            // single-check idiom
216            peerCertificateChain = result = createPeerCertificateChain();
217        }
218        return result;
219    }
220
221    /**
222     * Provide a value to initialize the volatile peerCertificateChain
223     * field based on the native SSL_SESSION
224     */
225    private javax.security.cert.X509Certificate[] createPeerCertificateChain()
226            throws SSLPeerUnverifiedException {
227        try {
228            javax.security.cert.X509Certificate[] chain
229                    = new javax.security.cert.X509Certificate[peerCertificates.length];
230
231            for (int i = 0; i < peerCertificates.length; i++) {
232                byte[] encoded = peerCertificates[i].getEncoded();
233                chain[i] = javax.security.cert.X509Certificate.getInstance(encoded);
234            }
235            return chain;
236        } catch (CertificateEncodingException e) {
237            SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
238            exception.initCause(exception);
239            throw exception;
240        } catch (CertificateException e) {
241            SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
242            exception.initCause(exception);
243            throw exception;
244        }
245    }
246
247    /**
248     * Return the identitity of the peer in this SSL session
249     * determined via certificate(s).
250     * @return an array of X509 certificates (the peer's one first and then
251     *         eventually that of the certification authority) or null if no
252     *         certificate were used during the SSL connection.
253     * @throws <code>SSLPeerUnverifiedException</code> if either a not X509
254     *         certificate was used (i.e. Kerberos certificates) or the peer
255     *         could not be verified.
256     */
257    public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
258        checkPeerCertificatesPresent();
259        return peerCertificates;
260    }
261
262    /**
263     * Throw SSLPeerUnverifiedException on null or empty peerCertificates array
264     */
265    private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException {
266        if (peerCertificates == null || peerCertificates.length == 0) {
267            throw new SSLPeerUnverifiedException("No peer certificates");
268        }
269    }
270
271    /**
272     * The identity of the principal that was used by the peer during the SSL
273     * handshake phase is returned by this method.
274     * @return a X500Principal of the last certificate for X509-based
275     *         cipher suites.
276     * @throws <code>SSLPeerUnverifiedException</code> if either a not X509
277     *         certificate was used (i.e. Kerberos certificates) or the
278     *         peer does not exist.
279     *
280     */
281    public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
282        checkPeerCertificatesPresent();
283        return peerCertificates[0].getSubjectX500Principal();
284    }
285
286    /**
287     * The peer's host name used in this SSL session is returned. It is the host
288     * name of the client for the server; and that of the server for the client.
289     * It is not a reliable way to get a fully qualified host name: it is mainly
290     * used internally to implement links for a temporary cache of SSL sessions.
291     *
292     * @return the host name of the peer, or null if no information is
293     *         available.
294     *
295     */
296    public String getPeerHost() {
297        return peerHost;
298    }
299
300    /**
301     * Returns the peer's port number for the actual SSL session. It is the port
302     * number of the client for the server; and that of the server for the
303     * client. It is not a reliable way to get a peer's port number: it is
304     * mainly used internally to implement links for a temporary cache of SSL
305     * sessions.
306     * @return the peer's port number, or -1 if no one is available.
307     *
308     */
309    public int getPeerPort() {
310        return peerPort;
311    }
312
313    /**
314     * Returns a string identifier of the crypto tools used in the actual SSL
315     * session. For example AES_256_WITH_MD5.
316     */
317    public String getCipherSuite() {
318        if (cipherSuite == null) {
319            String name = NativeCrypto.SSL_SESSION_cipher(sslSessionNativePointer);
320            cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name);
321            if (cipherSuite == null) {
322                cipherSuite = name;
323            }
324        }
325        return cipherSuite;
326    }
327
328    /**
329     * Returns the standard version name of the SSL protocol used in all
330     * connections pertaining to this SSL session.
331     */
332    public String getProtocol() {
333        if (protocol == null) {
334            protocol = NativeCrypto.SSL_SESSION_get_version(sslSessionNativePointer);
335        }
336        return protocol;
337    }
338
339    /**
340     * Returns the compression method name used in all connections
341     * pertaining to this SSL session.
342     */
343    public String getCompressionMethod() {
344        if (compressionMethod == null) {
345            compressionMethod
346                    = NativeCrypto.SSL_SESSION_compress_meth(sessionContext.sslCtxNativePointer,
347                                                             sslSessionNativePointer);
348        }
349        return compressionMethod;
350    }
351
352    /**
353     * Returns the context to which the actual SSL session is bound. A SSL
354     * context consists of (1) a possible delegate, (2) a provider and (3) a
355     * protocol.
356     * @return the SSL context used for this session, or null if it is
357     * unavailable.
358     */
359    public SSLSessionContext getSessionContext() {
360        return sessionContext;
361    }
362
363    /**
364     * Returns a boolean flag signaling whether a SSL session is valid
365     * and available for resuming or joining or not.
366     *
367     * @return true if this session may be resumed.
368     */
369    public boolean isValid() {
370        SSLSessionContext context = sessionContext;
371        if (isValid
372                && context != null
373                && context.getSessionTimeout() != 0
374                && getCreationTime() + (context.getSessionTimeout() * 1000)
375                    < System.currentTimeMillis()) {
376            isValid = false;
377        }
378        return isValid;
379    }
380
381    /**
382     * It invalidates a SSL session forbidding any resumption.
383     */
384    public void invalidate() {
385        isValid = false;
386        sessionContext = null;
387    }
388
389    /**
390     * Returns the object which is bound to the the input parameter name.
391     * This name is a sort of link to the data of the SSL session's application
392     * layer, if any exists. The search for this link is monitored, as a matter
393     * of security, by the full machinery of the <code>AccessController</code>
394     * class.
395     *
396     * @param name the name of the binding to find.
397     * @return the value bound to that name, or null if the binding does not
398     *         exist.
399     * @throws <code>IllegalArgumentException</code> if the argument is null.
400     */
401    public Object getValue(String name) {
402        if (name == null) {
403            throw new IllegalArgumentException("Parameter is null");
404        }
405        return values.get(name, AccessController.getContext());
406    }
407
408    /**
409     * Returns an array with the names (sort of links) of all the data
410     * objects of the application layer bound into the SSL session. The search
411     * for this link is monitored, as a matter of security, by the full
412     * machinery of the <code>AccessController</code> class.
413     *
414     * @return a non-null (possibly empty) array of names of the data objects
415     *         bound to this SSL session.
416     */
417    public String[] getValueNames() {
418        ArrayList<String> v = new ArrayList<String>();
419        AccessControlContext current = AccessController.getContext();
420        Set<Map.Entry<String, Object>> set = values.entrySet();
421        for (Map.Entry<String, Object> o : set) {
422            TwoKeyHashMap.Entry<String, AccessControlContext, Object> entry
423                    = (TwoKeyHashMap.Entry<String, AccessControlContext, Object>) o;
424            AccessControlContext cont = entry.getKey2();
425            if (Objects.equal(current, cont)) {
426                v.add(entry.getKey1());
427            }
428        }
429        return v.toArray(new String[v.size()]);
430    }
431
432    /**
433     * A link (name) with the specified value object of the SSL session's
434     * application layer data is created or replaced. If the new (or existing)
435     * value object implements the <code>SSLSessionBindingListener</code>
436     * interface, that object will be notified in due course. These links-to
437     * -data bounds are monitored, as a matter of security, by the full
438     * machinery of the <code>AccessController</code> class.
439     *
440     * @param name the name of the link (no null are
441     *            accepted!)
442     * @param value data object that shall be bound to
443     *            name.
444     * @throws <code>IllegalArgumentException</code> if one or both
445     *             argument(s) is null.
446     */
447    public void putValue(String name, Object value) {
448        if (name == null || value == null) {
449            throw new IllegalArgumentException("Parameter is null");
450        }
451        Object old = values.put(name, AccessController.getContext(), value);
452        if (value instanceof SSLSessionBindingListener) {
453            ((SSLSessionBindingListener) value)
454                    .valueBound(new SSLSessionBindingEvent(this, name));
455        }
456        if (old instanceof SSLSessionBindingListener) {
457            ((SSLSessionBindingListener) old)
458                    .valueUnbound(new SSLSessionBindingEvent(this, name));
459        }
460    }
461
462    /**
463     * Removes a link (name) with the specified value object of the SSL
464     * session's application layer data.
465     *
466     * <p>If the value object implements the <code>SSLSessionBindingListener</code>
467     * interface, the object will receive a <code>valueUnbound</code> notification.
468     *
469     * <p>These links-to -data bounds are
470     * monitored, as a matter of security, by the full machinery of the
471     * <code>AccessController</code> class.
472     *
473     * @param name the name of the link (no null are
474     *            accepted!)
475     * @throws <code>IllegalArgumentException</code> if the argument is null.
476     */
477    public void removeValue(String name) {
478        if (name == null) {
479            throw new IllegalArgumentException("Parameter is null");
480        }
481        Object old = values.remove(name, AccessController.getContext());
482        if (old instanceof SSLSessionBindingListener) {
483            SSLSessionBindingListener listener = (SSLSessionBindingListener) old;
484            listener.valueUnbound(new SSLSessionBindingEvent(this, name));
485        }
486    }
487
488    @Override protected void finalize() throws Throwable {
489        try {
490            NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
491        } finally {
492            super.finalize();
493        }
494    }
495}
496