1 /** 2 Botan TLS implementation 3 Copyright: © 2015 Sönke Ludwig, GlobecSys Inc 4 Authors: Sönke Ludwig, Etienne Cimon 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 */ 7 module vibe.stream.botan; 8 9 version(Have_botan): 10 11 version = X509; 12 import botan.constants; 13 import botan.cert.x509.x509cert; 14 import botan.cert.x509.certstor; 15 import botan.cert.x509.x509path; 16 import botan.math.bigint.bigint; 17 import botan.tls.blocking; 18 import botan.tls.channel; 19 import botan.tls.credentials_manager; 20 import botan.tls.exceptn; 21 import botan.tls.server; 22 import botan.tls.session_manager; 23 import botan.tls.server_info; 24 import botan.tls.ciphersuite; 25 import botan.rng.auto_rng; 26 import vibe.core.stream; 27 import vibe.stream.tls; 28 import vibe.core.net; 29 import vibe.internal.interfaceproxy : InterfaceProxy; 30 import std.datetime; 31 import std.exception; 32 33 class BotanTLSStream : TLSStream/*, Buffered*/ 34 { 35 @safe: 36 37 private { 38 InterfaceProxy!Stream m_stream; 39 TLSBlockingChannel m_tlsChannel; 40 BotanTLSContext m_ctx; 41 42 OnAlert m_alertCB; 43 OnHandshakeComplete m_handshakeComplete; 44 TLSCiphersuite m_cipher; 45 TLSProtocolVersion m_ver; 46 SysTime m_session_age; 47 X509Certificate m_peer_cert; 48 TLSCertificateInformation m_cert_compat; 49 ubyte[] m_sess_id; 50 Exception m_ex; 51 } 52 53 /// Returns the date/time the session was started 54 @property SysTime started() const { return m_session_age; } 55 56 /// Get the session ID 57 @property const(ubyte[]) sessionId() { return m_sess_id; } 58 59 /// Returns the remote public certificate from the chain 60 @property const(X509Certificate) x509Certificate() const @system { return m_peer_cert; } 61 62 /// Returns the negotiated version of the TLS Protocol 63 @property TLSProtocolVersion protocol() const { return m_ver; } 64 65 /// Returns the complete ciphersuite details from the negotiated TLS connection 66 @property TLSCiphersuite cipher() const { return m_cipher; } 67 68 @property string alpn() const @trusted { return m_tlsChannel.underlyingChannel().applicationProtocol(); } 69 70 @property TLSCertificateInformation peerCertificate() 71 { 72 import vibe.core.log : logWarn; 73 74 if (!!m_peer_cert) 75 logWarn("BotanTLSStream.peerCertificate is not implemented and does not return the actual certificate information."); 76 77 return TLSCertificateInformation.init; 78 } 79 80 // Constructs a new TLS Client Stream and connects with the specified handlers 81 this(InterfaceProxy!Stream underlying, BotanTLSContext ctx, 82 void delegate(in TLSAlert alert, in ubyte[] ub) alert_cb, 83 bool delegate(in TLSSession session) hs_cb, 84 string peer_name = null, NetworkAddress peer_address = NetworkAddress.init) 85 @trusted { 86 m_ctx = ctx; 87 m_stream = underlying; 88 m_alertCB = alert_cb; 89 m_handshakeComplete = hs_cb; 90 91 assert(m_ctx.m_kind == TLSContextKind.client, "Connecting through a server context is not supported"); 92 // todo: add service name? 93 TLSServerInformation server_info = TLSServerInformation(peer_name, peer_address.port); 94 m_tlsChannel = TLSBlockingChannel(&onRead, &onWrite, &onAlert, &onHandhsakeComplete, m_ctx.m_sessionManager, m_ctx.m_credentials, m_ctx.m_policy, m_ctx.m_rng, server_info, m_ctx.m_offer_version, m_ctx.m_clientOffers.dup); 95 96 try m_tlsChannel.doHandshake(); 97 catch (Exception e) { 98 m_ex = e; 99 } 100 } 101 102 // This constructor is used by the TLS Context for both server and client streams 103 this(InterfaceProxy!Stream underlying, BotanTLSContext ctx, TLSStreamState state, string peer_name = null, NetworkAddress peer_address = NetworkAddress.init) 104 @trusted { 105 m_ctx = ctx; 106 m_stream = underlying; 107 108 if (state == TLSStreamState.accepting) 109 { 110 assert(m_ctx.m_kind != TLSContextKind.client, "Accepting through a client context is not supported"); 111 m_tlsChannel = TLSBlockingChannel(&onRead, &onWrite, &onAlert, &onHandhsakeComplete, m_ctx.m_sessionManager, m_ctx.m_credentials, m_ctx.m_policy, m_ctx.m_rng, &m_ctx.nextProtocolHandler, &m_ctx.sniHandler, m_ctx.m_is_datagram); 112 113 } 114 else if (state == TLSStreamState.connecting) { 115 assert(m_ctx.m_kind == TLSContextKind.client, "Connecting through a server context is not supported"); 116 // todo: add service name? 117 TLSServerInformation server_info = TLSServerInformation(peer_name, peer_address.port); 118 m_tlsChannel = TLSBlockingChannel(&onRead, &onWrite, &onAlert, &onHandhsakeComplete, m_ctx.m_sessionManager, m_ctx.m_credentials, m_ctx.m_policy, m_ctx.m_rng, server_info, m_ctx.m_offer_version, m_ctx.m_clientOffers.dup); 119 } 120 else /*if (state == TLSStreamState.connected)*/ { 121 m_tlsChannel = TLSBlockingChannel.init; 122 throw new Exception("Cannot load BotanTLSSteam from a connected TLS session"); 123 } 124 125 try m_tlsChannel.doHandshake(); 126 catch (Exception e) { 127 m_ex = e; 128 } 129 } 130 131 ~this() 132 @trusted { 133 try m_tlsChannel.destroy(); 134 catch (Exception e) { 135 } 136 } 137 138 void flush() 139 { 140 processException(); 141 m_stream.flush(); 142 } 143 144 void finalize() 145 { 146 if (() @trusted { return m_tlsChannel.isClosed(); } ()) 147 return; 148 149 processException(); 150 scope(success) 151 processException(); 152 153 () @trusted { m_tlsChannel.close(); } (); 154 m_stream.flush(); 155 } 156 157 size_t read(scope ubyte[] dst, IOMode) 158 { 159 processException(); 160 scope(success) 161 processException(); 162 () @trusted { m_tlsChannel.read(dst); } (); 163 return dst.length; 164 } 165 166 alias read = Stream.read; 167 168 ubyte[] readChunk(ubyte[] buf) 169 { 170 processException(); 171 scope(success) 172 processException(); 173 return () @trusted { return m_tlsChannel.readBuf(buf); } (); 174 } 175 176 size_t write(in ubyte[] src, IOMode) 177 { 178 processException(); 179 scope(success) 180 processException(); 181 () @trusted { m_tlsChannel.write(src); } (); 182 return src.length; 183 } 184 185 alias write = Stream.write; 186 187 @property bool empty() 188 { 189 processException(); 190 return leastSize() == 0; 191 } 192 193 @property ulong leastSize() 194 { 195 size_t ret = () @trusted { return m_tlsChannel.pending(); } (); 196 if (ret > 0) return ret; 197 if (() @trusted { return m_tlsChannel.isClosed(); } () || m_ex !is null) return 0; 198 try () @trusted { m_tlsChannel.readBuf(null); } (); // force an exchange 199 catch (Exception e) { return 0; } 200 ret = () @trusted { return m_tlsChannel.pending(); } (); 201 //logDebug("Least size returned: ", ret); 202 return ret > 0 ? ret : m_stream.empty ? 0 : 1; 203 } 204 205 @property bool dataAvailableForRead() 206 { 207 processException(); 208 if (() @trusted { return m_tlsChannel.pending(); } () > 0) return true; 209 if (!m_stream.dataAvailableForRead) return false; 210 () @trusted { m_tlsChannel.readBuf(null); } (); // force an exchange 211 return () @trusted { return m_tlsChannel.pending(); } () > 0; 212 } 213 214 const(ubyte)[] peek() 215 { 216 processException(); 217 auto peeked = () @trusted { return m_tlsChannel.peek(); } (); 218 //logDebug("Peeked data: ", cast(ubyte[])peeked); 219 //logDebug("Peeked data ptr: ", peeked.ptr); 220 return peeked; 221 } 222 223 void setAlertCallback(OnAlert alert_cb) 224 @system { 225 processException(); 226 m_alertCB = alert_cb; 227 } 228 229 void setHandshakeCallback(OnHandshakeComplete hs_cb) 230 @system { 231 processException(); 232 m_handshakeComplete = hs_cb; 233 } 234 235 private void processException() 236 @safe { 237 if (auto ex = m_ex) { 238 m_ex = null; 239 throw ex; 240 } 241 } 242 243 private void onAlert(in TLSAlert alert, in ubyte[] data) 244 @trusted { 245 if (alert.isFatal) 246 m_ex = new Exception("TLS Alert Received: " ~ alert.typeString()); 247 if (m_alertCB) 248 m_alertCB(alert, data); 249 } 250 251 private bool onHandhsakeComplete(in TLSSession session) 252 @trusted { 253 m_sess_id = cast(ubyte[])session.sessionId()[].dup; 254 m_cipher = session.ciphersuite(); 255 m_session_age = session.startTime(); 256 m_ver = session.Version(); 257 if (session.peerCerts().length > 0) 258 m_peer_cert = session.peerCerts()[0]; 259 if (m_handshakeComplete) 260 return m_handshakeComplete(session); 261 return true; 262 } 263 264 private ubyte[] onRead(ubyte[] buf) 265 { 266 import std.algorithm : min; 267 268 ubyte[] ret; 269 /*if (auto buffered = cast(Buffered)m_stream) { 270 ret = buffered.readChunk(buf); 271 return ret; 272 }*/ 273 274 size_t len = min(m_stream.leastSize(), buf.length); 275 if (len == 0) return null; 276 m_stream.read(buf[0 .. len]); 277 return buf[0 .. len]; 278 } 279 280 private void onWrite(in ubyte[] src) { 281 //logDebug("Write: %s", src); 282 m_stream.write(src); 283 } 284 } 285 286 class BotanTLSContext : TLSContext { 287 private { 288 TLSSessionManager m_sessionManager; 289 TLSPolicy m_policy; 290 TLSCredentialsManager m_credentials; 291 TLSContextKind m_kind; 292 AutoSeededRNG m_rng; 293 TLSProtocolVersion m_offer_version; 294 TLSServerNameCallback m_sniCallback; 295 TLSALPNCallback m_serverCb; 296 Vector!string m_clientOffers; 297 bool m_is_datagram; 298 bool m_certChecked; 299 } 300 301 this(TLSContextKind kind, 302 TLSCredentialsManager credentials = null, 303 TLSPolicy policy = null, 304 TLSSessionManager session_manager = null, 305 bool is_datagram = false) 306 @trusted { 307 if (!credentials) 308 credentials = new CustomTLSCredentials(); 309 m_kind = kind; 310 m_credentials = credentials; 311 m_is_datagram = is_datagram; 312 313 if (is_datagram) 314 m_offer_version = TLSProtocolVersion.DTLS_V12; 315 else 316 m_offer_version = TLSProtocolVersion.TLS_V12; 317 318 m_rng = new AutoSeededRNG(); 319 if (!session_manager) 320 session_manager = new TLSSessionManagerInMemory(m_rng); 321 m_sessionManager = session_manager; 322 323 if (!policy) { 324 if (!gs_default_policy) 325 gs_default_policy = new CustomTLSPolicy(); 326 policy = cast(TLSPolicy)gs_default_policy; 327 } 328 m_policy = policy; 329 } 330 331 /// The kind of TLS context (client/server) 332 @property TLSContextKind kind() const { 333 return m_kind; 334 } 335 336 /// Used by clients to indicate protocol preference, use TLSPolicy to restrict the protocol versions 337 @property void defaultProtocolOffer(TLSProtocolVersion ver) { m_offer_version = ver; } 338 /// ditto 339 @property TLSProtocolVersion defaultProtocolOffer() { return m_offer_version; } 340 341 @property void sniCallback(TLSServerNameCallback callback) 342 { 343 m_sniCallback = callback; 344 } 345 @property inout(TLSServerNameCallback) sniCallback() inout { return m_sniCallback; } 346 347 /// Callback function invoked by server to choose alpn 348 @property void alpnCallback(TLSALPNCallback alpn_chooser) 349 { 350 m_serverCb = alpn_chooser; 351 } 352 353 /// Get the current ALPN callback function 354 @property TLSALPNCallback alpnCallback() const { return m_serverCb; } 355 356 /// Invoked by client to offer alpn, all strings are copied on the GC 357 @property void setClientALPN(string[] alpn_list) 358 { 359 () @trusted { m_clientOffers.clear(); } (); 360 foreach (alpn; alpn_list) 361 () @trusted { m_clientOffers ~= alpn.idup; } (); 362 } 363 364 /** Creates a new stream associated to this context. 365 */ 366 TLSStream createStream(InterfaceProxy!Stream underlying, TLSStreamState state, string peer_name = null, NetworkAddress peer_address = NetworkAddress.init) 367 { 368 if (!m_certChecked) 369 () @trusted { checkCert(); } (); 370 return new BotanTLSStream(underlying, this, state, peer_name, peer_address); 371 } 372 373 /** Specifies the validation level of remote peers. 374 375 The default mode for TLSContextKind.client is 376 TLSPeerValidationMode.trustedCert and the default for 377 TLSContextKind.server is TLSPeerValidationMode.none. 378 */ 379 @property void peerValidationMode(TLSPeerValidationMode mode) { 380 if (auto credentials = cast(CustomTLSCredentials)m_credentials) { 381 credentials.m_validationMode = mode; 382 return; 383 } 384 else assert(false, "Cannot handle peerValidationMode if CustomTLSCredentials is not used"); 385 } 386 /// ditto 387 @property TLSPeerValidationMode peerValidationMode() const { 388 if (auto credentials = cast(const(CustomTLSCredentials))m_credentials) { 389 return credentials.m_validationMode; 390 } 391 else assert(false, "Cannot handle peerValidationMode if CustomTLSCredentials is not used"); 392 } 393 394 /** An optional user callback for peer validation. 395 396 Peer validation callback is unused in Botan. Specify a custom TLS Policy to handle peer certificate data. 397 */ 398 @property void peerValidationCallback(TLSPeerValidationCallback callback) { assert(false, "Peer validation callback is unused in Botan. Specify a custom TLS Policy to handle peer certificate data."); } 399 /// ditto 400 @property inout(TLSPeerValidationCallback) peerValidationCallback() inout { return TLSPeerValidationCallback.init; } 401 402 /** The maximum length of an accepted certificate chain. 403 404 Any certificate chain longer than this will result in the TLS 405 negitiation failing. 406 407 The default value is 9. 408 */ 409 @property void maxCertChainLength(int val) { 410 411 if (auto credentials = cast(CustomTLSCredentials)m_credentials) { 412 credentials.m_max_cert_chain_length = val; 413 return; 414 } 415 else assert(false, "Cannot handle maxCertChainLength if CustomTLSCredentials is not used"); 416 } 417 /// ditto 418 @property int maxCertChainLength() const { 419 if (auto credentials = cast(const(CustomTLSCredentials))m_credentials) { 420 return credentials.m_max_cert_chain_length; 421 } 422 else assert(false, "Cannot handle maxCertChainLength if CustomTLSCredentials is not used"); 423 } 424 425 void setCipherList(string list = null) { assert(false, "Incompatible interface method requested"); } 426 427 /** Set params to use for DH cipher. 428 * 429 * By default the 2048-bit prime from RFC 3526 is used. 430 * 431 * Params: 432 * pem_file = Path to a PEM file containing the DH parameters. Calling 433 * this function without argument will restore the default. 434 */ 435 void setDHParams(string pem_file=null) { assert(false, "Incompatible interface method requested"); } 436 437 /** Set the elliptic curve to use for ECDH cipher. 438 * 439 * By default a curve is either chosen automatically or prime256v1 is used. 440 * 441 * Params: 442 * curve = The short name of the elliptic curve to use. Calling this 443 * function without argument will restore the default. 444 * 445 */ 446 void setECDHCurve(string curve=null) { assert(false, "Incompatible interface method requested"); } 447 448 /// Sets a certificate file to use for authenticating to the remote peer 449 void useCertificateChainFile(string path) { 450 if (auto credentials = cast(CustomTLSCredentials)m_credentials) { 451 m_certChecked = false; 452 () @trusted { credentials.m_server_cert = X509Certificate(path); } (); 453 return; 454 } 455 else assert(false, "Cannot handle useCertificateChainFile if CustomTLSCredentials is not used"); 456 } 457 458 /// Sets the private key to use for authenticating to the remote peer based 459 /// on the configured certificate chain file. 460 /// todo: Use passphrase? 461 void usePrivateKeyFile(string path) { 462 if (auto credentials = cast(CustomTLSCredentials)m_credentials) { 463 import botan.pubkey.pkcs8 : loadKey; 464 credentials.m_key = () @trusted { return loadKey(path, m_rng); } (); 465 return; 466 } 467 else assert(false, "Cannot handle usePrivateKeyFile if CustomTLSCredentials is not used"); 468 } 469 470 /** Sets the list of trusted certificates for verifying peer certificates. 471 472 If this is a server context, this also entails that the given 473 certificates are advertised to connecting clients during handshake. 474 475 On Linux, the system's root certificate authority list is usually 476 found at "/etc/ssl/certs/ca-certificates.crt", 477 "/etc/pki/tls/certs/ca-bundle.crt", or "/etc/ssl/ca-bundle.pem". 478 */ 479 void useTrustedCertificateFile(string path) { 480 if (auto credentials = cast(CustomTLSCredentials)m_credentials) { 481 auto store = () @trusted { return new CertificateStoreInMemory; } (); 482 483 () @trusted { store.addCertificate(X509Certificate(path)); } (); 484 () @trusted { credentials.m_stores.pushBack(store); } (); 485 return; 486 } 487 else assert(false, "Cannot handle useTrustedCertificateFile if CustomTLSCredentials is not used"); 488 } 489 490 private SNIContextSwitchInfo sniHandler(string hostname) 491 { 492 auto ctx = onSNI(hostname); 493 if (!ctx) return SNIContextSwitchInfo.init; 494 SNIContextSwitchInfo chgctx; 495 chgctx.session_manager = ctx.m_sessionManager; 496 chgctx.credentials = ctx.m_credentials; 497 chgctx.policy = ctx.m_policy; 498 chgctx.next_proto = &ctx.nextProtocolHandler; 499 //chgctx.user_data = cast(void*)hostname.toStringz(); 500 return chgctx; 501 } 502 503 private string nextProtocolHandler(in Vector!string offers) { 504 enforce(m_kind == TLSContextKind.server, "Attempted ALPN selection on a " ~ m_kind.to!string); 505 if (m_serverCb !is null) 506 return m_serverCb(offers[]); 507 else return ""; 508 } 509 510 private BotanTLSContext onSNI(string hostname) { 511 if (m_kind != TLSContextKind.serverSNI) 512 return null; 513 514 TLSContext ctx = m_sniCallback(hostname); 515 if (auto bctx = cast(BotanTLSContext) ctx) { 516 // Since this happens in a BotanTLSStream, the stream info (r/w callback) remains the same 517 return bctx; 518 } 519 520 // We cannot use anything else than a Botan stream, and any null value with serverSNI is a failure 521 throw new Exception("Could not find specified hostname"); 522 } 523 524 private void checkCert() { 525 m_certChecked = true; 526 if (m_kind == TLSContextKind.client) return; 527 if (auto creds = cast(CustomTLSCredentials) m_credentials) { 528 auto sigs = m_policy.allowedSignatureMethods(); 529 import botan.asn1.oids : OIDS; 530 import vibe.core.log : logDebug; 531 auto sig_algo = OIDS.lookup(creds.m_server_cert.signatureAlgorithm().oid()); 532 import std.range : front; 533 import std.algorithm.iteration : splitter; 534 string sig_algo_str = sig_algo.splitter("/").front.to!string; 535 logDebug("Certificate algorithm: %s", sig_algo_str); 536 bool found; 537 foreach (sig; sigs[]) { 538 if (sig == sig_algo_str) { 539 found = true; break; 540 } 541 } 542 assert(found, "Server Certificate uses a signing algorithm that is not accepted in the server policy."); 543 } 544 } 545 } 546 547 /** 548 * TLS Policy as a settings object 549 */ 550 private class CustomTLSPolicy : TLSPolicy 551 { 552 private { 553 TLSProtocolVersion m_min_ver = TLSProtocolVersion.TLS_V10; 554 int m_min_dh_group_size = 1024; 555 Vector!TLSCiphersuite m_pri_ciphersuites; 556 Vector!string m_pri_ecc_curves; 557 Duration m_session_lifetime = 24.hours; 558 bool m_pri_ciphers_exclusive; 559 bool m_pri_curves_exclusive; 560 } 561 562 /// Sets the minimum acceptable protocol version 563 @property void minProtocolVersion(TLSProtocolVersion ver) { m_min_ver = ver; } 564 565 /// Get the minimum acceptable protocol version 566 @property TLSProtocolVersion minProtocolVersion() { return m_min_ver; } 567 568 @property void minDHGroupSize(int sz) { m_min_dh_group_size = sz; } 569 @property int minDHGroupSize() { return m_min_dh_group_size; } 570 571 /// Add a cipher suite to the priority ciphers with lowest ordering value 572 void addPriorityCiphersuites(TLSCiphersuite[] suites) { m_pri_ciphersuites ~= suites; } 573 574 @property TLSCiphersuite[] ciphers() { return m_pri_ciphersuites[]; } 575 576 /// Set to true to use excuslively priority ciphers passed through "addCiphersuites" 577 @property void priorityCiphersOnly(bool b) { m_pri_ciphers_exclusive = b; } 578 @property bool priorityCiphersOnly() { return m_pri_ciphers_exclusive; } 579 580 void addPriorityCurves(string[] curves) { 581 m_pri_ecc_curves ~= curves; 582 } 583 @property string[] priorityCurves() { return m_pri_ecc_curves[]; } 584 585 /// Uses only priority curves passed through "add" 586 @property void priorityCurvesOnly(bool b) { m_pri_curves_exclusive = b; } 587 @property bool priorityCurvesOnly() { return m_pri_curves_exclusive; } 588 589 override string chooseCurve(in Vector!string curve_names) const 590 { 591 import std.algorithm : countUntil; 592 foreach (curve; m_pri_ecc_curves[]) { 593 if (curve_names[].countUntil(curve) != -1) 594 return curve; 595 } 596 597 if (!m_pri_curves_exclusive) 598 return super.chooseCurve((cast(Vector!string)curve_names).move); 599 return ""; 600 } 601 602 override Vector!string allowedEccCurves() const { 603 Vector!string ret; 604 if (!m_pri_ecc_curves.empty) 605 ret ~= m_pri_ecc_curves[]; 606 if (!m_pri_curves_exclusive) 607 ret ~= super.allowedEccCurves(); 608 return ret; 609 } 610 611 override Vector!ushort ciphersuiteList(TLSProtocolVersion _version, bool have_srp) const { 612 Vector!ushort ret; 613 if (m_pri_ciphersuites.length > 0) { 614 foreach (suite; m_pri_ciphersuites) { 615 ret ~= suite.ciphersuiteCode(); 616 } 617 } 618 619 if (!m_pri_ciphers_exclusive) { 620 ret ~= super.ciphersuiteList(_version, have_srp); 621 } 622 623 return ret; 624 } 625 626 override bool acceptableProtocolVersion(TLSProtocolVersion _version) const 627 { 628 if (m_min_ver != TLSProtocolVersion.init) 629 return _version >= m_min_ver; 630 return super.acceptableProtocolVersion(_version); 631 } 632 633 override Duration sessionTicketLifetime() const { 634 return m_session_lifetime; 635 } 636 637 override size_t minimumDhGroupSize() const { 638 return m_min_dh_group_size; 639 } 640 } 641 642 643 private class CustomTLSCredentials : TLSCredentialsManager 644 { 645 private { 646 TLSPeerValidationMode m_validationMode = TLSPeerValidationMode.none; 647 int m_max_cert_chain_length = 9; 648 } 649 650 public { 651 X509Certificate m_server_cert, m_ca_cert; 652 PrivateKey m_key; 653 Vector!CertificateStore m_stores; 654 } 655 656 this() { } 657 658 // Client constructor 659 this(TLSPeerValidationMode validation_mode = TLSPeerValidationMode.checkPeer) { 660 m_validationMode = validation_mode; 661 } 662 663 // Server constructor 664 this(X509Certificate server_cert, X509Certificate ca_cert, PrivateKey server_key) 665 { 666 m_server_cert = server_cert; 667 m_ca_cert = ca_cert; 668 m_key = server_key; 669 auto store = new CertificateStoreInMemory; 670 671 store.addCertificate(m_ca_cert); 672 m_stores.pushBack(store); 673 m_validationMode = TLSPeerValidationMode.none; 674 } 675 676 override Vector!CertificateStore trustedCertificateAuthorities(in string, in string) 677 { 678 // todo: Check machine stores for client mode 679 680 return m_stores.dup; 681 } 682 683 override Vector!X509Certificate certChain(const ref Vector!string cert_key_types, in string type, in string) 684 { 685 Vector!X509Certificate chain; 686 687 if (type == "tls-server") 688 { 689 bool have_match = false; 690 foreach (cert_key_type; cert_key_types[]) { 691 if (cert_key_type == m_key.algoName) { 692 enforce(m_server_cert, "Private Key was defined but no corresponding server certificate was found."); 693 have_match = true; 694 } 695 } 696 697 if (have_match) 698 { 699 chain.pushBack(m_server_cert); 700 if (m_ca_cert) chain.pushBack(m_ca_cert); 701 } 702 } 703 704 return chain.move(); 705 } 706 707 override void verifyCertificateChain(in string type, in string purported_hostname, const ref Vector!X509Certificate cert_chain) 708 { 709 if (cert_chain.empty) 710 throw new InvalidArgument("Certificate chain was empty"); 711 712 if (m_validationMode == TLSPeerValidationMode.validCert) 713 { 714 auto trusted_CAs = trustedCertificateAuthorities(type, purported_hostname); 715 716 PathValidationRestrictions restrictions; 717 restrictions.maxCertChainLength = m_max_cert_chain_length; 718 719 auto result = x509PathValidate(cert_chain, restrictions, trusted_CAs); 720 721 if (!result.successfulValidation()) 722 throw new Exception("Certificate validation failure: " ~ result.resultString()); 723 724 if (!certInSomeStore(trusted_CAs, result.trustRoot())) 725 throw new Exception("Certificate chain roots in unknown/untrusted CA"); 726 727 if (purported_hostname != "" && !cert_chain[0].matchesDnsName(purported_hostname)) 728 throw new Exception("Certificate did not match hostname"); 729 730 return; 731 } 732 733 if (m_validationMode & TLSPeerValidationMode.checkTrust) { 734 auto trusted_CAs = trustedCertificateAuthorities(type, purported_hostname); 735 736 PathValidationRestrictions restrictions; 737 restrictions.maxCertChainLength = m_max_cert_chain_length; 738 739 PathValidationResult result; 740 try result = x509PathValidate(cert_chain, restrictions, trusted_CAs); 741 catch (Exception e) { } 742 743 if (!certInSomeStore(trusted_CAs, result.trustRoot())) 744 throw new Exception("Certificate chain roots in unknown/untrusted CA"); 745 } 746 747 // Commit to basic tests for other validation modes 748 if (m_validationMode & TLSPeerValidationMode.checkCert) { 749 import botan.asn1.asn1_time : X509Time; 750 X509Time current_time = X509Time(Clock.currTime()); 751 // Check all certs for valid time range 752 if (current_time < X509Time(cert_chain[0].startTime())) 753 throw new Exception("Certificate is not yet valid"); 754 755 if (current_time > X509Time(cert_chain[0].endTime())) 756 throw new Exception("Certificate has expired"); 757 758 if (cert_chain[0].isSelfSigned()) 759 throw new Exception("Certificate was self signed"); 760 } 761 762 if (m_validationMode & TLSPeerValidationMode.checkPeer) 763 if (purported_hostname != "" && !cert_chain[0].matchesDnsName(purported_hostname)) 764 throw new Exception("Certificate did not match hostname"); 765 766 767 } 768 769 override PrivateKey privateKeyFor(in X509Certificate, in string, in string) 770 { 771 return m_key; 772 } 773 774 // Interface fallthrough 775 override Vector!X509Certificate certChainSingleType(in string cert_key_type, 776 in string type, 777 in string context) 778 { 779 return super.certChainSingleType(cert_key_type, type, context); 780 } 781 782 override bool attemptSrp(in string type, in string context) 783 { 784 return super.attemptSrp(type, context); 785 } 786 787 override string srpIdentifier(in string type, in string context) 788 { 789 return super.srpIdentifier(type, context); 790 } 791 792 override string srpPassword(in string type, in string context, in string identifier) 793 { 794 return super.srpPassword(type, context, identifier); 795 } 796 797 override bool srpVerifier(in string type, 798 in string context, 799 in string identifier, 800 ref string group_name, 801 ref BigInt verifier, 802 ref Vector!ubyte salt, 803 bool generate_fake_on_unknown) 804 { 805 return super.srpVerifier(type, context, identifier, group_name, verifier, salt, generate_fake_on_unknown); 806 } 807 808 override string pskIdentityHint(in string type, in string context) 809 { 810 return super.pskIdentityHint(type, context); 811 } 812 813 override string pskIdentity(in string type, in string context, in string identity_hint) 814 { 815 return super.pskIdentity(type, context, identity_hint); 816 } 817 818 override SymmetricKey psk(in string type, in string context, in string identity) 819 { 820 return super.psk(type, context, identity); 821 } 822 823 override bool hasPsk() 824 { 825 return super.hasPsk(); 826 } 827 } 828 829 private CustomTLSCredentials createCreds() 830 { 831 832 import botan.rng.auto_rng; 833 import botan.cert.x509.pkcs10; 834 import botan.cert.x509.x509self; 835 import botan.cert.x509.x509_ca; 836 import botan.pubkey.algo.rsa; 837 import botan.codec.hex; 838 import botan.utils.types; 839 scope rng = new AutoSeededRNG(); 840 auto ca_key = RSAPrivateKey(rng, 1024); 841 scope(exit) ca_key.destroy(); 842 843 X509CertOptions ca_opts; 844 ca_opts.common_name = "Test CA"; 845 ca_opts.country = "US"; 846 ca_opts.CAKey(1); 847 848 X509Certificate ca_cert = x509self.createSelfSignedCert(ca_opts, *ca_key, "SHA-256", rng); 849 850 auto server_key = RSAPrivateKey(rng, 1024); 851 852 X509CertOptions server_opts; 853 server_opts.common_name = "localhost"; 854 server_opts.country = "US"; 855 856 PKCS10Request req = x509self.createCertReq(server_opts, *server_key, "SHA-256", rng); 857 858 X509CA ca = X509CA(ca_cert, *ca_key, "SHA-256"); 859 860 auto now = Clock.currTime(UTC()); 861 X509Time start_time = X509Time(now); 862 X509Time end_time = X509Time(now + 365.days); 863 864 X509Certificate server_cert = ca.signRequest(req, rng, start_time, end_time); 865 866 return new CustomTLSCredentials(server_cert, ca_cert, server_key.release()); 867 868 } 869 870 private { 871 __gshared CustomTLSPolicy gs_default_policy; 872 }