runtime: Rsa ToXmlString is not supported on .NET Core 2.0

@myloveCc commented on Sun Aug 20 2017

rsa.ToXmlString(true) is not supported on .NET Core 2.0

 var rsa = RSA.Create(2048);
 var privateKey = rsa.ToXmlString(true);    // //not supported exception

@bartonjs commented on Mon Aug 21 2017

Yep. In .NET Framework networking, XML, and cryptography all live in one library (mscorlib). In .NET Core they’re in three separate libraries, and making this method work requires a circle:

  • XML needs networking, because (even though we discourage it) DTDs can initiate downloads.
  • Networking needs cryptography because TLS/https have X.509 certificates.
  • Cryptography needs XML for ToXmlString/FromXmlString.

The easiest link to snip is the ToXmlString/FromXmlString methods.

Technically ToXmlString can be written without XML (like in .NET Framework’s implementation, but FromXmlString requires an XML parser.

My recommendation would be to make a public static string ToXmlString(this RSAParameters rsaParameters) method, and a public static RSAParameters FromXmlString(string xml) method; borrowing from NetFx and CoreFx’s SignedXml; and to ultimately try moving off of the XML representations.


@myloveCc commented on Mon Aug 21 2017

This feature is not difficult, and I have been achieved in my project。

using System;
using System.Security.Cryptography;
using System.Xml;
using Newtonsoft.Json;
using NETCore.Encrypt.Shared;
using NETCore.Encrypt.Internal;

namespace NETCore.Encrypt.Extensions.Internal
{

    internal static class RSAKeyExtensions
    {
        #region JSON
        internal static void FromJsonString(this RSA rsa, string jsonString)
        {
            Check.Argument.IsNotEmpty(jsonString, nameof(jsonString));
            try
            {
                var paramsJson = JsonConvert.DeserializeObject<RSAParametersJson>(jsonString);

                RSAParameters parameters = new RSAParameters();

                parameters.Modulus = paramsJson.Modulus != null ? Convert.FromBase64String(paramsJson.Modulus) : null;
                parameters.Exponent = paramsJson.Exponent != null ? Convert.FromBase64String(paramsJson.Exponent) : null;
                parameters.P = paramsJson.P != null ? Convert.FromBase64String(paramsJson.P) : null;
                parameters.Q = paramsJson.Q != null ? Convert.FromBase64String(paramsJson.Q) : null;
                parameters.DP = paramsJson.DP != null ? Convert.FromBase64String(paramsJson.DP) : null;
                parameters.DQ = paramsJson.DQ != null ? Convert.FromBase64String(paramsJson.DQ) : null;
                parameters.InverseQ = paramsJson.InverseQ != null ? Convert.FromBase64String(paramsJson.InverseQ) : null;
                parameters.D = paramsJson.D != null ? Convert.FromBase64String(paramsJson.D) : null;
                rsa.ImportParameters(parameters);
            }
            catch
            {
                throw new Exception("Invalid JSON RSA key.");
            }
        }

        internal static string ToJsonString(this RSA rsa, bool includePrivateParameters)
        {
            RSAParameters parameters = rsa.ExportParameters(includePrivateParameters);

            var parasJson = new RSAParametersJson()
            {
                Modulus = parameters.Modulus != null ? Convert.ToBase64String(parameters.Modulus) : null,
                Exponent = parameters.Exponent != null ? Convert.ToBase64String(parameters.Exponent) : null,
                P = parameters.P != null ? Convert.ToBase64String(parameters.P) : null,
                Q = parameters.Q != null ? Convert.ToBase64String(parameters.Q) : null,
                DP = parameters.DP != null ? Convert.ToBase64String(parameters.DP) : null,
                DQ = parameters.DQ != null ? Convert.ToBase64String(parameters.DQ) : null,
                InverseQ = parameters.InverseQ != null ? Convert.ToBase64String(parameters.InverseQ) : null,
                D = parameters.D != null ? Convert.ToBase64String(parameters.D) : null
            };

            return JsonConvert.SerializeObject(parasJson);
        }
        #endregion

        #region XML

        public static void FromXmlString(this RSA rsa, string xmlString)
        {
            RSAParameters parameters = new RSAParameters();

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xmlString);

            if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue"))
            {
                foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes)
                {
                    switch (node.Name)
                    {
                        case "Modulus": parameters.Modulus = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "Exponent": parameters.Exponent = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "P": parameters.P = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "Q": parameters.Q = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "DP": parameters.DP = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "DQ": parameters.DQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "InverseQ": parameters.InverseQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                        case "D": parameters.D = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    }
                }
            }
            else
            {
                throw new Exception("Invalid XML RSA key.");
            }

            rsa.ImportParameters(parameters);
        }

        public static string ToXmlString(this RSA rsa, bool includePrivateParameters)
        {
            RSAParameters parameters = rsa.ExportParameters(includePrivateParameters);

            return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>",
                  parameters.Modulus != null ? Convert.ToBase64String(parameters.Modulus) : null,
                  parameters.Exponent != null ? Convert.ToBase64String(parameters.Exponent) : null,
                  parameters.P != null ? Convert.ToBase64String(parameters.P) : null,
                  parameters.Q != null ? Convert.ToBase64String(parameters.Q) : null,
                  parameters.DP != null ? Convert.ToBase64String(parameters.DP) : null,
                  parameters.DQ != null ? Convert.ToBase64String(parameters.DQ) : null,
                  parameters.InverseQ != null ? Convert.ToBase64String(parameters.InverseQ) : null,
                  parameters.D != null ? Convert.ToBase64String(parameters.D) : null);
        }

        #endregion
    }
}

@Petermarcu commented on Wed Aug 30 2017

@danmosemsft can you get this moved over to dotnet/corefx to be tracked as an issue there?

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 8
  • Comments: 40 (23 by maintainers)

Commits related to this issue

Most upvoted comments

The fact that I can create a brand new .NET 2 application, then get an intellisense suggestion to use rsaKey.ToXmlString(), but it immediately throws a PlatformNotSupported exception is nuts.

@myloveCc @bartonjs @danmosemsft @Petermarcu - Hi.

I’m seeing the same problem but I must say the “solution” here is rather unimpressive. If after all these years we’ve come back full circle and the much lauded “code reuse” promised by .Net is now nothing more than copying/pasting source code. then we’re in trouble.

The reason is I think obvious, frantically copying snippets of code every time we encounter an “issue” leads to fragility, once we actually get a non-trivial app constructed and we test it, we have much more work to do to prove if our code is flawed or something in the hand-copied-framewrork-support-logic is somehow in error, perhaps copied wrongly for example.

May I suggest that in situations like this at the very least Microsoft formally publish a recommended workaround until the platform itself gets the proper support. At least this way we’d be sure we’re all using the same workaround and reduce risk.

How are companies expected to build rock solid mission critical applications when fundamental parts of their system (in this case stuff associated with identity, encryption and authentication) are gleaned by copying/pasting snippets from Github issues!

Given Microsoft’s desire to be a strong player in cloud hosting it is absolutely astonishing that something as fundamental as this is apparently overlooked.

Thanks

These ToXmlSTring/FromXmlSTring methods do seem to have significant usage…

@creepygnome as @bartonjs says, we aren’t planning to back port any features. 3.0 release is not far off now and previews are available to use today.

@CreepyGnome In general, we don’t backport features. But, I’ll ask:

@danmosemsft Since you’d be part of the approval process and you were pushing (or, at least, nudging) to get this fixed for 3.0, wanna weigh in?

So you are saying there is plenty of time to get the fix into 2.2!! AWESOME!! Let’s Do It!

So Maybe should be re-opened so that can be done. As more people will be using 2.2 for a while before they jump to 3.0 and 2.0/2.1 people may be more likely to jump to 2.2 for a fix like this than to go to 3.0 for an already released application.

@fletchsod-developer I suggest adding to your project extension methods similar to the ones posted above. (I didn’t review that code but essentially that approach)

@bartonjs, I was referring to shipping extension methods like those above. I don’t believe that would cause a circular dependency.