# < Summary

Information

Class: | Renci.SshNet.PrivateKeyFile
Assembly: | Renci.SshNet
File(s): | \home\appveyor\projects\ssh-net\src\Renci.SshNet\PrivateKeyFile.cs

Line coverage
85%

Covered lines: | 316
Uncovered lines: | 52
Coverable lines: | 368
Total lines: | 732
Line coverage: | 85.8%

Branch coverage
78%

Covered branches: | 83
Total branches: | 106
Branch coverage: | 78.3%

Method coverage

Feature is only available for sponsors

Upgrade to PRO version

# Metrics

Method | Branch coverage | Cyclomatic complexity | Line coverage
--- | --- | --- | ---
.cctor() | 100% | 1 | 100%
.ctor(...) | 100% | 1 | 16.66%
get_HostKeyAlgorithms() | 100% | 1 | 100%
get_Key() | 100% | 1 | 100%
.ctor(...) | 100% | 1 | 100%
.ctor(...) | 100% | 1 | 100%
.ctor(...) | 100% | 2 | 100%
.ctor(...) | 100% | 1 | 100%
Open(...) | 84.61% | 52 | 88.63%
GetCipherKey(...) | 100% | 2 | 100%
DecryptKey(...) | 62.5% | 8 | 77.77%
ParseOpenSshV1Key(...) | 69.44% | 36 | 77.55%
Dispose() | 100% | 1 | 100%
Dispose(...) | 83.33% | 6 | 86.66%
Finalize() | 100% | 1 | 100%
.ctor(...) | 100% | 1 | 100%
ReadUInt32() | 100% | 1 | 100%
ReadString(...) | 100% | 1 | 100%
ReadBytes(...) | 100% | 1 | 100%
ReadBytes() | 100% | 1 | 100%
ReadBigIntWithBits() | 100% | 1 | 100%
ReadBignum() | 100% | 1 | 100%
ReadBignum2() | 100% | 1 | 100%
LoadData() | 100% | 1 | 100%
SaveData() | 100% | 1 | 0%

# File(s)

## \home\appveyor\projects\ssh-net\src\Renci.SshNet\PrivateKeyFile.cs

| # | Line |  | Line coverage
--- | --- | --- | --- | ---
|  | `1` |  | `using System;`
|  | `2` |  | `using System.Collections.Generic;`
|  | `3` |  | `using System.Diagnostics;`
|  | `4` |  | `using System.Globalization;`
|  | `5` |  | `using System.IO;`
|  | `6` |  | `using System.Security.Cryptography;`
|  | `7` |  | `using System.Text;`
|  | `8` |  | `using System.Text.RegularExpressions;`
|  | `9` |  | ``
|  | `10` |  | `using Renci.SshNet.Abstractions;`
|  | `11` |  | `using Renci.SshNet.Common;`
|  | `12` |  | `using Renci.SshNet.Security;`
|  | `13` |  | `using Renci.SshNet.Security.Cryptography;`
|  | `14` |  | `using Renci.SshNet.Security.Cryptography.Ciphers;`
|  | `15` |  | `using Renci.SshNet.Security.Cryptography.Ciphers.Modes;`
|  | `16` |  | `using Renci.SshNet.Security.Cryptography.Ciphers.Paddings;`
|  | `17` |  | ``
|  | `18` |  | `namespace Renci.SshNet`
|  | `19` |  | `{`
|  | `20` |  | `/// <summary>`
|  | `21` |  | `/// Represents private key information.`
|  | `22` |  | `/// </summary>`
|  | `23` |  | `/// <example>`
|  | `24` |  | `/// <code source="..\..\src\Renci.SshNet.Tests\Data\Key.RSA.txt" language="Text" title="Private RSA key example"`
|  | `25` |  | `/// </example>`
|  | `26` |  | `/// <remarks>`
|  | `27` |  | `/// <para>`
|  | `28` |  | `/// The following private keys are supported:`
|  | `29` |  | `/// <list type="bullet">`
|  | `30` |  | `/// <item>`
|  | `31` |  | `/// <description>RSA in OpenSSL PEM, ssh.com and OpenSSH key format</description>`
|  | `32` |  | `/// </item>`
|  | `33` |  | `/// <item>`
|  | `34` |  | `/// <description>DSA in OpenSSL PEM and ssh.com format</description>`
|  | `35` |  | `/// </item>`
|  | `36` |  | `/// <item>`
|  | `37` |  | `/// <description>ECDSA 256/384/521 in OpenSSL PEM and OpenSSH key format</description>`
|  | `38` |  | `/// </item>`
|  | `39` |  | `/// <item>`
|  | `40` |  | `/// <description>ED25519 in OpenSSH key format</description>`
|  | `41` |  | `/// </item>`
|  | `42` |  | `/// </list>`
|  | `43` |  | `/// </para>`
|  | `44` |  | `/// <para>`
|  | `45` |  | `/// The following encryption algorithms are supported:`
|  | `46` |  | `/// <list type="bullet">`
|  | `47` |  | `/// <item>`
|  | `48` |  | `/// <description>DES-EDE3-CBC</description>`
|  | `49` |  | `/// </item>`
|  | `50` |  | `/// <item>`
|  | `51` |  | `/// <description>DES-EDE3-CFB</description>`
|  | `52` |  | `/// </item>`
|  | `53` |  | `/// <item>`
|  | `54` |  | `/// <description>DES-CBC</description>`
|  | `55` |  | `/// </item>`
|  | `56` |  | `/// <item>`
|  | `57` |  | `/// <description>AES-128-CBC</description>`
|  | `58` |  | `/// </item>`
|  | `59` |  | `/// <item>`
|  | `60` |  | `/// <description>AES-192-CBC</description>`
|  | `61` |  | `/// </item>`
|  | `62` |  | `/// <item>`
|  | `63` |  | `/// <description>AES-256-CBC</description>`
|  | `64` |  | `/// </item>`
|  | `65` |  | `/// </list>`
|  | `66` |  | `/// </para>`
|  | `67` |  | `/// </remarks>`
|  | `68` |  | `public class PrivateKeyFile : IPrivateKeySource, IDisposable`
|  | `69` |  | `{`
| 4 | `70` |  | `private static readonly Regex PrivateKeyRegex = new Regex(@"^-+ *BEGIN (?<keyName>\w+( \w+)*) PRIVATE KEY *-+\r?`
| 4 | `71` |  | `RegexOptions.Compiled | RegexOptions.Multiline | Regex`
|  | `72` |  | ``
| 604 | `73` |  | `private readonly List<HostAlgorithm> _hostAlgorithms = new List<HostAlgorithm>();`
|  | `74` |  | `private Key _key;`
|  | `75` |  | `private bool _isDisposed;`
|  | `76` |  | ``
|  | `77` |  | `/// <summary>`
|  | `78` |  | `/// Gets the supported host algorithms for this key file.`
|  | `79` |  | `/// </summary>`
|  | `80` |  | `public IReadOnlyCollection<HostAlgorithm> HostKeyAlgorithms`
|  | `81` |  | `{`
|  | `82` |  | `get`
| 848 | `83` |  | `{`
| 848 | `84` |  | `return _hostAlgorithms;`
| 848 | `85` |  | `}`
|  | `86` |  | `}`
|  | `87` |  | ``
|  | `88` |  | `/// <summary>`
|  | `89` |  | `/// Gets the key.`
|  | `90` |  | `/// </summary>`
|  | `91` |  | `public Key Key`
|  | `92` |  | `{`
|  | `93` |  | `get`
| 30 | `94` |  | `{`
| 30 | `95` |  | `return _key;`
| 30 | `96` |  | `}`
|  | `97` |  | `}`
|  | `98` |  | ``
|  | `99` |  | `/// <summary>`
|  | `100` |  | `/// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.`
|  | `101` |  | `/// </summary>`
|  | `102` |  | `/// <param name="key">The key.</param>`
| 0 | `103` |  | `public PrivateKeyFile(Key key)`
| 0 | `104` |  | `{`
| 0 | `105` |  | `_key = key;`
| 0 | `106` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm(key.ToString(), key));`
| 0 | `107` |  | `}`
|  | `108` |  | ``
|  | `109` |  | `/// <summary>`
|  | `110` |  | `/// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.`
|  | `111` |  | `/// </summary>`
|  | `112` |  | `/// <param name="privateKey">The private key.</param>`
| 108 | `113` |  | `public PrivateKeyFile(Stream privateKey)`
| 108 | `114` |  | `{`
| 108 | `115` |  | `Open(privateKey, passPhrase: null);`
| 105 | `116` |  | `Debug.Assert(_hostAlgorithms.Count > 0, $"{nameof(HostKeyAlgorithms)} is not set.");`
| 105 | `117` |  | `}`
|  | `118` |  | ``
|  | `119` |  | `/// <summary>`
|  | `120` |  | `/// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.`
|  | `121` |  | `/// </summary>`
|  | `122` |  | `/// <param name="fileName">Name of the file.</param>`
|  | `123` |  | `/// <exception cref="ArgumentNullException"><paramref name="fileName"/> is <see langword="null"/> or empty.</exc`
|  | `124` |  | `/// <remarks>`
|  | `125` |  | `/// This method calls <see cref="File.Open(string, FileMode)"/> internally, this method does not catch exception`
|  | `126` |  | `/// </remarks>`
|  | `127` |  | `public PrivateKeyFile(string fileName)`
| 9 | `128` |  | `: this(fileName, passPhrase: null)`
| 3 | `129` |  | `{`
| 3 | `130` |  | `}`
|  | `131` |  | ``
|  | `132` |  | `/// <summary>`
|  | `133` |  | `/// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.`
|  | `134` |  | `/// </summary>`
|  | `135` |  | `/// <param name="fileName">Name of the file.</param>`
|  | `136` |  | `/// <param name="passPhrase">The pass phrase.</param>`
|  | `137` |  | `/// <exception cref="ArgumentNullException"><paramref name="fileName"/> is <see langword="null"/> or empty, or <`
|  | `138` |  | `/// <remarks>`
|  | `139` |  | `/// This method calls <see cref="File.Open(string, FileMode)"/> internally, this method does not catch exception`
|  | `140` |  | `/// </remarks>`
| 30 | `141` |  | `public PrivateKeyFile(string fileName, string passPhrase)`
| 30 | `142` |  | `{`
| 30 | `143` |  | `if (string.IsNullOrEmpty(fileName))`
| 12 | `144` |  | `{`
| 12 | `145` |  | `throw new ArgumentNullException(nameof(fileName));`
|  | `146` |  | `}`
|  | `147` |  | ``
| 18 | `148` |  | `using (var keyFile = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))`
| 18 | `149` |  | `{`
| 18 | `150` |  | `Open(keyFile, passPhrase);`
| 12 | `151` |  | `}`
|  | `152` |  | ``
| 12 | `153` |  | `Debug.Assert(_hostAlgorithms.Count > 0, $"{nameof(HostKeyAlgorithms)} is not set.");`
| 12 | `154` |  | `}`
|  | `155` |  | ``
|  | `156` |  | `/// <summary>`
|  | `157` |  | `/// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.`
|  | `158` |  | `/// </summary>`
|  | `159` |  | `/// <param name="privateKey">The private key.</param>`
|  | `160` |  | `/// <param name="passPhrase">The pass phrase.</param>`
|  | `161` |  | `/// <exception cref="ArgumentNullException"><paramref name="privateKey"/> or <paramref name="passPhrase"/> is <s`
| 466 | `162` |  | `public PrivateKeyFile(Stream privateKey, string passPhrase)`
| 466 | `163` |  | `{`
| 466 | `164` |  | `Open(privateKey, passPhrase);`
|  | `165` |  | ``
| 453 | `166` |  | `Debug.Assert(_hostAlgorithms.Count > 0, $"{nameof(HostKeyAlgorithms)} is not set.");`
| 453 | `167` |  | `}`
|  | `168` |  | ``
|  | `169` |  | `/// <summary>`
|  | `170` |  | `/// Opens the specified private key.`
|  | `171` |  | `/// </summary>`
|  | `172` |  | `/// <param name="privateKey">The private key.</param>`
|  | `173` |  | `/// <param name="passPhrase">The pass phrase.</param>`
|  | `174` |  | `private void Open(Stream privateKey, string passPhrase)`
| 592 | `175` |  | `{`
| 592 | `176` |  | `if (privateKey is null)`
| 6 | `177` |  | `{`
| 6 | `178` |  | `throw new ArgumentNullException(nameof(privateKey));`
|  | `179` |  | `}`
|  | `180` |  | ``
|  | `181` |  | `Match privateKeyMatch;`
|  | `182` |  | ``
| 586 | `183` |  | `using (var sr = new StreamReader(privateKey))`
| 586 | `184` |  | `{`
| 586 | `185` |  | `var text = sr.ReadToEnd();`
| 586 | `186` |  | `privateKeyMatch = PrivateKeyRegex.Match(text);`
| 586 | `187` |  | `}`
|  | `188` |  | ``
| 586 | `189` |  | `if (!privateKeyMatch.Success)`
| 0 | `190` |  | `{`
| 0 | `191` |  | `throw new SshException("Invalid private key file.");`
|  | `192` |  | `}`
|  | `193` |  | ``
| 586 | `194` |  | `var keyName = privateKeyMatch.Result("${keyName}");`
| 586 | `195` |  | `var cipherName = privateKeyMatch.Result("${cipherName}");`
| 586 | `196` |  | `var salt = privateKeyMatch.Result("${salt}");`
| 586 | `197` |  | `var data = privateKeyMatch.Result("${data}");`
|  | `198` |  | ``
| 586 | `199` |  | `var binaryData = Convert.FromBase64String(data);`
|  | `200` |  | ``
|  | `201` |  | `byte[] decryptedData;`
|  | `202` |  | ``
| 586 | `203` |  | `if (!string.IsNullOrEmpty(cipherName) && !string.IsNullOrEmpty(salt))`
| 48 | `204` |  | `{`
| 48 | `205` |  | `if (string.IsNullOrEmpty(passPhrase))`
| 7 | `206` |  | `{`
| 7 | `207` |  | `throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");`
|  | `208` |  | `}`
|  | `209` |  | ``
| 41 | `210` |  | `var binarySalt = new byte[salt.Length / 2];`
| 1250 | `211` |  | `for (var i = 0; i < binarySalt.Length; i++)`
| 584 | `212` |  | `{`
| 584 | `213` |  | `binarySalt[i] = Convert.ToByte(salt.Substring(i * 2, 2), 16);`
| 584 | `214` |  | `}`
|  | `215` |  | ``
|  | `216` |  | `CipherInfo cipher;`
| 41 | `217` |  | `switch (cipherName)`
|  | `218` |  | `{`
|  | `219` |  | `case "DES-EDE3-CBC":`
| 6 | `220` |  | `cipher = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CbcCipherMode(iv), new PK`
| 3 | `221` |  | `break;`
|  | `222` |  | `case "DES-EDE3-CFB":`
| 6 | `223` |  | `cipher = new CipherInfo(192, (key, iv) => new TripleDesCipher(key, new CfbCipherMode(iv), new PK`
| 3 | `224` |  | `break;`
|  | `225` |  | `case "DES-CBC":`
| 6 | `226` |  | `cipher = new CipherInfo(64, (key, iv) => new DesCipher(key, new CbcCipherMode(iv), new PKCS7Padd`
| 3 | `227` |  | `break;`
|  | `228` |  | `case "AES-128-CBC":`
| 50 | `229` |  | `cipher = new CipherInfo(128, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding`
| 25 | `230` |  | `break;`
|  | `231` |  | `case "AES-192-CBC":`
| 6 | `232` |  | `cipher = new CipherInfo(192, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding`
| 3 | `233` |  | `break;`
|  | `234` |  | `case "AES-256-CBC":`
| 8 | `235` |  | `cipher = new CipherInfo(256, (key, iv) => new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding`
| 4 | `236` |  | `break;`
|  | `237` |  | `default:`
| 0 | `238` |  | `throw new SshException(string.Format(CultureInfo.InvariantCulture, "Private key cipher \"{0}\" i`
|  | `239` |  | `}`
|  | `240` |  | ``
| 41 | `241` |  | `decryptedData = DecryptKey(cipher, binaryData, passPhrase, binarySalt);`
| 41 | `242` |  | `}`
|  | `243` |  | `else`
| 538 | `244` |  | `{`
| 538 | `245` |  | `decryptedData = binaryData;`
| 538 | `246` |  | `}`
|  | `247` |  | ``
| 579 | `248` |  | `switch (keyName)`
|  | `249` |  | `{`
|  | `250` |  | `case "RSA":`
| 499 | `251` |  | `var rsaKey = new RsaKey(decryptedData);`
| 499 | `252` |  | `_key = rsaKey;`
|  | `253` |  | `#pragma warning disable CA2000 // Dispose objects before losing scope`
| 499 | `254` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(rsaKey, HashA`
| 499 | `255` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(rsaKey, HashA`
|  | `256` |  | `#pragma warning restore CA2000 // Dispose objects before losing scope`
| 499 | `257` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key));`
| 499 | `258` |  | `break;`
|  | `259` |  | `case "DSA":`
| 0 | `260` |  | `_key = new DsaKey(decryptedData);`
| 0 | `261` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm("ssh-dss", _key));`
| 0 | `262` |  | `break;`
|  | `263` |  | `case "EC":`
| 19 | `264` |  | `_key = new EcdsaKey(decryptedData);`
| 19 | `265` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm(_key.ToString(), _key));`
| 19 | `266` |  | `break;`
|  | `267` |  | `case "OPENSSH":`
| 33 | `268` |  | `_key = ParseOpenSshV1Key(decryptedData, passPhrase);`
| 33 | `269` |  | `if (_key is RsaKey parsedRsaKey)`
| 6 | `270` |  | `{`
|  | `271` |  | `#pragma warning disable CA2000 // Dispose objects before losing scope`
| 6 | `272` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(parsedRsa`
| 6 | `273` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(parsedRsa`
|  | `274` |  | `#pragma warning restore CA2000 // Dispose objects before losing scope`
| 6 | `275` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key));`
| 6 | `276` |  | `}`
|  | `277` |  | `else`
| 27 | `278` |  | `{`
| 27 | `279` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm(_key.ToString(), _key));`
| 27 | `280` |  | `}`
|  | `281` |  | ``
| 33 | `282` |  | `break;`
|  | `283` |  | `case "SSH2 ENCRYPTED":`
| 28 | `284` |  | `var reader = new SshDataReader(decryptedData);`
| 28 | `285` |  | `var magicNumber = reader.ReadUInt32();`
| 28 | `286` |  | `if (magicNumber != 0x3f6ff9eb)`
| 0 | `287` |  | `{`
| 0 | `288` |  | `throw new SshException("Invalid SSH2 private key.");`
|  | `289` |  | `}`
|  | `290` |  | ``
| 28 | `291` |  | `_ = reader.ReadUInt32(); // Read total bytes length including magic number`
| 28 | `292` |  | `var keyType = reader.ReadString(SshData.Ascii);`
| 28 | `293` |  | `var ssh2CipherName = reader.ReadString(SshData.Ascii);`
| 28 | `294` |  | `var blobSize = (int)reader.ReadUInt32();`
|  | `295` |  | ``
|  | `296` |  | `byte[] keyData;`
| 28 | `297` |  | `if (ssh2CipherName == "none")`
| 12 | `298` |  | `{`
| 12 | `299` |  | `keyData = reader.ReadBytes(blobSize);`
| 12 | `300` |  | `}`
| 16 | `301` |  | `else if (ssh2CipherName == "3des-cbc")`
| 16 | `302` |  | `{`
| 16 | `303` |  | `if (string.IsNullOrEmpty(passPhrase))`
| 6 | `304` |  | `{`
| 6 | `305` |  | `throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empt`
|  | `306` |  | `}`
|  | `307` |  | ``
| 10 | `308` |  | `var key = GetCipherKey(passPhrase, 192 / 8);`
| 10 | `309` |  | `var ssh2Сipher = new TripleDesCipher(key, new CbcCipherMode(new byte[8]), new PKCS7Padding());`
| 10 | `310` |  | `keyData = ssh2Сipher.Decrypt(reader.ReadBytes(blobSize));`
| 10 | `311` |  | `}`
|  | `312` |  | `else`
| 0 | `313` |  | `{`
| 0 | `314` |  | `throw new SshException(string.Format("Cipher method '{0}' is not supported.", cipherName));`
|  | `315` |  | `}`
|  | `316` |  | ``
|  | `317` |  | `/*`
|  | `318` |  | `* TODO: Create two specific data types to avoid using SshDataReader class.`
|  | `319` |  | `*/`
|  | `320` |  | ``
| 22 | `321` |  | `reader = new SshDataReader(keyData);`
|  | `322` |  | ``
| 22 | `323` |  | `var decryptedLength = reader.ReadUInt32();`
|  | `324` |  | ``
| 22 | `325` |  | `if (decryptedLength > blobSize - 4)`
| 3 | `326` |  | `{`
| 3 | `327` |  | `throw new SshException("Invalid passphrase.");`
|  | `328` |  | `}`
|  | `329` |  | ``
| 19 | `330` |  | `if (keyType == "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}")`
| 6 | `331` |  | `{`
| 6 | `332` |  | `var exponent = reader.ReadBigIntWithBits(); // e`
| 6 | `333` |  | `var d = reader.ReadBigIntWithBits(); // d`
| 6 | `334` |  | `var modulus = reader.ReadBigIntWithBits(); // n`
| 6 | `335` |  | `var inverseQ = reader.ReadBigIntWithBits(); // u`
| 6 | `336` |  | `var q = reader.ReadBigIntWithBits(); // p`
| 6 | `337` |  | `var p = reader.ReadBigIntWithBits(); // q`
| 6 | `338` |  | `var decryptedRsaKey = new RsaKey(modulus, exponent, d, p, q, inverseQ);`
| 6 | `339` |  | `_key = decryptedRsaKey;`
|  | `340` |  | `#pragma warning disable CA2000 // Dispose objects before losing scope`
| 6 | `341` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-512", _key, new RsaDigitalSignature(decrypted`
| 6 | `342` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(decrypted`
|  | `343` |  | `#pragma warning restore CA2000 // Dispose objects before losing scope`
| 6 | `344` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm("ssh-rsa", _key));`
| 6 | `345` |  | `}`
| 13 | `346` |  | `else if (keyType == "dl-modp{sign{dsa-nist-sha1},dh{plain}}")`
| 13 | `347` |  | `{`
| 13 | `348` |  | `var zero = reader.ReadUInt32();`
| 13 | `349` |  | `if (zero != 0)`
| 0 | `350` |  | `{`
| 0 | `351` |  | `throw new SshException("Invalid private key");`
|  | `352` |  | `}`
|  | `353` |  | ``
| 13 | `354` |  | `var p = reader.ReadBigIntWithBits();`
| 13 | `355` |  | `var g = reader.ReadBigIntWithBits();`
| 13 | `356` |  | `var q = reader.ReadBigIntWithBits();`
| 13 | `357` |  | `var y = reader.ReadBigIntWithBits();`
| 13 | `358` |  | `var x = reader.ReadBigIntWithBits();`
| 13 | `359` |  | `_key = new DsaKey(p, q, g, y, x);`
| 13 | `360` |  | `_hostAlgorithms.Add(new KeyHostAlgorithm("ssh-dss", _key));`
| 13 | `361` |  | `}`
|  | `362` |  | `else`
| 0 | `363` |  | `{`
| 0 | `364` |  | `throw new NotSupportedException(string.Format("Key type '{0}' is not supported.", keyType));`
|  | `365` |  | `}`
|  | `366` |  | ``
| 19 | `367` |  | `break;`
|  | `368` |  | `default:`
| 0 | `369` |  | `throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Key '{0}' is not supporte`
|  | `370` |  | `}`
| 570 | `371` |  | `}`
|  | `372` |  | ``
|  | `373` |  | `private static byte[] GetCipherKey(string passphrase, int length)`
| 10 | `374` |  | `{`
| 10 | `375` |  | `var cipherKey = new List<byte>();`
|  | `376` |  | ``
| 10 | `377` |  | `using (var md5 = CryptoAbstraction.CreateMD5())`
| 10 | `378` |  | `{`
| 10 | `379` |  | `var passwordBytes = Encoding.UTF8.GetBytes(passphrase);`
|  | `380` |  | ``
| 10 | `381` |  | `var hash = md5.ComputeHash(passwordBytes);`
| 10 | `382` |  | `cipherKey.AddRange(hash);`
|  | `383` |  | ``
| 20 | `384` |  | `while (cipherKey.Count < length)`
| 10 | `385` |  | `{`
| 10 | `386` |  | `hash = passwordBytes.Concat(hash);`
| 10 | `387` |  | `hash = md5.ComputeHash(hash);`
| 10 | `388` |  | `cipherKey.AddRange(hash);`
| 10 | `389` |  | `}`
| 10 | `390` |  | `}`
|  | `391` |  | ``
| 10 | `392` |  | `return cipherKey.ToArray().Take(length);`
| 10 | `393` |  | `}`
|  | `394` |  | ``
|  | `395` |  | `/// <summary>`
|  | `396` |  | `/// Decrypts encrypted private key file data.`
|  | `397` |  | `/// </summary>`
|  | `398` |  | `/// <param name="cipherInfo">The cipher info.</param>`
|  | `399` |  | `/// <param name="cipherData">Encrypted data.</param>`
|  | `400` |  | `/// <param name="passPhrase">Decryption pass phrase.</param>`
|  | `401` |  | `/// <param name="binarySalt">Decryption binary salt.</param>`
|  | `402` |  | `/// <returns>Decrypted byte array.</returns>`
|  | `403` |  | `/// <exception cref="ArgumentNullException"><paramref name="cipherInfo" />, <paramref name="cipherData" />, <par`
|  | `404` |  | `private static byte[] DecryptKey(CipherInfo cipherInfo, byte[] cipherData, string passPhrase, byte[] binarySalt)`
| 41 | `405` |  | `{`
| 41 | `406` |  | `if (cipherInfo is null)`
| 0 | `407` |  | `{`
| 0 | `408` |  | `throw new ArgumentNullException(nameof(cipherInfo));`
|  | `409` |  | `}`
|  | `410` |  | ``
| 41 | `411` |  | `if (cipherData is null)`
| 0 | `412` |  | `{`
| 0 | `413` |  | `throw new ArgumentNullException(nameof(cipherData));`
|  | `414` |  | `}`
|  | `415` |  | ``
| 41 | `416` |  | `if (binarySalt is null)`
| 0 | `417` |  | `{`
| 0 | `418` |  | `throw new ArgumentNullException(nameof(binarySalt));`
|  | `419` |  | `}`
|  | `420` |  | ``
| 41 | `421` |  | `var cipherKey = new List<byte>();`
|  | `422` |  | ``
| 41 | `423` |  | `using (var md5 = CryptoAbstraction.CreateMD5())`
| 41 | `424` |  | `{`
| 41 | `425` |  | `var passwordBytes = Encoding.UTF8.GetBytes(passPhrase);`
|  | `426` |  | ``
|  | `427` |  | `// Use 8 bytes binary salt`
| 41 | `428` |  | `var initVector = passwordBytes.Concat(binarySalt.Take(8));`
|  | `429` |  | ``
| 41 | `430` |  | `var hash = md5.ComputeHash(initVector);`
| 41 | `431` |  | `cipherKey.AddRange(hash);`
|  | `432` |  | ``
| 54 | `433` |  | `while (cipherKey.Count < cipherInfo.KeySize / 8)`
| 13 | `434` |  | `{`
| 13 | `435` |  | `hash = hash.Concat(initVector);`
| 13 | `436` |  | `hash = md5.ComputeHash(hash);`
| 13 | `437` |  | `cipherKey.AddRange(hash);`
| 13 | `438` |  | `}`
| 41 | `439` |  | `}`
|  | `440` |  | ``
| 41 | `441` |  | `var cipher = cipherInfo.Cipher(cipherKey.ToArray(), binarySalt);`
|  | `442` |  | ``
| 41 | `443` |  | `return cipher.Decrypt(cipherData);`
| 41 | `444` |  | `}`
|  | `445` |  | ``
|  | `446` |  | `/// <summary>`
|  | `447` |  | `/// Parses an OpenSSH V1 key file (i.e. ED25519 key) according to the the key spec:`
|  | `448` |  | `/// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key.`
|  | `449` |  | `/// </summary>`
|  | `450` |  | `/// <param name="keyFileData">The key file data (i.e. base64 encoded data between the header/footer).</param>`
|  | `451` |  | `/// <param name="passPhrase">Passphrase or <see langword="null"/> if there isn't one.</param>`
|  | `452` |  | `/// <returns>`
|  | `453` |  | `/// The OpenSSH V1 key.`
|  | `454` |  | `/// </returns>`
|  | `455` |  | `private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)`
| 33 | `456` |  | `{`
| 33 | `457` |  | `var keyReader = new SshDataReader(keyFileData);`
|  | `458` |  | ``
|  | `459` |  | `// check magic header`
| 33 | `460` |  | `var authMagic = Encoding.UTF8.GetBytes("openssh-key-v1\0");`
| 33 | `461` |  | `var keyHeaderBytes = keyReader.ReadBytes(authMagic.Length);`
| 33 | `462` |  | `if (!authMagic.IsEqualTo(keyHeaderBytes))`
| 0 | `463` |  | `{`
| 0 | `464` |  | `throw new SshException("This openssh key does not contain the 'openssh-key-v1' format magic header");`
|  | `465` |  | `}`
|  | `466` |  | ``
|  | `467` |  | `// cipher will be "aes256-cbc" if using a passphrase, "none" otherwise`
| 33 | `468` |  | `var cipherName = keyReader.ReadString(Encoding.UTF8);`
|  | `469` |  | ``
|  | `470` |  | `// key derivation function (kdf): bcrypt or nothing`
| 33 | `471` |  | `var kdfName = keyReader.ReadString(Encoding.UTF8);`
|  | `472` |  | ``
|  | `473` |  | `// kdf options length: 24 if passphrase, 0 if no passphrase`
| 33 | `474` |  | `var kdfOptionsLen = (int)keyReader.ReadUInt32();`
| 33 | `475` |  | `byte[] salt = null;`
| 33 | `476` |  | `var rounds = 0;`
| 33 | `477` |  | `if (kdfOptionsLen > 0)`
| 18 | `478` |  | `{`
| 18 | `479` |  | `var saltLength = (int) keyReader.ReadUInt32();`
| 18 | `480` |  | `salt = keyReader.ReadBytes(saltLength);`
| 18 | `481` |  | `rounds = (int) keyReader.ReadUInt32();`
| 18 | `482` |  | `}`
|  | `483` |  | ``
|  | `484` |  | `// number of public keys, only supporting 1 for now`
| 33 | `485` |  | `var numberOfPublicKeys = (int)keyReader.ReadUInt32();`
| 33 | `486` |  | `if (numberOfPublicKeys != 1)`
| 0 | `487` |  | `{`
| 0 | `488` |  | `throw new SshException("At this time only one public key in the openssh key is supported.");`
|  | `489` |  | `}`
|  | `490` |  | ``
|  | `491` |  | `// read public key in ssh-format, but we dont need it`
| 33 | `492` |  | `_ = keyReader.ReadString(Encoding.UTF8);`
|  | `493` |  | ``
|  | `494` |  | `// possibly encrypted private key`
| 33 | `495` |  | `var privateKeyLength = (int) keyReader.ReadUInt32();`
| 33 | `496` |  | `var privateKeyBytes = keyReader.ReadBytes(privateKeyLength);`
|  | `497` |  | ``
|  | `498` |  | `// decrypt private key if necessary`
| 33 | `499` |  | `if (cipherName != "none")`
| 18 | `500` |  | `{`
| 18 | `501` |  | `if (string.IsNullOrEmpty(passPhrase))`
| 0 | `502` |  | `{`
| 0 | `503` |  | `throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");`
|  | `504` |  | `}`
|  | `505` |  | ``
| 18 | `506` |  | `if (string.IsNullOrEmpty(kdfName) || kdfName != "bcrypt")`
| 0 | `507` |  | `{`
| 0 | `508` |  | `throw new SshException("kdf " + kdfName + " is not supported for openssh key file");`
|  | `509` |  | `}`
|  | `510` |  | ``
|  | `511` |  | `// inspired by the SSHj library (https://github.com/hierynomus/sshj)`
|  | `512` |  | `// apply the kdf to derive a key and iv from the passphrase`
| 18 | `513` |  | `var passPhraseBytes = Encoding.UTF8.GetBytes(passPhrase);`
| 18 | `514` |  | `var keyiv = new byte[48];`
| 18 | `515` |  | `new BCrypt().Pbkdf(passPhraseBytes, salt, rounds, keyiv);`
| 18 | `516` |  | `var key = new byte[32];`
| 18 | `517` |  | `Array.Copy(keyiv, 0, key, 0, 32);`
| 18 | `518` |  | `var iv = new byte[16];`
| 18 | `519` |  | `Array.Copy(keyiv, 32, iv, 0, 16);`
|  | `520` |  | ``
|  | `521` |  | `AesCipher cipher;`
| 18 | `522` |  | `switch (cipherName)`
|  | `523` |  | `{`
|  | `524` |  | `case "aes256-cbc":`
| 0 | `525` |  | `cipher = new AesCipher(key, iv, AesCipherMode.CBC, pkcs7Padding: false);`
| 0 | `526` |  | `break;`
|  | `527` |  | `case "aes256-ctr":`
| 18 | `528` |  | `cipher = new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false);`
| 18 | `529` |  | `break;`
|  | `530` |  | `default:`
| 0 | `531` |  | `throw new SshException("Cipher '" + cipherName + "' is not supported for an OpenSSH key.");`
|  | `532` |  | `}`
|  | `533` |  | ``
|  | `534` |  | `try`
| 18 | `535` |  | `{`
| 18 | `536` |  | `privateKeyBytes = cipher.Decrypt(privateKeyBytes);`
| 18 | `537` |  | `}`
|  | `538` |  | `finally`
| 18 | `539` |  | `{`
| 18 | `540` |  | `cipher.Dispose();`
| 18 | `541` |  | `}`
| 18 | `542` |  | `}`
|  | `543` |  | ``
|  | `544` |  | `// validate private key length`
| 33 | `545` |  | `privateKeyLength = privateKeyBytes.Length;`
| 33 | `546` |  | `if (privateKeyLength % 8 != 0)`
| 0 | `547` |  | `{`
| 0 | `548` |  | `throw new SshException("The private key section must be a multiple of the block size (8)");`
|  | `549` |  | `}`
|  | `550` |  | ``
|  | `551` |  | `// now parse the data we called the private key, it actually contains the public key again`
|  | `552` |  | `// so we need to parse through it to get the private key bytes, plus there's some`
|  | `553` |  | `// validation we need to do.`
| 33 | `554` |  | `var privateKeyReader = new SshDataReader(privateKeyBytes);`
|  | `555` |  | ``
|  | `556` |  | `// check ints should match, they wouldn't match for example if the wrong passphrase was supplied`
| 33 | `557` |  | `var checkInt1 = (int) privateKeyReader.ReadUInt32();`
| 33 | `558` |  | `var checkInt2 = (int) privateKeyReader.ReadUInt32();`
| 33 | `559` |  | `if (checkInt1 != checkInt2)`
| 0 | `560` |  | `{`
| 0 | `561` |  | `throw new SshException(string.Format(CultureInfo.InvariantCulture,`
| 0 | `562` |  | `"The random check bytes of the OpenSSH key do not match ({0} <-> {1`
| 0 | `563` |  | `checkInt1.ToString(CultureInfo.InvariantCulture),`
| 0 | `564` |  | `checkInt2.ToString(CultureInfo.InvariantCulture)));`
|  | `565` |  | `}`
|  | `566` |  | ``
|  | `567` |  | `// key type`
| 33 | `568` |  | `var keyType = privateKeyReader.ReadString(Encoding.UTF8);`
|  | `569` |  | ``
|  | `570` |  | `Key parsedKey;`
|  | `571` |  | `byte[] publicKey;`
|  | `572` |  | `byte[] unencryptedPrivateKey;`
| 33 | `573` |  | `switch (keyType)`
|  | `574` |  | `{`
|  | `575` |  | `case "ssh-ed25519":`
|  | `576` |  | `// public key`
| 7 | `577` |  | `publicKey = privateKeyReader.ReadBignum2();`
|  | `578` |  | ``
|  | `579` |  | `// private key`
| 7 | `580` |  | `unencryptedPrivateKey = privateKeyReader.ReadBignum2();`
| 7 | `581` |  | `parsedKey = new ED25519Key(publicKey.Reverse(), unencryptedPrivateKey);`
| 7 | `582` |  | `break;`
|  | `583` |  | `case "ecdsa-sha2-nistp256":`
|  | `584` |  | `case "ecdsa-sha2-nistp384":`
|  | `585` |  | `case "ecdsa-sha2-nistp521":`
|  | `586` |  | `// curve`
| 20 | `587` |  | `var len = (int) privateKeyReader.ReadUInt32();`
| 20 | `588` |  | `var curve = Encoding.ASCII.GetString(privateKeyReader.ReadBytes(len));`
|  | `589` |  | ``
|  | `590` |  | `// public key`
| 20 | `591` |  | `publicKey = privateKeyReader.ReadBignum2();`
|  | `592` |  | ``
|  | `593` |  | `// private key`
| 20 | `594` |  | `unencryptedPrivateKey = privateKeyReader.ReadBignum2();`
| 20 | `595` |  | `parsedKey = new EcdsaKey(curve, publicKey, unencryptedPrivateKey.TrimLeadingZeros());`
| 20 | `596` |  | `break;`
|  | `597` |  | `case "ssh-rsa":`
| 6 | `598` |  | `var modulus = privateKeyReader.ReadBignum(); // n`
| 6 | `599` |  | `var exponent = privateKeyReader.ReadBignum(); // e`
| 6 | `600` |  | `var d = privateKeyReader.ReadBignum(); // d`
| 6 | `601` |  | `var inverseQ = privateKeyReader.ReadBignum(); // iqmp`
| 6 | `602` |  | `var p = privateKeyReader.ReadBignum(); // p`
| 6 | `603` |  | `var q = privateKeyReader.ReadBignum(); // q`
| 6 | `604` |  | `parsedKey = new RsaKey(modulus, exponent, d, p, q, inverseQ);`
| 6 | `605` |  | `break;`
|  | `606` |  | `default:`
| 0 | `607` |  | `throw new SshException("OpenSSH key type '" + keyType + "' is not supported.");`
|  | `608` |  | `}`
|  | `609` |  | ``
| 33 | `610` |  | `parsedKey.Comment = privateKeyReader.ReadString(Encoding.UTF8);`
|  | `611` |  | ``
|  | `612` |  | `// The list of privatekey/comment pairs is padded with the bytes 1, 2, 3, ...`
|  | `613` |  | `// until the total length is a multiple of the cipher block size.`
| 33 | `614` |  | `var padding = privateKeyReader.ReadBytes();`
| 666 | `615` |  | `for (var i = 0; i < padding.Length; i++)`
| 300 | `616` |  | `{`
| 300 | `617` |  | `if ((int) padding[i] != i + 1)`
| 0 | `618` |  | `{`
| 0 | `619` |  | `throw new SshException("Padding of openssh key format contained wrong byte at position: " +`
| 0 | `620` |  | `i.ToString(CultureInfo.InvariantCulture));`
|  | `621` |  | `}`
| 300 | `622` |  | `}`
|  | `623` |  | ``
| 33 | `624` |  | `return parsedKey;`
| 33 | `625` |  | `}`
|  | `626` |  | ``
|  | `627` |  | `/// <summary>`
|  | `628` |  | `/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.`
|  | `629` |  | `/// </summary>`
|  | `630` |  | `public void Dispose()`
| 3 | `631` |  | `{`
| 3 | `632` |  | `Dispose(disposing: true);`
| 3 | `633` |  | `GC.SuppressFinalize(this);`
| 3 | `634` |  | `}`
|  | `635` |  | ``
|  | `636` |  | `/// <summary>`
|  | `637` |  | `/// Releases unmanaged and - optionally - managed resources.`
|  | `638` |  | `/// </summary>`
|  | `639` |  | `/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langwor`
|  | `640` |  | `protected virtual void Dispose(bool disposing)`
| 604 | `641` |  | `{`
| 604 | `642` |  | `if (_isDisposed)`
| 0 | `643` |  | `{`
| 0 | `644` |  | `return;`
|  | `645` |  | `}`
|  | `646` |  | ``
| 604 | `647` |  | `if (disposing)`
| 3 | `648` |  | `{`
| 3 | `649` |  | `var key = _key;`
| 3 | `650` |  | `if (key != null)`
| 3 | `651` |  | `{`
| 3 | `652` |  | `((IDisposable) key).Dispose();`
| 3 | `653` |  | `_key = null;`
| 3 | `654` |  | `}`
|  | `655` |  | ``
| 3 | `656` |  | `_isDisposed = true;`
| 3 | `657` |  | `}`
| 604 | `658` |  | `}`
|  | `659` |  | ``
|  | `660` |  | `/// <summary>`
|  | `661` |  | `/// Finalizes an instance of the <see cref="PrivateKeyFile"/> class.`
|  | `662` |  | `/// </summary>`
|  | `663` |  | `~PrivateKeyFile()`
| 1202 | `664` |  | `{`
| 601 | `665` |  | `Dispose(disposing: false);`
| 1202 | `666` |  | `}`
|  | `667` |  | ``
|  | `668` |  | `private sealed class SshDataReader : SshData`
|  | `669` |  | `{`
| 116 | `670` |  | `public SshDataReader(byte[] data)`
| 116 | `671` |  | `{`
| 116 | `672` |  | `Load(data);`
| 116 | `673` |  | `}`
|  | `674` |  | ``
|  | `675` |  | `public new uint ReadUInt32()`
| 340 | `676` |  | `{`
| 340 | `677` |  | `return base.ReadUInt32();`
| 340 | `678` |  | `}`
|  | `679` |  | ``
|  | `680` |  | `public new string ReadString(Encoding encoding)`
| 221 | `681` |  | `{`
| 221 | `682` |  | `return base.ReadString(encoding);`
| 221 | `683` |  | `}`
|  | `684` |  | ``
|  | `685` |  | `public new byte[] ReadBytes(int length)`
| 126 | `686` |  | `{`
| 126 | `687` |  | `return base.ReadBytes(length);`
| 126 | `688` |  | `}`
|  | `689` |  | ``
|  | `690` |  | `public new byte[] ReadBytes()`
| 33 | `691` |  | `{`
| 33 | `692` |  | `return base.ReadBytes();`
| 33 | `693` |  | `}`
|  | `694` |  | ``
|  | `695` |  | `/// <summary>`
|  | `696` |  | `/// Reads next mpint data type from internal buffer where length specified in bits.`
|  | `697` |  | `/// </summary>`
|  | `698` |  | `/// <returns>mpint read.</returns>`
|  | `699` |  | `public BigInteger ReadBigIntWithBits()`
| 101 | `700` |  | `{`
| 101 | `701` |  | `var length = (int) base.ReadUInt32();`
|  | `702` |  | ``
| 101 | `703` |  | `length = (length + 7) / 8;`
|  | `704` |  | ``
| 101 | `705` |  | `var data = base.ReadBytes(length);`
| 101 | `706` |  | `var bytesArray = new byte[data.Length + 1];`
| 101 | `707` |  | `Buffer.BlockCopy(data, 0, bytesArray, 1, data.Length);`
|  | `708` |  | ``
| 101 | `709` |  | `return new BigInteger(bytesArray.Reverse());`
| 101 | `710` |  | `}`
|  | `711` |  | ``
|  | `712` |  | `public BigInteger ReadBignum()`
| 36 | `713` |  | `{`
| 36 | `714` |  | `return new BigInteger(ReadBignum2().Reverse());`
| 36 | `715` |  | `}`
|  | `716` |  | ``
|  | `717` |  | `public byte[] ReadBignum2()`
| 90 | `718` |  | `{`
| 90 | `719` |  | `var length = (int)base.ReadUInt32();`
| 90 | `720` |  | `return base.ReadBytes(length);`
| 90 | `721` |  | `}`
|  | `722` |  | ``
|  | `723` |  | `protected override void LoadData()`
| 116 | `724` |  | `{`
| 116 | `725` |  | `}`
|  | `726` |  | ``
|  | `727` |  | `protected override void SaveData()`
| 0 | `728` |  | `{`
| 0 | `729` |  | `}`
|  | `730` |  | `}`
|  | `731` |  | `}`
|  | `732` |  | `}`

# Methods/Properties

.cctor()
.ctor(Renci.SshNet.Security.Key)
get_HostKeyAlgorithms()
get_Key()
.ctor(System.IO.Stream)
.ctor(System.String)
.ctor(System.String,System.String)
.ctor(System.IO.Stream,System.String)
Open(System.IO.Stream,System.String)
GetCipherKey(System.String,System.Int32)
DecryptKey(Renci.SshNet.CipherInfo,System.Byte[],System.String,System.Byte[])
ParseOpenSshV1Key(System.Byte[],System.String)
Dispose()
Dispose(System.Boolean)
Finalize()
.ctor(System.Byte[])
ReadUInt32()
ReadString(System.Text.Encoding)
ReadBytes(System.Int32)
ReadBytes()
ReadBigIntWithBits()
ReadBignum()
ReadBignum2()
LoadData()
SaveData()