Я пытаюсь добавить метку времени к своей подписи в PDF-документе с помощью PDFBox 1.8.9. Я получаю сообщение об ошибке, в котором говорится, что отметка времени не может быть проверена , и когда я пытаюсь проверить сертификат центра отметки времени, он отображается как «Недоступно» .
Вот мой метод sign в классе, который реализует SignatureInterface, он вызывает метод SignTimeStamps
public byte[] sign(java.io.InputStream content)
{
CMSProcessableInputStream input = new CMSProcessableInputStream(content);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
// Certificate Chain
java.util.List certList = java.util.Arrays.asList(certificates);
java.security.cert.CertStore certStore = null;
try
{
certStore = java.security.cert.CertStore.getInstance("Collection", new java.security.cert.CollectionCertStoreParameters(certList), provider);
gen.addSigner(privateKey, (java.security.cert.X509Certificate)certList.get(0), CMSSignedGenerator.DIGEST_SHA256);
gen.addCertificatesAndCRLs(certStore);
CMSSignedData signedData = gen.generate(input, false, provider);
if (signatureResources.TsaUrl != null)
{
// signedData = AddTimeStamp(signedData);
signedData = SignTimeStamps(signedData);
}
return signedData.getEncoded();
}
catch (java.lang.Exception e)
{
// should be handled
System.Console.WriteLine("error while creating pkcs7 signature");
e.printStackTrace();
}
throw new java.lang.RuntimeException("problem while preparing signature");
}
Метод SignTimeStamps вызывает метод SignTimeStamp для каждого SignerInfo
private CMSSignedData SignTimeStamps(CMSSignedData signedData)
{
SignerInformationStore signerStore = signedData.getSignerInfos();
java.util.Iterator iterator = ((java.util.Collection)signerStore.getSigners()).iterator();
java.util.List newSigners = new java.util.ArrayList();
while (iterator.hasNext())
{
newSigners.add(SignTimeStamp((SignerInformation)iterator.next()));
}
return CMSSignedData.replaceSigners(signedData, new SignerInformationStore(newSigners));
}
И затем мы получаем подпись из SignerInfo, которая является отпечатком сообщения для нашего TimeStampToken, как указано в Приложении A RF C 3161
private SignerInformation SignTimeStamp(SignerInformation signer)
{
AttributeTable unsignedAttributes = signer.getUnsignedAttributes();
ASN1EncodableVector vector = new ASN1EncodableVector();
if (unsignedAttributes != null)
{
vector = unsignedAttributes.toASN1EncodableVector();
}
TSAClientBouncyCastle tsaClient = new TSAClientBouncyCastle(signatureResources.TsaUrl);
byte[] token = tsaClient.GetTimeStampToken(signer.getSignature());
DERObjectIdentifier oid = PKCSObjectIdentifiers.id_aa_signatureTimeStampToken;
ASN1Encodable signatureTimeStamp = new org.bouncycastle.asn1.cms.Attribute(oid, new DERSet(ASN1Object.fromByteArray(token)));
vector.add(signatureTimeStamp);
SignerInformation newSigner = SignerInformation.replaceUnsignedAttributes(signer, new AttributeTable(vector));
if (newSigner == null) return signer;
return newSigner;
}
Так что я не совсем уверен, что я делаю не так, что я могу сделать так, чтобы отметка времени могла быть проверена, и центр отметки времени отображался как доступный?
РЕДАКТИРОВАТЬ Вот ссылка на файл PDF, который я подписал https://www.dropbox.com/s/al3h8lorqisi34q/dummy_signed.pdf?dl=1. TSA - это бесплатный TSA (http://time.certum.pl/)
РЕДАКТИРОВАТЬ 2
Вот мой файл TSAClientBouncyCastle.cs
using System;
using System.IO;
using System.Collections;
using System.Net;
using System.Text;
using System.util;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Tsp;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Asn1.Cmp;
using Org.BouncyCastle.Asn1.Tsp;
using Org.BouncyCastle.Crypto;
namespace ProofOfConcept {
public class TSAClientBouncyCastle : ITSAClient {
/** The Logger instance. */
private static readonly ILogger LOGGER = LoggerFactory.GetLogger(typeof(TSAClientBouncyCastle));
/** URL of the Time Stamp Authority */
protected internal String tsaURL;
/** TSA Username */
protected internal String tsaUsername;
/** TSA password */
protected internal String tsaPassword;
/** An interface that allows you to inspect the timestamp info. */
protected ITSAInfoBouncyCastle tsaInfo;
/** The default value for the hash algorithm */
public const int DEFAULTTOKENSIZE = 4096;
/** Estimate of the received time stamp token */
protected internal int tokenSizeEstimate;
/** The default value for the hash algorithm */
public const String DEFAULTHASHALGORITHM = "SHA-256";
/** Hash algorithm */
protected internal String digestAlgorithm;
/**
* Creates an instance of a TSAClient that will use BouncyCastle.
* @param url String - Time Stamp Authority URL (i.e. "http://tsatest1.digistamp.com/TSA")
*/
public TSAClientBouncyCastle(String url)
: this(url, null, null, DEFAULTTOKENSIZE, DEFAULTHASHALGORITHM) {
}
/**
* Creates an instance of a TSAClient that will use BouncyCastle.
* @param url String - Time Stamp Authority URL (i.e. "http://tsatest1.digistamp.com/TSA")
* @param username String - user(account) name
* @param password String - password
*/
public TSAClientBouncyCastle(String url, String username, String password)
: this(url, username, password, DEFAULTTOKENSIZE, DEFAULTHASHALGORITHM) {
}
/**
* Constructor.
* Note the token size estimate is updated by each call, as the token
* size is not likely to change (as long as we call the same TSA using
* the same imprint length).
* @param url String - Time Stamp Authority URL (i.e. "http://tsatest1.digistamp.com/TSA")
* @param username String - user(account) name
* @param password String - password
* @param tokSzEstimate int - estimated size of received time stamp token (DER encoded)
*/
public TSAClientBouncyCastle(String url, String username, String password, int tokSzEstimate, String digestAlgorithm) {
this.tsaURL = url;
this.tsaUsername = username;
this.tsaPassword = password;
this.tokenSizeEstimate = tokSzEstimate;
this.digestAlgorithm = digestAlgorithm;
}
/**
* @param tsaInfo the tsaInfo to set
*/
public void SetTSAInfo(ITSAInfoBouncyCastle tsaInfo) {
this.tsaInfo = tsaInfo;
}
/**
* Get the token size estimate.
* Returned value reflects the result of the last succesfull call, padded
* @return an estimate of the token size
*/
public virtual int GetTokenSizeEstimate() {
return tokenSizeEstimate;
}
/**
* Gets the MessageDigest to digest the data imprint
* @return the digest algorithm name
*/
public IDigest GetMessageDigest() {
return DigestAlgorithms.GetMessageDigest(digestAlgorithm);
}
/**
* Get RFC 3161 timeStampToken.
* Method may return null indicating that timestamp should be skipped.
* @param imprint data imprint to be time-stamped
* @return encoded, TSA signed data of the timeStampToken
*/
public virtual byte[] GetTimeStampToken(byte[] imprint) {
byte[] respBytes = null;
// Setup the time stamp request
TimeStampRequestGenerator tsqGenerator = new TimeStampRequestGenerator();
tsqGenerator.SetCertReq(true);
// tsqGenerator.setReqPolicy("1.3.6.1.4.1.601.10.3.1");
BigInteger nonce = BigInteger.ValueOf(DateTime.Now.Ticks + Environment.TickCount);
TimeStampRequest request = tsqGenerator.Generate(DigestAlgorithms.GetAllowedDigests(digestAlgorithm), imprint, nonce);
byte[] requestBytes = request.GetEncoded();
// Call the communications layer
respBytes = GetTSAResponse(requestBytes);
// Handle the TSA response
TimeStampResponse response = new TimeStampResponse(respBytes);
// validate communication level attributes (RFC 3161 PKIStatus)
response.Validate(request);
PkiFailureInfo failure = response.GetFailInfo();
int value = (failure == null) ? 0 : failure.IntValue;
if (value != 0) {
// @todo: Translate value of 15 error codes defined by PKIFailureInfo to string
throw new IOException(MessageLocalization.GetComposedMessage("invalid.tsa.1.response.code.2", tsaURL, value));
}
// @todo: validate the time stap certificate chain (if we want
// assure we do not sign using an invalid timestamp).
// extract just the time stamp token (removes communication status info)
TimeStampToken tsToken = response.TimeStampToken;
if (tsToken == null) {
throw new IOException(MessageLocalization.GetComposedMessage("tsa.1.failed.to.return.time.stamp.token.2", tsaURL, response.GetStatusString()));
}
TimeStampTokenInfo tsTokenInfo = tsToken.TimeStampInfo; // to view details
byte[] encoded = tsToken.GetEncoded();
LOGGER.Info("Timestamp generated: " + tsTokenInfo.GenTime);
if (tsaInfo != null) {
tsaInfo.InspectTimeStampTokenInfo(tsTokenInfo);
}
// Update our token size estimate for the next call (padded to be safe)
this.tokenSizeEstimate = encoded.Length + 32;
return encoded;
}
/**
* Get timestamp token - communications layer
* @return - byte[] - TSA response, raw bytes (RFC 3161 encoded)
*/
protected internal virtual byte[] GetTSAResponse(byte[] requestBytes) {
HttpWebRequest con = (HttpWebRequest)WebRequest.Create(tsaURL);
con.ContentLength = requestBytes.Length;
con.ContentType = "application/timestamp-query";
con.Method = "POST";
if ((tsaUsername != null) && !tsaUsername.Equals("") ) {
string authInfo = tsaUsername + ":" + tsaPassword;
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo), Base64FormattingOptions.None);
con.Headers["Authorization"] = "Basic " + authInfo;
}
Stream outp = con.GetRequestStream();
outp.Write(requestBytes, 0, requestBytes.Length);
outp.Close();
HttpWebResponse response = (HttpWebResponse)con.GetResponse();
if (response.StatusCode != HttpStatusCode.OK)
throw new IOException(MessageLocalization.GetComposedMessage("invalid.http.response.1", (int)response.StatusCode));
Stream inp = response.GetResponseStream();
MemoryStream baos = new MemoryStream();
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = inp.Read(buffer, 0, buffer.Length)) > 0) {
baos.Write(buffer, 0, bytesRead);
}
inp.Close();
response.Close();
byte[] respBytes = baos.ToArray();
String encoding = response.ContentEncoding;
if (encoding != null && Util.EqualsIgnoreCase(encoding, "base64")) {
respBytes = Convert.FromBase64String(Encoding.ASCII.GetString(respBytes));
}
return respBytes;
}
}
РЕДАКТИРОВАТЬ ОКОНЧАТЕЛЬНОЕ РЕШЕНИЕ (РЕШЕНИЕ)
Спасибо всем за то, что помогли мне, как указал Тилман Хаушерр, я просто не хешировал подпись. Я заменил
byte[] token = tsaClient.GetTimeStampToken(signer.getSignature());
в моем методе SignTimeStamp на
java.security.MessageDigest mda = java.security.MessageDigest.getInstance("SHA-256");
byte[] digest = mda.digest(signer.getSignature());
byte[] token = tsaClient.GetTimeStampToken(digest);
И теперь я могу видеть полномочия по установке временных меток. Еще раз большое спасибо!