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 }