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