Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id E0D0F200AC8 for ; Tue, 7 Jun 2016 17:28:43 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id DF916160A36; Tue, 7 Jun 2016 15:28:43 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id D6B14160968 for ; Tue, 7 Jun 2016 17:28:42 +0200 (CEST) Received: (qmail 81716 invoked by uid 500); 7 Jun 2016 15:28:37 -0000 Mailing-List: contact users-help@pdfbox.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: users@pdfbox.apache.org Delivered-To: mailing list users@pdfbox.apache.org Received: (qmail 81705 invoked by uid 99); 7 Jun 2016 15:28:36 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd4-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 07 Jun 2016 15:28:36 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd4-us-west.apache.org (ASF Mail Server at spamd4-us-west.apache.org) with ESMTP id 4D4CEC0773 for ; Tue, 7 Jun 2016 15:28:36 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd4-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -0.446 X-Spam-Level: X-Spam-Status: No, score=-0.446 tagged_above=-999 required=6.31 tests=[KAM_LAZY_DOMAIN_SECURITY=1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-1.426] autolearn=disabled Received: from mx2-lw-us.apache.org ([10.40.0.8]) by localhost (spamd4-us-west.apache.org [10.40.0.11]) (amavisd-new, port 10024) with ESMTP id S9OIasMe6gZJ for ; Tue, 7 Jun 2016 15:28:32 +0000 (UTC) Received: from mailout08.t-online.de (mailout08.t-online.de [194.25.134.20]) by mx2-lw-us.apache.org (ASF Mail Server at mx2-lw-us.apache.org) with ESMTPS id 8DDA35F1BE for ; Tue, 7 Jun 2016 15:28:32 +0000 (UTC) Received: from fwd19.aul.t-online.de (fwd19.aul.t-online.de [172.20.27.65]) by mailout08.t-online.de (Postfix) with SMTP id 2276841D22B8 for ; Tue, 7 Jun 2016 17:28:25 +0200 (CEST) Received: from [192.168.2.104] (bVxLu0ZJZhfqt8PGWdudwHCNv4yZIA2pU+CIkQaubU2JuJk0Rzn4Hxsn+wm+mGHgXZ@[217.231.139.2]) by fwd19.t-online.de with (TLSv1.2:ECDHE-RSA-AES256-SHA encrypted) esmtp id 1bAIvS-0FYMW80; Tue, 7 Jun 2016 17:28:18 +0200 Subject: Re: Verifier digital signatures To: users@pdfbox.apache.org References: From: Tilman Hausherr Message-ID: <718d8867-63de-b3f0-3590-706d931790d6@t-online.de> Date: Tue, 7 Jun 2016 17:28:39 +0200 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Thunderbird/45.1.1 MIME-Version: 1.0 In-Reply-To: Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 7bit X-ID: bVxLu0ZJZhfqt8PGWdudwHCNv4yZIA2pU+CIkQaubU2JuJk0Rzn4Hxsn+wm+mGHgXZ X-TOI-MSGID: 09131687-25b8-4e1b-ada9-042a405f43f7 archived-at: Tue, 07 Jun 2016 15:28:44 -0000 Here's some code. At this time, it works only properly with adbe.pkcs7.detached signatures. I don't know whether it checks advanced stuff like certificate chain, revocation lists, etc. But it can detect that a file was changed. Enjoy. Tilman /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.pdfbox.examples.signature; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; import java.util.Collection; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSString; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSProcessable; import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.util.Store; /** * This will read a document from the filesystem, decrypt it and do something with the signature. * * @author Ben Litchfield */ public final class ShowSignature { private static SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); private ShowSignature() { } /** * This is the entry point for the application. * * @param args The command-line arguments. * * @throws IOException If there is an error reading the file. * @throws CertificateException */ public static void main(String[] args) throws IOException, CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException { ShowSignature show = new ShowSignature(); show.showSignature( args ); } private void showSignature(String[] args) throws IOException, CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException { if( args.length != 2 ) { usage(); } else { String password = args[0]; String infile = args[1]; PDDocument document = null; try { document = PDDocument.load(new File(infile), password); for (PDSignature sig : document.getSignatureDictionaries()) { COSDictionary sigDict = sig.getCOSObject(); COSString contents = (COSString) sigDict.getDictionaryObject(COSName.CONTENTS); // download the signed content, described in /ByteRange COSArray: // [offset1 len1 offset2 len2] int[] byteRange = sig.getByteRange(); byte[] buf = new byte[byteRange[1] + byteRange[3]]; RandomAccessFile raf = new RandomAccessFile(infile, "r"); raf.seek(byteRange[0]); raf.readFully(buf, byteRange[0], byteRange[1]); raf.seek(byteRange[2]); raf.readFully(buf, byteRange[1], byteRange[3]); raf.close(); System.out.println("Signature found"); System.out.println("Name: " + sig.getName()); System.out.println("Modified: " + sdf.format(sig.getSignDate().getTime())); String subFilter = sig.getSubFilter(); if (subFilter != null) { if (subFilter.equals("adbe.pkcs7.detached")) { CMSProcessable signedContent = new CMSProcessableByteArray(buf); // inspiration: // http://stackoverflow.com/a/26702631/535646 // http://stackoverflow.com/a/9261365/535646 CMSSignedData signedData = new CMSSignedData(signedContent, contents.getBytes()); Store certificatesStore = signedData.getCertificates(); Collection signers = signedData.getSignerInfos().getSigners(); SignerInformation signerInformation = signers.iterator().next(); Collection matches = certificatesStore.getMatches(signerInformation.getSID()); X509CertificateHolder certificateHolder = (X509CertificateHolder) matches.iterator().next(); X509Certificate certFromSignedData = new JcaX509CertificateConverter().getCertificate(certificateHolder); System.out.println("certFromSignedData: " + certFromSignedData); certFromSignedData.checkValidity(sig.getSignDate().getTime()); // CMSVerifierCertificateNotValidException means that the keystore wasn't valid at signing time if (signerInformation.verify(new JcaSimpleSignerInfoVerifierBuilder().build(certFromSignedData))) { System.out.println("Signature verified"); } else { System.out.println("Signature verification failed"); } //TODO check certificate chain, revocation lists, timestamp... } else if (subFilter.equals("adbe.x509.rsa_sha1")) { // PDFBOX-2693.pdf COSString certString = (COSString) sigDict.getDictionaryObject( COSName.getPDFName("Cert")); byte[] certData = certString.getBytes(); CertificateFactory factory = CertificateFactory.getInstance("X.509"); ByteArrayInputStream certStream = new ByteArrayInputStream(certData); Collection certs = factory.generateCertificates(certStream); System.out.println("certs=" + certs); //TODO verify signature } else if (subFilter.equals("adbe.pkcs7.sha1")) { // PDFBOX-1452.pdf COSString certString = (COSString) sigDict.getDictionaryObject( COSName.CONTENTS); byte[] certData = certString.getBytes(); CertificateFactory factory = CertificateFactory.getInstance("X.509"); ByteArrayInputStream certStream = new ByteArrayInputStream(certData); Collection certs = factory.generateCertificates(certStream); System.out.println("certs=" + certs); //TODO verify signature } else { System.err.println("Unknown certificate type: " + subFilter); } } else { throw new IOException("Missing subfilter for cert dictionary"); } } } catch (CMSException ex) { throw new IOException(ex); } catch (OperatorCreationException ex) { throw new IOException(ex); } finally { if (document != null) { document.close(); } } } } /** * This will print a usage message. */ private static void usage() { System.err.println( "usage: java " + ShowSignature.class.getName() + " " ); } } --------------------------------------------------------------------- To unsubscribe, e-mail: users-unsubscribe@pdfbox.apache.org For additional commands, e-mail: users-help@pdfbox.apache.org