summary refs log tree commit diff
path: root/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs')
-rw-r--r--crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs314
1 files changed, 314 insertions, 0 deletions
diff --git a/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs b/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs
new file mode 100644
index 000000000..a918d3483
--- /dev/null
+++ b/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs
@@ -0,0 +1,314 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.Sec;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Pkcs;
+
+namespace Org.BouncyCastle.Utilities.SSH
+{
+    public class OpenSSHPrivateKeyUtil
+    {
+        private OpenSSHPrivateKeyUtil()
+        {
+
+        }
+
+        /**
+         * Magic value for proprietary OpenSSH private key.
+         **/
+        static readonly byte[] AUTH_MAGIC = Strings.ToByteArray("openssh-key-v1\0"); // C string so null terminated
+
+        /**
+         * Encode a cipher parameters into an OpenSSH private key.
+         * This does not add headers like ----BEGIN RSA PRIVATE KEY----
+         *
+         * @param parameters the cipher parameters.
+         * @return a byte array
+         */
+        public static byte[] EncodePrivateKey(AsymmetricKeyParameter parameters)
+        {
+            if (parameters == null)
+            {
+                throw new ArgumentException("parameters is null");
+            }
+
+            if (parameters is RsaPrivateCrtKeyParameters || parameters is ECPrivateKeyParameters)
+            {
+                PrivateKeyInfo pInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(parameters);
+                return pInfo.ParsePrivateKey().GetEncoded();
+            }
+            else if (parameters is DsaPrivateKeyParameters dsaPrivateKey)
+            {
+                DsaParameters dsaparameters = dsaPrivateKey.Parameters;
+
+                Asn1EncodableVector vec = new Asn1EncodableVector
+                {
+                    new DerInteger(0),
+                    new DerInteger(dsaparameters.P),
+                    new DerInteger(dsaparameters.Q),
+                    new DerInteger(dsaparameters.G)
+                };
+
+                // public key = g.modPow(x, p);
+                BigInteger pubKey = dsaparameters.P.ModPow(dsaPrivateKey.X, dsaparameters.P);
+                vec.Add(new DerInteger(pubKey));
+                vec.Add(new DerInteger(dsaPrivateKey.X));
+                try
+                {
+                    return new DerSequence(vec).GetEncoded();
+                }
+                catch (Exception ex)
+                {
+                    throw new InvalidOperationException("unable to encode DSAPrivateKeyParameters " + ex.Message);
+                }
+            }
+            else if (parameters is Ed25519PrivateKeyParameters ed25519PrivateKey)
+            {
+                Ed25519PublicKeyParameters publicKeyParameters = ed25519PrivateKey.GeneratePublicKey();
+
+                SSHBuilder builder = new SSHBuilder();
+                builder.WriteBytes(AUTH_MAGIC);
+                builder.WriteString("none");    // cipher name
+                builder.WriteString("none");    // KDF name
+                builder.WriteString("");        // KDF options
+
+                builder.U32(1); // Number of keys
+
+                {
+                    byte[] pkEncoded = OpenSSHPublicKeyUtil.EncodePublicKey(publicKeyParameters);
+                    builder.WriteBlock(pkEncoded);
+                }
+
+                {
+                    SSHBuilder pkBuild = new SSHBuilder();
+
+                    int checkint = CryptoServicesRegistrar.GetSecureRandom().NextInt();
+                    pkBuild.U32((uint)checkint);
+                    pkBuild.U32((uint)checkint);
+
+                    pkBuild.WriteString("ssh-ed25519");
+
+                    // Public key (as part of private key pair)
+                    byte[] pubKeyEncoded = publicKeyParameters.GetEncoded();
+                    pkBuild.WriteBlock(pubKeyEncoded);
+
+                    // The private key in SSH is 64 bytes long and is the concatenation of the private and the public keys
+                    pkBuild.WriteBlock(Arrays.Concatenate(ed25519PrivateKey.GetEncoded(), pubKeyEncoded));
+
+                    // Comment for this private key (empty)
+                    pkBuild.WriteString("");    
+
+                    builder.WriteBlock(pkBuild.GetPaddedBytes());
+                }
+
+                return builder.GetBytes();
+            }
+
+            throw new ArgumentException("unable to convert " + parameters.GetType().Name + " to openssh private key");
+
+        }
+
+        /**
+         * Parse a private key.
+         * <p>
+         * This method accepts the body of the OpenSSH private key.
+         * The easiest way to extract the body is to use PemReader, for example:
+         * <p>
+         * byte[] blob = new PemReader([reader]).readPemObject().getContent();
+         * CipherParameters params = parsePrivateKeyBlob(blob);
+         *
+         * @param blob The key.
+         * @return A cipher parameters instance.
+         */
+        public static AsymmetricKeyParameter ParsePrivateKeyBlob(byte[] blob)
+        {
+            AsymmetricKeyParameter result = null;
+
+            if (blob[0] == 0x30)
+            {
+                Asn1Sequence sequence = Asn1Sequence.GetInstance(blob);
+
+                if (sequence.Count == 6)
+                {
+                    if (AllIntegers(sequence) && ((DerInteger)sequence[0]).PositiveValue.Equals(BigIntegers.Zero))
+                    {
+                        // length of 6 and all Integers -- DSA
+                        result = new DsaPrivateKeyParameters(
+                            ((DerInteger)sequence[5]).PositiveValue,
+                            new DsaParameters(
+                                ((DerInteger)sequence[1]).PositiveValue,
+                                ((DerInteger)sequence[2]).PositiveValue,
+                                ((DerInteger)sequence[3]).PositiveValue)
+                        );
+                    }
+                }
+                else if (sequence.Count == 9)
+                {
+                    if (AllIntegers(sequence) && ((DerInteger)sequence[0]).PositiveValue.Equals(BigIntegers.Zero))
+                    {
+                        // length of 8 and all Integers -- RSA
+                        RsaPrivateKeyStructure rsaPrivateKey = RsaPrivateKeyStructure.GetInstance(sequence);
+
+                        result = new RsaPrivateCrtKeyParameters(
+                            rsaPrivateKey.Modulus,
+                            rsaPrivateKey.PublicExponent,
+                            rsaPrivateKey.PrivateExponent,
+                            rsaPrivateKey.Prime1,
+                            rsaPrivateKey.Prime2,
+                            rsaPrivateKey.Exponent1,
+                            rsaPrivateKey.Exponent2,
+                            rsaPrivateKey.Coefficient);
+                    }
+                }
+                else if (sequence.Count == 4)
+                {
+                    if (sequence[3] is Asn1TaggedObject && sequence[2] is Asn1TaggedObject)
+                    {
+                        ECPrivateKeyStructure ecPrivateKey = ECPrivateKeyStructure.GetInstance(sequence);
+                        DerObjectIdentifier curveOID = DerObjectIdentifier.GetInstance(ecPrivateKey.GetParameters());
+                        X9ECParameters x9Params = ECNamedCurveTable.GetByOid(curveOID);
+                        result = new ECPrivateKeyParameters(
+                            ecPrivateKey.GetKey(),
+                            new ECNamedDomainParameters(
+                                curveOID,
+                                x9Params));
+                    }
+                }
+            }
+            else
+            {
+                SSHBuffer kIn = new SSHBuffer(AUTH_MAGIC, blob);
+
+                String cipherName = kIn.ReadString();
+                if (!"none".Equals(cipherName))
+                {
+                    throw new InvalidOperationException("encrypted keys not supported");
+                }
+
+                // KDF name
+                kIn.SkipBlock();
+
+                // KDF options
+                kIn.SkipBlock();
+
+                int publicKeyCount = kIn.ReadU32();
+                if (publicKeyCount != 1)
+                {
+                    throw new InvalidOperationException("multiple keys not supported");
+                }
+
+                // Burn off public key.
+                OpenSSHPublicKeyUtil.ParsePublicKey(kIn.ReadBlock());
+
+                byte[] privateKeyBlock = kIn.ReadPaddedBlock();
+
+                if (kIn.HasRemaining())
+                {
+                    throw new InvalidOperationException("decoded key has trailing data");
+                }
+
+                SSHBuffer pkIn = new SSHBuffer(privateKeyBlock);
+                int check1 = pkIn.ReadU32();
+                int check2 = pkIn.ReadU32();
+
+                if (check1 != check2)
+                {
+                    throw new InvalidOperationException("private key check values are not the same");
+                }
+
+                String keyType = pkIn.ReadString();
+
+                if ("ssh-ed25519".Equals(keyType))
+                {
+                    // Public key
+                    pkIn.ReadBlock();
+                    // Private key value..
+                    byte[] edPrivateKey = pkIn.ReadBlock();
+                    if (edPrivateKey.Length != Ed25519PrivateKeyParameters.KeySize + Ed25519PublicKeyParameters.KeySize)
+                    {
+                        throw new InvalidOperationException("private key value of wrong length");
+                    }
+
+                    result = new Ed25519PrivateKeyParameters(edPrivateKey, 0);
+                }
+                else if (keyType.StartsWith("ecdsa"))
+                {
+                    DerObjectIdentifier oid = SSHNamedCurves.GetByName(Strings.FromByteArray(pkIn.ReadBlock())) ?? 
+                        throw new InvalidOperationException("OID not found for: " + keyType);
+                    X9ECParameters curveParams = NistNamedCurves.GetByOid(oid) ?? throw new InvalidOperationException("Curve not found for: " + oid);
+
+                    // Skip public key.
+                    pkIn.ReadBlock();
+                    byte[] privKey = pkIn.ReadBlock();
+
+                    result = new ECPrivateKeyParameters(new BigInteger(1, privKey),
+                        new ECNamedDomainParameters(oid, curveParams));
+                }
+                else if (keyType.StartsWith("ssh-rsa"))
+                {
+                    BigInteger modulus = new BigInteger(1, pkIn.ReadBlock());
+                    BigInteger pubExp = new BigInteger(1, pkIn.ReadBlock());
+                    BigInteger privExp = new BigInteger(1, pkIn.ReadBlock());
+                    BigInteger coef = new BigInteger(1, pkIn.ReadBlock());
+                    BigInteger p = new BigInteger(1, pkIn.ReadBlock());
+                    BigInteger q = new BigInteger(1, pkIn.ReadBlock());
+
+                    BigInteger pSub1 = p.Subtract(BigIntegers.One);
+                    BigInteger qSub1 = q.Subtract(BigIntegers.One);
+                    BigInteger dP = privExp.Remainder(pSub1);
+                    BigInteger dQ = privExp.Remainder(qSub1);
+
+                    result = new RsaPrivateCrtKeyParameters(
+                                    modulus,
+                                    pubExp,
+                                    privExp,
+                                    p,
+                                    q,
+                                    dP,
+                                    dQ,
+                                    coef);
+                }
+
+                // Comment for private key
+                pkIn.SkipBlock();
+
+                if (pkIn.HasRemaining())
+                {
+                    throw new ArgumentException("private key block has trailing data");
+                }
+            }
+
+            if (result == null)
+            {
+                throw new ArgumentException("unable to parse key");
+            }
+
+            return result;
+        }
+
+        /**
+         * allIntegers returns true if the sequence holds only DerInteger types.
+         **/
+        private static Boolean AllIntegers(Asn1Sequence sequence)
+        {
+            for (int t = 0; t < sequence.Count; t++)
+            {
+                if (!(sequence[t] is DerInteger))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+}
+}