1 /** 2 * 3 */ 4 module vibe.noise; 5 6 import vibe.core.stream, vibe.core.file; 7 import std.array : empty; 8 import std.exception : enforceEx; 9 import std.algorithm : min; 10 import deimos.sodium; 11 import noise; 12 13 /** 14 * Exception thrown on internal errors. 15 */ 16 class NoiseException : Exception 17 { 18 public: 19 @trusted pure nothrow this(string message, int error, string file = __FILE__, 20 size_t line = __LINE__, Throwable next = null) 21 { 22 import core.stdc..string : strlen; 23 24 char[128] msgBuf; 25 noise_strerror(error, msgBuf.ptr, msgBuf.length); 26 char[] errMsg = msgBuf[0 .. strlen(msgBuf.ptr)]; 27 super(message ~ errMsg.idup, file, line, next); 28 } 29 30 @safe pure nothrow this(string message, string file = __FILE__, 31 size_t line = __LINE__, Throwable next = null) 32 { 33 super(message, file, line, next); 34 } 35 } 36 37 /** 38 * Exception thrown when authentication in createNoiseStream failed. 39 */ 40 class AuthException : Exception 41 { 42 public: 43 @safe pure nothrow this(string message, string file = __FILE__, 44 size_t line = __LINE__, Throwable next = null) 45 { 46 super(message, file, line, next); 47 } 48 } 49 50 /// Key size of public and private keys. 51 enum KeySize = 32; 52 53 /** 54 * Create a public/private keypair to be used with createNoiseStream. 55 * 56 * Warning: When using the overload taking two `ubyte[]` buffers, 57 * the privKey buffer needs to be a secure buffer. Avoid copying this 58 * data around and make sure to (properly!) zero the data if you no longer need it. 59 * Also guarding the key memory page as done by libsodium is recommended. 60 * 61 * The overloads not taking a privateKey `ubyte[]` buffer handle all these details. 62 */ 63 void createKeys(string privKeyFile, string pubKeyFile) 64 { 65 createKeys(Path(privKeyFile), Path(pubKeyFile)); 66 } 67 68 /// ditto 69 void createKeys(Path privKeyFile, Path pubKeyFile) 70 { 71 // 1*keys, then 2*keySize+1 for hex encoding 72 enum BufLength = KeySize + KeySize * 2 + 1; 73 auto keyPtr = sodium_malloc(BufLength); 74 noiseEnforce(keyPtr !is null, "Failed to allocate memory"); 75 scope (exit) 76 sodium_free(keyPtr); 77 78 auto pubKey = cast(ubyte[]) keyPtr[0 .. KeySize]; 79 auto pubKeyHex = cast(char[]) keyPtr[KeySize .. BufLength]; 80 81 createKeys(privKeyFile, pubKey); 82 pubKeyHex = keyToHex(pubKey, pubKeyHex); 83 84 writeFileUTF8(pubKeyFile, cast(string) pubKeyHex); 85 } 86 87 /// ditto 88 void createKeys(string privKeyFile, ubyte[] pubKey) 89 { 90 createKeys(Path(privKeyFile), pubKey); 91 } 92 93 /// ditto 94 void createKeys(Path privKeyFile, ubyte[] pubKey) 95 { 96 auto keyPtr = sodium_malloc(KeySize); 97 noiseEnforce(keyPtr !is null, "Failed to allocate memory"); 98 scope (exit) 99 sodium_free(keyPtr); 100 101 auto privKey = cast(ubyte[]) keyPtr[0 .. KeySize]; 102 createKeys(privKey, pubKey); 103 writeFile(privKeyFile, privKey); 104 } 105 106 /// ditto 107 void createKeys(ubyte[] privKey, ubyte[] pubKey) 108 { 109 noiseEnforce(sodiumResult != -1, "Failed to initialize libsodium"); 110 noiseEnforce(privKey.length == KeySize && pubKey.length == KeySize, 111 "Buffers for keys must be 32 byte long"); 112 113 NoiseDHState* dh; 114 // Generate a keypair 115 noiseCheck(noise_dhstate_new_by_id(&dh, NOISE_DH_CURVE25519)); 116 scope (exit) 117 noise_dhstate_free(dh); 118 119 noiseCheck(noise_dhstate_generate_keypair(dh)); 120 noiseCheck(noise_dhstate_get_keypair(dh, privKey.ptr, privKey.length, 121 pubKey.ptr, pubKey.length)); 122 } 123 124 // Test invalid buffer lengths 125 unittest 126 { 127 ubyte[31] sBuf; 128 ubyte[32] buf; 129 ubyte[33] lBuf; 130 131 assertThrown(createKeys(buf[], sBuf[])); 132 assertThrown(createKeys(buf[], lBuf[])); 133 assertThrown(createKeys(sBuf[], buf[])); 134 assertThrown(createKeys(lBuf[], buf[])); 135 } 136 137 /** 138 * Convert key from binary to hex format. 139 * 140 * Note: The KeyHex buffer needs to be 2*KeySize+1 bytes long as 141 * the function will internally write a `\0` at the end of the buffer. 142 * The return value returns a slice with adjusted length to exclude this trailing 143 * `\0`. 144 */ 145 char[] keyToHex(ubyte[] keyBin, char[] keyHex) 146 { 147 noiseEnforce(keyBin.length == KeySize && keyHex.length == 2 * KeySize + 1, 148 "Invalid input buffer size"); 149 noiseEnforce(sodium_bin2hex(keyHex.ptr, keyHex.length, keyBin.ptr, keyBin.length) !is null); 150 return keyHex[0 .. $ - 1]; 151 } 152 153 // Test invalid buffer lengths 154 unittest 155 { 156 ubyte[31] sBuf; 157 ubyte[32] buf; 158 char[64 + 1] hBuf; 159 160 assertThrown(keyToHex(buf[], cast(char[]) sBuf[])); 161 assertThrown(keyToHex(sBuf[], hBuf[])); 162 } 163 164 /** 165 * Convert key from hex to binary format. 166 * 167 * Note: keyHex.length needs to be 2*KeySize. 168 */ 169 void keyFromHex(char[] keyHex, ubyte[] keyBin) 170 { 171 noiseEnforce(keyBin.length == KeySize && keyHex.length == 2 * KeySize, 172 "Invalid input buffer size"); 173 174 size_t readLength = 0; 175 auto result = sodium_hex2bin(keyBin.ptr, keyBin.length, keyHex.ptr, 176 keyHex.length, null, &readLength, null); 177 178 noiseEnforce(result == 0 && readLength == 32, "Invalid public key input"); 179 } 180 181 /** 182 * Reads a public key file writen by createKeys. 183 */ 184 void readPublicKey(string file, ubyte[] data) 185 { 186 readPublicKey(Path(file), data); 187 } 188 189 /// ditto 190 void readPublicKey(Path file, ubyte[] data) 191 { 192 noiseEnforce(data.length == 32, "Key buffer needs to be 32 byte long"); 193 194 // 3 bytes BOM, 2 * keySize for hex encoding, allow up to 4 trailing bytes 195 // (CRLF CRLF) 196 ubyte[3 + 2 * 32 + 4] hexBuf; 197 scope (exit) 198 sodium_memzero(hexBuf.ptr, hexBuf.length); 199 200 ubyte[] hexRead; 201 // If file is too large, report error 202 try 203 hexRead = file.readFile(hexBuf[], hexBuf.length); 204 catch (Exception e) 205 noiseEnforce(false, "Invalid public key file"); 206 207 // Remove BOM 208 if (hexRead.length >= 3 && hexRead[0 .. 3] == [0xEF, 0xBB, 0xBF]) 209 { 210 hexRead = hexRead[3 .. $]; 211 } 212 213 // Need at least 64 bytes 214 noiseEnforce(hexRead.length >= 64, "Invalid public key file"); 215 216 // Convert to binary 217 keyFromHex(cast(char[]) hexRead[0 .. 2 * KeySize], data); 218 } 219 220 // Test whether readPublicKey reads data correctly 221 unittest 222 { 223 import std.file; 224 225 string privFile = "private.key"; 226 string pubFile = "public.key"; 227 scope (exit) 228 { 229 if (privFile.exists) 230 remove(privFile); 231 if (pubFile.exists) 232 remove(pubFile); 233 } 234 235 // Create & write key 236 ubyte[KeySize] pubKey; 237 char[2 * KeySize + 1] pubKeyHex; 238 createKeys(privFile, pubKey[]); 239 writeFileUTF8(Path(pubFile), cast(string) keyToHex(pubKey, pubKeyHex[])); 240 241 // Read & verify key 242 ubyte[32] readKey; 243 readPublicKey(pubFile, readKey[]); 244 assert(readKey[] == pubKey); 245 246 // Trailing new line 247 writeFileUTF8(Path(pubFile), (cast(string) keyToHex(pubKey, pubKeyHex[])) ~ "\r\n"); 248 readPublicKey(pubFile, readKey[]); 249 assert(readKey[] == pubKey); 250 251 // No BOM 252 writeFile(Path(pubFile), cast(ubyte[]) keyToHex(pubKey, pubKeyHex[])); 253 readPublicKey(pubFile, readKey[]); 254 assert(readKey[] == pubKey); 255 256 // No BOM + trailing newline 257 writeFile(Path(pubFile), cast(ubyte[])(keyToHex(pubKey, pubKeyHex[]) ~ ['\r', '\n'])); 258 readPublicKey(pubFile, readKey[]); 259 assert(readKey[] == pubKey); 260 } 261 262 // Test whether readPublicKey reads file generated by createKeys 263 unittest 264 { 265 import std.file; 266 267 string privFile = "private.key"; 268 string pubFile = "public.key"; 269 scope (exit) 270 { 271 if (privFile.exists) 272 remove(privFile); 273 if (pubFile.exists) 274 remove(pubFile); 275 } 276 277 // Create & write key 278 ubyte[32] readKey; 279 createKeys(privFile, pubFile); 280 readPublicKey(pubFile, readKey[]); 281 282 // Test short buffer 283 assertThrown(readPublicKey(pubFile, readKey[0 .. 31])); 284 } 285 286 // Read invalid files 287 unittest 288 { 289 import std.file; 290 291 string pubFile = "public.key"; 292 scope (exit) 293 { 294 if (pubFile.exists) 295 remove(pubFile); 296 } 297 298 ubyte[KeySize] key; 299 // too short 300 writeFileUTF8(Path(pubFile), "aabb00"); 301 assertThrown(readPublicKey(pubFile, key[])); 302 303 // too long 304 writeFileUTF8(Path(pubFile), 305 "aabb00aabb00aabb00aaaabb00aabb00aabb00aaaabb00" ~ "aabb00aabb00aaaabb00aabb00aabb00aaaabb00aabb00aabb00aaaabb00aabb0" ~ "0aabb00aaaabb00aabb00aabb00aaaabb00aabb00aabb00aa"); 306 assertThrown(readPublicKey(pubFile, key[])); 307 308 // non-existent file 309 remove(pubFile); 310 assertThrown(readPublicKey(pubFile, key[])); 311 312 // invalid data 313 writeFileUTF8(Path(pubFile), 314 "xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ~ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 315 assertThrown(readPublicKey(pubFile, key[])); 316 } 317 318 /** 319 * Kind of NoiseStream. 320 */ 321 enum NoiseKind 322 { 323 /// 324 server, 325 /// 326 client 327 } 328 329 /** 330 * Provide a delegate of this types to verify the public key of the node 331 * connected to. 332 */ 333 alias VerifyKeyDelegate = scope nothrow bool delegate(scope const(ubyte[])); 334 335 /** 336 * Settings for the connectNoiseStream function. 337 */ 338 struct NoiseSettings 339 { 340 /// Client or server role. 341 NoiseKind kind; 342 /** 343 * Path to private key file. Either this or the privateKey field 344 * must be set. If both are set the privateKeyPath field is used. 345 */ 346 Path privateKeyPath; 347 348 /** 349 * Private key in memory. 350 * 351 * Warning: When using this overload, make sure to use secure memory. 352 * The memory should be cleared after the createNoiseStream call returns. 353 * 354 * The privateKeyPath allows for a simpler, secure API. 355 */ 356 const(ubyte)[] privateKey; 357 358 /** 359 * If provided will be used to verify the remote public key. 360 * If this is not set remoteKeyPath is used instead. 361 */ 362 VerifyKeyDelegate verifyRemoteKey; 363 364 /** 365 * Path to public key file of remote server 366 */ 367 Path remoteKeyPath; 368 369 /** 370 * Convenience constructor. 371 */ 372 public this(NoiseKind kind) 373 { 374 this.kind = kind; 375 } 376 } 377 378 /** 379 * Create a noise encrypted stream on top of a normal stream. 380 * 381 * Throws: NoiseException on internal error, AuthException if authentication failed. 382 * Exceptions thrown from the low-level stream get passed through. 383 */ 384 NoiseStream createNoiseStream(Stream stream, scope NoiseSettings settings) 385 { 386 auto cstream = new NoiseStream(stream); 387 cstream.handshake(settings); 388 return cstream; 389 } 390 391 /** 392 * Wraps a normal Stream to add encryption based on the 393 * Noise_XX_25519_ChaChaPoly_BLAKE2b protocol. 394 */ 395 class NoiseStream : Stream 396 { 397 private: 398 enum NoiseProtocolID = "Noise_XX_25519_ChaChaPoly_BLAKE2b"; 399 NoiseCipherState* _writeCipher, _readCipher; 400 Stream _stream; 401 bool _finalized; 402 // Read buffer contains encrypted or decrypted data 403 ubyte[2048] _readBuf; 404 // Slice into _readBuf with remaining decrypted data 405 ubyte[] _readDecrypted; 406 // Buffer used for writing only 407 ubyte[2048] _writeBuf; 408 size_t _writeCached; 409 410 this(Stream stream) pure nothrow @safe @nogc 411 { 412 this._stream = stream; 413 } 414 415 void encryptAndSend() 416 { 417 NoiseBuffer mbuf; 418 if (_writeCached != 0) 419 { 420 noise_buffer_set_inout(mbuf, &_writeBuf[2], _writeCached, _writeBuf.length - 2); 421 noiseCheck(noise_cipherstate_encrypt(_writeCipher, &mbuf)); 422 _writeBuf[0] = cast(ubyte)(mbuf.size >> 8); 423 _writeBuf[1] = cast(ubyte) mbuf.size; 424 _stream.write(_writeBuf[0 .. mbuf.size + 2]); 425 _writeCached = 0; 426 } 427 } 428 429 size_t readPacket() 430 { 431 ubyte[2] message_size; 432 _stream.read(message_size[]); 433 ushort len = (message_size[0] << 8) | message_size[1]; 434 noiseEnforce(len < _readBuf.length, "Ivalid packet length"); 435 _stream.read(_readBuf[0 .. len]); 436 return len; 437 } 438 439 void readAndDecrypt() 440 { 441 NoiseBuffer mbuf; 442 while (_readDecrypted.empty) 443 { 444 auto len = readPacket(); 445 noise_buffer_set_input(mbuf, _readBuf.ptr, len); 446 noiseCheck(noise_cipherstate_decrypt(_readCipher, &mbuf)); 447 _readDecrypted = _readBuf[0 .. mbuf.size]; 448 } 449 } 450 451 void handshake(scope NoiseSettings settings) 452 { 453 NoiseHandshakeState* handshake; 454 455 // Check library initialization 456 noiseCheck(initResult, "Failed to initialize libnoise-c"); 457 noiseEnforce(sodiumResult != -1, "Failed to initialize libsodium"); 458 // Check settings 459 noiseEnforce(!settings.privateKeyPath.empty 460 || !settings.privateKey.empty, "Need either privateKeyPath or privateKey"); 461 noiseEnforce(settings.verifyRemoteKey !is null 462 || !settings.remoteKeyPath.empty, "Need either remoteKeyPath or verifyRemoteKey"); 463 464 auto role = settings.kind == NoiseKind.client ? NOISE_ROLE_INITIATOR : NOISE_ROLE_RESPONDER; 465 noiseCheck(noise_handshakestate_new_by_name(&handshake, NoiseProtocolID.ptr, 466 role)); 467 scope (exit) 468 noise_handshakestate_free(handshake); 469 470 // 2*keys 471 enum BufLength = KeySize * 2; 472 auto keyPtr = sodium_malloc(BufLength); 473 noiseEnforce(keyPtr !is null, "Failed to allocate memory"); 474 scope (exit) 475 sodium_free(keyPtr); 476 477 auto privKey = cast(ubyte[]) keyPtr[0 .. KeySize]; 478 auto pubKey = cast(ubyte[]) keyPtr[KeySize .. 2 * KeySize]; 479 480 // Set private key 481 auto dh = noise_handshakestate_get_local_keypair_dh(handshake); 482 if (settings.privateKeyPath.empty) 483 { 484 auto key_len = noise_dhstate_get_private_key_length(dh); 485 noiseEnforce(key_len == settings.privateKey.length, "Invalid length for private key"); 486 noiseCheck(noise_dhstate_set_keypair_private(dh, settings.privateKey.ptr, 487 key_len)); 488 } 489 else 490 { 491 settings.privateKeyPath.readFile(privKey, privKey.length); 492 noiseCheck(noise_dhstate_set_keypair_private(dh, privKey.ptr, privKey.length)); 493 } 494 495 // Start handshaking 496 NoiseBuffer mbuf; 497 noiseCheck(noise_handshakestate_start(handshake)); 498 while (true) 499 { 500 auto action = noise_handshakestate_get_action(handshake); 501 502 if (action == NOISE_ACTION_WRITE_MESSAGE) 503 { 504 /* Write the next handshake message with a zero-length payload */ 505 noise_buffer_set_output(mbuf, &_writeBuf[2], _writeBuf.length - 2); 506 noiseCheck(noise_handshakestate_write_message(handshake, &mbuf, null)); 507 _writeBuf[0] = cast(ubyte)(mbuf.size >> 8); 508 _writeBuf[1] = cast(ubyte) mbuf.size; 509 510 _stream.write(_writeBuf[0 .. mbuf.size + 2]); 511 _stream.flush(); 512 } 513 else if (action == NOISE_ACTION_READ_MESSAGE) 514 { 515 /* Read the next handshake message and discard the payload */ 516 auto len = readPacket(); 517 noise_buffer_set_input(mbuf, _readBuf.ptr, len); 518 noiseCheck(noise_handshakestate_read_message(handshake, &mbuf, null)); 519 } 520 else 521 { 522 break; 523 } 524 } 525 noiseEnforce(noise_handshakestate_get_action(handshake) == NOISE_ACTION_SPLIT); 526 527 // Verify public key 528 auto reDHState = noise_handshakestate_get_remote_public_key_dh(handshake); 529 ubyte[KeySize] recPublicKey; 530 noiseCheck(noise_dhstate_get_public_key(reDHState, recPublicKey.ptr, recPublicKey.length)); 531 scope (exit) 532 sodium_memzero(recPublicKey.ptr, recPublicKey.length); 533 534 if (settings.verifyRemoteKey is null) 535 { 536 readPublicKey(settings.remoteKeyPath, pubKey); 537 enforceEx!AuthException(recPublicKey[] == pubKey[], 538 "Authentication failed: Public keys not equal"); 539 } 540 else 541 { 542 enforceEx!AuthException(settings.verifyRemoteKey(recPublicKey), 543 "Authentication failed: Verification callback returned false"); 544 } 545 546 noiseCheck(noise_handshakestate_split(handshake, &_writeCipher, &_readCipher)); 547 } 548 549 public: 550 /** 551 * Returns true $(I iff) the end of the input stream has been reached. 552 */ 553 @property bool empty() 554 { 555 return _readDecrypted.empty && _stream.empty; 556 } 557 558 /** 559 * Returns the maximum number of bytes that are known to remain in this stream until the 560 * end is reached. After leastSize() bytes have been read, the stream will either have 561 * reached EOS and empty() returns true, or leastSize() returns again a number > 0. 562 */ 563 @property ulong leastSize() 564 { 565 if (!_readDecrypted.empty) 566 return _readDecrypted.length; 567 else 568 { 569 if (empty) 570 return 0; 571 572 readAndDecrypt(); 573 return _readDecrypted.length; 574 } 575 } 576 577 /** 578 * Queries if there is data available for immediate, non-blocking read. 579 */ 580 @property bool dataAvailableForRead() 581 { 582 // Do not check _stream.dataAvailableForRead: We do not know 583 // if we could read enough data to decrypt the next packet but if we 584 // can't decrypt, we have to block. Could maybe check peek of the 585 // underlying stream instead 586 return !_readDecrypted.empty; 587 } 588 589 /** 590 * Returns a temporary reference to the data that is currently buffered. 591 * The returned slice typically has the size `leastSize()` or `0` if 592 * `dataAvailableForRead()` returns false. Streams that don't have an 593 * internal buffer will always return an empty slice. 594 * Note that any method invocation on the same stream potentially 595 * invalidates the contents of the returned buffer. 596 */ 597 const(ubyte)[] peek() 598 { 599 return _readDecrypted; 600 } 601 602 /** 603 * Fills the preallocated array 'bytes' with data from the stream. 604 * Throws: An exception if the operation reads past the end of the stream 605 */ 606 void read(ubyte[] dst) 607 { 608 while (dst.length != 0) 609 { 610 if (_readDecrypted.empty) 611 readAndDecrypt(); 612 613 auto incr = min(dst.length, _readDecrypted.length); 614 dst[0 .. incr] = _readDecrypted[0 .. incr]; 615 _readDecrypted = _readDecrypted[incr .. $]; 616 dst = dst[incr .. $]; 617 } 618 } 619 620 /** 621 * Writes an array of bytes to the stream. 622 */ 623 void write(in ubyte[] bytesConst) 624 { 625 const(ubyte)[] bytes = bytesConst; 626 while (bytes.length != 0) 627 { 628 // 2 bytes for length, 16 bytes for MAC 629 enum MaxDataLength = _writeBuf.length - 2 - 16; 630 auto bufFree = MaxDataLength - _writeCached; 631 auto nextWrite = min(bytes.length, bufFree); 632 633 _writeBuf[2 + _writeCached .. 2 + _writeCached + nextWrite] = bytes[0 .. nextWrite]; 634 bytes = bytes[nextWrite .. $]; 635 _writeCached += nextWrite; 636 637 if(_writeCached == MaxDataLength) 638 encryptAndSend(); 639 } 640 } 641 642 /** 643 * Flushes the stream and makes sure that all data is being written to the output device. 644 */ 645 void flush() 646 { 647 encryptAndSend(); 648 _stream.flush(); 649 } 650 651 /** 652 * Flushes and finalizes the stream. 653 * Finalize has to be called on certain types of streams. No writes are possible after a 654 * call to finalize(). 655 */ 656 void finalize() 657 { 658 if (!_finalized) 659 { 660 _finalized = true; 661 noiseCheck(noise_cipherstate_free(_writeCipher)); 662 noiseCheck(noise_cipherstate_free(_readCipher)); 663 } 664 } 665 666 /** 667 * Not implemented. 668 */ 669 void write(InputStream stream, ulong nbytes = 0) 670 { 671 //TODO 672 noiseEnforce(false, "Not implemented"); 673 } 674 } 675 676 // Test that nonimplemente function write(Stream) throws 677 unittest 678 { 679 auto s = new NoiseStream(null); 680 assertThrown(s.write(null, 1)); 681 } 682 683 private: 684 void noiseEnforce(bool condition, string msg = "", string file = __FILE__, 685 size_t line = __LINE__, Throwable next = null) @safe 686 { 687 if (!condition) 688 throw new NoiseException(msg, file, line, next); 689 } 690 691 void noiseCheck(int code, string msg = "", string file = __FILE__, 692 size_t line = __LINE__, Throwable next = null) @safe 693 { 694 if (code != NOISE_ERROR_NONE) 695 throw new NoiseException(msg, code, file, line, next); 696 } 697 698 unittest 699 { 700 assertThrown!NoiseException(noiseCheck(NOISE_ERROR_INVALID_PRIVATE_KEY)); 701 } 702 703 __gshared int initResult; 704 __gshared int sodiumResult = -1; 705 706 shared static this() 707 { 708 initResult = noise_init(); 709 sodiumResult = sodium_init(); 710 } 711 712 version (unittest) 713 { 714 import vibe.d; 715 import std.exception, std.stdio; 716 717 short testPort = 8000; 718 719 void testClientAuth() 720 { 721 sleep(dur!"seconds"(1)); 722 auto conn = connectTCP("127.0.0.1", testPort); 723 auto settings = NoiseSettings(NoiseKind.client); 724 settings.privateKeyPath = Path("private.key"); 725 settings.remoteKeyPath = Path("public.key"); 726 assertThrown!AuthException(conn.createNoiseStream(settings)); 727 exitEventLoop(); 728 } 729 730 void testClientAuth2() 731 { 732 sleep(dur!"seconds"(1)); 733 auto conn = connectTCP("127.0.0.1", testPort); 734 auto settings = NoiseSettings(NoiseKind.client); 735 settings.privateKeyPath = Path("private.key"); 736 settings.verifyRemoteKey = (scope const(ubyte[]) remKey) { return false; }; 737 assertThrown!AuthException(conn.createNoiseStream(settings)); 738 exitEventLoop(); 739 } 740 741 void testClient() 742 { 743 scope (exit) 744 exitEventLoop(); 745 746 sleep(dur!"seconds"(1)); 747 auto conn = connectTCP("127.0.0.1", testPort); 748 auto settings = NoiseSettings(NoiseKind.client); 749 settings.privateKeyPath = Path("private.key"); 750 settings.remoteKeyPath = Path("public.key"); 751 auto stream = conn.createNoiseStream(settings); 752 753 // Test up to 32kB 754 auto wdata = new ubyte[1024 * 32]; 755 auto rdata = wdata.dup; 756 foreach (i, ref entry; wdata) 757 { 758 entry = i % 256; 759 } 760 761 // Write all different data lengths 762 for (size_t i = 0; i < wdata.length; i++) 763 { 764 stream.write(wdata[0 .. i]); 765 } 766 stream.flush(); 767 768 // Read all different data lengths 769 for (size_t i = 0; i < rdata.length; i++) 770 { 771 stream.read(rdata[0 .. i]); 772 assert(rdata[0 .. i] == wdata[0 .. i]); 773 } 774 775 // Read/Write all different data lengths 776 for (size_t i = 0; i < wdata.length; i += 512) 777 { 778 stream.write(wdata[0 .. i]); 779 stream.flush(); 780 stream.read(rdata[0 .. i]); 781 assert(rdata[0 .. i] == wdata[0 .. i]); 782 } 783 784 // Read & keep some data in buffer, server sent 128 bytes; 785 stream.read(rdata[0 .. 64]); 786 assert(rdata[0 .. 64] == wdata[0 .. 64]); 787 assert(!stream.empty); 788 assert(stream.dataAvailableForRead); 789 assert(stream.leastSize == 64); 790 assert(stream.peek.length == 64 && stream.peek()[0 .. 64] == wdata[64 .. 128]); 791 // Now drain the internal buffer exactly 792 stream.read(rdata[64 .. 128]); 793 assert(rdata[0 .. 128] == wdata[0 .. 128]); 794 assert(!stream.empty); 795 assert(!stream.dataAvailableForRead); 796 assert(stream.peek.length == 0); 797 798 // Now this reads in the next packet 799 assert(stream.leastSize == 64); 800 // Now read two 64 byte writes as one 128 byte read 801 stream.read(rdata[0 .. 128]); 802 assert(rdata[0 .. 128] == wdata[0 .. 128]); 803 804 // Now test stream closed behaviour 805 assert(stream.empty); 806 assert(!stream.dataAvailableForRead); 807 assert(stream.leastSize == 0); 808 assert(stream.peek().length == 0); 809 assertThrown(stream.read(rdata[0 .. 128])); 810 811 stream.finalize(); 812 conn.close(); 813 } 814 815 struct NoiseServer 816 { 817 NoiseSettings settings; 818 819 void testServer(TCPConnection conn) 820 { 821 auto stream = conn.createNoiseStream(settings); 822 823 // Test up to 32kB 824 auto wdata = new ubyte[1024 * 32]; 825 auto rdata = wdata.dup; 826 foreach (i, ref entry; wdata) 827 { 828 entry = i % 256; 829 } 830 831 // Read all different data lengths 832 for (size_t i = 0; i < rdata.length; i++) 833 { 834 stream.read(rdata[0 .. i]); 835 assert(rdata[0 .. i] == wdata[0 .. i]); 836 } 837 838 // Write all different data lengths 839 for (size_t i = 0; i < wdata.length; i++) 840 { 841 stream.write(wdata[0 .. i]); 842 } 843 stream.flush(); 844 845 // Write/Read different data lengths 846 for (size_t i = 0; i < wdata.length; i += 512) 847 { 848 stream.read(rdata[0 .. i]); 849 stream.write(wdata[0 .. i]); 850 stream.flush(); 851 assert(rdata[0 .. i] == wdata[0 .. i]); 852 } 853 854 // Send 128 bytes; 855 stream.write(wdata[0 .. 128]); 856 stream.flush(); 857 // Send two 64 byte packets 858 stream.write(wdata[0 .. 64]); 859 stream.flush(); 860 stream.write(wdata[64 .. 128]); 861 stream.flush(); 862 863 stream.finalize(); 864 conn.close(); 865 } 866 } 867 } 868 869 // Test key generation 870 unittest 871 { 872 import std.file; 873 874 string privFile = "private.key"; 875 string pubFile = "public.key"; 876 877 createKeys(privFile, pubFile); 878 scope (exit) 879 { 880 if (privFile.exists) 881 remove(privFile); 882 if (pubFile.exists) 883 remove(pubFile); 884 } 885 886 assert(privFile.exists && pubFile.exists); 887 auto content = readFileUTF8(pubFile); 888 assert(content.length == 64); 889 890 auto privContent = read(privFile); 891 assert(privContent.length == 32); 892 } 893 894 // Full test using key files 895 unittest 896 { 897 import std.file; 898 899 string privFile = "private.key"; 900 string pubFile = "public.key"; 901 902 createKeys(privFile, pubFile); 903 scope (exit) 904 { 905 if (privFile.exists) 906 remove(privFile); 907 if (pubFile.exists) 908 remove(pubFile); 909 } 910 911 // Run server 912 auto settings = NoiseSettings(NoiseKind.server); 913 settings.privateKeyPath = Path(privFile); 914 settings.remoteKeyPath = Path(pubFile); 915 auto server = NoiseServer(settings); 916 listenTCP(testPort, &server.testServer); 917 918 // Run client 919 runTask(toDelegate(&testClient)); 920 921 runEventLoop(); 922 testPort++; 923 } 924 925 // Full test using private key file 926 unittest 927 { 928 import std.file; 929 930 string privFile = "private.key"; 931 string pubFile = "public.key"; 932 933 createKeys(privFile, pubFile); 934 scope (exit) 935 { 936 if (privFile.exists) 937 remove(privFile); 938 if (pubFile.exists) 939 remove(pubFile); 940 } 941 942 ubyte[32] pubKey; 943 readPublicKey(pubFile, pubKey); 944 945 // Run server 946 auto settings = NoiseSettings(NoiseKind.server); 947 settings.verifyRemoteKey = (scope const(ubyte[]) remKey) { 948 assert(remKey[] == pubKey[]); 949 return remKey[] == pubKey[]; 950 }; 951 settings.privateKeyPath = Path(privFile); 952 auto server = NoiseServer(settings); 953 listenTCP(testPort, &server.testServer); 954 // Run client 955 runTask(toDelegate(&testClient)); 956 957 runEventLoop(); 958 testPort++; 959 } 960 961 // Full test using public key file 962 unittest 963 { 964 import std.file; 965 966 string privFile = "private.key"; 967 string pubFile = "public.key"; 968 969 createKeys(privFile, pubFile); 970 scope (exit) 971 { 972 if (privFile.exists) 973 remove(privFile); 974 if (pubFile.exists) 975 remove(pubFile); 976 } 977 978 // Run server 979 auto settings = NoiseSettings(NoiseKind.server); 980 settings.privateKey = readFile(privFile); 981 settings.remoteKeyPath = Path(pubFile); 982 auto server = NoiseServer(settings); 983 listenTCP(testPort, &server.testServer); 984 985 // Run client 986 runTask(toDelegate(&testClient)); 987 988 runEventLoop(); 989 testPort++; 990 } 991 992 // Full test using no key files 993 unittest 994 { 995 import std.file, std.typecons; 996 997 string privFile = "private.key"; 998 string pubFile = "public.key"; 999 1000 createKeys(privFile, pubFile); 1001 scope (exit) 1002 { 1003 if (privFile.exists) 1004 remove(privFile); 1005 if (pubFile.exists) 1006 remove(pubFile); 1007 } 1008 1009 ubyte[32] pubKey; 1010 readPublicKey(pubFile, pubKey); 1011 1012 // Run server 1013 auto settings = NoiseSettings(NoiseKind.server); 1014 settings.verifyRemoteKey = (scope const(ubyte[]) remKey) { 1015 assert(remKey[] == pubKey[]); 1016 return remKey[] == pubKey[]; 1017 }; 1018 settings.privateKey = readFile(privFile); 1019 auto server = NoiseServer(settings); 1020 listenTCP(testPort, &server.testServer); 1021 1022 // Run client 1023 runTask(toDelegate(&testClient)); 1024 1025 runEventLoop(); 1026 testPort++; 1027 } 1028 1029 // Fail Authentication 1030 unittest 1031 { 1032 import std.file; 1033 1034 string privFile = "private.key"; 1035 string pubFile = "public.key"; 1036 1037 createKeys(privFile, pubFile); 1038 scope (exit) 1039 { 1040 if (privFile.exists) 1041 remove(privFile); 1042 if (pubFile.exists) 1043 remove(pubFile); 1044 } 1045 1046 // Run server 1047 auto settings = NoiseSettings(NoiseKind.server); 1048 settings.privateKeyPath = Path(privFile); 1049 settings.verifyRemoteKey = (scope const(ubyte[]) remKey) { return true; }; 1050 auto server = NoiseServer(settings); 1051 listenTCP(testPort, &server.testServer); 1052 1053 // Run client 1054 runTask(toDelegate(&testClientAuth2)); 1055 1056 runEventLoop(); 1057 testPort++; 1058 } 1059 1060 // Fail Authentication 1061 unittest 1062 { 1063 import std.file; 1064 1065 string privFile = "private.key"; 1066 string pubFile = "public.key"; 1067 string privFile2 = "private2.key"; 1068 string pubFile2 = "public2.key"; 1069 1070 createKeys(privFile, pubFile); 1071 createKeys(privFile2, pubFile2); 1072 scope (exit) 1073 { 1074 if (privFile.exists) 1075 remove(privFile); 1076 if (pubFile.exists) 1077 remove(pubFile); 1078 if (privFile2.exists) 1079 remove(privFile2); 1080 if (pubFile2.exists) 1081 remove(pubFile2); 1082 } 1083 1084 // Run server 1085 auto settings = NoiseSettings(NoiseKind.server); 1086 settings.privateKeyPath = Path(privFile2); 1087 settings.verifyRemoteKey = (scope const(ubyte[]) remKey) { return true; }; 1088 auto server = NoiseServer(settings); 1089 listenTCP(testPort, &server.testServer); 1090 1091 // Run client 1092 runTask(toDelegate(&testClientAuth)); 1093 1094 runEventLoop(); 1095 testPort++; 1096 } 1097 1098 // Test invalid settings 1099 unittest 1100 { 1101 import std.file, std.typecons; 1102 1103 string privFile = "private.key"; 1104 string pubFile = "public.key"; 1105 1106 createKeys(privFile, pubFile); 1107 1108 ubyte[32] pubKey; 1109 readPublicKey(pubFile, pubKey); 1110 1111 // No private key 1112 auto settings = NoiseSettings(NoiseKind.server); 1113 settings.remoteKeyPath = Path(pubFile); 1114 1115 // No public key verification 1116 settings = NoiseSettings(NoiseKind.server); 1117 settings.privateKey = readFile(privFile); 1118 assertThrown(createNoiseStream(null, settings)); 1119 }