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