/// <summary>
        /// Read private key parameters with OpenSSH format.
        /// </summary>
        /// <param name="passphrase">passphrase for decrypt the key file</param>
        /// <param name="keyPair">key pair</param>
        /// <param name="comment">comment or empty if it didn't exist</param>
        public void Load(string passphrase, out KeyPair keyPair, out string comment)
        {
            // Note:
            //  The structure of the private key format is described in "PROTOCOL.key" file in OpenSSH sources.

            String base64Text;

            using (StreamReader sreader = GetStreamReader()) {
                string line = sreader.ReadLine();
                if (line == null)
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (unexpected eof)");
                }

                if (line != PrivateKeyFileHeader.SSH2_OPENSSH_HEADER_OPENSSH)
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (unexpected key type)");
                }

                string footer = line.Replace("BEGIN", "END");

                StringBuilder buf = new StringBuilder();
                while (true)
                {
                    line = sreader.ReadLine();
                    if (line == null)
                    {
                        throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (unexpected eof)");
                    }
                    if (line == footer)
                    {
                        break;
                    }
                    buf.Append(line);
                }
                base64Text = buf.ToString();
            }

            byte[] blob = Base64.Decode(Encoding.ASCII.GetBytes(base64Text));

            int numKeys;    // number of keys

            byte[][] publicKeyBlobs;
            byte[]   privateKeyData;
            bool     decrypted;

            using (var blobStream = new MemoryStream(blob, false)) {
                if (!CheckMagic(blobStream))
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (unsupported format)");
                }

                string          cipherName = ReadString(blobStream);
                CipherAlgorithm?cipherAlgorithm;
                int             cipherKeySize;
                int             cipherIVSize;
                switch (cipherName)
                {
                case "none":
                    cipherAlgorithm = null;
                    cipherKeySize   = 0;
                    cipherIVSize    = 0;
                    break;

                case "aes128-cbc":
                    cipherAlgorithm = CipherAlgorithm.AES128;
                    cipherKeySize   = 16;
                    cipherIVSize    = 16;   // use block size
                    break;

                case "aes192-cbc":
                    cipherAlgorithm = CipherAlgorithm.AES192;
                    cipherKeySize   = 24;
                    cipherIVSize    = 16;   // use block size
                    break;

                case "aes256-cbc":
                    cipherAlgorithm = CipherAlgorithm.AES256;
                    cipherKeySize   = 32;
                    cipherIVSize    = 16;   // use block size
                    break;

                default:
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (unsupported cipher)");
                }

                string kdfName = ReadString(blobStream);
                if (kdfName != "bcrypt" && kdfName != "none")
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (unsupported kdf)");
                }

                if ((cipherName == "none") != (kdfName == "none"))
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (invalid cipher)");
                }

                byte[] kdfOptions = ReadBytes(blobStream);
                if (kdfOptions == null)
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (missing kdf options)");
                }
                byte[] kdfSalt;
                uint   kdfRounds;
                byte[] key;
                byte[] iv;
                if (kdfName == "none")
                {
                    kdfSalt   = null;
                    kdfRounds = 0;
                    key       = null;
                    iv        = null;
                }
                else
                {
                    if (!ReadKdfOptions(kdfOptions, out kdfSalt, out kdfRounds))
                    {
                        throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (invalid kdf options)");
                    }

                    if (passphrase == null || passphrase.Length == 0)
                    {
                        throw new SSHException(Strings.GetString("WrongPassphrase"));
                    }

                    // prepare decryption
                    Bcrypt bcrypt = new Bcrypt();
                    byte[] tmpkey = bcrypt.BcryptPbkdf(passphrase, kdfSalt, kdfRounds, cipherKeySize + cipherIVSize);
                    if (tmpkey == null)
                    {
                        throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (invalid kdf options)");
                    }
                    key = new byte[cipherKeySize];
                    Buffer.BlockCopy(tmpkey, 0, key, 0, cipherKeySize);
                    iv = new byte[cipherIVSize];
                    Buffer.BlockCopy(tmpkey, cipherKeySize, iv, 0, cipherIVSize);
                }

                if (!ReadInt32(blobStream, out numKeys) || numKeys < 0)
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (missing keys)");
                }

                publicKeyBlobs = new byte[numKeys][];
                for (int i = 0; i < numKeys; ++i)
                {
                    byte[] data = ReadBytes(blobStream);
                    if (data == null)
                    {
                        throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (missing public keys)");
                    }
                    publicKeyBlobs[i] = data;
                }

                privateKeyData = ReadBytes(blobStream);
                if (privateKeyData == null)
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (missing private keys)");
                }

                if (cipherAlgorithm.HasValue && key != null && iv != null)
                {
                    // decrypt private keys
                    Cipher cipher = CipherFactory.CreateCipher(SSHProtocol.SSH2, cipherAlgorithm.Value, key, iv);
                    cipher.Decrypt(privateKeyData, 0, privateKeyData.Length, privateKeyData, 0);
                    decrypted = true;
                }
                else
                {
                    decrypted = false;
                }
            }

            using (var privateKeysStream = new MemoryStream(privateKeyData, false)) {
                uint check1, check2;
                if (!ReadUInt32(privateKeysStream, out check1) ||
                    !ReadUInt32(privateKeysStream, out check2))
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (invalid private keys)");
                }
                if (check1 != check2)
                {
                    throw new SSHException(decrypted ?
                                           Strings.GetString("WrongPassphrase") : Strings.GetString("NotValidPrivateKeyFile"));
                }

                for (int i = 0; i < numKeys; ++i)
                {
                    string privateKeyType = ReadString(privateKeysStream);

                    using (var publicKeyBlobStream = new MemoryStream(publicKeyBlobs[i], false)) {
                        string publicKeyType = ReadString(publicKeyBlobStream);
                        if (publicKeyType != privateKeyType)
                        {
                            throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (key type unmatched)");
                        }

                        switch (privateKeyType)
                        {
                        case "ssh-ed25519": {
                            byte[] pk = ReadBytes(privateKeysStream);
                            if (pk == null)
                            {
                                throw new SSHException(Strings.GetString("NotValidPrivateKeyFile"));
                            }
                            byte[] sk = ReadBytes(privateKeysStream);
                            if (sk == null)
                            {
                                throw new SSHException(Strings.GetString("NotValidPrivateKeyFile"));
                            }
                            string cmnt = ReadString(privateKeysStream);           // comment
                            if (cmnt == null)
                            {
                                throw new SSHException(Strings.GetString("NotValidPrivateKeyFile"));
                            }

                            byte[] publicKey = ReadBytes(publicKeyBlobStream);
                            if (publicKey == null)
                            {
                                throw new SSHException(Strings.GetString("NotValidPrivateKeyFile"));
                            }

                            // sanity check
                            if (!AreEqual(publicKey, pk))
                            {
                                throw new SSHException(Strings.GetString("WrongPassphrase"));
                            }

                            // first 32 bytes of secret key is used as a private key for ed25519
                            byte[] privateKey = new byte[32];
                            if (sk.Length < privateKey.Length)
                            {
                                throw new SSHException(Strings.GetString("NotValidPrivateKeyFile"));
                            }
                            Buffer.BlockCopy(sk, 0, privateKey, 0, privateKey.Length);

                            var curve = EdwardsCurve.FindByAlgorithm(PublicKeyAlgorithm.ED25519);
                            if (curve != null)
                            {
                                var kp = new EDDSAKeyPair(curve, new EDDSAPublicKey(curve, publicKey), privateKey);
                                if (!kp.CheckKeyConsistency())
                                {
                                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile"));
                                }
                                keyPair = kp;
                                comment = cmnt;
                                return;
                            }
                        }
                        break;

                        default:
                            // unsupported key type; check the next key.
                            break;
                        }
                    }
                }
            }

            throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (supported private key was not found)");
        }
Exemple #2
0
        /// <summary>
        /// Read PuTTY SSH2 private key parameters.
        /// </summary>
        /// <param name="passphrase">passphrase for decrypt the key file</param>
        /// <param name="keyPair">key pair</param>
        /// <param name="comment">comment or empty if it didn't exist</param>
        public void Load(string passphrase, out KeyPair keyPair, out string comment)
        {
            if (keyFile == null)
            {
                throw new SSHException("A key file is not loaded yet");
            }

            int             version;
            string          keyTypeName;
            KeyType         keyType;
            string          encryptionName;
            CipherAlgorithm?encryption;

            byte[] publicBlob;
            byte[] privateBlob;
            string privateMac;
            string privateHash;

            using (StreamReader sreader = GetStreamReader())
            {
                //*** Read header and key type
                ReadHeaderLine(sreader, out version, out keyTypeName);

                if (keyTypeName == "ssh-rsa")
                {
                    keyType = KeyType.RSA;
                }
                else if (keyTypeName == "ssh-dss")
                {
                    keyType = KeyType.DSA;
                }
                else if (keyTypeName.StartsWith("ecdsa-sha2-"))
                {
                    keyType = KeyType.ECDSA;
                }
                else if (keyTypeName == "ssh-ed25519")
                {
                    keyType = KeyType.ED25519;
                }
                else
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (unexpected key type)");
                }

                //*** Read encryption
                ReadItemLine(sreader, "Encryption", out encryptionName);

                if (encryptionName == "aes256-cbc")
                {
                    encryption = CipherAlgorithm.AES256;
                }
                else if (encryptionName == "none")
                {
                    encryption = null;
                    passphrase = "";    // prevent HMAC error
                }
                else
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (unexpected encryption)");
                }

                //*** Read comment
                ReadItemLine(sreader, "Comment", out comment);

                //*** Read public lines
                string publicLinesStr;
                ReadItemLine(sreader, "Public-Lines", out publicLinesStr);
                int publicLines;
                if (!Int32.TryParse(publicLinesStr, out publicLines) || publicLines < 0)
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (invalid public lines)");
                }

                ReadBlob(sreader, publicLines, out publicBlob);

                //*** Read private lines
                string privateLinesStr;
                ReadItemLine(sreader, "Private-Lines", out privateLinesStr);
                int privateLines;
                if (!Int32.TryParse(privateLinesStr, out privateLines) || privateLines < 0)
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (invalid private lines)");
                }

                ReadBlob(sreader, privateLines, out privateBlob);

                //*** Read private MAC
                ReadPrivateMACLine(sreader, version, out privateMac, out privateHash);
            }

            if (encryption.HasValue)
            {
                byte[] key    = PuTTYPassphraseToKey(passphrase);
                byte[] iv     = new byte[16];
                Cipher cipher = CipherFactory.CreateCipher(SSHProtocol.SSH2, encryption.Value, key, iv);
                if (privateBlob.Length % cipher.BlockSize != 0)
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (invalid key data size)");
                }
                cipher.Decrypt(privateBlob, 0, privateBlob.Length, privateBlob, 0);
            }

            bool verified = Verify(version, privateMac, privateHash,
                                   passphrase, keyTypeName, encryptionName, comment, publicBlob, privateBlob);

            if (!verified)
            {
                if (encryption.HasValue)
                {
                    throw new SSHException(Strings.GetString("WrongPassphrase"));
                }
                else
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (HMAC verification failed)");
                }
            }

            if (keyType == KeyType.RSA)
            {
                SSH2DataReader reader = new SSH2DataReader(publicBlob);
                string         magic  = reader.ReadString();
                if (magic != "ssh-rsa")
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (missing magic)");
                }

                BigInteger e = reader.ReadMPInt();
                BigInteger n = reader.ReadMPInt();

                reader = new SSH2DataReader(privateBlob);
                BigInteger d    = reader.ReadMPInt();
                BigInteger p    = reader.ReadMPInt();
                BigInteger q    = reader.ReadMPInt();
                BigInteger iqmp = reader.ReadMPInt();

                BigInteger u = p.ModInverse(q);

                keyPair = new RSAKeyPair(e, d, n, u, p, q);
            }
            else if (keyType == KeyType.DSA)
            {
                SSH2DataReader reader = new SSH2DataReader(publicBlob);
                string         magic  = reader.ReadString();
                if (magic != "ssh-dss")
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (missing magic)");
                }

                BigInteger p = reader.ReadMPInt();
                BigInteger q = reader.ReadMPInt();
                BigInteger g = reader.ReadMPInt();
                BigInteger y = reader.ReadMPInt();

                reader = new SSH2DataReader(privateBlob);
                BigInteger x = reader.ReadMPInt();

                keyPair = new DSAKeyPair(p, g, q, y, x);
            }
            else if (keyType == KeyType.ECDSA)
            {
                SSH2DataReader reader        = new SSH2DataReader(publicBlob);
                string         algorithmName = reader.ReadString();
                string         curveName     = reader.ReadString();
                byte[]         publicKeyPt   = reader.ReadByteString();

                reader = new SSH2DataReader(privateBlob);
                BigInteger privateKey = reader.ReadMPInt();

                EllipticCurve curve = EllipticCurve.FindByName(curveName);
                if (curve == null)
                {
                    throw new SSHException(Strings.GetString("UnsupportedEllipticCurve") + " : " + curveName);
                }
                ECPoint publicKey;
                if (!ECPoint.Parse(publicKeyPt, curve, out publicKey))
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (parsing public key failed)");
                }

                keyPair = new ECDSAKeyPair(curve, new ECDSAPublicKey(curve, publicKey), privateKey);

                if (!((ECDSAKeyPair)keyPair).CheckKeyConsistency())
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (invalid key pair)");
                }
            }
            else if (keyType == KeyType.ED25519)
            {
                SSH2DataReader reader        = new SSH2DataReader(publicBlob);
                string         algorithmName = reader.ReadString();
                byte[]         publicKey     = reader.ReadByteString();

                reader = new SSH2DataReader(privateBlob);
                byte[] privateKey = reader.ReadByteString();

                EdwardsCurve curve = EdwardsCurve.FindByAlgorithm(PublicKeyAlgorithm.ED25519);
                if (curve == null)
                {
                    throw new SSHException(Strings.GetString("UnsupportedEllipticCurve"));
                }

                keyPair = new EDDSAKeyPair(curve, new EDDSAPublicKey(curve, publicKey), privateKey);

                if (!((EDDSAKeyPair)keyPair).CheckKeyConsistency())
                {
                    throw new SSHException(Strings.GetString("NotValidPrivateKeyFile") + " (invalid key pair)");
                }
            }
            else
            {
                throw new SSHException("Unknown file type. This should not happen.");
            }
        }