/*
* Copyright 2013 by Graz University of Technology, Austria
* MOCCA has been developed 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.bku.slcommands.impl.cms;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.xml.crypto.dsig.DigestMethod;
import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3._2000._09.xmldsig_.DigestMethodType;
import at.buergerkarte.namespaces.securitylayer._1_2_3.CMSDataObjectOptionalMetaType;
import at.buergerkarte.namespaces.securitylayer._1_2_3.CMSDataObjectRequiredMetaType;
import at.buergerkarte.namespaces.securitylayer._1_2_3.DigestAndRefType;
import at.buergerkarte.namespaces.securitylayer._1_2_3.ExcludedByteRangeType;
import at.gv.egiz.bku.slcommands.impl.xsect.AlgorithmMethodFactory;
import at.gv.egiz.bku.slcommands.impl.xsect.AlgorithmMethodFactoryImpl;
import at.gv.egiz.bku.slcommands.impl.xsect.STALSignatureException;
import at.gv.egiz.bku.slexceptions.SLCommandException;
import at.gv.egiz.bku.utils.urldereferencer.URLDereferencer;
import at.gv.egiz.stal.HashDataInput;
import at.gv.egiz.stal.STAL;
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.CMSException;
import iaik.cms.CMSSignatureException;
import iaik.cms.CertificateIdentifier;
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.X509ExtensionException;
/**
* This class represents a CMS-Signature as to be created by the
* security layer command CreateCMSSignatureRequest
.
*
* @author tkellner
*/
public class Signature {
public final static String ID_AA_ETS_MIMETYPE = "0.4.0.1733.2.1";
/**
* Logging facility.
*/
private final Logger log = LoggerFactory.getLogger(Signature.class);
protected SignedData signedData;
protected SignerInfo signerInfo;
protected byte[] signedDocument;
protected String mimeType;
protected AlgorithmID signatureAlgorithm;
protected AlgorithmID digestAlgorithm;
protected byte[] digestValue;
protected String signatureAlgorithmURI;
protected String digestAlgorithmURI;
protected ExcludedByteRangeType excludedByteRange;
private HashDataInput hashDataInput;
public Signature(CMSDataObjectOptionalMetaType dataObject, String structure,
X509Certificate signingCertificate, Date signingTime, URLDereferencer urlDereferencer,
boolean useStrongHash)
throws NoSuchAlgorithmException, CertificateEncodingException,
CertificateException, X509ExtensionException, InvalidParameterException,
CodingException, SLCommandException, IOException, CMSException {
int mode = structure.equalsIgnoreCase("enveloping") ? SignedData.IMPLICIT : SignedData.EXPLICIT;
if (dataObject.getContent() != null) {
byte[] dataToBeSigned = getContent(dataObject, urlDereferencer);
this.signedData = new SignedData(dataToBeSigned, mode);
if (dataObject.getMetaInfo() != null) {
this.mimeType = dataObject.getMetaInfo().getMimeType();
}
hashDataInput = new CMSHashDataInput(signedDocument, mimeType);
} else {
DigestAndRefType digestAndRef = dataObject.getDigestAndRef();
DigestMethodType digestMethod = digestAndRef.getDigestMethod();
hashDataInput = new ReferencedHashDataInput(dataObject.getMetaInfo().getMimeType(), urlDereferencer,
digestAndRef.getReference(), dataObject.getExcludedByteRange());
try {
digestAlgorithm = getAlgorithmID(digestMethod.getAlgorithm());
} catch (URISyntaxException e) {
//TODO: choose proper execption
throw new NoSuchAlgorithmException(e);
}
digestValue = digestAndRef.getDigestValue();
this.signedData = new SignedData(ObjectID.pkcs7_data);
}
setAlgorithmIDs(signingCertificate, useStrongHash);
createSignerInfo(signingCertificate);
setSignerCertificate(signingCertificate);
this.mimeType = dataObject.getMetaInfo().getMimeType();
setAttributes(this.mimeType, signingCertificate, signingTime);
}
public Signature(CMSDataObjectRequiredMetaType dataObject, String structure,
X509Certificate signingCertificate, URLDereferencer urlDereferencer,
boolean useStrongHash)
throws NoSuchAlgorithmException, CertificateEncodingException,
CertificateException, X509ExtensionException, InvalidParameterException,
CodingException, SLCommandException, IOException {
byte[] dataToBeSigned = getContent(dataObject, urlDereferencer);
int mode = structure.equalsIgnoreCase("enveloping") ? SignedData.IMPLICIT : SignedData.EXPLICIT;
this.signedData = new SignedData(dataToBeSigned, mode);
setAlgorithmIDs(signingCertificate, useStrongHash);
createSignerInfo(signingCertificate);
setSignerCertificate(signingCertificate);
setAttributes(signingCertificate);
}
private void createSignerInfo(X509Certificate signingCertificate) throws CertificateEncodingException, CertificateException {
iaik.x509.X509Certificate sigcert =
new iaik.x509.X509Certificate(signingCertificate.getEncoded());
CertificateIdentifier signerIdentifier =
new IssuerAndSerialNumber(sigcert);
PrivateKey privateKey = new STALPrivateKey(signatureAlgorithmURI, digestAlgorithmURI);
signerInfo = new SignerInfo(signerIdentifier, digestAlgorithm,
signatureAlgorithm, privateKey);
}
private void setSignerCertificate(X509Certificate signingCertificate) {
X509Certificate[] sigcerts = new X509Certificate[] { signingCertificate };
signedData.addCertificates(sigcerts);
}
private void setAttributes(String mimeType, X509Certificate signingCertificate, Date signingTime) throws CertificateException, NoSuchAlgorithmException, CodingException {
List attributes = new ArrayList();
setMimeTypeAttrib(attributes, mimeType);
setContentTypeAttrib(attributes);
setSigningCertificateAttrib(attributes, signingCertificate);
if (signingTime != null)
setSigningTimeAttrib(attributes, signingTime);
Attribute[] attributeArray = attributes.toArray(new Attribute[attributes.size()]);
signerInfo.setSignedAttributes(attributeArray);
}
private void setAttributes(X509Certificate signingCertificate) throws CertificateException, NoSuchAlgorithmException, CodingException {
List attributes = new ArrayList();
setContentTypeAttrib(attributes);
setSigningCertificateAttrib(attributes, signingCertificate);
Attribute[] attributeArray = attributes.toArray(new Attribute[attributes.size()]);
signerInfo.setSignedAttributes(attributeArray);
}
private void setMimeTypeAttrib(List attributes, String mimeType) {
String oidStr = ID_AA_ETS_MIMETYPE;
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();
if (digestAlgorithm.equals(AlgorithmID.sha1)) {
id = ObjectID.signingCertificate;
value.addComponent(new ESSCertID(signingCertificate, true).toASN1Object());
}
else {
id = ObjectID.signingCertificateV2;
value.addComponent(new ESSCertIDv2(digestAlgorithm, 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 byte[] getContent(CMSDataObjectOptionalMetaType dataObject, URLDereferencer urlDereferencer)
throws InvalidParameterException, SLCommandException, IOException {
byte[] data = dataObject.getContent().getBase64Content();
if (data == null) {
String reference = dataObject.getContent().getReference();
if (reference == null)
throw new SLCommandException(4003);
InputStream is = urlDereferencer.dereference(reference).getStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
for (int i = is.read(buffer); i > -1; i = is.read(buffer)) {
baos.write(buffer, 0, i);
}
data = baos.toByteArray();
is.close();
}
this.signedDocument = data.clone();
this.excludedByteRange = dataObject.getExcludedByteRange();
if (this.excludedByteRange == null)
return data;
int from = this.excludedByteRange.getFrom().intValue();
int to = this.excludedByteRange.getTo().intValue();
if (from > data.length || to > data.length || from > to)
throw new InvalidParameterException("ExcludedByteRange contains invalid data: [" +
from + "-" + to + "], Content length: " + data.length);
// Fill ExcludedByteRange with 0s for document to display in viewer
Arrays.fill(this.signedDocument, from, to+1, (byte)0);
// Remove ExcludedByteRange from data to be signed
byte[] first = null;
byte[] second = null;
if (from > 0)
first = Arrays.copyOfRange(data, 0, from);
if ((to + 1) < data.length)
second = Arrays.copyOfRange(data, to + 1, data.length);
data = ArrayUtils.addAll(first, second);
log.debug("ExcludedByteRange [" + from + "-" + to + "], Content length: " + data.length);
return data;
}
protected void setAlgorithmIDs(X509Certificate signingCertificate, boolean useStrongHash) throws NoSuchAlgorithmException {
AlgorithmMethodFactory amf = new AlgorithmMethodFactoryImpl(signingCertificate, useStrongHash);
signatureAlgorithmURI = amf.getSignatureAlgorithmURI();
signatureAlgorithm = amf.getSignatureAlgorithmID();
if (digestAlgorithm != null) {
if (AlgorithmID.sha1.equals(digestAlgorithm)) {
digestAlgorithmURI = DigestMethod.SHA1;
} else if (AlgorithmID.sha256.equals(digestAlgorithm)) {
digestAlgorithmURI = DigestMethod.SHA256;
} else if (AlgorithmID.sha512.equals(digestAlgorithm)) {
digestAlgorithmURI = DigestMethod.SHA512;
} else if (AlgorithmID.ripeMd160.equals(digestAlgorithm)) {
digestAlgorithmURI = DigestMethod.RIPEMD160;
} else {
throw new NoSuchAlgorithmException("Algorithm '" + digestAlgorithm + "' not supported.");
}
} else {
digestAlgorithmURI = amf.getDigestAlgorithmURI();
digestAlgorithm = amf.getDigestAlgorithmID();
}
}
public HashDataInput getHashDataInput() {
if (hashDataInput != null) {
return hashDataInput;
} else {
return new CMSHashDataInput(signedDocument, mimeType);
}
}
public byte[] sign(STAL stal, String keyboxIdentifier) throws CMSException, CMSSignatureException, SLCommandException {
STALSecurityProvider securityProvider = new STALSecurityProvider(stal, keyboxIdentifier, getHashDataInput(), this.excludedByteRange);
signedData.setSecurityProvider(securityProvider);
try {
signedData.addSignerInfo(signerInfo);
} catch (NoSuchAlgorithmException e) {
STALSignatureException stalSignatureException = securityProvider.getStalSignatureException();
if (stalSignatureException != null) {
throw new SLCommandException(stalSignatureException.getErrorCode());
}
throw new CMSSignatureException(e);
}
if (digestValue != null) {
try {
signedData.setMessageDigest(digestAlgorithm, digestValue);
} catch (NoSuchAlgorithmException e) {
throw new CMSSignatureException(e);
}
}
ContentInfo contentInfo = new ContentInfo(signedData);
return contentInfo.getEncoded();
}
protected AlgorithmID getAlgorithmID(String uri) throws URISyntaxException {
String oid = null;
URI urn = new URI(uri);
String scheme = urn.getScheme();
if ("URN".equalsIgnoreCase(scheme)) {
String schemeSpecificPart = urn.getSchemeSpecificPart().toLowerCase();
if (schemeSpecificPart.startsWith("oid:")) {
oid = schemeSpecificPart.substring(4, schemeSpecificPart.length());
}
}
return new AlgorithmID(new ObjectID(oid));
}
}