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 }