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 }