public async Task <KeyExchangeOutput> TryExchangeAsync(SshConnection connection, KeyExchangeInput input, ILogger logger, CancellationToken ct) { var sequencePool = connection.SequencePool; using ECDiffieHellman ecdh = ECDiffieHellman.Create(_ecCurve); // Send ECDH_INIT. using ECDiffieHellmanPublicKey myPublicKey = ecdh.PublicKey; ECPoint q_c = myPublicKey.ExportParameters().Q; { using var ecdhInitMsg = CreateEcdhInitMessage(sequencePool, q_c); await connection.SendPacketAsync(ecdhInitMsg, ct); } // Receive ECDH_REPLY. Packet exchangeInitMsg = input.ExchangeInitMsg; using Packet exchangeInitMsgDispose = exchangeInitMsg.IsEmpty ? (exchangeInitMsg = await connection.ReceivePacketAsync(ct)) : default(Packet); var ecdhReply = ParceEcdhReply(exchangeInitMsg); // TODO: Verify received key is valid. // TODO: Verify host key belongs to server. // Compute shared secret. // TODO: what types of exceptions can we get when creating the public key? ECParameters parameters = new ECParameters { Curve = _ecCurve, Q = ecdhReply.q_s }; using ECDiffieHellman peerEcdh = ECDiffieHellman.Create(parameters); using ECDiffieHellmanPublicKey peerPublicKey = peerEcdh.PublicKey; BigInteger sharedSecret = DeriveSharedSecret(ecdh, peerPublicKey); var publicHostKey = PublicKey.Read(ecdhReply.public_host_key, input.HostKeyAlgorithms); // Generate exchange hash. byte[] exchangeHash = CalculateExchangeHash(sequencePool, input.ConnectionInfo, input.ClientKexInitMsg, input.ServerKexInitMsg, ecdhReply.public_host_key, q_c, ecdhReply.q_s, sharedSecret); // Verify the server's signature. if (!publicHostKey.VerifySignature(exchangeHash, ecdhReply.exchange_hash_signature)) { throw new KeyExchangeFailedException("Signature does not match host key."); } byte[] sessionId = input.ConnectionInfo.SessionId ?? exchangeHash; byte[] initialIVC2S = Hash(sequencePool, sharedSecret, exchangeHash, (byte)'A', sessionId, input.InitialIVC2SLength); byte[] initialIVS2C = Hash(sequencePool, sharedSecret, exchangeHash, (byte)'B', sessionId, input.InitialIVS2CLength); byte[] encryptionKeyC2S = Hash(sequencePool, sharedSecret, exchangeHash, (byte)'C', sessionId, input.EncryptionKeyC2SLength); byte[] encryptionKeyS2C = Hash(sequencePool, sharedSecret, exchangeHash, (byte)'D', sessionId, input.EncryptionKeyS2CLength); byte[] integrityKeyC2S = Hash(sequencePool, sharedSecret, exchangeHash, (byte)'E', sessionId, input.IntegrityKeyC2SLength); byte[] integrityKeyS2C = Hash(sequencePool, sharedSecret, exchangeHash, (byte)'F', sessionId, input.IntegrityKeyS2CLength); return(new KeyExchangeOutput(exchangeHash, initialIVS2C, encryptionKeyS2C, integrityKeyS2C, initialIVC2S, encryptionKeyC2S, integrityKeyC2S)); }
// Serializes an ECDiffieHellmanPublicKey to an ECC public key blob // "ECDiffieHellmanPublicKey.ToByteArray() doesn't have a (standards-)defined export // format. The version used by ECDiffieHellmanPublicKeyCng is Windows-specific" // from https://github.com/dotnet/runtime/issues/27276 // => ECDiffieHellmanPublicKey.ToByteArray() is not supported in Unix internal static byte[] ECDHPublicKeyToECCKeyBlob(ECDiffieHellmanPublicKey publicKey) { byte[] keyBlob = new byte[ECCPublicKeyBlob.Size]; // Set magic number Array.Copy(KeyBlobMagicNumber.ECDHPublicP384, 0, keyBlob, 0, 4); // Set key size keyBlob[4] = (byte)ECCPublicKeyBlob.KeySize; ECPoint ecPoint = publicKey.ExportParameters().Q; Debug.Assert(ecPoint.X.Length == ECCPublicKeyBlob.KeySize && ecPoint.Y.Length == ECCPublicKeyBlob.KeySize, $"ECDH public key was not the expected length. Actual (X): {ecPoint.X.Length}. Actual (Y): {ecPoint.Y.Length} Expected: {ECCPublicKeyBlob.Size}"); // Copy x and y coordinates to key blob Array.Copy(ecPoint.X, 0, keyBlob, ECCPublicKeyBlob.HeaderSize, ECCPublicKeyBlob.KeySize); Array.Copy(ecPoint.Y, 0, keyBlob, ECCPublicKeyBlob.HeaderSize + ECCPublicKeyBlob.KeySize, ECCPublicKeyBlob.KeySize); return(keyBlob); }
private byte[]? DeriveSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey, System.Security.Cryptography.IncrementalHash?hash) { PublicKey otherPartyNSecPublicKey; if (otherPartyPublicKey is X25519PublicKey x25519PublicKey) { otherPartyNSecPublicKey = x25519PublicKey.publicKey; } else { var parameters = otherPartyPublicKey.ExportParameters(); otherPartyNSecPublicKey = NSec.Cryptography.PublicKey.Import(KeyAgreementAlgorithm.X25519, parameters.Q.X, KeyBlobFormat.RawPublicKey); } Debug.Assert(this.privateKey != null); Debug.Assert(hash != null); using var sharedSecret = KeyAgreementAlgorithm.X25519.Agree(this.privateKey, otherPartyNSecPublicKey); // NSec doesn't offer a way to export the shared secret. Unfortunately it also doesn't provide // the correct key derivation function so we have to resort to using the private API. var memoryHandle = (SafeHandle?)typeof(SharedSecret).GetProperty("Handle", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetMethod?.Invoke(sharedSecret, null); Debug.Assert(memoryHandle != null); byte[] secretBytes = new byte[32]; try { Marshal.Copy(memoryHandle.DangerousGetHandle(), secretBytes, 0, 32); hash.AppendData(secretBytes); } finally { CryptographicOperations.ZeroMemory(secretBytes); } return(null); }
public override ECParameters ExportParameters() => _wrapped.ExportParameters();