Example #1
0
        public void SharedSecret()
        {
            var curve = "P-256";
            var alice = EphermalKey.Generate(curve);
            var bob   = EphermalKey.Generate(curve);

            var aliceSecret = alice.GenerateSharedSecret(bob);
            var bobSecret   = bob.GenerateSharedSecret(alice);

            CollectionAssert.AreEqual(aliceSecret, bobSecret);
            Assert.AreEqual(32, aliceSecret.Length);
        }
Example #2
0
#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);
        }