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 409 this(Stream stream) pure nothrow @safe @nogc 410 { 411 this._stream = stream; 412 } 413 414 size_t readPacket() 415 { 416 ubyte[2] message_size; 417 _stream.read(message_size[]); 418 ushort len = (message_size[0] << 8) | message_size[1]; 419 noiseEnforce(len < _readBuf.length, "Ivalid packet length"); 420 _stream.read(_readBuf[0 .. len]); 421 return len; 422 } 423 424 void readAndDecrypt() 425 { 426 NoiseBuffer mbuf; 427 while (_readDecrypted.empty) 428 { 429 auto len = readPacket(); 430 noise_buffer_set_input(mbuf, _readBuf.ptr, len); 431 noiseCheck(noise_cipherstate_decrypt(_readCipher, &mbuf)); 432 _readDecrypted = _readBuf[0 .. mbuf.size]; 433 } 434 } 435 436 void handshake(scope NoiseSettings settings) 437 { 438 NoiseHandshakeState* handshake; 439 440 // Check library initialization 441 noiseCheck(initResult, "Failed to initialize libnoise-c"); 442 noiseEnforce(sodiumResult != -1, "Failed to initialize libsodium"); 443 // Check settings 444 noiseEnforce(!settings.privateKeyPath.empty 445 || !settings.privateKey.empty, "Need either privateKeyPath or privateKey"); 446 noiseEnforce(settings.verifyRemoteKey !is null 447 || !settings.remoteKeyPath.empty, "Need either remoteKeyPath or verifyRemoteKey"); 448 449 auto role = settings.kind == NoiseKind.client ? NOISE_ROLE_INITIATOR : NOISE_ROLE_RESPONDER; 450 noiseCheck(noise_handshakestate_new_by_name(&handshake, NoiseProtocolID.ptr, 451 role)); 452 scope (exit) 453 noise_handshakestate_free(handshake); 454 455 // 2*keys 456 enum BufLength = KeySize * 2; 457 auto keyPtr = sodium_malloc(BufLength); 458 noiseEnforce(keyPtr !is null, "Failed to allocate memory"); 459 scope (exit) 460 sodium_free(keyPtr); 461 462 auto privKey = cast(ubyte[]) keyPtr[0 .. KeySize]; 463 auto pubKey = cast(ubyte[]) keyPtr[KeySize .. 2 * KeySize]; 464 465 // Set private key 466 auto dh = noise_handshakestate_get_local_keypair_dh(handshake); 467 if (settings.privateKeyPath.empty) 468 { 469 auto key_len = noise_dhstate_get_private_key_length(dh); 470 noiseEnforce(key_len == settings.privateKey.length, "Invalid length for private key"); 471 noiseCheck(noise_dhstate_set_keypair_private(dh, settings.privateKey.ptr, 472 key_len)); 473 } 474 else 475 { 476 settings.privateKeyPath.readFile(privKey, privKey.length); 477 noiseCheck(noise_dhstate_set_keypair_private(dh, privKey.ptr, privKey.length)); 478 } 479 480 // Start handshaking 481 NoiseBuffer mbuf; 482 noiseCheck(noise_handshakestate_start(handshake)); 483 while (true) 484 { 485 auto action = noise_handshakestate_get_action(handshake); 486 487 if (action == NOISE_ACTION_WRITE_MESSAGE) 488 { 489 /* Write the next handshake message with a zero-length payload */ 490 noise_buffer_set_output(mbuf, &_writeBuf[2], _writeBuf.length - 2); 491 noiseCheck(noise_handshakestate_write_message(handshake, &mbuf, null)); 492 _writeBuf[0] = cast(ubyte)(mbuf.size >> 8); 493 _writeBuf[1] = cast(ubyte) mbuf.size; 494 495 _stream.write(_writeBuf[0 .. mbuf.size + 2]); 496 _stream.flush(); 497 } 498 else if (action == NOISE_ACTION_READ_MESSAGE) 499 { 500 /* Read the next handshake message and discard the payload */ 501 auto len = readPacket(); 502 noise_buffer_set_input(mbuf, _readBuf.ptr, len); 503 noiseCheck(noise_handshakestate_read_message(handshake, &mbuf, null)); 504 } 505 else 506 { 507 break; 508 } 509 } 510 noiseEnforce(noise_handshakestate_get_action(handshake) == NOISE_ACTION_SPLIT); 511 512 // Verify public key 513 auto reDHState = noise_handshakestate_get_remote_public_key_dh(handshake); 514 ubyte[KeySize] recPublicKey; 515 noiseCheck(noise_dhstate_get_public_key(reDHState, recPublicKey.ptr, recPublicKey.length)); 516 scope (exit) 517 sodium_memzero(recPublicKey.ptr, recPublicKey.length); 518 519 if (settings.verifyRemoteKey is null) 520 { 521 readPublicKey(settings.remoteKeyPath, pubKey); 522 enforceEx!AuthException(recPublicKey[] == pubKey[], 523 "Authentication failed: Public keys not equal"); 524 } 525 else 526 { 527 enforceEx!AuthException(settings.verifyRemoteKey(recPublicKey), 528 "Authentication failed: Verification callback returned false"); 529 } 530 531 noiseCheck(noise_handshakestate_split(handshake, &_writeCipher, &_readCipher)); 532 } 533 534 public: 535 /** 536 * Returns true $(I iff) the end of the input stream has been reached. 537 */ 538 @property bool empty() 539 { 540 return _readDecrypted.empty && _stream.empty; 541 } 542 543 /** 544 * Returns the maximum number of bytes that are known to remain in this stream until the 545 * end is reached. After leastSize() bytes have been read, the stream will either have 546 * reached EOS and empty() returns true, or leastSize() returns again a number > 0. 547 */ 548 @property ulong leastSize() 549 { 550 if (!_readDecrypted.empty) 551 return _readDecrypted.length; 552 else 553 { 554 if (empty) 555 return 0; 556 557 readAndDecrypt(); 558 return _readDecrypted.length; 559 } 560 } 561 562 /** 563 * Queries if there is data available for immediate, non-blocking read. 564 */ 565 @property bool dataAvailableForRead() 566 { 567 // Do not check _stream.dataAvailableForRead: We do not know 568 // if we could read enough data to decrypt the next packet but if we 569 // can't decrypt, we have to block. Could maybe check peek of the 570 // underlying stream instead 571 return !_readDecrypted.empty; 572 } 573 574 /** 575 * Returns a temporary reference to the data that is currently buffered. 576 * The returned slice typically has the size `leastSize()` or `0` if 577 * `dataAvailableForRead()` returns false. Streams that don't have an 578 * internal buffer will always return an empty slice. 579 * Note that any method invocation on the same stream potentially 580 * invalidates the contents of the returned buffer. 581 */ 582 const(ubyte)[] peek() 583 { 584 return _readDecrypted; 585 } 586 587 /** 588 * Fills the preallocated array 'bytes' with data from the stream. 589 * Throws: An exception if the operation reads past the end of the stream 590 */ 591 void read(ubyte[] dst) 592 { 593 while (dst.length != 0) 594 { 595 if (_readDecrypted.empty) 596 readAndDecrypt(); 597 598 auto incr = min(dst.length, _readDecrypted.length); 599 dst[0 .. incr] = _readDecrypted[0 .. incr]; 600 _readDecrypted = _readDecrypted[incr .. $]; 601 dst = dst[incr .. $]; 602 } 603 } 604 605 /** 606 * Writes an array of bytes to the stream. 607 */ 608 void write(in ubyte[] bytesConst) 609 { 610 const(ubyte)[] bytes = bytesConst; 611 NoiseBuffer mbuf; 612 while (bytes.length != 0) 613 { 614 // 2 bytes for length, 16 bytes for MAC 615 enum MaxDataLength = _writeBuf.length - 2 - 16; 616 auto nextWrite = min(bytes.length, MaxDataLength); 617 _writeBuf[2 .. nextWrite + 2] = bytes[0 .. nextWrite]; 618 bytes = bytes[nextWrite .. $]; 619 620 noise_buffer_set_inout(mbuf, &_writeBuf[2], nextWrite, _writeBuf.length - 2); 621 noiseCheck(noise_cipherstate_encrypt(_writeCipher, &mbuf)); 622 _writeBuf[0] = cast(ubyte)(mbuf.size >> 8); 623 _writeBuf[1] = cast(ubyte) mbuf.size; 624 _stream.write(_writeBuf[0 .. mbuf.size + 2]); 625 } 626 } 627 628 /** 629 * Flushes the stream and makes sure that all data is being written to the output device. 630 */ 631 void flush() 632 { 633 _stream.flush(); 634 // We always create one crypted packet in write 635 // TODO: is this a problem for small writes? Is flush for network like 636 // streams well supported? Then we could fill the buffer before writing... 637 } 638 639 /** 640 * Flushes and finalizes the stream. 641 * Finalize has to be called on certain types of streams. No writes are possible after a 642 * call to finalize(). 643 */ 644 void finalize() 645 { 646 if (!_finalized) 647 { 648 _finalized = true; 649 noiseCheck(noise_cipherstate_free(_writeCipher)); 650 noiseCheck(noise_cipherstate_free(_readCipher)); 651 } 652 } 653 654 /** 655 * Not implemented. 656 */ 657 void write(InputStream stream, ulong nbytes = 0) 658 { 659 //TODO 660 noiseEnforce(false, "Not implemented"); 661 } 662 } 663 664 // Test that nonimplemente function write(Stream) throws 665 unittest 666 { 667 auto s = new NoiseStream(null); 668 assertThrown(s.write(null, 1)); 669 } 670 671 private: 672 void noiseEnforce(bool condition, string msg = "", string file = __FILE__, 673 size_t line = __LINE__, Throwable next = null) @safe 674 { 675 if (!condition) 676 throw new NoiseException(msg, file, line, next); 677 } 678 679 void noiseCheck(int code, string msg = "", string file = __FILE__, 680 size_t line = __LINE__, Throwable next = null) @safe 681 { 682 if (code != NOISE_ERROR_NONE) 683 throw new NoiseException(msg, code, file, line, next); 684 } 685 686 unittest 687 { 688 assertThrown!NoiseException(noiseCheck(NOISE_ERROR_INVALID_PRIVATE_KEY)); 689 } 690 691 __gshared int initResult; 692 __gshared int sodiumResult = -1; 693 694 shared static this() 695 { 696 initResult = noise_init(); 697 sodiumResult = sodium_init(); 698 } 699 700 version (unittest) 701 { 702 import vibe.d; 703 import std.exception, std.stdio; 704 705 short testPort = 8000; 706 707 void testClientAuth() 708 { 709 sleep(dur!"seconds"(1)); 710 auto conn = connectTCP("127.0.0.1", testPort); 711 auto settings = NoiseSettings(NoiseKind.client); 712 settings.privateKeyPath = Path("private.key"); 713 settings.remoteKeyPath = Path("public.key"); 714 assertThrown!AuthException(conn.createNoiseStream(settings)); 715 exitEventLoop(); 716 } 717 718 void testClientAuth2() 719 { 720 sleep(dur!"seconds"(1)); 721 auto conn = connectTCP("127.0.0.1", testPort); 722 auto settings = NoiseSettings(NoiseKind.client); 723 settings.privateKeyPath = Path("private.key"); 724 settings.verifyRemoteKey = (scope const(ubyte[]) remKey) { return false; }; 725 assertThrown!AuthException(conn.createNoiseStream(settings)); 726 exitEventLoop(); 727 } 728 729 void testClient() 730 { 731 scope (exit) 732 exitEventLoop(); 733 734 sleep(dur!"seconds"(1)); 735 auto conn = connectTCP("127.0.0.1", testPort); 736 auto settings = NoiseSettings(NoiseKind.client); 737 settings.privateKeyPath = Path("private.key"); 738 settings.remoteKeyPath = Path("public.key"); 739 auto stream = conn.createNoiseStream(settings); 740 741 // Test up to 32kB 742 auto wdata = new ubyte[1024 * 32]; 743 auto rdata = wdata.dup; 744 foreach (i, ref entry; wdata) 745 { 746 entry = i % 256; 747 } 748 749 // Write all different data lengths 750 for (size_t i = 0; i < wdata.length; i++) 751 { 752 stream.write(wdata[0 .. i]); 753 } 754 755 // Read all different data lengths 756 for (size_t i = 0; i < rdata.length; i++) 757 { 758 stream.read(rdata[0 .. i]); 759 assert(rdata[0 .. i] == wdata[0 .. i]); 760 } 761 762 // Read/Write all different data lengths 763 for (size_t i = 0; i < wdata.length; i += 512) 764 { 765 stream.write(wdata[0 .. i]); 766 stream.read(rdata[0 .. i]); 767 assert(rdata[0 .. i] == wdata[0 .. i]); 768 } 769 770 // Read & keep some data in buffer, server sent 128 bytes; 771 stream.read(rdata[0 .. 64]); 772 assert(rdata[0 .. 64] == wdata[0 .. 64]); 773 assert(!stream.empty); 774 assert(stream.dataAvailableForRead); 775 assert(stream.leastSize == 64); 776 assert(stream.peek.length == 64 && stream.peek()[0 .. 64] == wdata[64 .. 128]); 777 // Now drain the internal buffer exactly 778 stream.read(rdata[64 .. 128]); 779 assert(rdata[0 .. 128] == wdata[0 .. 128]); 780 assert(!stream.empty); 781 assert(!stream.dataAvailableForRead); 782 assert(stream.peek.length == 0); 783 784 // Now this reads in the next packet 785 assert(stream.leastSize == 64); 786 // Now read two 64 byte writes as one 128 byte read 787 stream.read(rdata[0 .. 128]); 788 assert(rdata[0 .. 128] == wdata[0 .. 128]); 789 790 // Now test stream closed behaviour 791 assert(stream.empty); 792 assert(!stream.dataAvailableForRead); 793 assert(stream.leastSize == 0); 794 assert(stream.peek().length == 0); 795 assertThrown(stream.read(rdata[0 .. 128])); 796 797 stream.finalize(); 798 conn.close(); 799 } 800 801 struct NoiseServer 802 { 803 NoiseSettings settings; 804 805 void testServer(TCPConnection conn) 806 { 807 scope (failure) 808 exitEventLoop(); 809 810 auto stream = conn.createNoiseStream(settings); 811 812 // Test up to 32kB 813 auto wdata = new ubyte[1024 * 32]; 814 auto rdata = wdata.dup; 815 foreach (i, ref entry; wdata) 816 { 817 entry = i % 256; 818 } 819 820 // Read all different data lengths 821 for (size_t i = 0; i < rdata.length; i++) 822 { 823 stream.read(rdata[0 .. i]); 824 assert(rdata[0 .. i] == wdata[0 .. i]); 825 } 826 827 // Write all different data lengths 828 for (size_t i = 0; i < wdata.length; i++) 829 { 830 stream.write(wdata[0 .. i]); 831 } 832 833 // Write/Read different data lengths 834 for (size_t i = 0; i < wdata.length; i += 512) 835 { 836 stream.read(rdata[0 .. i]); 837 stream.write(wdata[0 .. i]); 838 assert(rdata[0 .. i] == wdata[0 .. i]); 839 } 840 841 // Send 128 bytes; 842 stream.write(wdata[0 .. 128]); 843 // Send two 64 byte packets 844 stream.write(wdata[0 .. 64]); 845 stream.write(wdata[64 .. 128]); 846 847 stream.flush(); 848 stream.finalize(); 849 conn.close(); 850 } 851 } 852 } 853 854 // Test key generation 855 unittest 856 { 857 import std.file; 858 859 string privFile = "private.key"; 860 string pubFile = "public.key"; 861 862 createKeys(privFile, pubFile); 863 scope (exit) 864 { 865 if (privFile.exists) 866 remove(privFile); 867 if (pubFile.exists) 868 remove(pubFile); 869 } 870 871 assert(privFile.exists && pubFile.exists); 872 auto content = readFileUTF8(pubFile); 873 assert(content.length == 64); 874 875 auto privContent = read(privFile); 876 assert(privContent.length == 32); 877 } 878 879 // Full test using key files 880 unittest 881 { 882 import std.file; 883 884 string privFile = "private.key"; 885 string pubFile = "public.key"; 886 887 createKeys(privFile, pubFile); 888 scope (exit) 889 { 890 if (privFile.exists) 891 remove(privFile); 892 if (pubFile.exists) 893 remove(pubFile); 894 } 895 896 // Run server 897 auto settings = NoiseSettings(NoiseKind.server); 898 settings.privateKeyPath = Path(privFile); 899 settings.remoteKeyPath = Path(pubFile); 900 auto server = NoiseServer(settings); 901 listenTCP(testPort, &server.testServer); 902 903 // Run client 904 runTask(toDelegate(&testClient)); 905 906 runEventLoop(); 907 testPort++; 908 } 909 910 // Full test using private key file 911 unittest 912 { 913 import std.file; 914 915 string privFile = "private.key"; 916 string pubFile = "public.key"; 917 918 createKeys(privFile, pubFile); 919 scope (exit) 920 { 921 if (privFile.exists) 922 remove(privFile); 923 if (pubFile.exists) 924 remove(pubFile); 925 } 926 927 ubyte[32] pubKey; 928 readPublicKey(pubFile, pubKey); 929 930 // Run server 931 auto settings = NoiseSettings(NoiseKind.server); 932 settings.verifyRemoteKey = (scope const(ubyte[]) remKey) { 933 assert(remKey[] == pubKey[]); 934 return remKey[] == pubKey[]; 935 }; 936 settings.privateKeyPath = Path(privFile); 937 auto server = NoiseServer(settings); 938 listenTCP(testPort, &server.testServer); 939 // Run client 940 runTask(toDelegate(&testClient)); 941 942 runEventLoop(); 943 testPort++; 944 } 945 946 // Full test using public key file 947 unittest 948 { 949 import std.file; 950 951 string privFile = "private.key"; 952 string pubFile = "public.key"; 953 954 createKeys(privFile, pubFile); 955 scope (exit) 956 { 957 if (privFile.exists) 958 remove(privFile); 959 if (pubFile.exists) 960 remove(pubFile); 961 } 962 963 // Run server 964 auto settings = NoiseSettings(NoiseKind.server); 965 settings.privateKey = readFile(privFile); 966 settings.remoteKeyPath = Path(pubFile); 967 auto server = NoiseServer(settings); 968 listenTCP(testPort, &server.testServer); 969 970 // Run client 971 runTask(toDelegate(&testClient)); 972 973 runEventLoop(); 974 testPort++; 975 } 976 977 // Full test using no key files 978 unittest 979 { 980 import std.file, std.typecons; 981 982 string privFile = "private.key"; 983 string pubFile = "public.key"; 984 985 createKeys(privFile, pubFile); 986 scope (exit) 987 { 988 if (privFile.exists) 989 remove(privFile); 990 if (pubFile.exists) 991 remove(pubFile); 992 } 993 994 ubyte[32] pubKey; 995 readPublicKey(pubFile, pubKey); 996 997 // Run server 998 auto settings = NoiseSettings(NoiseKind.server); 999 settings.verifyRemoteKey = (scope const(ubyte[]) remKey) { 1000 assert(remKey[] == pubKey[]); 1001 return remKey[] == pubKey[]; 1002 }; 1003 settings.privateKey = readFile(privFile); 1004 auto server = NoiseServer(settings); 1005 listenTCP(testPort, &server.testServer); 1006 1007 // Run client 1008 runTask(toDelegate(&testClient)); 1009 1010 runEventLoop(); 1011 testPort++; 1012 } 1013 1014 // Fail Authentication 1015 unittest 1016 { 1017 import std.file; 1018 1019 string privFile = "private.key"; 1020 string pubFile = "public.key"; 1021 1022 createKeys(privFile, pubFile); 1023 scope (exit) 1024 { 1025 if (privFile.exists) 1026 remove(privFile); 1027 if (pubFile.exists) 1028 remove(pubFile); 1029 } 1030 1031 // Run server 1032 auto settings = NoiseSettings(NoiseKind.server); 1033 settings.privateKeyPath = Path(privFile); 1034 settings.remoteKeyPath = Path(pubFile); 1035 auto server = NoiseServer(settings); 1036 listenTCP(testPort, &server.testServer); 1037 1038 // Run client 1039 runTask(toDelegate(&testClientAuth2)); 1040 1041 runEventLoop(); 1042 testPort++; 1043 } 1044 1045 // Fail Authentication 1046 unittest 1047 { 1048 import std.file; 1049 1050 string privFile = "private.key"; 1051 string pubFile = "public.key"; 1052 string privFile2 = "private2.key"; 1053 string pubFile2 = "public2.key"; 1054 1055 createKeys(privFile, pubFile); 1056 createKeys(privFile2, pubFile2); 1057 scope (exit) 1058 { 1059 if (privFile.exists) 1060 remove(privFile); 1061 if (pubFile.exists) 1062 remove(pubFile); 1063 if (privFile2.exists) 1064 remove(privFile2); 1065 if (pubFile2.exists) 1066 remove(pubFile2); 1067 } 1068 1069 // Run server 1070 auto settings = NoiseSettings(NoiseKind.server); 1071 settings.privateKeyPath = Path(privFile2); 1072 settings.remoteKeyPath = Path(pubFile); 1073 auto server = NoiseServer(settings); 1074 listenTCP(testPort, &server.testServer); 1075 1076 // Run client 1077 runTask(toDelegate(&testClientAuth)); 1078 1079 runEventLoop(); 1080 testPort++; 1081 } 1082 1083 // Test invalid settings 1084 unittest 1085 { 1086 import std.file, std.typecons; 1087 1088 string privFile = "private.key"; 1089 string pubFile = "public.key"; 1090 1091 createKeys(privFile, pubFile); 1092 1093 ubyte[32] pubKey; 1094 readPublicKey(pubFile, pubKey); 1095 1096 // No private key 1097 auto settings = NoiseSettings(NoiseKind.server); 1098 settings.remoteKeyPath = Path(pubFile); 1099 1100 // No public key verification 1101 settings = NoiseSettings(NoiseKind.server); 1102 settings.privateKey = readFile(privFile); 1103 assertThrown(createNoiseStream(null, settings)); 1104 }