Itextsharp подписания PDF с подписанным хеш - PullRequest
0 голосов
/ 23 октября 2018

Я пытаюсь подписать pdf через службу подписи.Для этого сервиса требуется отправить дайджест SHA256 в шестнадцатеричном формате, а взамен я получаю подпись в шестнадцатеричном формате.Кроме того, я также получаю сертификат подписи, промежуточный сертификат, ответ OCSP и TimeStampToken.Тем не менее, я уже застрял, пытаясь подписать pdf с помощью signatureValue.

Я прочитал официальный документ Bruno, чрезмерно просмотрел Интернет и пробовал много разных способов, но подпись продолжает появляться как недействительная.

Моя последняя попытка:

Сначала подготовьте pdf

PdfReader reader = new PdfReader(src);
FileStream os = new FileStream(dest, FileMode.Create);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Certificate = signingCertificate;
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);

string hashAlgorithm = "SHA-256";
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
PdfSignatureAppearance appearance2 = stamper.SignatureAppearance;
Stream stream = appearance2.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(stream, hashAlgorithm);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);

Хеш-байт [] sh и преобразуйте его в строку следующим образом

private static String sha256_hash(Byte[] value)
{
    using (SHA256 hash = SHA256.Create())
    {
         return String.Concat(hash.ComputeHash(value).Select(item => item.ToString("x2"))).ToUpper();
    }
}

и отправьте вподписывающий сервис.Полученное шестнадцатеричное закодированное значение подписи я затем конвертирую в байты

private static byte[] StringToByteArray(string hex)
{
    return Enumerable.Range(0, hex.Length).Where(x => x % 2 == 0).Select(x => Convert.ToByte(hex.Substring(x, 2), 16)).ToArray();
}

Наконец, создаю подпись

private void CreateSignature(string src, string dest, byte[] sig) 
{
    PdfReader reader = new PdfReader(src); // src is now prepared pdf
    FileStream os = new FileStream(dest, FileMode.Create);
    IExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
    MakeSignature.SignDeferred(reader, "Signature1", os, external);

    reader.Close();
    os.Close();
}
private class MyExternalSignatureContainer : IExternalSignatureContainer
{
    protected byte[] sig;
    public MyExternalSignatureContainer(byte[] sig)
    {
        this.sig = sig;
    }
    public byte[] Sign(Stream s)
    {
        return sig;
    }
    public void ModifySigningDictionary(PdfDictionary signDic) { }
}

Что я делаю не так?Помощь очень ценится.Спасибо!

Редактировать: Текущее состояние

Благодаря помощи mkl и следуя примеру отложенной подписи Бруно, я получил сообщение о недействительной подписи.По-видимому, я не получаю полную цепочку от службы подписи, а просто промежуточный сертификат, который вызвал недействительное сообщение.К сожалению, подпись все еще имеет недостатки.

Я строю цепочку следующим образом:

List<X509Certificate> certificateChain = new List<X509Certificate>
{
     signingCertificate,
     intermediateCertificate
}; 

В методе подписания MyExternalSignatureContainer я сейчас создаю и возвращаю контейнер подписи:

public byte[] Sign(Stream s)
{
    string hashAlgorithm = "SHA-256";
    PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);

    byte[] ocspResponse = Convert.FromBase64String("Base64 encoded DER representation of the OCSP response received from signing service");
    byte[] hash = DigestAlgorithms.Digest(s, hashAlgorithm);
    byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocspResponse, null, CryptoStandard.CMS);

    string messageDigest = Sha256_hash(sh);
    // messageDigest sent to signing service
    byte[] signatureAsByte = StringToByteArray("Hex encoded SignatureValue received from signing service");

    sgn.SetExternalDigest(signatureAsByte, null, "RSA");

    ITSAClient tsaClient = new MyITSAClient();

    return sgn.GetEncodedPKCS7(hash, tsaClient, ocspResponse, null, CryptoStandard.CMS); 
}

public class MyITSAClient : ITSAClient
{
    public int GetTokenSizeEstimate()
    {
        return 0;
    }

    public IDigest GetMessageDigest()
    {
        return new Sha256Digest();
    }

    public byte[] GetTimeStampToken(byte[] imprint)
    {
        string hashedImprint = HexEncode(imprint);
        // Hex encoded Imprint sent to signing service

        return Convert.FromBase64String("Base64 encoded DER representation of TimeStampToken received from signing service");
    }
}

Все еще получаю эти сообщения:

  1. "Идентификатор подписавшего неизвестен, поскольку он не был включен в список доверенных идентификаторов и ни один, или его родительские сертификаты не являются доверенными идентификаторами"
  2. "Подпись имеет временную метку,но отметка времени не может быть подтверждена "

Дальнейшая помощь очень ценится снова!

1 Ответ

0 голосов
/ 24 октября 2018

«Что я делаю не так?»

Проблема в том, что с одной стороны вы начинаете создавать контейнер сигнатур CMS с использованием PdfPKCS7 экземпляра

PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);

и для рассчитанногодайджест документа hash извлекает подписанные атрибуты как

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);

для отправки их на подпись.

Пока все хорошо.

Но тогда вы игнорируете контейнер CMSвы начали конструировать, но вместо этого вставьте открытые байты подписи , полученные от службы , в PDF .

Это не может работать, поскольку байты вашей подписи не подписывают документнапрямую, но вместо этого они подписывают эти подписанные атрибуты (и, следовательно, косвенно документ как хэш документа является одним из подписанных атрибутов).Таким образом, игнорируя создаваемый контейнер CMS, вы удаляете фактически подписанные данные ...

Кроме того, используемый вами подфильтр ADBE_PKCS7_DETACHED обещает, что встроенная подпись является полным контейнером сигнатуры CMS, а не несколькими голыми сигнатурами.в байтах, поэтому формат также неправильный.

Как это сделать вместо этого?

Вместо того, чтобы вставлять в PDF-файл открытые байты подписи, полученные из службы, как есть, их необходимо установить.как внешний дайджест в экземпляре PdfPKCS7, в котором вы изначально начали создавать контейнер подписи:

sgn.SetExternalDigest(sig, null, ENCRYPTION_ALGO);

(ENCRYPTION_ALGO должна быть частью шифрования алгоритма подписи, я полагаю в вашем случае "RSA".)

и затем вы можете получить сгенерированный контейнер подписи CMS:

byte[] encodedSig = sgn.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);

Теперь это контейнер подписи, который нужно вставить в документ с помощью MyExternalSignatureContainer:

IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSig);
MakeSignature.SignDeferred(reader, "Signature1", os, external);

Остальные проблемы

Исправив ваш код, Adobe Reader по-прежнему предупреждает о вашей подписи:

  1. "ПодписьИдентификатор NER неизвестен, поскольку он не был включен в список доверенных идентификаторов, и ни один, или его родительские сертификаты не являются доверенными. "

Это предупреждение следует ожидатьи исправьте!

Идентификатор подписавшего неизвестен , поскольку ваша служба подписи использует только демонстрационный сертификат, а не сертификат для производственного использования:

Signer certificate in certificate viewer

Как видите, сертификат выпущен "GlobalSign Непубличная демонстрация HVCA", и непубличная демонстрация эмитентов по очевидным причинам должнынельзя доверять (если вы не добавите их вручную в хранилище доверенных сертификатов для тестирования).

«Подпись помечена меткой времени, но метка не может быть проверена»

Существует две причины, по которым Adobe не одобряет вашу метку времени:

На однойС другой стороны, как и выше, сертификат временной метки является закрытым демонстрационным сертификатом («DSS Non-Public Demo TSA Responder»).Таким образом, у верификатора нет причин доверять вашей метке времени.

С другой стороны, однако, в коде вашей метки времени есть реальная ошибка, вы применяете алгоритм хеширования дважды!В вашем MyITSAClient классе у вас есть

public byte[] GetTimeStampToken(byte[] imprint)
{
    string hashedImprint = Sha256_hash(imprint);
    // hashedImprint sent to signing service

    return Convert.FromBase64String("Base64 encoded DER representation of TimeStampToken received from signing service");
}

Параметр imprint вашей реализации GetTimeStampToken уже хэширован, поэтому вам необходимо закодировать эти байты в шестнадцатеричном формате и отправить их для отметки времени.Но вы применяете свой метод Sha256_hash, который сначала хеширует, а затем выполняет шестнадцатеричное кодирование этого нового хэша.

Таким образом, вместо применения Sha256_hash просто шестнадцатеричное кодирование кодирует imprint!

...