VBAからAdobe Acrobatを制御してPDFファイルをデジタル署名する

# はじめに
証明書ファイル(PFXファイル)を用いて、VBAからAdobe Acrobatを制御してPDFファイルのデジタル署名をしましたので、その方法について整理しました。

# 環境
・Windows 10 Pro
・Microsoft 365 (Excel)
・Adobe Acrobat Pro

# 署名方法
## 事前設定
Acrobatを起動して「メニュ」の「編集」→「環境設定」→分類で「セキュリティ（拡張）」を選択後、「サンドボックスによる保護」の「起動時に保護モードを有効にする」のチェックを外す必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1184971/68b0c4b2-2f60-b6df-b90e-a8bf39f10608.png)

## VBAからAcrobatを制御するためには
VBAの参照設定でAcrobatのライブラリーを参照することで、PDFファイルを開くなど、一部のAcrobat機能を制御することはできますが、ほとんどの機能を制御することはまだできません。
より多くの機能を制御するため(デジタル証明するため)には、「**JSObject**」というオブジェクトを呼び出す必要があります。

 http://pdf-file.nnn2.com/?p=228

JSObjectを使うと、Acrobat JavaScriptの機能を使うことができます。
(しかし、VBAからはJavaScriptで使うオブジェクト形式などを作ることができないので、使えられる機能もあります。)
レファレンスは以下のURLで確認できます。

 https://opensource.adobe.com/dc-acrobat-sdk-docs/index.html

## 処理の流れ
1. PDFファイルを開く
2. JSObjectを呼び出す
3. 証明書ログインする
4. PDFファイルに署名フィールドを生成する
5. 署名フィールドに署名を追加してファイルを出力する
6. 証明書ログアウトする
7. PDFファイルを閉じる

## コードの例
```vb
Public Sub CreateSignaturePdf()
    Dim acroPdObj As New Acrobat.AcroPDDoc
    Dim acroAvObj As New Acrobat.AcroAVDoc
    Dim jsObj As Object
    Dim objSign As Object
    Dim signatureCoords() As Variant
    Dim signField As Object

    Dim bottomLeftX As Long
    Dim bottomLeftY As Long
    Dim signWidth As Long
    Dim signHeight As Long

    Dim targetFilePath As String
    Dim certFilePath As String
    Dim certPassword As String
    Dim outputFilePath As String
    
    targetFilePath = "C:\SignTest\Input\TargetPdf.pdf"
    certFilePath = "C:\SignTest\Certificate\Certificate.pfx"
    certPassword = "password"
    outputFilePath = "C:\SignTest\Output\SignedPdf.pdf"
    
    ' 署名を表示する場合、適切に値を設定します
    bottomLeftX = 200
    bottomLeftY = 100
    signWidth = 200
    signHeight = 100

    ' PDFファイルを開く
    If acroPdObj.Open(targetFilePath) Then
        Set acroAvObj = acroPdObj.OpenAVDoc(targetFilePath)

        ' JSObjectを定義する
        Set jsObj = acroPdObj.GetJSObject

        ' SecurityHandlerを生成
        ' https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/JS_API_AcroJS.html#gethandler
        Set objSign = jsObj.security.getHandler("Adobe.PPKLite")

        ' 証明書ログイン
        ' https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/JS_API_AcroJS.html#login
        Call objSign.login(certPassword, certFilePath)
        
        ' パスワードのタイムアウトを設定
        ' https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/JS_API_AcroJS.html#setpasswordtimeout
        Call objSign.setPasswordTimeout(certPassword, 30)

        ' 署名を付ける座標を設定する
        signatureCoords = Array(bottomLeftX, bottomLeftY + signHeight, bottomLeftX + signWidth, bottomLeftY)

        ' 署名フィールドを生成
        ' https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/doc.html#addfield
        Set signField = jsObj.addField("MySignatureField", "signature", 0, signatureCoords)

        ' デジタル署名する
        ' https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/JS_API_AcroJS.html#id652
        Call signField.signatureSign(objSign, Array(), outputFilePath, False)

        ' 証明書ログアウト
        Call objSign.logout

        ' PDFファイルを閉じる
        Call acroAvObj.Close(1)
        Call acroPdObj.Close
    End If
    
    Set acroPdObj = Nothing
    Set jsObj = Nothing
    Set objSign = Nothing
End Sub
```

## コード例の説明
```vb
    Dim acroPdObj As New Acrobat.AcroPDDoc
    Dim acroAvObj As New Acrobat.AcroAVDoc
    ...
    ' PDFファイルを開く
    If acroPdObj.Open(targetFilePath) Then
        Set acroAvObj = acroPdObj.OpenAVDoc(targetFilePath)
```
PDFファイルを開きます。
Acrobat.AcroPDDocでPDFを開いたら、バックグラウンドでAcrobatが起動しますが、デジタル署名は、Acrobatがバックグラウンドの状態ではうまく動作できない場合があります。
そのため、Acrobat.AcroAVDocでファイルを開くようにして、Acrobatが画面上で表示するようにします。

```vb
        ' JSObjectを定義する
        Set jsObj = acroPdObj.GetJSObject
```
JSObjectを取得します。
JSObjectは、Acrobat.AcroPDDocから取得できます。

```vb
        ' SecurityHandlerを生成
        ' https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/JS_API_AcroJS.html#gethandler
        Set objSign = jsObj.security.getHandler("Adobe.PPKLite")

        ' 証明書ログイン
        ' https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/JS_API_AcroJS.html#login
        Call objSign.login(certPassword, certFilePath)
        
        ' パスワードのタイムアウトを設定
        ' https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/JS_API_AcroJS.html#setpasswordtimeout
        Call objSign.setPasswordTimeout(certPassword, 30)
```
証明書を使うため、SecurityHandlerを生成します。
Handlerは、例の「Adobe.PPKLite」以外にも、下記のURLの方法で使用できるものを確認することができます。

https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/JS_API_AcroJS.html#handlers

その後、証明書ファイルにログインします。
また、パスワードのタイムアウトを設定します。
(タイムアウト時間をデフォルト値(0)にすると、正しく署名処理ができない場合があります。)

```vb
        ' 署名を表示する場合、適切に値を設定します
        bottomLeftX = 200
        bottomLeftY = 100
        signWidth = 200
        signHeight = 100
        ...
        ' 署名を付ける座標を設定する
        signatureCoords = Array(bottomLeftX, bottomLeftY + signHeight, bottomLeftX + signWidth, bottomLeftY)

        ' 署名フィールドを生成
        ' https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/doc.html#addfield
        Set signField = jsObj.addField("MySignatureField", "signature", 0, signatureCoords)
```
署名を表示する位置を指定して、PDFファイルに署名を付けるFieldを追加します。
署名位置は、(x1, y1, x2, y2)であり、「x1 = x2」かつ「y1 = y2」の場合、非表示で署名を付けます。
```vb:非表示署名の座標設定の例
' bottomLeftX = 200
' bottomLeftY = 100
' signWidth = 0
' signHeight = 0
signatureCoords = Array(200, 100, 200, 100)
```

「addField」は、以下のように構成されます。
| パラメーター | 説明 | 例 |
|:-|:-|:-|
| cName | フィールド名<br>自由に設定 | "MySignatureField" |
| cFieldType | フィールドタイプ<br>「signature」に固定 | "signature" |
| nPageNum | ページ番号<br>0から始める  | 0 |
| oCoords | 署名表示の座標<br>配列 | Array(200, 100, 400, 200) |

詳細は以下のレファレンスを確認してください。

https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/doc.html#addfield


```vb
        ' デジタル署名する
        ' https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/JS_API_AcroJS.html#id652
        Call signField.signatureSign(objSign, Array(), outputFilePath, False)
```
指定したフィールドにデジタル署名を付けて、ファイルを出力します。
「signatureSign」は、以下のように構成されます。
| パラメーター | 説明 | 例 |
|:-|:-|:-|
| oSig | SecurityHandlerのオブジェクト | objSign |
| oInfo | SignatureInfoのオブジェクト<br>VBAからは作成できない形式 | Array() |
| cDIPath | 出力ファイルパス | "C:\SignTest\Output\SignedPdf.pdf" |
| bUI | 署名のためのユーザインタフェース表示 <br> 「True」の場合、署名ダイアログから署名<br>「False」(デフォルト)の場合、ユーザインタフェースなしで署名 | False |


```vb
        ' 証明書ログアウト
        Call objSign.logout

        ' PDFファイルを閉じる
        Call acroAvObj.Close(1)
        Call acroPdObj.Close
    End If
```
証明書ログアウトして、PDFファイルを閉じます。

# 参考
https://opensource.adobe.com/dc-acrobat-sdk-docs/index.html

http://pdf-file.nnn2.com/

https://www.youtube.com/watch?v=EpO3oWlGdWA