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