• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License", "www.google.com", 443);
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 
17 package org.conscrypt;
18 
19 import static org.conscrypt.SSLUtils.SessionType.OPEN_SSL_WITH_OCSP;
20 import static org.conscrypt.SSLUtils.SessionType.OPEN_SSL_WITH_TLS_SCT;
21 import static org.conscrypt.SSLUtils.SessionType.isSupportedType;
22 
23 import java.io.ByteArrayOutputStream;
24 import java.io.DataOutputStream;
25 import java.io.IOException;
26 import java.nio.BufferUnderflowException;
27 import java.nio.ByteBuffer;
28 import java.security.Principal;
29 import java.security.cert.Certificate;
30 import java.security.cert.CertificateEncodingException;
31 import java.util.List;
32 import javax.net.ssl.SSLException;
33 import javax.net.ssl.SSLPeerUnverifiedException;
34 import javax.net.ssl.SSLSession;
35 import javax.net.ssl.SSLSessionContext;
36 import javax.security.cert.X509Certificate;
37 
38 /**
39  * A utility wrapper that abstracts operations on the underlying native SSL_SESSION instance.
40  *
41  * This is abstract only to support mocking for tests.
42  */
43 abstract class SslSessionWrapper {
44     /**
45      * Creates a new instance. Since BoringSSL does not provide an API to get access to all
46      * session information via the SSL_SESSION, we get some values (e.g. peer certs) from
47      * the active session instead (i.e. the SSL object).
48      */
newInstance(NativeRef.SSL_SESSION ref, ActiveSession activeSession)49     static SslSessionWrapper newInstance(NativeRef.SSL_SESSION ref, ActiveSession activeSession)
50             throws SSLPeerUnverifiedException {
51         AbstractSessionContext context = (AbstractSessionContext) activeSession.getSessionContext();
52         if (context instanceof ClientSessionContext) {
53             return new Impl(context, ref, activeSession.getPeerHost(),
54                     activeSession.getPeerPort(), activeSession.getPeerCertificates(),
55                     getOcspResponse(activeSession),
56                     activeSession.getPeerSignedCertificateTimestamp());
57         }
58 
59         // Server's will be cached by ID and won't have any of the extra fields.
60         return new Impl(context, ref, null, -1, null, null, null);
61     }
62 
getOcspResponse(ActiveSession activeSession)63     private static byte[] getOcspResponse(ActiveSession activeSession) {
64         List<byte[]> ocspResponseList = activeSession.getStatusResponses();
65         if (ocspResponseList.size() >= 1) {
66             return ocspResponseList.get(0);
67         }
68         return null;
69     }
70 
71     /**
72      * Creates a new {@link SslSessionWrapper} instance from the provided serialized bytes, which
73      * were generated by {@link #toBytes()}.
74      *
75      * @return The new instance if successful. If unable to parse the bytes for any reason, returns
76      * {@code null}.
77      */
newInstance( AbstractSessionContext context, byte[] data, String host, int port)78     static SslSessionWrapper newInstance(
79             AbstractSessionContext context, byte[] data, String host, int port) {
80         ByteBuffer buf = ByteBuffer.wrap(data);
81         try {
82             int type = buf.getInt();
83             if (!isSupportedType(type)) {
84                 throw new IOException("Unexpected type ID: " + type);
85             }
86 
87             int length = buf.getInt();
88             checkRemaining(buf, length);
89 
90             byte[] sessionData = new byte[length];
91             buf.get(sessionData);
92 
93             int count = buf.getInt();
94             checkRemaining(buf, count);
95 
96             java.security.cert.X509Certificate[] peerCerts =
97                     new java.security.cert.X509Certificate[count];
98             for (int i = 0; i < count; i++) {
99                 length = buf.getInt();
100                 checkRemaining(buf, length);
101 
102                 byte[] certData = new byte[length];
103                 buf.get(certData);
104                 try {
105                     peerCerts[i] = OpenSSLX509Certificate.fromX509Der(certData);
106                 } catch (Exception e) {
107                     throw new IOException("Can not read certificate " + i + "/" + count);
108                 }
109             }
110 
111             byte[] ocspData = null;
112             if (type >= OPEN_SSL_WITH_OCSP.value) {
113                 // We only support one OCSP response now, but in the future
114                 // we may support RFC 6961 which has multiple.
115                 int countOcspResponses = buf.getInt();
116                 checkRemaining(buf, countOcspResponses);
117 
118                 if (countOcspResponses >= 1) {
119                     int ocspLength = buf.getInt();
120                     checkRemaining(buf, ocspLength);
121 
122                     ocspData = new byte[ocspLength];
123                     buf.get(ocspData);
124 
125                     // Skip the rest of the responses.
126                     for (int i = 1; i < countOcspResponses; i++) {
127                         ocspLength = buf.getInt();
128                         checkRemaining(buf, ocspLength);
129                         buf.position(buf.position() + ocspLength);
130                     }
131                 }
132             }
133 
134             byte[] tlsSctData = null;
135             if (type == OPEN_SSL_WITH_TLS_SCT.value) {
136                 int tlsSctDataLength = buf.getInt();
137                 checkRemaining(buf, tlsSctDataLength);
138 
139                 if (tlsSctDataLength > 0) {
140                     tlsSctData = new byte[tlsSctDataLength];
141                     buf.get(tlsSctData);
142                 }
143             }
144 
145             if (buf.remaining() != 0) {
146                 log(new AssertionError("Read entire session, but data still remains; rejecting"));
147                 return null;
148             }
149 
150             NativeRef.SSL_SESSION ref =
151                     new NativeRef.SSL_SESSION(NativeCrypto.d2i_SSL_SESSION(sessionData));
152             return new Impl(context, ref, host, port, peerCerts, ocspData, tlsSctData);
153         } catch (IOException e) {
154             log(e);
155             return null;
156         } catch (BufferUnderflowException e) {
157             log(e);
158             return null;
159         }
160     }
161 
getId()162     abstract byte[] getId();
163 
isValid()164     abstract boolean isValid();
165 
offerToResume(SslWrapper ssl)166     abstract void offerToResume(SslWrapper ssl) throws SSLException;
167 
getCipherSuite()168     abstract String getCipherSuite();
169 
getProtocol()170     abstract String getProtocol();
171 
getPeerHost()172     abstract String getPeerHost();
173 
getPeerPort()174     abstract int getPeerPort();
175 
176     /**
177      * Returns the OCSP stapled response. The returned array is not copied; the caller must
178      * either not modify the returned array or make a copy.
179      *
180      * @see <a href="https://tools.ietf.org/html/rfc6066">RFC 6066</a>
181      * @see <a href="https://tools.ietf.org/html/rfc6961">RFC 6961</a>
182      */
getPeerOcspStapledResponse()183     abstract byte[] getPeerOcspStapledResponse();
184 
185     /**
186      * Returns the signed certificate timestamp (SCT) received from the peer. The returned array
187      * is not copied; the caller must either not modify the returned array or make a copy.
188      *
189      * @see <a href="https://tools.ietf.org/html/rfc6962">RFC 6962</a>
190      */
getPeerSignedCertificateTimestamp()191     abstract byte[] getPeerSignedCertificateTimestamp();
192 
193     /**
194      * Converts the given session to bytes.
195      *
196      * @return session data as bytes or null if the session can't be converted
197      */
toBytes()198     abstract byte[] toBytes();
199 
200     /**
201      * Converts this object to a {@link SSLSession}. The returned session will support only a
202      * subset of the {@link SSLSession} API.
203      */
toSSLSession()204     abstract SSLSession toSSLSession();
205 
206     /**
207      * The session wrapper implementation.
208      */
209     private static final class Impl extends SslSessionWrapper {
210         private final NativeRef.SSL_SESSION ref;
211 
212         // BoringSSL offers no API to obtain these values directly from the SSL_SESSION.
213         private final AbstractSessionContext context;
214         private final String host;
215         private final int port;
216         private final String protocol;
217         private final String cipherSuite;
218         private final java.security.cert.X509Certificate[] peerCertificates;
219         private final byte[] peerOcspStapledResponse;
220         private final byte[] peerSignedCertificateTimestamp;
221 
Impl(AbstractSessionContext context, NativeRef.SSL_SESSION ref, String host, int port, java.security.cert.X509Certificate[] peerCertificates, byte[] peerOcspStapledResponse, byte[] peerSignedCertificateTimestamp)222         private Impl(AbstractSessionContext context, NativeRef.SSL_SESSION ref, String host,
223                 int port, java.security.cert.X509Certificate[] peerCertificates,
224                 byte[] peerOcspStapledResponse, byte[] peerSignedCertificateTimestamp) {
225             this.context = context;
226             this.host = host;
227             this.port = port;
228             this.peerCertificates = peerCertificates;
229             this.peerOcspStapledResponse = peerOcspStapledResponse;
230             this.peerSignedCertificateTimestamp = peerSignedCertificateTimestamp;
231             this.protocol = NativeCrypto.SSL_SESSION_get_version(ref.context);
232             this.cipherSuite =
233                     NativeCrypto.cipherSuiteToJava(NativeCrypto.SSL_SESSION_cipher(ref.context));
234             this.ref = ref;
235         }
236 
237         @Override
getId()238         byte[] getId() {
239             return NativeCrypto.SSL_SESSION_session_id(ref.context);
240         }
241 
getCreationTime()242         private long getCreationTime() {
243             return NativeCrypto.SSL_SESSION_get_time(ref.context);
244         }
245 
246         @Override
isValid()247         boolean isValid() {
248             long creationTimeMillis = getCreationTime();
249             // Use the minimum of the timeout from the context and the session.
250             long timeoutMillis =
251                     Math.max(0,
252                             Math.min(context.getSessionTimeout(),
253                                     NativeCrypto.SSL_SESSION_get_timeout(ref.context)))
254                     * 1000;
255             return (System.currentTimeMillis() - timeoutMillis) < creationTimeMillis;
256         }
257 
258         @Override
offerToResume(SslWrapper ssl)259         void offerToResume(SslWrapper ssl) throws SSLException {
260             ssl.offerToResumeSession(ref.context);
261         }
262 
263         @Override
getCipherSuite()264         String getCipherSuite() {
265             return cipherSuite;
266         }
267 
268         @Override
getProtocol()269         String getProtocol() {
270             return protocol;
271         }
272 
273         @Override
getPeerHost()274         String getPeerHost() {
275             return host;
276         }
277 
278         @Override
getPeerPort()279         int getPeerPort() {
280             return port;
281         }
282 
283         @Override
getPeerOcspStapledResponse()284         byte[] getPeerOcspStapledResponse() {
285             return peerOcspStapledResponse;
286         }
287 
288         @Override
getPeerSignedCertificateTimestamp()289         byte[] getPeerSignedCertificateTimestamp() {
290             return peerSignedCertificateTimestamp;
291         }
292 
293         @Override
toBytes()294         byte[] toBytes() {
295             try {
296                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
297                 DataOutputStream daos = new DataOutputStream(baos);
298 
299                 daos.writeInt(OPEN_SSL_WITH_TLS_SCT.value); // session type ID
300 
301                 // Session data.
302                 byte[] data = NativeCrypto.i2d_SSL_SESSION(ref.context);
303                 daos.writeInt(data.length);
304                 daos.write(data);
305 
306                 // Certificates.
307                 daos.writeInt(peerCertificates.length);
308 
309                 for (Certificate cert : peerCertificates) {
310                     data = cert.getEncoded();
311                     daos.writeInt(data.length);
312                     daos.write(data);
313                 }
314 
315                 if (peerOcspStapledResponse != null) {
316                     daos.writeInt(1);
317                     daos.writeInt(peerOcspStapledResponse.length);
318                     daos.write(peerOcspStapledResponse);
319                 } else {
320                     daos.writeInt(0);
321                 }
322 
323                 if (peerSignedCertificateTimestamp != null) {
324                     daos.writeInt(peerSignedCertificateTimestamp.length);
325                     daos.write(peerSignedCertificateTimestamp);
326                 } else {
327                     daos.writeInt(0);
328                 }
329 
330                 // TODO: local certificates?
331 
332                 return baos.toByteArray();
333             } catch (IOException e) {
334                 // TODO(nathanmittler): Better error handling?
335                 System.err.println("Failed to convert saved SSL Session: " + e.getMessage());
336                 return null;
337             } catch (CertificateEncodingException e) {
338                 log(e);
339                 return null;
340             }
341         }
342 
343         @Override
toSSLSession()344         SSLSession toSSLSession() {
345             return new SSLSession() {
346                 @Override
347                 public byte[] getId() {
348                     return Impl.this.getId();
349                 }
350 
351                 @Override
352                 public String getCipherSuite() {
353                     return Impl.this.getCipherSuite();
354                 }
355 
356                 @Override
357                 public String getProtocol() {
358                     return Impl.this.getProtocol();
359                 }
360 
361                 @Override
362                 public String getPeerHost() {
363                     return Impl.this.getPeerHost();
364                 }
365 
366                 @Override
367                 public int getPeerPort() {
368                     return Impl.this.getPeerPort();
369                 }
370 
371                 @Override
372                 public long getCreationTime() {
373                     return Impl.this.getCreationTime();
374                 }
375 
376                 @Override
377                 public boolean isValid() {
378                     return Impl.this.isValid();
379                 }
380 
381                 // UNSUPPORTED OPERATIONS
382 
383                 @Override
384                 public SSLSessionContext getSessionContext() {
385                     throw new UnsupportedOperationException();
386                 }
387 
388                 @Override
389                 public long getLastAccessedTime() {
390                     throw new UnsupportedOperationException();
391                 }
392 
393                 @Override
394                 public void invalidate() {
395                     throw new UnsupportedOperationException();
396                 }
397 
398                 @Override
399                 public void putValue(String s, Object o) {
400                     throw new UnsupportedOperationException();
401                 }
402 
403                 @Override
404                 public Object getValue(String s) {
405                     throw new UnsupportedOperationException();
406                 }
407 
408                 @Override
409                 public void removeValue(String s) {
410                     throw new UnsupportedOperationException();
411                 }
412 
413                 @Override
414                 public String[] getValueNames() {
415                     throw new UnsupportedOperationException();
416                 }
417 
418                 @Override
419                 public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
420                     throw new UnsupportedOperationException();
421                 }
422 
423                 @Override
424                 public Certificate[] getLocalCertificates() {
425                     throw new UnsupportedOperationException();
426                 }
427 
428                 @Override
429                 public X509Certificate[] getPeerCertificateChain()
430                         throws SSLPeerUnverifiedException {
431                     throw new UnsupportedOperationException();
432                 }
433 
434                 @Override
435                 public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
436                     throw new UnsupportedOperationException();
437                 }
438 
439                 @Override
440                 public Principal getLocalPrincipal() {
441                     throw new UnsupportedOperationException();
442                 }
443 
444                 @Override
445                 public int getPacketBufferSize() {
446                     throw new UnsupportedOperationException();
447                 }
448 
449                 @Override
450                 public int getApplicationBufferSize() {
451                     throw new UnsupportedOperationException();
452                 }
453             };
454         }
455     }
456 
457     private static void log(Throwable t) {
458         // TODO(nathanmittler): Better error handling?
459         System.out.println("Error inflating SSL session: "
460                 + (t.getMessage() != null ? t.getMessage() : t.getClass().getName()));
461     }
462 
463     private static void checkRemaining(ByteBuffer buf, int length) throws IOException {
464         if (length < 0) {
465             throw new IOException("Length is negative: " + length);
466         }
467         if (length > buf.remaining()) {
468             throw new IOException(
469                     "Length of blob is longer than available: " + length + " > " + buf.remaining());
470         }
471     }
472 }
473