# 数字签名

在本节中,您将了解签名和验证签名的常规步骤、相关的签名 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.detachedadbe.pkcs7.sha1

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

adbe.pkcs7.sha1 支持的对应的摘要算法有:'sha1'

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

使用 adbe.pkcs7.detachedsha256:

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.sha1sha1:

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 = await singedPDF.loadPDFForm();
var verify = (signatureField, plainBuffer, signedData, hasDataOutOfScope) => {
  return requestData("post", "http://127.0.0.1:7777/verify", "text", {
    filter: signatureField.getFilter(),
    subfilter: signatureField.getSubfilter(),
    signer: 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)