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