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