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