// =========================================================================== //  ©2013-2026 WebSupergoo. All rights reserved. // //  This source code is for use exclusively with the ABCpdf product with //  which it is distributed, under the terms of the license for that //  product. Details can be found at // //    http://www.websupergoo.com/ // //  This copyright notice must not be deleted and must be reproduced alongside //  any sections of code extracted from this module. // =========================================================================== using System; using System.Diagnostics; using System.IO; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using System.Net; using System.Net.Http; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using WebSupergoo.ABCpdf14; using WebSupergoo.ABCpdf14.Objects; namespace SignPdf {   class Program {     // You will need to change these values to ones appropriate for your login and setup.     // See the instructions at the link below for details of what these values are and how to get them.     // https://www.websupergoo.com/abcpdf-pdf-signatures-globalsign.aspx     // Files should be placed in the top level folder - the one in which the .csproj is located.     static Rest rest = new Rest("0000000000", "0000000000000000000000000000000000000000", "wsg.pfx", "password");     static void Main(string[] args) {       if (rest.Dir == null) {         Console.WriteLine($"Unable to find pfx file. See Notes.rtf for what to do.");         Console.ReadKey();         return;       }       Console.WriteLine($"Connecting to GlobalSign");       var l = rest.Login();       // The validation policy tells which fields you should set.       // You must set the required ones, you may set the optional ones, ignore all the rest.       // Many fields are static which means they are pre-filled for you by GlobalSign.       var v = rest.ValidationPolicy(l);       // For example...       Rest.IdentityRequest id = new Rest.IdentityRequest();       if (v.subject_dn.common_name.presence == "REQUIRED") {         id.subject_dn = new Rest.IdentityRequest.Subject();         id.subject_dn.common_name = "John Doe";       }       var identity = rest.Identity(l, id);       var cert = X509Certificate2.CreateFromPem(identity.signing_cert);       Console.WriteLine($"Signing");       using (Doc doc = new Doc()) {         doc.FontSize = 96;         doc.Rect.Inset(36, 36);         doc.AddText("GlobalSign Example");         Signature sig = doc.Form.AddSignature(new XRect("100 100 300 200"), "Sig");         sig.CustomSigner2 = (data, state) => {           if (state.HasFlag(Signature.State.Signing)) {             using (var s = SHA256.Create())               data = s.ComputeHash(data);             var signature = rest.Sign(l, identity, Convert.ToHexString(data)).signature;             return Convert.FromHexString(signature);           }           if (state.HasFlag(Signature.State.Timestamping)) {             var signature = rest.Timestamp(l, Convert.ToHexString(data)).token;             return Convert.FromBase64String(signature);           }           throw new Exception("State not recognized.");         };         // It is important to embed Long Term Validation (LTV) information         // as GlobalSign certificates typically expire after ten minutes.         // An easy way to do this is to specify an appropriate PAdES level.         sig.CompliancePades = Signature.PadesLevel.PAdES_B_LTA;         var type = Signature.DataType.Pkcs9Digest |           Signature.DataType.TimeStampDigest |           Signature.DataType.TimeStampToken;         sig.Sign(new Oid("SHA256"), type, 5120, cert);         doc.Save(Path.Combine(rest.Dir, "_GlobalSign.pdf"));       }       Console.WriteLine($"Validating");       using (Doc doc = new Doc()) {         doc.Read(Path.Combine(rest.Dir, "_GlobalSign.pdf"));         var sig = (Signature)doc.Form.Fields["Sig"];         var ok = sig.Validate(new X509Certificate2[] { cert });         if (!ok || !sig.Validity.SigningTimeIsTrusted)           Console.WriteLine($"Error: Signature invalid");       }       Console.WriteLine($"Finished");       Console.ReadKey();     }     class Rest {       public readonly string Dir;       private readonly string _key;       private readonly string _secret;       private readonly HttpClientHandler _handler;       private readonly HttpClient _client;       private Rest() { }       public Rest(string apiKey, string apiSecret, string mTlsName, string mTlsPassword) {         _key = apiKey;         _secret = apiSecret;         var dir = new DirectoryInfo(Directory.GetCurrentDirectory());         while (!File.Exists(Path.Combine(dir.FullName, mTlsName))) {           dir = dir.Parent;           if (dir == null)             return;         }         Dir = dir.FullName;         _handler = new HttpClientHandler();         _handler.ClientCertificateOptions = ClientCertificateOption.Manual;         _handler.SslProtocols = SslProtocols.Tls12;         _handler.ClientCertificates.Add(new X509Certificate2(Path.Combine(Dir, mTlsName), mTlsPassword));         _client = new HttpClient(_handler);       }       // These JSON deserialization classes are based on the structures detailed here       // https://www.globalsign.com/en/resources/apis/api-documentation/digital-signing-service-api-documentation.html       public class LoginRequest {         public string api_key { get; set; }         public string api_secret { get; set; }       }       public class LoginResponse {         public string access_token { get; set; }       }       public class IdentityRequest {         public class TypeAndValue {           public string type { get; set; }           public string value { get; set; }         }         public class Subject {           public string country { get; set; }           public string state { get; set; }           public string locality { get; set; }           public string street_address { get; set; }           public string organization { get; set; }           public string[] organizational_unit { get; set; }           public string common_name { get; set; }           public string email { get; set; }           public string jurisdiction_of_incorporation_locality_name { get; set; }           public string jurisdiction_of_incorporation_state_or_province_name { get; set; }           public string jurisdiction_of_incorporation_country_name { get; set; }           public string business_category { get; set; }           public TypeAndValue[] extra_attributes { get; set; }         }         public class San {           public string[] dns_names { get; set; }           public string[] emails { get; set; }           public string[] ip_addresses { get; set; }           public string[] uris { get; set; }           public TypeAndValue[] other_names { get; set; }           public TypeAndValue[] Items { get; set; }         }         public class KeyUsages {           public string digital_signature { get; set; }           public bool content_commitment { get; set; }           public bool key_encipherment { get; set; }           public bool data_encipherment { get; set; }           public bool key_agreement { get; set; }           public bool key_certificate_sign { get; set; }           public bool crl_sign { get; set; }           public bool encipher_only { get; set; }           public bool decipher_only { get; set; }         }         public Subject subject_dn { get; set; }         public San san { get; set; }         public KeyUsages key_usages { get; set; }         public string[] extended_key_usages { get; set; }         // more properties can be found in GlobalSign docs       }       public class IdentityResponse {         public string id { get; set; }         public string signing_cert { get; set; }         public string ocsp_response { get; set; }       }       public class ValidationResponse {         public DN subject_dn { get; set; }         public class DN {           public class Policy {             public string presence { get; set; }             public string format { get; set; }           }           public Policy common_name { get; set; }           public Policy organization { get; set; }           public Policy organizational_unit { get; set; }           public Policy country { get; set; }           public Policy state { get; set; }           public Policy locality { get; set; }           public Policy street_address { get; set; }           public Policy email { get; set; }           public Policy jurisdiction_of_incorporation_locality_name { get; set; }           public Policy jurisdiction_of_incorporation_state_or_province_name { get; set; }           public Policy jurisdiction_of_incorporation_country_name { get; set; }           public Policy business_category { get; set; }           public string extra_attributes { get; set; }         }       }       public class IdentitySignResponse {         public string signature { get; set; }       }       public class TimestampResponse {         public string token { get; set; }       }       public class CertificateResponse {         public string path { get; set; }       }       public class TrustChainResponse {         public string[] trustchain { get; set; }         public string[] ocsp_revocation_info { get; set; }       }       public LoginResponse Login() {         var login = new LoginRequest() { api_key = _key, api_secret = _secret };         return Call<LoginRequest, LoginResponse>(false, "login", login, null);       }       public IdentityResponse Identity(LoginResponse login, IdentityRequest identity) {         return Call<IdentityRequest, IdentityResponse>(false, "identity", identity, login.access_token);       }       public ValidationResponse ValidationPolicy(LoginResponse login) {         return Call<string, ValidationResponse>(true, "validationpolicy", "", login.access_token);       }       public CertificateResponse Certificate(LoginResponse login) {         return Call<string, CertificateResponse>(true, "certificate_path", "", login.access_token);       }       public TrustChainResponse TrustChain(LoginResponse login) {         return Call<string, TrustChainResponse>(true, "trustchain", "", login.access_token);       }       public IdentitySignResponse Sign(LoginResponse login, IdentityResponse identity, string digest) {         return Call<string, IdentitySignResponse>(true, $"identity/{identity.id}/sign/{digest}", "", login.access_token);       }       public TimestampResponse Timestamp(LoginResponse login, string digest) {         return Call<string, TimestampResponse>(true, $"timestamp/{digest}", "", login.access_token);       }       private A Call<Q, A>(bool get, string verb, Q question, string token) {         var task = CallAsync<Q, A>(get, verb, question, token);         task.Wait();         return task.Result;       }       private async Task<A> CallAsync<Q, A>(bool get, string verb, Q question, string token) {         var opts = new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };         var json = question is string ? (string)(object)question : JsonSerializer.Serialize(question, opts);         var content = new StringContent(json, Encoding.UTF8, "application/json");         var uri = "https://emea.api.dss.globalsign.com:8443/v2/" + verb;         var request = new HttpRequestMessage(get ? HttpMethod.Get : HttpMethod.Post, uri);         if (!get)           request.Content = content;         request.Headers.Add("Authorization", "Bearer " + token);         HttpResponseMessage response = await _client.SendAsync(request);         var str = await response.Content.ReadAsStringAsync();         if (response.StatusCode != HttpStatusCode.OK)           throw new Exception($"Error calling {verb}: {str}.");         return typeof(A) == typeof(string) ? (A)(object)str : JsonSerializer.Deserialize<A>(str);       }     }   } }