| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- #nullable enable
- using System;
- using System.Collections.Generic;
- using System.Security.Cryptography;
- using System.Text;
- using Renci.SshNet.Common;
- using Renci.SshNet.Security;
- using Renci.SshNet.Security.Cryptography.Ciphers;
- using CipherMode = System.Security.Cryptography.CipherMode;
- namespace Renci.SshNet
- {
- public partial class PrivateKeyFile
- {
- private sealed class SSHCOM : IPrivateKeyParser
- {
- private readonly byte[] _data;
- private readonly string? _passPhrase;
- public SSHCOM(byte[] data, string? passPhrase)
- {
- _data = data;
- _passPhrase = passPhrase;
- }
- public Key Parse()
- {
- var reader = new SshDataReader(_data);
- var magicNumber = reader.ReadUInt32();
- if (magicNumber != 0x3f6ff9eb)
- {
- throw new SshException("Invalid SSH2 private key.");
- }
- _ = reader.ReadUInt32(); // Read total bytes length including magic number
- var keyType = reader.ReadString(SshData.Ascii);
- var ssh2CipherName = reader.ReadString(SshData.Ascii);
- var blobSize = (int)reader.ReadUInt32();
- byte[] keyData;
- if (ssh2CipherName == "none")
- {
- keyData = reader.ReadBytes(blobSize);
- }
- else if (ssh2CipherName == "3des-cbc")
- {
- if (string.IsNullOrEmpty(_passPhrase))
- {
- throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
- }
- var key = GetCipherKey(_passPhrase, 192 / 8);
- var ssh2Сipher = new TripleDesCipher(key, new byte[8], CipherMode.CBC, pkcs7Padding: false);
- keyData = ssh2Сipher.Decrypt(reader.ReadBytes(blobSize));
- }
- else
- {
- throw new SshException(string.Format("Cipher method '{0}' is not supported.", ssh2CipherName));
- }
- /*
- * TODO: Create two specific data types to avoid using SshDataReader class.
- */
- reader = new SshDataReader(keyData);
- var decryptedLength = reader.ReadUInt32();
- if (decryptedLength > blobSize - 4)
- {
- throw new SshException("Invalid passphrase.");
- }
- if (keyType.Contains("rsa"))
- {
- var exponent = reader.ReadBigIntWithBits(); // e
- var d = reader.ReadBigIntWithBits(); // d
- var modulus = reader.ReadBigIntWithBits(); // n
- var inverseQ = reader.ReadBigIntWithBits(); // u
- var q = reader.ReadBigIntWithBits(); // p
- var p = reader.ReadBigIntWithBits(); // q
- return new RsaKey(modulus, exponent, d, p, q, inverseQ);
- }
- else if (keyType.Contains("dsa"))
- {
- var zero = reader.ReadUInt32();
- if (zero != 0)
- {
- throw new SshException("Invalid private key");
- }
- var p = reader.ReadBigIntWithBits();
- var g = reader.ReadBigIntWithBits();
- var q = reader.ReadBigIntWithBits();
- var y = reader.ReadBigIntWithBits();
- var x = reader.ReadBigIntWithBits();
- return new DsaKey(p, q, g, y, x);
- }
- throw new NotSupportedException(string.Format("Key type '{0}' is not supported.", keyType));
- }
- private static byte[] GetCipherKey(string passphrase, int length)
- {
- var cipherKey = new List<byte>();
- #pragma warning disable CA1850 // Prefer static HashData method; We'll reuse the object on lower targets.
- using (var md5 = MD5.Create())
- {
- var passwordBytes = Encoding.UTF8.GetBytes(passphrase);
- var hash = md5.ComputeHash(passwordBytes);
- cipherKey.AddRange(hash);
- while (cipherKey.Count < length)
- {
- hash = passwordBytes.Concat(hash);
- hash = md5.ComputeHash(hash);
- cipherKey.AddRange(hash);
- }
- }
- #pragma warning restore CA1850 // Prefer static HashData method
- return cipherKey.ToArray().Take(length);
- }
- }
- }
- }
|