summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crypto/src/crypto/util/OpenSshPrivateKeyUtilities.cs92
-rw-r--r--crypto/src/crypto/util/OpenSshPublicKeyUtilities.cs116
-rw-r--r--crypto/src/crypto/util/SshBuffer.cs104
-rw-r--r--crypto/src/crypto/util/SshBuilder.cs22
-rw-r--r--crypto/src/crypto/util/SshNamedCurves.cs132
-rw-r--r--crypto/test/src/crypto/test/OpenSshKeyParsingTest.cs2
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
     {
         /// <summary>Magic value for proprietary OpenSSH private key.</summary>
         /// <remarks>C string so null terminated.</remarks>
-        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<byte> 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<byte> 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<byte> 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<byte> ReadBlockSpan()
+        {
+            int len = ReadU32();
+            if (len == 0)
+                return ReadOnlySpan<byte>.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<string, DerObjectIdentifier> OidMap =
-            new Dictionary<string, DerObjectIdentifier>
-            {
-                { "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<string, DerObjectIdentifier> objIds =
+            new Dictionary<string, DerObjectIdentifier>(StringComparer.OrdinalIgnoreCase);
+        private static readonly Dictionary<DerObjectIdentifier, string> names =
+            new Dictionary<DerObjectIdentifier, string>();
 
+        private static void DefineCurveAlias(string name, DerObjectIdentifier oid)
+        {
+            if (FindByOidLazy(oid) == null)
+                throw new InvalidOperationException();
 
-        private static readonly Dictionary<string, string> CurveNameToSSHName =
-            new Dictionary<string, string>
-            {
-                {"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<ECCurve, string> CurveMap =
-            CustomNamedCurves.Names.ToDictionary(k => CustomNamedCurves.GetByNameLazy(k).Curve, v => v);
+        private static X9ECParametersHolder FindByOidLazy(DerObjectIdentifier oid) =>
+            ECKeyPairGenerator.FindECCurveByOidLazy(oid);
 
-        private static readonly Dictionary<DerObjectIdentifier, string> 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);
+        }
 
+        /// <summary>Look up the <see cref="X9ECParameters"/> for the curve with the given name.</summary>
+        /// <param name="name">The name of the curve.</param>
+        public static X9ECParameters GetByName(string name)
+        {
+            DerObjectIdentifier oid = GetOid(name);
+            return oid == null ? null : GetByOid(oid);
+        }
 
-        public static DerObjectIdentifier GetByName(string sshName)
+        /// <summary>Look up an <see cref="X9ECParametersHolder"/> for the curve with the given name.</summary>
+        /// <remarks>
+        /// Allows accessing the <see cref="Math.EC.ECCurve">curve</see> without necessarily triggering the creation of
+        /// the full <see cref="X9ECParameters"/>.
+        /// </remarks>
+        /// <param name="name">The name of the curve.</param>
+        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)
+        /// <summary>Look up the <see cref="X9ECParameters"/> for the curve with the given
+        /// <see cref="DerObjectIdentifier">OID</see>.</summary>
+        /// <param name="oid">The <see cref="DerObjectIdentifier">OID</see> for the curve.</param>
+        public static X9ECParameters GetByOid(DerObjectIdentifier oid)
         {
-            return NistNamedCurves.GetByOid(OidMap[sshName.ToLower()]);
+            return GetByOidLazy(oid)?.Parameters;
         }
 
-        public static X9ECParameters GetParameters(DerObjectIdentifier oid)
+        /// <summary>Look up an <see cref="X9ECParametersHolder"/> for the curve with the given
+        /// <see cref="DerObjectIdentifier">OID</see>.</summary>
+        /// <remarks>
+        /// Allows accessing the <see cref="Math.EC.ECCurve">curve</see> without necessarily triggering the creation of
+        /// the full <see cref="X9ECParameters"/>.
+        /// </remarks>
+        /// <param name="oid">The <see cref="DerObjectIdentifier">OID</see> for the curve.</param>
+        public static X9ECParametersHolder GetByOidLazy(DerObjectIdentifier oid)
         {
-            return NistNamedCurves.GetByOid(oid);
+            return names.ContainsKey(oid) ? FindByOidLazy(oid) : null;
         }
 
+        /// <summary>Look up the name of the curve with the given <see cref="DerObjectIdentifier">OID</see>.</summary>
+        /// <param name="oid">The <see cref="DerObjectIdentifier">OID</see> for the curve.</param>
         public static string GetName(DerObjectIdentifier oid)
         {
-            return OidToName[oid];
+            return CollectionUtilities.GetValueOrNull(names, oid);
         }
 
-        public static string GetNameForParameters(ECDomainParameters parameters)
+        /// <summary>Look up the <see cref="DerObjectIdentifier">OID</see> of the curve with the given name.</summary>
+        /// <param name="name">The name of the curve.</param>
+        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)
+        /// <summary>Enumerate the available curve names in this registry.</summary>
+        public static IEnumerable<string> 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();