/// <file>validation_rest.cs</file>
/// <version>1.0.0 (2026-06-30T07:17Z)</version>
/// <author>David Ireland <https://di-mgt.com.au/contact></author>
/// <copyright>Copyright (c) 2026 DI Management Services Pty Ltd</copyright>
/// <license>SPDX-License-Identifier: Apache-2.0</license>
/// <summary>REST validation client for the FirmaSAT validation service.</summary>
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace ValidationProject;
public class Validator
{
/// <summary>
/// Loads a certificate from a file path or returns the provided string if it's already in base64 or PEM format.
/// </summary>
/// <param name="certFileOrString">The file path to a certificate or a base64/PEM encoded certificate string.</param>
/// <returns>A base64 encoded certificate string, or an empty string if no certificate is provided.</returns>
private static string LoadCert(string certFileOrString)
{
return certFileOrString.Length == 0
? ""
: File.Exists(certFileOrString)
? Convert.ToBase64String(X509CertificateLoader.LoadCertificateFromFile(certFileOrString).RawData)
: certFileOrString;
}
/// <summary>
/// Validates the supplied signed XML document against the remote FSA validation API.
/// </summary>
/// <param name="xmlFile">The path to the XML file to validate.</param>
/// <param name="certFileOrString">
/// An optional PAC certificate path or a certificate string in base64/PEM format.
/// If empty, no PAC certificate is included in the request.
/// </param>
/// <returns>A task that completes after the validation request has been sent and the response has been printed.</returns>
/// <remarks>
/// The method reads the XML file, optionally loads a PAC certificate, serializes the payload as JSON,
/// sends it to the configured validation endpoint, and writes the response to the console in formatted JSON.
/// </remarks>
public static async Task Validate(string xmlFile, string certFileOrString = "")
{
// Constants
const string url = "https://di-mgt.com.au/cryptosys/api/fsa_validation";
const string apiToken = "YOUR_API_TOKEN_HERE"; // Replace with your own api-token
// Read in XML file to string
string xmlContent = File.ReadAllText(xmlFile, Encoding.UTF8);
XDocument xDoc = XDocument.Parse(xmlContent);
string encodedXml = xDoc.ToString();
// Read in optional PAC certificate
string certContent = LoadCert(certFileOrString);
// Create payload for REST request serialized for JSON
var payload = new Dictionary<string, string>
{
["api-token"] = apiToken,
["xmldata"] = encodedXml,
["pac-cert"] = certContent,
};
string jsonContent = JsonSerializer.Serialize(payload);
using var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
using var client = new HttpClient();
string result;
try
{
using HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
result = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
result = $"Error: {ex.Message}";
}
// Display results in pretty-printed JSON
var options = new JsonSerializerOptions { WriteIndented = true };
try
{
var jsonNode = JsonNode.Parse(result);
// Parse validation-result if it exists and is a string
if (jsonNode?["validation-result"] != null && jsonNode["validation-result"]?.GetValueKind() == JsonValueKind.String)
{
var validationResultString = jsonNode["validation-result"]?.GetValue<string>();
if (validationResultString != null)
{
jsonNode["validation-result"] = JsonNode.Parse(validationResultString);
}
}
Console.WriteLine(jsonNode?.ToJsonString(options));
}
catch
{
Console.WriteLine(result);
}
}
}
public class ValidationApp
{
public static async Task Main(string[] args)
{
// These two files must exist in the CWD
await Validator.Validate("cfdv40-ejemplo-signed-tfd.xml", "pac.cer");
}
}