Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which data from a CMSSignedData object must I pass to generate a valid Timestamp?

I have a valid PKCS7 file loaded into a CMSSignedData object. This PKCS7 file includes a plain text message and a valid attached digital signature (all in the same file).

Now I want to timestamp this file. This is the code I'm using (source):

 private static CMSSignedData addTimestamp(CMSSignedData signedData)
throws Exception {
        Collection ss = signedData.getSignerInfos().getSigners();
        SignerInformation si = (SignerInformation) ss.iterator().next();

        TimeStampToken tok = getTimeStampToken();

        ASN1InputStream asn1InputStream = new ASN1InputStream
(tok.getEncoded());
        DERObject tstDER = asn1InputStream.readObject();
        DERSet ds = new DERSet(tstDER);

        Attribute a = new Attribute(new
DERObjectIdentifier("1.2.840.113549.1.9.16.2.14"), ds);
        DEREncodableVector dv = new DEREncodableVector();
        dv.add(a);
        AttributeTable at = new AttributeTable(dv);
        si = SignerInformation.replaceUnsignedAttributes(si, at);
        ss.clear();
        ss.add(si);
        SignerInformationStore sis = new SignerInformationStore(ss);

        signedData = CMSSignedData.replaceSigners(signedData, sis);
        return signedData;
    }


 private static TimeStampToken getTimeStampToken() throws
Exception {
        Security.addProvider (new
org.bouncycastle.jce.provider.BouncyCastleProvider());

        PostMethod post = new PostMethod("http://My-TrustedTimeStampProvier.com");

// I'm omitting the part where I pass the user and password

        TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
        //request TSA to return certificate
        reqGen.setCertReq (true); // In my case this works

        //make a TSP request this is a dummy sha1 hash (20 zero bytes)
        TimeStampRequest request =
            reqGen.generate(TSPAlgorithms.SHA1, new byte[20], BigInteger.valueOf(100));

        byte[] enc_req = request.getEncoded();
        ByteArrayInputStream bais = new ByteArrayInputStream(enc_req);

        post.setRequestBody(bais);
        post.setRequestContentLength (enc_req.length);
        post.setRequestHeader("Content-type","application/timestamp-query");

        HttpClient http_client = new HttpClient();
        http_client.executeMethod(post);
        InputStream in = post.getResponseBodyAsStream();

        //read TSP response
        TimeStampResponse resp = new TimeStampResponse (in);

        resp.validate(request);

        TimeStampToken  tsToken = resp.getTimeStampToken();       
        return tsToken;
    }  

I can get a valid TimeStamp, and I could put it into my CMSSignedData object and save it to a file writting the bytes from signedData.getEncoded() to the harddisk. But when I validate my new shinny timestamped file with a third party software, this software tells the original signature is ok, but the Timestamp doesn't correspond with the signature. This software also can show me the original plain text message.

I think the problem is in this line:

TimeStampRequest request =
    reqGen.generate(TSPAlgorithms.SHA1, new byte[20], BigInteger.valueOf(100));

I think I have to pass a digest instead of a dummy byte array, but I don't know which digest, or what are the right bytes I have to timeStamp. I successfully could get and verify a SignerInformation object from my signedData. Then I tried to pass to the reqGen.generate() function the bytes from mySignerInformation.getSignature(). The timestamp verification failed. Then I passed a Sha1 digest of mySignerInformation.getSignature(), but my timestamp verification failed again.

The RFC3161 specification says:

2.4.1. Request Format

A time-stamping request is as follows:

TimeStampReq ::= SEQUENCE { version INTEGER { v1(1) }, messageImprint MessageImprint, --a hash algorithm OID and the hash value of the data to be

(...)

The messageImprint field SHOULD contain the hash of the datum to be time-stamped. The hash is represented as an OCTET STRING. Its
length MUST match the length of the hash value for that algorithm
(e.g., 20 bytes for SHA-1 or 16 bytes for MD5).

MessageImprint ::= SEQUENCE { hashAlgorithm AlgorithmIdentifier, hashedMessage OCTET STRING }

But it doesn't tell me where or how I get the MessageImprint data if I want to TimeStamp the bytes inside a CMSSignedData object.

I'm a newbie in this digital signature stuff.

like image 200
Broken_Window Avatar asked Feb 19 '15 20:02

Broken_Window


1 Answers

You're right, the problem is that you're timestamping the incorrect data. The rest of the code seems correct to me.

So the thing is that you've to timestamp the hash of the signature. To get the signature from your CMSSignedData and hash it; you can use the follow code (supposing that you've only one signer in your PKCS7 and you're using SHA1 hash algorithm):

CMSSignedData signedData = ...
// get the signers of your CMSSignedData signedData
Collection ss = signedData.getSignerInfos().getSigners();
SignerInformation si = (SignerInformation) ss.iterator().next();
// hash the signature
byte[] signDigest = MessageDigest
      .getInstance(TSPAlgorithms.SHA1, new BouncyCastleProvider())
      .digest(si.getSignature()); // since you're adding the bc provider with Security.addProvider you can use "BC" instead of passing the new BouncyCastleProvider() 
TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
// generate the TSRequest
TimeStampRequest request =
            reqGen.generate(TSPAlgorithms.SHA1, signDigest, BigInteger.valueOf(100));
...

Hope this helps,

like image 169
albciff Avatar answered Nov 03 '22 00:11

albciff