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