/*
* This file is part of rockframework.
*
* rockframework is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* rockframework is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>;.
*/
package br.net.woodstock.rockframework.security.sign.impl;
import java.io.ByteArrayOutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.tsp.TimeStampToken;
import br.net.woodstock.rockframework.core.RockFrameworkVersion;
import br.net.woodstock.rockframework.core.util.Assert;
import br.net.woodstock.rockframework.core.utils.Collections;
import br.net.woodstock.rockframework.core.utils.Conditions;
import br.net.woodstock.rockframework.security.Alias;
import br.net.woodstock.rockframework.security.Identity;
import br.net.woodstock.rockframework.security.digest.DigestType;
import br.net.woodstock.rockframework.security.sign.PKCS7SignatureParameters;
import br.net.woodstock.rockframework.security.sign.Signatory;
import br.net.woodstock.rockframework.security.sign.Signature;
import br.net.woodstock.rockframework.security.sign.SignatureInfo;
import br.net.woodstock.rockframework.security.sign.SignatureType;
import br.net.woodstock.rockframework.security.sign.SignerException;
import br.net.woodstock.rockframework.security.store.CertificateEntry;
import br.net.woodstock.rockframework.security.store.KeyStoreType;
import br.net.woodstock.rockframework.security.store.Store;
import br.net.woodstock.rockframework.security.store.impl.JCAStore;
import br.net.woodstock.rockframework.security.timestamp.TimeStamp;
import br.net.woodstock.rockframework.security.timestamp.impl.BouncyCastleTimeStampHelper;
import br.net.woodstock.rockframework.security.util.BouncyCastleProviderHelper;
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfDate;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignature;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfString;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.CertificateUtil;
import com.itextpdf.text.pdf.security.CertificateVerification;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.OcspClient;
import com.itextpdf.text.pdf.security.OcspClientBouncyCastle;
import com.itextpdf.text.pdf.security.PdfPKCS7;
import com.itextpdf.text.pdf.security.PrivateKeySignature;
import com.itextpdf.text.pdf.security.TSAClient;
public class PDFSigner extends AbstractSigner {
private static final long serialVersionUID = RockFrameworkVersion.VERSION;
private static final char PDF_SIGNATURE_VERSION = '\0';
private PKCS7SignatureParameters parameters;
public PDFSigner(final PKCS7SignatureParameters parameters) {
super();
this.parameters = parameters;
}
@Override
public byte[] sign(final byte[] data) {
Assert.notNull(this.parameters, "parameters");
Assert.notNull(data, "data");
try {
byte[] currentData = data;
for (Identity identity : this.parameters.getIdentities()) {
currentData = this.singleSign(data, identity);
}
return currentData;
} catch (Exception e) {
throw new SignerException(e);
}
}
private byte[] singleSign(final byte[] data, final Identity identity) {
Assert.notEmpty(data, "data");
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PrivateKey privateKey = identity.getPrivateKey();
Certificate[] chain = identity.getChain();
X509Certificate certificate = (X509Certificate) chain[0];
DigestType digestType = this.getDigestTypeFromSignature(certificate.getSigAlgName());
Calendar calendar = Calendar.getInstance();
PdfReader reader = new PdfReader(data);
PdfStamper stamper = PdfStamper.createSignature(reader, outputStream, PDFSigner.PDF_SIGNATURE_VERSION, null, true);
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
if (this.parameters.getSignatureInfo() != null) {
appearance.setContact(this.parameters.getSignatureInfo().getContactInfo());
appearance.setLocation(this.parameters.getSignatureInfo().getLocation());
appearance.setReason(this.parameters.getSignatureInfo().getReason());
}
appearance.setSignDate(calendar);
PdfSignature signature = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
signature.setReason(appearance.getReason());
signature.setLocation(appearance.getLocation());
signature.setContact(appearance.getContact());
signature.setDate(new PdfDate(appearance.getSignDate()));
if ((this.parameters.getSignatureInfo() != null) && (Conditions.isNotEmpty(this.parameters.getSignatureInfo().getName()))) {
signature.setName(this.parameters.getSignatureInfo().getName());
} else {
signature.setName(BouncyCastleProviderHelper.toString(certificate.getSubjectX500Principal()));
}
appearance.setCryptoDictionary(signature);
String providerName = null;
if (Conditions.isNotEmpty(this.parameters.getProvider())) {
providerName = this.parameters.getProvider();
} else {
providerName = BouncyCastleProviderHelper.PROVIDER_NAME;
}
ExternalSignature externalSignature = new PrivateKeySignature(privateKey, digestType.getAlgorithm(), providerName);
ExternalDigest externalDigest = new BouncyCastleDigest();
TSAClient tsc = null;
if (this.parameters.getTimeStampClient() != null) {
tsc = new DelegateTSAClient(this.parameters.getTimeStampClient(), digestType);
}
OcspClient ocsp = null;
if (Conditions.isNotEmpty(chain)) {
String ocspUrl = CertificateUtil.getOCSPURL(certificate);
X509Certificate parentCertificate = null;
for (Certificate c : chain) {
if (!certificate.equals(c)) {
parentCertificate = (X509Certificate) c;
break;
}
}
if (parentCertificate != null) {
if ((ocspUrl != null) && (ocspUrl.trim().length() > 0)) {
ocsp = new OcspClientBouncyCastle();
}
}
}
MakeSignature.signDetached(appearance, externalDigest, externalSignature, chain, null, ocsp, tsc, 0, CryptoStandard.CMS);
return outputStream.toByteArray();
} catch (Exception e) {
throw new SignerException(e);
}
}
@Override
public boolean verify(final byte[] data, final byte[] signature) {
try {
Signature[] signatures = this.getSignatures(signature);
if (Conditions.isNotEmpty(signatures)) {
for (Signature s : signatures) {
if (!s.getValid().booleanValue()) {
return false;
}
}
}
return true;
} catch (Exception e) {
throw new SignerException(e);
}
}
@Override
public Signature[] getSignatures(final byte[] data) {
try {
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(null, null);
PdfReader reader = new PdfReader(data);
AcroFields fields = reader.getAcroFields();
Collection<Signature> signatures = new ArrayList<Signature>();
if (fields != null) {
List<String> list = fields.getSignatureNames();
if ((list != null) && (!list.isEmpty())) {
for (String str : list) {
PdfPKCS7 pk = fields.verifySignature(str);
PdfString string = fields.getSignatureDictionary(str).getAsString(PdfName.CONTENTS);
byte[] content = string.getBytes();
X509Certificate certificate = pk.getSigningCertificate();
byte[] encoded = content;
TimeStamp timeStamp = null;
String location = pk.getLocation();
String reason = pk.getReason();
String signName = pk.getSignName();
Date date = pk.getSignDate().getTime();
Boolean valid = Boolean.TRUE;
Signatory signatory = this.toSignatory(certificate);
Store store = new JCAStore(KeyStoreType.JKS);
store.add(new CertificateEntry(new Alias(certificate.getSerialNumber().toString()), certificate));
if (pk.verify()) {
valid = Boolean.FALSE;
}
Object[] fails = CertificateVerification.verifyCertificates(pk.getCertificates(), keystore, null, pk.getSignDate());
if (Conditions.isNotEmpty(fails)) {
valid = Boolean.FALSE;
}
TimeStampToken timeStampToken = pk.getTimeStampToken();
if (timeStampToken != null) {
timeStamp = BouncyCastleTimeStampHelper.toTimeStamp(timeStampToken);
timeStampToken.getTimeStampInfo();
if (valid.booleanValue()) {
boolean ok = pk.verifyTimestampImprint();
valid = Boolean.valueOf(ok);
}
}
Signature sig = new Signature();
sig.setDate(date);
sig.setEncoded(encoded);
sig.setSignatories(new ArrayList<Signatory>());
sig.getSignatories().add(signatory);
sig.setTimeStamp(timeStamp);
sig.setValid(valid);
if ((Conditions.isNotEmpty(location)) || (Conditions.isNotEmpty(reason)) || (Conditions.isNotEmpty(signName))) {
SignatureInfo info = new SignatureInfo(signName, reason, location, null);
sig.setSignatureInfo(info);
}
signatures.add(sig);
}
}
}
return Collections.toArray(signatures, Signature.class);
} catch (Exception e) {
throw new SignerException(e);
}
}
protected Signatory toSignatory(final X509Certificate certificate) {
X500Principal x500Subject = certificate.getSubjectX500Principal();
X500Principal x500Issuer = certificate.getIssuerX500Principal();
String subject = BouncyCastleProviderHelper.toString(x500Subject);
String issuer = BouncyCastleProviderHelper.toString(x500Issuer);
Signatory signatory = new Signatory();
signatory.setCertificate(certificate);
signatory.setIssuer(issuer);
signatory.setSubject(subject);
return signatory;
}
protected SignatureType getSignatureType(final String signatureAlgorithm) {
SignatureType type = SignatureType.getSignType(signatureAlgorithm);
if (type == null) {
type = SignatureType.SHA1_RSA;
}
return type;
}
protected DigestType getDigestTypeFromSignature(final String signatureAlgorithm) {
SignatureType signatureType = this.getSignatureType(signatureAlgorithm);
DigestType digestType = signatureType.getDigestType();
return digestType;
}
}