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 }