Signing PDF document by certificate - c#

Im trying digitaly sign PDF document with Syncfusion. (Library for generating pdf document)
Entire document, not just field.
I don't know mutch about signing or certificates. I know that certificate is type of HSM, so i used:
Syncfusion - Externally sign a pdf document
It works on my development PC, but doesn't in production. Certificate is find, but at signing document it causes:
CryptographicException: Unknown error „-1073741823“ at System.Security.Cryptography.Pkcs.SignedCms.Sign(CmsSigner signer, Boolean silent)
My code:
using Syncfusion.Licensing;
using Syncfusion.Pdf;
using Syncfusion.Pdf.Graphics;
using Syncfusion.Pdf.Parsing;
using Syncfusion.Pdf.Security;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
namespace SyncfusionTest3
{
class Program
{
public static X509Certificate2 infoCertifikat = null;
static void Main(string[] args)
{
var store = new System.Security.Cryptography.X509Certificates.X509Store(StoreLocation.LocalMachine);
string thumbprint = "9F.."; //Production
store.Open(OpenFlags.ReadOnly);
foreach (var mCert in store.Certificates)
{
if (mCert.Thumbprint.ToUpper().Equals(thumbprint.ToUpper()))
infoCertifikat = mCert;
}
if (infoCertifikat == null)
{
Console.WriteLine("404 Certificate not found");
Console.ReadKey();
return;
}
string licenceKey = "LicenceKey";
SyncfusionLicenseProvider.RegisterLicense(licenceKey);
using (var pdfDoc = new PdfLoadedDocument("document.pdf"))
{
pdfDoc.DocumentInformation.Creator = "Me";
pdfDoc.DocumentInformation.Author = "Naxi";
PdfCertificate pdfCertificate = new PdfCertificate(infoCertifikat);
//Normal signing
//Syncfusion.Pdf.Security.PdfSignature signature1 = new Syncfusion.Pdf.Security.PdfSignature(pdfDoc, pdfDoc.Pages[0], pdfCertificate, "DigitalSign");
//External signing becouse of HSM type of certificate
Syncfusion.Pdf.Security.PdfSignature signature1 = new Syncfusion.Pdf.Security.PdfSignature(pdfDoc, pdfDoc.Pages[0], null, "DigitalSign");
signature1.ComputeHash += Signature_ComputeHash1;
signature1.Bounds = new System.Drawing.RectangleF((6 * 25.4f / 0.352777778f), (9.3f * 25.4f / 0.352777778f), 65, 25);
signature1.ContactInfo = "Contact";
signature1.LocationInfo = "World";
signature1.Reason = "I want it";
PdfStandardFont font = new PdfStandardFont(PdfFontFamily.Helvetica, 3.8f);
float row_height = 4.2f;
signature1.Appearance.Normal.Graphics.DrawString("Digitally Signed by " + signature1.ContactInfo, font, PdfBrushes.Black, 0, row_height * 1);
signature1.Appearance.Normal.Graphics.DrawString("Reason: " + signature1.Reason, font, PdfBrushes.Black, 0, row_height * 2);
signature1.Appearance.Normal.Graphics.DrawString("Location: " + signature1.LocationInfo, font, PdfBrushes.Black, 0, row_height * 3);
signature1.Appearance.Normal.Graphics.DrawString((DateTime.Now).ToString(), font, PdfBrushes.Black, 0, row_height * 4);
pdfDoc.Save("document_signed.pdf");
pdfDoc.Close(true);
}
}
private static void Signature_ComputeHash1(object sender, PdfSignatureEventArgs ars)
{
//Get the document bytes
byte[] documentBytes = ars.Data;
SignedCms signedCms = new SignedCms(new ContentInfo(documentBytes), detached: true);
var cmsSigner = new CmsSigner(infoCertifikat);
//Set the digest algorithm SHA256
//cmsSigner.DigestAlgorithm = new Oid("1.3.6.1.4.1.311.10.3.12"); //Document signing – just tried
//cmsSigner.DigestAlgorithm = new Oid("1.2.840.113549.1.1.11"); //SHA256RSA
//cmsSigner.DigestAlgorithm = new Oid("1.2.840.113549.1.1.5"); //SHA1RSA
//cmsSigner.DigestAlgorithm = new Oid("1.3.14.3.2.26"); //SHA1
cmsSigner.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1"); //SHA256
cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly; //Without this it throws CryptographicException: A certificate chain could not be bulit to a trusted root authority. (only in production)
signedCms.ComputeSignature(cmsSigner);
//Embed the encoded digital signature to the PDF document
ars.SignedData = signedCms.Encode();
}
}
}
I tried use different DigestAlgorithm. (commented in code) But i dont't know which i should use.
Im building it for Platform target x64 because without it it causes:
CryptographicException: The keyset is not defined. at System.Security.Cryptography.Pkcs.PkcsUtils.CreateSignerEncodeInfo(CmsSigner signer, Boolean silent, DafeCryptoProvHandle)
There is censored certificate used on production:
My certificates on develop have Key Usage: Signing document ,which is missing on production.
Or Enhanced Key Usage is missing something?
Thank you for any sugesstion.
UPDATE:
So, I made i little steps in different few ways which look hopefully, but not mutch.
I tried Normal signing of Syncfusion and set CryptographicStandard.CADES
Syncfusion.Pdf.Security.PdfSignature signature1 = new Syncfusion.Pdf.Security.PdfSignature(pdfDoc, pdfDoc.Pages[0], pdfCertificate, "DigitalSign");
signature1.Settings.CryptographicStandard = CryptographicStandard.CADES;
Its make signed document and crash the program. Like without any exception.
I looked on way, which was used some time ago and i find out, that it was signing with hashSHA1, RSA + PKCS#1.
So, i tried in external way use oid:
cmsSigner.DigestAlgorithm = new Oid("1, 3, 36, 3, 3, 1, 1"); //RSA + SHA1
And that causes: CryptographicsException: The object identifier is poorly formatted. at SignedCms.Sign(...)
And another way Im triying is invating colleague, so we can suffer colletively.
I dont know what Im doing.
Update 2:
So, the right way is probably using external with SHA1. Signing document works, but when program ends, the program don't close like normal but stops working.
The same thing do microsoft application certutil.
After lists certificates it stops working.
This is new info about that certificate. Its in HSM provided by Luna.
================ Certificate 0 ================
Serial Number: ...
Issuer: ...
NotBefore: ...
NotAfter: ...
Subject: ...
Non-root Certificate
Cert Hash(sha1): 9f
Key Container = ...
Provider = Luna enhanced RSA and AES provider for Microsoft Windows
Private key is NOT exportable
ERROR: Could not verify certificate public key against private key
Revocation check skipped -- server offline
Certificate is valid

Make sure the certificate is installed in the correct store (you code shows StoreLocation.LocalMachine). So let's first check if the certificate in the store is fine and the key is associated and accessible.
Run the following command to list and verify all certificates in the store:
certutil -verifystore MY
The output should show something like this:
================ Certificate X ================
Serial Number: ...
Issuer: ...
NotBefore: ...
NotAfter: ...
Subject: ...
Non-root Certificate
Cert Hash(sha1): ...
Key Container = ...
Unique container name: ...
Provider = ...
Private key is NOT exportable
Signature test passed
...
This will show you the Private Key Association and its Provider and will create a signature to check if the key is accessible.
So ensure the permission to access the key is given for the user running the command, as well as your application. You might need to clarify this by reading the HSM user manual or contact someone the responsible person (in-house or the manufacturer support)
You SignedCms code looks good - for testing keep it as simple as possible (no digest algorithm) and step forward in small steps.
EDIT
Additionally, of curse, you need to have your certificate trusted. This means that the certificate trust chain is installed to the certificate store.
The certificates can be found here: http://crt.postsignum.cz/certifikaty_autorit.html
Run certlm.msc
PostSignum Root QCA 4 root certification authority Needs to be added to the Trusted Root Certificate Authorities by right clicking the directory and selecting All tasks and * Import*
PostSignum Qualified CA 4 Needs to be added to the Intermediate Certificate Authorities by right clicking the directory and selecting All tasks and * Import*

Related

How to AuthenticateAsServer when Certificate is in HSM and privateKey is not embeded in Certificate and PrivateKey is not extractable from HSM

I want to ask a qusetion according to my code, My code is as bellow:
in AuthenticateAsServer I get "The server mode SSL must use a certificate with the associated private key" error cause privatekey is not in my certificate and also privatekey is not extractable from the HSM, would you please guid me what is the solution here?
static void ProcessClient(TcpClient client)
{
SslStream sslClientStream = new SslStream(client.GetStream(), true, AllowAnyServerCertificate, null, EncryptionPolicy.RequireEncryption);
try
{
X509Certificate2 _HsmserverCertificate = null;
string pkcs11LibraryPath = "C:\\Program Files (x86)\\nCipher\\nfast\\toolkits\\pkcs11\\cknfast-64.dll";
Pkcs11InteropFactories factories = new Pkcs11InteropFactories();
using (IPkcs11Library pkcs11Library = factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, pkcs11LibraryPath, AppType.MultiThreaded))
{
ISlot slot = HelpersMethods.GetUsableSlot(pkcs11Library);
using (Net.Pkcs11Interop.HighLevelAPI.ISession session = slot.OpenSession(SessionType.ReadWrite))
{
session.Login(CKU.CKU_USER, #"1234");
var certificate = ReadCertificates(slot, session)[0];
_HsmserverCertificate = new X509Certificate2(certificate.CkaValue);
session.Logout();
}
}
sslClientStream.ReadTimeout = glb_intReciveTimeOut;
sslClientStream.WriteTimeout = glb_intSendTimeOut;
sslClientStream.AuthenticateAsServer(_HsmserverCertificate,
clientCertificateRequired: false,
SslProtocols.Tls12,
checkCertificateRevocation: true);
}
}
Unwritten rule in .NET world: If you want to use an instance X509Certificate2 class in SSL connection then you cannot create it manually but you need to acquire it from X509Store.
X509Store class provides access to all certificates propagated into windows certificate store. Take a look at your device documentation for more details on how to propagate your device certificates into windows certificate store. Sometimes it is also referred to as CAPI, CSP, CNG, KSP etc. If you are unfamiliar with those terms then your best bet is to contact device vendor support.

EPPlus - How to Remove Digital Signature

I would like to remove a digital signature from a VBA signed excel macro file. However when I look at EPPlus's library I see that the "Signature" property is read-only, and setting the Certificate as null doesn't seem to remove it, only invalidates the signature in the file:
using (ExcelPackage xlPackage = new ExcelPackage(fiNew))
{
xlPackage.Workbook.VbaProject.Signature.Certificate = null;
xlPackage.Save();
}
Calling the dispose method doesn't work either, errors out on the save. Does anybody know how to do this in EPPlus?
Looking at the source just provide a certificate without a private key - see line 137.
internal void Save(ExcelVbaProject proj)
{
if (Certificate == null)
{
return;
}
if (Certificate.HasPrivateKey==false) //No signature. Remove any Signature part
You could for example just use the first certificate in the Trusted Root Certificate Authorities, which have no keys, as long as you are not running on a root certificate authority or somebody improted a PFX by accident... so we filter for that too:
Here is some code to read from the Trusted Root Certificate Authorities store:
using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) {
store.Open(OpenFlags.ReadOnly);
var someCertWithoutPrivateKey =
store.Certificates
.Cast<X509Certificate2>()
.Where(c => !c.HasPrivateKey)
.FirstOrDefault();
}

How do I use the private key from a PFX certificate stored in Azure Key Vault in .NET Core 2?

I've written an ASP.NET Core 2.0 website in C# and have Facebook authentication enabled, so it requires HTTPS. I'm using the native Kestrel web server to host the site and have a listener set to take the PFX certificate per MS' documentation. I can't seem to find a way for Kestrel to recognize the private key after recall from Key Vault. I know it's present, as I wrote two debug statements that indicate it is, in fact present.
This is the function that I'm using to retrieve the secret, which is working.
public static async Task<X509Certificate2> GetKeyVaultCert()
{
X509Certificate2 pfx;
try
{
var kvClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
var secret = await kvClient
.GetSecretAsync("https://macscampvault.vault.azure.net/secrets/letsencrypt").ConfigureAwait(false);
byte[] bytes;
if(secret.ContentType == "application/x-pkcs12")
bytes = Convert.FromBase64String(secret.Value);
else
{
bytes = new byte[0];
Console.WriteLine("secret is not PFX!!");
throw new ArgumentException("This is not a PFX string!!");
}
var password = new SecureString();
var coll = new X509Certificate2Collection();
coll.Import(bytes, null, X509KeyStorageFlags.Exportable);
pfx = coll[0];
// File output added in case I end up needing to write cert to container
// File.WriteAllBytes(Directory.GetCurrentDirectory().ToString() + "/Macs.pfx", bytes);
Console.WriteLine(pfx.HasPrivateKey);
Console.WriteLine(pfx.GetRSAPrivateKey());
}
catch (Exception ex)
{
Console.WriteLine($"There was a problem during the key vault operation\n{ex.Message}");
throw;
}
return pfx;
}
The debug statements after the assignment call pfx = coll[0]; tell me that this private key exists, but when I try to connect to the website using lynx https://localhost I receive the following exception:
System.NotSupportedException: The server mode SSL must use a certificate with the associated private key.
So, how do I use the private key? Here's a gist to the file in question.
I already was helped by How to serialize and deserialize a PFX certificate in Azure Key Vault? but after following it, I got to this state.
In your gist you have the following code:
var keyVaultCert = GetKeyVaultCert().Result ??
throw new ArgumentNullException("GetKeyVaultCert().Result");
pfx = new X509Certificate2(keyVaultCert.RawData);
The second line there removes the private key, because the RawData property just returns the DER encoded X.509 object.
keyVaultCert is already an X509Certificate2 with a private key, you probably want to just use it.
pfx = GetKeyVaultCert().Result ?? throw etc;

C# USB eToken signature and validation issue

I have an x509 certificate with a public and private key that is stored on a safenet usb token.
I have some data I want to sign. I need to use the public key of the certificate to verify the signature.
Ultimate code doing the signing with my own self signed certificate:
RSACryptoServiceProvider rsa1 = (RSACryptoServiceProvider)useCertificate.PrivateKey;
byte[] digitalSignature = rsa1.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
And the code to verify using the public key of the certificate:
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)useCertificate.PublicKey.Key;
Verified = rsa.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), digitalSignature);
With the self signed certificate this works fine. The signature I get back is 256
Bytes.
With the token using this code to obtain the signature and then verify it, I get only 128 Byte signature and the verify fails:
CspParameters csp = new CspParameters(1, "SafeNet RSA CSP");
csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
csp.KeyNumber = (int)KeyNumber.Signature;
RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider(csp);
Verify code same as above.
I note that the certificate I want to use is the default in the token. Why am I only getting a 128 Byte signature back instead of 256? I suspect that is why it won't verify.
Do I need some other parameters and settings in my csp?
Thanks
* Update based on comments *
It's clear that I am using 1024 bits when I specify the csp.keyNumber = (int)KeyNumber.Signature - but this is the only way the token actually returns anything. Even though the token key size is 2048 bits and the key specification is AT_KEYEXCHANGE. When I use the exchange keynumber which I think is actually correct, then when I try to compute a signature I am prompted to login, but then I get an exception "The parameter is invalid". So I need one of 2 things as far as I can see:
1 - how to use the public key to verify the signature using 1024 bits (without the token - we need to verify on a machine without the token).
or
2 - how to set whatever is incorrect so that we can get passed the exception -- which I think is the better idea.
Does anyone have any advice on what I can do about this exception or what might be causing it?
Full exception details below:
HResult = -2147024809
Message = The parameter is incorrect.
Stack Trace
at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)
at System.Security.Cryptography.Utils.SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash, Int32 cbHash, ObjectHandleOnStack retSignature)
at System.Security.Cryptography.Utils.SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash)
at System.Security.Cryptography.RSACryptoServiceProvider.SignHash(Byte[] rgbHash, Int32 calgHash)
at System.Security.Cryptography.RSACryptoServiceProvider.SignHash(Byte[] rgbHash, String str)
at TE.Program.Main(String[] args) in z:\Work\compusolve\enctest\TE\TE\Program.cs:line 77
The answer to this is two fold. If you are using one of these devices, I found that in the registry under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider
There are 3 different providers. Each with identical settings for type and even image - the dll used. But selecting a different one, in my case Datakey RSP CSP, provided the 256 byte signature based on the 2048 bit key. You also have to ensure that the certificate you are using is the default certificate in the token. In my case there were two different certificates. I was verifying using one, but signing using another.
Complete source code for a test client is below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
namespace TE
{
class Program
{
static void Main(string[] args)
{
try
{
// these variables should be changed to math your installation
// find CSP's in this windows registry key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider
string TokenCSPName = "Datakey RSA CSP";
string TokenCertificateName = "ACME Inc";
string NonTokenCertificateName = "SelfSigned";
string certLocation = "Token"; // change to something else to use self signed "Token" for token
// the certificate on the token should be installed into the local users certificate store
// tokens will not store or export the private key, only the public key
// find the certificate we want to use - there's no recovery if the certificate is not found
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.OpenExistingOnly);
X509Certificate2Collection certificates = store.Certificates;
X509Certificate2 certificate = new X509Certificate2();
X509Certificate2 useCertificate = new X509Certificate2();
if (certLocation == "Token")
{
for (int i = 0; i < certificates.Count; i++)
{
certificate = certificates[i];
string subj = certificate.Subject;
List<X509KeyUsageExtension> extensions = certificate.Extensions.OfType<X509KeyUsageExtension>().ToList();
if (certificate.GetNameInfo(X509NameType.SimpleName, false).ToString() == TokenCertificateName)
{
for (int j = 0; j < extensions.Count; j++)
{
if ((extensions[j].KeyUsages & X509KeyUsageFlags.DigitalSignature) == X509KeyUsageFlags.DigitalSignature)
{
useCertificate = certificate;
j = extensions.Count + 1;
}
}
}
}
} else
{
for (int i = 0; i < certificates.Count; i++)
{
certificate = certificates[i];
string subj = certificate.Subject;
List<X509KeyUsageExtension> extensions = certificate.Extensions.OfType<X509KeyUsageExtension>().ToList();
if (certificate.GetNameInfo(X509NameType.SimpleName, false).ToString() == NonTokenCertificateName)
useCertificate = certificate;
}
}
CspParameters csp = new CspParameters(1, TokenCSPName);
csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
csp.KeyNumber = (int)KeyNumber.Exchange;
RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider(csp);
string SignatureString = "Data that is to be signed";
byte[] plainTextBytes = Encoding.ASCII.GetBytes(SignatureString);
bool Verified = false;
using (SHA1CryptoServiceProvider shaM = new SHA1CryptoServiceProvider())
{
// hash the data to be signed - you can use signData and avoid the hashing if you like
byte[] hash = shaM.ComputeHash(plainTextBytes);
// sign the hash
byte[] digitalSignature = rsa1.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
// check your signature size here - if not 256 bytes then you may not be using the proper
// crypto provider
// Verify the signature with the hash
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)useCertificate.PublicKey.Key;
Verified = rsa.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), digitalSignature);
if (Verified)
{
Console.WriteLine("Signature Verified");
}
else
{
Console.WriteLine("Signature Failed Verification");
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
I have to challenge your assertion that it's actually the default key container key (you may have caused that one to be created the first time your code ran, since you didn't assert the UseExistingKey flag).
Assuming the certificate is in your cert store, run certutil -user -silent store my and find the certificate entry and check the Key Container value:
================ Certificate 11 ================
Serial Number: 0123456789abcdeffedcba9876543210
Issuer: CN=Intermediate Certificate Authority
NotBefore: 10/21/2016 7:26 AM
NotAfter: 10/21/2017 7:26 AM
Subject: CN=bartonjs
Non-root Certificate
Template:
Cert Hash(sha1): 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14
Key Container = le-Smartcard-987abcdf-6332-43-16531
Provider = Microsoft Base Smart Card Crypto Provider
If you copy/paste whatever value that is and use it as the key container name your signatures will probably start being the correct size.
(If your certificate is in the machine store instead of the user store, omit the -user option)

Certificates from SmartCard in C#

How can I ensure to I am accesing the Certificates from my SmartCard and not form my personal certificate store in c#?
and How can I make my RSACryptoProvider to utilize my smart card certificate private key?
thanks
Wally
Sometimes, especially if you are not using default key container name on the smart card (recommended by Microsoft), certificates are not copied to local certificate store. The solution is to use crypto api to access the key with KP_CERTIFICATE, construct certificate from the retrieved data, and assign it a new RSACryptoServiceProvider constructed using your own key container name.
The pseudo C# code follows:
int reti = CryptoApi.CryptGetUserKey(_hprovider, keytype, ref userKey);
if (reti)
{
reti =CryptoApi.CryptGetKeyParam(_userKey, KP_CERTIFICATE, ref pbdata, ref pwddatalen, 0);
}
if (reti || pwddatalen>0)
{
byte[] data = new byte[pwddatalen];
ret = CryptoApi.CryptGetKeyParam(_userKey, KP_CERTIFICATE, data, ref pwddatalen, 0);
if (ret)
{
X509Certificate2 c = new X509Certificate2(data);
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection col = store.Certificates.Find(X509FindType.FindByThumbprint, c.Thumbprint, validonly);
store.Close();
if (col.Count != 1)
{
//not found in store - CSP didn't copy it
c.PrivateKey = PrivateKey(keytype);
return c;
}
else
{
return col[0];
}
}
}
private RSACryptoServiceProvider PrivateKey (KeyType keytype)
{
CspParameters csparms = new CspParameters();
csparms.KeyContainerName = _containerName;
csparms.ProviderName = _provider;
csparms.ProviderType = 1;
csparms.Flags = CspProviderFlags.UseMachineKeyStore | CspProviderFlags.UseExistingKey;
csparms.KeyNumber = (int)keytype;
return new RSACryptoServiceProvider(csparms);
}
You will need to go through your Cryptographic Service Provider (CSP) for your smartcard. On Windows (2000, XP, and Vista) any time you insert your smartcard into a smartcard reader all the certificates on it are propogated to your personal certificate store. Your private key stays on your smart card. What that means is if you use your certificate (for example to digitally sign an e-mail) then you are prompted to insert your smart card. If your smart card requires a PIN you will be asked to input it. The reason for this is that there is one location for applications to look for user certificates, your personal certificate store, so applications don't have to be rewritten just to handle certificates on smartcards.

Categories