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.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 int 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(int 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 DER data. This
72     * allows loading the saved session.
73     * @throws IOException
74     */
75    OpenSSLSessionImpl(byte[] derData,
76            String peerHost, int peerPort,
77            X509Certificate[] peerCertificates,
78            AbstractSessionContext sessionContext)
79            throws IOException {
80        this(NativeCrypto.d2i_SSL_SESSION(derData),
81             null,
82             peerCertificates,
83             peerHost,
84             peerPort,
85             sessionContext);
86        // TODO move this check into native code so we can throw an error with more information
87        if (this.sslSessionNativePointer == 0) {
88            throw new IOException("Invalid session data");
89        }
90    }
91
92    /**
93     * Gets the identifier of the actual SSL session
94     * @return array of sessions' identifiers.
95     */
96    public byte[] getId() {
97        if (id == null) {
98            resetId();
99        }
100        return id;
101    }
102
103    /**
104     * Reset the id field to the current value found in the native
105     * SSL_SESSION. It can change during the lifetime of the session
106     * because while a session is created during initial handshake,
107     * with handshake_cutthrough, the SSL_do_handshake may return
108     * before we have read the session ticket from the server side and
109     * therefore have computed no id based on the SHA of the ticket.
110     */
111    void resetId() {
112        id = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);
113    }
114
115    /**
116     * Get the session object in DER format. This allows saving the session
117     * data or sharing it with other processes.
118     */
119    byte[] getEncoded() {
120        return NativeCrypto.i2d_SSL_SESSION(sslSessionNativePointer);
121    }
122
123    /**
124     * Gets the creation time of the SSL session.
125     * @return the session's creation time in milliseconds since the epoch
126     */
127    public long getCreationTime() {
128        if (creationTime == 0) {
129            creationTime = NativeCrypto.SSL_SESSION_get_time(sslSessionNativePointer);
130        }
131        return creationTime;
132    }
133
134    /**
135     * Returns the last time this concrete SSL session was accessed. Accessing
136     * here is to mean that a new connection with the same SSL context data was
137     * established.
138     *
139     * @return the session's last access time in milliseconds since the epoch
140     */
141    public long getLastAccessedTime() {
142        return (lastAccessedTime == 0) ? getCreationTime() : lastAccessedTime;
143    }
144
145    /**
146     * Returns the largest buffer size for the application's data bound to this
147     * concrete SSL session.
148     * @return the largest buffer size
149     */
150    public int getApplicationBufferSize() {
151        return SSLRecordProtocol.MAX_DATA_LENGTH;
152    }
153
154    /**
155     * Returns the largest SSL/TLS packet size one can expect for this concrete
156     * SSL session.
157     * @return the largest packet size
158     */
159    public int getPacketBufferSize() {
160        return SSLRecordProtocol.MAX_SSL_PACKET_SIZE;
161    }
162
163    /**
164     * Returns the principal (subject) of this concrete SSL session used in the
165     * handshaking phase of the connection.
166     * @return a X509 certificate or null if no principal was defined
167     */
168    public Principal getLocalPrincipal() {
169        if (localCertificates != null && localCertificates.length > 0) {
170            return localCertificates[0].getSubjectX500Principal();
171        } else {
172            return null;
173        }
174    }
175
176    /**
177     * Returns the certificate(s) of the principal (subject) of this concrete SSL
178     * session used in the handshaking phase of the connection. The OpenSSL
179     * native method supports only RSA certificates.
180     * @return an array of certificates (the local one first and then eventually
181     *         that of the certification authority) or null if no certificate
182     *         were used during the handshaking phase.
183     */
184    public Certificate[] getLocalCertificates() {
185        return localCertificates;
186    }
187
188    /**
189     * Returns the certificate(s) of the peer in this SSL session
190     * used in the handshaking phase of the connection.
191     * Please notice hat this method is superseded by
192     * <code>getPeerCertificates()</code>.
193     * @return an array of X509 certificates (the peer's one first and then
194     *         eventually that of the certification authority) or null if no
195     *         certificate were used during the SSL connection.
196     * @throws <code>SSLPeerUnverifiedCertificateException</code> if either a
197     *         not X509 certificate was used (i.e. Kerberos certificates) or the
198     *         peer could not be verified.
199     */
200    public javax.security.cert.X509Certificate[] getPeerCertificateChain()
201            throws SSLPeerUnverifiedException {
202        checkPeerCertificatesPresent();
203        javax.security.cert.X509Certificate[] result = peerCertificateChain;
204        if (result == null) {
205            // single-check idiom
206            peerCertificateChain = result = createPeerCertificateChain();
207        }
208        return result;
209    }
210
211    /**
212     * Provide a value to initialize the volatile peerCertificateChain
213     * field based on the native SSL_SESSION
214     */
215    private javax.security.cert.X509Certificate[] createPeerCertificateChain()
216            throws SSLPeerUnverifiedException {
217        try {
218            javax.security.cert.X509Certificate[] chain
219                    = new javax.security.cert.X509Certificate[peerCertificates.length];
220
221            for (int i = 0; i < peerCertificates.length; i++) {
222                byte[] encoded = peerCertificates[i].getEncoded();
223                chain[i] = javax.security.cert.X509Certificate.getInstance(encoded);
224            }
225            return chain;
226        } catch (CertificateEncodingException e) {
227            SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
228            exception.initCause(exception);
229            throw exception;
230        } catch (CertificateException e) {
231            SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
232            exception.initCause(exception);
233            throw exception;
234        }
235    }
236
237    /**
238     * Return the identity of the peer in this SSL session
239     * determined via certificate(s).
240     * @return an array of X509 certificates (the peer's one first and then
241     *         eventually that of the certification authority) or null if no
242     *         certificate were used during the SSL connection.
243     * @throws <code>SSLPeerUnverifiedException</code> if either a not X509
244     *         certificate was used (i.e. Kerberos certificates) or the peer
245     *         could not be verified.
246     */
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 <code>SSLPeerUnverifiedException</code> if either a not X509
267     *         certificate was used (i.e. Kerberos certificates) or the
268     *         peer does not exist.
269     *
270     */
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 null if no information is
283     *         available.
284     *
285     */
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     * @return the peer's port number, or -1 if no one is available.
297     *
298     */
299    public int getPeerPort() {
300        return peerPort;
301    }
302
303    /**
304     * Returns a string identifier of the crypto tools used in the actual SSL
305     * session. For example AES_256_WITH_MD5.
306     */
307    public String getCipherSuite() {
308        if (cipherSuite == null) {
309            String name = NativeCrypto.SSL_SESSION_cipher(sslSessionNativePointer);
310            cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name);
311            if (cipherSuite == null) {
312                cipherSuite = name;
313            }
314        }
315        return cipherSuite;
316    }
317
318    /**
319     * Returns the standard version name of the SSL protocol used in all
320     * connections pertaining to this SSL session.
321     */
322    public String getProtocol() {
323        if (protocol == null) {
324            protocol = NativeCrypto.SSL_SESSION_get_version(sslSessionNativePointer);
325        }
326        return protocol;
327    }
328
329    /**
330     * Returns the context to which the actual SSL session is bound. A SSL
331     * context consists of (1) a possible delegate, (2) a provider and (3) a
332     * protocol.
333     * @return the SSL context used for this session, or null if it is
334     * unavailable.
335     */
336    public SSLSessionContext getSessionContext() {
337        return sessionContext;
338    }
339
340    /**
341     * Returns a boolean flag signaling whether a SSL session is valid
342     * and available for resuming or joining or not.
343     *
344     * @return true if this session may be resumed.
345     */
346    public boolean isValid() {
347        SSLSessionContext context = sessionContext;
348        if (isValid
349                && context != null
350                && context.getSessionTimeout() != 0
351                && getCreationTime() + (context.getSessionTimeout() * 1000)
352                    < System.currentTimeMillis()) {
353            isValid = false;
354        }
355        return isValid;
356    }
357
358    /**
359     * It invalidates a SSL session forbidding any resumption.
360     */
361    public void invalidate() {
362        isValid = false;
363        sessionContext = null;
364    }
365
366    /**
367     * Returns the object which is bound to the the input parameter name.
368     * This name is a sort of link to the data of the SSL session's application
369     * layer, if any exists.
370     *
371     * @param name the name of the binding to find.
372     * @return the value bound to that name, or null if the binding does not
373     *         exist.
374     * @throws <code>IllegalArgumentException</code> if the argument is null.
375     */
376    public Object getValue(String name) {
377        if (name == null) {
378            throw new IllegalArgumentException("name == null");
379        }
380        return values.get(name);
381    }
382
383    /**
384     * Returns an array with the names (sort of links) of all the data
385     * objects of the application layer bound into the SSL session.
386     *
387     * @return a non-null (possibly empty) array of names of the data objects
388     *         bound to this SSL session.
389     */
390    public String[] getValueNames() {
391        return values.keySet().toArray(new String[values.size()]);
392    }
393
394    /**
395     * A link (name) with the specified value object of the SSL session's
396     * application layer data is created or replaced. If the new (or existing)
397     * value object implements the <code>SSLSessionBindingListener</code>
398     * interface, that object will be notified in due course.
399     *
400     * @param name the name of the link (no null are
401     *            accepted!)
402     * @param value data object that shall be bound to
403     *            name.
404     * @throws <code>IllegalArgumentException</code> if one or both
405     *             argument(s) is null.
406     */
407    public void putValue(String name, Object value) {
408        if (name == null || value == null) {
409            throw new IllegalArgumentException("name == null || value == null");
410        }
411        Object old = values.put(name, value);
412        if (value instanceof SSLSessionBindingListener) {
413            ((SSLSessionBindingListener) value)
414                    .valueBound(new SSLSessionBindingEvent(this, name));
415        }
416        if (old instanceof SSLSessionBindingListener) {
417            ((SSLSessionBindingListener) old)
418                    .valueUnbound(new SSLSessionBindingEvent(this, name));
419        }
420    }
421
422    /**
423     * Removes a link (name) with the specified value object of the SSL
424     * session's application layer data.
425     *
426     * <p>If the value object implements the <code>SSLSessionBindingListener</code>
427     * interface, the object will receive a <code>valueUnbound</code> notification.
428     *
429     * @param name the name of the link (no null are
430     *            accepted!)
431     * @throws <code>IllegalArgumentException</code> if the argument is null.
432     */
433    public void removeValue(String name) {
434        if (name == null) {
435            throw new IllegalArgumentException("name == null");
436        }
437        Object old = values.remove(name);
438        if (old instanceof SSLSessionBindingListener) {
439            SSLSessionBindingListener listener = (SSLSessionBindingListener) old;
440            listener.valueUnbound(new SSLSessionBindingEvent(this, name));
441        }
442    }
443
444    @Override protected void finalize() throws Throwable {
445        try {
446            NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
447        } finally {
448            super.finalize();
449        }
450    }
451}
452