1 /** 2 TLS stream implementation 3 4 TLSStream can be used to implement TLS communication on top of a TCP connection. The 5 TLSContextKind of an TLSStream determines if the TLS tunnel is established actively (client) or 6 passively (server). 7 8 Copyright: © 2012-2014 Sönke Ludwig 9 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 10 Authors: Sönke Ludwig 11 */ 12 module vibe.stream.tls; 13 14 import vibe.core.log; 15 import vibe.core.net; 16 import vibe.core.path : NativePath; 17 import vibe.core.stream; 18 import vibe.core.sync; 19 20 import vibe.utils.dictionarylist; 21 import vibe.internal.interfaceproxy; 22 23 import std.algorithm; 24 import std.array; 25 import std.conv; 26 import std.exception; 27 import std.string; 28 29 import core.stdc.string : strlen; 30 import core.sync.mutex; 31 import core.thread; 32 33 version (VibeNoSSL) {} 34 else version(Have_openssl) version = OpenSSL; 35 else version(Have_botan) version = Botan; 36 37 38 /// A simple TLS client 39 unittest { 40 import vibe.core.net; 41 import vibe.stream.tls; 42 43 void sendTLSMessage() 44 { 45 auto conn = connectTCP("127.0.0.1", 1234); 46 auto sslctx = createTLSContext(TLSContextKind.client); 47 auto stream = createTLSStream(conn, sslctx); 48 stream.write("Hello, World!"); 49 stream.finalize(); 50 conn.close(); 51 } 52 } 53 54 /// Corresponding server 55 unittest { 56 import vibe.core.log; 57 import vibe.core.net; 58 import vibe.stream.operations; 59 import vibe.stream.tls; 60 61 void listenForTLS() 62 { 63 auto sslctx = createTLSContext(TLSContextKind.server); 64 sslctx.useCertificateChainFile("server.crt"); 65 sslctx.usePrivateKeyFile("server.key"); 66 listenTCP(1234, delegate void(TCPConnection conn) nothrow { 67 try { 68 auto stream = createTLSStream(conn, sslctx); 69 logInfo("Got message: %s", stream.readAllUTF8()); 70 stream.finalize(); 71 } catch (Exception e) { 72 logInfo("Failed to receive encrypted message"); 73 } 74 }); 75 } 76 } 77 78 79 /**************************************************************************************************/ 80 /* Public functions */ 81 /**************************************************************************************************/ 82 @safe: 83 84 /** Creates a new context of the given kind. 85 86 Params: 87 kind = Specifies if the context is going to be used on the client 88 or on the server end of the TLS tunnel 89 ver = The TLS protocol used for negotiating the tunnel 90 */ 91 TLSContext createTLSContext(TLSContextKind kind, TLSVersion ver = TLSVersion.any) 92 @trusted { 93 version (OpenSSL) { 94 static TLSContext createOpenSSLContext(TLSContextKind kind, TLSVersion ver) @safe { 95 import vibe.stream.openssl; 96 return new OpenSSLContext(kind, ver); 97 } 98 if (!gs_sslContextFactory) 99 setTLSContextFactory(&createOpenSSLContext); 100 } else version(Botan) { 101 static TLSContext createBotanContext(TLSContextKind kind, TLSVersion ver) @safe { 102 import vibe.stream.botan; 103 return new BotanTLSContext(kind); 104 } 105 if (!gs_sslContextFactory) 106 setTLSContextFactory(&createBotanContext); 107 } 108 assert(gs_sslContextFactory !is null, "No TLS context factory registered. Compile in botan or openssl dependencies, or call setTLSContextFactory first."); 109 return gs_sslContextFactory(kind, ver); 110 } 111 112 /** Constructs a new TLS tunnel and infers the stream state from the TLSContextKind. 113 114 Depending on the TLSContextKind of ctx, the tunnel will try to establish an TLS 115 tunnel by either passively accepting or by actively connecting. 116 117 Params: 118 underlying = The base stream which is used for the TLS tunnel 119 ctx = TLS context used for initiating the tunnel 120 peer_name = DNS name of the remote peer, used for certificate validation 121 peer_address = IP address of the remote peer, used for certificate validation 122 */ 123 TLSStream createTLSStream(Stream)(Stream underlying, TLSContext ctx, string peer_name = null, NetworkAddress peer_address = NetworkAddress.init) 124 if (isStream!Stream) 125 { 126 auto stream_state = ctx.kind == TLSContextKind.client ? TLSStreamState.connecting : TLSStreamState.accepting; 127 return createTLSStream(underlying, ctx, stream_state, peer_name, peer_address); 128 } 129 130 /** Constructs a new TLS tunnel, allowing to override the stream state. 131 132 This constructor allows to specify a custom tunnel state, which can 133 be useful when a tunnel has already been established by other means. 134 135 Params: 136 underlying = The base stream which is used for the TLS tunnel 137 ctx = TLS context used for initiating the tunnel 138 state = The manually specified tunnel state 139 peer_name = DNS name of the remote peer, used for certificate validation 140 peer_address = IP address of the remote peer, used for certificate validation 141 */ 142 TLSStream createTLSStream(Stream)(Stream underlying, TLSContext ctx, TLSStreamState state, string peer_name = null, NetworkAddress peer_address = NetworkAddress.init) 143 if (isStream!Stream) 144 { 145 return ctx.createStream(interfaceProxy!(.Stream)(underlying), state, peer_name, peer_address); 146 } 147 148 /** 149 Constructs a new TLS stream using manual memory allocator. 150 */ 151 auto createTLSStreamFL(Stream)(Stream underlying, TLSContext ctx, TLSStreamState state, string peer_name = null, NetworkAddress peer_address = NetworkAddress.init) 152 if (isStream!Stream) 153 { 154 // This function has an auto return type to avoid the import of the TLS 155 // implementation headers. When client code uses this function the compiler 156 // will have to semantically analyse it and subsequently will import the TLS 157 // implementation headers. 158 version (OpenSSL) { 159 import vibe.internal.freelistref; 160 import vibe.stream.openssl; 161 static assert(AllocSize!TLSStream > 0); 162 return FreeListRef!OpenSSLStream(interfaceProxy!(.Stream)(underlying), cast(OpenSSLContext)ctx, 163 state, peer_name, peer_address); 164 } else version (Botan) { 165 import vibe.internal.freelistref; 166 import vibe.stream.botan; 167 return FreeListRef!BotanTLSStream(interfaceProxy!(.Stream)(underlying), cast(BotanTLSContext) ctx, state, peer_name, peer_address); 168 } else assert(false, "No TLS support compiled in (VibeNoTLS)"); 169 } 170 171 void setTLSContextFactory(TLSContext function(TLSContextKind, TLSVersion) @safe factory) 172 { 173 () @trusted { gs_sslContextFactory = factory; } (); 174 } 175 176 177 /**************************************************************************************************/ 178 /* Public types */ 179 /**************************************************************************************************/ 180 181 /** 182 Creates an TLS tunnel within an existing stream. 183 184 Note: Be sure to call finalize before finalizing/closing the outer stream so that the TLS 185 tunnel is properly closed first. 186 */ 187 interface TLSStream : Stream { 188 @safe: 189 190 @property TLSCertificateInformation peerCertificate(); 191 192 //-/ The host name reported through SNI 193 //@property string hostName() const; 194 195 /** The ALPN that has been negotiated for this connection. 196 197 See_also: $(WEB https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation) 198 */ 199 @property string alpn() const; 200 } 201 202 enum TLSStreamState { 203 connecting, 204 accepting, 205 connected 206 } 207 208 209 /** 210 Encapsulates the configuration for an TLS tunnel. 211 212 Note that when creating an TLSContext with TLSContextKind.client, the 213 peerValidationMode will be set to TLSPeerValidationMode.trustedCert, 214 but no trusted certificate authorities are added by default. Use 215 useTrustedCertificateFile to add those. 216 */ 217 interface TLSContext { 218 @safe: 219 220 /// The kind of TLS context (client/server) 221 @property TLSContextKind kind() const; 222 223 /** Specifies the validation level of remote peers. 224 225 The default mode for TLSContextKind.client is 226 TLSPeerValidationMode.trustedCert and the default for 227 TLSContextKind.server is TLSPeerValidationMode.none. 228 */ 229 @property void peerValidationMode(TLSPeerValidationMode mode); 230 /// ditto 231 @property TLSPeerValidationMode peerValidationMode() const; 232 233 /** The maximum length of an accepted certificate chain. 234 235 Any certificate chain longer than this will result in the TLS 236 negitiation failing. 237 238 The default value is 9. 239 */ 240 @property void maxCertChainLength(int val); 241 /// ditto 242 @property int maxCertChainLength() const; 243 244 /** An optional user callback for peer validation. 245 246 This callback will be called for each peer and each certificate of 247 its certificate chain to allow overriding the validation decision 248 based on the selected peerValidationMode (e.g. to allow invalid 249 certificates or to reject valid ones). This is mainly useful for 250 presenting the user with a dialog in case of untrusted or mismatching 251 certificates. 252 */ 253 @property void peerValidationCallback(TLSPeerValidationCallback callback); 254 /// ditto 255 @property inout(TLSPeerValidationCallback) peerValidationCallback() inout; 256 257 /** The callback used to associcate host names with TLS certificates/contexts. 258 259 This property is only used for kind $(D TLSContextKind.serverSNI). 260 */ 261 @property void sniCallback(TLSServerNameCallback callback); 262 /// ditto 263 @property inout(TLSServerNameCallback) sniCallback() inout; 264 265 /// Callback function invoked to choose alpn (client side) 266 @property void alpnCallback(TLSALPNCallback alpn_chooser); 267 /// ditto 268 @property TLSALPNCallback alpnCallback() const; 269 270 /// Setter method invoked to offer ALPN (server side) 271 void setClientALPN(string[] alpn); 272 273 /** Creates a new stream associated to this context. 274 */ 275 TLSStream createStream(InterfaceProxy!Stream underlying, TLSStreamState state, string peer_name = null, NetworkAddress peer_address = NetworkAddress.init); 276 277 /** Set the list of cipher specifications to use for TLS tunnels. 278 279 The list must be a colon separated list of cipher 280 specifications as accepted by OpenSSL. Calling this function 281 without argument will restore the default. 282 283 See_also: $(LINK https://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT) 284 */ 285 void setCipherList(string list = null); 286 287 /** Set params to use for DH cipher. 288 * 289 * By default the 2048-bit prime from RFC 3526 is used. 290 * 291 * Params: 292 * pem_file = Path to a PEM file containing the DH parameters. Calling 293 * this function without argument will restore the default. 294 */ 295 void setDHParams(string pem_file=null); 296 297 /** Set the elliptic curve to use for ECDH cipher. 298 * 299 * By default a curve is either chosen automatically or prime256v1 is used. 300 * 301 * Params: 302 * curve = The short name of the elliptic curve to use. Calling this 303 * function without argument will restore the default. 304 * 305 */ 306 void setECDHCurve(string curve=null); 307 308 /// Sets a certificate file to use for authenticating to the remote peer 309 void useCertificateChainFile(string path); 310 /// ditto 311 final void useCertificateChainFile(NativePath path) { useCertificateChainFile(path.toString()); } 312 313 /// Sets the private key to use for authenticating to the remote peer based 314 /// on the configured certificate chain file. 315 void usePrivateKeyFile(string path); 316 /// ditto 317 final void usePrivateKeyFile(NativePath path) { usePrivateKeyFile(path.toString()); } 318 319 /** Sets the list of trusted certificates for verifying peer certificates. 320 321 If this is a server context, this also entails that the given 322 certificates are advertised to connecting clients during handshake. 323 324 On Linux, the system's root certificate authority list is usually 325 found at "/etc/ssl/certs/ca-certificates.crt", 326 "/etc/pki/tls/certs/ca-bundle.crt", or "/etc/ssl/ca-bundle.pem". 327 */ 328 void useTrustedCertificateFile(string path); 329 } 330 331 enum TLSContextKind { 332 client, /// Client context (active connector) 333 server, /// Server context (passive connector) 334 serverSNI, /// Server context with multiple certificate support (SNI) 335 } 336 337 enum TLSVersion { 338 any, /// Accept SSLv3 or TLSv1.0 and greater 339 ssl3, /// Accept only SSLv3 340 tls1, /// Accept only TLSv1.0 341 tls1_1, /// Accept only TLSv1.1 342 tls1_2, /// Accept only TLSv1.2 343 dtls1, /// Use DTLSv1.0 344 345 ssl23 = any /// Deprecated compatibility alias 346 } 347 348 349 /** Specifies how rigorously TLS peer certificates are validated. 350 351 The individual options can be combined using a bitwise "or". Usually it is 352 recommended to use $(D trustedCert) for full validation. 353 */ 354 enum TLSPeerValidationMode { 355 /** Accept any peer regardless if and which certificate is presented. 356 357 This mode is generally discouraged and should only be used with 358 a custom validation callback set to do the verification. 359 */ 360 none = 0, 361 362 /** Require the peer to always present a certificate. 363 364 Note that this option alone does not verify the certificate at all. It 365 can be used together with the "check" options, or by using a custom 366 validation callback to actually validate certificates. 367 */ 368 requireCert = 1<<0, 369 370 /** Check the certificate for basic validity. 371 372 This verifies the validity of the certificate chain and some other 373 general properties, such as expiration time. It doesn't verify 374 either the peer name or the trust state of the certificate. 375 */ 376 checkCert = 1<<1, 377 378 /** Validate the actual peer name/address against the certificate. 379 380 Compares the name/address of the connected peer, as passed to 381 $(D createTLSStream) to the list of patterns present in the 382 certificate, if any. If no match is found, the connection is 383 rejected. 384 */ 385 checkPeer = 1<<2, 386 387 /** Requires that the certificate or any parent certificate is trusted. 388 389 Searches list of trusted certificates for a match of the certificate 390 chain. If no match is found, the connection is rejected. 391 392 See_also: $(D useTrustedCertificateFile) 393 */ 394 checkTrust = 1<<3, 395 396 /** Require a valid certificate matching the peer name. 397 398 In this mode, the certificate is validated for general consistency and 399 possible expiration, and the peer name is checked to see if the 400 certificate actually applies. 401 402 However, the certificate chain is not matched against the system's 403 pool of trusted certificate authorities, so a custom validation 404 callback is still needed to get a secure validation process. 405 406 This option is a combination $(D requireCert), $(D checkCert) and 407 $(D checkPeer). 408 */ 409 validCert = requireCert | checkCert | checkPeer, 410 411 /** Require a valid and trusted certificate (strongly recommended). 412 413 Checks the certificate and peer name for validity and requires that 414 the certificate chain originates from a trusted CA (based on the 415 registered pool of certificate authorities). 416 417 This option is a combination $(D validCert) and $(D checkTrust). 418 419 See_also: $(D useTrustedCertificateFile) 420 */ 421 trustedCert = validCert | checkTrust, 422 } 423 424 /** Certificate information */ 425 struct TLSCertificateInformation { 426 427 /** Information about the certificate's subject name. 428 429 Maps fields to their values. For example, typical fields on a 430 certificate will be 'commonName', 'countryName', 'emailAddress', etc. 431 */ 432 DictionaryList!(string, false, 8) subjectName; 433 434 /** Vendor specific representation of the peer certificate. 435 436 This field is only set if the functionality is supported and if the 437 peer certificate is a X509 certificate. 438 439 For the OpenSSL driver, this will point to an `X509` struct. Note 440 that the life time of the object is limited to the life time of the 441 TLS stream. 442 */ 443 void* _x509; 444 } 445 446 struct TLSPeerValidationData { 447 char[] certName; 448 string errorString; 449 // certificate chain 450 // public key 451 // public key fingerprint 452 } 453 454 alias TLSPeerValidationCallback = bool delegate(scope TLSPeerValidationData data); 455 456 alias TLSServerNameCallback = TLSContext delegate(string hostname); 457 alias TLSALPNCallback = string delegate(string[] alpn_choices); 458 459 private { 460 __gshared TLSContext function(TLSContextKind, TLSVersion) gs_sslContextFactory; 461 }