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.cert.X509Certificate;
21import java.util.Collections;
22import java.util.HashMap;
23import java.util.List;
24import java.util.Map;
25import javax.net.ssl.SSLPeerUnverifiedException;
26import javax.net.ssl.SSLSessionBindingEvent;
27import javax.net.ssl.SSLSessionBindingListener;
28
29/**
30 * Implementation of the class OpenSSLSessionImpl
31 * based on BoringSSL.
32 *
33 * @hide
34 */
35@Internal
36public class OpenSSLSessionImpl extends AbstractOpenSSLSession {
37    private long creationTime = 0;
38    long lastAccessedTime = 0;
39    final X509Certificate[] localCertificates;
40    final X509Certificate[] peerCertificates;
41
42    private final Map<String, Object> values = new HashMap<String, Object>();
43    private byte[] peerCertificateOcspData;
44    private byte[] peerTlsSctData;
45    protected long sslSessionNativePointer;
46    private String peerHost;
47    private int peerPort = -1;
48    private String cipherSuite;
49    private String protocol;
50    private byte[] id;
51
52    /**
53     * Class constructor creates an SSL session context given the appropriate
54     * SSL parameters.
55     */
56    protected OpenSSLSessionImpl(long sslSessionNativePointer, X509Certificate[] localCertificates,
57            X509Certificate[] peerCertificates, byte[] peerCertificateOcspData,
58            byte[] peerTlsSctData, String peerHost, int peerPort,
59            AbstractSessionContext sessionContext) {
60        super(sessionContext);
61        this.sslSessionNativePointer = sslSessionNativePointer;
62        this.localCertificates = localCertificates;
63        this.peerCertificates = peerCertificates;
64        this.peerCertificateOcspData = peerCertificateOcspData;
65        this.peerTlsSctData = peerTlsSctData;
66        this.peerHost = peerHost;
67        this.peerPort = peerPort;
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, byte[] peerCertificateOcspData,
78            byte[] peerTlsSctData, AbstractSessionContext sessionContext)
79            throws IOException {
80        this(NativeCrypto.d2i_SSL_SESSION(derData), null, peerCertificates,
81                peerCertificateOcspData, peerTlsSctData, peerHost, peerPort, sessionContext);
82    }
83
84    /**
85     * Gets the identifier of the actual SSL session
86     * @return array of sessions' identifiers.
87     */
88    @Override
89    public byte[] getId() {
90        if (id == null) {
91            resetId();
92        }
93        return id;
94    }
95
96    /**
97     * Reset the id field to the current value found in the native
98     * SSL_SESSION. It can change during the lifetime of the session
99     * because while a session is created during initial handshake,
100     * with handshake_cutthrough, the SSL_do_handshake may return
101     * before we have read the session ticket from the server side and
102     * therefore have computed no id based on the SHA of the ticket.
103     */
104    @Override
105    void resetId() {
106        id = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);
107    }
108
109    /**
110     * Get the session object in DER format. This allows saving the session
111     * data or sharing it with other processes.
112     */
113    public byte[] getEncoded() {
114        return NativeCrypto.i2d_SSL_SESSION(sslSessionNativePointer);
115    }
116
117    /**
118     * Gets the creation time of the SSL session.
119     * @return the session's creation time in milliseconds since the epoch
120     */
121    @Override
122    public long getCreationTime() {
123        if (creationTime == 0) {
124            creationTime = NativeCrypto.SSL_SESSION_get_time(sslSessionNativePointer);
125        }
126        return creationTime;
127    }
128
129    /**
130     * Returns the last time this concrete SSL session was accessed. Accessing
131     * here is to mean that a new connection with the same SSL context data was
132     * established.
133     *
134     * @return the session's last access time in milliseconds since the epoch
135     */
136    @Override
137    public long getLastAccessedTime() {
138        return (lastAccessedTime == 0) ? getCreationTime() : lastAccessedTime;
139    }
140
141    @Override
142    public void setLastAccessedTime(long accessTimeMillis) {
143        lastAccessedTime = accessTimeMillis;
144    }
145
146    @Override
147    protected X509Certificate[] getX509LocalCertificates() {
148        return localCertificates;
149    }
150
151    @Override
152    protected X509Certificate[] getX509PeerCertificates() throws SSLPeerUnverifiedException {
153        if (peerCertificates == null || peerCertificates.length == 0) {
154            throw new SSLPeerUnverifiedException("No peer certificates");
155        }
156        return peerCertificates;
157    }
158
159    /**
160     * The peer's host name used in this SSL session is returned. It is the host
161     * name of the client for the server; and that of the server for the client.
162     * It is not a reliable way to get a fully qualified host name: it is mainly
163     * used internally to implement links for a temporary cache of SSL sessions.
164     *
165     * @return the host name of the peer, or {@code null} if no information is
166     *         available.
167     */
168    @Override
169    public String getPeerHost() {
170        return peerHost;
171    }
172
173    /**
174     * Returns the peer's port number for the actual SSL session. It is the port
175     * number of the client for the server; and that of the server for the
176     * client. It is not a reliable way to get a peer's port number: it is
177     * mainly used internally to implement links for a temporary cache of SSL
178     * sessions.
179     *
180     * @return the peer's port number, or {@code -1} if no one is available.
181     */
182    @Override
183    public int getPeerPort() {
184        return peerPort;
185    }
186
187    /**
188     * Returns a string identifier of the crypto tools used in the actual SSL
189     * session. For example AES_256_WITH_MD5.
190     */
191    @Override
192    public String getCipherSuite() {
193        if (cipherSuite == null) {
194            String name = NativeCrypto.SSL_SESSION_cipher(sslSessionNativePointer);
195            cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name);
196            if (cipherSuite == null) {
197                cipherSuite = name;
198            }
199        }
200        return cipherSuite;
201    }
202
203    /**
204     * Returns the standard version name of the SSL protocol used in all
205     * connections pertaining to this SSL session.
206     */
207    @Override
208    public String getProtocol() {
209        if (protocol == null) {
210            protocol = NativeCrypto.SSL_SESSION_get_version(sslSessionNativePointer);
211        }
212        return protocol;
213    }
214
215    /**
216     * Returns the object which is bound to the the input parameter name.
217     * This name is a sort of link to the data of the SSL session's application
218     * layer, if any exists.
219     *
220     * @param name the name of the binding to find.
221     * @return the value bound to that name, or null if the binding does not
222     *         exist.
223     * @throws IllegalArgumentException if the argument is null.
224     */
225    @Override
226    public Object getValue(String name) {
227        if (name == null) {
228            throw new IllegalArgumentException("name == null");
229        }
230        return values.get(name);
231    }
232
233    /**
234     * Returns an array with the names (sort of links) of all the data
235     * objects of the application layer bound into the SSL session.
236     *
237     * @return a non-null (possibly empty) array of names of the data objects
238     *         bound to this SSL session.
239     */
240    @Override
241    public String[] getValueNames() {
242        return values.keySet().toArray(new String[values.size()]);
243    }
244
245    /**
246     * A link (name) with the specified value object of the SSL session's
247     * application layer data is created or replaced. If the new (or existing)
248     * value object implements the <code>SSLSessionBindingListener</code>
249     * interface, that object will be notified in due course.
250     *
251     * @param name the name of the link (no null are
252     *            accepted!)
253     * @param value data object that shall be bound to
254     *            name.
255     * @throws IllegalArgumentException if one or both argument(s) is null.
256     */
257    @Override
258    public void putValue(String name, Object value) {
259        if (name == null || value == null) {
260            throw new IllegalArgumentException("name == null || value == null");
261        }
262        Object old = values.put(name, value);
263        if (value instanceof SSLSessionBindingListener) {
264            ((SSLSessionBindingListener) value)
265                    .valueBound(new SSLSessionBindingEvent(this, name));
266        }
267        if (old instanceof SSLSessionBindingListener) {
268            ((SSLSessionBindingListener) old)
269                    .valueUnbound(new SSLSessionBindingEvent(this, name));
270        }
271    }
272
273    /**
274     * Removes a link (name) with the specified value object of the SSL
275     * session's application layer data.
276     *
277     * <p>If the value object implements the <code>SSLSessionBindingListener</code>
278     * interface, the object will receive a <code>valueUnbound</code> notification.
279     *
280     * @param name the name of the link (no null are
281     *            accepted!)
282     * @throws IllegalArgumentException if the argument is null.
283     */
284    @Override
285    public void removeValue(String name) {
286        if (name == null) {
287            throw new IllegalArgumentException("name == null");
288        }
289        Object old = values.remove(name);
290        if (old instanceof SSLSessionBindingListener) {
291            SSLSessionBindingListener listener = (SSLSessionBindingListener) old;
292            listener.valueUnbound(new SSLSessionBindingEvent(this, name));
293        }
294    }
295
296    /**
297     * Returns the name requested by the SNI extension.
298     */
299    @Override
300    public String getRequestedServerName() {
301        return NativeCrypto.get_SSL_SESSION_tlsext_hostname(sslSessionNativePointer);
302    }
303
304    /**
305     * Returns the OCSP stapled response.
306     */
307    @Override
308    public List<byte[]> getStatusResponses() {
309        if (peerCertificateOcspData == null) {
310            return Collections.<byte[]>emptyList();
311        }
312
313        return Collections.singletonList(peerCertificateOcspData.clone());
314    }
315
316    @Override
317    public byte[] getTlsSctData() {
318        if (peerTlsSctData == null) {
319            return null;
320        }
321        return peerTlsSctData.clone();
322    }
323
324    @Override
325    protected void finalize() throws Throwable {
326        try {
327            // The constructor can throw an exception if this object is constructed from invalid
328            // saved session data.
329            if (sslSessionNativePointer != 0) {
330                NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
331            }
332        } finally {
333            super.finalize();
334        }
335    }
336}
337