A professional .NET library for digital signature operations using XAdES signatures, specifically designed for SRI (Servicio de Rentas Internas) electronic invoicing in Ecuador.
Also available in Spanish: README_es.md
- XAdES Digital Signatures: Full support for XML Advanced Electronic Signatures
- Certificate Management: Flexible certificate loading from P12/PFX files and Base64
- Base64 Certificates: Full support for embedded Base64 certificates (ideal for cloud and containers)
- Async Operations: High-performance asynchronous signature operations
- Configuration Support: Multiple configuration options (appsettings.json, code-based, dynamic)
- Validation: Built-in signature validation and certificate verification
- Logging: Comprehensive logging with structured logging support
- Error Handling: Robust error handling with detailed error messages
- Performance Monitoring: Built-in performance metrics and timing
dotnet add package Yamgooo.SRI.Signgit clone https://github.com/yamgooo/Sri.Sign.git
cd Sri.Sign
dotnet buildusing Yamgooo.SRI.Sign;
// Register the service
services.AddSriSignService();
// Use the service
var sriSignService = serviceProvider.GetRequiredService<ISriSignService>();
// Set certificate dynamically
sriSignService.SetDefaultCertificate("path/to/certificate.p12", "your-password");
// Sign XML content
var result = await sriSignService.SignAsync(xmlContent);
if (result.Success)
{
Console.WriteLine($"XML signed successfully in {result.ProcessingTimeMs}ms");
var signedXml = result.SignedXml;
}
else
{
Console.WriteLine($"Error: {result.ErrorMessage}");
}// Sign with specific certificate (no configuration needed)
var result = await sriSignService.SignAsync(
xmlContent,
"path/to/certificate.p12",
"your-password"
);// Sign with Base64 certificate (ideal for cloud and containers)
var result = await sriSignService.SignWithBase64CertificateAsync(
xmlContent,
"MIIKsAIBAzCCCmwGCSqGSIb3DQEHAaCCCl0EggpZMIIKVTCCBQ...",
"your-password"
);
// Or set default Base64 certificate
sriSignService.SetDefaultBase64Certificate(certificateBase64, password);
var result = await sriSignService.SignWithDefaultBase64CertificateAsync(xmlContent);{
"SriSign": {
"CertificatePath": "Certificates/certificate.p12",
"CertificatePassword": "your-secure-password",
"CertificateBase64": "MIIKsAIBAzCCCmwGCSqGSIb3DQEHAaCCCl0EggpZMIIKVTCCBQ...",
"Base64CertificatePassword": "your-base64-password"
}
}// Register with configuration
services.AddSriSignService(configuration);
// Use the service
var sriSignService = serviceProvider.GetRequiredService<ISriSignService>();
var result = await sriSignService.SignAsync(xmlContent);var config = new SriSignConfiguration
{
CertificatePath = "path/to/certificate.p12",
CertificatePassword = "your-password",
CertificateBase64 = "MIIKsAIBAzCCCmwGCSqGSIb3DQEHAaCCCl0EggpZMIIKVTCCBQ...",
Base64CertificatePassword = "your-base64-password"
};
services.AddSriSignService(config);// Sign with default certificate configuration (prioritizes Base64 if configured)
Task<SignatureResult> SignAsync(string xmlContent);
// Sign with specific certificate
Task<SignatureResult> SignAsync(string xmlContent, string certificatePath, string password);
// Sign with specific Base64 certificate
Task<SignatureResult> SignWithBase64CertificateAsync(string xmlContent, string certificateBase64, string password);
// Sign with default Base64 certificate
Task<SignatureResult> SignWithDefaultBase64CertificateAsync(string xmlContent);// Validate a signed XML document
bool ValidateSignature(string signedXml);
// Validate a Base64 certificate
bool ValidateBase64Certificate(string certificateBase64, string password);// Set default certificate for subsequent operations
void SetDefaultCertificate(string certificatePath, string password);
// Set default Base64 certificate for subsequent operations
void SetDefaultBase64Certificate(string certificateBase64, string password);public class SignatureResult
{
public bool Success { get; set; }
public string SignedXml { get; set; }
public string ErrorMessage { get; set; }
public DateTime SignatureTimestamp { get; set; }
public long ProcessingTimeMs { get; set; }
}// From appsettings.json
services.AddSriSignService(configuration, "SriSign");
// With custom configuration object
services.AddSriSignService(customConfig);
// With direct certificate parameters
services.AddSriSignService("certificate.p12", "password");
// Without configuration (for dynamic use)
services.AddSriSignService();The service automatically validates:
- Certificate file existence and accessibility
- Certificate file size (1KB - 10MB)
- Certificate password validity
- Certificate expiration
- Private key availability
- Valid Base64 format for embedded certificates
- Automatic Base64 format cleaning (line breaks, PEM headers, etc.)
public class Base64CertificateExample
{
private readonly ISriSignService _sriSignService;
public async Task<SignatureResult> SignWithBase64CertificateAsync(string xmlContent)
{
// Convert certificate file to Base64 (one-time operation)
var certificateBytes = File.ReadAllBytes("certificate.p12");
var certificateBase64 = Convert.ToBase64String(certificateBytes);
var password = "your-password";
// Validate Base64 certificate before using
if (!_sriSignService.ValidateBase64Certificate(certificateBase64, password))
{
throw new InvalidOperationException("Invalid Base64 certificate");
}
// Sign with Base64 certificate
return await _sriSignService.SignWithBase64CertificateAsync(xmlContent, certificateBase64, password);
}
public async Task<SignatureResult> SignWithConfiguredBase64Async(string xmlContent)
{
// Configure default Base64 certificate
var certificateBase64 = Environment.GetEnvironmentVariable("SRI_CERTIFICATE_BASE64");
var password = Environment.GetEnvironmentVariable("SRI_CERTIFICATE_PASSWORD");
_sriSignService.SetDefaultBase64Certificate(certificateBase64, password);
// Sign using default configuration
return await _sriSignService.SignAsync(xmlContent);
}
}public class InvoiceSigningService
{
private readonly ISriSignService _sriSignService;
private readonly ILogger<InvoiceSigningService> _logger;
public InvoiceSigningService(ISriSignService sriSignService, ILogger<InvoiceSigningService> logger)
{
_sriSignService = sriSignService;
_logger = logger;
}
public async Task<SignatureResult> SignInvoiceAsync(string invoiceXml)
{
try
{
// Sign the invoice XML
var result = await _sriSignService.SignAsync(invoiceXml);
if (result.Success)
{
_logger.LogInformation("Invoice signed successfully");
// Validate the signature
if (_sriSignService.ValidateSignature(result.SignedXml))
{
_logger.LogInformation("Signature validation passed");
}
else
{
_logger.LogWarning("Signature validation failed");
}
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error signing invoice");
return SignatureResult.CreateFailure(ex.Message);
}
}
}public class MultiTenantSigningService
{
private readonly ISriSignService _sriSignService;
public async Task<SignatureResult> SignForTenantAsync(string xmlContent, string tenantId)
{
// Get tenant-specific certificate
var certificatePath = GetTenantCertificatePath(tenantId);
var certificatePassword = GetTenantCertificatePassword(tenantId);
// Sign with tenant-specific certificate
return await _sriSignService.SignAsync(xmlContent, certificatePath, certificatePassword);
}
}- Certificate Storage: Store certificates securely and never commit them to source control
- Base64 Certificates: Ideal for containers and cloud services, store in environment variables or secret services
- Password Management: Use secure configuration providers (Azure Key Vault, AWS Secrets Manager, etc.)
- File Permissions: Ensure certificate files have appropriate access permissions
- Network Security: Use HTTPS for all network communications
- Logging: Be careful not to log sensitive information like certificate passwords
- Certificate Rotation: Consider rotating certificates regularly, especially in production environments
[Test]
public async Task SignAsync_WithValidXml_ReturnsSuccess()
{
// Arrange
var mockLogger = new Mock<ILogger<SriSignService>>();
var service = new SriSignService(mockLogger.Object);
var xmlContent = "<test>content</test>";
var certificatePath = "test-cert.p12";
var password = "test-password";
// Act
var result = await service.SignAsync(xmlContent, certificatePath, password);
// Assert
Assert.IsNotNull(result);
// Add more specific assertions based on your test certificate
}The service is optimized for high-performance operations:
- Async Operations: All I/O operations are asynchronous
- Memory Efficient: Uses streaming for large XML documents
- Caching: Certificate loading is optimized
- Metrics: Built-in performance monitoring
Typical performance metrics:
- Small XML (< 1KB): ~50-100ms
- Medium XML (1-10KB): ~100-200ms
- Large XML (10-100KB): ~200-500ms
- .NET 9.0: Target framework
- FirmaXadesNet: XAdES signature implementation
- Microsoft.Extensions.Configuration: Configuration support
- Microsoft.Extensions.Logging: Logging infrastructure
- Microsoft.Extensions.Options: Options pattern support
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Issues: GitHub Issues
- Documentation: Wiki
- Email: erikportillapesantez@outlook.com
Made with β€οΈ for the Ecuadorian developer community