使用 SoftHSM 2.2.0(带有 SHA256 的 ECDSA)C# .net 从 Pkcs11Interop 为 CKM_ECDSA_SHA256 签名 PDF

Signing PDF from Pkcs11Interop for CKM_ECDSA_SHA256 using SoftHSM 2.2.0 (ECDSA with SHA256) C# .net

我正在尝试使用 Pkcs11Interop .net 库对 Pdf 文档进行签名。 我需要使用 ECDSA 加密 算法和 SHA256 哈希算法。 我正在使用 SoftHSM 2.2.0 来存储私钥。

我发现了一个 CKM 枚举,CKM_ECDSA_SHA256,我在创建一个 class 机制的对象时传递它来调用 Session 的 Sign 方法。

我从 "Signdata" 方法得到响应,但是,在打开签名后生成的 Pdf 文件时出现错误 "Signature Invalid"。 下面是 Signdata 方法调用的代码片段。 我在代码中没有发现任何错误或异常,但是,我提到的 pdf 显示签名无效。

private Pkcs11 _pkcs11;
private Slot _slot;
private Session _session;

try
{
   _pkcs11 = new Pkcs11(hsmCryptoApi, true);
}
catch (Pkcs11Exception ex)
{
   if (ex.RV == CKR.CKR_CANT_LOCK)
      _pkcs11 = new Pkcs11(hsmCryptoApi, false);
   else
       throw ex;
}

_slot = FindSlot(_pkcs11, _certificateInformation.TokenLabel);
_session = _slot.OpenSession(true);

using (Mechanism mechanism = new Mechanism(CKM.CKM_ECDSA_SHA256))
{
  _session.Login(CKU.CKU_USER, passowrd);
  byte[] signedHash = _session.Sign(mechanism, GetPrivateKeyHandle(), message);
  _session.Logout();
  return signedHash;
}

private ObjectHandle GetPrivateKeyHandle()
{
  string keyLabel = _certificateInformation.KeyLabel;
  List<ObjectAttribute> searchTemplate = new List<ObjectAttribute>();
  searchTemplate.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY));
  searchTemplate.Add(new ObjectAttribute(CKA.CKA_LABEL, keyLabel));
  List<ObjectHandle> foundObjects = _session.FindAllObjects(searchTemplate);
  return foundObjects[0]; 
}

我认为您需要构建 ECDSA-Sig-Value 结构并用 signedHash 变量中的数据填充它。

PKCS#11 v2.20 第 12.3.1 章:

For the purposes of these mechanisms, an ECDSA signature is an octet string of even length which is at most two times nLen octets, where nLen is the length in octets of the base point order n. The signature octets correspond to the concatenation of the ECDSA values r and s, both represented as an octet string of equal length of at most nLen with the most significant byte first. If r and s have different octet length, the shorter of both must be padded with leading zero octets such that both have the same octet length. Loosely spoken, the first half of the signature is r and the second half is s. For signatures created by a token, the resulting signature is always of length 2nLen. For signatures passed to a token for verification, the signature may have a shorter length but must be composed as specified before.

RFC5753 第 7.2 章:

When using ECDSA with SignedData, ECDSA signatures are encoded using the type:

ECDSA-Sig-Value ::= SEQUENCE {
    r INTEGER,
    s INTEGER }

ECDSA-Sig-Value is specified in [PKI-ALG]. Within CMS, ECDSA-Sig-Value is DER-encoded and placed within a signature field of SignedData.

以下方法使用 BouncyCastle 库构造 DER 编码的 ECDSA-Sig-Value 结构:

public static byte[] ConstructEcdsaSigValue(byte[] rs)
{
    if (rs == null)
        throw new ArgumentNullException(nameof(rs));

    if (rs.Length < 2 || rs.Length % 2 != 0)
        throw new ArgumentException("Invalid length", nameof(rs));

    int halfLen = rs.Length / 2;

    byte[] half1 = new byte[halfLen];
    Array.Copy(rs, 0, half1, 0, halfLen);
    var r = new Org.BouncyCastle.Math.BigInteger(1, half1);

    byte[] half2 = new byte[halfLen];
    Array.Copy(rs, halfLen, half2, 0, halfLen);
    var s = new Org.BouncyCastle.Math.BigInteger(1, half2);

    var derSequence = new Org.BouncyCastle.Asn1.DerSequence(
        new Org.BouncyCastle.Asn1.DerInteger(r),
        new Org.BouncyCastle.Asn1.DerInteger(s));

    return derSequence.GetDerEncoded();
}

只是想分享对我有用的解决方案。在上面提到的代码片段中,我添加了以下内容:

   using (Mechanism mechanism = new Mechanism(CKM.CKM_ECDSA))
        {
          _session.Login(CKU.CKU_USER, passowrd);
          byte[] signedHash = _session.Sign(mechanism, GetPrivateKeyHandle(), GetMessageDigest(message));
          _session.Logout();
          return ConstructEcdsaSigValue(signedHash);
        }

    private byte[] GetMessageDigest(byte[] message)
    {
       using (Mechanism mechanism = new Mechanism(CKM_SHA256))
       {
         return _session.Digest(mechanism, message);
        }
    }

    public static byte[] ConstructEcdsaSigValue(byte[] rs)
    {
        if (rs == null)
            throw new ArgumentNullException(nameof(rs));

        if (rs.Length < 2 || rs.Length % 2 != 0)
            throw new ArgumentException("Invalid length", nameof(rs));

        int halfLen = rs.Length / 2;

        byte[] half1 = new byte[halfLen];
        Array.Copy(rs, 0, half1, 0, halfLen);
        var r = new Org.BouncyCastle.Math.BigInteger(1, half1);

        byte[] half2 = new byte[halfLen];
        Array.Copy(rs, halfLen, half2, 0, halfLen);
        var s = new Org.BouncyCastle.Math.BigInteger(1, half2);

        var derSequence = new Org.BouncyCastle.Asn1.DerSequence(
            new Org.BouncyCastle.Asn1.DerInteger(r),
            new Org.BouncyCastle.Asn1.DerInteger(s));

        return derSequence.GetDerEncoded();
    }