//// This code is part of Document Solutions for PDF .NET demos.// Copyright (c) MESCIUS inc. All rights reserved.//usingSystem;usingSystem.IO;usingSystem.Drawing;usingSystem.Text;usingSystem.Collections.Generic;usingSystem.Security.Cryptography.X509Certificates; usingOrg.BouncyCastle.Crypto;usingOrg.BouncyCastle.Crypto.Digests;usingOrg.BouncyCastle.Asn1;usingOrg.BouncyCastle.Asn1.X509; usingNet.Pkcs11Interop.Common;usingNet.Pkcs11Interop.HighLevelAPI; usingGrapeCity.Documents.Pdf;usingGrapeCity.Documents.Pdf.Security; namespaceDsPdfWeb.Demos{// This sample shows how to sign an existing PDF file that contains// an empty signature field with a certificate that is stored// on a USB Token for DSC (Digital Signature Certificate).//// The sample includes a ready to use utility class Pkcs11SignatureGenerator// that implements the GrapeCity.Documents.Pdf.IPkcs7SignatureGenerator interface,// and can be used to sign PDFs with certificates stored on a USB Token for DSC.// // Please note that when run directly off the DsPdf demo site,// this sample will NOT sign the PDF, as it passes dummy library name/parameters.// to the Pkcs11SignatureGenerator's ctor. You will need to download the sample// and provide your own library and parameters for the sample code to actually sign a PDF.//publicclassSignUsbToken {publicintCreatePDF(Stream stream) {var doc = newGcPdfDocument();usingvar s = File.OpenRead(Path.Combine("Resources", "PDFs", "SignUsbToken.pdf")); doc.Load(s); try {// This WILL NOT WORK due to dummy USB Token for DSC library name/parameters.// Supply valid library name and parameters to actually sign the PDF.usingvar sg = newPkcs11SignatureGenerator("path-to-dummy-PKCS11.dll",null,null,Encoding.ASCII.GetBytes("12345"),null,null,OID.HashAlgorithms.SHA512); var sp = newSignatureProperties() {SignatureBuilder = newPkcs7SignatureBuilder() {SignatureGenerator = sg,CertificateChain = newX509Certificate2[] { sg.Certificate }, },SignatureField = doc.AcroForm.Fields[0] }; doc.Sign(sp, stream); }catch (Exception) {var page = doc.Pages[0];var r = doc.AcroForm.Fields[0].Widgets[0].Rect;Common.Util.AddNote("Signing failed because a dummy USB Token for DSC library name and dummy parameters were used.\n" +"Provide a valid USB Token library and correct parameters to sign the PDF.", page,newRectangleF(r.Left, r.Bottom + 24, page.Size.Width - r.Left * 2, 0)); doc.Save(stream); } // Done.return doc.Pages.Count; } } /// <summary>/// Implements <seecref="IPkcs7SignatureGenerator"/> /// and allows generating a digital signature using a certificate/// stored on a USB Token for DSC (Digital Signature Certificate)./// /// The <b>Pkcs11Interop</b> NuGet package is used to manage the token./// </summary>publicclassPkcs11SignatureGenerator : IPkcs7SignatureGenerator, IDisposable {publicstaticreadonlyPkcs11InteropFactoriesFactories = newPkcs11InteropFactories(); privateIPkcs11Library_pkcs11Library;privateISlot_slot;privateISession_session;privateIObjectHandle_privateKeyHandle;privatestring_ckaLabel;privatebyte[] _ckaId;privateX509Certificate2_certificate;privateOID_hashAlgorithm;privateIDigest_hashDigest; /// <summary>/// Initializes a new instance of the <seecref="Pkcs11SignatureGenerator"/> class./// The <paramrefname="tokenSerial"/> and <paramrefname="tokenLabel"/> parameters are used/// to select the token to use if several tokens are connected. /// If only one token is connected then both these parameters can be <seelangword="null"/>./// The <paramrefname="ckaLabel"/> and <paramrefname="ckaId"/> parameters are used/// to select the private key to use if the token contains multiple keys./// If the token contains a single private key then both these parameters can be <seelangword="null"/>./// </summary>/// <paramname="libraryPath">Path to the unmanaged PCKS#11 library to use.</param>/// <paramname="tokenSerial">Serial number of the token (smartcard) that contains the signing key.</param>/// <paramname="tokenLabel">Label of the token (smartcard) that contains the signing key.</param>/// <paramname="pin">PIN for the token (smartcard).</param>/// <paramname="ckaLabel">Label (value of CKA_LABEL attribute) of the private key used for signing.</param>/// <paramname="ckaId">Hex encoded string with identifier (value of CKA_ID attribute) of the private key used for signing.</param>/// <paramname="hashAlgorihtm">The hash algorithm to use when creating the signature.</param>publicPkcs11SignatureGenerator(string libraryPath, string tokenSerial, string tokenLabel, byte[] pin, string ckaLabel, byte[] ckaId, OID hashAlgorihtm) {Init(libraryPath, tokenSerial, tokenLabel, pin, ckaLabel, ckaId, hashAlgorihtm); } ~Pkcs11SignatureGenerator() {Dispose(false); } /// <summary>/// Releases resources used by this object./// </summary>publicvoidDispose() {Dispose(true);GC.SuppressFinalize(this); } protectedvoidDispose(bool disposing) {if (disposing) {if (_certificate != null) {_certificate.Dispose();_certificate = null; }if (_session != null) {_session.Dispose();_session = null; }if (_pkcs11Library != null) {_pkcs11Library.Dispose();_pkcs11Library = null; } } } privateISlotFindSlot(string tokenSerial, string tokenLabel) {if (string.IsNullOrEmpty(tokenSerial) && string.IsNullOrEmpty(tokenLabel))thrownewArgumentException("Token serial and/or label has to be specified"); List<ISlot> slots = _pkcs11Library.GetSlotList(SlotsType.WithTokenPresent);foreach (ISlot slot in slots) {ITokenInfo tokenInfo = null; try { tokenInfo = slot.GetTokenInfo(); }catch (Pkcs11Exception ex) {if (ex.RV != CKR.CKR_TOKEN_NOT_RECOGNIZED && ex.RV != CKR.CKR_TOKEN_NOT_PRESENT)throw; } if (tokenInfo == null)continue; if (!string.IsNullOrEmpty(tokenSerial))if (String.Compare(tokenSerial, tokenInfo.SerialNumber, StringComparison.InvariantCultureIgnoreCase) != 0)continue; if (!string.IsNullOrEmpty(tokenLabel))if (String.Compare(tokenLabel, tokenInfo.Label, StringComparison.InvariantCultureIgnoreCase) != 0)continue; return slot; }returnnull; } protectedvoidInit(string libraryPath, string tokenSerial, string tokenLabel, byte[] pin, string ckaLabel, byte[] ckaId, OID hashAlgorihtm) {if (string.IsNullOrEmpty(libraryPath))thrownewArgumentNullException($"Invalid library path \"{libraryPath}\"."); try {_pkcs11Library = Factories.Pkcs11LibraryFactory.LoadPkcs11Library(Factories, libraryPath, AppType.SingleThreaded); _slot = FindSlot(tokenSerial, tokenLabel);if (_slot == null)thrownewException(string.Format("Token with serial \"{0}\" and label \"{1}\" was not found", tokenSerial, tokenLabel)); _session = _slot.OpenSession(SessionType.ReadOnly);_session.Login(CKU.CKU_USER, pin); // initialize _privateKeyHandle and _certificateusing (ISession session = _slot.OpenSession(SessionType.ReadOnly)) {// private keyList<IObjectAttribute> searchTemplate = newList<IObjectAttribute>(); searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY)); searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_KEY_TYPE, CKK.CKK_RSA));if (!string.IsNullOrEmpty(ckaLabel)) searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, ckaLabel));if (ckaId != null) searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, ckaId)); List<IObjectHandle> foundObjects = session.FindAllObjects(searchTemplate);if (foundObjects.Count < 1)thrownewException(string.Format("Private key with label \"{0}\" and id \"{1}\" was not found.", ckaLabel, (ckaId == null) ? null : ConvertUtils.BytesToHexString(ckaId)));elseif (foundObjects.Count > 1)thrownewException(string.Format("More than one private key with label \"{0}\" and id \"{1}\" was found.", ckaLabel, (ckaId == null) ? null : ConvertUtils.BytesToHexString(ckaId)));_privateKeyHandle = foundObjects[0]; // certificate searchTemplate.Clear(); searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_CERTIFICATE));if (!string.IsNullOrEmpty(ckaLabel)) searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, ckaLabel));if (ckaId != null) searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, ckaId)); foundObjects = session.FindAllObjects(searchTemplate);if (foundObjects.Count == 1) {List<CKA> attributes = newList<CKA>(); attributes.Add(CKA.CKA_VALUE); List<IObjectAttribute> certificateAttributes = session.GetAttributeValue(foundObjects[0], attributes);byte[] certificateData = certificateAttributes[0].GetValueAsByteArray();_certificate = newX509Certificate2(certificateData); } } _ckaLabel = ckaLabel;_ckaId = ckaId;if (hashAlgorihtm == OID.HashAlgorithms.SHA1)_hashDigest = newSha1Digest();elseif (hashAlgorihtm == OID.HashAlgorithms.SHA256)_hashDigest = newSha256Digest();elseif (hashAlgorihtm == OID.HashAlgorithms.SHA384)_hashDigest = newSha384Digest();elseif (hashAlgorihtm == OID.HashAlgorithms.SHA512)_hashDigest = newSha512Digest();elsethrownewException($"Unsupported HASH algorithm {hashAlgorihtm}.");_hashAlgorithm = hashAlgorihtm; }catch {if (_session != null) {_session.Dispose();_session = null; }if (_pkcs11Library != null) {_pkcs11Library.Dispose();_pkcs11Library = null; } throw; } } /// <summary>/// Gets the <seecref="Sys.X509Certificate2"/> object found on the token/// with same <b>ckaLabel</b> and <b>ckaId</b> as a private key./// </summary>publicX509Certificate2Certificate {get { return_certificate; } } /// <summary>/// Gets the ID of the hash algorithm./// </summary>publicOIDHashAlgorithm => _hashAlgorithm; /// <summary>/// Gets the ID of the encryption algorithm./// </summary>publicOIDDigestEncryptionAlgorithm => OID.EncryptionAlgorithms.RSA; /// <summary>/// Signs data./// </summary>/// <paramname="input">The input data to sign.</param>/// <returns>The signed data.</returns>publicbyte[] SignData(byte[] input) {using (ISession session = _slot.OpenSession(SessionType.ReadOnly))using (IMechanism mechanism = Factories.MechanismFactory.Create(CKM.CKM_RSA_PKCS)) {byte[] hash = newbyte[_hashDigest.GetDigestSize()];_hashDigest.Reset();_hashDigest.BlockUpdate(input, 0, input.Length);_hashDigest.DoFinal(hash, 0); var derObjectIdentifier = newDerObjectIdentifier(_hashAlgorithm.ID);var algorithmIdentifier = newAlgorithmIdentifier(derObjectIdentifier, DerNull.Instance);var digestInfo = newDigestInfo(algorithmIdentifier, hash);byte[] digestInfoBytes = digestInfo.GetDerEncoded(); return session.Sign(mechanism, _privateKeyHandle, digestInfoBytes); } } }}