private void Reinitialize(Protocol protocol, ProtocolConfig config, State state) { bool fallback = state == State.Switch && (protocol.Modifiers & PatternModifiers.Fallback) != 0; if (fallback && handshakeState == null) { throw new InvalidOperationException("Cannot perform the fallback handshake on an uninitialized server."); } if (protocol.HandshakePattern.Patterns.Count() == 1) { throw new NotSupportedException("One-way patterns are not yet supported."); } if (transport != null) { throw new InvalidOperationException($"Cannot change protocol after the handshake has been completed."); } if (this.state != State.Initial) { throw new InvalidOperationException($"Cannot change protocol more than once."); } this.protocol = protocol; this.config = config; this.state = state; this.fallback = fallback; if (!fallback && handshakeState != null) { handshakeState.Dispose(); handshakeState = null; } isNextMessageEncrypted = IsInitialMessageEncrypted(protocol); }
/// <summary> /// Releases all resources used by the current /// instance of the <see cref="NoiseSocket"/> class. /// </summary> public void Dispose() { if (!disposed) { if (!leaveOpen) { stream.Dispose(); } handshakeState?.Dispose(); transport?.Dispose(); disposed = true; } }
/// <summary> /// Perform handshake protocol if it has not been already run. /// </summary> /// <remarks> /// Most uses of this package need not call Handshake explicitly: /// the first Read or Write will call it automatically. /// </remarks> internal void HandShake() { // Locking the handshakeMutex this.handshakeMutex.WaitOne(); HandshakeState handshakeState = null; try { Strobe c1; Strobe c2; byte[] receivedPayload = null; // did we already go through the handshake? if (this.handshakeComplite) { return; } KeyPair remoteKeyPair = null; if (this.config.RemoteKey != null) { if (this.config.RemoteKey.Length != Asymmetric.DhLen) { throw new Exception($"disco: the provided remote key is not {Asymmetric.DhLen}-byte"); } remoteKeyPair = new KeyPair { PublicKey = new byte[this.config.RemoteKey.Length] }; Array.Copy(this.config.RemoteKey, remoteKeyPair.PublicKey, this.config.RemoteKey.Length); } handshakeState = DiscoHelper.InitializeDisco( this.config.HandshakePattern, this.isClient, this.config.Prologue, this.config.KeyPair, null, remoteKeyPair, null); // pre-shared key handshakeState.Psk = this.config.PreSharedKey; do { // start handshake if (handshakeState.ShouldWrite) { // we're writing the next message pattern // if it's the message pattern and we're sending a static key, we also send a proof // TODO: is this the best way of sending a proof :/ ? byte[] bufToWrite; if (handshakeState.MessagePatterns.Length <= 2 && this.config.StaticPublicKeyProof != null) { (c1, c2) = handshakeState.WriteMessage(this.config.StaticPublicKeyProof, out bufToWrite); } else { (c1, c2) = handshakeState.WriteMessage(new byte[] { }, out bufToWrite); } // header (length) var length = new[] { (byte)(bufToWrite.Length >> 8), (byte)(bufToWrite.Length % 256) }; // write var dataToWrite = length.Concat(bufToWrite).ToArray(); this.connectionStream.Write(dataToWrite, 0, dataToWrite.Length); } else { var bufHeader = this.ReadFromUntil(this.connectionStream, 2); var length = (bufHeader[0] << 8) | bufHeader[1]; if (length > Config.NoiseMessageLength) { throw new Exception("disco: Disco message received exceeds DiscoMessageLength"); } var noiseMessage = this.ReadFromUntil(this.connectionStream, length); (c1, c2) = handshakeState.ReadMessage(noiseMessage, out receivedPayload); } }while (c1 == null); // Has the other peer been authenticated so far? if (!this.isRemoteAuthenticated && this.config.PublicKeyVerifier != null) { byte isRemoteStaticKeySet = 0; // test if remote static key is empty foreach (var val in handshakeState.Rs.PublicKey) { isRemoteStaticKeySet |= val; } if (isRemoteStaticKeySet != 0) { // a remote static key has been received. Verify it if (!this.config.PublicKeyVerifier(handshakeState.Rs.PublicKey, receivedPayload)) { throw new Exception("disco: the received public key could not be authenticated"); } this.isRemoteAuthenticated = true; this.RemotePublicKey = handshakeState.Rs.PublicKey; } } // Processing the final handshake message returns two CipherState objects // the first for encrypting transport messages from initiator to responder // and the second for messages in the other direction. if (c2 != null) { if (this.isClient) { (this.strobeOut, this.strobeIn) = (c1, c2); } else { (this.strobeOut, this.strobeIn) = (c2, c1); } } else { this.IsHalfDuplex = true; this.strobeIn = c1; this.strobeOut = c1; } // TODO: preserve c.hs.symmetricState.h // At that point the HandshakeState should be deleted except for the hash value h, which may be used for post-handshake channel binding (see Section 11.2). handshakeState.Dispose(); // no errors :) this.handshakeComplite = true; } finally { handshakeState?.Dispose(); this.handshakeMutex.ReleaseMutex(); } }
/// <summary> /// Disco peer initialization /// </summary> /// <param name="handshakeType">Noise handshake pattern</param> /// <param name="initiator">This party initiates connection</param> /// <param name="prologue">Prologue string, some data prior to handshake</param> /// <param name="s">local static key</param> /// <param name="e">local ephemeral key</param> /// <param name="rs">remote static key</param> /// <param name="re">remote ephemeral key</param> /// <returns>Initialized Disco handshake state</returns> public static HandshakeState InitializeDisco( NoiseHandshakeType handshakeType, bool initiator, byte[] prologue, KeyPair s, KeyPair e, KeyPair rs, KeyPair re) { var handshakePattern = HandshakePattern.GetPattern(handshakeType); var handshakeState = new HandshakeState { SymmetricState = new SymmetricState($"Noise_{handshakePattern.Name}_25519_STROBEv1.0.2"), Initiator = initiator, ShouldWrite = initiator }; try { if (prologue != null) { handshakeState.SymmetricState.MixHash(prologue); } if (s != null) { handshakeState.S = s; } if (e != null) { throw new NotSupportedException("disco: fallback patterns are not implemented"); } if (rs != null) { handshakeState.Rs = rs; } if (re != null) { throw new NotSupportedException("disco: fallback patterns are not implemented"); } //Calls MixHash() once for each public key listed in the pre-messages from handshake_pattern, //with the specified public key as input (see Section 7 for an explanation of pre-messages). //If both initiator and responder have pre-messages, the initiator's public keys are hashed first. // initiator pre-message pattern foreach (var token in handshakePattern.PreMessagePatterns[0]) { if (token == Tokens.TokenS) { if (initiator) { if (s == null) { throw new Exception("disco: the static key of the client should be set"); } handshakeState.SymmetricState.MixHash(s.PublicKey); } else { if (rs == null) { throw new Exception("disco: the remote static key of the server should be set"); } handshakeState.SymmetricState.MixHash(rs.PublicKey); } } else { throw new Exception("disco: token of pre-message not supported"); } } // responder pre-message pattern foreach (var token in handshakePattern.PreMessagePatterns[1]) { if (token == Tokens.TokenS) { if (initiator) { if (rs == null) { throw new Exception("disco: the remote static key of the client should be set"); } handshakeState.SymmetricState.MixHash(rs.PublicKey); } else { if (s == null) { throw new Exception("disco: the static key of the server should be set"); } handshakeState.SymmetricState.MixHash(s.PublicKey); } } else { throw new NotSupportedException("disco: token of pre - message not supported"); } } handshakeState.MessagePatterns = handshakePattern.MessagePatterns; return(handshakeState); } catch (Exception) { handshakeState.Dispose(); throw; } }