我正在尝试在API后面生成Xml文档,对其进行签名,并在Console应用程序中验证签名。 Xml和签名的创建似乎可以正常工作,但是签名未在客户端应用程序中验证。
在我的API控制器中,我发布了一个客户,该代码创建并保留了一个新的RSA公钥/私钥。该方法返回PublicKeyXml。我将客户ID和PublicKeyXml存储在我的客户端测试应用中。客户ID位于appsettings.json中,而PublicKeyXml存储在文件中。
当客户端请求许可证时,将使用附加到端点URI中指定该客户的私钥对输出的Xml文档进行签名。
我的API:
namespace License.HostedApi.Controllers
{
[ApiController]
[Route("api")]
public class LicenseController : ControllerBase
{
private readonly ILogger<LicenseController> _logger;
private const string customerDataFileName = @"Data\Customers.json";
private readonly Dictionary<string, string> customerDictionary;
public LicenseController(ILogger<LicenseController> logger)
{
_logger = logger;
if (System.IO.File.Exists(customerDataFileName))
{
Monitor.Enter(lockObject);
string customerContent = System.IO.File.ReadAllText(customerDataFileName);
Monitor.Exit(lockObject);
if (string.IsNullOrWhiteSpace(customerContent))
{
customerDictionary = new Dictionary<string, string>();
}
else
{
customerDictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(customerContent);
}
}
else
{
customerDictionary = new Dictionary<string, string>();
}
}
[HttpGet("ping")]
public ActionResult Ping()
{
return Ok(true);
}
[HttpPost("customer")]
public ActionResult<string> CreateCustomer([FromBody] string customerName)
{
if (string.IsNullOrWhiteSpace(customerName)) { return BadRequest(); }
Customer customer = new Customer(customerName);
AsymmetricEncryptionService asymmetricEncryptionService = new AsymmetricEncryptionService();
int keySizeBits = 2048;
AsymmetricKeyPairPersistenceResult asymmetricKeyPairPersistenceResult = asymmetricEncryptionService.PersistNewAsymmetricKeyPair(keySizeBits);
if (asymmetricKeyPairPersistenceResult.Success)
{
customerDictionary.Add(customer.GlobalId.ToString(), asymmetricKeyPairPersistenceResult.KeyContainerName);
SaveCustomerDictionary();
return Ok(asymmetricKeyPairPersistenceResult.PublicKeyXml);
}
else
{
return StatusCode(500, "Persistence of asymmetric key failed.");
}
}
[HttpGet("license/{customerId}")]
public ActionResult<string> Get(string customerId)
{
if (customerDictionary.ContainsKey(customerId))
{
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = customerDictionary[customerId];
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = false;
xmlDoc.LoadXml("<license><mode>Trial</mode><features><feature>All</feature></features></license>");
SignedXml signedXml = new SignedXml(xmlDoc);
signedXml.SigningKey = rsaKey;
Reference reference = new Reference();
reference.Uri = string.Empty;
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
signedXml.AddReference(reference);
signedXml.ComputeSignature();
XmlElement xmlDigitalSignature = signedXml.GetXml();
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
MemoryStream stream = new MemoryStream();
XmlWriterSettings settings = new XmlWriterSettings
{
Async = false,
Indent = true
};
stream.Position = 0;
var xWriter = XmlWriter.Create(stream, settings);
xmlDoc.WriteContentTo(xWriter);
xWriter.Flush();
stream.Position = 0;
return Ok(Encoding.UTF8.GetString(stream.ToArray()));
}
else
{
return NotFound($"Customer {customerId} not found");
}
}
private object lockObject = new object();
private void SaveCustomerDictionary()
{
Monitor.Enter(lockObject);
string customerJson = JsonConvert.SerializeObject(customerDictionary, Newtonsoft.Json.Formatting.Indented);
System.IO.File.WriteAllText(customerDataFileName, customerJson);
Monitor.Exit(lockObject);
}
}
}
持久性函数如下所示:
public AsymmetricKeyPairPersistenceResult PersistNewAsymmetricKeyPair(int keySizeBits)
{
AsymmetricKeyPairPersistenceResult asymmetricKeyPairPersistenceResult = new AsymmetricKeyPairPersistenceResult();
try
{
CspParameters cspParams = new CspParameters();
string containerName = Guid.NewGuid().ToString();
cspParams.KeyContainerName = containerName;
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(keySizeBits, cspParams)
{ PersistKeyInCsp = true };
CspKeyContainerInfo keyContainerInfo = new CspKeyContainerInfo(cspParams);
string pathToMachineLevelAsymmetricKeysFolder = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
@"Microsoft\Crypto\RSA\MachineKeys");
asymmetricKeyPairPersistenceResult.KeyContainerName = keyContainerInfo.KeyContainerName;
asymmetricKeyPairPersistenceResult.KeyStorageTopFolder = pathToMachineLevelAsymmetricKeysFolder;
asymmetricKeyPairPersistenceResult.KeyStorageFileFullPath =
Path.Combine(pathToMachineLevelAsymmetricKeysFolder, keyContainerInfo.UniqueKeyContainerName);
asymmetricKeyPairPersistenceResult.Success = true;
asymmetricKeyPairPersistenceResult.PublicKeyXml = rsaProvider.ToXmlString(false);
asymmetricKeyPairPersistenceResult.PublicPrivateKeyPairXml = rsaProvider.ToXmlString(true);
}
catch (Exception ex)
{
asymmetricKeyPairPersistenceResult.ExceptionMessage = ex.Message;
}
return asymmetricKeyPairPersistenceResult;
}
测试客户:
using Microsoft.Extensions.Configuration;
using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace TestClient
{
class Program
{
static readonly HttpClient client = new HttpClient();
static IConfiguration configuration;
static async Task Main(string[] args)
{
configuration = new ConfigurationBuilder()
.AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true)
.Build();
string customerId = configuration.GetSection("CustomerId").Value;
Console.WriteLine($"Customer ID = {customerId}");
var response = await client.GetAsync($"https://localhost:58579/api/license/{customerId}");
if (response.IsSuccessStatusCode)
{
Console.WriteLine("License fetched.");
XmlDocument publicKeyDoc = new XmlDocument();
publicKeyDoc.Load("test1_public_key.xml");
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
RSAParameters parms = new RSAParameters();
parms.Modulus = Encoding.UTF8.GetBytes(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Modulus").InnerText);
parms.Exponent = Encoding.UTF8.GetBytes(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Exponent").InnerText);
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
rsaKey.ImportParameters(parms);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(content);
SignedXml signedXml = new SignedXml(xmlDoc);
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
signedXml.LoadXml((XmlElement)nodeList[0]);
bool isValid = signedXml.CheckSignature(rsaKey);
if (isValid)
{
Console.WriteLine("Doc signature is valid");
}
else
{
Console.WriteLine("Doc signature is INVALID");
}
}
else
{
Console.WriteLine($"Error fetching license: {await response.Content.ReadAsStringAsync()}");
}
}
}
}
公共RSA密钥,与客户端一起存储在文件中:
<?xml version="1.0" encoding="utf-8" ?>
<RSAKeyValue>
<Modulus>xQIe438pmPoGKGkZoDkxrSaj1+DQH6g4tqZaZrTe5yXlD4BZZEWimWaQpuRarD3oOJPmS7GLLqBNWXepWubRkck3GyqyNY5Lraiarm7Jzp4tQ/0H0SPOcbDu6CBlmbET1tIXb0e461VmKupP35/2GSgsmSYEpDZkIF24wf+zt+wzOe7aEQiHQ2Y085yd7JbtkHWbmK8v+85a5RDvNJ75eLUgvmBiwi5RgHQEiIJkLR10IUAq5N/u4EcxvQgGa2rGlWTXMayeQJSvgv0cAMF6kQcTy9sc3MlGEa0qGplhB5FxLcq0uJN0QYQcxMNkYtLXVfrzFCbVDuptUptdNv278Q==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
程序的输出:
Customer ID = f9453533-ba8b-4f6e-8353-bd11700d68be
License fetched.
<?xml version="1.0" encoding="utf-8"?>
<license>
<mode>Trial</mode>
<features>
<feature>All</feature>
</features>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<DigestValue>iANXMen0uhmOxr20/p59uE2A6zHxuY3p0wZyJ/31N9o=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>njoZxBxWGD121IoBZ8yA3hZMa9QojRIS7sjkPicZn0QJv8lzqXIDoLExP1Iw8cuMl3DeSnohBaDz3RWZeqsV+xtwZLh5GN4vUefWp5CBsbRqWSLfCZ/bcVbKfybXWhIQf9kOE5gvczGw4AS1Tw3JLvF9EddAoDEbwQkCdD4jaJM=</SignatureValue>
</Signature>
</license>
Doc signature is INVALID
我认为我缺少简单的东西,但我不知道它是什么。编码,也许吗?
感谢您的帮助。我在做什么错?
您能否检查下面根据原始代码编写的程序?当然,由于简化了代码以使其能够在联机编译器上运行,因此它不会在存储设备上存储任何文件,也不会从远程位置读取内容,但是我认为该示例将提供指导,因为这种方法是相似的。
主要区别包括在创建的许可证XML中使用Convert.FromBase64String和注意UTF8-BOM字符,因为如果存在BOM字符,它会导致LoadXml()方法出错。另外,不使用存储是有区别的,但是由于最终文件在原始代码中被读取为字符串,所以这种区别应该不是很重要。
using System;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace TestClient
{
abstract class OperationResult
{
public bool Success { get; set; }
public string ExceptionMessage { get; set; }
}
class AsymmetricKeyPairPersistenceResult : OperationResult
{
public string KeyContainerName { get; set; }
public string KeyStorageFileFullPath { get; set; }
public string KeyStorageTopFolder { get; set; }
public string PublicKeyXml { get; set; }
public string PublicPrivateKeyPairXml { get; set; }
}
class AsymmetricEncryptionService
{
public AsymmetricKeyPairPersistenceResult PersistNewAsymmetricKeyPair(int keySizeBits)
{
AsymmetricKeyPairPersistenceResult asymmetricKeyPairPersistenceResult = new AsymmetricKeyPairPersistenceResult();
try
{
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(keySizeBits);
asymmetricKeyPairPersistenceResult.Success = true;
asymmetricKeyPairPersistenceResult.PublicKeyXml = rsaProvider.ToXmlString(false);
asymmetricKeyPairPersistenceResult.PublicPrivateKeyPairXml = rsaProvider.ToXmlString(true);
}
catch (Exception ex)
{
asymmetricKeyPairPersistenceResult.ExceptionMessage = ex.Message;
}
return asymmetricKeyPairPersistenceResult;
}
}
class Customer{
public Customer(string Name) { this.Name = Name; this.GlobalId = Guid.NewGuid().ToString(); }
public string Name { get; set; }
public string GlobalId { get; set; }
}
class Program
{
private static readonly Dictionary<string, string> customerDictionary = new Dictionary<string, string>();
private static readonly HttpClient client = new HttpClient();
//static IConfiguration configuration;
public static bool StartProcess()
{
/*configuration = new ConfigurationBuilder()
.AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true)
.Build();
*/
string customerId = createCustomer("Test User").GlobalId;
Console.WriteLine("Customer ID = " + customerId + "\n");
XmlDocument publicKeyDoc = new XmlDocument();
publicKeyDoc.LoadXml(customerDictionary[customerId]);
string content = CreateAndSignXML(customerId, publicKeyDoc);
Console.WriteLine("License fetched.");
Console.WriteLine("-----------------");
Console.WriteLine(content + "\n");
return VerifySignature(content, publicKeyDoc);
}
private static bool VerifySignature(string content, XmlDocument publicKeyDoc){
RSAParameters parameters = new RSAParameters();
parameters.Modulus = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Modulus").InnerText);
parameters.Exponent = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Exponent").InnerText);
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
rsaKey.ImportParameters(parameters);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = false;
xmlDoc.LoadXml(content);
SignedXml signedXml = new SignedXml(xmlDoc);
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
signedXml.LoadXml((XmlElement)nodeList[0]);
return signedXml.CheckSignature(rsaKey);
}
private static string CreateAndSignXML(string customerId, XmlDocument publicKeyDoc){
Console.WriteLine("Started creating XML..");
RSAParameters parameters = new RSAParameters();
// Convert.FromBase64String is used instead of Encoding.UTF8.GetBytes
parameters.Modulus = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Modulus").InnerText);
parameters.Exponent = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Exponent").InnerText);
parameters.D = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/D").InnerText);
parameters.P = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/P").InnerText);
parameters.Q = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/Q").InnerText);
parameters.InverseQ = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/InverseQ").InnerText);
parameters.DP = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/DP").InnerText);
parameters.DQ = Convert.FromBase64String(publicKeyDoc.SelectSingleNode("//RSAKeyValue/DQ").InnerText);
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
rsaKey.ImportParameters(parameters);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = false;
xmlDoc.LoadXml("<license><mode>Trial</mode><features><feature>All</feature></features></license>");
Console.WriteLine("Started signing XML..");
SignedXml signedXml = new SignedXml(xmlDoc);
signedXml.SigningKey = rsaKey;
Reference reference = new Reference();
reference.Uri = string.Empty;
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
signedXml.AddReference(reference);
signedXml.ComputeSignature();
XmlElement xmlDigitalSignature = signedXml.GetXml();
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
MemoryStream stream = new MemoryStream();
XmlWriterSettings settings = new XmlWriterSettings
{
Async = false,
Indent = true
};
stream.Position = 0;
var xWriter = XmlWriter.Create(stream, settings);
xmlDoc.WriteContentTo(xWriter);
xWriter.Flush();
stream.Position = 0;
Console.WriteLine("Completed creating and signing XML.." + "\n");
// Removing BOM character
string finalXmlContent = Encoding.UTF8.GetString(stream.ToArray());
string byteOrderMarkUTF8 = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble());
if (finalXmlContent.StartsWith(byteOrderMarkUTF8))
{
finalXmlContent = finalXmlContent.Remove(0, byteOrderMarkUTF8.Length);
}
return finalXmlContent;
}
private static Customer createCustomer(string customerName){
Customer customer = new Customer(customerName);
AsymmetricEncryptionService asymmetricEncryptionService = new AsymmetricEncryptionService();
int keySizeBits = 2048;
AsymmetricKeyPairPersistenceResult asymmetricKeyPairPersistenceResult = asymmetricEncryptionService.PersistNewAsymmetricKeyPair(keySizeBits);
if (asymmetricKeyPairPersistenceResult.Success)
{
customerDictionary.Add(customer.GlobalId.ToString(), asymmetricKeyPairPersistenceResult.PublicPrivateKeyPairXml);
Console.WriteLine("Public/Private Key Pair");
Console.WriteLine("------------------------");
Console.WriteLine(JsonConvert.SerializeObject(customerDictionary) + "\n");
}
else{
Console.WriteLine(asymmetricKeyPairPersistenceResult.ExceptionMessage);
}
Console.WriteLine("Customer is created successfully");
return customer;
}
public static void Main(string[] args){
bool result = StartProcess();
Console.WriteLine("RESULT");
Console.WriteLine("-------");
if (result)
{
Console.WriteLine("Doc signature is VALID");
}
else
{
Console.WriteLine("Doc signature is INVALID");
}
}
}
}
实时/可运行的版本位于:https://dotnetfiddle.net/Uap80i
注:此示例仅用于测试目的,目的是确定该方法是否成功。因此,该过程不是相同的,而是相似的。