/*******************************************************************************
* Copyright 2014 by E-Government Innovation Center EGIZ, Graz, Austria
* PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a
* joint initiative of the Federal Chancellery Austria and Graz University of
* Technology.
*
* Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
* the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
* http://www.osor.eu/eupl/
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
*
* This product combines work with different licenses. See the "NOTICE" text
* file for details on the various modules and licenses.
* The "NOTICE" text file is part of the distribution. Any derivative works
* that you distribute must include a readable copy of the "NOTICE" text file.
******************************************************************************/
package at.gv.egiz.pdfas.sigs.pades;
import iaik.asn1.ASN1Object;
import iaik.asn1.CodingException;
import iaik.asn1.ObjectID;
import iaik.asn1.SEQUENCE;
import iaik.asn1.UTF8String;
import iaik.asn1.structures.AlgorithmID;
import iaik.asn1.structures.Attribute;
import iaik.asn1.structures.ChoiceOfTime;
import iaik.cms.ContentInfo;
import iaik.cms.IssuerAndSerialNumber;
import iaik.cms.SignedData;
import iaik.cms.SignerInfo;
import iaik.smime.ess.ESSCertID;
import iaik.smime.ess.ESSCertIDv2;
import iaik.x509.X509Certificate;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStore.Entry;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import at.gv.egiz.pdfas.common.exceptions.ErrorConstants;
import at.gv.egiz.pdfas.common.exceptions.PDFASError;
import at.gv.egiz.pdfas.common.exceptions.PdfAsException;
import at.gv.egiz.pdfas.common.exceptions.PdfAsSignatureException;
import at.gv.egiz.pdfas.lib.api.PdfAsFactory;
import at.gv.egiz.pdfas.lib.api.sign.IPlainSigner;
import at.gv.egiz.pdfas.lib.api.sign.SignParameter;
import at.gv.egiz.pdfas.lib.api.verify.VerifyResult;
import at.gv.egiz.pdfas.lib.impl.status.RequestedSignature;
import at.gv.egiz.pdfas.lib.util.CertificateUtils;
import at.gv.egiz.pdfas.lib.util.SignatureUtils;
public class PAdESSignerKeystore implements IPlainSigner, PAdESConstants {
private static final Logger logger = LoggerFactory
.getLogger(PAdESSignerKeystore.class);
private static final String fallBackProvider = "SunJSSE";
public static final String SIGNATURE_DEVICE = "JKS";
PrivateKey privKey;
X509Certificate cert;
private void readKeyStore(KeyStore ks, String alias, String keypassword) throws Throwable {
if (keypassword == null) {
throw new PdfAsException("error.pdf.sig.16");
}
PasswordProtection pwdProt = new PasswordProtection(
keypassword.toCharArray());
logger.info("Opening Alias: [" + alias + "]");
Entry entry = ks.getEntry(alias, pwdProt);
if (!(entry instanceof PrivateKeyEntry)) {
throw new PdfAsException("error.pdf.sig.18");
}
PrivateKeyEntry privateEntry = (PrivateKeyEntry) entry;
privKey = privateEntry.getPrivateKey();
if (privKey == null) {
throw new PdfAsException("error.pdf.sig.13");
}
Certificate c = privateEntry.getCertificate();
if (c == null) {
if (privateEntry.getCertificateChain() != null) {
if (privateEntry.getCertificateChain().length > 0) {
c = privateEntry.getCertificateChain()[0];
}
}
}
if (c == null) {
throw new PdfAsException("error.pdf.sig.17");
}
cert = new X509Certificate(c.getEncoded());
}
private KeyStore buildKeyStoreFromFile(String file, String kspassword,
String type, String provider) throws Throwable {
String viusalProvider = (provider == null ? "IAIK" : provider);
logger.info("Opening Keystore: " + file + " with [" + viusalProvider
+ "]");
KeyStore ks = null;
if (provider == null) {
ks = KeyStore.getInstance(type);
} else {
ks = KeyStore.getInstance(type, provider);
}
if (ks == null) {
throw new PdfAsException("error.pdf.sig.14");
}
if (kspassword == null) {
throw new PdfAsException("error.pdf.sig.15");
}
FileInputStream is = null;
try {
is = new FileInputStream(file);
ks.load(is, kspassword.toCharArray());
} finally {
if (is != null) {
is.close();
}
}
return ks;
}
private void loadKeystore(String file, String alias, String kspassword,
String keypassword, String type, String provider) throws Throwable {
KeyStore ks = buildKeyStoreFromFile(file, kspassword, type, provider);
readKeyStore(ks, alias, keypassword);
}
public PAdESSignerKeystore(KeyStore ks, String alias,
String keypassword) throws PDFASError {
try {
readKeyStore(ks, alias, keypassword);
} catch (Throwable e) {
throw new PDFASError(PDFASError.ERROR_SIG_FAILED_OPEN_KS, e);
}
}
public PAdESSignerKeystore(String file, String alias, String kspassword,
String keypassword, String type) throws PDFASError {
try {
// Load keystore with default security provider (IAIK)
loadKeystore(file, alias, kspassword, keypassword, type, null);
} catch (Throwable e) {
try {
// IAIK Provider seems to have problem with some PKCS12 files..
loadKeystore(file, alias, kspassword, keypassword, type,
fallBackProvider);
logger.warn("Failed to open Keystore with IAIK provider!");
} catch (Throwable e1) {
logger.error("Keystore IAIK provider error: ", e);
logger.error("Keystore " + fallBackProvider
+ " provider error: ", e1);
throw new PDFASError(PDFASError.ERROR_SIG_FAILED_OPEN_KS, e1);
}
}
}
public PAdESSignerKeystore(PrivateKey privKey, java.security.cert.Certificate cert) throws PDFASError {
if(cert == null) {
logger.error("PAdESSignerKeystore provided certificate is NULL");
throw new NullPointerException();
}
if(privKey == null) {
logger.error("PAdESSignerKeystore provided private Key is NULL");
throw new NullPointerException();
}
if(cert instanceof X509Certificate) {
this.cert = (X509Certificate)cert;
} else {
try {
this.cert = new X509Certificate(cert.getEncoded());
} catch (CertificateEncodingException e) {
throw new PDFASError(PDFASError.ERROR_INVALID_CERTIFICATE, e);
} catch (CertificateException e) {
throw new PDFASError(PDFASError.ERROR_INVALID_CERTIFICATE, e);
}
}
this.privKey = privKey;
}
public X509Certificate getCertificate(SignParameter parameter) {
return cert;
}
private void setMimeTypeAttrib(List attributes, String mimeType) {
String oidStr = "0.4.0.1733.2.1";
String name = "mime-type";
ObjectID mimeTypeOID = new ObjectID(oidStr, name);
Attribute mimeTypeAtt = new Attribute(mimeTypeOID,
new ASN1Object[] { new UTF8String(mimeType) });
attributes.add(mimeTypeAtt);
}
private void setContentTypeAttrib(List attributes) {
Attribute contentType = new Attribute(ObjectID.contentType,
new ASN1Object[] { ObjectID.cms_data });
attributes.add(contentType);
}
private void setSigningCertificateAttrib(List attributes,
X509Certificate signingCertificate) throws CertificateException,
NoSuchAlgorithmException, CodingException {
ObjectID id;
ASN1Object value = new SEQUENCE();
AlgorithmID[] algorithms = CertificateUtils
.getAlgorithmIDs(signingCertificate);
if (algorithms[1].equals(AlgorithmID.sha1)) {
id = ObjectID.signingCertificate;
value.addComponent(new ESSCertID(signingCertificate, true)
.toASN1Object());
} else {
id = ObjectID.signingCertificateV2;
value.addComponent(new ESSCertIDv2(algorithms[1],
signingCertificate, true).toASN1Object());
}
ASN1Object signingCert = new SEQUENCE();
signingCert.addComponent(value);
Attribute signingCertificateAttrib = new Attribute(id,
new ASN1Object[] { signingCert });
attributes.add(signingCertificateAttrib);
}
private void setSigningTimeAttrib(List attributes, Date date) {
Attribute signingTime = new Attribute(ObjectID.signingTime,
new ASN1Object[] { new ChoiceOfTime(date).toASN1Object() });
attributes.add(signingTime);
}
private void setAttributes(String mimeType,
X509Certificate signingCertificate, Date signingTime,
SignerInfo signerInfo) throws CertificateException,
NoSuchAlgorithmException, CodingException {
List attributes = new ArrayList();
setMimeTypeAttrib(attributes, mimeType);
setContentTypeAttrib(attributes);
setSigningCertificateAttrib(attributes, signingCertificate);
//setSigningTimeAttrib(attributes, signingTime);
Attribute[] attributeArray = attributes
.toArray(new Attribute[attributes.size()]);
signerInfo.setSignedAttributes(attributeArray);
}
public byte[] sign(byte[] input, int[] byteRange, SignParameter parameter,
RequestedSignature requestedSignature) throws PdfAsException {
try {
logger.info("Creating PAdES signature.");
requestedSignature.getStatus().getMetaInformations()
.put(ErrorConstants.STATUS_INFO_SIGDEVICE, SIGNATURE_DEVICE);
requestedSignature.getStatus().getMetaInformations()
.put(ErrorConstants.STATUS_INFO_SIGDEVICEVERSION, PdfAsFactory.getVersion());
IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(cert);
AlgorithmID[] algorithms = CertificateUtils.getAlgorithmIDs(cert);
SignerInfo signer1 = new SignerInfo(issuer, algorithms[1],
algorithms[0], privKey);
SignedData si = new SignedData(input, SignedData.EXPLICIT);
si.addCertificates(new Certificate[] { cert });
setAttributes("application/pdf", cert, new Date(), signer1);
si.addSignerInfo(signer1);
InputStream dataIs = si.getInputStream();
byte[] buf = new byte[1024];
@SuppressWarnings("unused")
int r;
while ((r = dataIs.read(buf)) > 0)
; // skip data
ContentInfo ci = new ContentInfo(si);
byte[] signature = ci.getEncoded();
VerifyResult verifyResult = SignatureUtils.verifySignature(
signature, input);
return signature;
} catch (NoSuchAlgorithmException e) {
throw new PdfAsSignatureException("error.pdf.sig.01", e);
} catch (iaik.cms.CMSException e) {
throw new PdfAsSignatureException("error.pdf.sig.01", e);
} catch (IOException e) {
throw new PdfAsSignatureException("error.pdf.sig.01", e);
} catch (CertificateException e) {
throw new PdfAsSignatureException("error.pdf.sig.01", e);
} catch (CodingException e) {
throw new PdfAsSignatureException("error.pdf.sig.01", e);
} catch (PDFASError e) {
throw new PdfAsSignatureException("error.pdf.sig.01", e);
}
}
public String getPDFSubFilter() {
return SUBFILTER_ETSI_CADES_DETACHED;
}
public String getPDFFilter() {
return FILTER_ADOBE_PPKLITE;
}
}