|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text;
namespace System.Security.Cryptography
{
public abstract partial class RSA : AsymmetricAlgorithm
{
private static byte[] ReadRequiredElement(
ref XmlKeyHelper.ParseState state,
string name,
int sizeHint = -1)
{
byte[]? ret = XmlKeyHelper.ReadCryptoBinary(ref state, name, sizeHint);
if (ret == null)
{
throw new CryptographicException(
SR.Format(SR.Cryptography_InvalidFromXmlString, nameof(RSA), name));
}
return ret;
}
public override void FromXmlString(string xmlString)
{
// ParseDocument does the nullcheck for us.
XmlKeyHelper.ParseState state = XmlKeyHelper.ParseDocument(xmlString);
byte[] n = ReadRequiredElement(ref state, nameof(RSAParameters.Modulus));
byte[] e = ReadRequiredElement(ref state, nameof(RSAParameters.Exponent));
int halfN = (n.Length + 1) / 2;
// .NET Framework doesn't report any element other than Modulus/Exponent as required,
// it just lets import fail if they're imbalanced.
byte[]? p = XmlKeyHelper.ReadCryptoBinary(ref state, nameof(RSAParameters.P), halfN);
byte[]? q = XmlKeyHelper.ReadCryptoBinary(ref state, nameof(RSAParameters.Q), halfN);
byte[]? dp = XmlKeyHelper.ReadCryptoBinary(ref state, nameof(RSAParameters.DP), halfN);
byte[]? dq = XmlKeyHelper.ReadCryptoBinary(ref state, nameof(RSAParameters.DQ), halfN);
byte[]? qInv = XmlKeyHelper.ReadCryptoBinary(ref state, nameof(RSAParameters.InverseQ), halfN);
byte[]? d = XmlKeyHelper.ReadCryptoBinary(ref state, nameof(RSAParameters.D), n.Length);
RSAParameters keyParameters = new RSAParameters
{
Modulus = n,
Exponent = e,
D = d,
P = p,
Q = q,
DP = dp,
DQ = dq,
InverseQ = qInv,
};
ImportParameters(keyParameters);
}
public override string ToXmlString(bool includePrivateParameters)
{
// The format of this output is based on the xmldsig ds:RSAKeyValue value, except
// * It writes values as xml:base64Binary instead of ds:CryptoBinary
// * It doesn't strip off leading 0x00 byte values before base64
// * It doesn't emit the output in a namespace
// * When includePrivateParameters is true it writes the private key elements.
// * D, P, Q, DP, DQ, InverseQ
//
// These deviations are inherited from .NET Framework.
// For a public-only export, the output is like the following, but with no whitespace
//
// <RSAKeyValue>
// <Modulus>[base64 modulus]</Modulus>
// <Exponent>AQAB</Exponent>
// </RSAKeyValue>
//
// (using the knowledge that 99.9(etc)% of RSA keys use the same exponent, 65537).
// rsa.KeySize (bits) / 6 will produce a value just slightly smaller than needed:
//
// KeySize | BytesReq | Div5 | Div6
// --------|----------|------|-----
// 16384 | 2732 | 3276 | 2730
// 2048 | 344 | 409 | 341
// 1024 | 172 | 204 | 170
// 512 | 88 | 102 | 85
//
// So just add 3 chars to the overhead.
// The overhead, otherwise, is 65 chars, plus exponent's actual value.
// While most keys are AQAB (0x010001) it's technically a variable.
// CAPI has a limit of 32 bits. CNG-Win7 is unbounded, CNG-Win10 is 64-bits.
// So call it 12 chars ((64/8 + 2) / 3 * 4).
// 65 + 32 + 3 = 100. Nice, round, number.
//
// For private keys, D is the same size as Modulus, and P/Q/DP/DQ/InverseQ are
// each half the size of Modulus. So their variable payload is 5 * (KeySize / 2 / 6).
//
// Their element tags add 58 extra characters, and sprinkle in another 3 each (18 total) for
// base64 vs div6 padding, for a conditional overhead of 76 chars.
int keySizeDiv6 = KeySize / 6;
int initialCapacity = 100 + keySizeDiv6;
if (includePrivateParameters)
{
initialCapacity += 76 + 5 * keySizeDiv6 / 2;
}
RSAParameters keyParameters = ExportParameters(includePrivateParameters);
StringBuilder builder = new StringBuilder(initialCapacity);
builder.Append("<RSAKeyValue>");
XmlKeyHelper.WriteCryptoBinary(nameof(RSAParameters.Modulus), keyParameters.Modulus, builder);
XmlKeyHelper.WriteCryptoBinary(nameof(RSAParameters.Exponent), keyParameters.Exponent, builder);
if (includePrivateParameters)
{
// Match .NET Framework field order.
XmlKeyHelper.WriteCryptoBinary(nameof(RSAParameters.P), keyParameters.P, builder);
XmlKeyHelper.WriteCryptoBinary(nameof(RSAParameters.Q), keyParameters.Q, builder);
XmlKeyHelper.WriteCryptoBinary(nameof(RSAParameters.DP), keyParameters.DP, builder);
XmlKeyHelper.WriteCryptoBinary(nameof(RSAParameters.DQ), keyParameters.DQ, builder);
XmlKeyHelper.WriteCryptoBinary(nameof(RSAParameters.InverseQ), keyParameters.InverseQ, builder);
XmlKeyHelper.WriteCryptoBinary(nameof(RSAParameters.D), keyParameters.D, builder);
}
builder.Append("</RSAKeyValue>");
return builder.ToString();
}
}
}
|