PullMsgResult ProduceWelcome(ref Msg msg) { Span <byte> cookieNonce = stackalloc byte[Curve25519XSalsa20Poly1305.NonceLength]; Span <byte> cookiePlaintext = stackalloc byte[64]; Span <byte> cookieCiphertext = stackalloc byte[64 + XSalsa20Poly1305.TagLength]; // Create full nonce for encryption // 8-byte prefix plus 16-byte random nonce CookieNoncePrefix.CopyTo(cookieNonce); using var rng = RandomNumberGenerator.Create(); #if NETSTANDARD2_1 rng.GetBytes(cookieNonce.Slice(8)); #else byte[] temp = new byte[16]; rng.GetBytes(temp); temp.CopyTo(cookieNonce.Slice(8)); #endif // Generate cookie = Box [C' + s'](t) m_cnClientKey.CopyTo(cookiePlaintext); m_cnSecretKey.CopyTo(cookiePlaintext.Slice(32)); // Generate fresh cookie key rng.GetBytes(m_cookieKey); // Encrypt using symmetric cookie key using var secretBox = new XSalsa20Poly1305(m_cookieKey); secretBox.Encrypt(cookieCiphertext, cookiePlaintext, cookieNonce); cookiePlaintext.Clear(); Span <byte> welcomeNonce = stackalloc byte[Curve25519XSalsa20Poly1305.NonceLength]; Span <byte> welcomePlaintext = stackalloc byte[128]; Span <byte> welcomeCiphertext = stackalloc byte[128 + Curve25519XSalsa20Poly1305.TagLength]; // Create full nonce for encryption // 8-byte prefix plus 16-byte random nonce WelcomeNoncePrefix.CopyTo(welcomeNonce); #if NETSTANDARD2_1 rng.GetBytes(welcomeNonce.Slice(8)); #else rng.GetBytes(temp); temp.CopyTo(welcomeNonce.Slice(8)); #endif // Create 144-byte Box [S' + cookie](S->C') m_cnPublicKey.CopyTo(welcomePlaintext); cookieNonce.Slice(8).CopyTo(welcomePlaintext.Slice(32)); cookieCiphertext.CopyTo(welcomePlaintext.Slice(48)); using var box = new Curve25519XSalsa20Poly1305(m_secretKey, m_cnClientKey); box.Encrypt(welcomeCiphertext, welcomePlaintext, welcomeNonce); welcomePlaintext.Clear(); msg.InitPool(168); // TODO: we can save some allocation here by allocating this earlier Span <byte> welcome = msg; WelcomeLiteral.CopyTo(welcome); welcomeNonce.Slice(8, 16).CopyTo(welcome.Slice(8)); welcomeCiphertext.CopyTo(welcome.Slice(24)); return(PullMsgResult.Ok); }
PushMsgResult ProcessInitiate(ref Msg msg) { if (!CheckBasicCommandStructure(ref msg)) { return(PushMsgResult.Error); } Span <byte> initiate = msg; if (!IsCommand("INITIATE", ref msg)) { return(PushMsgResult.Error); } if (initiate.Length < 257) { return(PushMsgResult.Error); } Span <byte> cookieNonce = stackalloc byte[Curve25519XSalsa20Poly1305.NonceLength]; Span <byte> cookiePlaintext = stackalloc byte[64]; Span <byte> cookieBox = initiate.Slice(25, 80); CookieNoncePrefix.CopyTo(cookieNonce); initiate.Slice(9, 16).CopyTo(cookieNonce.Slice(8)); using var secretBox = new XSalsa20Poly1305(m_cookieKey); bool decrypted = secretBox.TryDecrypt(cookiePlaintext, cookieBox, cookieNonce); if (!decrypted) { return(PushMsgResult.Error); } // Check cookie plain text is as expected [C' + s'] if (!SpanUtility.Equals(m_cnClientKey, cookiePlaintext.Slice(0, 32)) || !SpanUtility.Equals(m_cnSecretKey, cookiePlaintext.Slice(32, 32))) { return(PushMsgResult.Error); } Span <byte> initiateNonce = stackalloc byte[Curve25519XSalsa20Poly1305.NonceLength]; byte[] initiatePlaintext = new byte[msg.Size - 113]; var initiateBox = initiate.Slice(113); InitiatieNoncePrefix.CopyTo(initiateNonce); initiate.Slice(105, 8).CopyTo(initiateNonce.Slice(16)); m_peerNonce = NetworkOrderBitsConverter.ToUInt64(initiate, 105); using var box = new Curve25519XSalsa20Poly1305(m_cnSecretKey, m_cnClientKey); bool decrypt = box.TryDecrypt(initiatePlaintext, initiateBox, initiateNonce); if (!decrypt) { return(PushMsgResult.Error); } Span <byte> vouchNonce = stackalloc byte[Curve25519XSalsa20Poly1305.NonceLength]; Span <byte> vouchPlaintext = stackalloc byte[64]; Span <byte> vouchBox = new Span <byte>(initiatePlaintext, 48, 80); var clientKey = new Span <byte>(initiatePlaintext, 0, 32); VouchNoncePrefix.CopyTo(vouchNonce); new Span <byte>(initiatePlaintext, 32, 16).CopyTo(vouchNonce.Slice(8)); using var box2 = new Curve25519XSalsa20Poly1305(m_cnSecretKey, clientKey); decrypt = box2.TryDecrypt(vouchPlaintext, vouchBox, vouchNonce); if (!decrypt) { return(PushMsgResult.Error); } // What we decrypted must be the client's short-term public key if (!SpanUtility.Equals(vouchPlaintext.Slice(0, 32), m_cnClientKey)) { return(PushMsgResult.Error); } // Create the session box m_box = new Curve25519XSalsa20Poly1305(m_cnSecretKey, m_cnClientKey); // This supports the Stonehouse pattern (encryption without authentication). m_state = State.SendingReady; if (!ParseMetadata(new Span <byte>(initiatePlaintext, 128, initiatePlaintext.Length - 128 - 16))) { return(PushMsgResult.Error); } vouchPlaintext.Clear(); Array.Clear(initiatePlaintext, 0, initiatePlaintext.Length); return(PushMsgResult.Ok); }