public void StretchedSecret() { var cipher = "AES-256"; var hash = "SHA256"; var secret = new byte[] { 195, 191, 209, 165, 209, 201, 127, 122, 136, 111, 31, 66, 111, 68, 38, 155, 216, 204, 46, 181, 200, 188, 170, 204, 104, 74, 239, 251, 173, 114, 222, 234 }; StretchedKey.Generate(cipher, hash, secret, out var k1, out var k2); Assert.IsNotNull(k1); CollectionAssert.AreEqual( new byte[] { 208, 132, 203, 169, 253, 52, 40, 83, 161, 91, 17, 71, 33, 136, 67, 96 }, k1.Iv); CollectionAssert.AreEqual( new byte[] { 156, 48, 241, 157, 92, 248, 153, 186, 114, 127, 195, 114, 106, 104, 215, 133, 35, 11, 131, 137, 123, 70, 74, 26, 15, 60, 189, 32, 67, 221, 115, 137 }, k1.CipherKey); CollectionAssert.AreEqual( new byte[] { 6, 179, 91, 245, 224, 56, 153, 120, 77, 140, 29, 5, 15, 213, 187, 65, 137, 230, 202, 120 }, k1.MacKey); Assert.IsNotNull(k2); CollectionAssert.AreEqual( new byte[] { 236, 17, 34, 141, 90, 106, 197, 56, 197, 184, 157, 135, 91, 88, 112, 19 }, k2.Iv); CollectionAssert.AreEqual( new byte[] { 151, 145, 195, 219, 76, 195, 102, 109, 187, 231, 100, 150, 132, 245, 251, 130, 254, 37, 178, 55, 227, 34, 114, 39, 238, 34, 2, 193, 107, 130, 32, 87 }, k2.CipherKey); CollectionAssert.AreEqual( new byte[] { 3, 229, 77, 212, 241, 217, 23, 113, 220, 126, 38, 255, 18, 117, 108, 205, 198, 89, 1, 236 }, k2.MacKey); }
#pragma warning disable VSTHRD103 /// <inheritdoc /> public async Task <Stream> EncryptAsync(PeerConnection connection, CancellationToken cancel = default(CancellationToken)) { var stream = connection.Stream; var localPeer = connection.LocalPeer; connection.RemotePeer = connection.RemotePeer ?? new Peer(); var remotePeer = connection.RemotePeer; // ============================================================================= // step 1. Propose -- propose cipher suite + send pubkey + nonce var rng = new SecureRandom(); var localNonce = new byte[16]; rng.NextBytes(localNonce); var localProposal = new Secio1Propose { Nonce = localNonce, Exchanges = "P-256,P-384,P-521", Ciphers = "AES-256,AES-128", Hashes = "SHA256,SHA512", PublicKey = Convert.FromBase64String(localPeer.PublicKey) }; ProtoBuf.Serializer.SerializeWithLengthPrefix(stream, localProposal, PrefixStyle.Fixed32BigEndian); await stream.FlushAsync().ConfigureAwait(false); // ============================================================================= // step 1.1 Identify -- get identity from their key var remoteProposal = ProtoBuf.Serializer.DeserializeWithLengthPrefix <Secio1Propose>(stream, PrefixStyle.Fixed32BigEndian); var ridAlg = (remoteProposal.PublicKey.Length <= 48) ? "identity" : "sha2-256"; var remoteId = MultiHash.ComputeHash(remoteProposal.PublicKey, ridAlg); if (remotePeer.Id == null) { remotePeer.Id = remoteId; } else if (remoteId != remotePeer.Id) { throw new Exception($"Expected peer '{remotePeer.Id}', got '{remoteId}'"); } // ============================================================================= // step 1.2 Selection -- select/agree on best encryption parameters // to determine order, use cmp(H(remote_pubkey||local_rand), H(local_pubkey||remote_rand)). // oh1 := hashSha256(append(proposeIn.GetPubkey(), nonceOut...)) // oh2 := hashSha256(append(myPubKeyBytes, proposeIn.GetRand()...)) // order := bytes.Compare(oh1, oh2) byte[] oh1; byte[] oh2; using (var hasher = MultiHash.GetHashAlgorithm("sha2-256")) using (var ms = new MemoryStream()) { ms.Write(remoteProposal.PublicKey, 0, remoteProposal.PublicKey.Length); ms.Write(localProposal.Nonce, 0, localProposal.Nonce.Length); ms.Position = 0; oh1 = hasher.ComputeHash(ms); } using (var hasher = MultiHash.GetHashAlgorithm("sha2-256")) using (var ms = new MemoryStream()) { ms.Write(localProposal.PublicKey, 0, localProposal.PublicKey.Length); ms.Write(remoteProposal.Nonce, 0, remoteProposal.Nonce.Length); ms.Position = 0; oh2 = hasher.ComputeHash(ms); } int order = 0; for (int i = 0; order == 0 && i < oh1.Length; ++i) { order = oh1[i].CompareTo(oh2[i]); } if (order == 0) { throw new Exception("Same keys and nonces; talking to self"); } var curveName = SelectBest(order, localProposal.Exchanges, remoteProposal.Exchanges); if (curveName == null) { throw new Exception("Cannot agree on a key exchange."); } var cipherName = SelectBest(order, localProposal.Ciphers, remoteProposal.Ciphers); if (cipherName == null) { throw new Exception("Cannot agree on a chipher."); } var hashName = SelectBest(order, localProposal.Hashes, remoteProposal.Hashes); if (hashName == null) { throw new Exception("Cannot agree on a hash."); } // ============================================================================= // step 2. Exchange -- exchange (signed) ephemeral keys. verify signatures. // Generate EphemeralPubKey var localEphemeralKey = EphermalKey.Generate(curveName); var localEphemeralPublicKey = localEphemeralKey.PublicKeyBytes(); // Send Exchange packet var localExchange = new Secio1Exchange(); using (var ms = new MemoryStream()) { ProtoBuf.Serializer.Serialize(ms, localProposal); ProtoBuf.Serializer.Serialize(ms, remoteProposal); ms.Write(localEphemeralPublicKey, 0, localEphemeralPublicKey.Length); localExchange.Signature = connection.LocalPeerKey.Sign(ms.ToArray()); } localExchange.EPublicKey = localEphemeralPublicKey; ProtoBuf.Serializer.SerializeWithLengthPrefix(stream, localExchange, PrefixStyle.Fixed32BigEndian); await stream.FlushAsync(cancel).ConfigureAwait(false); // Receive their Exchange packet. If nothing, then most likely the // remote has closed the connection because it does not like us. var remoteExchange = ProtoBuf.Serializer.DeserializeWithLengthPrefix <Secio1Exchange>(stream, PrefixStyle.Fixed32BigEndian); if (remoteExchange == null) { throw new Exception("Remote refuses the SECIO exchange."); } // ============================================================================= // step 2.1. Verify -- verify their exchange packet is good. var remotePeerKey = Key.CreatePublicKeyFromIpfs(remoteProposal.PublicKey); using (var ms = new MemoryStream()) { ProtoBuf.Serializer.Serialize(ms, remoteProposal); ProtoBuf.Serializer.Serialize(ms, localProposal); ms.Write(remoteExchange.EPublicKey, 0, remoteExchange.EPublicKey.Length); remotePeerKey.Verify(ms.ToArray(), remoteExchange.Signature); } var remoteEphemeralKey = EphermalKey.CreatePublicKeyFromIpfs(curveName, remoteExchange.EPublicKey); // ============================================================================= // step 2.2. Keys -- generate keys for mac + encryption var sharedSecret = localEphemeralKey.GenerateSharedSecret(remoteEphemeralKey); StretchedKey.Generate(cipherName, hashName, sharedSecret, out StretchedKey k1, out StretchedKey k2); if (order < 0) { StretchedKey tmp = k1; k1 = k2; k2 = tmp; } // ============================================================================= // step 2.3. MAC + Cipher -- prepare MAC + cipher var secureStream = new Secio1Stream(stream, cipherName, hashName, k1, k2); // ============================================================================= // step 3. Finish -- send expected message to verify encryption works (send local nonce) // Send thier nonce, await secureStream.WriteAsync(remoteProposal.Nonce, 0, remoteProposal.Nonce.Length, cancel).ConfigureAwait(false); await secureStream.FlushAsync(cancel).ConfigureAwait(false); // Receive our nonce. var verification = new byte[localNonce.Length]; await secureStream.ReadExactAsync(verification, 0, verification.Length, cancel); if (!localNonce.SequenceEqual(verification)) { throw new Exception($"SECIO verification message failure."); } log.Debug($"Secure session with {remotePeer}"); // Fill in the remote peer remotePeer.PublicKey = Convert.ToBase64String(remoteProposal.PublicKey); // Set secure task done connection.Stream = secureStream; connection.SecurityEstablished.SetResult(true); return(secureStream); }