1 /******************************************************************************* 2 3 copyright: Copyright (c) 2008 Jeff Davey. All rights reserved 4 5 license: BSD style: $(LICENSE) 6 7 author: Jeff Davey <j@submersion.com> 8 9 *******************************************************************************/ 10 11 module tango.net.device.SSLSocket; 12 13 private import tango.net.util.PKI; 14 15 private import tango.net.device.Socket; 16 17 private import tango.net.device.Berkeley; 18 19 private import tango.net.util.c.OpenSSL; 20 21 /******************************************************************************* 22 23 SSLSocket is a sub-class of Socket. It's purpose is to 24 provide SSL encryption at the socket level as well as easily fit into 25 existing Tango network applications that may already be using Socket. 26 27 SSLSocket requires the OpenSSL library, and uses a dynamic binding 28 to the library. You can find the library at http://www.openssl.org and a 29 Win32 specific port at http://www.slproweb.com/products/Win32OpenSSL.html. 30 31 SSLSockets have two modes: 32 33 1. Client mode, useful for connecting to existing servers, but not 34 accepting new connections. Accepting a new connection will cause 35 the library to stall on a write on connection. 36 37 2. Server mode, useful for creating an SSL server, but not connecting 38 to an existing server. Connection will cause the library to stall on a 39 read on connection. 40 41 Example SSL client 42 --- 43 auto s = new SSLSocket; 44 if (s.connect("www.yahoo.com", 443)) 45 { 46 char[1024] buff; 47 48 s.write("GET / HTTP/1.0\r\n\r\n"); 49 auto bytesRead = s.read(buff); 50 if (bytesRead != s.Eof) 51 Stdout.formatln("received: {}", buff[0..bytesRead]); 52 } 53 --- 54 55 *******************************************************************************/ 56 57 class SSLSocket : Socket 58 { 59 protected BIO *sslSocket = null; 60 protected SSLCtx sslCtx = null; 61 /+ 62 private bool timeout; 63 private SocketSet readSet; 64 private SocketSet writeSet; 65 +/ 66 /******************************************************************************* 67 68 Create a default Client Mode SSLSocket. 69 70 *******************************************************************************/ 71 72 this (bool config = true) 73 { 74 super(); 75 76 if (config) 77 setCtx (new SSLCtx, true); 78 } 79 80 /+ 81 /******************************************************************************* 82 83 Creates a Client Mode SSLSocket 84 85 This is overriding the Socket ctor in order to emulate the 86 existing free-list frameowrk. 87 88 Specifying anything other than ProtocolType.TCP or SocketType.STREAM will 89 cause an Exception to be thrown. 90 91 *******************************************************************************/ 92 93 override this(SocketType type, ProtocolType protocol) 94 { 95 if (protocol != ProtocolType.TCP) 96 throw new Exception("SSL is only supported over TCP."); 97 if (type != SocketType.STREAM) 98 throw new Exception("SSL is only supporting with streaming types."); 99 super(AddressFamily.INET, type, protocol); // hardcoding this to INET for now 100 //if (create) 101 { 102 sslCtx = new SSLCtx(); 103 sslSocket = _convertToSSL(sslCtx, false, true); 104 } 105 } 106 107 /******************************************************************************* 108 109 Creates a SSLSocket 110 111 This class allows the ability to turn a regular Socket into an 112 SSLSocket. It also gives the ability to change an SSLSocket 113 into Server Mode or ClientMode. 114 115 Params: 116 sock = The socket to wrap in SSL 117 SSLCtx = the SSL Context as provided by the PKI layer. 118 clientMode = if true the socket will be Client Mode, Server otherwise. 119 120 *******************************************************************************/ 121 122 123 this(Socket sock, SSLCtx ctx, bool clientMode = true) 124 { 125 super(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP, false); // hardcoding to inet now 126 socket_ = sock; 127 sslCtx = ctx; 128 sslSocket = _convertToSSL(sslCtx, false, clientMode); 129 } 130 131 ~this() 132 { 133 if (sslSocket) 134 { 135 BIO_reset(sslSocket); 136 BIO_free_all(sslSocket); 137 sslSocket = null; 138 } 139 } 140 +/ 141 142 /******************************************************************************* 143 144 Release this SSLSocket. 145 146 As per Socket.detach. 147 148 *******************************************************************************/ 149 150 override void detach() 151 { 152 if (sslSocket) 153 { 154 BIO_reset(sslSocket); 155 BIO_free_all(sslSocket); 156 sslSocket = null; 157 } 158 super.detach(); 159 } 160 161 /******************************************************************************* 162 163 Writes the passed buffer to the underlying socket stream. This will 164 block until socket error. 165 166 As per Socket.write 167 168 *******************************************************************************/ 169 170 override size_t write(const(void)[] src) 171 { 172 if (src.length is 0) 173 return 0; 174 175 int bytes = BIO_write(sslSocket, cast(void*)src.ptr, cast(uint)src.length); 176 if (bytes <= 0) 177 return Eof; 178 return cast(size_t) bytes; 179 } 180 181 /******************************************************************************* 182 183 Reads from the underlying socket stream. If needed, setTimeout will 184 set the max length of time the read will take before returning. 185 186 As per Socket.read 187 188 *******************************************************************************/ 189 190 191 override size_t read(void[] dst) 192 { 193 /+ 194 timeout = false; 195 if (tv.tv_usec | tv.tv_sec) 196 { 197 size_t rtn = Eof; 198 // need to switch to nonblocking... 199 bool blocking = socket_.blocking; 200 if (blocking) socket_.blocking = false; 201 do 202 { 203 int bytesRead = BIO_read(sslSocket, dst.ptr, dst.length); 204 if (bytesRead <= 0) 205 { 206 bool read = false; 207 bool write = false; 208 if (!BIO_should_retry(sslSocket)) 209 break; 210 if (BIO_should_read(sslSocket)) 211 read = true; 212 if (BIO_should_write(sslSocket)) 213 write = true; 214 if (read || write) 215 { 216 if (read) 217 { 218 if (readSet is null) 219 readSet = new SocketSet(1); 220 readSet.reset(); 221 readSet.add(socket_); 222 } 223 if (write) 224 { 225 if (writeSet is null) 226 writeSet = new SocketSet(1); 227 writeSet.reset(); 228 writeSet.add(socket_); 229 } 230 auto copy = tv; 231 int i = socket_.select(read ? readSet : null, write ? writeSet : null, null, ©); 232 if (i <= 0) 233 { 234 if (i is 0) 235 timeout = true; 236 break; 237 } 238 } 239 else if (BIO_should_io_special(sslSocket)) // wasn't write, wasn't read.. something "special" just wait for the socket to become ready... 240 Thread.sleep(.05); 241 else 242 break; 243 } 244 else 245 { 246 rtn = bytesRead; 247 break; 248 } 249 } while(BIO_should_retry(sslSocket)); 250 if (blocking) socket_.blocking = blocking; 251 return rtn; 252 } 253 +/ 254 int bytes = BIO_read(sslSocket, dst.ptr, cast(uint)dst.length); 255 if (bytes <= 0) 256 return Eof; 257 return cast(size_t) bytes; 258 } 259 260 /******************************************************************************* 261 262 Shuts down the underlying socket for reading and writing. 263 264 As per Socket.shutdown 265 266 *******************************************************************************/ 267 268 override SSLSocket shutdown() 269 { 270 SSL *obj; 271 BIO_get_ssl(sslSocket, &obj); 272 if (obj) 273 { 274 if (!SSL_get_shutdown) 275 SSL_set_shutdown(obj, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); 276 } 277 return this; 278 } 279 280 /******************************************************************************* 281 282 Used in conjuction with the above ctor with the create flag disabled. It is 283 useful for accepting a new socket into a SSLSocket, and then re-using 284 the Server's existing SSLCtx. 285 286 Params: 287 ctx = SSLCtx class as provided by PKI 288 clientMode = if true, the socket will be in Client Mode, Server otherwise. 289 290 *******************************************************************************/ 291 292 293 void setCtx(SSLCtx ctx, bool clientMode = true) 294 { 295 sslCtx = ctx; 296 sslSocket = _convertToSSL(sslCtx, false, clientMode); 297 } 298 299 /* 300 Converts an existing socket (should be TCP) to an "SSL" socket 301 close = close the socket when finished -- should probably be false usually 302 client = if true, "client-mode" if false "server-mode" 303 */ 304 private BIO *_convertToSSL(SSLCtx sslCtx, bool close, bool client) 305 { 306 BIO *rtn = null; 307 308 BIO *socketBio = BIO_new_socket(native.handle, close ? BIO_CLOSE : BIO_NOCLOSE); 309 if (socketBio) 310 { 311 rtn = BIO_new_ssl(sslCtx.native, client); 312 if (rtn) 313 rtn = BIO_push(rtn, socketBio); 314 if (!rtn) 315 BIO_free_all(socketBio); 316 } 317 318 if (rtn is null) 319 throwOpenSSLError(); 320 return rtn; 321 } 322 } 323 324 325 /******************************************************************************* 326 327 SSLServerSocket is a sub-class of ServerSocket. It's purpose is to provide 328 SSL encryption at the socket level as well as easily tie into existing 329 Tango applications that may already be using ServerSocket. 330 331 SSLServerSocket requires the OpenSSL library, and uses a dynamic binding 332 to the library. You can find the library at http://www.openssl.org and a 333 Win32 specific port at http://www.slproweb.com/products/Win32OpenSSL.html. 334 335 Example SSL server 336 --- 337 auto cert = new Certificate(cast(char[])File.get("public.pem")); 338 auto pkey = new PrivateKey(cast(char[])File.get("private.pem")); 339 auto ctx = new SSLCtx; 340 ctx.certificate(cert).privateKey(pkey); 341 auto server = new SSLServerSocket(443, ctx); 342 for(;;) 343 { 344 auto sc = server.accept; 345 sc.write("HTTP/1.1 200\r\n\r\n<b>Hello World</b>"); 346 sc.shutdown.close; 347 } 348 --- 349 350 *******************************************************************************/ 351 352 class SSLServerSocket : ServerSocket 353 { 354 private SSLCtx sslCtx; 355 356 /******************************************************************************* 357 358 *******************************************************************************/ 359 360 this (ushort port, SSLCtx ctx, int backlog=32, bool reuse=false) 361 { 362 scope addr = new IPv4Address (port); 363 this (addr, ctx, backlog, reuse); 364 } 365 366 /******************************************************************************* 367 368 Constructs a new SSLServerSocket. This constructor is similar to 369 ServerSocket, except it takes a SSLCtx as provided by PKI. 370 371 Params: 372 addr = the address to bind and listen on. 373 ctx = the provided SSLCtx 374 backlog = the number of connections to backlog before refusing connection 375 reuse = if enabled, allow rebinding of existing ip/port 376 377 *******************************************************************************/ 378 379 this(Address addr, SSLCtx ctx, int backlog=32, bool reuse=false) 380 { 381 super(addr, backlog, reuse); 382 sslCtx = ctx; 383 } 384 385 alias ServerSocket.accept accept; 386 387 /******************************************************************************* 388 389 Accepts a new connection and copies the provided server SSLCtx to a new 390 SSLSocket. 391 392 *******************************************************************************/ 393 394 SSLSocket accept (SSLSocket recipient = null) 395 { 396 if (recipient is null) 397 recipient = new SSLSocket(false); 398 399 super.accept (recipient); 400 recipient.setCtx(sslCtx, false); 401 return recipient; 402 } 403 } 404 405 406 407 408 409 version(Test) 410 { 411 import tetra.util.Test; 412 import tango.io.Stdout; 413 import tango.io.device.File; 414 import tango.io.FilePath; 415 import tango.core.Thread; 416 import tango.stdc.stringz; 417 418 extern (C) 419 { 420 int blah(int booger, void *x) 421 { 422 return 1; 423 } 424 } 425 426 427 unittest 428 { 429 auto t2 = 1.0; 430 loadOpenSSL(); 431 Test.Status sslCTXTest(ref char[][] messages) 432 { 433 auto s1 = new SSLSocket(); 434 if (s1) 435 { 436 bool good = false; 437 try 438 auto s2 = new SSLSocket(SocketType.STREAM, ProtocolType.UDP); 439 catch (Exception e) 440 good = true; 441 442 if (good) 443 { 444 Socket mySock = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP); 445 if (mySock) 446 { 447 Certificate publicCertificate; 448 PrivateKey privateKey; 449 try 450 { 451 publicCertificate = new Certificate(cast(char[])File.get ("public.pem")); 452 privateKey = new PrivateKey(cast(char[])File.get ("private.pem")); 453 } 454 catch (Exception ex) 455 { 456 privateKey = new PrivateKey(2048); 457 publicCertificate = new Certificate(); 458 publicCertificate.privateKey(privateKey).serialNumber(123).dateBeforeOffset(t1).dateAfterOffset(t2); 459 publicCertificate.setSubject("CA", "Alberta", "Place", "None", "First Last", "no unit", "email@example.com").sign(publicCertificate, privateKey); 460 } 461 auto sslCtx = new SSLCtx(); 462 sslCtx.certificate(publicCertificate).privateKey(privateKey).checkKey(); 463 auto s3 = new SSLSocket(mySock, sslCtx); 464 if (s3) 465 return Test.Status.Success; 466 } 467 } 468 } 469 return Test.Status.Failure; 470 } 471 472 Test.Status sslReadWriteTest(ref char[][] messages) 473 { 474 auto s1 = new SSLSocket(); 475 auto address = new IPv4Address("209.20.65.224", 443); 476 if (s1.connect(address)) 477 { 478 char[] command = "GET /result.txt\r\n"; 479 s1.write(command); 480 char[1024] result; 481 uint bytesRead = s1.read(result); 482 if (bytesRead > 0 && bytesRead != Eof && (result[0 .. bytesRead] == "I got results!\n")) 483 return Test.Status.Success; 484 else 485 messages ~= Stdout.layout()("Received wrong results: (bytesRead: {}), (result: {})", bytesRead, result[0..bytesRead]); 486 } 487 return Test.Status.Failure; 488 } 489 490 Test.Status sslReadWriteTestWithTimeout(ref char[][] messages) 491 { 492 auto s1 = new SSLSocket(); 493 auto address = new IPv4Address("209.20.65.224", 443); 494 if (s1.connect(address)) 495 { 496 char[] command = "GET /result.txt HTTP/1.1\r\nHost: submersion.com\r\n\r\n"; 497 s1.write(command); 498 char[1024] result; 499 uint bytesRead = s1.read(result); 500 char[] expectedResult = "HTTP/1.1 200 OK"; 501 if (bytesRead > 0 && bytesRead != Eof && (result[0 .. expectedResult.length] == expectedResult)) 502 { 503 s1.setTimeout(t2); 504 while (bytesRead != s1.Eof) 505 bytesRead = s1.read(result); 506 if (s1.hadTimeout) 507 return Test.Status.Success; 508 else 509 messages ~= Stdout.layout()("Did not get timeout on read: {}", bytesRead); 510 } 511 else 512 messages ~= Stdout.layout()("Received wrong results: (bytesRead: {}), (result: {})", bytesRead, result[0..bytesRead]); 513 } 514 return Test.Status.Failure; 515 } 516 517 auto t = new Test("tetra.net.SSLSocket"); 518 t["SSL_CTX"] = &sslCTXTest; 519 t["Read/Write"] = &sslReadWriteTest; 520 t["Read/Write Timeout"] = &sslReadWriteTestWithTimeout; 521 t.run(); 522 } 523 }