Description

Etre certain de l’identité du signataire d’un PDF c’est déjà bien, mais si on désire avoir la même certitude sur l’horodatage de cette signature nous devons ajouter un autre « service » le « Time-Stamp Protocol » (rfc3161).

En effet, par défaut, quand nous générons une signature numérique c’est la date du poste réalisant l’action qui est pris en compte. Cela ne permet aucune assurance sur la validité de cette date (nous pouvons très facilement modifier la date de notre poste de travail).

Le service TSA permet de rechercher un timestamp sur un serveur externe certifié. Le principe est très simple :

  • émission d’une requête vers le serveur TSA
  • Construction de la réponse par le serveur TSA
  • réception de la réponse du serveur TSA
  • inclusion du TimeStamp dans le document



J’ai essayé d’intégrer ce service lors de la signature d’un document PDF pour apposer un timestamp « valide » sur le document et ainsi finaliser mes tests pour « Signer un document PDF ». Hélas, l’API d’iText (et principalement la classe PdfPKCS7) ne semble pas proposer ce service (j’espère que cela sera bientôt implémenté). Diverses recherches sur le Web m’ont donné une base de travail et vous trouverez ci dessous le résultat final de ces tests.

Actuellement les seules solutions que je suis arrivé à faire fonctionner sont des adaptations de la classe PdfPKCS7 (exemple). Je préfèrerais ne pas utiliser de classe modifiée de la librairie iText ou pouvoir spécialiser cette classe.

SignPdf.zip Source de l’exemple

signTSA.pdf Exemple de fichier généré

Le service TSA : Time-Stamp-Authority

Pour faire fonctionner cela vous devez obligatoirement faire appel à une TSA (Time Stamp Authority). Vous pouvez soit en utiliser une sur le net (certaines sont libres d’utilisation pour tests), soit en installer une dans votre entreprise. Vous trouverez ci dessous une liste de liens pouvant vous aider.

Source

Le code source de ce test est basé sur diverses pistes trouvées sur le net. A la base c’est une modification de la classe PdfPKCS7 et l’utilisation de la librairie bouncycastle

Exemple

public static final boolean signTSAPdf() throws IOException, DocumentException, Exception
{
	try {
		// Creation d'un KeyStore
		KeyStore ks = KeyStore.getInstance("PKCS12");
		// Chargement du certificat p12 dans el magasin
		ks.load(new FileInputStream(fileKey), fileKeyPassword.toCharArray());
		String alias = (String)ks.aliases().nextElement();
		PrivateKey key = (PrivateKey)ks.getKey(alias, fileKeyPassword.toCharArray());
		Certificate[] chain = ks.getCertificateChain(alias);
 
		PdfReader pdfReader = new PdfReader((new File(dirname + fname+".pdf")).getAbsolutePath());
		File outputFile = new File(dirname + "sign_TSA_" + fname +".pdf");
		PdfStamper pdfStamper;
		pdfStamper = PdfStamper.createSignature(pdfReader, null, '\0', outputFile);
 
		PdfSignatureAppearance sap = pdfStamper.getSignatureAppearance();
 
		sap.setCrypto(key, chain, null, PdfSignatureAppearance.WINCER_SIGNED);
		sap.setReason("Test SignPDF berthou.mc");
		sap.setVisibleSignature(new Rectangle(10, 10, 50, 30), 1, "sign_rbl");
 
		// TSA Service
		TSAClient  tsc = new TSAClientBouncyCastle("http://www.edelweb.fr/cgi-bin/service-tsp") ;
 
		// Setup proxy (if you need a proxy)
		java.net.Proxy p = new java.net.Proxy(java.net.Proxy.Type.HTTP,
	 	new java.net.InetSocketAddress("10.2.0.211", 8089) ) ;
		tsc.setProxy(p) ;
 
		// Configure PDF signature dictionary (PdfName.ADOBE_PPKLITE or PPKMS works too)
		PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_SHA1);
 
		dic.setReason(sap.getReason());
		dic.setLocation(sap.getLocation());
		dic.setContact(sap.getContact());
		dic.setDate(new PdfDate(sap.getSignDate())); // time-stamp will over-rule this
 
	        PdfDictionary transformParams = new PdfDictionary();
	        transformParams.put(PdfName.P, new PdfNumber(1));
	        transformParams.put(PdfName.V, new PdfName("1.2"));
	        transformParams.put(PdfName.TYPE, PdfName.TRANSFORMPARAMS);
	        PdfDictionary reference = new PdfDictionary();
	        reference.put(PdfName.TRANSFORMMETHOD, PdfName.DOCMDP );
	        reference.put(PdfName.TYPE, new PdfName("SigRef"));
	        reference.put(PdfName.TRANSFORMPARAMS, transformParams);
	        PdfArray types = new PdfArray();
	        types.add(reference);
	        dic.put(PdfName.REFERENCE, types);
 
		sap.setCryptoDictionary(dic);
 
		// Estimate signature size, creating a 'fake' one using fake data
		// (SHA1 length does not depend upon the data length)
		byte[] estSignature = genPKCS7Signature(new ByteArrayInputStream("fake".getBytes()), null, key, chain);
		int contentEst = estSignature.length + ((tsc == null) ? 0 : tsc.getTokenSizeEstimate());
 
		// Preallocate excluded byte-range for the signature content (hex encoded)
		HashMap exc = new HashMap();
		exc.put(PdfName.CONTENTS, new Integer(contentEst * 2 + 2));
		sap.preClose(exc);
 
		// Get the true data signature, including a true time stamp token
		byte[] encodedSig = genPKCS7Signature(sap.getRangeStream(), tsc, key, chain) ;
		if (contentEst + 2 < encodedSig.length) {
			throw new Exception("Timestamp size estimate " + contentEst +
                                    " is too low for actual " + encodedSig.length);
		}
 
		// Copy signature into a zero-filled array, padding it up to estimate
		byte[] paddedSig = new byte[contentEst];
		System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
 
		// Finally, load zero-padded signature into the signature field /Content
		PdfDictionary dic2 = new PdfDictionary();
		dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
		sap.close(dic2);
 
		return true;
	}
	catch (Exception key) {
		throw new Exception(key);
	}
}
 
/**
 * Generate the PKCS7 encoded signature
 * @param data InputStream - data to digest
 * @param doTimestamp boolean - true to include time-stamp
 * @return byte[]
 * @throws Exception
 */
static protected byte[] genPKCS7Signature(InputStream data, TSAClient tsc, PrivateKey key, Certificate[] chain )
				throws  Exception {
        // assume sub-filter is adobe.pkcs7.sha1
        TsaPdfPKCS7 sgn = new TsaPdfPKCS7(key, chain, null, "SHA1", null, true);
 
        byte[] buff = new byte[2048];
        int len = 0;
        while ((len = data.read(buff)) > 0) {
            sgn.update(buff, 0, len);
        }
        return sgn.getEncodedPKCS7(null, null, tsc);
 
}
Be Sociable, Share!