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