From d0d06afd4ab9dbd680fa30db70428f35a9442d12 Mon Sep 17 00:00:00 2001 From: Anh Vo Date: Mon, 27 Mar 2023 18:38:27 -0400 Subject: port OpenSSH key support from java api --- crypto/src/crypto/ec/CustomNamedCurves.cs | 12 +- crypto/src/pkcs/PrivateKeyInfoFactory.cs | 20 +- crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs | 314 +++++++++++++ crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs | 178 +++++++ crypto/src/util/ssh/SSHBuffer.cs | 150 ++++++ crypto/src/util/ssh/SSHBuilder.cs | 83 ++++ crypto/src/util/ssh/SSHNamedCurves.cs | 96 ++++ .../test/src/crypto/test/OpenSSHKeyParsingTests.cs | 521 +++++++++++++++++++++ 8 files changed, 1364 insertions(+), 10 deletions(-) create mode 100644 crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs create mode 100644 crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs create mode 100644 crypto/src/util/ssh/SSHBuffer.cs create mode 100644 crypto/src/util/ssh/SSHBuilder.cs create mode 100644 crypto/src/util/ssh/SSHNamedCurves.cs create mode 100644 crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs diff --git a/crypto/src/crypto/ec/CustomNamedCurves.cs b/crypto/src/crypto/ec/CustomNamedCurves.cs index d256dba73..2f6fdfeaa 100644 --- a/crypto/src/crypto/ec/CustomNamedCurves.cs +++ b/crypto/src/crypto/ec/CustomNamedCurves.cs @@ -782,14 +782,16 @@ namespace Org.BouncyCastle.Crypto.EC new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary curves = new Dictionary(); - private static readonly Dictionary names = + private static readonly Dictionary objIdToName = new Dictionary(); + private static readonly List names = new List(); private static void DefineCurve(string name, DerObjectIdentifier oid, X9ECParametersHolder holder) { objIds.Add(name, oid); - names.Add(oid, name); + objIdToName.Add(oid, name); curves.Add(oid, holder); + names.Add(name); } private static void DefineCurveAlias(string name, DerObjectIdentifier oid) @@ -902,7 +904,7 @@ namespace Org.BouncyCastle.Crypto.EC /// The OID for the curve. public static string GetName(DerObjectIdentifier oid) { - return CollectionUtilities.GetValueOrNull(names, oid); + return CollectionUtilities.GetValueOrNull(objIdToName, oid); } /// Look up the OID of the curve with the given name. @@ -912,10 +914,10 @@ namespace Org.BouncyCastle.Crypto.EC return CollectionUtilities.GetValueOrNull(objIds, name); } - /// Enumerate the available curve names in this registry. + /// Enumerate the available curve objIdToName in this registry. public static IEnumerable Names { - get { return CollectionUtilities.Proxy(objIds.Keys); } + get { return CollectionUtilities.Proxy(names); } } } } diff --git a/crypto/src/pkcs/PrivateKeyInfoFactory.cs b/crypto/src/pkcs/PrivateKeyInfoFactory.cs index d56831f35..b180e49f2 100644 --- a/crypto/src/pkcs/PrivateKeyInfoFactory.cs +++ b/crypto/src/pkcs/PrivateKeyInfoFactory.cs @@ -164,15 +164,25 @@ namespace Org.BouncyCastle.Pkcs else { X962Parameters x962; - if (priv.PublicKeyParamSet == null) + //if (priv.PublicKeyParamSet == null) + //{ + // X9ECParameters ecP = new X9ECParameters(dp.Curve, new X9ECPoint(dp.G, false), dp.N, dp.H, + // dp.GetSeed()); + // x962 = new X962Parameters(ecP); + //} + //else + //{ + // x962 = new X962Parameters(priv.PublicKeyParamSet); + //} + if (dp is ECNamedDomainParameters _dp) { - X9ECParameters ecP = new X9ECParameters(dp.Curve, new X9ECPoint(dp.G, false), dp.N, dp.H, - dp.GetSeed()); - x962 = new X962Parameters(ecP); + x962 = new X962Parameters(_dp.Name); } else { - x962 = new X962Parameters(priv.PublicKeyParamSet); + X9ECParameters ecP = new X9ECParameters(dp.Curve, new X9ECPoint(dp.G, false), dp.N, dp.H, + dp.GetSeed()); + x962 = new X962Parameters(ecP); } ec = new ECPrivateKeyStructure(orderBitLength, priv.D, publicKey, x962); 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. + *

+ * This method accepts the body of the OpenSSH private key. + * The easiest way to extract the body is to use PemReader, for example: + *

+ * 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; + } +} +} diff --git a/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs b/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs new file mode 100644 index 000000000..8f1fa8ec1 --- /dev/null +++ b/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs @@ -0,0 +1,178 @@ +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Org.BouncyCastle.Utilities.SSH +{ + public class OpenSSHPublicKeyUtil + { + private OpenSSHPublicKeyUtil() + { + + } + + private static readonly String RSA = "ssh-rsa"; + private static readonly String ECDSA = "ecdsa"; + private static readonly String ED_25519 = "ssh-ed25519"; + private static readonly String DSS = "ssh-dss"; + + /** + * Parse a public key. + *

+ * This method accepts the bytes that are Base64 encoded in an OpenSSH public key file. + * + * @param encoded The key. + * @return An AsymmetricKeyParameter instance. + */ + public static AsymmetricKeyParameter ParsePublicKey(byte[] encoded) + { + SSHBuffer buffer = new SSHBuffer(encoded); + return ParsePublicKey(buffer); + } + + /** + * Encode a public key from an AsymmetricKeyParameter instance. + * + * @param cipherParameters The key to encode. + * @return the key OpenSSH encoded. + * @throws IOException + */ + public static byte[] EncodePublicKey(AsymmetricKeyParameter cipherParameters) + { + if (cipherParameters == null) + { + throw new ArgumentException("cipherParameters was null."); + } + + if (cipherParameters is RsaKeyParameters) + { + if (cipherParameters.IsPrivate) + { + throw new ArgumentException("RSAKeyParamaters was for encryption"); + } + + RsaKeyParameters rsaPubKey = (RsaKeyParameters)cipherParameters; + + SSHBuilder builder = new SSHBuilder(); + builder.WriteString(RSA); + builder.WriteBigNum(rsaPubKey.Exponent); + builder.WriteBigNum(rsaPubKey.Modulus); + + return builder.GetBytes(); + + } + else if (cipherParameters is ECPublicKeyParameters ecPublicKey) + { + SSHBuilder builder = new SSHBuilder(); + + // + // checked for named curve parameters.. + // + String name = SSHNamedCurves.GetNameForParameters(ecPublicKey.Parameters); + + if (name == null) + { + throw new ArgumentException("unable to derive ssh curve name for " + ecPublicKey.Parameters.Curve.GetType().Name); + } + + builder.WriteString(ECDSA + "-sha2-" + name); // Magic + builder.WriteString(name); + builder.WriteBlock(ecPublicKey.Q.GetEncoded(false)); //Uncompressed + return builder.GetBytes(); + } + else if (cipherParameters is DsaPublicKeyParameters dsaPubKey) + { + DsaParameters dsaParams = dsaPubKey.Parameters; + + SSHBuilder builder = new SSHBuilder(); + builder.WriteString(DSS); + builder.WriteBigNum(dsaParams.P); + builder.WriteBigNum(dsaParams.Q); + builder.WriteBigNum(dsaParams.G); + builder.WriteBigNum(dsaPubKey.Y); + return builder.GetBytes(); + } + else if (cipherParameters is Ed25519PublicKeyParameters ed25519PublicKey) + { + SSHBuilder builder = new SSHBuilder(); + builder.WriteString(ED_25519); + builder.WriteBlock(ed25519PublicKey.GetEncoded()); + return builder.GetBytes(); + } + + throw new ArgumentException("unable to convert " + cipherParameters.GetType().Name + " to private key"); + } + + /** + * Parse a public key from an SSHBuffer instance. + * + * @param buffer containing the SSH public key. + * @return A CipherParameters instance. + */ + public static AsymmetricKeyParameter ParsePublicKey(SSHBuffer buffer) + { + AsymmetricKeyParameter result = null; + + string magic = buffer.ReadString(); + if (RSA.Equals(magic)) + { + BigInteger e = buffer.ReadBigNumPositive(); + BigInteger n = buffer.ReadBigNumPositive(); + result = new RsaKeyParameters(false, n, e); + } + else if (DSS.Equals(magic)) + { + BigInteger p = buffer.ReadBigNumPositive(); + BigInteger q = buffer.ReadBigNumPositive(); + BigInteger g = buffer.ReadBigNumPositive(); + BigInteger pubKey = buffer.ReadBigNumPositive(); + + result = new DsaPublicKeyParameters(pubKey, new DsaParameters(p, q, g)); + } + else if (magic.StartsWith(ECDSA)) + { + String curveName = buffer.ReadString(); + DerObjectIdentifier oid = SSHNamedCurves.GetByName(curveName); + X9ECParameters x9ECParameters = SSHNamedCurves.GetParameters(oid) ?? + throw new InvalidOperationException("unable to find curve for " + magic + " using curve name " + curveName); + var curve = x9ECParameters.Curve; + byte[] pointRaw = buffer.ReadBlock(); + + result = new ECPublicKeyParameters( + curve.DecodePoint(pointRaw), + new ECNamedDomainParameters(oid, x9ECParameters)); + } + else if (ED_25519.Equals(magic)) + { + byte[] pubKeyBytes = buffer.ReadBlock(); + if (pubKeyBytes.Length != Ed25519PublicKeyParameters.KeySize) + { + throw new InvalidOperationException("public key value of wrong length"); + } + + result = new Ed25519PublicKeyParameters(pubKeyBytes, 0); + } + + if (result == null) + { + throw new ArgumentException("unable to parse key"); + } + + if (buffer.HasRemaining()) + { + throw new ArgumentException("decoded key has trailing data"); + } + + return result; + } + } +} diff --git a/crypto/src/util/ssh/SSHBuffer.cs b/crypto/src/util/ssh/SSHBuffer.cs new file mode 100644 index 000000000..8d3c3f977 --- /dev/null +++ b/crypto/src/util/ssh/SSHBuffer.cs @@ -0,0 +1,150 @@ +using System; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Utilities.SSH +{ + public class SSHBuffer + { + private readonly byte[] buffer; + private int pos = 0; + + public SSHBuffer(byte[] magic, byte[] buffer) + { + this.buffer = buffer; + for (int i = 0; i != magic.Length; i++) + { + if (magic[i] != buffer[i]) + { + throw new ArgumentException("magic-number incorrect"); + } + } + + pos += magic.Length; + } + + public SSHBuffer(byte[] buffer) + { + this.buffer = buffer; + } + + public int ReadU32() + { + if (pos > (buffer.Length - 4)) + { + throw new ArgumentOutOfRangeException("4 bytes for U32 exceeds buffer."); + } + + int i = (buffer[pos++] & 0xFF) << 24; + i |= (buffer[pos++] & 0xFF) << 16; + i |= (buffer[pos++] & 0xFF) << 8; + i |= (buffer[pos++] & 0xFF); + + return i; + } + + public String ReadString() + { + return Strings.FromByteArray(ReadBlock()); + } + + public byte[] ReadBlock() + { + int len = ReadU32(); + if (len == 0) + { + return new byte[0]; + } + + if (pos > (buffer.Length - len)) + { + throw new ArgumentException("not enough data for block"); + } + + int start = pos; pos += len; + return Arrays.CopyOfRange(buffer, start, pos); + } + + public void SkipBlock() + { + int len = ReadU32(); + if (pos > (buffer.Length - len)) + { + throw new ArgumentException("not enough data for block"); + } + + pos += len; + } + + public byte[] ReadPaddedBlock() + { + return ReadPaddedBlock(8); + } + + public byte[] ReadPaddedBlock(int blockSize) + { + int len = ReadU32(); + if (len == 0) + { + return new byte[0]; + } + + if (pos > (buffer.Length - len)) + { + throw new ArgumentException("not enough data for block"); + } + + int align = len % blockSize; + if (0 != align) + { + throw new ArgumentException("missing padding"); + } + + int start = pos; pos += len; + int end = pos; + + if (len > 0) + { + // TODO If encryption is supported, should be constant-time + int lastByte = buffer[pos - 1] & 0xFF; + if (0 < lastByte && lastByte < blockSize) + { + int padCount = lastByte; + end -= padCount; + + for (int i = 1, padPos = end; i <= padCount; ++i, ++padPos) + { + if (i != (buffer[padPos] & 0xFF)) + { + throw new ArgumentException("incorrect padding"); + } + } + } + } + + return Arrays.CopyOfRange(buffer, start, end); + } + + public BigInteger ReadBigNumPositive() + { + int len = ReadU32(); + if (pos + len > buffer.Length) + { + throw new ArgumentException("not enough data for big num"); + } + + int start = pos; pos += len; + byte[] d = Arrays.CopyOfRange(buffer, start, pos); + return new BigInteger(1, d); + } + + public byte[] GetBuffer() + { + return Arrays.Clone(buffer); + } + + public Boolean HasRemaining() + { + return pos < buffer.Length; + } + } +} diff --git a/crypto/src/util/ssh/SSHBuilder.cs b/crypto/src/util/ssh/SSHBuilder.cs new file mode 100644 index 000000000..5fa92de4b --- /dev/null +++ b/crypto/src/util/ssh/SSHBuilder.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Utilities.SSH +{ + public class SSHBuilder + { + private readonly MemoryStream bos = new MemoryStream(); + + [CLSCompliant(false)] + public void U32(uint value) + { + bos.WriteByte(Convert.ToByte((value >> 24) & 0xFF)); + bos.WriteByte(Convert.ToByte((value >> 16) & 0xFF)); + bos.WriteByte(Convert.ToByte((value >> 8) & 0xFF)); + bos.WriteByte(Convert.ToByte(value & 0xFF)); + } + + public void WriteBigNum(BigInteger n) + { + WriteBlock(n.ToByteArray()); + } + + public void WriteBlock(byte[] value) + { + U32((uint)value.Length); + try + { + bos.Write(value, 0, value.Length); + } + catch (IOException e) + { + throw new InvalidOperationException(e.Message, e); + } + } + + public void WriteBytes(byte[] value) + { + try + { + bos.Write(value, 0, value.Length); + } + catch (IOException e) + { + throw new InvalidOperationException(e.Message, e); + } + } + + public void WriteString(String str) + { + WriteBlock(Strings.ToByteArray(str)); + } + + public byte[] GetBytes() + { + return bos.ToArray(); + } + + public byte[] GetPaddedBytes() + { + return GetPaddedBytes(8); + } + + public byte[] GetPaddedBytes(int blockSize) + { + int align = (int)bos.Length % blockSize; + if (0 != align) + { + int padCount = blockSize - align; + for (int i = 1; i <= padCount; ++i) + { + bos.WriteByte(Convert.ToByte(i)); + } + } + return bos.ToArray(); + } + } +} diff --git a/crypto/src/util/ssh/SSHNamedCurves.cs b/crypto/src/util/ssh/SSHNamedCurves.cs new file mode 100644 index 000000000..31c350128 --- /dev/null +++ b/crypto/src/util/ssh/SSHNamedCurves.cs @@ -0,0 +1,96 @@ +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.EC; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math.EC; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Org.BouncyCastle.Utilities.SSH +{ + public class SSHNamedCurves + { + private static readonly Dictionary OidMap = + new Dictionary + { + { "nistp256", SecObjectIdentifiers.SecP256r1 }, + { "nistp384", SecObjectIdentifiers.SecP384r1 }, + { "nistp521", SecObjectIdentifiers.SecP521r1 }, + { "nistk163", SecObjectIdentifiers.SecT163k1 }, + { "nistp192", SecObjectIdentifiers.SecP192r1 }, + { "nistp224", SecObjectIdentifiers.SecP224r1 }, + { "nistk233", SecObjectIdentifiers.SecT233k1 }, + { "nistb233", SecObjectIdentifiers.SecT233r1 }, + { "nistk283", SecObjectIdentifiers.SecT283k1 }, + { "nistk409", SecObjectIdentifiers.SecT409k1 }, + { "nistb409", SecObjectIdentifiers.SecT409r1 }, + { "nistt571", SecObjectIdentifiers.SecT571k1 } + }; + + + private static readonly Dictionary CurveNameToSSHName = + new Dictionary + { + {"secp256r1", "nistp256"}, + {"secp384r1", "nistp384"}, + {"secp521r1", "nistp521"}, + {"sect163k1", "nistk163"}, + {"secp192r1", "nistp192"}, + {"secp224r1", "nistp224"}, + {"sect233k1", "nistk233"}, + {"sect233r1", "nistb233"}, + {"sect283k1", "nistk283"}, + {"sect409k1", "nistk409"}, + {"sect409r1", "nistb409"}, + {"sect571k1", "nistt571"} + }; + + private static readonly Dictionary CurveMap = + CustomNamedCurves.Names.ToDictionary(k => CustomNamedCurves.GetByNameLazy(k).Curve, v => v); + + private static readonly Dictionary OidToName = + OidMap.ToDictionary(k => k.Value, v => v.Key); + + + public static DerObjectIdentifier GetByName(string sshName) + { + return OidMap[sshName]; + } + + public static X9ECParameters GetParameters(string sshName) + { + return NistNamedCurves.GetByOid(OidMap[sshName.ToLower()]); + } + + public static X9ECParameters GetParameters(DerObjectIdentifier oid) + { + return NistNamedCurves.GetByOid(oid); + } + + public static string GetName(DerObjectIdentifier oid) + { + return OidToName[oid]; + } + + public static string GetNameForParameters(ECDomainParameters parameters) + { + if (parameters is ECNamedDomainParameters) + { + return GetName(((ECNamedDomainParameters)parameters).Name); + } + + return GetNameForParameters(parameters.Curve); + } + + public static string GetNameForParameters(ECCurve curve) + { + return CurveNameToSSHName[CurveMap[curve]]; + } + } +} diff --git a/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs b/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs new file mode 100644 index 000000000..c347dcc4b --- /dev/null +++ b/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs @@ -0,0 +1,521 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Paddings; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities.Test; +using Org.BouncyCastle.Utilities.SSH; +using Org.BouncyCastle.Utilities.IO.Pem; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Crypto.Signers; +using System.Linq; + +namespace Org.BouncyCastle.Crypto.Tests +{ + [TestFixture] + public class OpenSSHKeyParsingTests : SimpleTest + { + private static SecureRandom secureRandom = new SecureRandom(); + + String rsa1024Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn\n" + + "NhAAAAAwEAAQAAAIEA37C9iHf9kS3ekS8xVE4p5/bmA7Yc37gXqN10W6c53FzVMiT9ZzVm\n" + + "GXqJCRTpLjlX4NgRGHK3nLwyrEhR5JmTrLAXfwb04y3AcdZWZwkZBiXR2rFToEnXNobrvG\n" + + "gmXEshBvCq6kUcGWf1FnW4av0kbVRkfiAjM1aMae1KIwlNMDcAAAIIVPY+b1T2Pm8AAAAH\n" + + "c3NoLXJzYQAAAIEA37C9iHf9kS3ekS8xVE4p5/bmA7Yc37gXqN10W6c53FzVMiT9ZzVmGX\n" + + "qJCRTpLjlX4NgRGHK3nLwyrEhR5JmTrLAXfwb04y3AcdZWZwkZBiXR2rFToEnXNobrvGgm\n" + + "XEshBvCq6kUcGWf1FnW4av0kbVRkfiAjM1aMae1KIwlNMDcAAAADAQABAAAAgCWqIc/HvH\n" + + "dkjNRPaPP0EVRQm1xGnsgAvGMfnscL+k8jhnZiChUFxcJGgqp3zeeNmkFuwDoonsgSXEns\n" + + "B3YBcf7SE//XNMGrGi2FAQTccoTm80NLY77wONST2DNPqxY5xTsTiOJx/DPnru84laq1ae\n" + + "t7WiNZCxsmuC0sPYDAG515AAAAQQDzeUo4QQbByJ9JVS0zcj26HKwGZSxxVb1Flq2Y+w0W\n" + + "E/4GuYvh3ujXlwEankjYUNGNI0/u0NCzuDPZzBx9LZdeAAAAQQD9TiakDmscY9Dd8bEBL+\n" + + "cAhCHrdxtx9ND/793cQNkpm10NL0Fz4jXQfn2/Z7nLFKmMzJlQXzUHH/itzWg9s0MlAAAA\n" + + "QQDiEe/BJMLRZ+94n98VCEr7E+eG2isQctxiAowH7o/wp5WAkFSD9W58dqUobuneXleG+F\n" + + "DAfXzFhYvNE+TdLXUrAAAADm1hcmtAYmFybmFjbGVzAQIDBA==\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + String rsa2048Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn\n" + + "NhAAAAAwEAAQAAAQEArxWa1zW+Uf0lUrYoL1yqgTYUT1TfUkfojrhguPB1s/1AEMj8sueu\n" + + "YDtLozZW/GB+KwO+nzC48CmqsCbCEOqalmdRIQCCQIBs776c0KLnhqzHCmj0Q+6gM0KvUG\n" + + "z8elzJ8LZuTj5xGRDvFxli4yl2M119X7K2JMci18N95rszioxDECSWg2Arvd25kMKBK5MA\n" + + "qJjosvxr46soRmxiAHeGzinoLXgpLh9axwySpJ0WVGPl079ZtaYs/XpSoh9HXqCgwnsVy9\n" + + "JscWbmtaAktjMw2zTfOvmFs9PVJXtXQRzP4nvtT6myK/7v8tPeg8yLnAot9erklHcUOEyb\n" + + "1LsOrk68+QAAA8j/Xs/E/17PxAAAAAdzc2gtcnNhAAABAQCvFZrXNb5R/SVStigvXKqBNh\n" + + "RPVN9SR+iOuGC48HWz/UAQyPyy565gO0ujNlb8YH4rA76fMLjwKaqwJsIQ6pqWZ1EhAIJA\n" + + "gGzvvpzQoueGrMcKaPRD7qAzQq9QbPx6XMnwtm5OPnEZEO8XGWLjKXYzXX1fsrYkxyLXw3\n" + + "3muzOKjEMQJJaDYCu93bmQwoErkwComOiy/GvjqyhGbGIAd4bOKegteCkuH1rHDJKknRZU\n" + + "Y+XTv1m1piz9elKiH0deoKDCexXL0mxxZua1oCS2MzDbNN86+YWz09Ule1dBHM/ie+1Pqb\n" + + "Ir/u/y096DzIucCi316uSUdxQ4TJvUuw6uTrz5AAAAAwEAAQAAAQBPpNBO3Y+51CHKQjp9\n" + + "cPXO2T7b54u+7h8H7S9ycU/ZlHY0LHlnGKTl+ZMqp2liXLKH9qgb2hoGha2ze64D6/RuPo\n" + + "lVLdoSZVkopdjHv5L6XFYekierTz1olAkT2L/xGYxzB0meJiFkeaOJKm8lTpMKQpjpk23v\n" + + "xPZAmBkJgFatyueHaVWGYp0KzUDpdMcS97R6CWCGrYlAUP3F1meC9+Sb3d94qxeqLZsgEn\n" + + "PYJs1Q7fyL4jYBYm9/pA9O5RLKMQwqY7Qln7l2XTyhavZCIxTmAa6lEf32yB3+EoQR+YEz\n" + + "eCXXSClbMcnnx83jYyV5uNxN27VJAlgeN7J2ZyJTLlKRAAAAgAUnKuxYaYezMWyBShwR4N\n" + + "eVAW8vT3CBxsMR/v3u6XmLTzjq4r0gKCxofnnj972uK0LvyTZ21/00MSl0KaAjJySl2hLj\n" + + "BNQA3TcDXnLEc5KcsKZdDhuWkHGmaoajDp/okfQd6CxuKaBKG/OFdbYqVgOOVeACUUWxT4\n" + + "NN4e3CxTWQAAAAgQDV3vzDCQanGAXMKZSxfHUU63Tmh+2NcB1I6Sb0/CwpBgLH1y0CTB9r\n" + + "c8TLSs6HoHx1lfzOp6Yj7BQ9CWHS94Mi+RYBF+SpaMLoZKqCU4Q3UWiHiOyPnMaohAdvRE\n" + + "gJkaY2OAkFaaCI31rwBrs6b5U/ErtRTUZNJEI7OCi6wDBfBwAAAIEA0ZKyuUW5+VFcTyuR\n" + + "1G0ky5uihtJryFCjA2fzu7tgobm0gsIgSDClp9TdMh5CDyJo0R9fQnH8Lki0Ku+jgc4X+a\n" + + "/XMw47d1iL7Hdu9NAJsplezKD5Unso4xJRXhLnXUT5FT8lSgwE+9xUBuILKUmZQa20ejKM\n" + + "20U6szOxEEclA/8AAAAObWFya0BiYXJuYWNsZXMBAgMEBQ==\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + String rsa3072Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "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\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + String rsa4096Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "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\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + String ecdsa256Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n" + + "1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS9VjynnoLGUcT2hXXPkFwGfbleI4Ln\n" + + "1kkgt2UgibKXw9NtesSqpKdEBDW5Kh2nmqLCIk+fdbsTGkxlfaYBtUrkAAAAqBQCER8UAh\n" + + "EfAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL1WPKeegsZRxPaF\n" + + "dc+QXAZ9uV4jgufWSSC3ZSCJspfD0216xKqkp0QENbkqHaeaosIiT591uxMaTGV9pgG1Su\n" + + "QAAAAgbAJJUVcjwwU/olgrxgINJ1DViX6GcCBhgeH8wAXiNKoAAAAObWFya0BiYXJuYWNs\n" + + "ZXMBAg==\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + String ecdsa384Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS\n" + + "1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQS0yKimt2kBeyNKUqNivPfSPBVyU4jH\n" + + "9+6hNsRIJG4NKRgKdIOIiOOLm6pGLUmwN4yDS+0ssdPxwRthQzL879HRtwbqAAb1ShK0CT\n" + + "rljAhk9+SUgrOqWnKL2Ngo1uU5KZgAAADYJC2IQSQtiEEAAAATZWNkc2Etc2hhMi1uaXN0\n" + + "cDM4NAAAAAhuaXN0cDM4NAAAAGEEtMioprdpAXsjSlKjYrz30jwVclOIx/fuoTbESCRuDS\n" + + "kYCnSDiIjji5uqRi1JsDeMg0vtLLHT8cEbYUMy/O/R0bcG6gAG9UoStAk65YwIZPfklIKz\n" + + "qlpyi9jYKNblOSmYAAAAMQChvecXe7PGUVG0Pz2IgM9f80YLXdarf98sRptbGSIPwu8KlW\n" + + "OlGv0Any5ue51/I5wAAAAObWFya0BiYXJuYWNsZXMB\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + String ecdsa521Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS\n" + + "1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA90An5exsl3UEU0d8fhqV8rgmoyzJ\n" + + "21sZYrjFV+bs583tbSIMYAapk8jSKtk+r1z48KQdsR9czydmy2yYbdXruXMBPdQrf+11BB\n" + + "dCs1E9iFet1UB8OruVeduD5dm0In7yJK1Qo18xe0NpOjOHeZ1ixAxdOt9zuolAlBTwZYth\n" + + "FMESME8AAAEQApLNRAKSzUQAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ\n" + + "AAAIUEAPdAJ+XsbJd1BFNHfH4alfK4JqMsydtbGWK4xVfm7OfN7W0iDGAGqZPI0irZPq9c\n" + + "+PCkHbEfXM8nZstsmG3V67lzAT3UK3/tdQQXQrNRPYhXrdVAfDq7lXnbg+XZtCJ+8iStUK\n" + + "NfMXtDaTozh3mdYsQMXTrfc7qJQJQU8GWLYRTBEjBPAAAAQgFHl5a1JDqcCeaAx84z3u/v\n" + + "z7dyVl4uohlQPaiZ+hhtbbUg6oLMnVGGjjmviR0C0aDzx0xDEsK8TseFd16mBWpOnAAAAA\n" + + "5tYXJrQGJhcm5hY2xlcwECAwQ=\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + + public static void main( + String[] args) + { + RunTest(new OpenSSHKeyParsingTests()); + } + + + public void TestDSA() + { + var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(Base64.Decode("AAAAB3NzaC1kc3MAAACBAJBB5+S4kZZYZLswaQ/zm3GM7YWmHsumwo/Xxu+z6Cg2l5PUoiBBZ4ET9EhhQuL2ja/zrCMCi0ZwiSRuSp36ayPrHLbNJb3VdOuJg8xExRa6F3YfVZfcTPUEKh6FU72fI31HrQmi4rpyHnWxL/iDX496ZG2Hdq6UkPISQpQwj4TtAAAAFQCP9TXcVahR/2rpfEhvdXR0PfhbRwAAAIBdXzAVqoOtb9zog6lNF1cGS1S06W9W/clvuwq2xF1s3bkoI/xUbFSc0IAPsGl2kcB61PAZqcop50lgpvYzt8cq/tbqz3ypq1dCQ0xdmJHj975QsRFax+w6xQ0kgpBhwcS2EOizKb+C+tRzndGpcDSoSMuVXp9i4wn5pJSTZxAYFQAAAIEAhQZc687zYxrEDR/1q6m4hw5GFxuVvLsC+bSHtMF0c11Qy4IPg7mBeP7K5Kq4WyJPtmZhuc5Bb12bJQR6qgd1uLn692fe1UK2kM6eWXBzhlzZ54BslfSKHGNN4qH+ln3Zaf/4rpKE7fvoinkrgkOZmj0PMx9D6wlpHKkXMUxeXtc=")); + + var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader("-----BEGIN DSA PRIVATE KEY-----\n" + + "MIIBuwIBAAKBgQCQQefkuJGWWGS7MGkP85txjO2Fph7LpsKP18bvs+goNpeT1KIg\n" + + "QWeBE/RIYULi9o2v86wjAotGcIkkbkqd+msj6xy2zSW91XTriYPMRMUWuhd2H1WX\n" + + "3Ez1BCoehVO9nyN9R60JouK6ch51sS/4g1+PemRth3aulJDyEkKUMI+E7QIVAI/1\n" + + "NdxVqFH/aul8SG91dHQ9+FtHAoGAXV8wFaqDrW/c6IOpTRdXBktUtOlvVv3Jb7sK\n" + + "tsRdbN25KCP8VGxUnNCAD7BpdpHAetTwGanKKedJYKb2M7fHKv7W6s98qatXQkNM\n" + + "XZiR4/e+ULERWsfsOsUNJIKQYcHEthDosym/gvrUc53RqXA0qEjLlV6fYuMJ+aSU\n" + + "k2cQGBUCgYEAhQZc687zYxrEDR/1q6m4hw5GFxuVvLsC+bSHtMF0c11Qy4IPg7mB\n" + + "eP7K5Kq4WyJPtmZhuc5Bb12bJQR6qgd1uLn692fe1UK2kM6eWXBzhlzZ54BslfSK\n" + + "HGNN4qH+ln3Zaf/4rpKE7fvoinkrgkOZmj0PMx9D6wlpHKkXMUxeXtcCFELnLOJ8\n" + + "D0akSCUFY/iDLo/KnOIH\n" + + "-----END DSA PRIVATE KEY-----\n")).ReadPemObject().Content); + + var signer = new DsaSigner(); + signer.Init(true, privSpec); + + byte[] originalMessage = new byte[10]; + secureRandom.NextBytes(originalMessage); + + BigInteger[] rs = signer.GenerateSignature(originalMessage); + + signer.Init(false, pubSpec); + + IsTrue("DSA test", signer.VerifySignature(originalMessage, rs[0], rs[1])); + } + + + public void TestECDSA_curvesFromSSHKeyGen() + { + var pairs = new Tuple []{ + Tuple.Create( + "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBbxKE+/DXstQZmwH7Wso8SUt8LvYoMQpxN/7INC0lMn7mNCbxJcSOCfucBuWOrdoFyFZUkGli2mzKj3hJlcPiI=", + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n" + + "1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQW8ShPvw17LUGZsB+1rKPElLfC72KD\n" + + "EKcTf+yDQtJTJ+5jQm8SXEjgn7nAbljq3aBchWVJBpYtpsyo94SZXD4iAAAAuKFclDShXJ\n" + + "Q0AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBbxKE+/DXstQZmw\n" + + "H7Wso8SUt8LvYoMQpxN/7INC0lMn7mNCbxJcSOCfucBuWOrdoFyFZUkGli2mzKj3hJlcPi\n" + + "IAAAAhAP4L/ciGBDF4HoQSvMaKM8svW4Ss0uYi7HkZ1sn/zCe0AAAAHW1lZ2Fud29vZHNA\n" + + "dHljaGUtMzI2NS5nYXRld2F5AQI=\n" + + "-----END OPENSSH PRIVATE KEY-----\n" + ), + Tuple.Create( + "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBOT0Cc/zauJsOWo/0P0sMNeyFI5Enz3+lKJtjWXQD7DpFgZmG5Ise8IXR5/ot7fo0kWlYQrye/uSmNmWBuDvOpBCHOnyR6Kaej36qoOO/gwbH+mezSYXSxCTA9Qb8VzxLA==", + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS\n" + + "1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTk9AnP82ribDlqP9D9LDDXshSORJ89\n" + + "/pSibY1l0A+w6RYGZhuSLHvCF0ef6Le36NJFpWEK8nv7kpjZlgbg7zqQQhzp8keimno9+q\n" + + "qDjv4MGx/pns0mF0sQkwPUG/Fc8SwAAADorZ3naK2d52gAAAATZWNkc2Etc2hhMi1uaXN0\n" + + "cDM4NAAAAAhuaXN0cDM4NAAAAGEE5PQJz/Nq4mw5aj/Q/Sww17IUjkSfPf6Uom2NZdAPsO\n" + + "kWBmYbkix7whdHn+i3t+jSRaVhCvJ7+5KY2ZYG4O86kEIc6fJHopp6Pfqqg47+DBsf6Z7N\n" + + "JhdLEJMD1BvxXPEsAAAAMQDLno+rINnY7/Ht1WmSGZYJ3EMPtysbxuBnQFEL4USa3kyAb1\n" + + "QMR6+jtqraKtE7kLwAAAAdbWVnYW53b29kc0B0eWNoZS0zMjY1LmdhdGV3YXkBAg==\n" + + "-----END OPENSSH PRIVATE KEY-----\n" + ), + Tuple.Create( + "AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADXE/q1WSR002vRI+tiPLpdRjzeymSk+RjD7ZIC9CndqLmI0rhTMh5xReAzved12BH9lQJIGIw4YoIQDudsMbRUsQEjFvbFzSXLJBYWdZf8Voa/97/R9w/i8bKUMUPP0disypZlGdQn5+XvzHG6bhX2Qr9aJacGFZoVHugF/M8QyC+GyA==", + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS\n" + + "1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA1xP6tVkkdNNr0SPrYjy6XUY83spk\n" + + "pPkYw+2SAvQp3ai5iNK4UzIecUXgM73nddgR/ZUCSBiMOGKCEA7nbDG0VLEBIxb2xc0lyy\n" + + "QWFnWX/FaGv/e/0fcP4vGylDFDz9HYrMqWZRnUJ+fl78xxum4V9kK/WiWnBhWaFR7oBfzP\n" + + "EMgvhsgAAAEgs+rbdbPq23UAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ\n" + + "AAAIUEANcT+rVZJHTTa9Ej62I8ul1GPN7KZKT5GMPtkgL0Kd2ouYjSuFMyHnFF4DO953XY\n" + + "Ef2VAkgYjDhighAO52wxtFSxASMW9sXNJcskFhZ1l/xWhr/3v9H3D+LxspQxQ8/R2KzKlm\n" + + "UZ1Cfn5e/McbpuFfZCv1olpwYVmhUe6AX8zxDIL4bIAAAAQgCM8ojULpNk3UhBZhPfK+Tw\n" + + "QjT9MHU0OTi4twvKPAE0vOLQ/C1g9AMlspyKxS2NKx2gxxXISowFGNL6Jkx9198ElQAAAB\n" + + "1tZWdhbndvb2RzQHR5Y2hlLTMyNjUuZ2F0ZXdheQECAwQF\n" + + "-----END OPENSSH PRIVATE KEY-----\n" + ) + }; + + String[] ecPriv = new String[] { ecdsa256Key, ecdsa384Key, ecdsa521Key }; + for (int i = 0; i != ecPriv.Length; i++) + { + ECPrivateKeyParameters privKey = (ECPrivateKeyParameters)OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob( + new PemReader( + new StringReader(ecPriv[i])).ReadPemObject().Content); + var q = privKey.Parameters.G.Multiply(privKey.D); + + DoECSigTest(new ECPublicKeyParameters(q, privKey.Parameters), privKey); + } + + + for (int i = 0; i != pairs.Length; i++) + { + var pair = pairs[i]; + + var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey( + Base64.Decode(pair.Item1)); + + var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob( + new PemReader( + new StringReader(pair.Item2)).ReadPemObject().Content); + + DoECSigTest(pubSpec, privSpec); + + byte[] originalMessage; + BigInteger[] rs; + + // + // Test encode + // + var recoveredPubKey = OpenSSHPublicKeyUtil.ParsePublicKey(OpenSSHPublicKeyUtil.EncodePublicKey((AsymmetricKeyParameter)pubSpec)); + var recoveredPrivateKey = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(OpenSSHPrivateKeyUtil.EncodePrivateKey((AsymmetricKeyParameter)privSpec)); + + var signer = new ECDsaSigner(); + signer.Init(true, privSpec); + + originalMessage = new byte[10]; + secureRandom.NextBytes(originalMessage); + + rs = signer.GenerateSignature(originalMessage); + + signer.Init(false, pubSpec); + IsTrue("ECDSA test post encoded / Decode", signer.VerifySignature(originalMessage, rs[0], rs[1])); + } + + } + + private void DoECSigTest(AsymmetricKeyParameter pubSpec, AsymmetricKeyParameter privSpec) + { + var signer = new ECDsaSigner(); + signer.Init(true, privSpec); + + byte[] originalMessage = new byte[10]; + secureRandom.NextBytes(originalMessage); + + BigInteger[] rs = signer.GenerateSignature(originalMessage); + + signer.Init(false, pubSpec); + + IsTrue("ECDSA test", signer.VerifySignature(originalMessage, rs[0], rs[1])); + } + + + public void TestECDSA() + + { + var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(Base64.Decode("AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHq5qxGqnh93Gpbj2w1Avx1UwBl6z5bZC3Viog1yNHDZYcV6Da4YQ3i0/hN7xY7sUy9dNF6g16tJSYXQQ4tvO3g=")); + + var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader("-----BEGIN EC PRIVATE KEY-----\n" + + "MHcCAQEEIHeg/+m02j6nr4bO8ubfbzhs0fqOjiuIoWbvGnVg+FmpoAoGCCqGSM49\n" + + "AwEHoUQDQgAEermrEaqeH3caluPbDUC/HVTAGXrPltkLdWKiDXI0cNlhxXoNrhhD\n" + + "eLT+E3vFjuxTL100XqDXq0lJhdBDi287eA==\n" + + "-----END EC PRIVATE KEY-----\n")).ReadPemObject().Content); + + DoECSigTest(pubSpec, privSpec); + + } + + + public void TestED25519() + + { + var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(Base64.Decode("AAAAC3NzaC1lZDI1NTE5AAAAIM4CaV7WQcy0lht0hclgXf4Olyvzvv2fnUvQ3J8IYsWF")); + var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader("-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + + "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + + "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + + "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + + "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + + "-----END OPENSSH PRIVATE KEY-----\n")).ReadPemObject().Content); + + Ed25519Signer signer = new Ed25519Signer(); + signer.Init(true, privSpec); + + byte[] originalMessage = new byte[10]; + secureRandom.NextBytes(originalMessage); + signer.BlockUpdate(originalMessage, 0, originalMessage.Length); + + byte[] sig = signer.GenerateSignature(); + + signer.Init(false, pubSpec); + + signer.BlockUpdate(originalMessage, 0, originalMessage.Length); + + IsTrue("ED25519Signer test", signer.VerifySignature(sig)); + + } + + public void TestFailures() + { + byte[] + blob = new PemReader(new StringReader("-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + + "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + + "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + + "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + + "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + + "-----END OPENSSH PRIVATE KEY-----\n")).ReadPemObject().Content; + + // + // Altering the check value. + // + blob[98] ^= 1; + + try + { + var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(blob); + Fail("Change should trigger failure."); + } + catch (InvalidOperationException iles) + { + IsEquals("Check value mismatch ", iles.Message, "private key check values are not the same"); + } + + // + // Altering the cipher name. + // + + + blob = new PemReader(new StringReader("-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + + "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + + "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + + "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + + "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + + "-----END OPENSSH PRIVATE KEY-----\n")).ReadPemObject().Content; + blob[19] = (byte)'C'; + + try + { + var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(blob); + Fail("Change should trigger failure."); + } + catch (InvalidOperationException iles) + { + IsEquals("enc keys not supported ", iles.Message, "encrypted keys not supported"); + } + } + + public override string Name + { + get { return "OpenSSHTest"; } + } + + public override void PerformTest() + { + TestECDSA_curvesFromSSHKeyGen(); + TestDSA(); + TestECDSA(); + TestRSA(); + TestED25519(); + TestFailures(); + } + + public void TestRSA() + { + var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(Base64.Decode("AAAAB3NzaC1yc2EAAAADAQABAAAAgQDvh2BophdIp8ojwGZQR0FQ/awowXnV24nAPm+/na8MOUrdySNhOnlek4LAZl82/+Eu2t21XD6hQUiHKAj6XaNFBthTuss7Cz/tA348DLEMHD9wUtT0FXVmsxqN4BfusunbcULxxVWG2z8FvqeaGgc/Unkp9y7/kyf54pPUCBcClw==")); + + var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader("-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXgIBAAKBgQDvh2BophdIp8ojwGZQR0FQ/awowXnV24nAPm+/na8MOUrdySNh\n" + + "Onlek4LAZl82/+Eu2t21XD6hQUiHKAj6XaNFBthTuss7Cz/tA348DLEMHD9wUtT0\n" + + "FXVmsxqN4BfusunbcULxxVWG2z8FvqeaGgc/Unkp9y7/kyf54pPUCBcClwIDAQAB\n" + + "AoGBAOMXYEoXHgAeREE9CkOWKtDUkEJbnF0rNSB0kZIDt5BJSTeYmNh3jdYi2FX9\n" + + "OMx2MFIx4v0tJZvQvyiUxl5IJJ9ZJsYUWF+6VbcTVwYYfdVzZzP2TNyGmF9/ADZW\n" + + "wBehqP04uRlYjt94kqb4HoOKF3gJ3LC4uW9xcEltTBeHWCfhAkEA/2biF5St9/Ya\n" + + "540E4zu/FKPsxLSaT8LWCo9+X7IqIzlBQCB4GjM+nZeTm7eZOkfAFZoxwfiNde/9\n" + + "qleXXf6B2QJBAPAW+jDBC3QF4/g8n9cDxm/A3ICmcOFSychLSrydk9ZyRPbTRyQC\n" + + "YlC2mf/pCrO/yO7h189BXyQ3PXOEhnujce8CQQD7gDy0K90EiH0F94AQpA0OLj5B\n" + + "lfc/BAXycEtpwPBtrzvqAg9C/aNzXIgmly10jqNAoo7NDA2BTcrlq0uLa8xBAkBl\n" + + "7Hs+I1XnZXDIO4Rn1VRysN9rRj15ipnbDAuoUwUl7tDUMBFteg2e0kZCW/6NHIgC\n" + + "0aG6fLgVOdY+qi4lYtfFAkEAqqiBgEgSrDmnJLTm6j/Pv1mBA6b9bJbjOqomrDtr\n" + + "AWTXe+/kSCv/jYYdpNA/tDgAwEmtkWWEie6+SwJB5cXXqg==\n" + + "-----END RSA PRIVATE KEY-----\n")).ReadPemObject().Content); + + DoRSATest(pubSpec, privSpec); + + privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader(rsa1024Key)).ReadPemObject().Content); + pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); + + DoRSATest(pubSpec, privSpec); + + privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader(rsa2048Key)).ReadPemObject().Content); + pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); + + DoRSATest(pubSpec, privSpec); + + privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader(rsa3072Key)).ReadPemObject().Content); + pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); + + DoRSATest(pubSpec, privSpec); + + privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader(rsa4096Key)).ReadPemObject().Content); + pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); + + DoRSATest(pubSpec, privSpec); + } + + private void DoRSATest(AsymmetricKeyParameter pubSpec, AsymmetricKeyParameter privSpec) + { + byte[] originalMessage = new byte[10]; + secureRandom.NextBytes(originalMessage); + + originalMessage[0] |= 1; + + var rsaEngine = new RsaEngine(); + rsaEngine.Init(true, privSpec); + + byte[] ct = rsaEngine.ProcessBlock(originalMessage, 0, originalMessage.Length); + + rsaEngine.Init(false, pubSpec); + byte[] result = rsaEngine.ProcessBlock(ct, 0, ct.Length); + + IsTrue("Result did not match original message", Enumerable.SequenceEqual(originalMessage, result)); + } + + [Test] + public void TestFunction() + { + string resultText = Perform().ToString(); + + Assert.AreEqual(Name + ": Okay", resultText); + } + } +} -- cgit 1.4.1 From 14d34d01a97a3dbc0f5255ce4d84d80664e0038d Mon Sep 17 00:00:00 2001 From: Anh Vo Date: Mon, 27 Mar 2023 18:39:49 -0400 Subject: removed commented out code --- crypto/src/pkcs/PrivateKeyInfoFactory.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/crypto/src/pkcs/PrivateKeyInfoFactory.cs b/crypto/src/pkcs/PrivateKeyInfoFactory.cs index b180e49f2..1baf2dd9f 100644 --- a/crypto/src/pkcs/PrivateKeyInfoFactory.cs +++ b/crypto/src/pkcs/PrivateKeyInfoFactory.cs @@ -164,16 +164,6 @@ namespace Org.BouncyCastle.Pkcs else { X962Parameters x962; - //if (priv.PublicKeyParamSet == null) - //{ - // X9ECParameters ecP = new X9ECParameters(dp.Curve, new X9ECPoint(dp.G, false), dp.N, dp.H, - // dp.GetSeed()); - // x962 = new X962Parameters(ecP); - //} - //else - //{ - // x962 = new X962Parameters(priv.PublicKeyParamSet); - //} if (dp is ECNamedDomainParameters _dp) { x962 = new X962Parameters(_dp.Name); -- cgit 1.4.1 From 9ec864713cb64069bdcd43009340865415984c70 Mon Sep 17 00:00:00 2001 From: Anh Vo Date: Mon, 27 Mar 2023 18:41:24 -0400 Subject: reverted incorrect edit in comment --- crypto/src/crypto/ec/CustomNamedCurves.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/src/crypto/ec/CustomNamedCurves.cs b/crypto/src/crypto/ec/CustomNamedCurves.cs index 2f6fdfeaa..7bf274be4 100644 --- a/crypto/src/crypto/ec/CustomNamedCurves.cs +++ b/crypto/src/crypto/ec/CustomNamedCurves.cs @@ -914,7 +914,7 @@ namespace Org.BouncyCastle.Crypto.EC return CollectionUtilities.GetValueOrNull(objIds, name); } - ///

Enumerate the available curve objIdToName in this registry. + /// Enumerate the available curve names in this registry. public static IEnumerable Names { get { return CollectionUtilities.Proxy(names); } -- cgit 1.4.1 From ddf257fd60d1c4bed773eb37c28726b0a1078a54 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sat, 15 Apr 2023 19:05:05 +0700 Subject: Initial fixups for github_439 --- crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs | 42 ++-- crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs | 38 ++-- crypto/src/util/ssh/SSHBuffer.cs | 7 +- crypto/src/util/ssh/SSHBuilder.cs | 8 +- crypto/src/util/ssh/SSHNamedCurves.cs | 13 +- .../test/src/crypto/test/OpenSSHKeyParsingTests.cs | 237 +++++++++------------ 6 files changed, 143 insertions(+), 202 deletions(-) diff --git a/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs b/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs index a918d3483..0ddd90773 100644 --- a/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs +++ b/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs @@ -1,9 +1,5 @@ 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; @@ -16,17 +12,11 @@ using Org.BouncyCastle.Pkcs; namespace Org.BouncyCastle.Utilities.SSH { - public class OpenSSHPrivateKeyUtil + public static class OpenSshPrivateKeyUtilities { - 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 + /// Magic value for proprietary OpenSSH private key. + /// C string so null terminated. + private static readonly byte[] AUTH_MAGIC = Strings.ToByteArray("openssh-key-v1\0"); /** * Encode a cipher parameters into an OpenSSH private key. @@ -38,9 +28,7 @@ namespace Org.BouncyCastle.Utilities.SSH public static byte[] EncodePrivateKey(AsymmetricKeyParameter parameters) { if (parameters == null) - { - throw new ArgumentException("parameters is null"); - } + throw new ArgumentNullException(nameof(parameters)); if (parameters is RsaPrivateCrtKeyParameters || parameters is ECPrivateKeyParameters) { @@ -76,7 +64,7 @@ namespace Org.BouncyCastle.Utilities.SSH { Ed25519PublicKeyParameters publicKeyParameters = ed25519PrivateKey.GeneratePublicKey(); - SSHBuilder builder = new SSHBuilder(); + SshBuilder builder = new SshBuilder(); builder.WriteBytes(AUTH_MAGIC); builder.WriteString("none"); // cipher name builder.WriteString("none"); // KDF name @@ -85,12 +73,12 @@ namespace Org.BouncyCastle.Utilities.SSH builder.U32(1); // Number of keys { - byte[] pkEncoded = OpenSSHPublicKeyUtil.EncodePublicKey(publicKeyParameters); + byte[] pkEncoded = OpenSshPublicKeyUtilities.EncodePublicKey(publicKeyParameters); builder.WriteBlock(pkEncoded); } { - SSHBuilder pkBuild = new SSHBuilder(); + SshBuilder pkBuild = new SshBuilder(); int checkint = CryptoServicesRegistrar.GetSecureRandom().NextInt(); pkBuild.U32((uint)checkint); @@ -120,10 +108,10 @@ namespace Org.BouncyCastle.Utilities.SSH /** * Parse a private key. - *

+ *

* This method accepts the body of the OpenSSH private key. * The easiest way to extract the body is to use PemReader, for example: - *

+ *

* byte[] blob = new PemReader([reader]).readPemObject().getContent(); * CipherParameters params = parsePrivateKeyBlob(blob); * @@ -187,7 +175,7 @@ namespace Org.BouncyCastle.Utilities.SSH } else { - SSHBuffer kIn = new SSHBuffer(AUTH_MAGIC, blob); + SshBuffer kIn = new SshBuffer(AUTH_MAGIC, blob); String cipherName = kIn.ReadString(); if (!"none".Equals(cipherName)) @@ -208,7 +196,7 @@ namespace Org.BouncyCastle.Utilities.SSH } // Burn off public key. - OpenSSHPublicKeyUtil.ParsePublicKey(kIn.ReadBlock()); + OpenSshPublicKeyUtilities.ParsePublicKey(kIn.ReadBlock()); byte[] privateKeyBlock = kIn.ReadPaddedBlock(); @@ -217,7 +205,7 @@ namespace Org.BouncyCastle.Utilities.SSH throw new InvalidOperationException("decoded key has trailing data"); } - SSHBuffer pkIn = new SSHBuffer(privateKeyBlock); + SshBuffer pkIn = new SshBuffer(privateKeyBlock); int check1 = pkIn.ReadU32(); int check2 = pkIn.ReadU32(); @@ -243,7 +231,7 @@ namespace Org.BouncyCastle.Utilities.SSH } else if (keyType.StartsWith("ecdsa")) { - DerObjectIdentifier oid = SSHNamedCurves.GetByName(Strings.FromByteArray(pkIn.ReadBlock())) ?? + 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); diff --git a/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs b/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs index 8f1fa8ec1..02e6928e0 100644 --- a/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs +++ b/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs @@ -1,25 +1,15 @@ -using Org.BouncyCastle.Asn1; +using System; + +using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; -using Org.BouncyCastle.Math.EC; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; namespace Org.BouncyCastle.Utilities.SSH { - public class OpenSSHPublicKeyUtil + public static class OpenSshPublicKeyUtilities { - private OpenSSHPublicKeyUtil() - { - - } - private static readonly String RSA = "ssh-rsa"; private static readonly String ECDSA = "ecdsa"; private static readonly String ED_25519 = "ssh-ed25519"; @@ -27,7 +17,7 @@ namespace Org.BouncyCastle.Utilities.SSH /** * Parse a public key. - *

+ *

* This method accepts the bytes that are Base64 encoded in an OpenSSH public key file. * * @param encoded The key. @@ -35,7 +25,7 @@ namespace Org.BouncyCastle.Utilities.SSH */ public static AsymmetricKeyParameter ParsePublicKey(byte[] encoded) { - SSHBuffer buffer = new SSHBuffer(encoded); + SshBuffer buffer = new SshBuffer(encoded); return ParsePublicKey(buffer); } @@ -62,7 +52,7 @@ namespace Org.BouncyCastle.Utilities.SSH RsaKeyParameters rsaPubKey = (RsaKeyParameters)cipherParameters; - SSHBuilder builder = new SSHBuilder(); + SshBuilder builder = new SshBuilder(); builder.WriteString(RSA); builder.WriteBigNum(rsaPubKey.Exponent); builder.WriteBigNum(rsaPubKey.Modulus); @@ -72,12 +62,12 @@ namespace Org.BouncyCastle.Utilities.SSH } else if (cipherParameters is ECPublicKeyParameters ecPublicKey) { - SSHBuilder builder = new SSHBuilder(); + SshBuilder builder = new SshBuilder(); // // checked for named curve parameters.. // - String name = SSHNamedCurves.GetNameForParameters(ecPublicKey.Parameters); + String name = SshNamedCurves.GetNameForParameters(ecPublicKey.Parameters); if (name == null) { @@ -93,7 +83,7 @@ namespace Org.BouncyCastle.Utilities.SSH { DsaParameters dsaParams = dsaPubKey.Parameters; - SSHBuilder builder = new SSHBuilder(); + SshBuilder builder = new SshBuilder(); builder.WriteString(DSS); builder.WriteBigNum(dsaParams.P); builder.WriteBigNum(dsaParams.Q); @@ -103,7 +93,7 @@ namespace Org.BouncyCastle.Utilities.SSH } else if (cipherParameters is Ed25519PublicKeyParameters ed25519PublicKey) { - SSHBuilder builder = new SSHBuilder(); + SshBuilder builder = new SshBuilder(); builder.WriteString(ED_25519); builder.WriteBlock(ed25519PublicKey.GetEncoded()); return builder.GetBytes(); @@ -118,7 +108,7 @@ namespace Org.BouncyCastle.Utilities.SSH * @param buffer containing the SSH public key. * @return A CipherParameters instance. */ - public static AsymmetricKeyParameter ParsePublicKey(SSHBuffer buffer) + private static AsymmetricKeyParameter ParsePublicKey(SshBuffer buffer) { AsymmetricKeyParameter result = null; @@ -141,8 +131,8 @@ namespace Org.BouncyCastle.Utilities.SSH else if (magic.StartsWith(ECDSA)) { String curveName = buffer.ReadString(); - DerObjectIdentifier oid = SSHNamedCurves.GetByName(curveName); - X9ECParameters x9ECParameters = SSHNamedCurves.GetParameters(oid) ?? + DerObjectIdentifier oid = SshNamedCurves.GetByName(curveName); + X9ECParameters x9ECParameters = SshNamedCurves.GetParameters(oid) ?? throw new InvalidOperationException("unable to find curve for " + magic + " using curve name " + curveName); var curve = x9ECParameters.Curve; byte[] pointRaw = buffer.ReadBlock(); diff --git a/crypto/src/util/ssh/SSHBuffer.cs b/crypto/src/util/ssh/SSHBuffer.cs index 8d3c3f977..795641032 100644 --- a/crypto/src/util/ssh/SSHBuffer.cs +++ b/crypto/src/util/ssh/SSHBuffer.cs @@ -1,14 +1,15 @@ using System; + using Org.BouncyCastle.Math; namespace Org.BouncyCastle.Utilities.SSH { - public class SSHBuffer + internal class SshBuffer { private readonly byte[] buffer; private int pos = 0; - public SSHBuffer(byte[] magic, byte[] buffer) + internal SshBuffer(byte[] magic, byte[] buffer) { this.buffer = buffer; for (int i = 0; i != magic.Length; i++) @@ -22,7 +23,7 @@ namespace Org.BouncyCastle.Utilities.SSH pos += magic.Length; } - public SSHBuffer(byte[] buffer) + internal SshBuffer(byte[] buffer) { this.buffer = buffer; } diff --git a/crypto/src/util/ssh/SSHBuilder.cs b/crypto/src/util/ssh/SSHBuilder.cs index 5fa92de4b..24121d0d8 100644 --- a/crypto/src/util/ssh/SSHBuilder.cs +++ b/crypto/src/util/ssh/SSHBuilder.cs @@ -1,18 +1,14 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; + using Org.BouncyCastle.Math; namespace Org.BouncyCastle.Utilities.SSH { - public class SSHBuilder + internal class SshBuilder { private readonly MemoryStream bos = new MemoryStream(); - [CLSCompliant(false)] public void U32(uint value) { bos.WriteByte(Convert.ToByte((value >> 24) & 0xFF)); diff --git a/crypto/src/util/ssh/SSHNamedCurves.cs b/crypto/src/util/ssh/SSHNamedCurves.cs index 31c350128..6839627b8 100644 --- a/crypto/src/util/ssh/SSHNamedCurves.cs +++ b/crypto/src/util/ssh/SSHNamedCurves.cs @@ -1,20 +1,17 @@ -using Org.BouncyCastle.Asn1; +using System.Collections.Generic; +using System.Linq; + +using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.Nist; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto.EC; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math.EC; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Org.BouncyCastle.Utilities.SSH { - public class SSHNamedCurves + public class SshNamedCurves { private static readonly Dictionary OidMap = new Dictionary diff --git a/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs b/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs index c347dcc4b..a647ba652 100644 --- a/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs +++ b/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs @@ -1,28 +1,26 @@ -using NUnit.Framework; -using System; -using System.Collections.Generic; +using System; using System.IO; -using Org.BouncyCastle.Security; +using NUnit.Framework; + using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Paddings; using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Encoders; -using Org.BouncyCastle.Utilities.Test; -using Org.BouncyCastle.Utilities.SSH; using Org.BouncyCastle.Utilities.IO.Pem; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Crypto.Signers; -using System.Linq; +using Org.BouncyCastle.Utilities.SSH; namespace Org.BouncyCastle.Crypto.Tests { [TestFixture] - public class OpenSSHKeyParsingTests : SimpleTest + public class OpenSshKeyParsingTests { private static SecureRandom secureRandom = new SecureRandom(); - String rsa1024Key = + private static readonly string rsa1024Key = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn\n" + "NhAAAAAwEAAQAAAIEA37C9iHf9kS3ekS8xVE4p5/bmA7Yc37gXqN10W6c53FzVMiT9ZzVm\n" @@ -39,7 +37,7 @@ namespace Org.BouncyCastle.Crypto.Tests + "QQDiEe/BJMLRZ+94n98VCEr7E+eG2isQctxiAowH7o/wp5WAkFSD9W58dqUobuneXleG+F\n" + "DAfXzFhYvNE+TdLXUrAAAADm1hcmtAYmFybmFjbGVzAQIDBA==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; - String rsa2048Key = + private static readonly string rsa2048Key = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn\n" + "NhAAAAAwEAAQAAAQEArxWa1zW+Uf0lUrYoL1yqgTYUT1TfUkfojrhguPB1s/1AEMj8sueu\n" @@ -67,7 +65,7 @@ namespace Org.BouncyCastle.Crypto.Tests + "/XMw47d1iL7Hdu9NAJsplezKD5Unso4xJRXhLnXUT5FT8lSgwE+9xUBuILKUmZQa20ejKM\n" + "20U6szOxEEclA/8AAAAObWFya0BiYXJuYWNsZXMBAgMEBQ==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; - String rsa3072Key = + private static readonly string rsa3072Key = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn\n" + "NhAAAAAwEAAQAAAYEA34VbMJbV2+ZJyRQANnFkTPNcSPkjBllNbnUrlGFQ9wxRyr6xiVRj\n" @@ -106,7 +104,7 @@ namespace Org.BouncyCastle.Crypto.Tests + "xcxQuK2sGkRT7Q2NdfEQ9LG4GNIusJeISJgY9NdDBaXrSODSkJI2KCOxDlNY5NsNXXc0Ty\n" + "7fLQW04MPjqisAAAAObWFya0BiYXJuYWNsZXMBAgMEBQ==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; - String rsa4096Key = + private static readonly string rsa4096Key = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn\n" + "NhAAAAAwEAAQAAAgEA2UjzaFgy2oYc6eyCk2tHEhMw/D807dSgVmOJz6ZXfbxIgh5aptbj\n" @@ -156,7 +154,7 @@ namespace Org.BouncyCastle.Crypto.Tests + "7NalafAFnf+Sg12wVD6f0ujP/ozQ24Arzc5rmE/AV+XJ7vqnjS1CeHSxTHPYrpKtC6mFQy\n" + "S+iAb4yzfmFnAAAADm1hcmtAYmFybmFjbGVzAQIDBA==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; - String ecdsa256Key = + private static readonly string ecdsa256Key = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n" + "1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS9VjynnoLGUcT2hXXPkFwGfbleI4Ln\n" @@ -166,7 +164,7 @@ namespace Org.BouncyCastle.Crypto.Tests + "QAAAAgbAJJUVcjwwU/olgrxgINJ1DViX6GcCBhgeH8wAXiNKoAAAAObWFya0BiYXJuYWNs\n" + "ZXMBAg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; - String ecdsa384Key = + private static readonly string ecdsa384Key = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS\n" + "1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQS0yKimt2kBeyNKUqNivPfSPBVyU4jH\n" @@ -177,7 +175,7 @@ namespace Org.BouncyCastle.Crypto.Tests + "qlpyi9jYKNblOSmYAAAAMQChvecXe7PGUVG0Pz2IgM9f80YLXdarf98sRptbGSIPwu8KlW\n" + "OlGv0Any5ue51/I5wAAAAObWFya0BiYXJuYWNsZXMB\n" + "-----END OPENSSH PRIVATE KEY-----\n"; - String ecdsa521Key = + private static readonly string ecdsa521Key = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS\n" + "1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA90An5exsl3UEU0d8fhqV8rgmoyzJ\n" @@ -191,18 +189,12 @@ namespace Org.BouncyCastle.Crypto.Tests + "5tYXJrQGJhcm5hY2xlcwECAwQ=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; - public static void main( - String[] args) - { - RunTest(new OpenSSHKeyParsingTests()); - } - - - public void TestDSA() + [Test] + public void TestDsa() { - var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(Base64.Decode("AAAAB3NzaC1kc3MAAACBAJBB5+S4kZZYZLswaQ/zm3GM7YWmHsumwo/Xxu+z6Cg2l5PUoiBBZ4ET9EhhQuL2ja/zrCMCi0ZwiSRuSp36ayPrHLbNJb3VdOuJg8xExRa6F3YfVZfcTPUEKh6FU72fI31HrQmi4rpyHnWxL/iDX496ZG2Hdq6UkPISQpQwj4TtAAAAFQCP9TXcVahR/2rpfEhvdXR0PfhbRwAAAIBdXzAVqoOtb9zog6lNF1cGS1S06W9W/clvuwq2xF1s3bkoI/xUbFSc0IAPsGl2kcB61PAZqcop50lgpvYzt8cq/tbqz3ypq1dCQ0xdmJHj975QsRFax+w6xQ0kgpBhwcS2EOizKb+C+tRzndGpcDSoSMuVXp9i4wn5pJSTZxAYFQAAAIEAhQZc687zYxrEDR/1q6m4hw5GFxuVvLsC+bSHtMF0c11Qy4IPg7mBeP7K5Kq4WyJPtmZhuc5Bb12bJQR6qgd1uLn692fe1UK2kM6eWXBzhlzZ54BslfSKHGNN4qH+ln3Zaf/4rpKE7fvoinkrgkOZmj0PMx9D6wlpHKkXMUxeXtc=")); + var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey(Base64.Decode("AAAAB3NzaC1kc3MAAACBAJBB5+S4kZZYZLswaQ/zm3GM7YWmHsumwo/Xxu+z6Cg2l5PUoiBBZ4ET9EhhQuL2ja/zrCMCi0ZwiSRuSp36ayPrHLbNJb3VdOuJg8xExRa6F3YfVZfcTPUEKh6FU72fI31HrQmi4rpyHnWxL/iDX496ZG2Hdq6UkPISQpQwj4TtAAAAFQCP9TXcVahR/2rpfEhvdXR0PfhbRwAAAIBdXzAVqoOtb9zog6lNF1cGS1S06W9W/clvuwq2xF1s3bkoI/xUbFSc0IAPsGl2kcB61PAZqcop50lgpvYzt8cq/tbqz3ypq1dCQ0xdmJHj975QsRFax+w6xQ0kgpBhwcS2EOizKb+C+tRzndGpcDSoSMuVXp9i4wn5pJSTZxAYFQAAAIEAhQZc687zYxrEDR/1q6m4hw5GFxuVvLsC+bSHtMF0c11Qy4IPg7mBeP7K5Kq4WyJPtmZhuc5Bb12bJQR6qgd1uLn692fe1UK2kM6eWXBzhlzZ54BslfSKHGNN4qH+ln3Zaf/4rpKE7fvoinkrgkOZmj0PMx9D6wlpHKkXMUxeXtc=")); - var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader("-----BEGIN DSA PRIVATE KEY-----\n" + + var privSpec = ParsePrivateKeyData("-----BEGIN DSA PRIVATE KEY-----\n" + "MIIBuwIBAAKBgQCQQefkuJGWWGS7MGkP85txjO2Fph7LpsKP18bvs+goNpeT1KIg\n" + "QWeBE/RIYULi9o2v86wjAotGcIkkbkqd+msj6xy2zSW91XTriYPMRMUWuhd2H1WX\n" + "3Ez1BCoehVO9nyN9R60JouK6ch51sS/4g1+PemRth3aulJDyEkKUMI+E7QIVAI/1\n" + @@ -213,23 +205,20 @@ namespace Org.BouncyCastle.Crypto.Tests "eP7K5Kq4WyJPtmZhuc5Bb12bJQR6qgd1uLn692fe1UK2kM6eWXBzhlzZ54BslfSK\n" + "HGNN4qH+ln3Zaf/4rpKE7fvoinkrgkOZmj0PMx9D6wlpHKkXMUxeXtcCFELnLOJ8\n" + "D0akSCUFY/iDLo/KnOIH\n" + - "-----END DSA PRIVATE KEY-----\n")).ReadPemObject().Content); + "-----END DSA PRIVATE KEY-----\n"); + + byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); var signer = new DsaSigner(); signer.Init(true, privSpec); - - byte[] originalMessage = new byte[10]; - secureRandom.NextBytes(originalMessage); - BigInteger[] rs = signer.GenerateSignature(originalMessage); signer.Init(false, pubSpec); - - IsTrue("DSA test", signer.VerifySignature(originalMessage, rs[0], rs[1])); + Assert.IsTrue(signer.VerifySignature(originalMessage, rs[0], rs[1]), "DSA test"); } - - public void TestECDSA_curvesFromSSHKeyGen() + [Test] + public void TestECDsa_CurvesFromSshKeyGen() { var pairs = new Tuple []{ Tuple.Create( @@ -274,125 +263,115 @@ namespace Org.BouncyCastle.Crypto.Tests ) }; - String[] ecPriv = new String[] { ecdsa256Key, ecdsa384Key, ecdsa521Key }; + string[] ecPriv = { ecdsa256Key, ecdsa384Key, ecdsa521Key }; for (int i = 0; i != ecPriv.Length; i++) { - ECPrivateKeyParameters privKey = (ECPrivateKeyParameters)OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob( - new PemReader( - new StringReader(ecPriv[i])).ReadPemObject().Content); + var privKey = (ECPrivateKeyParameters)ParsePrivateKeyData(ecPriv[i]); + var q = privKey.Parameters.G.Multiply(privKey.D); DoECSigTest(new ECPublicKeyParameters(q, privKey.Parameters), privKey); } - for (int i = 0; i != pairs.Length; i++) { var pair = pairs[i]; - var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey( - Base64.Decode(pair.Item1)); - - var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob( - new PemReader( - new StringReader(pair.Item2)).ReadPemObject().Content); + var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey(Base64.Decode(pair.Item1)); + var privSpec = ParsePrivateKeyData(pair.Item2); DoECSigTest(pubSpec, privSpec); - byte[] originalMessage; - BigInteger[] rs; - // // Test encode // - var recoveredPubKey = OpenSSHPublicKeyUtil.ParsePublicKey(OpenSSHPublicKeyUtil.EncodePublicKey((AsymmetricKeyParameter)pubSpec)); - var recoveredPrivateKey = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(OpenSSHPrivateKeyUtil.EncodePrivateKey((AsymmetricKeyParameter)privSpec)); + var recoveredPubKey = OpenSshPublicKeyUtilities.ParsePublicKey( + OpenSshPublicKeyUtilities.EncodePublicKey((AsymmetricKeyParameter)pubSpec)); + var recoveredPrivateKey = OpenSshPrivateKeyUtilities.ParsePrivateKeyBlob( + OpenSshPrivateKeyUtilities.EncodePrivateKey((AsymmetricKeyParameter)privSpec)); + + byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); var signer = new ECDsaSigner(); signer.Init(true, privSpec); - - originalMessage = new byte[10]; - secureRandom.NextBytes(originalMessage); - - rs = signer.GenerateSignature(originalMessage); + BigInteger[] rs = signer.GenerateSignature(originalMessage); signer.Init(false, pubSpec); - IsTrue("ECDSA test post encoded / Decode", signer.VerifySignature(originalMessage, rs[0], rs[1])); + Assert.IsTrue(signer.VerifySignature(originalMessage, rs[0], rs[1]), + "ECDSA test post encoded / Decode"); } } private void DoECSigTest(AsymmetricKeyParameter pubSpec, AsymmetricKeyParameter privSpec) { + byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); + var signer = new ECDsaSigner(); signer.Init(true, privSpec); - - byte[] originalMessage = new byte[10]; - secureRandom.NextBytes(originalMessage); - BigInteger[] rs = signer.GenerateSignature(originalMessage); signer.Init(false, pubSpec); - - IsTrue("ECDSA test", signer.VerifySignature(originalMessage, rs[0], rs[1])); + Assert.IsTrue(signer.VerifySignature(originalMessage, rs[0], rs[1]), "ECDSA test"); } - - public void TestECDSA() - + [Test] + public void TestECDsa() { - var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(Base64.Decode("AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHq5qxGqnh93Gpbj2w1Avx1UwBl6z5bZC3Viog1yNHDZYcV6Da4YQ3i0/hN7xY7sUy9dNF6g16tJSYXQQ4tvO3g=")); + var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey( + Base64.Decode("AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHq5qxGqnh93Gpbj2w1Avx1UwBl6z5bZC3Viog1yNHDZYcV6Da4YQ3i0/hN7xY7sUy9dNF6g16tJSYXQQ4tvO3g=")); - var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader("-----BEGIN EC PRIVATE KEY-----\n" + + var privSpec = ParsePrivateKeyData("-----BEGIN EC PRIVATE KEY-----\n" + "MHcCAQEEIHeg/+m02j6nr4bO8ubfbzhs0fqOjiuIoWbvGnVg+FmpoAoGCCqGSM49\n" + "AwEHoUQDQgAEermrEaqeH3caluPbDUC/HVTAGXrPltkLdWKiDXI0cNlhxXoNrhhD\n" + "eLT+E3vFjuxTL100XqDXq0lJhdBDi287eA==\n" + - "-----END EC PRIVATE KEY-----\n")).ReadPemObject().Content); + "-----END EC PRIVATE KEY-----\n"); DoECSigTest(pubSpec, privSpec); - } - - public void TestED25519() - + [Test] + public void TestEd25519() { - var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(Base64.Decode("AAAAC3NzaC1lZDI1NTE5AAAAIM4CaV7WQcy0lht0hclgXf4Olyvzvv2fnUvQ3J8IYsWF")); - var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader("-----BEGIN OPENSSH PRIVATE KEY-----\n" + + var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey( + Base64.Decode("AAAAC3NzaC1lZDI1NTE5AAAAIM4CaV7WQcy0lht0hclgXf4Olyvzvv2fnUvQ3J8IYsWF")); + + var privSpec = ParsePrivateKeyData("-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + - "-----END OPENSSH PRIVATE KEY-----\n")).ReadPemObject().Content); + "-----END OPENSSH PRIVATE KEY-----\n"); + + byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); Ed25519Signer signer = new Ed25519Signer(); signer.Init(true, privSpec); - - byte[] originalMessage = new byte[10]; - secureRandom.NextBytes(originalMessage); signer.BlockUpdate(originalMessage, 0, originalMessage.Length); - byte[] sig = signer.GenerateSignature(); signer.Init(false, pubSpec); - signer.BlockUpdate(originalMessage, 0, originalMessage.Length); - - IsTrue("ED25519Signer test", signer.VerifySignature(sig)); - + Assert.IsTrue(signer.VerifySignature(sig), "ED25519Signer test"); } + [Test] public void TestFailures() { - byte[] - blob = new PemReader(new StringReader("-----BEGIN OPENSSH PRIVATE KEY-----\n" + - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + - "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + - "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + - "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + - "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + - "-----END OPENSSH PRIVATE KEY-----\n")).ReadPemObject().Content; + var data = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + + "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + + "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + + "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + + "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + + byte[] blob; + using (var pemReader = new PemReader(new StringReader(data))) + { + blob = pemReader.ReadPemObject().Content; + } // // Altering the check value. @@ -401,59 +380,51 @@ namespace Org.BouncyCastle.Crypto.Tests try { - var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(blob); - Fail("Change should trigger failure."); + var privSpec = OpenSshPrivateKeyUtilities.ParsePrivateKeyBlob(blob); + Assert.Fail("Change should trigger failure."); } catch (InvalidOperationException iles) { - IsEquals("Check value mismatch ", iles.Message, "private key check values are not the same"); + Assert.AreEqual("private key check values are not the same", iles.Message, "Check value mismatch"); } // // Altering the cipher name. // - - blob = new PemReader(new StringReader("-----BEGIN OPENSSH PRIVATE KEY-----\n" + + data = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + - "-----END OPENSSH PRIVATE KEY-----\n")).ReadPemObject().Content; + "-----END OPENSSH PRIVATE KEY-----\n"; + + using (var pemReader = new PemReader(new StringReader(data))) + { + blob = pemReader.ReadPemObject().Content; + } + blob[19] = (byte)'C'; try { - var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(blob); - Fail("Change should trigger failure."); + var privSpec = OpenSshPrivateKeyUtilities.ParsePrivateKeyBlob(blob); + Assert.Fail("Change should trigger failure."); } catch (InvalidOperationException iles) { - IsEquals("enc keys not supported ", iles.Message, "encrypted keys not supported"); + Assert.AreEqual("encrypted keys not supported", iles.Message, "enc keys not supported"); } } - public override string Name - { - get { return "OpenSSHTest"; } - } - - public override void PerformTest() - { - TestECDSA_curvesFromSSHKeyGen(); - TestDSA(); - TestECDSA(); - TestRSA(); - TestED25519(); - TestFailures(); - } - + [Test] public void TestRSA() { - var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(Base64.Decode("AAAAB3NzaC1yc2EAAAADAQABAAAAgQDvh2BophdIp8ojwGZQR0FQ/awowXnV24nAPm+/na8MOUrdySNhOnlek4LAZl82/+Eu2t21XD6hQUiHKAj6XaNFBthTuss7Cz/tA348DLEMHD9wUtT0FXVmsxqN4BfusunbcULxxVWG2z8FvqeaGgc/Unkp9y7/kyf54pPUCBcClw==")); + var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey( + Base64.Decode("AAAAB3NzaC1yc2EAAAADAQABAAAAgQDvh2BophdIp8ojwGZQR0FQ/awowXnV24nAPm+/na8MOUrdySNhOnlek4LAZl82/+Eu2t21XD6hQUiHKAj6XaNFBthTuss7Cz/tA348DLEMHD9wUtT0FXVmsxqN4BfusunbcULxxVWG2z8FvqeaGgc/Unkp9y7/kyf54pPUCBcClw==")); - var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader("-----BEGIN RSA PRIVATE KEY-----\n" + + var privSpec = ParsePrivateKeyData("-----BEGIN RSA PRIVATE KEY-----\n" + "MIICXgIBAAKBgQDvh2BophdIp8ojwGZQR0FQ/awowXnV24nAPm+/na8MOUrdySNh\n" + "Onlek4LAZl82/+Eu2t21XD6hQUiHKAj6XaNFBthTuss7Cz/tA348DLEMHD9wUtT0\n" + "FXVmsxqN4BfusunbcULxxVWG2z8FvqeaGgc/Unkp9y7/kyf54pPUCBcClwIDAQAB\n" + @@ -467,26 +438,26 @@ namespace Org.BouncyCastle.Crypto.Tests "7Hs+I1XnZXDIO4Rn1VRysN9rRj15ipnbDAuoUwUl7tDUMBFteg2e0kZCW/6NHIgC\n" + "0aG6fLgVOdY+qi4lYtfFAkEAqqiBgEgSrDmnJLTm6j/Pv1mBA6b9bJbjOqomrDtr\n" + "AWTXe+/kSCv/jYYdpNA/tDgAwEmtkWWEie6+SwJB5cXXqg==\n" + - "-----END RSA PRIVATE KEY-----\n")).ReadPemObject().Content); + "-----END RSA PRIVATE KEY-----\n"); DoRSATest(pubSpec, privSpec); - privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader(rsa1024Key)).ReadPemObject().Content); + privSpec = ParsePrivateKeyData(rsa1024Key); pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); DoRSATest(pubSpec, privSpec); - privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader(rsa2048Key)).ReadPemObject().Content); + privSpec = ParsePrivateKeyData(rsa2048Key); pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); DoRSATest(pubSpec, privSpec); - privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader(rsa3072Key)).ReadPemObject().Content); + privSpec = ParsePrivateKeyData(rsa3072Key); pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); DoRSATest(pubSpec, privSpec); - privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader(rsa4096Key)).ReadPemObject().Content); + privSpec = ParsePrivateKeyData(rsa4096Key); pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); DoRSATest(pubSpec, privSpec); @@ -494,9 +465,7 @@ namespace Org.BouncyCastle.Crypto.Tests private void DoRSATest(AsymmetricKeyParameter pubSpec, AsymmetricKeyParameter privSpec) { - byte[] originalMessage = new byte[10]; - secureRandom.NextBytes(originalMessage); - + byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); originalMessage[0] |= 1; var rsaEngine = new RsaEngine(); @@ -507,15 +476,15 @@ namespace Org.BouncyCastle.Crypto.Tests rsaEngine.Init(false, pubSpec); byte[] result = rsaEngine.ProcessBlock(ct, 0, ct.Length); - IsTrue("Result did not match original message", Enumerable.SequenceEqual(originalMessage, result)); + Assert.IsTrue(Arrays.AreEqual(originalMessage, result), "Result did not match original message"); } - [Test] - public void TestFunction() + private static AsymmetricKeyParameter ParsePrivateKeyData(string data) { - string resultText = Perform().ToString(); - - Assert.AreEqual(Name + ": Okay", resultText); + using (var pemReader = new PemReader(new StringReader(data))) + { + return OpenSshPrivateKeyUtilities.ParsePrivateKeyBlob(pemReader.ReadPemObject().Content); + } } } } -- cgit 1.4.1 From ba8033a0bd947c58a109ae20d89f8b3817d3d0db Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sat, 15 Apr 2023 19:47:09 +0700 Subject: Move/rename files --- .../src/crypto/util/OpenSshPrivateKeyUtilities.cs | 302 +++++++++++++ .../src/crypto/util/OpenSshPublicKeyUtilities.cs | 167 +++++++ crypto/src/crypto/util/SshBuffer.cs | 152 +++++++ crypto/src/crypto/util/SshBuilder.cs | 80 ++++ crypto/src/crypto/util/SshNamedCurves.cs | 93 ++++ crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs | 302 ------------- crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs | 168 ------- crypto/src/util/ssh/SSHBuffer.cs | 151 ------- crypto/src/util/ssh/SSHBuilder.cs | 79 ---- crypto/src/util/ssh/SSHNamedCurves.cs | 93 ---- .../test/src/crypto/test/OpenSSHKeyParsingTests.cs | 490 --------------------- .../test/src/crypto/test/OpenSshKeyParsingTest.cs | 490 +++++++++++++++++++++ 12 files changed, 1284 insertions(+), 1283 deletions(-) create mode 100644 crypto/src/crypto/util/OpenSshPrivateKeyUtilities.cs create mode 100644 crypto/src/crypto/util/OpenSshPublicKeyUtilities.cs create mode 100644 crypto/src/crypto/util/SshBuffer.cs create mode 100644 crypto/src/crypto/util/SshBuilder.cs create mode 100644 crypto/src/crypto/util/SshNamedCurves.cs delete mode 100644 crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs delete mode 100644 crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs delete mode 100644 crypto/src/util/ssh/SSHBuffer.cs delete mode 100644 crypto/src/util/ssh/SSHBuilder.cs delete mode 100644 crypto/src/util/ssh/SSHNamedCurves.cs delete mode 100644 crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs create mode 100644 crypto/test/src/crypto/test/OpenSshKeyParsingTest.cs diff --git a/crypto/src/crypto/util/OpenSshPrivateKeyUtilities.cs b/crypto/src/crypto/util/OpenSshPrivateKeyUtilities.cs new file mode 100644 index 000000000..187ecc39e --- /dev/null +++ b/crypto/src/crypto/util/OpenSshPrivateKeyUtilities.cs @@ -0,0 +1,302 @@ +using System; + +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.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Utilities +{ + public static class OpenSshPrivateKeyUtilities + { + ///

Magic value for proprietary OpenSSH private key. + /// C string so null terminated. + private static readonly byte[] AUTH_MAGIC = Strings.ToByteArray("openssh-key-v1\0"); + + /** + * 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 ArgumentNullException(nameof(parameters)); + + 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 = OpenSshPublicKeyUtilities.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. + *

+ * This method accepts the body of the OpenSSH private key. + * The easiest way to extract the body is to use PemReader, for example: + *

+ * 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. + OpenSshPublicKeyUtilities.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 bool AllIntegers(Asn1Sequence sequence) + { + for (int t = 0; t < sequence.Count; t++) + { + if (!(sequence[t] is DerInteger)) + { + return false; + } + } + return true; + } + } +} diff --git a/crypto/src/crypto/util/OpenSshPublicKeyUtilities.cs b/crypto/src/crypto/util/OpenSshPublicKeyUtilities.cs new file mode 100644 index 000000000..cdb16b06a --- /dev/null +++ b/crypto/src/crypto/util/OpenSshPublicKeyUtilities.cs @@ -0,0 +1,167 @@ +using System; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Utilities +{ + public static class OpenSshPublicKeyUtilities + { + private static readonly string RSA = "ssh-rsa"; + private static readonly string ECDSA = "ecdsa"; + private static readonly string ED_25519 = "ssh-ed25519"; + private static readonly string DSS = "ssh-dss"; + + /** + * Parse a public key. + *

+ * This method accepts the bytes that are Base64 encoded in an OpenSSH public key file. + * + * @param encoded The key. + * @return An AsymmetricKeyParameter instance. + */ + public static AsymmetricKeyParameter ParsePublicKey(byte[] encoded) + { + SshBuffer buffer = new SshBuffer(encoded); + return ParsePublicKey(buffer); + } + + /** + * Encode a public key from an AsymmetricKeyParameter instance. + * + * @param cipherParameters The key to encode. + * @return the key OpenSSH encoded. + * @throws IOException + */ + public static byte[] EncodePublicKey(AsymmetricKeyParameter cipherParameters) + { + if (cipherParameters == null) + { + throw new ArgumentException("cipherParameters was null."); + } + + if (cipherParameters is RsaKeyParameters) + { + if (cipherParameters.IsPrivate) + { + throw new ArgumentException("RSAKeyParamaters was for encryption"); + } + + RsaKeyParameters rsaPubKey = (RsaKeyParameters)cipherParameters; + + SshBuilder builder = new SshBuilder(); + builder.WriteString(RSA); + builder.WriteBigNum(rsaPubKey.Exponent); + builder.WriteBigNum(rsaPubKey.Modulus); + + return builder.GetBytes(); + + } + else if (cipherParameters is ECPublicKeyParameters ecPublicKey) + { + SshBuilder builder = new SshBuilder(); + + // + // checked for named curve parameters.. + // + string name = SshNamedCurves.GetNameForParameters(ecPublicKey.Parameters); + + if (name == null) + { + throw new ArgumentException("unable to derive ssh curve name for " + ecPublicKey.Parameters.Curve.GetType().Name); + } + + builder.WriteString(ECDSA + "-sha2-" + name); // Magic + builder.WriteString(name); + builder.WriteBlock(ecPublicKey.Q.GetEncoded(false)); //Uncompressed + return builder.GetBytes(); + } + else if (cipherParameters is DsaPublicKeyParameters dsaPubKey) + { + DsaParameters dsaParams = dsaPubKey.Parameters; + + SshBuilder builder = new SshBuilder(); + builder.WriteString(DSS); + builder.WriteBigNum(dsaParams.P); + builder.WriteBigNum(dsaParams.Q); + builder.WriteBigNum(dsaParams.G); + builder.WriteBigNum(dsaPubKey.Y); + return builder.GetBytes(); + } + else if (cipherParameters is Ed25519PublicKeyParameters ed25519PublicKey) + { + SshBuilder builder = new SshBuilder(); + builder.WriteString(ED_25519); + builder.WriteBlock(ed25519PublicKey.GetEncoded()); + return builder.GetBytes(); + } + + throw new ArgumentException("unable to convert " + cipherParameters.GetType().Name + " to private key"); + } + + /** + * Parse a public key from an SSHBuffer instance. + * + * @param buffer containing the SSH public key. + * @return A CipherParameters instance. + */ + private static AsymmetricKeyParameter ParsePublicKey(SshBuffer buffer) + { + AsymmetricKeyParameter result = null; + + string magic = buffer.ReadString(); + if (RSA.Equals(magic)) + { + BigInteger e = buffer.ReadBigNumPositive(); + BigInteger n = buffer.ReadBigNumPositive(); + result = new RsaKeyParameters(false, n, e); + } + else if (DSS.Equals(magic)) + { + BigInteger p = buffer.ReadBigNumPositive(); + BigInteger q = buffer.ReadBigNumPositive(); + BigInteger g = buffer.ReadBigNumPositive(); + BigInteger pubKey = buffer.ReadBigNumPositive(); + + result = new DsaPublicKeyParameters(pubKey, new DsaParameters(p, q, g)); + } + else if (magic.StartsWith(ECDSA)) + { + string curveName = buffer.ReadString(); + DerObjectIdentifier oid = SshNamedCurves.GetByName(curveName); + X9ECParameters x9ECParameters = SshNamedCurves.GetParameters(oid) ?? + throw new InvalidOperationException("unable to find curve for " + magic + " using curve name " + curveName); + var curve = x9ECParameters.Curve; + byte[] pointRaw = buffer.ReadBlock(); + + result = new ECPublicKeyParameters( + curve.DecodePoint(pointRaw), + new ECNamedDomainParameters(oid, x9ECParameters)); + } + else if (ED_25519.Equals(magic)) + { + byte[] pubKeyBytes = buffer.ReadBlock(); + if (pubKeyBytes.Length != Ed25519PublicKeyParameters.KeySize) + { + throw new InvalidOperationException("public key value of wrong length"); + } + + result = new Ed25519PublicKeyParameters(pubKeyBytes, 0); + } + + if (result == null) + { + throw new ArgumentException("unable to parse key"); + } + + if (buffer.HasRemaining()) + { + throw new ArgumentException("decoded key has trailing data"); + } + + return result; + } + } +} diff --git a/crypto/src/crypto/util/SshBuffer.cs b/crypto/src/crypto/util/SshBuffer.cs new file mode 100644 index 000000000..094b85364 --- /dev/null +++ b/crypto/src/crypto/util/SshBuffer.cs @@ -0,0 +1,152 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Utilities +{ + internal class SshBuffer + { + private readonly byte[] buffer; + private int pos = 0; + + internal SshBuffer(byte[] magic, byte[] buffer) + { + this.buffer = buffer; + for (int i = 0; i != magic.Length; i++) + { + if (magic[i] != buffer[i]) + { + throw new ArgumentException("magic-number incorrect"); + } + } + + pos += magic.Length; + } + + internal SshBuffer(byte[] buffer) + { + this.buffer = buffer; + } + + public int ReadU32() + { + if (pos > buffer.Length - 4) + { + throw new ArgumentOutOfRangeException("4 bytes for U32 exceeds buffer."); + } + + int i = (buffer[pos++] & 0xFF) << 24; + i |= (buffer[pos++] & 0xFF) << 16; + i |= (buffer[pos++] & 0xFF) << 8; + i |= buffer[pos++] & 0xFF; + + return i; + } + + public string ReadString() + { + return Strings.FromByteArray(ReadBlock()); + } + + public byte[] ReadBlock() + { + int len = ReadU32(); + if (len == 0) + { + return new byte[0]; + } + + if (pos > buffer.Length - len) + { + throw new ArgumentException("not enough data for block"); + } + + int start = pos; pos += len; + return Arrays.CopyOfRange(buffer, start, pos); + } + + public void SkipBlock() + { + int len = ReadU32(); + if (pos > buffer.Length - len) + { + throw new ArgumentException("not enough data for block"); + } + + pos += len; + } + + public byte[] ReadPaddedBlock() + { + return ReadPaddedBlock(8); + } + + public byte[] ReadPaddedBlock(int blockSize) + { + int len = ReadU32(); + if (len == 0) + { + return new byte[0]; + } + + if (pos > buffer.Length - len) + { + throw new ArgumentException("not enough data for block"); + } + + int align = len % blockSize; + if (0 != align) + { + throw new ArgumentException("missing padding"); + } + + int start = pos; pos += len; + int end = pos; + + if (len > 0) + { + // TODO If encryption is supported, should be constant-time + int lastByte = buffer[pos - 1] & 0xFF; + if (0 < lastByte && lastByte < blockSize) + { + int padCount = lastByte; + end -= padCount; + + for (int i = 1, padPos = end; i <= padCount; ++i, ++padPos) + { + if (i != (buffer[padPos] & 0xFF)) + { + throw new ArgumentException("incorrect padding"); + } + } + } + } + + return Arrays.CopyOfRange(buffer, start, end); + } + + public BigInteger ReadBigNumPositive() + { + int len = ReadU32(); + if (pos + len > buffer.Length) + { + throw new ArgumentException("not enough data for big num"); + } + + int start = pos; pos += len; + byte[] d = Arrays.CopyOfRange(buffer, start, pos); + return new BigInteger(1, d); + } + + public byte[] GetBuffer() + { + return Arrays.Clone(buffer); + } + + public bool HasRemaining() + { + return pos < buffer.Length; + } + } +} diff --git a/crypto/src/crypto/util/SshBuilder.cs b/crypto/src/crypto/util/SshBuilder.cs new file mode 100644 index 000000000..9f2f35360 --- /dev/null +++ b/crypto/src/crypto/util/SshBuilder.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Utilities +{ + internal class SshBuilder + { + private readonly MemoryStream bos = new MemoryStream(); + + public void U32(uint value) + { + bos.WriteByte(Convert.ToByte(value >> 24 & 0xFF)); + bos.WriteByte(Convert.ToByte(value >> 16 & 0xFF)); + bos.WriteByte(Convert.ToByte(value >> 8 & 0xFF)); + bos.WriteByte(Convert.ToByte(value & 0xFF)); + } + + public void WriteBigNum(BigInteger n) + { + WriteBlock(n.ToByteArray()); + } + + public void WriteBlock(byte[] value) + { + U32((uint)value.Length); + try + { + bos.Write(value, 0, value.Length); + } + catch (IOException e) + { + throw new InvalidOperationException(e.Message, e); + } + } + + public void WriteBytes(byte[] value) + { + try + { + bos.Write(value, 0, value.Length); + } + catch (IOException e) + { + throw new InvalidOperationException(e.Message, e); + } + } + + public void WriteString(string str) + { + WriteBlock(Strings.ToByteArray(str)); + } + + public byte[] GetBytes() + { + return bos.ToArray(); + } + + public byte[] GetPaddedBytes() + { + return GetPaddedBytes(8); + } + + public byte[] GetPaddedBytes(int blockSize) + { + int align = (int)bos.Length % blockSize; + if (0 != align) + { + int padCount = blockSize - align; + for (int i = 1; i <= padCount; ++i) + { + bos.WriteByte(Convert.ToByte(i)); + } + } + return bos.ToArray(); + } + } +} diff --git a/crypto/src/crypto/util/SshNamedCurves.cs b/crypto/src/crypto/util/SshNamedCurves.cs new file mode 100644 index 000000000..d97c2476e --- /dev/null +++ b/crypto/src/crypto/util/SshNamedCurves.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.Linq; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.EC; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math.EC; + +namespace Org.BouncyCastle.Crypto.Utilities +{ + public class SshNamedCurves + { + private static readonly Dictionary OidMap = + new Dictionary + { + { "nistp256", SecObjectIdentifiers.SecP256r1 }, + { "nistp384", SecObjectIdentifiers.SecP384r1 }, + { "nistp521", SecObjectIdentifiers.SecP521r1 }, + { "nistk163", SecObjectIdentifiers.SecT163k1 }, + { "nistp192", SecObjectIdentifiers.SecP192r1 }, + { "nistp224", SecObjectIdentifiers.SecP224r1 }, + { "nistk233", SecObjectIdentifiers.SecT233k1 }, + { "nistb233", SecObjectIdentifiers.SecT233r1 }, + { "nistk283", SecObjectIdentifiers.SecT283k1 }, + { "nistk409", SecObjectIdentifiers.SecT409k1 }, + { "nistb409", SecObjectIdentifiers.SecT409r1 }, + { "nistt571", SecObjectIdentifiers.SecT571k1 } + }; + + + private static readonly Dictionary CurveNameToSSHName = + new Dictionary + { + {"secp256r1", "nistp256"}, + {"secp384r1", "nistp384"}, + {"secp521r1", "nistp521"}, + {"sect163k1", "nistk163"}, + {"secp192r1", "nistp192"}, + {"secp224r1", "nistp224"}, + {"sect233k1", "nistk233"}, + {"sect233r1", "nistb233"}, + {"sect283k1", "nistk283"}, + {"sect409k1", "nistk409"}, + {"sect409r1", "nistb409"}, + {"sect571k1", "nistt571"} + }; + + private static readonly Dictionary CurveMap = + CustomNamedCurves.Names.ToDictionary(k => CustomNamedCurves.GetByNameLazy(k).Curve, v => v); + + private static readonly Dictionary OidToName = + OidMap.ToDictionary(k => k.Value, v => v.Key); + + + public static DerObjectIdentifier GetByName(string sshName) + { + return OidMap[sshName]; + } + + public static X9ECParameters GetParameters(string sshName) + { + return NistNamedCurves.GetByOid(OidMap[sshName.ToLower()]); + } + + public static X9ECParameters GetParameters(DerObjectIdentifier oid) + { + return NistNamedCurves.GetByOid(oid); + } + + public static string GetName(DerObjectIdentifier oid) + { + return OidToName[oid]; + } + + public static string GetNameForParameters(ECDomainParameters parameters) + { + if (parameters is ECNamedDomainParameters) + { + return GetName(((ECNamedDomainParameters)parameters).Name); + } + + return GetNameForParameters(parameters.Curve); + } + + public static string GetNameForParameters(ECCurve curve) + { + return CurveNameToSSHName[CurveMap[curve]]; + } + } +} diff --git a/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs b/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs deleted file mode 100644 index 0ddd90773..000000000 --- a/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs +++ /dev/null @@ -1,302 +0,0 @@ -using System; - -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 static class OpenSshPrivateKeyUtilities - { - ///

Magic value for proprietary OpenSSH private key. - /// C string so null terminated. - private static readonly byte[] AUTH_MAGIC = Strings.ToByteArray("openssh-key-v1\0"); - - /** - * 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 ArgumentNullException(nameof(parameters)); - - 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 = OpenSshPublicKeyUtilities.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. - *

- * This method accepts the body of the OpenSSH private key. - * The easiest way to extract the body is to use PemReader, for example: - *

- * 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. - OpenSshPublicKeyUtilities.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; - } -} -} diff --git a/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs b/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs deleted file mode 100644 index 02e6928e0..000000000 --- a/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; - -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math; - -namespace Org.BouncyCastle.Utilities.SSH -{ - public static class OpenSshPublicKeyUtilities - { - private static readonly String RSA = "ssh-rsa"; - private static readonly String ECDSA = "ecdsa"; - private static readonly String ED_25519 = "ssh-ed25519"; - private static readonly String DSS = "ssh-dss"; - - /** - * Parse a public key. - *

- * This method accepts the bytes that are Base64 encoded in an OpenSSH public key file. - * - * @param encoded The key. - * @return An AsymmetricKeyParameter instance. - */ - public static AsymmetricKeyParameter ParsePublicKey(byte[] encoded) - { - SshBuffer buffer = new SshBuffer(encoded); - return ParsePublicKey(buffer); - } - - /** - * Encode a public key from an AsymmetricKeyParameter instance. - * - * @param cipherParameters The key to encode. - * @return the key OpenSSH encoded. - * @throws IOException - */ - public static byte[] EncodePublicKey(AsymmetricKeyParameter cipherParameters) - { - if (cipherParameters == null) - { - throw new ArgumentException("cipherParameters was null."); - } - - if (cipherParameters is RsaKeyParameters) - { - if (cipherParameters.IsPrivate) - { - throw new ArgumentException("RSAKeyParamaters was for encryption"); - } - - RsaKeyParameters rsaPubKey = (RsaKeyParameters)cipherParameters; - - SshBuilder builder = new SshBuilder(); - builder.WriteString(RSA); - builder.WriteBigNum(rsaPubKey.Exponent); - builder.WriteBigNum(rsaPubKey.Modulus); - - return builder.GetBytes(); - - } - else if (cipherParameters is ECPublicKeyParameters ecPublicKey) - { - SshBuilder builder = new SshBuilder(); - - // - // checked for named curve parameters.. - // - String name = SshNamedCurves.GetNameForParameters(ecPublicKey.Parameters); - - if (name == null) - { - throw new ArgumentException("unable to derive ssh curve name for " + ecPublicKey.Parameters.Curve.GetType().Name); - } - - builder.WriteString(ECDSA + "-sha2-" + name); // Magic - builder.WriteString(name); - builder.WriteBlock(ecPublicKey.Q.GetEncoded(false)); //Uncompressed - return builder.GetBytes(); - } - else if (cipherParameters is DsaPublicKeyParameters dsaPubKey) - { - DsaParameters dsaParams = dsaPubKey.Parameters; - - SshBuilder builder = new SshBuilder(); - builder.WriteString(DSS); - builder.WriteBigNum(dsaParams.P); - builder.WriteBigNum(dsaParams.Q); - builder.WriteBigNum(dsaParams.G); - builder.WriteBigNum(dsaPubKey.Y); - return builder.GetBytes(); - } - else if (cipherParameters is Ed25519PublicKeyParameters ed25519PublicKey) - { - SshBuilder builder = new SshBuilder(); - builder.WriteString(ED_25519); - builder.WriteBlock(ed25519PublicKey.GetEncoded()); - return builder.GetBytes(); - } - - throw new ArgumentException("unable to convert " + cipherParameters.GetType().Name + " to private key"); - } - - /** - * Parse a public key from an SSHBuffer instance. - * - * @param buffer containing the SSH public key. - * @return A CipherParameters instance. - */ - private static AsymmetricKeyParameter ParsePublicKey(SshBuffer buffer) - { - AsymmetricKeyParameter result = null; - - string magic = buffer.ReadString(); - if (RSA.Equals(magic)) - { - BigInteger e = buffer.ReadBigNumPositive(); - BigInteger n = buffer.ReadBigNumPositive(); - result = new RsaKeyParameters(false, n, e); - } - else if (DSS.Equals(magic)) - { - BigInteger p = buffer.ReadBigNumPositive(); - BigInteger q = buffer.ReadBigNumPositive(); - BigInteger g = buffer.ReadBigNumPositive(); - BigInteger pubKey = buffer.ReadBigNumPositive(); - - result = new DsaPublicKeyParameters(pubKey, new DsaParameters(p, q, g)); - } - else if (magic.StartsWith(ECDSA)) - { - String curveName = buffer.ReadString(); - DerObjectIdentifier oid = SshNamedCurves.GetByName(curveName); - X9ECParameters x9ECParameters = SshNamedCurves.GetParameters(oid) ?? - throw new InvalidOperationException("unable to find curve for " + magic + " using curve name " + curveName); - var curve = x9ECParameters.Curve; - byte[] pointRaw = buffer.ReadBlock(); - - result = new ECPublicKeyParameters( - curve.DecodePoint(pointRaw), - new ECNamedDomainParameters(oid, x9ECParameters)); - } - else if (ED_25519.Equals(magic)) - { - byte[] pubKeyBytes = buffer.ReadBlock(); - if (pubKeyBytes.Length != Ed25519PublicKeyParameters.KeySize) - { - throw new InvalidOperationException("public key value of wrong length"); - } - - result = new Ed25519PublicKeyParameters(pubKeyBytes, 0); - } - - if (result == null) - { - throw new ArgumentException("unable to parse key"); - } - - if (buffer.HasRemaining()) - { - throw new ArgumentException("decoded key has trailing data"); - } - - return result; - } - } -} diff --git a/crypto/src/util/ssh/SSHBuffer.cs b/crypto/src/util/ssh/SSHBuffer.cs deleted file mode 100644 index 795641032..000000000 --- a/crypto/src/util/ssh/SSHBuffer.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; - -using Org.BouncyCastle.Math; - -namespace Org.BouncyCastle.Utilities.SSH -{ - internal class SshBuffer - { - private readonly byte[] buffer; - private int pos = 0; - - internal SshBuffer(byte[] magic, byte[] buffer) - { - this.buffer = buffer; - for (int i = 0; i != magic.Length; i++) - { - if (magic[i] != buffer[i]) - { - throw new ArgumentException("magic-number incorrect"); - } - } - - pos += magic.Length; - } - - internal SshBuffer(byte[] buffer) - { - this.buffer = buffer; - } - - public int ReadU32() - { - if (pos > (buffer.Length - 4)) - { - throw new ArgumentOutOfRangeException("4 bytes for U32 exceeds buffer."); - } - - int i = (buffer[pos++] & 0xFF) << 24; - i |= (buffer[pos++] & 0xFF) << 16; - i |= (buffer[pos++] & 0xFF) << 8; - i |= (buffer[pos++] & 0xFF); - - return i; - } - - public String ReadString() - { - return Strings.FromByteArray(ReadBlock()); - } - - public byte[] ReadBlock() - { - int len = ReadU32(); - if (len == 0) - { - return new byte[0]; - } - - if (pos > (buffer.Length - len)) - { - throw new ArgumentException("not enough data for block"); - } - - int start = pos; pos += len; - return Arrays.CopyOfRange(buffer, start, pos); - } - - public void SkipBlock() - { - int len = ReadU32(); - if (pos > (buffer.Length - len)) - { - throw new ArgumentException("not enough data for block"); - } - - pos += len; - } - - public byte[] ReadPaddedBlock() - { - return ReadPaddedBlock(8); - } - - public byte[] ReadPaddedBlock(int blockSize) - { - int len = ReadU32(); - if (len == 0) - { - return new byte[0]; - } - - if (pos > (buffer.Length - len)) - { - throw new ArgumentException("not enough data for block"); - } - - int align = len % blockSize; - if (0 != align) - { - throw new ArgumentException("missing padding"); - } - - int start = pos; pos += len; - int end = pos; - - if (len > 0) - { - // TODO If encryption is supported, should be constant-time - int lastByte = buffer[pos - 1] & 0xFF; - if (0 < lastByte && lastByte < blockSize) - { - int padCount = lastByte; - end -= padCount; - - for (int i = 1, padPos = end; i <= padCount; ++i, ++padPos) - { - if (i != (buffer[padPos] & 0xFF)) - { - throw new ArgumentException("incorrect padding"); - } - } - } - } - - return Arrays.CopyOfRange(buffer, start, end); - } - - public BigInteger ReadBigNumPositive() - { - int len = ReadU32(); - if (pos + len > buffer.Length) - { - throw new ArgumentException("not enough data for big num"); - } - - int start = pos; pos += len; - byte[] d = Arrays.CopyOfRange(buffer, start, pos); - return new BigInteger(1, d); - } - - public byte[] GetBuffer() - { - return Arrays.Clone(buffer); - } - - public Boolean HasRemaining() - { - return pos < buffer.Length; - } - } -} diff --git a/crypto/src/util/ssh/SSHBuilder.cs b/crypto/src/util/ssh/SSHBuilder.cs deleted file mode 100644 index 24121d0d8..000000000 --- a/crypto/src/util/ssh/SSHBuilder.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.IO; - -using Org.BouncyCastle.Math; - -namespace Org.BouncyCastle.Utilities.SSH -{ - internal class SshBuilder - { - private readonly MemoryStream bos = new MemoryStream(); - - public void U32(uint value) - { - bos.WriteByte(Convert.ToByte((value >> 24) & 0xFF)); - bos.WriteByte(Convert.ToByte((value >> 16) & 0xFF)); - bos.WriteByte(Convert.ToByte((value >> 8) & 0xFF)); - bos.WriteByte(Convert.ToByte(value & 0xFF)); - } - - public void WriteBigNum(BigInteger n) - { - WriteBlock(n.ToByteArray()); - } - - public void WriteBlock(byte[] value) - { - U32((uint)value.Length); - try - { - bos.Write(value, 0, value.Length); - } - catch (IOException e) - { - throw new InvalidOperationException(e.Message, e); - } - } - - public void WriteBytes(byte[] value) - { - try - { - bos.Write(value, 0, value.Length); - } - catch (IOException e) - { - throw new InvalidOperationException(e.Message, e); - } - } - - public void WriteString(String str) - { - WriteBlock(Strings.ToByteArray(str)); - } - - public byte[] GetBytes() - { - return bos.ToArray(); - } - - public byte[] GetPaddedBytes() - { - return GetPaddedBytes(8); - } - - public byte[] GetPaddedBytes(int blockSize) - { - int align = (int)bos.Length % blockSize; - if (0 != align) - { - int padCount = blockSize - align; - for (int i = 1; i <= padCount; ++i) - { - bos.WriteByte(Convert.ToByte(i)); - } - } - return bos.ToArray(); - } - } -} diff --git a/crypto/src/util/ssh/SSHNamedCurves.cs b/crypto/src/util/ssh/SSHNamedCurves.cs deleted file mode 100644 index 6839627b8..000000000 --- a/crypto/src/util/ssh/SSHNamedCurves.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.Nist; -using Org.BouncyCastle.Asn1.Sec; -using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Crypto.EC; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math.EC; - -namespace Org.BouncyCastle.Utilities.SSH -{ - public class SshNamedCurves - { - private static readonly Dictionary OidMap = - new Dictionary - { - { "nistp256", SecObjectIdentifiers.SecP256r1 }, - { "nistp384", SecObjectIdentifiers.SecP384r1 }, - { "nistp521", SecObjectIdentifiers.SecP521r1 }, - { "nistk163", SecObjectIdentifiers.SecT163k1 }, - { "nistp192", SecObjectIdentifiers.SecP192r1 }, - { "nistp224", SecObjectIdentifiers.SecP224r1 }, - { "nistk233", SecObjectIdentifiers.SecT233k1 }, - { "nistb233", SecObjectIdentifiers.SecT233r1 }, - { "nistk283", SecObjectIdentifiers.SecT283k1 }, - { "nistk409", SecObjectIdentifiers.SecT409k1 }, - { "nistb409", SecObjectIdentifiers.SecT409r1 }, - { "nistt571", SecObjectIdentifiers.SecT571k1 } - }; - - - private static readonly Dictionary CurveNameToSSHName = - new Dictionary - { - {"secp256r1", "nistp256"}, - {"secp384r1", "nistp384"}, - {"secp521r1", "nistp521"}, - {"sect163k1", "nistk163"}, - {"secp192r1", "nistp192"}, - {"secp224r1", "nistp224"}, - {"sect233k1", "nistk233"}, - {"sect233r1", "nistb233"}, - {"sect283k1", "nistk283"}, - {"sect409k1", "nistk409"}, - {"sect409r1", "nistb409"}, - {"sect571k1", "nistt571"} - }; - - private static readonly Dictionary CurveMap = - CustomNamedCurves.Names.ToDictionary(k => CustomNamedCurves.GetByNameLazy(k).Curve, v => v); - - private static readonly Dictionary OidToName = - OidMap.ToDictionary(k => k.Value, v => v.Key); - - - public static DerObjectIdentifier GetByName(string sshName) - { - return OidMap[sshName]; - } - - public static X9ECParameters GetParameters(string sshName) - { - return NistNamedCurves.GetByOid(OidMap[sshName.ToLower()]); - } - - public static X9ECParameters GetParameters(DerObjectIdentifier oid) - { - return NistNamedCurves.GetByOid(oid); - } - - public static string GetName(DerObjectIdentifier oid) - { - return OidToName[oid]; - } - - public static string GetNameForParameters(ECDomainParameters parameters) - { - if (parameters is ECNamedDomainParameters) - { - return GetName(((ECNamedDomainParameters)parameters).Name); - } - - return GetNameForParameters(parameters.Curve); - } - - public static string GetNameForParameters(ECCurve curve) - { - return CurveNameToSSHName[CurveMap[curve]]; - } - } -} diff --git a/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs b/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs deleted file mode 100644 index a647ba652..000000000 --- a/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs +++ /dev/null @@ -1,490 +0,0 @@ -using System; -using System.IO; - -using NUnit.Framework; - -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Crypto.Signers; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.Utilities; -using Org.BouncyCastle.Utilities.Encoders; -using Org.BouncyCastle.Utilities.IO.Pem; -using Org.BouncyCastle.Utilities.SSH; - -namespace Org.BouncyCastle.Crypto.Tests -{ - [TestFixture] - public class OpenSshKeyParsingTests - { - private static SecureRandom secureRandom = new SecureRandom(); - - private static readonly string rsa1024Key = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn\n" - + "NhAAAAAwEAAQAAAIEA37C9iHf9kS3ekS8xVE4p5/bmA7Yc37gXqN10W6c53FzVMiT9ZzVm\n" - + "GXqJCRTpLjlX4NgRGHK3nLwyrEhR5JmTrLAXfwb04y3AcdZWZwkZBiXR2rFToEnXNobrvG\n" - + "gmXEshBvCq6kUcGWf1FnW4av0kbVRkfiAjM1aMae1KIwlNMDcAAAIIVPY+b1T2Pm8AAAAH\n" - + "c3NoLXJzYQAAAIEA37C9iHf9kS3ekS8xVE4p5/bmA7Yc37gXqN10W6c53FzVMiT9ZzVmGX\n" - + "qJCRTpLjlX4NgRGHK3nLwyrEhR5JmTrLAXfwb04y3AcdZWZwkZBiXR2rFToEnXNobrvGgm\n" - + "XEshBvCq6kUcGWf1FnW4av0kbVRkfiAjM1aMae1KIwlNMDcAAAADAQABAAAAgCWqIc/HvH\n" - + "dkjNRPaPP0EVRQm1xGnsgAvGMfnscL+k8jhnZiChUFxcJGgqp3zeeNmkFuwDoonsgSXEns\n" - + "B3YBcf7SE//XNMGrGi2FAQTccoTm80NLY77wONST2DNPqxY5xTsTiOJx/DPnru84laq1ae\n" - + "t7WiNZCxsmuC0sPYDAG515AAAAQQDzeUo4QQbByJ9JVS0zcj26HKwGZSxxVb1Flq2Y+w0W\n" - + "E/4GuYvh3ujXlwEankjYUNGNI0/u0NCzuDPZzBx9LZdeAAAAQQD9TiakDmscY9Dd8bEBL+\n" - + "cAhCHrdxtx9ND/793cQNkpm10NL0Fz4jXQfn2/Z7nLFKmMzJlQXzUHH/itzWg9s0MlAAAA\n" - + "QQDiEe/BJMLRZ+94n98VCEr7E+eG2isQctxiAowH7o/wp5WAkFSD9W58dqUobuneXleG+F\n" - + "DAfXzFhYvNE+TdLXUrAAAADm1hcmtAYmFybmFjbGVzAQIDBA==\n" - + "-----END OPENSSH PRIVATE KEY-----\n"; - private static readonly string rsa2048Key = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn\n" - + "NhAAAAAwEAAQAAAQEArxWa1zW+Uf0lUrYoL1yqgTYUT1TfUkfojrhguPB1s/1AEMj8sueu\n" - + "YDtLozZW/GB+KwO+nzC48CmqsCbCEOqalmdRIQCCQIBs776c0KLnhqzHCmj0Q+6gM0KvUG\n" - + "z8elzJ8LZuTj5xGRDvFxli4yl2M119X7K2JMci18N95rszioxDECSWg2Arvd25kMKBK5MA\n" - + "qJjosvxr46soRmxiAHeGzinoLXgpLh9axwySpJ0WVGPl079ZtaYs/XpSoh9HXqCgwnsVy9\n" - + "JscWbmtaAktjMw2zTfOvmFs9PVJXtXQRzP4nvtT6myK/7v8tPeg8yLnAot9erklHcUOEyb\n" - + "1LsOrk68+QAAA8j/Xs/E/17PxAAAAAdzc2gtcnNhAAABAQCvFZrXNb5R/SVStigvXKqBNh\n" - + "RPVN9SR+iOuGC48HWz/UAQyPyy565gO0ujNlb8YH4rA76fMLjwKaqwJsIQ6pqWZ1EhAIJA\n" - + "gGzvvpzQoueGrMcKaPRD7qAzQq9QbPx6XMnwtm5OPnEZEO8XGWLjKXYzXX1fsrYkxyLXw3\n" - + "3muzOKjEMQJJaDYCu93bmQwoErkwComOiy/GvjqyhGbGIAd4bOKegteCkuH1rHDJKknRZU\n" - + "Y+XTv1m1piz9elKiH0deoKDCexXL0mxxZua1oCS2MzDbNN86+YWz09Ule1dBHM/ie+1Pqb\n" - + "Ir/u/y096DzIucCi316uSUdxQ4TJvUuw6uTrz5AAAAAwEAAQAAAQBPpNBO3Y+51CHKQjp9\n" - + "cPXO2T7b54u+7h8H7S9ycU/ZlHY0LHlnGKTl+ZMqp2liXLKH9qgb2hoGha2ze64D6/RuPo\n" - + "lVLdoSZVkopdjHv5L6XFYekierTz1olAkT2L/xGYxzB0meJiFkeaOJKm8lTpMKQpjpk23v\n" - + "xPZAmBkJgFatyueHaVWGYp0KzUDpdMcS97R6CWCGrYlAUP3F1meC9+Sb3d94qxeqLZsgEn\n" - + "PYJs1Q7fyL4jYBYm9/pA9O5RLKMQwqY7Qln7l2XTyhavZCIxTmAa6lEf32yB3+EoQR+YEz\n" - + "eCXXSClbMcnnx83jYyV5uNxN27VJAlgeN7J2ZyJTLlKRAAAAgAUnKuxYaYezMWyBShwR4N\n" - + "eVAW8vT3CBxsMR/v3u6XmLTzjq4r0gKCxofnnj972uK0LvyTZ21/00MSl0KaAjJySl2hLj\n" - + "BNQA3TcDXnLEc5KcsKZdDhuWkHGmaoajDp/okfQd6CxuKaBKG/OFdbYqVgOOVeACUUWxT4\n" - + "NN4e3CxTWQAAAAgQDV3vzDCQanGAXMKZSxfHUU63Tmh+2NcB1I6Sb0/CwpBgLH1y0CTB9r\n" - + "c8TLSs6HoHx1lfzOp6Yj7BQ9CWHS94Mi+RYBF+SpaMLoZKqCU4Q3UWiHiOyPnMaohAdvRE\n" - + "gJkaY2OAkFaaCI31rwBrs6b5U/ErtRTUZNJEI7OCi6wDBfBwAAAIEA0ZKyuUW5+VFcTyuR\n" - + "1G0ky5uihtJryFCjA2fzu7tgobm0gsIgSDClp9TdMh5CDyJo0R9fQnH8Lki0Ku+jgc4X+a\n" - + "/XMw47d1iL7Hdu9NAJsplezKD5Unso4xJRXhLnXUT5FT8lSgwE+9xUBuILKUmZQa20ejKM\n" - + "20U6szOxEEclA/8AAAAObWFya0BiYXJuYWNsZXMBAgMEBQ==\n" - + "-----END OPENSSH PRIVATE KEY-----\n"; - private static readonly string rsa3072Key = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - + "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\n" - + "-----END OPENSSH PRIVATE KEY-----\n"; - private static readonly string rsa4096Key = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - + "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\n" - + "-----END OPENSSH PRIVATE KEY-----\n"; - private static readonly string ecdsa256Key = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n" - + "1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS9VjynnoLGUcT2hXXPkFwGfbleI4Ln\n" - + "1kkgt2UgibKXw9NtesSqpKdEBDW5Kh2nmqLCIk+fdbsTGkxlfaYBtUrkAAAAqBQCER8UAh\n" - + "EfAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL1WPKeegsZRxPaF\n" - + "dc+QXAZ9uV4jgufWSSC3ZSCJspfD0216xKqkp0QENbkqHaeaosIiT591uxMaTGV9pgG1Su\n" - + "QAAAAgbAJJUVcjwwU/olgrxgINJ1DViX6GcCBhgeH8wAXiNKoAAAAObWFya0BiYXJuYWNs\n" - + "ZXMBAg==\n" - + "-----END OPENSSH PRIVATE KEY-----\n"; - private static readonly string ecdsa384Key = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS\n" - + "1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQS0yKimt2kBeyNKUqNivPfSPBVyU4jH\n" - + "9+6hNsRIJG4NKRgKdIOIiOOLm6pGLUmwN4yDS+0ssdPxwRthQzL879HRtwbqAAb1ShK0CT\n" - + "rljAhk9+SUgrOqWnKL2Ngo1uU5KZgAAADYJC2IQSQtiEEAAAATZWNkc2Etc2hhMi1uaXN0\n" - + "cDM4NAAAAAhuaXN0cDM4NAAAAGEEtMioprdpAXsjSlKjYrz30jwVclOIx/fuoTbESCRuDS\n" - + "kYCnSDiIjji5uqRi1JsDeMg0vtLLHT8cEbYUMy/O/R0bcG6gAG9UoStAk65YwIZPfklIKz\n" - + "qlpyi9jYKNblOSmYAAAAMQChvecXe7PGUVG0Pz2IgM9f80YLXdarf98sRptbGSIPwu8KlW\n" - + "OlGv0Any5ue51/I5wAAAAObWFya0BiYXJuYWNsZXMB\n" - + "-----END OPENSSH PRIVATE KEY-----\n"; - private static readonly string ecdsa521Key = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS\n" - + "1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA90An5exsl3UEU0d8fhqV8rgmoyzJ\n" - + "21sZYrjFV+bs583tbSIMYAapk8jSKtk+r1z48KQdsR9czydmy2yYbdXruXMBPdQrf+11BB\n" - + "dCs1E9iFet1UB8OruVeduD5dm0In7yJK1Qo18xe0NpOjOHeZ1ixAxdOt9zuolAlBTwZYth\n" - + "FMESME8AAAEQApLNRAKSzUQAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ\n" - + "AAAIUEAPdAJ+XsbJd1BFNHfH4alfK4JqMsydtbGWK4xVfm7OfN7W0iDGAGqZPI0irZPq9c\n" - + "+PCkHbEfXM8nZstsmG3V67lzAT3UK3/tdQQXQrNRPYhXrdVAfDq7lXnbg+XZtCJ+8iStUK\n" - + "NfMXtDaTozh3mdYsQMXTrfc7qJQJQU8GWLYRTBEjBPAAAAQgFHl5a1JDqcCeaAx84z3u/v\n" - + "z7dyVl4uohlQPaiZ+hhtbbUg6oLMnVGGjjmviR0C0aDzx0xDEsK8TseFd16mBWpOnAAAAA\n" - + "5tYXJrQGJhcm5hY2xlcwECAwQ=\n" - + "-----END OPENSSH PRIVATE KEY-----\n"; - - [Test] - public void TestDsa() - { - var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey(Base64.Decode("AAAAB3NzaC1kc3MAAACBAJBB5+S4kZZYZLswaQ/zm3GM7YWmHsumwo/Xxu+z6Cg2l5PUoiBBZ4ET9EhhQuL2ja/zrCMCi0ZwiSRuSp36ayPrHLbNJb3VdOuJg8xExRa6F3YfVZfcTPUEKh6FU72fI31HrQmi4rpyHnWxL/iDX496ZG2Hdq6UkPISQpQwj4TtAAAAFQCP9TXcVahR/2rpfEhvdXR0PfhbRwAAAIBdXzAVqoOtb9zog6lNF1cGS1S06W9W/clvuwq2xF1s3bkoI/xUbFSc0IAPsGl2kcB61PAZqcop50lgpvYzt8cq/tbqz3ypq1dCQ0xdmJHj975QsRFax+w6xQ0kgpBhwcS2EOizKb+C+tRzndGpcDSoSMuVXp9i4wn5pJSTZxAYFQAAAIEAhQZc687zYxrEDR/1q6m4hw5GFxuVvLsC+bSHtMF0c11Qy4IPg7mBeP7K5Kq4WyJPtmZhuc5Bb12bJQR6qgd1uLn692fe1UK2kM6eWXBzhlzZ54BslfSKHGNN4qH+ln3Zaf/4rpKE7fvoinkrgkOZmj0PMx9D6wlpHKkXMUxeXtc=")); - - var privSpec = ParsePrivateKeyData("-----BEGIN DSA PRIVATE KEY-----\n" + - "MIIBuwIBAAKBgQCQQefkuJGWWGS7MGkP85txjO2Fph7LpsKP18bvs+goNpeT1KIg\n" + - "QWeBE/RIYULi9o2v86wjAotGcIkkbkqd+msj6xy2zSW91XTriYPMRMUWuhd2H1WX\n" + - "3Ez1BCoehVO9nyN9R60JouK6ch51sS/4g1+PemRth3aulJDyEkKUMI+E7QIVAI/1\n" + - "NdxVqFH/aul8SG91dHQ9+FtHAoGAXV8wFaqDrW/c6IOpTRdXBktUtOlvVv3Jb7sK\n" + - "tsRdbN25KCP8VGxUnNCAD7BpdpHAetTwGanKKedJYKb2M7fHKv7W6s98qatXQkNM\n" + - "XZiR4/e+ULERWsfsOsUNJIKQYcHEthDosym/gvrUc53RqXA0qEjLlV6fYuMJ+aSU\n" + - "k2cQGBUCgYEAhQZc687zYxrEDR/1q6m4hw5GFxuVvLsC+bSHtMF0c11Qy4IPg7mB\n" + - "eP7K5Kq4WyJPtmZhuc5Bb12bJQR6qgd1uLn692fe1UK2kM6eWXBzhlzZ54BslfSK\n" + - "HGNN4qH+ln3Zaf/4rpKE7fvoinkrgkOZmj0PMx9D6wlpHKkXMUxeXtcCFELnLOJ8\n" + - "D0akSCUFY/iDLo/KnOIH\n" + - "-----END DSA PRIVATE KEY-----\n"); - - byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); - - var signer = new DsaSigner(); - signer.Init(true, privSpec); - BigInteger[] rs = signer.GenerateSignature(originalMessage); - - signer.Init(false, pubSpec); - Assert.IsTrue(signer.VerifySignature(originalMessage, rs[0], rs[1]), "DSA test"); - } - - [Test] - public void TestECDsa_CurvesFromSshKeyGen() - { - var pairs = new Tuple []{ - Tuple.Create( - "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBbxKE+/DXstQZmwH7Wso8SUt8LvYoMQpxN/7INC0lMn7mNCbxJcSOCfucBuWOrdoFyFZUkGli2mzKj3hJlcPiI=", - "-----BEGIN OPENSSH PRIVATE KEY-----\n" + - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n" + - "1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQW8ShPvw17LUGZsB+1rKPElLfC72KD\n" + - "EKcTf+yDQtJTJ+5jQm8SXEjgn7nAbljq3aBchWVJBpYtpsyo94SZXD4iAAAAuKFclDShXJ\n" + - "Q0AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBbxKE+/DXstQZmw\n" + - "H7Wso8SUt8LvYoMQpxN/7INC0lMn7mNCbxJcSOCfucBuWOrdoFyFZUkGli2mzKj3hJlcPi\n" + - "IAAAAhAP4L/ciGBDF4HoQSvMaKM8svW4Ss0uYi7HkZ1sn/zCe0AAAAHW1lZ2Fud29vZHNA\n" + - "dHljaGUtMzI2NS5nYXRld2F5AQI=\n" + - "-----END OPENSSH PRIVATE KEY-----\n" - ), - Tuple.Create( - "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBOT0Cc/zauJsOWo/0P0sMNeyFI5Enz3+lKJtjWXQD7DpFgZmG5Ise8IXR5/ot7fo0kWlYQrye/uSmNmWBuDvOpBCHOnyR6Kaej36qoOO/gwbH+mezSYXSxCTA9Qb8VzxLA==", - "-----BEGIN OPENSSH PRIVATE KEY-----\n" + - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS\n" + - "1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTk9AnP82ribDlqP9D9LDDXshSORJ89\n" + - "/pSibY1l0A+w6RYGZhuSLHvCF0ef6Le36NJFpWEK8nv7kpjZlgbg7zqQQhzp8keimno9+q\n" + - "qDjv4MGx/pns0mF0sQkwPUG/Fc8SwAAADorZ3naK2d52gAAAATZWNkc2Etc2hhMi1uaXN0\n" + - "cDM4NAAAAAhuaXN0cDM4NAAAAGEE5PQJz/Nq4mw5aj/Q/Sww17IUjkSfPf6Uom2NZdAPsO\n" + - "kWBmYbkix7whdHn+i3t+jSRaVhCvJ7+5KY2ZYG4O86kEIc6fJHopp6Pfqqg47+DBsf6Z7N\n" + - "JhdLEJMD1BvxXPEsAAAAMQDLno+rINnY7/Ht1WmSGZYJ3EMPtysbxuBnQFEL4USa3kyAb1\n" + - "QMR6+jtqraKtE7kLwAAAAdbWVnYW53b29kc0B0eWNoZS0zMjY1LmdhdGV3YXkBAg==\n" + - "-----END OPENSSH PRIVATE KEY-----\n" - ), - Tuple.Create( - "AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADXE/q1WSR002vRI+tiPLpdRjzeymSk+RjD7ZIC9CndqLmI0rhTMh5xReAzved12BH9lQJIGIw4YoIQDudsMbRUsQEjFvbFzSXLJBYWdZf8Voa/97/R9w/i8bKUMUPP0disypZlGdQn5+XvzHG6bhX2Qr9aJacGFZoVHugF/M8QyC+GyA==", - "-----BEGIN OPENSSH PRIVATE KEY-----\n" + - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS\n" + - "1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA1xP6tVkkdNNr0SPrYjy6XUY83spk\n" + - "pPkYw+2SAvQp3ai5iNK4UzIecUXgM73nddgR/ZUCSBiMOGKCEA7nbDG0VLEBIxb2xc0lyy\n" + - "QWFnWX/FaGv/e/0fcP4vGylDFDz9HYrMqWZRnUJ+fl78xxum4V9kK/WiWnBhWaFR7oBfzP\n" + - "EMgvhsgAAAEgs+rbdbPq23UAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ\n" + - "AAAIUEANcT+rVZJHTTa9Ej62I8ul1GPN7KZKT5GMPtkgL0Kd2ouYjSuFMyHnFF4DO953XY\n" + - "Ef2VAkgYjDhighAO52wxtFSxASMW9sXNJcskFhZ1l/xWhr/3v9H3D+LxspQxQ8/R2KzKlm\n" + - "UZ1Cfn5e/McbpuFfZCv1olpwYVmhUe6AX8zxDIL4bIAAAAQgCM8ojULpNk3UhBZhPfK+Tw\n" + - "QjT9MHU0OTi4twvKPAE0vOLQ/C1g9AMlspyKxS2NKx2gxxXISowFGNL6Jkx9198ElQAAAB\n" + - "1tZWdhbndvb2RzQHR5Y2hlLTMyNjUuZ2F0ZXdheQECAwQF\n" + - "-----END OPENSSH PRIVATE KEY-----\n" - ) - }; - - string[] ecPriv = { ecdsa256Key, ecdsa384Key, ecdsa521Key }; - for (int i = 0; i != ecPriv.Length; i++) - { - var privKey = (ECPrivateKeyParameters)ParsePrivateKeyData(ecPriv[i]); - - var q = privKey.Parameters.G.Multiply(privKey.D); - - DoECSigTest(new ECPublicKeyParameters(q, privKey.Parameters), privKey); - } - - for (int i = 0; i != pairs.Length; i++) - { - var pair = pairs[i]; - - var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey(Base64.Decode(pair.Item1)); - var privSpec = ParsePrivateKeyData(pair.Item2); - - DoECSigTest(pubSpec, privSpec); - - // - // Test encode - // - var recoveredPubKey = OpenSshPublicKeyUtilities.ParsePublicKey( - OpenSshPublicKeyUtilities.EncodePublicKey((AsymmetricKeyParameter)pubSpec)); - var recoveredPrivateKey = OpenSshPrivateKeyUtilities.ParsePrivateKeyBlob( - OpenSshPrivateKeyUtilities.EncodePrivateKey((AsymmetricKeyParameter)privSpec)); - - byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); - - var signer = new ECDsaSigner(); - signer.Init(true, privSpec); - BigInteger[] rs = signer.GenerateSignature(originalMessage); - - signer.Init(false, pubSpec); - Assert.IsTrue(signer.VerifySignature(originalMessage, rs[0], rs[1]), - "ECDSA test post encoded / Decode"); - } - - } - - private void DoECSigTest(AsymmetricKeyParameter pubSpec, AsymmetricKeyParameter privSpec) - { - byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); - - var signer = new ECDsaSigner(); - signer.Init(true, privSpec); - BigInteger[] rs = signer.GenerateSignature(originalMessage); - - signer.Init(false, pubSpec); - Assert.IsTrue(signer.VerifySignature(originalMessage, rs[0], rs[1]), "ECDSA test"); - } - - [Test] - public void TestECDsa() - { - var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey( - Base64.Decode("AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHq5qxGqnh93Gpbj2w1Avx1UwBl6z5bZC3Viog1yNHDZYcV6Da4YQ3i0/hN7xY7sUy9dNF6g16tJSYXQQ4tvO3g=")); - - var privSpec = ParsePrivateKeyData("-----BEGIN EC PRIVATE KEY-----\n" + - "MHcCAQEEIHeg/+m02j6nr4bO8ubfbzhs0fqOjiuIoWbvGnVg+FmpoAoGCCqGSM49\n" + - "AwEHoUQDQgAEermrEaqeH3caluPbDUC/HVTAGXrPltkLdWKiDXI0cNlhxXoNrhhD\n" + - "eLT+E3vFjuxTL100XqDXq0lJhdBDi287eA==\n" + - "-----END EC PRIVATE KEY-----\n"); - - DoECSigTest(pubSpec, privSpec); - } - - [Test] - public void TestEd25519() - { - var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey( - Base64.Decode("AAAAC3NzaC1lZDI1NTE5AAAAIM4CaV7WQcy0lht0hclgXf4Olyvzvv2fnUvQ3J8IYsWF")); - - var privSpec = ParsePrivateKeyData("-----BEGIN OPENSSH PRIVATE KEY-----\n" + - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + - "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + - "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + - "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + - "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + - "-----END OPENSSH PRIVATE KEY-----\n"); - - byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); - - Ed25519Signer signer = new Ed25519Signer(); - signer.Init(true, privSpec); - signer.BlockUpdate(originalMessage, 0, originalMessage.Length); - byte[] sig = signer.GenerateSignature(); - - signer.Init(false, pubSpec); - signer.BlockUpdate(originalMessage, 0, originalMessage.Length); - Assert.IsTrue(signer.VerifySignature(sig), "ED25519Signer test"); - } - - [Test] - public void TestFailures() - { - var data = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + - "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + - "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + - "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + - "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + - "-----END OPENSSH PRIVATE KEY-----\n"; - - byte[] blob; - using (var pemReader = new PemReader(new StringReader(data))) - { - blob = pemReader.ReadPemObject().Content; - } - - // - // Altering the check value. - // - blob[98] ^= 1; - - try - { - var privSpec = OpenSshPrivateKeyUtilities.ParsePrivateKeyBlob(blob); - Assert.Fail("Change should trigger failure."); - } - catch (InvalidOperationException iles) - { - Assert.AreEqual("private key check values are not the same", iles.Message, "Check value mismatch"); - } - - // - // Altering the cipher name. - // - - data = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + - "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + - "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + - "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + - "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + - "-----END OPENSSH PRIVATE KEY-----\n"; - - using (var pemReader = new PemReader(new StringReader(data))) - { - blob = pemReader.ReadPemObject().Content; - } - - blob[19] = (byte)'C'; - - try - { - var privSpec = OpenSshPrivateKeyUtilities.ParsePrivateKeyBlob(blob); - Assert.Fail("Change should trigger failure."); - } - catch (InvalidOperationException iles) - { - Assert.AreEqual("encrypted keys not supported", iles.Message, "enc keys not supported"); - } - } - - [Test] - public void TestRSA() - { - var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey( - Base64.Decode("AAAAB3NzaC1yc2EAAAADAQABAAAAgQDvh2BophdIp8ojwGZQR0FQ/awowXnV24nAPm+/na8MOUrdySNhOnlek4LAZl82/+Eu2t21XD6hQUiHKAj6XaNFBthTuss7Cz/tA348DLEMHD9wUtT0FXVmsxqN4BfusunbcULxxVWG2z8FvqeaGgc/Unkp9y7/kyf54pPUCBcClw==")); - - var privSpec = ParsePrivateKeyData("-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXgIBAAKBgQDvh2BophdIp8ojwGZQR0FQ/awowXnV24nAPm+/na8MOUrdySNh\n" + - "Onlek4LAZl82/+Eu2t21XD6hQUiHKAj6XaNFBthTuss7Cz/tA348DLEMHD9wUtT0\n" + - "FXVmsxqN4BfusunbcULxxVWG2z8FvqeaGgc/Unkp9y7/kyf54pPUCBcClwIDAQAB\n" + - "AoGBAOMXYEoXHgAeREE9CkOWKtDUkEJbnF0rNSB0kZIDt5BJSTeYmNh3jdYi2FX9\n" + - "OMx2MFIx4v0tJZvQvyiUxl5IJJ9ZJsYUWF+6VbcTVwYYfdVzZzP2TNyGmF9/ADZW\n" + - "wBehqP04uRlYjt94kqb4HoOKF3gJ3LC4uW9xcEltTBeHWCfhAkEA/2biF5St9/Ya\n" + - "540E4zu/FKPsxLSaT8LWCo9+X7IqIzlBQCB4GjM+nZeTm7eZOkfAFZoxwfiNde/9\n" + - "qleXXf6B2QJBAPAW+jDBC3QF4/g8n9cDxm/A3ICmcOFSychLSrydk9ZyRPbTRyQC\n" + - "YlC2mf/pCrO/yO7h189BXyQ3PXOEhnujce8CQQD7gDy0K90EiH0F94AQpA0OLj5B\n" + - "lfc/BAXycEtpwPBtrzvqAg9C/aNzXIgmly10jqNAoo7NDA2BTcrlq0uLa8xBAkBl\n" + - "7Hs+I1XnZXDIO4Rn1VRysN9rRj15ipnbDAuoUwUl7tDUMBFteg2e0kZCW/6NHIgC\n" + - "0aG6fLgVOdY+qi4lYtfFAkEAqqiBgEgSrDmnJLTm6j/Pv1mBA6b9bJbjOqomrDtr\n" + - "AWTXe+/kSCv/jYYdpNA/tDgAwEmtkWWEie6+SwJB5cXXqg==\n" + - "-----END RSA PRIVATE KEY-----\n"); - - DoRSATest(pubSpec, privSpec); - - privSpec = ParsePrivateKeyData(rsa1024Key); - pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); - - DoRSATest(pubSpec, privSpec); - - privSpec = ParsePrivateKeyData(rsa2048Key); - pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); - - DoRSATest(pubSpec, privSpec); - - privSpec = ParsePrivateKeyData(rsa3072Key); - pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); - - DoRSATest(pubSpec, privSpec); - - privSpec = ParsePrivateKeyData(rsa4096Key); - pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); - - DoRSATest(pubSpec, privSpec); - } - - private void DoRSATest(AsymmetricKeyParameter pubSpec, AsymmetricKeyParameter privSpec) - { - byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); - originalMessage[0] |= 1; - - var rsaEngine = new RsaEngine(); - rsaEngine.Init(true, privSpec); - - byte[] ct = rsaEngine.ProcessBlock(originalMessage, 0, originalMessage.Length); - - rsaEngine.Init(false, pubSpec); - byte[] result = rsaEngine.ProcessBlock(ct, 0, ct.Length); - - Assert.IsTrue(Arrays.AreEqual(originalMessage, result), "Result did not match original message"); - } - - private static AsymmetricKeyParameter ParsePrivateKeyData(string data) - { - using (var pemReader = new PemReader(new StringReader(data))) - { - return OpenSshPrivateKeyUtilities.ParsePrivateKeyBlob(pemReader.ReadPemObject().Content); - } - } - } -} diff --git a/crypto/test/src/crypto/test/OpenSshKeyParsingTest.cs b/crypto/test/src/crypto/test/OpenSshKeyParsingTest.cs new file mode 100644 index 000000000..46cac0f4c --- /dev/null +++ b/crypto/test/src/crypto/test/OpenSshKeyParsingTest.cs @@ -0,0 +1,490 @@ +using System; +using System.IO; + +using NUnit.Framework; + +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities.IO.Pem; + +namespace Org.BouncyCastle.Crypto.Tests +{ + [TestFixture] + public class OpenSshKeyParsingTests + { + private static SecureRandom secureRandom = new SecureRandom(); + + private static readonly string rsa1024Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn\n" + + "NhAAAAAwEAAQAAAIEA37C9iHf9kS3ekS8xVE4p5/bmA7Yc37gXqN10W6c53FzVMiT9ZzVm\n" + + "GXqJCRTpLjlX4NgRGHK3nLwyrEhR5JmTrLAXfwb04y3AcdZWZwkZBiXR2rFToEnXNobrvG\n" + + "gmXEshBvCq6kUcGWf1FnW4av0kbVRkfiAjM1aMae1KIwlNMDcAAAIIVPY+b1T2Pm8AAAAH\n" + + "c3NoLXJzYQAAAIEA37C9iHf9kS3ekS8xVE4p5/bmA7Yc37gXqN10W6c53FzVMiT9ZzVmGX\n" + + "qJCRTpLjlX4NgRGHK3nLwyrEhR5JmTrLAXfwb04y3AcdZWZwkZBiXR2rFToEnXNobrvGgm\n" + + "XEshBvCq6kUcGWf1FnW4av0kbVRkfiAjM1aMae1KIwlNMDcAAAADAQABAAAAgCWqIc/HvH\n" + + "dkjNRPaPP0EVRQm1xGnsgAvGMfnscL+k8jhnZiChUFxcJGgqp3zeeNmkFuwDoonsgSXEns\n" + + "B3YBcf7SE//XNMGrGi2FAQTccoTm80NLY77wONST2DNPqxY5xTsTiOJx/DPnru84laq1ae\n" + + "t7WiNZCxsmuC0sPYDAG515AAAAQQDzeUo4QQbByJ9JVS0zcj26HKwGZSxxVb1Flq2Y+w0W\n" + + "E/4GuYvh3ujXlwEankjYUNGNI0/u0NCzuDPZzBx9LZdeAAAAQQD9TiakDmscY9Dd8bEBL+\n" + + "cAhCHrdxtx9ND/793cQNkpm10NL0Fz4jXQfn2/Z7nLFKmMzJlQXzUHH/itzWg9s0MlAAAA\n" + + "QQDiEe/BJMLRZ+94n98VCEr7E+eG2isQctxiAowH7o/wp5WAkFSD9W58dqUobuneXleG+F\n" + + "DAfXzFhYvNE+TdLXUrAAAADm1hcmtAYmFybmFjbGVzAQIDBA==\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + private static readonly string rsa2048Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn\n" + + "NhAAAAAwEAAQAAAQEArxWa1zW+Uf0lUrYoL1yqgTYUT1TfUkfojrhguPB1s/1AEMj8sueu\n" + + "YDtLozZW/GB+KwO+nzC48CmqsCbCEOqalmdRIQCCQIBs776c0KLnhqzHCmj0Q+6gM0KvUG\n" + + "z8elzJ8LZuTj5xGRDvFxli4yl2M119X7K2JMci18N95rszioxDECSWg2Arvd25kMKBK5MA\n" + + "qJjosvxr46soRmxiAHeGzinoLXgpLh9axwySpJ0WVGPl079ZtaYs/XpSoh9HXqCgwnsVy9\n" + + "JscWbmtaAktjMw2zTfOvmFs9PVJXtXQRzP4nvtT6myK/7v8tPeg8yLnAot9erklHcUOEyb\n" + + "1LsOrk68+QAAA8j/Xs/E/17PxAAAAAdzc2gtcnNhAAABAQCvFZrXNb5R/SVStigvXKqBNh\n" + + "RPVN9SR+iOuGC48HWz/UAQyPyy565gO0ujNlb8YH4rA76fMLjwKaqwJsIQ6pqWZ1EhAIJA\n" + + "gGzvvpzQoueGrMcKaPRD7qAzQq9QbPx6XMnwtm5OPnEZEO8XGWLjKXYzXX1fsrYkxyLXw3\n" + + "3muzOKjEMQJJaDYCu93bmQwoErkwComOiy/GvjqyhGbGIAd4bOKegteCkuH1rHDJKknRZU\n" + + "Y+XTv1m1piz9elKiH0deoKDCexXL0mxxZua1oCS2MzDbNN86+YWz09Ule1dBHM/ie+1Pqb\n" + + "Ir/u/y096DzIucCi316uSUdxQ4TJvUuw6uTrz5AAAAAwEAAQAAAQBPpNBO3Y+51CHKQjp9\n" + + "cPXO2T7b54u+7h8H7S9ycU/ZlHY0LHlnGKTl+ZMqp2liXLKH9qgb2hoGha2ze64D6/RuPo\n" + + "lVLdoSZVkopdjHv5L6XFYekierTz1olAkT2L/xGYxzB0meJiFkeaOJKm8lTpMKQpjpk23v\n" + + "xPZAmBkJgFatyueHaVWGYp0KzUDpdMcS97R6CWCGrYlAUP3F1meC9+Sb3d94qxeqLZsgEn\n" + + "PYJs1Q7fyL4jYBYm9/pA9O5RLKMQwqY7Qln7l2XTyhavZCIxTmAa6lEf32yB3+EoQR+YEz\n" + + "eCXXSClbMcnnx83jYyV5uNxN27VJAlgeN7J2ZyJTLlKRAAAAgAUnKuxYaYezMWyBShwR4N\n" + + "eVAW8vT3CBxsMR/v3u6XmLTzjq4r0gKCxofnnj972uK0LvyTZ21/00MSl0KaAjJySl2hLj\n" + + "BNQA3TcDXnLEc5KcsKZdDhuWkHGmaoajDp/okfQd6CxuKaBKG/OFdbYqVgOOVeACUUWxT4\n" + + "NN4e3CxTWQAAAAgQDV3vzDCQanGAXMKZSxfHUU63Tmh+2NcB1I6Sb0/CwpBgLH1y0CTB9r\n" + + "c8TLSs6HoHx1lfzOp6Yj7BQ9CWHS94Mi+RYBF+SpaMLoZKqCU4Q3UWiHiOyPnMaohAdvRE\n" + + "gJkaY2OAkFaaCI31rwBrs6b5U/ErtRTUZNJEI7OCi6wDBfBwAAAIEA0ZKyuUW5+VFcTyuR\n" + + "1G0ky5uihtJryFCjA2fzu7tgobm0gsIgSDClp9TdMh5CDyJo0R9fQnH8Lki0Ku+jgc4X+a\n" + + "/XMw47d1iL7Hdu9NAJsplezKD5Unso4xJRXhLnXUT5FT8lSgwE+9xUBuILKUmZQa20ejKM\n" + + "20U6szOxEEclA/8AAAAObWFya0BiYXJuYWNsZXMBAgMEBQ==\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + private static readonly string rsa3072Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "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\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + private static readonly string rsa4096Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "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\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + private static readonly string ecdsa256Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n" + + "1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS9VjynnoLGUcT2hXXPkFwGfbleI4Ln\n" + + "1kkgt2UgibKXw9NtesSqpKdEBDW5Kh2nmqLCIk+fdbsTGkxlfaYBtUrkAAAAqBQCER8UAh\n" + + "EfAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL1WPKeegsZRxPaF\n" + + "dc+QXAZ9uV4jgufWSSC3ZSCJspfD0216xKqkp0QENbkqHaeaosIiT591uxMaTGV9pgG1Su\n" + + "QAAAAgbAJJUVcjwwU/olgrxgINJ1DViX6GcCBhgeH8wAXiNKoAAAAObWFya0BiYXJuYWNs\n" + + "ZXMBAg==\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + private static readonly string ecdsa384Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS\n" + + "1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQS0yKimt2kBeyNKUqNivPfSPBVyU4jH\n" + + "9+6hNsRIJG4NKRgKdIOIiOOLm6pGLUmwN4yDS+0ssdPxwRthQzL879HRtwbqAAb1ShK0CT\n" + + "rljAhk9+SUgrOqWnKL2Ngo1uU5KZgAAADYJC2IQSQtiEEAAAATZWNkc2Etc2hhMi1uaXN0\n" + + "cDM4NAAAAAhuaXN0cDM4NAAAAGEEtMioprdpAXsjSlKjYrz30jwVclOIx/fuoTbESCRuDS\n" + + "kYCnSDiIjji5uqRi1JsDeMg0vtLLHT8cEbYUMy/O/R0bcG6gAG9UoStAk65YwIZPfklIKz\n" + + "qlpyi9jYKNblOSmYAAAAMQChvecXe7PGUVG0Pz2IgM9f80YLXdarf98sRptbGSIPwu8KlW\n" + + "OlGv0Any5ue51/I5wAAAAObWFya0BiYXJuYWNsZXMB\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + private static readonly string ecdsa521Key = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS\n" + + "1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA90An5exsl3UEU0d8fhqV8rgmoyzJ\n" + + "21sZYrjFV+bs583tbSIMYAapk8jSKtk+r1z48KQdsR9czydmy2yYbdXruXMBPdQrf+11BB\n" + + "dCs1E9iFet1UB8OruVeduD5dm0In7yJK1Qo18xe0NpOjOHeZ1ixAxdOt9zuolAlBTwZYth\n" + + "FMESME8AAAEQApLNRAKSzUQAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ\n" + + "AAAIUEAPdAJ+XsbJd1BFNHfH4alfK4JqMsydtbGWK4xVfm7OfN7W0iDGAGqZPI0irZPq9c\n" + + "+PCkHbEfXM8nZstsmG3V67lzAT3UK3/tdQQXQrNRPYhXrdVAfDq7lXnbg+XZtCJ+8iStUK\n" + + "NfMXtDaTozh3mdYsQMXTrfc7qJQJQU8GWLYRTBEjBPAAAAQgFHl5a1JDqcCeaAx84z3u/v\n" + + "z7dyVl4uohlQPaiZ+hhtbbUg6oLMnVGGjjmviR0C0aDzx0xDEsK8TseFd16mBWpOnAAAAA\n" + + "5tYXJrQGJhcm5hY2xlcwECAwQ=\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + + [Test] + public void TestDsa() + { + var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey(Base64.Decode("AAAAB3NzaC1kc3MAAACBAJBB5+S4kZZYZLswaQ/zm3GM7YWmHsumwo/Xxu+z6Cg2l5PUoiBBZ4ET9EhhQuL2ja/zrCMCi0ZwiSRuSp36ayPrHLbNJb3VdOuJg8xExRa6F3YfVZfcTPUEKh6FU72fI31HrQmi4rpyHnWxL/iDX496ZG2Hdq6UkPISQpQwj4TtAAAAFQCP9TXcVahR/2rpfEhvdXR0PfhbRwAAAIBdXzAVqoOtb9zog6lNF1cGS1S06W9W/clvuwq2xF1s3bkoI/xUbFSc0IAPsGl2kcB61PAZqcop50lgpvYzt8cq/tbqz3ypq1dCQ0xdmJHj975QsRFax+w6xQ0kgpBhwcS2EOizKb+C+tRzndGpcDSoSMuVXp9i4wn5pJSTZxAYFQAAAIEAhQZc687zYxrEDR/1q6m4hw5GFxuVvLsC+bSHtMF0c11Qy4IPg7mBeP7K5Kq4WyJPtmZhuc5Bb12bJQR6qgd1uLn692fe1UK2kM6eWXBzhlzZ54BslfSKHGNN4qH+ln3Zaf/4rpKE7fvoinkrgkOZmj0PMx9D6wlpHKkXMUxeXtc=")); + + var privSpec = ParsePrivateKeyData("-----BEGIN DSA PRIVATE KEY-----\n" + + "MIIBuwIBAAKBgQCQQefkuJGWWGS7MGkP85txjO2Fph7LpsKP18bvs+goNpeT1KIg\n" + + "QWeBE/RIYULi9o2v86wjAotGcIkkbkqd+msj6xy2zSW91XTriYPMRMUWuhd2H1WX\n" + + "3Ez1BCoehVO9nyN9R60JouK6ch51sS/4g1+PemRth3aulJDyEkKUMI+E7QIVAI/1\n" + + "NdxVqFH/aul8SG91dHQ9+FtHAoGAXV8wFaqDrW/c6IOpTRdXBktUtOlvVv3Jb7sK\n" + + "tsRdbN25KCP8VGxUnNCAD7BpdpHAetTwGanKKedJYKb2M7fHKv7W6s98qatXQkNM\n" + + "XZiR4/e+ULERWsfsOsUNJIKQYcHEthDosym/gvrUc53RqXA0qEjLlV6fYuMJ+aSU\n" + + "k2cQGBUCgYEAhQZc687zYxrEDR/1q6m4hw5GFxuVvLsC+bSHtMF0c11Qy4IPg7mB\n" + + "eP7K5Kq4WyJPtmZhuc5Bb12bJQR6qgd1uLn692fe1UK2kM6eWXBzhlzZ54BslfSK\n" + + "HGNN4qH+ln3Zaf/4rpKE7fvoinkrgkOZmj0PMx9D6wlpHKkXMUxeXtcCFELnLOJ8\n" + + "D0akSCUFY/iDLo/KnOIH\n" + + "-----END DSA PRIVATE KEY-----\n"); + + byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); + + var signer = new DsaSigner(); + signer.Init(true, privSpec); + BigInteger[] rs = signer.GenerateSignature(originalMessage); + + signer.Init(false, pubSpec); + Assert.IsTrue(signer.VerifySignature(originalMessage, rs[0], rs[1]), "DSA test"); + } + + [Test] + public void TestECDsa_CurvesFromSshKeyGen() + { + var pairs = new Tuple []{ + Tuple.Create( + "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBbxKE+/DXstQZmwH7Wso8SUt8LvYoMQpxN/7INC0lMn7mNCbxJcSOCfucBuWOrdoFyFZUkGli2mzKj3hJlcPiI=", + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n" + + "1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQW8ShPvw17LUGZsB+1rKPElLfC72KD\n" + + "EKcTf+yDQtJTJ+5jQm8SXEjgn7nAbljq3aBchWVJBpYtpsyo94SZXD4iAAAAuKFclDShXJ\n" + + "Q0AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBbxKE+/DXstQZmw\n" + + "H7Wso8SUt8LvYoMQpxN/7INC0lMn7mNCbxJcSOCfucBuWOrdoFyFZUkGli2mzKj3hJlcPi\n" + + "IAAAAhAP4L/ciGBDF4HoQSvMaKM8svW4Ss0uYi7HkZ1sn/zCe0AAAAHW1lZ2Fud29vZHNA\n" + + "dHljaGUtMzI2NS5nYXRld2F5AQI=\n" + + "-----END OPENSSH PRIVATE KEY-----\n" + ), + Tuple.Create( + "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBOT0Cc/zauJsOWo/0P0sMNeyFI5Enz3+lKJtjWXQD7DpFgZmG5Ise8IXR5/ot7fo0kWlYQrye/uSmNmWBuDvOpBCHOnyR6Kaej36qoOO/gwbH+mezSYXSxCTA9Qb8VzxLA==", + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS\n" + + "1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTk9AnP82ribDlqP9D9LDDXshSORJ89\n" + + "/pSibY1l0A+w6RYGZhuSLHvCF0ef6Le36NJFpWEK8nv7kpjZlgbg7zqQQhzp8keimno9+q\n" + + "qDjv4MGx/pns0mF0sQkwPUG/Fc8SwAAADorZ3naK2d52gAAAATZWNkc2Etc2hhMi1uaXN0\n" + + "cDM4NAAAAAhuaXN0cDM4NAAAAGEE5PQJz/Nq4mw5aj/Q/Sww17IUjkSfPf6Uom2NZdAPsO\n" + + "kWBmYbkix7whdHn+i3t+jSRaVhCvJ7+5KY2ZYG4O86kEIc6fJHopp6Pfqqg47+DBsf6Z7N\n" + + "JhdLEJMD1BvxXPEsAAAAMQDLno+rINnY7/Ht1WmSGZYJ3EMPtysbxuBnQFEL4USa3kyAb1\n" + + "QMR6+jtqraKtE7kLwAAAAdbWVnYW53b29kc0B0eWNoZS0zMjY1LmdhdGV3YXkBAg==\n" + + "-----END OPENSSH PRIVATE KEY-----\n" + ), + Tuple.Create( + "AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADXE/q1WSR002vRI+tiPLpdRjzeymSk+RjD7ZIC9CndqLmI0rhTMh5xReAzved12BH9lQJIGIw4YoIQDudsMbRUsQEjFvbFzSXLJBYWdZf8Voa/97/R9w/i8bKUMUPP0disypZlGdQn5+XvzHG6bhX2Qr9aJacGFZoVHugF/M8QyC+GyA==", + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS\n" + + "1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA1xP6tVkkdNNr0SPrYjy6XUY83spk\n" + + "pPkYw+2SAvQp3ai5iNK4UzIecUXgM73nddgR/ZUCSBiMOGKCEA7nbDG0VLEBIxb2xc0lyy\n" + + "QWFnWX/FaGv/e/0fcP4vGylDFDz9HYrMqWZRnUJ+fl78xxum4V9kK/WiWnBhWaFR7oBfzP\n" + + "EMgvhsgAAAEgs+rbdbPq23UAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ\n" + + "AAAIUEANcT+rVZJHTTa9Ej62I8ul1GPN7KZKT5GMPtkgL0Kd2ouYjSuFMyHnFF4DO953XY\n" + + "Ef2VAkgYjDhighAO52wxtFSxASMW9sXNJcskFhZ1l/xWhr/3v9H3D+LxspQxQ8/R2KzKlm\n" + + "UZ1Cfn5e/McbpuFfZCv1olpwYVmhUe6AX8zxDIL4bIAAAAQgCM8ojULpNk3UhBZhPfK+Tw\n" + + "QjT9MHU0OTi4twvKPAE0vOLQ/C1g9AMlspyKxS2NKx2gxxXISowFGNL6Jkx9198ElQAAAB\n" + + "1tZWdhbndvb2RzQHR5Y2hlLTMyNjUuZ2F0ZXdheQECAwQF\n" + + "-----END OPENSSH PRIVATE KEY-----\n" + ) + }; + + string[] ecPriv = { ecdsa256Key, ecdsa384Key, ecdsa521Key }; + for (int i = 0; i != ecPriv.Length; i++) + { + var privKey = (ECPrivateKeyParameters)ParsePrivateKeyData(ecPriv[i]); + + var q = privKey.Parameters.G.Multiply(privKey.D); + + DoECSigTest(new ECPublicKeyParameters(q, privKey.Parameters), privKey); + } + + for (int i = 0; i != pairs.Length; i++) + { + var pair = pairs[i]; + + var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey(Base64.Decode(pair.Item1)); + var privSpec = ParsePrivateKeyData(pair.Item2); + + DoECSigTest(pubSpec, privSpec); + + // + // Test encode + // + var recoveredPubKey = OpenSshPublicKeyUtilities.ParsePublicKey( + OpenSshPublicKeyUtilities.EncodePublicKey((AsymmetricKeyParameter)pubSpec)); + var recoveredPrivateKey = OpenSshPrivateKeyUtilities.ParsePrivateKeyBlob( + OpenSshPrivateKeyUtilities.EncodePrivateKey((AsymmetricKeyParameter)privSpec)); + + byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); + + var signer = new ECDsaSigner(); + signer.Init(true, privSpec); + BigInteger[] rs = signer.GenerateSignature(originalMessage); + + signer.Init(false, pubSpec); + Assert.IsTrue(signer.VerifySignature(originalMessage, rs[0], rs[1]), + "ECDSA test post encoded / Decode"); + } + + } + + private void DoECSigTest(AsymmetricKeyParameter pubSpec, AsymmetricKeyParameter privSpec) + { + byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); + + var signer = new ECDsaSigner(); + signer.Init(true, privSpec); + BigInteger[] rs = signer.GenerateSignature(originalMessage); + + signer.Init(false, pubSpec); + Assert.IsTrue(signer.VerifySignature(originalMessage, rs[0], rs[1]), "ECDSA test"); + } + + [Test] + public void TestECDsa() + { + var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey( + Base64.Decode("AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHq5qxGqnh93Gpbj2w1Avx1UwBl6z5bZC3Viog1yNHDZYcV6Da4YQ3i0/hN7xY7sUy9dNF6g16tJSYXQQ4tvO3g=")); + + var privSpec = ParsePrivateKeyData("-----BEGIN EC PRIVATE KEY-----\n" + + "MHcCAQEEIHeg/+m02j6nr4bO8ubfbzhs0fqOjiuIoWbvGnVg+FmpoAoGCCqGSM49\n" + + "AwEHoUQDQgAEermrEaqeH3caluPbDUC/HVTAGXrPltkLdWKiDXI0cNlhxXoNrhhD\n" + + "eLT+E3vFjuxTL100XqDXq0lJhdBDi287eA==\n" + + "-----END EC PRIVATE KEY-----\n"); + + DoECSigTest(pubSpec, privSpec); + } + + [Test] + public void TestEd25519() + { + var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey( + Base64.Decode("AAAAC3NzaC1lZDI1NTE5AAAAIM4CaV7WQcy0lht0hclgXf4Olyvzvv2fnUvQ3J8IYsWF")); + + var privSpec = ParsePrivateKeyData("-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + + "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + + "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + + "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + + "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + + "-----END OPENSSH PRIVATE KEY-----\n"); + + byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); + + Ed25519Signer signer = new Ed25519Signer(); + signer.Init(true, privSpec); + signer.BlockUpdate(originalMessage, 0, originalMessage.Length); + byte[] sig = signer.GenerateSignature(); + + signer.Init(false, pubSpec); + signer.BlockUpdate(originalMessage, 0, originalMessage.Length); + Assert.IsTrue(signer.VerifySignature(sig), "ED25519Signer test"); + } + + [Test] + public void TestFailures() + { + var data = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + + "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + + "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + + "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + + "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + + byte[] blob; + using (var pemReader = new PemReader(new StringReader(data))) + { + blob = pemReader.ReadPemObject().Content; + } + + // + // Altering the check value. + // + blob[98] ^= 1; + + try + { + var privSpec = OpenSshPrivateKeyUtilities.ParsePrivateKeyBlob(blob); + Assert.Fail("Change should trigger failure."); + } + catch (InvalidOperationException iles) + { + Assert.AreEqual("private key check values are not the same", iles.Message, "Check value mismatch"); + } + + // + // Altering the cipher name. + // + + data = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + + "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" + + "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" + + "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" + + "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" + + "-----END OPENSSH PRIVATE KEY-----\n"; + + using (var pemReader = new PemReader(new StringReader(data))) + { + blob = pemReader.ReadPemObject().Content; + } + + blob[19] = (byte)'C'; + + try + { + var privSpec = OpenSshPrivateKeyUtilities.ParsePrivateKeyBlob(blob); + Assert.Fail("Change should trigger failure."); + } + catch (InvalidOperationException iles) + { + Assert.AreEqual("encrypted keys not supported", iles.Message, "enc keys not supported"); + } + } + + [Test] + public void TestRSA() + { + var pubSpec = OpenSshPublicKeyUtilities.ParsePublicKey( + Base64.Decode("AAAAB3NzaC1yc2EAAAADAQABAAAAgQDvh2BophdIp8ojwGZQR0FQ/awowXnV24nAPm+/na8MOUrdySNhOnlek4LAZl82/+Eu2t21XD6hQUiHKAj6XaNFBthTuss7Cz/tA348DLEMHD9wUtT0FXVmsxqN4BfusunbcULxxVWG2z8FvqeaGgc/Unkp9y7/kyf54pPUCBcClw==")); + + var privSpec = ParsePrivateKeyData("-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXgIBAAKBgQDvh2BophdIp8ojwGZQR0FQ/awowXnV24nAPm+/na8MOUrdySNh\n" + + "Onlek4LAZl82/+Eu2t21XD6hQUiHKAj6XaNFBthTuss7Cz/tA348DLEMHD9wUtT0\n" + + "FXVmsxqN4BfusunbcULxxVWG2z8FvqeaGgc/Unkp9y7/kyf54pPUCBcClwIDAQAB\n" + + "AoGBAOMXYEoXHgAeREE9CkOWKtDUkEJbnF0rNSB0kZIDt5BJSTeYmNh3jdYi2FX9\n" + + "OMx2MFIx4v0tJZvQvyiUxl5IJJ9ZJsYUWF+6VbcTVwYYfdVzZzP2TNyGmF9/ADZW\n" + + "wBehqP04uRlYjt94kqb4HoOKF3gJ3LC4uW9xcEltTBeHWCfhAkEA/2biF5St9/Ya\n" + + "540E4zu/FKPsxLSaT8LWCo9+X7IqIzlBQCB4GjM+nZeTm7eZOkfAFZoxwfiNde/9\n" + + "qleXXf6B2QJBAPAW+jDBC3QF4/g8n9cDxm/A3ICmcOFSychLSrydk9ZyRPbTRyQC\n" + + "YlC2mf/pCrO/yO7h189BXyQ3PXOEhnujce8CQQD7gDy0K90EiH0F94AQpA0OLj5B\n" + + "lfc/BAXycEtpwPBtrzvqAg9C/aNzXIgmly10jqNAoo7NDA2BTcrlq0uLa8xBAkBl\n" + + "7Hs+I1XnZXDIO4Rn1VRysN9rRj15ipnbDAuoUwUl7tDUMBFteg2e0kZCW/6NHIgC\n" + + "0aG6fLgVOdY+qi4lYtfFAkEAqqiBgEgSrDmnJLTm6j/Pv1mBA6b9bJbjOqomrDtr\n" + + "AWTXe+/kSCv/jYYdpNA/tDgAwEmtkWWEie6+SwJB5cXXqg==\n" + + "-----END RSA PRIVATE KEY-----\n"); + + DoRSATest(pubSpec, privSpec); + + privSpec = ParsePrivateKeyData(rsa1024Key); + pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); + + DoRSATest(pubSpec, privSpec); + + privSpec = ParsePrivateKeyData(rsa2048Key); + pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); + + DoRSATest(pubSpec, privSpec); + + privSpec = ParsePrivateKeyData(rsa3072Key); + pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); + + DoRSATest(pubSpec, privSpec); + + privSpec = ParsePrivateKeyData(rsa4096Key); + pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent); + + DoRSATest(pubSpec, privSpec); + } + + private void DoRSATest(AsymmetricKeyParameter pubSpec, AsymmetricKeyParameter privSpec) + { + byte[] originalMessage = SecureRandom.GetNextBytes(secureRandom, 10); + originalMessage[0] |= 1; + + var rsaEngine = new RsaEngine(); + rsaEngine.Init(true, privSpec); + + byte[] ct = rsaEngine.ProcessBlock(originalMessage, 0, originalMessage.Length); + + rsaEngine.Init(false, pubSpec); + byte[] result = rsaEngine.ProcessBlock(ct, 0, ct.Length); + + Assert.IsTrue(Arrays.AreEqual(originalMessage, result), "Result did not match original message"); + } + + private static AsymmetricKeyParameter ParsePrivateKeyData(string data) + { + using (var pemReader = new PemReader(new StringReader(data))) + { + return OpenSshPrivateKeyUtilities.ParsePrivateKeyBlob(pemReader.ReadPemObject().Content); + } + } + } +} -- cgit 1.4.1 From 08e9f2e9aaaf55bd93f3a7e8aa4c148dca4c3c97 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sun, 16 Apr 2023 00:02:10 +0700 Subject: EC public keys pick up OID from named parameters --- crypto/src/crypto/parameters/ECKeyParameters.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/crypto/src/crypto/parameters/ECKeyParameters.cs b/crypto/src/crypto/parameters/ECKeyParameters.cs index ed8537f3d..b015433ab 100644 --- a/crypto/src/crypto/parameters/ECKeyParameters.cs +++ b/crypto/src/crypto/parameters/ECKeyParameters.cs @@ -40,6 +40,7 @@ namespace Org.BouncyCastle.Crypto.Parameters this.algorithm = VerifyAlgorithmName(algorithm); this.parameters = parameters; + this.publicKeyParamSet = (parameters as ECNamedDomainParameters)?.Name; } protected ECKeyParameters( -- cgit 1.4.1 From 0e419469d0b685c5f90485962e94b5e5800811db Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sun, 16 Apr 2023 00:03:06 +0700 Subject: Restore files to before openssh changes --- crypto/src/crypto/ec/CustomNamedCurves.cs | 10 ++++------ crypto/src/pkcs/PrivateKeyInfoFactory.cs | 10 +++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/crypto/src/crypto/ec/CustomNamedCurves.cs b/crypto/src/crypto/ec/CustomNamedCurves.cs index 7bf274be4..d256dba73 100644 --- a/crypto/src/crypto/ec/CustomNamedCurves.cs +++ b/crypto/src/crypto/ec/CustomNamedCurves.cs @@ -782,16 +782,14 @@ namespace Org.BouncyCastle.Crypto.EC new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary curves = new Dictionary(); - private static readonly Dictionary objIdToName = + private static readonly Dictionary names = new Dictionary(); - private static readonly List names = new List(); private static void DefineCurve(string name, DerObjectIdentifier oid, X9ECParametersHolder holder) { objIds.Add(name, oid); - objIdToName.Add(oid, name); + names.Add(oid, name); curves.Add(oid, holder); - names.Add(name); } private static void DefineCurveAlias(string name, DerObjectIdentifier oid) @@ -904,7 +902,7 @@ namespace Org.BouncyCastle.Crypto.EC /// The OID for the curve. public static string GetName(DerObjectIdentifier oid) { - return CollectionUtilities.GetValueOrNull(objIdToName, oid); + return CollectionUtilities.GetValueOrNull(names, oid); } ///

Look up the OID of the curve with the given name. @@ -917,7 +915,7 @@ namespace Org.BouncyCastle.Crypto.EC /// Enumerate the available curve names in this registry. public static IEnumerable Names { - get { return CollectionUtilities.Proxy(names); } + get { return CollectionUtilities.Proxy(objIds.Keys); } } } } diff --git a/crypto/src/pkcs/PrivateKeyInfoFactory.cs b/crypto/src/pkcs/PrivateKeyInfoFactory.cs index 1baf2dd9f..d56831f35 100644 --- a/crypto/src/pkcs/PrivateKeyInfoFactory.cs +++ b/crypto/src/pkcs/PrivateKeyInfoFactory.cs @@ -164,16 +164,16 @@ namespace Org.BouncyCastle.Pkcs else { X962Parameters x962; - if (dp is ECNamedDomainParameters _dp) - { - x962 = new X962Parameters(_dp.Name); - } - else + if (priv.PublicKeyParamSet == null) { X9ECParameters ecP = new X9ECParameters(dp.Curve, new X9ECPoint(dp.G, false), dp.N, dp.H, dp.GetSeed()); x962 = new X962Parameters(ecP); } + else + { + x962 = new X962Parameters(priv.PublicKeyParamSet); + } ec = new ECPrivateKeyStructure(orderBitLength, priv.D, publicKey, x962); -- cgit 1.4.1 From daa52b50b945dddb08b640c5794b1d3c9c39a212 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sun, 16 Apr 2023 00:04:25 +0700 Subject: Fixes and improvements for github_439 --- .../src/crypto/util/OpenSshPrivateKeyUtilities.cs | 92 ++++++-------- .../src/crypto/util/OpenSshPublicKeyUtilities.cs | 116 +++++++++--------- crypto/src/crypto/util/SshBuffer.cs | 104 ++++++++-------- crypto/src/crypto/util/SshBuilder.cs | 22 ++-- crypto/src/crypto/util/SshNamedCurves.cs | 132 ++++++++++++--------- .../test/src/crypto/test/OpenSshKeyParsingTest.cs | 2 +- 6 files changed, 236 insertions(+), 232 deletions(-) diff --git a/crypto/src/crypto/util/OpenSshPrivateKeyUtilities.cs b/crypto/src/crypto/util/OpenSshPrivateKeyUtilities.cs index 187ecc39e..aa934b1d6 100644 --- a/crypto/src/crypto/util/OpenSshPrivateKeyUtilities.cs +++ b/crypto/src/crypto/util/OpenSshPrivateKeyUtilities.cs @@ -1,7 +1,7 @@ using System; +using System.Text; using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.Nist; using Org.BouncyCastle.Asn1.Pkcs; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; @@ -16,7 +16,7 @@ namespace Org.BouncyCastle.Crypto.Utilities { /// Magic value for proprietary OpenSSH private key. /// C string so null terminated. - private static readonly byte[] AUTH_MAGIC = Strings.ToByteArray("openssh-key-v1\0"); + private static readonly byte[] AUTH_MAGIC = Encoding.ASCII.GetBytes("openssh-key-v1\0"); /** * Encode a cipher parameters into an OpenSSH private key. @@ -66,9 +66,9 @@ namespace Org.BouncyCastle.Crypto.Utilities SshBuilder builder = new SshBuilder(); builder.WriteBytes(AUTH_MAGIC); - builder.WriteString("none"); // cipher name - builder.WriteString("none"); // KDF name - builder.WriteString(""); // KDF options + builder.WriteStringAscii("none"); // cipher name + builder.WriteStringAscii("none"); // KDF name + builder.WriteStringAscii(""); // KDF options builder.U32(1); // Number of keys @@ -84,7 +84,7 @@ namespace Org.BouncyCastle.Crypto.Utilities pkBuild.U32((uint)checkint); pkBuild.U32((uint)checkint); - pkBuild.WriteString("ssh-ed25519"); + pkBuild.WriteStringAscii("ssh-ed25519"); // Public key (as part of private key pair) byte[] pubKeyEncoded = publicKeyParameters.GetEncoded(); @@ -94,7 +94,7 @@ namespace Org.BouncyCastle.Crypto.Utilities pkBuild.WriteBlock(Arrays.Concatenate(ed25519PrivateKey.GetEncoded(), pubKeyEncoded)); // Comment for this private key (empty) - pkBuild.WriteString(""); + pkBuild.WriteStringUtf8(""); builder.WriteBlock(pkBuild.GetPaddedBytes()); } @@ -102,8 +102,7 @@ namespace Org.BouncyCastle.Crypto.Utilities return builder.GetBytes(); } - throw new ArgumentException("unable to convert " + parameters.GetType().Name + " to openssh private key"); - + throw new ArgumentException("unable to convert " + Platform.GetTypeName(parameters) + " to openssh private key"); } /** @@ -177,11 +176,9 @@ namespace Org.BouncyCastle.Crypto.Utilities { SshBuffer kIn = new SshBuffer(AUTH_MAGIC, blob); - string cipherName = kIn.ReadString(); + string cipherName = kIn.ReadStringAscii(); if (!"none".Equals(cipherName)) - { throw new InvalidOperationException("encrypted keys not supported"); - } // KDF name kIn.SkipBlock(); @@ -191,9 +188,7 @@ namespace Org.BouncyCastle.Crypto.Utilities int publicKeyCount = kIn.ReadU32(); if (publicKeyCount != 1) - { throw new InvalidOperationException("multiple keys not supported"); - } // Burn off public key. OpenSshPublicKeyUtilities.ParsePublicKey(kIn.ReadBlock()); @@ -201,87 +196,80 @@ namespace Org.BouncyCastle.Crypto.Utilities 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(); + string keyType = pkIn.ReadStringAscii(); if ("ssh-ed25519".Equals(keyType)) { // Public key - pkIn.ReadBlock(); + pkIn.SkipBlock(); + // Private key value.. +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + ReadOnlySpan edPrivateKey = pkIn.ReadBlockSpan(); +#else byte[] edPrivateKey = pkIn.ReadBlock(); +#endif + if (edPrivateKey.Length != Ed25519PrivateKeyParameters.KeySize + Ed25519PublicKeyParameters.KeySize) - { throw new InvalidOperationException("private key value of wrong length"); - } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + result = new Ed25519PrivateKeyParameters(edPrivateKey[..Ed25519PrivateKeyParameters.KeySize]); +#else result = new Ed25519PrivateKeyParameters(edPrivateKey, 0); +#endif } else if (keyType.StartsWith("ecdsa")) { - DerObjectIdentifier oid = SshNamedCurves.GetByName(Strings.FromByteArray(pkIn.ReadBlock())) ?? + var curveName = pkIn.ReadStringAscii(); + + var oid = SshNamedCurves.GetOid(curveName) ?? throw new InvalidOperationException("OID not found for: " + keyType); - X9ECParameters curveParams = NistNamedCurves.GetByOid(oid) ?? throw new InvalidOperationException("Curve not found for: " + oid); + + var x9ECParameters = SshNamedCurves.GetByOid(oid) ?? + throw new InvalidOperationException("Curve not found for: " + oid); // Skip public key. - pkIn.ReadBlock(); - byte[] privKey = pkIn.ReadBlock(); + pkIn.SkipBlock(); - result = new ECPrivateKeyParameters(new BigInteger(1, privKey), - new ECNamedDomainParameters(oid, curveParams)); + var d = pkIn.ReadMpintPositive(); + + result = new ECPrivateKeyParameters(d, new ECNamedDomainParameters(oid, x9ECParameters)); } 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 modulus = pkIn.ReadMpintPositive(); + BigInteger pubExp = pkIn.ReadMpintPositive(); + BigInteger privExp = pkIn.ReadMpintPositive(); + BigInteger coef = pkIn.ReadMpintPositive(); + BigInteger p = pkIn.ReadMpintPositive(); + BigInteger q = pkIn.ReadMpintPositive(); 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); + 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; + return result ?? throw new ArgumentException("unable to parse key"); ; } /** @@ -292,9 +280,7 @@ namespace Org.BouncyCastle.Crypto.Utilities for (int t = 0; t < sequence.Count; t++) { if (!(sequence[t] is DerInteger)) - { return false; - } } return true; } diff --git a/crypto/src/crypto/util/OpenSshPublicKeyUtilities.cs b/crypto/src/crypto/util/OpenSshPublicKeyUtilities.cs index cdb16b06a..f04ab85f7 100644 --- a/crypto/src/crypto/util/OpenSshPublicKeyUtilities.cs +++ b/crypto/src/crypto/util/OpenSshPublicKeyUtilities.cs @@ -1,9 +1,9 @@ using System; -using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Utilities { @@ -38,43 +38,34 @@ namespace Org.BouncyCastle.Crypto.Utilities public static byte[] EncodePublicKey(AsymmetricKeyParameter cipherParameters) { if (cipherParameters == null) - { - throw new ArgumentException("cipherParameters was null."); - } + throw new ArgumentNullException(nameof(cipherParameters)); + if (cipherParameters.IsPrivate) + throw new ArgumentException("Not a public key", nameof(cipherParameters)); - if (cipherParameters is RsaKeyParameters) + if (cipherParameters is RsaKeyParameters rsaPubKey) { - if (cipherParameters.IsPrivate) - { - throw new ArgumentException("RSAKeyParamaters was for encryption"); - } - - RsaKeyParameters rsaPubKey = (RsaKeyParameters)cipherParameters; - SshBuilder builder = new SshBuilder(); - builder.WriteString(RSA); - builder.WriteBigNum(rsaPubKey.Exponent); - builder.WriteBigNum(rsaPubKey.Modulus); - + builder.WriteStringAscii(RSA); + builder.WriteMpint(rsaPubKey.Exponent); + builder.WriteMpint(rsaPubKey.Modulus); return builder.GetBytes(); - } else if (cipherParameters is ECPublicKeyParameters ecPublicKey) { - SshBuilder builder = new SshBuilder(); + string curveName = null; - // - // checked for named curve parameters.. - // - string name = SshNamedCurves.GetNameForParameters(ecPublicKey.Parameters); - - if (name == null) + var oid = ecPublicKey.PublicKeyParamSet; + if (oid != null) { - throw new ArgumentException("unable to derive ssh curve name for " + ecPublicKey.Parameters.Curve.GetType().Name); + curveName = SshNamedCurves.GetName(oid); } - builder.WriteString(ECDSA + "-sha2-" + name); // Magic - builder.WriteString(name); + if (curveName == null) + throw new ArgumentException("unable to derive ssh curve name for EC public key"); + + SshBuilder builder = new SshBuilder(); + builder.WriteStringAscii(ECDSA + "-sha2-" + curveName); // Magic + builder.WriteStringAscii(curveName); builder.WriteBlock(ecPublicKey.Q.GetEncoded(false)); //Uncompressed return builder.GetBytes(); } @@ -83,22 +74,22 @@ namespace Org.BouncyCastle.Crypto.Utilities DsaParameters dsaParams = dsaPubKey.Parameters; SshBuilder builder = new SshBuilder(); - builder.WriteString(DSS); - builder.WriteBigNum(dsaParams.P); - builder.WriteBigNum(dsaParams.Q); - builder.WriteBigNum(dsaParams.G); - builder.WriteBigNum(dsaPubKey.Y); + builder.WriteStringAscii(DSS); + builder.WriteMpint(dsaParams.P); + builder.WriteMpint(dsaParams.Q); + builder.WriteMpint(dsaParams.G); + builder.WriteMpint(dsaPubKey.Y); return builder.GetBytes(); } else if (cipherParameters is Ed25519PublicKeyParameters ed25519PublicKey) { SshBuilder builder = new SshBuilder(); - builder.WriteString(ED_25519); + builder.WriteStringAscii(ED_25519); builder.WriteBlock(ed25519PublicKey.GetEncoded()); return builder.GetBytes(); } - throw new ArgumentException("unable to convert " + cipherParameters.GetType().Name + " to private key"); + throw new ArgumentException("unable to convert " + Platform.GetTypeName(cipherParameters) + " to private key"); } /** @@ -111,55 +102,60 @@ namespace Org.BouncyCastle.Crypto.Utilities { AsymmetricKeyParameter result = null; - string magic = buffer.ReadString(); + string magic = buffer.ReadStringAscii(); if (RSA.Equals(magic)) { - BigInteger e = buffer.ReadBigNumPositive(); - BigInteger n = buffer.ReadBigNumPositive(); + BigInteger e = buffer.ReadMpintPositive(); + BigInteger n = buffer.ReadMpintPositive(); result = new RsaKeyParameters(false, n, e); } else if (DSS.Equals(magic)) { - BigInteger p = buffer.ReadBigNumPositive(); - BigInteger q = buffer.ReadBigNumPositive(); - BigInteger g = buffer.ReadBigNumPositive(); - BigInteger pubKey = buffer.ReadBigNumPositive(); + BigInteger p = buffer.ReadMpintPositive(); + BigInteger q = buffer.ReadMpintPositive(); + BigInteger g = buffer.ReadMpintPositive(); + BigInteger pubKey = buffer.ReadMpintPositive(); result = new DsaPublicKeyParameters(pubKey, new DsaParameters(p, q, g)); } else if (magic.StartsWith(ECDSA)) { - string curveName = buffer.ReadString(); - DerObjectIdentifier oid = SshNamedCurves.GetByName(curveName); - X9ECParameters x9ECParameters = SshNamedCurves.GetParameters(oid) ?? - throw new InvalidOperationException("unable to find curve for " + magic + " using curve name " + curveName); - var curve = x9ECParameters.Curve; - byte[] pointRaw = buffer.ReadBlock(); - - result = new ECPublicKeyParameters( - curve.DecodePoint(pointRaw), - new ECNamedDomainParameters(oid, x9ECParameters)); + var curveName = buffer.ReadStringAscii(); + + var oid = SshNamedCurves.GetOid(curveName); + + X9ECParameters x9ECParameters = oid == null ? null : SshNamedCurves.GetByOid(oid); + if (x9ECParameters == null) + { + throw new InvalidOperationException( + "unable to find curve for " + magic + " using curve name " + curveName); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + ReadOnlySpan pointEncoding = buffer.ReadBlockSpan(); +#else + byte[] pointEncoding = buffer.ReadBlock(); +#endif + var point = x9ECParameters.Curve.DecodePoint(pointEncoding); + + result = new ECPublicKeyParameters(point, new ECNamedDomainParameters(oid, x9ECParameters)); } else if (ED_25519.Equals(magic)) { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + ReadOnlySpan pubKeyBytes = buffer.ReadBlockSpan(); +#else byte[] pubKeyBytes = buffer.ReadBlock(); - if (pubKeyBytes.Length != Ed25519PublicKeyParameters.KeySize) - { - throw new InvalidOperationException("public key value of wrong length"); - } +#endif - result = new Ed25519PublicKeyParameters(pubKeyBytes, 0); + result = new Ed25519PublicKeyParameters(pubKeyBytes); } if (result == null) - { throw new ArgumentException("unable to parse key"); - } if (buffer.HasRemaining()) - { throw new ArgumentException("decoded key has trailing data"); - } return result; } diff --git a/crypto/src/crypto/util/SshBuffer.cs b/crypto/src/crypto/util/SshBuffer.cs index 094b85364..0df123388 100644 --- a/crypto/src/crypto/util/SshBuffer.cs +++ b/crypto/src/crypto/util/SshBuffer.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using Org.BouncyCastle.Math; using Org.BouncyCastle.Utilities; @@ -16,9 +17,7 @@ namespace Org.BouncyCastle.Crypto.Utilities for (int i = 0; i != magic.Length; i++) { if (magic[i] != buffer[i]) - { throw new ArgumentException("magic-number incorrect"); - } } pos += magic.Length; @@ -32,47 +31,62 @@ namespace Org.BouncyCastle.Crypto.Utilities public int ReadU32() { if (pos > buffer.Length - 4) - { throw new ArgumentOutOfRangeException("4 bytes for U32 exceeds buffer."); - } - - int i = (buffer[pos++] & 0xFF) << 24; - i |= (buffer[pos++] & 0xFF) << 16; - i |= (buffer[pos++] & 0xFF) << 8; - i |= buffer[pos++] & 0xFF; + int i = (int)Pack.BE_To_UInt32(buffer, pos); + pos += 4; return i; } - public string ReadString() + public string ReadStringAscii() { - return Strings.FromByteArray(ReadBlock()); +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return Encoding.ASCII.GetString(ReadBlockSpan()); +#else + return Encoding.ASCII.GetString(ReadBlock()); +#endif + } + + public string ReadStringUtf8() + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return Encoding.UTF8.GetString(ReadBlockSpan()); +#else + return Encoding.UTF8.GetString(ReadBlock()); +#endif } public byte[] ReadBlock() { int len = ReadU32(); if (len == 0) - { - return new byte[0]; - } - + return Arrays.EmptyBytes; if (pos > buffer.Length - len) - { - throw new ArgumentException("not enough data for block"); - } + throw new InvalidOperationException("not enough data for block"); int start = pos; pos += len; return Arrays.CopyOfRange(buffer, start, pos); } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public ReadOnlySpan ReadBlockSpan() + { + int len = ReadU32(); + if (len == 0) + return ReadOnlySpan.Empty; + if (pos > buffer.Length - len) + throw new InvalidOperationException("not enough data for block"); + + int start = pos; pos += len; + return buffer.AsSpan(start, len); + } +#endif + public void SkipBlock() { int len = ReadU32(); if (pos > buffer.Length - len) - { - throw new ArgumentException("not enough data for block"); - } + throw new InvalidOperationException("not enough data for block"); pos += len; } @@ -86,20 +100,13 @@ namespace Org.BouncyCastle.Crypto.Utilities { int len = ReadU32(); if (len == 0) - { - return new byte[0]; - } - + return Arrays.EmptyBytes; if (pos > buffer.Length - len) - { - throw new ArgumentException("not enough data for block"); - } + throw new InvalidOperationException("not enough data for block"); int align = len % blockSize; if (0 != align) - { - throw new ArgumentException("missing padding"); - } + throw new InvalidOperationException("missing padding"); int start = pos; pos += len; int end = pos; @@ -116,9 +123,7 @@ namespace Org.BouncyCastle.Crypto.Utilities for (int i = 1, padPos = end; i <= padCount; ++i, ++padPos) { if (i != (buffer[padPos] & 0xFF)) - { - throw new ArgumentException("incorrect padding"); - } + throw new InvalidOperationException("incorrect padding"); } } } @@ -126,27 +131,32 @@ namespace Org.BouncyCastle.Crypto.Utilities return Arrays.CopyOfRange(buffer, start, end); } - public BigInteger ReadBigNumPositive() + public BigInteger ReadMpint() { int len = ReadU32(); - if (pos + len > buffer.Length) - { - throw new ArgumentException("not enough data for big num"); - } + if (pos > buffer.Length - len) + throw new InvalidOperationException("not enough data for big num"); + + if (len == 0) + return BigInteger.Zero; + if (len == 1 && buffer[pos] == 0) + throw new InvalidOperationException("Zero MUST be stored with length 0"); + if (len > 1 && buffer[pos] == (byte)-(buffer[pos + 1] >> 7)) + throw new InvalidOperationException("Unnecessary leading bytes MUST NOT be included"); int start = pos; pos += len; - byte[] d = Arrays.CopyOfRange(buffer, start, pos); - return new BigInteger(1, d); + return new BigInteger(buffer, start, len); } - public byte[] GetBuffer() + public BigInteger ReadMpintPositive() { - return Arrays.Clone(buffer); - } + BigInteger n = ReadMpint(); + if (n.SignValue < 0) + throw new InvalidOperationException("Expected a positive mpint"); - public bool HasRemaining() - { - return pos < buffer.Length; + return n; } + + public bool HasRemaining() => pos < buffer.Length; } } diff --git a/crypto/src/crypto/util/SshBuilder.cs b/crypto/src/crypto/util/SshBuilder.cs index 9f2f35360..d631448d5 100644 --- a/crypto/src/crypto/util/SshBuilder.cs +++ b/crypto/src/crypto/util/SshBuilder.cs @@ -1,8 +1,8 @@ using System; using System.IO; +using System.Text; using Org.BouncyCastle.Math; -using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Utilities { @@ -18,7 +18,7 @@ namespace Org.BouncyCastle.Crypto.Utilities bos.WriteByte(Convert.ToByte(value & 0xFF)); } - public void WriteBigNum(BigInteger n) + public void WriteMpint(BigInteger n) { WriteBlock(n.ToByteArray()); } @@ -26,14 +26,7 @@ namespace Org.BouncyCastle.Crypto.Utilities public void WriteBlock(byte[] value) { U32((uint)value.Length); - try - { - bos.Write(value, 0, value.Length); - } - catch (IOException e) - { - throw new InvalidOperationException(e.Message, e); - } + WriteBytes(value); } public void WriteBytes(byte[] value) @@ -48,9 +41,14 @@ namespace Org.BouncyCastle.Crypto.Utilities } } - public void WriteString(string str) + public void WriteStringAscii(string str) + { + WriteBlock(Encoding.ASCII.GetBytes(str)); + } + + public void WriteStringUtf8(string str) { - WriteBlock(Strings.ToByteArray(str)); + WriteBlock(Encoding.UTF8.GetBytes(str)); } public byte[] GetBytes() diff --git a/crypto/src/crypto/util/SshNamedCurves.cs b/crypto/src/crypto/util/SshNamedCurves.cs index d97c2476e..019a4d5f5 100644 --- a/crypto/src/crypto/util/SshNamedCurves.cs +++ b/crypto/src/crypto/util/SshNamedCurves.cs @@ -1,93 +1,107 @@ -using System.Collections.Generic; -using System.Linq; +using System; +using System.Collections.Generic; using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.Nist; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Crypto.EC; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Utilities.Collections; namespace Org.BouncyCastle.Crypto.Utilities { - public class SshNamedCurves + public static class SshNamedCurves { - private static readonly Dictionary OidMap = - new Dictionary - { - { "nistp256", SecObjectIdentifiers.SecP256r1 }, - { "nistp384", SecObjectIdentifiers.SecP384r1 }, - { "nistp521", SecObjectIdentifiers.SecP521r1 }, - { "nistk163", SecObjectIdentifiers.SecT163k1 }, - { "nistp192", SecObjectIdentifiers.SecP192r1 }, - { "nistp224", SecObjectIdentifiers.SecP224r1 }, - { "nistk233", SecObjectIdentifiers.SecT233k1 }, - { "nistb233", SecObjectIdentifiers.SecT233r1 }, - { "nistk283", SecObjectIdentifiers.SecT283k1 }, - { "nistk409", SecObjectIdentifiers.SecT409k1 }, - { "nistb409", SecObjectIdentifiers.SecT409r1 }, - { "nistt571", SecObjectIdentifiers.SecT571k1 } - }; + private static readonly Dictionary objIds = + new Dictionary(StringComparer.OrdinalIgnoreCase); + private static readonly Dictionary names = + new Dictionary(); + private static void DefineCurveAlias(string name, DerObjectIdentifier oid) + { + if (FindByOidLazy(oid) == null) + throw new InvalidOperationException(); - private static readonly Dictionary CurveNameToSSHName = - new Dictionary - { - {"secp256r1", "nistp256"}, - {"secp384r1", "nistp384"}, - {"secp521r1", "nistp521"}, - {"sect163k1", "nistk163"}, - {"secp192r1", "nistp192"}, - {"secp224r1", "nistp224"}, - {"sect233k1", "nistk233"}, - {"sect233r1", "nistb233"}, - {"sect283k1", "nistk283"}, - {"sect409k1", "nistk409"}, - {"sect409r1", "nistb409"}, - {"sect571k1", "nistt571"} - }; + objIds.Add(name, oid); + names.Add(oid, name); + } - private static readonly Dictionary CurveMap = - CustomNamedCurves.Names.ToDictionary(k => CustomNamedCurves.GetByNameLazy(k).Curve, v => v); + private static X9ECParametersHolder FindByOidLazy(DerObjectIdentifier oid) => + ECKeyPairGenerator.FindECCurveByOidLazy(oid); - private static readonly Dictionary OidToName = - OidMap.ToDictionary(k => k.Value, v => v.Key); + static SshNamedCurves() + { + DefineCurveAlias("nistp192", SecObjectIdentifiers.SecP192r1); + DefineCurveAlias("nistp224", SecObjectIdentifiers.SecP224r1); + DefineCurveAlias("nistp256", SecObjectIdentifiers.SecP256r1); + DefineCurveAlias("nistp384", SecObjectIdentifiers.SecP384r1); + DefineCurveAlias("nistp521", SecObjectIdentifiers.SecP521r1); + DefineCurveAlias("nistb233", SecObjectIdentifiers.SecT233r1); + DefineCurveAlias("nistb409", SecObjectIdentifiers.SecT409r1); + DefineCurveAlias("nistk163", SecObjectIdentifiers.SecT163k1); + DefineCurveAlias("nistk233", SecObjectIdentifiers.SecT233k1); + DefineCurveAlias("nistk283", SecObjectIdentifiers.SecT283k1); + DefineCurveAlias("nistk409", SecObjectIdentifiers.SecT409k1); + DefineCurveAlias("nistt571", SecObjectIdentifiers.SecT571k1); + } + /// Look up the for the curve with the given name. + /// The name of the curve. + public static X9ECParameters GetByName(string name) + { + DerObjectIdentifier oid = GetOid(name); + return oid == null ? null : GetByOid(oid); + } - public static DerObjectIdentifier GetByName(string sshName) + /// Look up an for the curve with the given name. + /// + /// Allows accessing the curve without necessarily triggering the creation of + /// the full . + /// + /// The name of the curve. + public static X9ECParametersHolder GetByNameLazy(string name) { - return OidMap[sshName]; + DerObjectIdentifier oid = GetOid(name); + return oid == null ? null : GetByOidLazy(oid); } - public static X9ECParameters GetParameters(string sshName) + /// Look up the for the curve with the given + /// OID. + /// The OID for the curve. + public static X9ECParameters GetByOid(DerObjectIdentifier oid) { - return NistNamedCurves.GetByOid(OidMap[sshName.ToLower()]); + return GetByOidLazy(oid)?.Parameters; } - public static X9ECParameters GetParameters(DerObjectIdentifier oid) + /// Look up an for the curve with the given + /// OID. + /// + /// Allows accessing the curve without necessarily triggering the creation of + /// the full . + /// + /// The OID for the curve. + public static X9ECParametersHolder GetByOidLazy(DerObjectIdentifier oid) { - return NistNamedCurves.GetByOid(oid); + return names.ContainsKey(oid) ? FindByOidLazy(oid) : null; } + /// Look up the name of the curve with the given OID. + /// The OID for the curve. public static string GetName(DerObjectIdentifier oid) { - return OidToName[oid]; + return CollectionUtilities.GetValueOrNull(names, oid); } - public static string GetNameForParameters(ECDomainParameters parameters) + /// Look up the OID of the curve with the given name. + /// The name of the curve. + public static DerObjectIdentifier GetOid(string name) { - if (parameters is ECNamedDomainParameters) - { - return GetName(((ECNamedDomainParameters)parameters).Name); - } - - return GetNameForParameters(parameters.Curve); + return CollectionUtilities.GetValueOrNull(objIds, name); } - public static string GetNameForParameters(ECCurve curve) + /// Enumerate the available curve names in this registry. + public static IEnumerable Names { - return CurveNameToSSHName[CurveMap[curve]]; + get { return CollectionUtilities.Proxy(objIds.Keys); } } } } diff --git a/crypto/test/src/crypto/test/OpenSshKeyParsingTest.cs b/crypto/test/src/crypto/test/OpenSshKeyParsingTest.cs index 46cac0f4c..02559b5c9 100644 --- a/crypto/test/src/crypto/test/OpenSshKeyParsingTest.cs +++ b/crypto/test/src/crypto/test/OpenSshKeyParsingTest.cs @@ -16,7 +16,7 @@ using Org.BouncyCastle.Utilities.IO.Pem; namespace Org.BouncyCastle.Crypto.Tests { [TestFixture] - public class OpenSshKeyParsingTests + public class OpenSshKeyParsingTest { private static SecureRandom secureRandom = new SecureRandom(); -- cgit 1.4.1