DioDocs for PDFでは、PDFドキュメントにデジタル署名を付加し、コンテンツの真正性を保護できます。 ライブラリでは、SignatureFieldを使用してデジタル署名を付加できます。また、タイムスタンプ付きのデジタル署名を付加し、PDFドキュメントの署名の日時をマークを付けることもできます。DioDocs for PDFでは、Time Stamp Authority(TSA)などの信頼できる機関によって作成された合法的なスタンプがサポートされています。デフォルトでドキュメントを増分更新するドキュメントに署名して保存するには、Signメソッドを使用できます。または、SaveMode列挙をIncrementalUpdateに設定し、それをパラメータとしてSignメソッドに渡すこともできます。どちらの方法も、元の署名を無効にすることなく、また元のコンテンツを変更することなく、ドキュメントに複数回署名することができます。 DioDocs for PDFでは、署名されたドキュメントに対して3つのレベルの後続の変更が可能です。
ドキュメントに署名を付加された場合、新しいフィールドを追加すると既存の署名が無効になります。そのため、ドキュメントには、後続のすべての署名に収まるのに十分な署名フィールドがすでに含まれている必要があります。また、有効なライセンスキーなしで署名されたPDFを使用したサンプルを実行すると、生成されたPDF内の元の署名が無効になる現象が発生します。この現象は、元の署名されたドキュメントを変更するライセンスヘッダがPDFに追加されるために発生します。
さらに、DioDocs for PDFを使用すると、署名を削除して署名フィールドを保持するか、署名フィールドを削除することで、署名されたPDFテンプレートを再利用できます。
PDFドキュメントにデジタル署名を追加するには、以下の手順に従います。
C# |
コードのコピー
|
---|---|
public static void CreatePDF(Stream stream) { GcPdfDocument doc = new GcPdfDocument(); Page page = doc.NewPage(); TextFormat tf = new TextFormat() { Font = StandardFonts.Times, FontSize = 14 }; page.Graphics.DrawString( "Hello, World!\r\nSigned below by GcPdfWeb SignDoc sample." + "\r\n(Note that some browser built-in viewers may not show the signature.)", tf, new PointF(72, 72)); // テスト用の証明書を初期化します var pfxPath = Path.Combine("Resources", "Misc", "GcPdfTest.pfx"); X509Certificate2 cert = new X509Certificate2(File.ReadAllBytes(pfxPath), "qq", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); SignatureProperties sp = new SignatureProperties(); sp.Certificate = cert; sp.Location = "GcPdfWeb Sample Browser"; sp.SignerName = "GcPdfWeb"; // タイムスタンプを追加します sp.TimeStamp = new TimeStamp("https://freetsa.org/tsr"); // 署名を保持するために署名フィールドを初期化します SignatureField sf = new SignatureField(); sf.Widget.Rect = new RectangleF(72, 72 * 2, 72 * 4, 36); sf.Widget.Page = page; sf.Widget.BackColor = Color.LightSeaGreen; sf.Widget.TextFormat.Font = StandardFonts.Helvetica; sf.Widget.ButtonAppearance.Caption = $"Signer: " + $"{sp.SignerName}\r\nLocation: {sp.Location}"; // ドキュメントに署名フィールドを追加します doc.AcroForm.Fields.Add(sf); // 署名フィールドと署名プロパティを接続します sp.SignatureField = sf; // ドキュメントに署名を付加して保存します // メモ // - 署名と保存は不可分操作であり、2つを分離することはできません // - Sign()メソッドに渡されるストリームは読み取り可能である必要があります doc.Sign(sp, stream); // ストリームを巻き戻して、作成したばかりのドキュメントを // 別のGcPdfDocumentに読み込み、署名を確認します stream.Seek(0, SeekOrigin.Begin); GcPdfDocument doc2 = new GcPdfDocument(); doc2.Load(stream); SignatureField sf2 = (SignatureField)doc2.AcroForm.Fields[0]; if (!sf2.Value.VerifySignature()) throw new Exception("Failed to verify the signature"); // 完了(生成および署名されたドキュメントはすでに「ストリーム」に保存されています) } |
DioDocs for PDFでは、PDFファイルからデジタル署名を簡単に削除できます。 ライブラリを使用すると、署名フィールドから署名を削除して、PDFファイルの内容を再度使用できるようにすることができます。
署名を削除してPDFドキュメントの署名フィールドを保持するには、以下の手順に従います。
C# |
コードのコピー
|
---|---|
var doc = new GcPdfDocument(); using (var fs = new FileStream( "TimeSheet.pdf", FileMode.Open, FileAccess.Read)) { doc.Load(fs); // フィールドは他のフィールドの子になることができるので、ツリー全体を反復処理する // ために再帰的な方法を使用します removeSignatures(doc.AcroForm.Fields); doc.Save("TimeSheet_NoSign.pdf"); //ドキュメントを保存します void removeSignatures(FieldCollection fields) { foreach (var f in fields) { if (f is SignatureField sf) sf.Value = null; //ドキュメントから署名を削除します removeSignatures(f.Children); } } } |
SignatureクラスのContentプロパティを使用することで、PDFドキュメントのデジタル署名から署名情報を抽出できます。署名情報は、署名の有効性を検証するために必要な詳細を提供します。署名からIssuer、IssuerName、SerialNumber、Subject、Thumbprint、NotAfter、NotBefore、SignatureAlgorithmなどの、情報フィールドを抽出できます。
デジタル署名から署名情報を抽出するには、以下の手順に従います。
C# |
コードのコピー
|
---|---|
MemoryStream ms = new MemoryStream(File.ReadAllBytes(@"AdobePDFWithEmptySignatureField.pdf")); GcPdfDocument doc = new GcPdfDocument(); doc.Load(ms); //証明書を初期化します X509Certificate2 cert = new X509Certificate2(@"User.pfx", "User12"); SignatureProperties sp = new SignatureProperties(); sp.SignatureFormat = SignatureFormat.PKCS7Detached; sp.SignatureDigestAlgorithm = SignatureDigestAlgorithm.SHA1; sp.Certificate = cert; sp.Location = "MACHINE"; sp.SignerName = "USER"; sp.SigningDateTime = null; sp.SignatureField = doc.AcroForm.Fields["EmptySignatureField"]; using (MemoryStream ms2 = new MemoryStream()) { //ドキュメントに署名します doc.Sign(sp, ms2, false); ms2.Seek(0, SeekOrigin.Begin); //署名されたドキュメントをロードします GcPdfDocument doc2 = new GcPdfDocument(); doc2.Load(ms2); //署名フィールドと署名を取得します SignatureField sf2 = (SignatureField)doc2.AcroForm.Fields["EmptySignatureField"]; var sk = sf2.Value.Content; //証明書を取得し、そのプロパティを印刷します var sc = sk.SigningCertificate; Console.WriteLine($"Subject: {sc.Subject}"); Console.WriteLine($"Issuer: {sc.Issuer}"); Console.WriteLine($"GetEffectiveDateString: {sc.GetEffectiveDateString()}"); Console.WriteLine($"GetExpirationDateString: {sc.GetExpirationDateString()}"); } |
DioDocs for PDFには、デジタル署名のカスタム実装を実現するように使用できるISignatureBuilderおよびIPkcs7SignatureGeneratorインターフェイスが提供されます。 Pkcs7SignatureBuilderクラスは、ISignatureBuilderインターフェイスを実装し、次のようなさまざまなメソッドとプロパティを提供します。
カスタム署名の実装のいくつかを以下に説明します。
.p12ファイルの証明書を使用してドキュメントに署名するには、以下の手順に従います。
C# |
コードのコピー
|
---|---|
using (FileStream fs = new FileStream(@"AdobePDFWithEmptySignatureField.pdf", FileMode.Open)) { GcPdfDocument doc = new GcPdfDocument(); doc.Load(fs); SignatureProperties sp = new SignatureProperties(); sp.SignatureBuilder = new Pkcs7SignatureBuilder() { CertificateChain = SecurityUtils.GetCertificateChain("1571753451.p12", "test"), }; sp.SignatureField = doc.AcroForm.Fields[0]; doc.Sign(sp, "signed.pdf"); } |
有効な証明書を持つUSBトークンを使用してドキュメントに署名できます。詳細については、DioDocs for PDF サンプルブラウザを参照してください。
Azure Key Vaultに保存されている証明書を使用してドキュメントに署名できます。詳細については、DioDocs for PDF サンプルブラウザを参照してください。
カスタム タイムスタンプ トークンを作成してドキュメントに署名するには、ITimeStampGenerator インターフェイスを実装し、SignatureProperties?クラスと TimeStampProperties?クラスの TimeStamp プロパティに割り当てます。ITimeStampGenerator インターフェイスは、タイムスタンプトークンを生成する方法を定義します。
次のサンプルコードは、PDF ドキュメントにカスタム タイムスタンプ トークンとカスタム タイムスタンプ トークンを使用した署名を追加する方法を示しています。
C# |
コードのコピー
|
---|---|
// カスタムのタイムスタンプ ジェネレータを作成します。 public class TimeStampGenerator : ITimeStampGenerator { public string ServerUrl; public string UserName; public string Password; public OID HashAlgorithm; public TimeStampGenerator() { HashAlgorithm = new OID("2.16.840.1.101.3.4.2.1", "SHA256"); } public TimeStampGenerator(string serverUrl, string userName, string password, OID hashAlgorithm) { ServerUrl = serverUrl; UserName = userName; Password = password; HashAlgorithm = hashAlgorithm; } private static long CopyStream(Stream src, Stream dst, bool useSingleWriteOperation = false) { byte[] buffer = new byte[16 * 1024]; int bytesRead; long result = 0; while ((bytesRead = src.Read(buffer, 0, buffer.Length)) != 0) { dst.Write(buffer, 0, bytesRead); result += bytesRead; } return result; } private static void Update(IDigest dgst, byte[] input) { Update(dgst, input, 0, input.Length); } private static void Update(IDigest dgst, byte[] input, int offset, int len) { dgst.BlockUpdate(input, offset, len); } private static byte[] Digest(IDigest dgst) { byte[] output = new byte[dgst.GetDigestSize()]; dgst.DoFinal(output, 0); return output; } private static byte[] Digest(IDigest dgst, byte[] input) { Update(dgst, input); return Digest(dgst); } private static byte[] Digest(IDigest dgst, Stream data) { byte[] buf = new byte[8192]; int n; while ((n = data.Read(buf, 0, buf.Length)) > 0) { Update(dgst, buf, 0, n); } return Digest(dgst); } private static IDigest GetMessageDigest(OID hashAlgorithm) { if (hashAlgorithm == OID.HashAlgorithms.MD2) return new MD2Digest(); else if (hashAlgorithm == OID.HashAlgorithms.MD5) return new MD5Digest(); else if (hashAlgorithm == OID.HashAlgorithms.SHA1) return new Sha1Digest(); else if (hashAlgorithm == OID.HashAlgorithms.SHA224) return new Sha224Digest(); else if (hashAlgorithm == OID.HashAlgorithms.SHA256) return new Sha256Digest(); else if (hashAlgorithm == OID.HashAlgorithms.SHA384) return new Sha384Digest(); else if (hashAlgorithm == OID.HashAlgorithms.SHA512) return new Sha512Digest(); else if (hashAlgorithm == OID.HashAlgorithms.RIPEMD128) return new RipeMD128Digest(); else if (hashAlgorithm == OID.HashAlgorithms.RIPEMD160) return new RipeMD160Digest(); else if (hashAlgorithm == OID.HashAlgorithms.RIPEMD256) return new RipeMD256Digest(); else if (hashAlgorithm == OID.HashAlgorithms.GOST3411) return new Gost3411Digest(); throw new ArgumentException(); } private byte[] GetTsaResponseForUserRequest(byte[] requestBytes) { HttpWebRequest con; try { con = (HttpWebRequest)WebRequest.Create(ServerUrl); } catch (Exception e) { throw new Exception(string.Format("Failed to get response from TSA server {0}.", ServerUrl), e); } con.ContentLength = requestBytes.Length; con.ContentType = "application/timestamp-query"; con.Method = "POST"; if (!string.IsNullOrEmpty(UserName)) { string authInfo = UserName + ":" + Password; authInfo = Convert.ToBase64String(Encoding.UTF8.GetBytes(authInfo)); con.Headers["Authorization"] = "Basic " + authInfo; } using (Stream outp = con.GetRequestStream()) outp.Write(requestBytes, 0, requestBytes.Length); HttpWebResponse httpWebResponse = (HttpWebResponse)con.GetResponse(); using (Stream stream = httpWebResponse.GetResponseStream()) { if (stream == null) return null; using (MemoryStream ms = new MemoryStream()) { CopyStream(stream, ms); string encoding = httpWebResponse.Headers[HttpResponseHeader.ContentEncoding]; byte[] data = ms.ToArray(); if (string.Compare(encoding, "base64", StringComparison.InvariantCultureIgnoreCase) == 0) data = Convert.FromBase64String(Encoding.ASCII.GetString(data)); return data; } } } // ハッシュのタイムスタンプトークンを取得します。 private byte[] GetTimeStampTokenForHash(byte[] hash) { // タイムスタンプリクエストを設定します。 TimeStampRequestGenerator tsqGenerator = new TimeStampRequestGenerator(); tsqGenerator.SetCertReq(true); // 乱数を生成します。 BigInteger nonce = BigInteger.ValueOf(unchecked((int)DateTime.Now.Ticks) + Environment.TickCount); TimeStampRequest request = tsqGenerator.Generate(new DerObjectIdentifier(HashAlgorithm.ID), hash, nonce); // 通信レイヤーを呼び出します。 byte[] requestBytes = request.GetEncoded(); byte[] respBytes = GetTsaResponseForUserRequest(requestBytes); // TSA 応答を処理します。 TimeStampResponse response = new TimeStampResponse(respBytes); // 通信レベル属性(RFC 3161 PKIStatus)を検証します。 response.Validate(request); PkiFailureInfo failure = response.GetFailInfo(); int value = failure == null ? 0 : failure.IntValue; if (value != 0) throw new Exception(string.Format("Invalid TSA response from {0}: {1}.", ServerUrl, response.GetStatusString())); // タイムスタンプトークンのみ(通信ステータス情報は削除します)を抽出します。 TimeStampToken tsToken = response.TimeStampToken; if (tsToken == null) throw new Exception(string.Format("No timetoken in TSA response from {0}.", ServerUrl)); return tsToken.GetEncoded(); } // タイムスタンプトークンを取得します。 public byte[] GetTimeStampToken(byte[] data) { // データのハッシュを構築します。 byte[] hash = Digest(GetMessageDigest(HashAlgorithm), data); return GetTimeStampTokenForHash(hash); } public byte[] GetTimeStampToken(Stream stream) { // データのハッシュを構築します。 byte[] hash = Digest(GetMessageDigest(HashAlgorithm), stream); return GetTimeStampTokenForHash(hash); } } internal class Program { static void Main(string[] args) { // タイムスタンプ トークンを使用して署名を生成します。 X509Certificate2 crt = new X509Certificate2(@"..\..\..\User.pfx", "User12"); using (FileStream fs = new FileStream(@"..\..\..\AdobePDFWithEmptySignatureField.pdf", FileMode.Open)) { // GcPdfDocument を初期化します。 GcPdfDocument doc = new GcPdfDocument(); // ストリームから PDF ドキュメントを読み込みます。 doc.Load(fs); // SignaturePropertiesを初期化します。 SignatureProperties sp = new SignatureProperties(); // 証明書のチェーンを構築します。 sp.SignatureBuilder = new Pkcs7SignatureBuilder() { CertificateChain = new X509Certificate2[] { crt }, }; // カスタム タイムスタンプを追加します。 sp.TimeStamp = new TimeStampGenerator() { ServerUrl = @"http://ts.ssl.com", }; // 署名フィールドに署名を追加します。 sp.SignatureField = doc.AcroForm.Fields[0]; // PDF ドキュメントに署名します。 doc.Sign(sp, "signed.pdf"); } // タイムスタンプ付きのドキュメントを生成します。 using (FileStream fs = new FileStream(@"..\..\..\AdobePDFWithEmptySignatureField.pdf", FileMode.Open)) { // GcPdfDocument を初期化します。 GcPdfDocument doc = new GcPdfDocument(); // ストリームから PDF ドキュメントを読み込みます。 doc.Load(fs); // TimeStampPropertiesを初期化します。 TimeStampProperties tsp = new TimeStampProperties(); // カスタムタイムスタンプを追加します。 tsp.TimeStamp = new TimeStampGenerator() { ServerUrl = @"http://ts.ssl.com", }; // 署名フィールドにタイムスタンプを追加します。 tsp.SignatureField = doc.AcroForm.Fields[0]; // タイムスタンプを追加してドキュメントを保存します。 doc.TimeStamp(tsp, "timestamp.pdf"); } } } |
DioDocs for PDFでは、PDF Advanced Electronic Signatures(PAdES)を使用してPDFドキュメントにデジタル署名を付加できます。 PAdESは、PDFドキュメントが電子的に署名されるときに使用される拡張機能と制限のグループを参照する一連の規格です。 PAdES形式を使用して署名されたドキュメントは、長期間有効です。
PAdESでは、DioDocs for PDFは次のレベルのデジタル署名の検証をサポートしています。
DioDocs for PDFでは、以下で説明するように、SignaturePropertiesクラスのCreatePAdES_B_BメソッドとCreatePAdES_B_Tメソッドを使用して、PDFドキュメントにそれぞれの署名を作成できます。さらに、GrapeCity.Documents.Pdf.Security.DocumentSecurityStoreクラスとGcPdfDocument.TimeStamp()メソッドを使用して、B-LTやB-LTAレベルなどの高度な電子署名を作成できます。
PAdES B-B署名を作成するには、以下の手順に従います。
C# |
コードのコピー
|
---|---|
using (FileStream fs = new FileStream(@"PDFWithEmptySignatureField.pdf", FileMode.Open)) { GcPdfDocument doc = new GcPdfDocument(); doc.Load(fs); X509Certificate2 cert = new X509Certificate2("User.pfx", "User12"); SignatureProperties sp = SignatureProperties.CreatePAdES_B_B(cert); sp.SignatureAppearance.Caption = "PAdES B-B"; sp.SignatureField = doc.AcroForm.Fields[0]; doc.Sign(sp, "SignPAdESBB.pdf"); } |
PAdES B-T署名を作成するには、以下の手順に従います。
C# |
コードのコピー
|
---|---|
using (FileStream fs = new FileStream(@"PDFWithEmptySignatureField.pdf", FileMode.Open)) { GcPdfDocument doc = new GcPdfDocument(); doc.Load(fs); X509Certificate2 cert = new X509Certificate2("User.pfx", "User12"); SignatureProperties sp = SignatureProperties.CreatePAdES_B_T(new TimeStamp("https://freetsa.org/tsr"), cert); sp.SignatureAppearance.Caption = "PAdES B-T"; sp.SignatureField = doc.AcroForm.Fields[0]; doc.Sign(sp, "SignPAdESBT.pdf"); } |
B-LT署名は、署名の長期間有効に必要なすべてのプロパティを追加することで、B-T署名に基づいて構築されています。 PAdES B-LT署名を作成するには、以下の手順に従います。
C# |
コードのコピー
|
---|---|
using (FileStream fs = new FileStream(@"SignPAdESBT.pdf", FileMode.Open)) { GcPdfDocument doc = new GcPdfDocument(); doc.Load(fs); // PAdES B-LTに準拠した署名にするためのLTV情報を追加します SignatureField signField = (SignatureField)doc.AcroForm.Fields[0]; var sig = signField.Value; var vp = new DocumentSecurityStore.VerificationParams(); X509Certificate2 cert = new X509Certificate2("User.pfx", "User12"); vp.Certificates = new X509Certificate2[] { cert }; if (!doc.SecurityStore.AddVerification(sig, vp)) throw new Exception($"{sig.Name}に対する検証を追加できませんでした。"); doc.Save("SignPAdESBLT.pdf", SaveMode.IncrementalUpdate); } |
B-LTA署名は、有効資料にタイムスタンプのトークンを追加することで、B-LT署名に基づいて構築されています。 PAdES B-LTA署名を作成するには、次の手順に従います。
C# |
コードのコピー
|
---|---|
using (FileStream fs = new FileStream(@"SignPAdESBLT.pdf", FileMode.Open)) { GcPdfDocument doc = new GcPdfDocument(); doc.Load(fs); // 署名されたPDFにタイムスタンプを追加し、B-LTAレベルに準拠したドキュメントにします TimeStampProperties ts = new TimeStampProperties() { TimeStamp = new TimeStamp(@"http://ts.ssl.com"), }; // PDFにタイムスタンプを追加して保存します doc.TimeStamp(ts, "SignPAdESBLTA.pdf"); } |