# # 数字签名

在本节中，您将了解签名和验证签名的常规步骤、相关的签名 API、数字签名交互的方式、以及我们提供的测试签名服务路由。

## # 在 PDF 上签名和验证数字签名的步骤

在 PDF 上签名和验证数字签名，应执行以下过程：

- 签名文档

  1. 生成包含签名覆盖范围信息（签名域字典的 byteRange）的文档文件流。有关详细信息，请参阅 PDF Reference 1.7 或更高版本。

  2. 对签名覆盖范围的文件流计算相应的摘要。这可以通过调用 `PDFUI.registerSignHandler(signerInfo)` 或者 `PDFDoc.sign(signInfo,digestSignHandler)` 接口来实现。

  3. 使用证书对摘要进行签名来获取 signedData。这可以通过调用 `PDFUI.registerSignHandler(signerInfo)` 或者 `PDFDoc.sign(signInfo,digestSignHandler)` 接口来实现。

  4. 将 signedData 写入文件流。signedData 的位置在 byteRange 中指定。

- 验证签名

  1. 获取文档原始（未修改）文件流、签名覆盖范围信息(签名域字典的 byteRange)、签名数据和签名者。

  2. 对签名覆盖范围的文件流计算相应的摘要。这可以通过调用 `PDFUI.setVerifyHandler(verifyFunction)` 或者 `PDFDoc.verifySignature(signatureField, verifyHandler)` 接口来实现。

  3. 验证摘要和签名数据，并输出验证结果，其中包括文档是否被修改、签名者信息是否有效和时间戳是否过期等信息。这可以通过调用 `PDFUI.setVerifyHandler(verifyFunction)` 或者 `PDFDoc.verifySignature(signatureField, verifyHandler)` 接口来实现。

## # 数字签名相关 API

### # PDFUI.registerSignHandler(signerInfo)

当前，Foxit PDF SDK for Web 支持两种类型的 signature filter：`adbe.pkcs7.detached` 和 `adbe.pkcs7.sha1`。

`adbe.pkcs7.detached` 支持的对应的摘要算法有：`'sha1'`, `'sha256'`, 和 `'sha384'`。

`adbe.pkcs7.sha1` 支持的对应的摘要算法有：`'sha1'`。

此方法用于注册签名者数据。下面是示例代码：

使用 `adbe.pkcs7.detached` 和 `sha256`:

    pdfui.registerSignHandler({
      filter: "Adobe.PPKLite",
      subfilter: "adbe.pkcs7.detached",
      flag: 0x100,
      distinguishName: "e=foxit@foxitsoftware.cn",
      location: "FZ",
      reason: "Test",
      signer: "web sdk",
      showTime: true,
      sign: (setting, plainContent) => {
    return requestData("post", "http://localhost:7777/digest_and_sign", "arraybuffer", {
          subfilter: setting.subfilter,
          md: "sha256", // "sha1", "sha256", "sha384"
          plain: plainContent,
        });
      },
    });

使用 `adbe.pkcs7.sha1` 和 `sha1`:

    pdfui.registerSignHandler({
      filter: "Adobe.PPKLite",
      subfilter: "adbe.pkcs7.sha1",
      flag: 0x100,
      distinguishName: "support@foxitsoftware.com",
      location: "FZ",
      reason: "Test",
      signer: "web sdk",
      sign: (signInfo, plainContent) => {
        //sign handler which complete the signing action, return a Promise with signed data;
        //function getDigest() and sign() should be completed by user.
        let digest = getDigest(plainContent);//plainContent is a Blob data
        let signData = sign(digest);
        return Promise.resolve(signData);
      }
    });

### # PDFUI.setVerifyHandler(verifyFunction)

此方法用于设置 Verification handler，在验证签名时将调用该 handler。Verification handler 返回称为 `Signature_State` 的验证结果状态。下面是示例代码：

    pdfui.setVerifyHandler((signatureField, plainBuffer, signedData) => {
      //function getDigest() and verify() should be completed by user.
      let digest = getDigest(plainBuffer);
      let verifiedStatus = verify(
        signatureField.getFilter(),
        signatureField.getSubfilter(),
        signatureField.getSigner(),
        digest,
        signedData
      );
      return Promise.resolve(verifiedStatus);
    });

### # PDFDoc.sign(signInfo,digestSignHandler)

此方法用于对文档进行签名。需要消息摘要和签名函数。下面是示例代码：

    /**
     * @returns {Blob} - File stream of signed document.
     */
    const signResult= await pdfdoc.sign(signInfo,(signInfo,plainContent) => {
        //function getSignData() should be completed by developer.
        return Promise.resolve(getSignData(signInfo,plainContent))//plainContent is a Blob data
    });

### # PDFDoc.verifySignature(signatureField, verifyHandler)

此方法用于验证签名。需要回调函数。下面是示例代码：

    /**
     * @returns {number} - Signature state.
     */
    var result = await singedPDF.verifySignature(
      pdfform.getField("Signature_0"),
      function verify(signatureField, plainBuffer, signedData, hasDataOutOfScope) {
        //function verifySignData() should be completed by developer.
        let signInfo = {
          byteRange: signatureField.getByteRange(),
          signer: signatureField.getSigner(),
          filter: signatureField.getFilter(),
          subfilter: signatureField.getSubfilter(),
        };
        return Promise.resolve(verifySignData(signInfo, buffer));
      }
    );

### # PDFSignature Class

- `PDFSignature.isSigned()` - 检查当前签名是否已签名。
- `PDFSignature.getByteRange()` - 获取当前签名的文件流中指定范围的字节范围。
- `PDFSignature.getFilter()` - 获取当前签名 filter。
- `PDFSignature.getSubfilter()` - 获取当前签名的 subfilter。

## # 数字签名功能交互

您可以通过使用 API 或 UI 的方式体验我们的签名工作流。该工作流基于 Node.js后端，可以访问发布包中的 `./server/signature-server-for-win`。

### # 方法1 以编程方式在当前文档上放置签名

1. 开启服务器，运行 `https://webviewer-demo.foxitsoftware.com/`。

2. 在控制台中运行如下的代码。其将自动创建一个签名域，并在该签名域上放置一个数字签名。

3. 签名后的文档将会被下载，并在 viewer 中重新打开。您可以点击签名域来进行验证。

    //this code example assumes you are running the signature service on a local host and using the default port 7777.
    var pdfviewer = await pdfui.getPDFViewer();
    var pdfdoc = await pdfviewer.getCurrentPDFDoc();
    var signInfo = {
      filter: "Adobe.PPKLite",
      subfilter: "adbe.pkcs7.sha1",
      rect: { left: 10, bottom: 10, right: 300, top: 300 },
      pageIndex: 0,
      flag: 511,
      signer: "signer",
      reason: "reason",
      email: "email",
      distinguishName: "distinguishName",
      location: "loc",
      text: "text",
    };
    const signResult = await pdfdoc.sign(signInfo, (signInfo,plainContent) => {
      return requestData(
        "post",
        "http://127.0.0.1:7777/digest_and_sign",
        "arraybuffer",
        { plain: plainContent }
      );
    });
    //open the signed PDF
    const singedPDF = await pdfviewer.openPDFByFile(signResult);
    var pdfform = singedPDF.getPDFForm();
    var verify = async (signatureField, plainBuffer, signedData, hasDataOutOfScope) => {
      return requestData("post", "http://127.0.0.1:7777/verify", "text", {
        filter: await signatureField.getFilter(),
        subfilter: await signatureField.getSubfilter(),
        signer: await signatureField.getSigner(),
        plainContent: new Blob([plainBuffer]),
        signedData: new Blob([signedData]),
      });
    };
    var result = singedPDF.verifySignature(pdfform.getField("Signature_0"), verify);

### # 方法2 以 UI 的方式来放置签名

通过我们在线的 viewer `https://webviewer-demo.foxitsoftware.com/` 来体验其是如何工作的。

- 准备工作

  - 在您的浏览器中打开 `https://webviewer-demo.foxitsoftware.com/`。

- 添加一个签名并对其进行签名

  1. 单击 Form 选项卡中的 signature 按钮，切换到 addSignatureStateHandler。

  2. 单击左键，在页面上绘制一个矩形域。

  3. 点击手型工具或者按下 `Esc` 键，切换到 handStateHandler。

  4. 在弹出框中设置签名信息，然后单击 OK 进行签名。签名后的文档将自动下载并重新打开。

- 验证签名

  - 点击手型工具，然后点击已签名的签名域来进行验证。此时会弹出一个提示框，显示验证的结果。

备注：为了使此签名流程正常工作，我们在 complete_webViewer 的 index.html 文件中引用了如下的回调代码，并在后端运行签名服务。

    //the variable `origin` refers to the service http address where your signature service is running.
    //signature handlers
    var requestData = (type, url, responseType, body) => {
      return new Promise((res, rej) => {
        var xmlHttp = new XMLHttpRequest();
        xmlHttp.open(type, url);

        xmlHttp.responseType = responseType || "arraybuffer";
        let formData = new FormData();
        if (body) {
          for (let key in body) {
            if (body[key] instanceof Blob) {
              formData.append(key, body[key], key);
            } else {
              formData.append(key, body[key]);
            }
          }
        }
        xmlHttp.onload = (e) => {
          let status = xmlHttp.status;
          if ((status >= 200 && status < 300) || status === 304) {
            res(xmlHttp.response);
          }
        };
        xmlHttp.send(body ? formData : null);
      });
    };
    //set signature information and function. This function can be called to register different algorithm and information for signing
    //the api `/digest_and_sign` is used to calculate the digest and return the signed data
    pdfui.registerSignHandler({
      filter: "Adobe.PPKLite",
      subfilter: "adbe.pkcs7.sha1",
      flag: 0x100,
      distinguishName: "e=foxit@foxitsoftware.com",
      location: "FZ",
      reason: "Test",
      signer: "web sdk",
      showTime: true,
      sign: (setting, plainContent) => {
        return requestData("post", "origin", "arraybuffer", {
          plain: plainContent,
        });
      }
    });
    //set signature verification function
    //the api /verify is used to verify the state of signature
    pdfui.setVerifyHandler((signatureField, plainBuffer, signedData) => {
      return requestData("post", "origin", "text", {
        filter: signatureField.getFilter(),
        subfilter: signatureField.getSubfilter(),
        signer: signatureField.getSigner(),
        plainContent: new Blob([plainBuffer]),
        signedData: new Blob([signedData]),
      });
    });

## # 签名 HTTP 服务

如果您没有可用的后端签名服务，您可以使用以下我们提供作为测试的 HTTP 服务路由。

http://webviewer-demo.foxitsoftware.com/signature/digest_and_sign
(opens new window)

http://webviewer-demo.foxitsoftware.com/signature/verify
(opens new window)