diff options
Diffstat (limited to 'crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs')
-rw-r--r-- | crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs | 314 |
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; + } +} +} |