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 }