1 /**
2  * Botan-powered SSL.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.net.ssl.botan;
15 
16 import botan.math.bigint.bigint;
17 import botan.rng.auto_rng;
18 import botan.tls.client;
19 import botan.tls.server;
20 
21 import ae.net.asockets;
22 import ae.net.ssl;
23 
24 debug = BotanSSL;
25 debug(BotanSSL) import std.stdio : stderr;
26 
27 /// Botan implementation of SSLProvider.
28 class BotanSSLProvider : SSLProvider
29 {
30 	override SSLContext createContext(SSLContext.Kind kind)
31 	{
32 		return new BotanSSLContext(kind);
33 	}
34 
35 	override SSLAdapter createAdapter(SSLContext context, IConnection next)
36 	{
37 		auto ctx = cast(BotanSSLContext)context;
38 		assert(ctx, "Not a BotanSSLContext");
39 		return new BotanSSLAdapter(ctx, next);
40 	}
41 }
42 
43 /// Implementation of TLSCredentialsManager with the default behavior.
44 class DefaultTLSCredentialsManager : TLSCredentialsManager
45 {
46 	override Vector!CertificateStore trustedCertificateAuthorities(in string type, in string context)
47 	{
48 		return super.trustedCertificateAuthorities(type, context);
49 	}
50 
51 	override void verifyCertificateChain(in string type, in string purported_hostname, const ref Vector!X509Certificate cert_chain)
52 	{
53 		return super.verifyCertificateChain(type, purported_hostname, cert_chain);
54 	}
55 
56 	override Vector!X509Certificate certChain(const ref Vector!string cert_key_types, in string type, in string context)
57 	{
58 		return super.certChain(cert_key_types, type, context);
59 	}
60 
61 	override Vector!X509Certificate certChainSingleType(in string cert_key_type, in string type, in string context)
62 	{
63 		return super.certChainSingleType(cert_key_type, type, context);
64 	}
65 
66 	override PrivateKey privateKeyFor(in X509Certificate cert, in string type, in string context)
67 	{
68 		return super.privateKeyFor(cert, type, context);
69 	}
70 
71 	override bool attemptSrp(in string type, in string context)
72 	{
73 		return super.attemptSrp(type, context);
74 	}
75 
76 	override string srpIdentifier(in string type, in string context)
77 	{
78 		return super.srpIdentifier(type, context);
79 	}
80 
81 	override string srpPassword(in string type,
82 								 in string context,
83 								 in string identifier)
84 	{
85 		return super.srpPassword(type, context, identifier);
86 	}
87 
88 	override bool srpVerifier(in string type,
89 							  in string context,
90 							  in string identifier,
91 							  ref string group_name,
92 							  ref BigInt verifier,
93 							  ref Vector!ubyte salt,
94 							  bool generate_fake_on_unknown)
95 	{
96 		return super.srpVerifier(type, context, identifier, group_name, verifier, salt, generate_fake_on_unknown);
97 	}
98 
99 	override string pskIdentityHint(in string type, in string context)
100 	{
101 		return super.pskIdentityHint(type, context);
102 	}
103 
104 	override string pskIdentity(in string type, in string context, in string identity_hint)
105 	{
106 		return super.pskIdentity(type, context, identity_hint);
107 	}
108 
109 	override bool hasPsk()
110 	{
111 		return super.hasPsk();
112 	}
113 
114 	override SymmetricKey psk(in string type, in string context, in string identity)
115 	{
116 		return super.psk(type, context, identity);
117 	}
118 }
119 
120 class AETLSCredentialsManager : DefaultTLSCredentialsManager
121 {
122 	/// E.g.: `() => readText(".../ca-bundle.crt")`
123 	static string delegate() trustedRootCABundleProvider = null;
124 	static CertificateStore trustedrootCABundleStore;
125 
126 	SSLContext.Verify verify;
127 
128 	override Vector!CertificateStore trustedCertificateAuthorities(in string type, in string context)
129 	{
130 		if (!trustedrootCABundleStore)
131 		{
132 			auto memStore = new CertificateStoreInMemory();
133 			import std.string : split;
134 			auto bundleText = trustedRootCABundleProvider();
135 			foreach (certText; bundleText.split("\n=")[1..$])
136 				memStore.addCertificate(X509Certificate(cast(DataSource) DataSourceMemory(certText)));
137 			trustedrootCABundleStore = memStore;
138 		}
139 
140 		return Vector!CertificateStore(trustedrootCABundleStore);
141 	}
142 
143 	override void verifyCertificateChain(in string type, in string purported_hostname, const ref Vector!X509Certificate cert_chain)
144 	{
145 		if (verify == SSLContext.Verify.none)
146 			return;
147 		if (verify == SSLContext.Verify.verify && cert_chain.empty)
148 			return;
149 		super.verifyCertificateChain(type, purported_hostname, cert_chain);
150 	}
151 }
152 
153 class AETLSPolicy : TLSPolicy
154 {
155 }
156 
157 class BotanSSLContext : SSLContext
158 {
159 	Kind kind;
160 	Verify verify;
161 
162 	TLSSessionManager sessions;
163 	RandomNumberGenerator rng;
164 	TLSPolicy policy;
165 
166 	this(Kind kind)
167 	{
168 		this.kind = kind;
169 		this.rng = new AutoSeededRNG;
170 		this.sessions = new TLSSessionManagerInMemory(rng);
171 		this.policy = new AETLSPolicy();
172 	}
173 
174 	override void setCipherList(string[] ciphers)
175 	{
176 		assert(false, "TODO");
177 	}
178 
179 	override void enableDH(int bits)
180 	{
181 		assert(false, "TODO");
182 	}
183 
184 	override void enableECDH()
185 	{
186 		assert(false, "TODO");
187 	}
188 
189 	override void setCertificate(string path)
190 	{
191 		assert(false, "TODO");
192 	}
193 
194 	override void setPrivateKey(string path)
195 	{
196 		assert(false, "TODO");
197 	}
198 
199 	override void setPeerVerify(Verify verify)
200 	{
201 		this.verify = verify;
202 	}
203 
204 	override void setPeerRootCertificate(string path)
205 	{
206 		assert(false, "TODO");
207 	}
208 
209 	override void setFlags(int flags)
210 	{
211 		assert(false, "TODO");
212 	}
213 }
214 
215 static this()
216 {
217 	ssl = new BotanSSLProvider();
218 }
219 
220 static this()
221 {
222 	// Needed for OCSP validation
223 	import botan.utils.http_util.http_util : tcp_message_handler;
224 	tcp_message_handler =
225 		(in string hostname, string message)
226 		{
227 			import std.socket : TcpSocket, InternetAddress;
228 			auto s = new TcpSocket(new InternetAddress(hostname, 80));
229 			import std.array;
230 			// stderr.writeln("OCSP send:", message);
231 			// import std.file; std.file.write("ocsp-req", message);
232 			while (message.length)
233 			{
234 				auto sent = s.send(message);
235 				message = message[sent .. $];
236 			}
237 			string reply;
238 			char[4096] buf;
239 			while (true)
240 			{
241 				auto received = s.receive(buf);
242 				if (received > 0)
243 					reply ~= buf[0 .. received];
244 				else
245 					break;
246 			}
247 			// stderr.writeln("OCSP:", [reply]);
248 			s.close();
249 			return reply;
250 		};
251 }
252 
253 // ***************************************************************************
254 
255 class BotanSSLAdapter : SSLAdapter
256 {
257 	BotanSSLContext context;
258 	TLSChannel channel;
259 	AETLSCredentialsManager creds;
260 	TLSServerInformation serverInfo;
261 
262 	this(BotanSSLContext context, IConnection next)
263 	{
264 		this.context = context;
265 		this.creds = new AETLSCredentialsManager();
266 		this.creds.verify = context.verify;
267 		super(next);
268 
269 		if (next.state == ConnectionState.connected)
270 			initialize();
271 	}
272 
273 	override void onConnect()
274 	{
275 		initialize();
276 	}
277 
278 	private final void initialize()
279 	{
280 		final switch (context.kind)
281 		{
282 			case SSLContext.Kind.client:
283 				channel = new TLSClient(
284 					&botanSocketOutput,
285 					&botanClientData,
286 					&botanAlert,
287 					&botanHandshake,
288 					context.sessions,
289 					creds,
290 					context.policy,
291 					context.rng,
292 					serverInfo,
293 				);
294 				break;
295 			case SSLContext.Kind.server:
296 				assert(false, "TODO");
297 				// break;
298 		}
299 	}
300 
301 	override void onReadData(Data data)
302 	{
303 		bool wasActive = channel.isActive();
304 		channel.receivedData(cast(ubyte*)data.ptr, data.length);
305 		if (!wasActive && channel.isActive())
306 			super.onConnect();
307 	}
308 
309 	override void send(Data[] data, int priority)
310 	{
311 		foreach (datum; data)
312 			channel.send(cast(ubyte*)datum.ptr, datum.length);
313 	}
314 
315 	override void setHostName(string hostname, ushort port = 0, string service = null)
316 	{
317 		serverInfo = TLSServerInformation(hostname, service, port);
318 	}
319 
320 	override SSLCertificate getHostCertificate()
321 	{
322 		assert(false, "TODO");
323 	}
324 
325 	override SSLCertificate getPeerCertificate()
326 	{
327 		assert(false, "TODO");
328 	}
329 
330 	void botanSocketOutput(in ubyte[] data)
331 	{
332 		next.send(Data(data, true));
333 	}
334 
335 	void botanClientData(in ubyte[] data)
336 	{
337 		super.onReadData(Data(data));
338 	}
339 
340 	void botanAlert(in TLSAlert alert, in ubyte[] data)
341 	{
342 		if (alert.isFatal)
343 			super.disconnect("Fatal TLS alert: " ~ alert.typeString, DisconnectType.error);
344 	}
345 
346 	bool botanHandshake(in TLSSession session)
347 	{
348 		debug(BotanSSL) stderr.writeln("Handshake done!");
349 		return true;
350 	}
351 }
352 
353 class BotanSSLCertificate : SSLCertificate
354 {
355 }
356 
357 // ***************************************************************************
358 
359 unittest
360 {
361 	import std.file : readText;
362 	AETLSCredentialsManager.trustedRootCABundleProvider = () => readText("/home/vladimir/Downloads/ca-bundle.crt");
363 
364 	void testServer(string host, ushort port)
365 	{
366 		auto c = new TcpConnection;
367 		auto ctx = ssl.createContext(SSLContext.Kind.client);
368 		auto s = ssl.createAdapter(ctx, c);
369 		Data allData;
370 
371 		s.handleConnect =
372 		{
373 			debug(BotanSSL) stderr.writeln("Connected!");
374 			s.send(Data("GET /d/nettest/testUrl1 HTTP/1.0\r\nHost: thecybershadow.net\r\n\r\n"));
375 		};
376 		s.handleReadData = (Data data)
377 		{
378 			debug(BotanSSL) { stderr.write(cast(string)data.contents); stderr.flush(); }
379 			allData ~= data;
380 		};
381 		s.handleDisconnect = (string reason, DisconnectType type)
382 		{
383 			debug(BotanSSL) { stderr.writeln(reason); }
384 			assert(type == DisconnectType.graceful);
385 			import std.algorithm.searching : endsWith;
386 			assert((cast(string)allData.contents).endsWith("Hello world\n"));
387 		};
388 		s.setHostName("thecybershadow.net");
389 		c.connect(host, port);
390 		socketManager.loop();
391 	}
392 
393 	testServer("thecybershadow.net", 443);
394 }
395 
396 // version (unittest) import ae.net.ssl.test;
397 // unittest { testSSL(new BotanSSLProvider); }