1 /** 2 * TLS Server 3 * 4 * Copyright: 5 * (C) 2004-2011,2015 Jack Lloyd 6 * (C) 2014-2015 Etienne Cimon 7 * 8 * License: 9 * Botan is released under the Simplified BSD License (see LICENSE.md) 10 */ 11 module botan.tls.server; 12 13 import botan.constants; 14 static if (BOTAN_HAS_TLS): 15 16 public import botan.tls.channel; 17 import botan.tls.credentials_manager; 18 import botan.tls.handshake_state; 19 import botan.tls.messages; 20 import botan.tls.alert; 21 import botan.rng.rng; 22 import memutils.dictionarylist; 23 import memutils.hashmap; 24 import botan.utils.types; 25 import std.datetime; 26 27 alias NextProtocolHandler = string delegate(in Vector!string); 28 alias SNIHandler = SNIContextSwitchInfo delegate(string); 29 30 struct SNIContextSwitchInfo 31 { 32 TLSSessionManager session_manager; 33 TLSCredentialsManager credentials; 34 TLSPolicy policy; 35 NextProtocolHandler next_proto; 36 void* user_data; 37 } 38 39 40 /** 41 * TLS Server 42 */ 43 final class TLSServer : TLSChannel 44 { 45 public: 46 47 /** 48 * TLSServer initialization 49 */ 50 this(DataWriter output_fn, 51 OnClearData data_cb, 52 OnAlert alert_cb, 53 OnHandshakeComplete handshake_cb, 54 TLSSessionManager session_manager, 55 TLSCredentialsManager creds, 56 TLSPolicy policy, 57 RandomNumberGenerator rng, 58 NextProtocolHandler next_proto = null, 59 SNIHandler sni_handler = null, 60 bool is_datagram = false, 61 size_t io_buf_sz = 16*1024) 62 { 63 super(output_fn, data_cb, alert_cb, handshake_cb, session_manager, rng, is_datagram, io_buf_sz); 64 m_policy = policy; 65 m_creds = creds; 66 m_choose_next_protocol = next_proto; 67 m_sni_handler = sni_handler; 68 } 69 70 void* getUserData() { return m_user_data; } 71 72 protected: 73 override Vector!X509Certificate getPeerCertChain(in HandshakeState state) const 74 { 75 if (state.clientCerts()) 76 return state.clientCerts().certChain().dup(); 77 return Vector!X509Certificate(); 78 } 79 80 /* 81 * Send a hello request to the client 82 */ 83 override void initiateHandshake(HandshakeState state, bool force_full_renegotiation) 84 { 85 (cast(ServerHandshakeState)state).allow_session_resumption = !force_full_renegotiation; 86 87 auto hello_req = scoped!HelloRequest(state.handshakeIo()); 88 } 89 90 /* 91 * Process a handshake message 92 */ 93 override void processHandshakeMsg(in HandshakeState active_state, 94 HandshakeState state_base, 95 HandshakeType type, 96 const ref Vector!ubyte contents) 97 { 98 ServerHandshakeState state = cast(ServerHandshakeState)(state_base); 99 100 state.confirmTransitionTo(type); 101 102 /* 103 * The change cipher spec message isn't technically a handshake 104 * message so it's not included in the hash. The finished and 105 * certificate verify messages are verified based on the current 106 * state of the hash *before* this message so we delay adding them 107 * to the hash computation until we've processed them below. 108 */ 109 if (type != HANDSHAKE_CCS && type != FINISHED && type != CERTIFICATE_VERIFY) 110 { 111 state.hash().update(state.handshakeIo().format(contents, type)); 112 } 113 114 if (type == CLIENT_HELLO) 115 { 116 const bool initial_handshake = !active_state; 117 118 if (!m_policy.allowInsecureRenegotiation() && 119 !(initial_handshake || secureRenegotiationSupported())) 120 { 121 sendWarningAlert(TLSAlert.NO_RENEGOTIATION); 122 return; 123 } 124 state.clientHello(new ClientHello(contents, type)); 125 126 // choose a proper context depending on hostname and register the userdata 127 if (m_sni_handler) { 128 const string sni_hostname = state.clientHello().sniHostname(); 129 SNIContextSwitchInfo ctx = m_sni_handler(sni_hostname); 130 if (ctx !is SNIContextSwitchInfo.init) 131 switchContext(ctx); 132 } 133 134 const TLSProtocolVersion client_version = state.clientHello().Version(); 135 136 TLSProtocolVersion negotiated_version; 137 138 const TLSProtocolVersion latest_supported = m_policy.latestSupportedVersion(client_version.isDatagramProtocol()); 139 140 if ((initial_handshake && client_version.knownVersion()) || 141 (!initial_handshake && client_version == active_state.Version())) 142 { 143 /* 144 Common cases: new client hello with some known version, or a 145 renegotiation using the same version as previously 146 negotiated. 147 */ 148 149 negotiated_version = client_version; 150 } 151 else if (!initial_handshake && (client_version != active_state.Version())) 152 { 153 /* 154 * If this is a renegotiation, and the client has offered a 155 * later version than what it initially negotiated, negotiate 156 * the old version. This matches OpenSSL's behavior. If the 157 * client is offering a version earlier than what it initially 158 * negotiated, reject as a probable attack. 159 */ 160 if (active_state.Version() > client_version) 161 { 162 throw new TLSException(TLSAlert.PROTOCOL_VERSION, 163 "TLSClient negotiated " ~ 164 active_state.Version().toString() ~ 165 " then renegotiated with " ~ 166 client_version.toString()); 167 } 168 else 169 negotiated_version = active_state.Version(); 170 } 171 else 172 { 173 /* 174 New negotiation using a version we don't know. Offer 175 them the best we currently know and support. 176 */ 177 negotiated_version = latest_supported; 178 } 179 180 if (!m_policy.acceptableProtocolVersion(negotiated_version)) 181 { 182 throw new TLSException(TLSAlert.PROTOCOL_VERSION, "Client version " ~ negotiated_version.toString() ~ " is unacceptable by policy"); 183 } 184 185 if (state.clientHello().sentFallbackSCSV()) 186 { 187 if (latest_supported > client_version) 188 throw new TLSException(TLSAlert.INAPPROPRIATE_FALLBACK, "Client signalled fallback SCSV, possible attack"); 189 } 190 191 secureRenegotiationCheck(state.clientHello()); 192 193 state.setVersion(negotiated_version); 194 195 TLSSession session_info; 196 scope(exit) destroy(session_info); 197 const bool resuming = state.allow_session_resumption && 198 checkForResume(session_info, 199 sessionManager(), 200 m_creds, 201 state.clientHello(), 202 m_policy.sessionTicketLifetime()); 203 204 bool have_session_ticket_key = false; 205 206 if (m_creds.hasPsk()) try 207 { 208 have_session_ticket_key = m_creds.psk("tls-server", "session-ticket", "").length > 0; 209 } 210 catch (Exception) {} 211 212 m_application_protocol = ""; 213 214 if (m_choose_next_protocol && state.clientHello().supportsAlpn()) 215 m_application_protocol = m_choose_next_protocol(state.clientHello().nextProtocols()); 216 217 if (resuming) 218 { 219 // resume session 220 221 const bool offer_new_session_ticket = (state.clientHello().supportsSessionTicket() && 222 state.clientHello().sessionTicket().empty && 223 have_session_ticket_key); 224 225 state.serverHello(new ServerHello(state.handshakeIo(), 226 state.hash(), 227 m_policy, 228 state.clientHello().sessionId().dup, 229 session_info.Version(), 230 session_info.ciphersuiteCode(), 231 session_info.compressionMethod(), 232 session_info.fragmentSize(), 233 state.clientHello().secureRenegotiation(), 234 state.clientHello().supportsExtendedMasterSecret(), 235 secureRenegotiationDataForServerHello(), 236 offer_new_session_ticket, 237 state.clientHello().supportsAlpn(), 238 m_application_protocol, 239 state.clientHello().supportsHeartbeats(), 240 rng())); 241 242 // Load ChannelID Original Handshake Hash 243 if (state.clientHello().supportsChannelID() && state.serverHello().supportsChannelID()) 244 { 245 TLSSession sess; 246 scope(exit) if (sess) sess.destroy(); 247 if (sessionManager() && sessionManager().loadFromSessionId(state.clientHello().sessionId(), sess)) 248 { 249 state.setOriginalHandshakeHash(sess.originalHandshakeHash().dup()); 250 } 251 } 252 253 secureRenegotiationCheck(state.serverHello()); 254 255 state.computeSessionKeys(session_info.masterSecret().dup); 256 257 if (!saveSession(session_info)) 258 { 259 auto entry = &session_info.sessionId(); 260 sessionManager().removeEntry(*entry); 261 262 if (state.serverHello().supportsSessionTicket()) // send an empty ticket 263 { 264 state.newSessionTicket(new NewSessionTicket(state.handshakeIo(), state.hash())); 265 } 266 } 267 268 if (state.serverHello().supportsSessionTicket() && !state.newSessionTicket()) 269 { 270 if (m_creds.hasPsk()) try 271 { 272 273 const SymmetricKey ticket_key = m_creds.psk("tls-server", "session-ticket", ""); 274 275 state.newSessionTicket(new NewSessionTicket(state.handshakeIo(), 276 state.hash(), 277 session_info.encrypt(ticket_key, rng()), 278 m_policy.sessionTicketLifetime())); 279 } 280 catch (Exception) {} 281 282 if (!state.newSessionTicket()) 283 { 284 state.newSessionTicket(new NewSessionTicket(state.handshakeIo(), state.hash())); 285 } 286 } 287 288 state.handshakeIo().send(scoped!ChangeCipherSpec()); 289 290 changeCipherSpecWriter(SERVER); 291 292 state.serverFinished(new Finished(state.handshakeIo(), state, SERVER)); 293 294 state.setExpectedNext(HANDSHAKE_CCS); 295 } 296 else // new session 297 { 298 HashMapRef!(string, Array!X509Certificate) cert_chains; 299 300 const string sni_hostname = state.clientHello().sniHostname(); 301 302 cert_chains = getServerCerts(sni_hostname, m_creds); 303 304 if (sni_hostname != "" && cert_chains.length == 0) 305 { 306 cert_chains = getServerCerts("", m_creds); 307 308 /* 309 * Only send the unrecognized_name alert if we couldn't 310 * find any certs for the requested name but did find at 311 * least one cert to use in general. That avoids sending an 312 * unrecognized_name when a server is configured for purely 313 * anonymous operation. 314 */ 315 if (cert_chains.length != 0) 316 sendAlert(TLSAlert(TLSAlert.UNRECOGNIZED_NAME)); 317 } 318 state.serverHello( 319 new ServerHello( state.handshakeIo(), 320 state.hash(), 321 m_policy, 322 makeHelloRandom(rng(), m_policy), // new session ID 323 state.Version(), 324 chooseCiphersuite(m_policy, state.Version(), m_creds, cert_chains, state.clientHello()), 325 chooseCompression(m_policy, state.clientHello().compressionMethods()), 326 state.clientHello().fragmentSize(), 327 state.clientHello().secureRenegotiation(), 328 state.clientHello().supportsExtendedMasterSecret(), 329 secureRenegotiationDataForServerHello(), 330 state.clientHello().supportsSessionTicket() && have_session_ticket_key, 331 state.clientHello().supportsAlpn(), 332 m_application_protocol, 333 state.clientHello().supportsHeartbeats(), 334 rng() 335 ) 336 ); 337 338 secureRenegotiationCheck(state.serverHello()); 339 340 const string sig_algo = state.ciphersuite().sigAlgo(); 341 const string kex_algo = state.ciphersuite().kexAlgo(); 342 343 if (sig_algo != "") 344 { 345 assert(!cert_chains[sig_algo].empty, 346 "Attempting to send empty certificate chain"); 347 348 state.serverCerts(new Certificate(state.handshakeIo(), state.hash(), cert_chains[sig_algo]) 349 ); 350 } 351 352 PrivateKey priv_key = null; 353 354 if (kex_algo == "RSA" || sig_algo != "") 355 { 356 priv_key = m_creds.privateKeyFor(state.serverCerts().certChain()[0], 357 "tls-server", 358 sni_hostname ); 359 360 if (!priv_key) 361 throw new InternalError("No private key located for associated server cert"); 362 } 363 364 if (kex_algo == "RSA") 365 { 366 state.server_rsa_kex_key = priv_key; 367 } 368 else 369 { 370 state.serverKex( 371 new ServerKeyExchange(state.handshakeIo(), 372 state, 373 m_policy, 374 m_creds, 375 rng(), 376 priv_key) 377 ); 378 } 379 380 auto trusted_CAs = m_creds.trustedCertificateAuthorities("tls-server", sni_hostname); 381 382 Vector!X509DN client_auth_CAs; 383 384 foreach (store; trusted_CAs[]) 385 { 386 auto subjects = store.allSubjects(); 387 client_auth_CAs ~= subjects[]; 388 } 389 390 if (!client_auth_CAs.empty && state.ciphersuite().sigAlgo() != "") 391 { 392 state.certReq(new CertificateReq(state.handshakeIo(), state.hash(), m_policy, 393 client_auth_CAs.move(), state.Version())); 394 395 state.setExpectedNext(CERTIFICATE); 396 } 397 398 /* 399 * If the client doesn't have a cert they want to use they are 400 * allowed to send either an empty cert message or proceed 401 * directly to the client key exchange, so allow either case. 402 */ 403 state.setExpectedNext(CLIENT_KEX); 404 405 state.serverHelloDone(new ServerHelloDone(state.handshakeIo(), state.hash())); 406 } 407 } 408 else if (type == CERTIFICATE) 409 { 410 state.clientCerts(new Certificate(contents)); 411 412 state.setExpectedNext(CLIENT_KEX); 413 } 414 else if (type == CLIENT_KEX) 415 { 416 if (state.receivedHandshakeMsg(CERTIFICATE) && !state.clientCerts().empty) 417 state.setExpectedNext(CERTIFICATE_VERIFY); 418 else 419 state.setExpectedNext(HANDSHAKE_CCS); 420 421 state.clientKex( 422 new ClientKeyExchange(contents, state, state.server_rsa_kex_key, m_creds, m_policy, rng()) 423 ); 424 425 state.computeSessionKeys(); 426 } 427 else if (type == CERTIFICATE_VERIFY) 428 { 429 state.clientVerify(new CertificateVerify(contents, state.Version())); 430 431 const(Vector!X509Certificate)* client_certs = &state.clientCerts().certChain(); 432 433 const bool sig_valid = state.clientVerify().verify((*client_certs)[0], state); 434 435 state.hash().update(state.handshakeIo().format(contents, type)); 436 437 /* 438 * Using DECRYPT_ERROR looks weird here, but per RFC 4346 is for 439 * "A handshake cryptographic operation failed, including being 440 * unable to correctly verify a signature, ..." 441 */ 442 if (!sig_valid) 443 throw new TLSException(TLSAlert.DECRYPT_ERROR, "TLSClient cert verify failed"); 444 445 try 446 { 447 m_creds.verifyCertificateChain("tls-server", "", *client_certs); 448 } 449 catch(Exception e) 450 { 451 throw new TLSException(TLSAlert.BAD_CERTIFICATE, e.msg); 452 } 453 454 state.setExpectedNext(HANDSHAKE_CCS); 455 } 456 else if (type == HANDSHAKE_CCS) 457 { 458 459 if (state.originalHandshakeHash().length == 0) 460 state.setExpectedNext(FINISHED); 461 else state.setExpectedNext(CHANNEL_ID); 462 463 changeCipherSpecReader(SERVER); 464 } 465 else if (type == CHANNEL_ID) { 466 // TODO: decode and verify 467 468 state.setExpectedNext(FINISHED); 469 } 470 else if (type == FINISHED) 471 { 472 state.setExpectedNext(HANDSHAKE_NONE); 473 474 state.clientFinished(new Finished(contents.dup)); 475 476 if (!state.clientFinished().verify(state, CLIENT)) 477 throw new TLSException(TLSAlert.DECRYPT_ERROR, "Finished message didn't verify"); 478 479 if (!state.serverFinished()) 480 { 481 482 // already sent finished if resuming, so this is a new session 483 484 state.hash().update(state.handshakeIo().format(contents, type)); 485 486 // Save Handshake Hash for ChannelID validation 487 if (state.clientHello().supportsChannelID() && state.serverHello().supportsChannelID()) 488 state.setOriginalHandshakeHash(state.hash().flushInto(state.Version(), state.ciphersuite().prfAlgo())); 489 490 auto session_info = new TLSSession(state.serverHello().sessionId().dup, 491 state.sessionKeys().masterSecret().dup, 492 state.originalHandshakeHash().dup, 493 state.serverHello().Version(), 494 state.serverHello().ciphersuite(), 495 state.serverHello().compressionMethod(), 496 SERVER, 497 state.serverHello().fragmentSize(), 498 state.serverHello().supportsExtendedMasterSecret(), 499 getPeerCertChain(state), 500 Vector!ubyte(), 501 TLSServerInformation(state.clientHello().sniHostname()), 502 state.srpIdentifier() 503 ); 504 505 if (saveSession(session_info)) 506 { 507 if (state.serverHello().supportsSessionTicket()) 508 { 509 try 510 { 511 const SymmetricKey ticket_key = m_creds.psk("tls-server", "session-ticket", ""); 512 513 state.newSessionTicket( 514 new NewSessionTicket(state.handshakeIo(), 515 state.hash(), 516 session_info.encrypt(ticket_key, rng()), 517 m_policy.sessionTicketLifetime()) 518 ); 519 } 520 catch (Exception) {} 521 } 522 else 523 sessionManager().save(session_info); 524 } 525 526 if (!state.newSessionTicket() && state.serverHello().supportsSessionTicket()) 527 { 528 state.newSessionTicket(new NewSessionTicket(state.handshakeIo(), state.hash())); 529 } 530 531 state.handshakeIo().send(scoped!ChangeCipherSpec()); 532 533 changeCipherSpecWriter(SERVER); 534 535 state.serverFinished(new Finished(state.handshakeIo(), state, SERVER)); 536 } 537 activateSession(); 538 } 539 else 540 throw new TLSUnexpectedMessage("Unknown handshake message received"); 541 } 542 543 override HandshakeState newHandshakeState(HandshakeIO io) 544 { 545 HandshakeState state = new ServerHandshakeState(io); 546 state.setExpectedNext(CLIENT_HELLO); 547 return state; 548 } 549 550 void switchContext(SNIContextSwitchInfo info) 551 { 552 m_creds = info.credentials; 553 m_session_manager = info.session_manager; 554 m_policy = info.policy; 555 m_choose_next_protocol = info.next_proto; 556 m_user_data = info.user_data; 557 } 558 559 private: 560 TLSPolicy m_policy; 561 TLSCredentialsManager m_creds; 562 563 NextProtocolHandler m_choose_next_protocol; 564 SNIHandler m_sni_handler; 565 void* m_user_data; 566 } 567 568 private: 569 570 bool checkForResume(ref TLSSession session_info, 571 TLSSessionManager session_manager, 572 TLSCredentialsManager credentials, 573 in ClientHello clientHello, 574 Duration session_ticket_lifetime) 575 { 576 const(Vector!ubyte)* client_session_id = &clientHello.sessionId(); 577 const Vector!ubyte session_ticket = clientHello.sessionTicket(); 578 579 if (session_ticket.empty) 580 { 581 if (client_session_id.empty) // not resuming 582 return false; 583 584 // not found 585 if (!session_manager.loadFromSessionId(*client_session_id, session_info)) 586 return false; 587 } 588 else 589 { 590 // If a session ticket was sent, ignore client session ID 591 try 592 { 593 session_info = TLSSession.decrypt(session_ticket, 594 credentials.psk("tls-server", "session-ticket", "")); 595 596 if (session_ticket_lifetime != Duration.init && 597 session_info.sessionAge() > session_ticket_lifetime) 598 return false; // ticket has expired 599 } 600 catch (Exception) 601 { 602 return false; 603 } 604 } 605 606 if (!session_info) 607 return false; 608 609 // wrong version 610 if (clientHello.Version() != session_info.Version()) 611 return false; 612 import std.algorithm : canFind; 613 // client didn't send original ciphersuite 614 if (!clientHello.ciphersuitesData().canFind(session_info.ciphersuiteCode())) 615 return false; 616 617 // client didn't send original compression method 618 if (!valueExists(clientHello.compressionMethods(), 619 session_info.compressionMethod())) 620 return false; 621 622 // client sent a different SRP identity 623 if (clientHello.srpIdentifier() != "") 624 { 625 if (clientHello.srpIdentifier() != session_info.srpIdentifier()) 626 return false; 627 } 628 629 // client sent a different SNI hostname 630 if (clientHello.sniHostname() != "") 631 { 632 if (clientHello.sniHostname() != session_info.serverInfo().hostname()) 633 return false; 634 } 635 636 if (clientHello.supportsExtendedMasterSecret() != session_info.supportsExtendedMasterSecret()) 637 { 638 if (!session_info.supportsExtendedMasterSecret()) 639 return false; 640 else { 641 throw new TLSException(TLSAlert.HANDSHAKE_FAILURE, "Client resumed extended ms session without sending extension"); 642 } 643 } 644 645 return true; 646 } 647 648 /* 649 * Choose which ciphersuite to use 650 */ 651 ushort chooseCiphersuite(in TLSPolicy policy, 652 TLSProtocolVersion _version, 653 TLSCredentialsManager creds, 654 in HashMapRef!(string, Array!X509Certificate) cert_chains, 655 in ClientHello client_hello) 656 { 657 import std.algorithm : canFind; 658 const bool our_choice = policy.serverUsesOwnCiphersuitePreferences(); 659 660 const bool have_srp = creds.attemptSrp("tls-server", client_hello.sniHostname()); 661 662 ushort[] client_suites = cast(ushort[])client_hello.ciphersuitesData(); 663 664 const Vector!ushort server_suites = policy.ciphersuiteList(_version, have_srp); 665 666 if (server_suites.empty) 667 throw new TLSException(TLSAlert.HANDSHAKE_FAILURE, "TLSPolicy forbids us from negotiating any ciphersuite"); 668 669 const bool have_shared_ecc_curve = (policy.chooseCurve(client_hello.supportedEccCurves()) != ""); 670 671 ushort[] pref_list = cast()server_suites[]; 672 673 if (!our_choice) 674 pref_list = client_suites; 675 debug { 676 string dbg_msg; 677 string dbg_sig; 678 } 679 foreach (suite_id; pref_list) 680 { 681 if (!client_suites.canFind(suite_id)) 682 continue; 683 684 TLSCiphersuite suite = TLSCiphersuite.byId(suite_id); 685 686 if (!have_shared_ecc_curve && suite.eccCiphersuite()) 687 continue; 688 689 if (suite.sigAlgo() != "" && cert_chains.get(suite.sigAlgo(), Array!X509Certificate(0)) == Array!X509Certificate(0)) 690 { 691 debug { 692 dbg_msg = "Server Certificate type did not match a type that was accepted from policy: "; 693 dbg_sig = suite.sigAlgo; 694 } 695 continue; 696 } 697 /* 698 The client may offer SRP cipher suites in the hello message but 699 omit the SRP extension. If the server would like to select an 700 SRP cipher suite in this case, the server SHOULD return a fatal 701 "unknown_psk_identity" alert immediately after processing the 702 client hello message. 703 - RFC 5054 section 2.5.1.2 704 */ 705 if (suite.kexAlgo() == "SRP_SHA" && client_hello.srpIdentifier() == "") 706 throw new TLSException(TLSAlert.UNKNOWN_PSK_IDENTITY, 707 "TLSClient wanted SRP but did not send username"); 708 709 return suite_id; 710 } 711 debug logDebug(dbg_msg, dbg_sig); 712 throw new TLSException(TLSAlert.HANDSHAKE_FAILURE, "Can't agree on a ciphersuite with client"); 713 } 714 715 /* 716 * Choose which compression algorithm to use 717 */ 718 ubyte chooseCompression(in TLSPolicy policy, const ref Vector!ubyte c_comp) 719 { 720 Vector!ubyte s_comp = policy.compression(); 721 722 for (size_t i = 0; i != s_comp.length; ++i) 723 for (size_t j = 0; j != c_comp.length; ++j) 724 if (s_comp[i] == c_comp[j]) 725 return s_comp[i]; 726 727 return NO_COMPRESSION; 728 } 729 730 HashMapRef!(string, Array!X509Certificate) 731 getServerCerts(in string hostname, TLSCredentialsManager creds) 732 { 733 string[] cert_types = [ "RSA", "DSA", "ECDSA", null ]; 734 735 HashMapRef!(string, Array!X509Certificate) cert_chains; 736 737 for (size_t i = 0; cert_types[i]; ++i) 738 { 739 Vector!X509Certificate certs = creds.certChainSingleType(cert_types[i], "tls-server", hostname); 740 741 if (!certs.empty) 742 cert_chains[cert_types[i]] = certs.dupr; 743 } 744 745 return cert_chains; 746 } 747 748 private final class ServerHandshakeState : HandshakeState 749 { 750 public: 751 this(HandshakeIO io) 752 { 753 super(io); 754 } 755 756 // Used by the server only, in case of RSA key exchange. Not owned 757 PrivateKey server_rsa_kex_key = null; 758 759 /* 760 * Used by the server to know if resumption should be allowed on 761 * a server-initiated renegotiation 762 */ 763 bool allow_session_resumption = true; 764 }