/*
* 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 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.security.ecc.interfaces.ECDSAParams;
import iaik.smime.ess.ESSCertID;
import iaik.smime.ess.ESSCertIDv2;
import iaik.x509.X509ExtensionException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECParameterSpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import at.buergerkarte.namespaces.securitylayer._1_2_3.CMSDataObjectRequiredMetaType;
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;
/**
* 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);
private SignedData signedData;
private SignerInfo signerInfo;
private byte[] signedDocument;
private String mimeType;
private AlgorithmID signatureAlgorithm;
private AlgorithmID digestAlgorithm;
private String signatureAlgorithmURI;
private String digestAlgorithmURI;
private ExcludedByteRangeType excludedByteRange;
public Signature(CMSDataObjectRequiredMetaType dataObject, String structure,
X509Certificate signingCertificate, Date signingTime, 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);
this.mimeType = dataObject.getMetaInfo().getMimeType();
setAttributes(this.mimeType, signingCertificate, signingTime);
}
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 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(CMSDataObjectRequiredMetaType 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;
}
private void setSignerInfo() throws SLCommandException, CMSException, CMSSignatureException {
try {
signedData.addSignerInfo(signerInfo);
} catch (NoSuchAlgorithmException e) {
if (e.getCause() instanceof CMSException) {
CMSException e2 = (CMSException) e.getCause();
if (e2.getCause() instanceof SignatureException)
{
SignatureException e3 = (SignatureException) e2.getCause();
if (e3.getCause() instanceof STALSignatureException) {
STALSignatureException e4 = (STALSignatureException) e3.getCause();
throw new SLCommandException(e4.getErrorCode());
}
}
throw e2;
}
throw new CMSSignatureException(e);
}
}
private void setAlgorithmIDs(X509Certificate signingCertificate, boolean useStrongHash) throws NoSuchAlgorithmException {
PublicKey publicKey = signingCertificate.getPublicKey();
String algorithm = publicKey.getAlgorithm();
AlgorithmMethodFactory amf = new AlgorithmMethodFactoryImpl(signingCertificate, useStrongHash);
signatureAlgorithmURI = amf.getSignatureAlgorithmURI();
digestAlgorithmURI = amf.getDigestAlgorithmURI();
if ("DSA".equals(algorithm)) {
signatureAlgorithm = AlgorithmID.dsaWithSHA1;
} else if ("RSA".equals(algorithm)) {
int keyLength = 0;
if (publicKey instanceof RSAPublicKey) {
keyLength = ((RSAPublicKey) publicKey).getModulus().bitLength();
}
if (useStrongHash && keyLength >= 2048) {
signatureAlgorithm = AlgorithmID.sha256WithRSAEncryption;
digestAlgorithm = AlgorithmID.sha256;
// } else if (useStrongHash) { // Cannot be used if not enabled in AlgorithmMethodFactoryImpl
// signatureAlgorithm = AlgorithmID.rsaSignatureWithRipemd160;
// digestAlgorithm = AlgorithmID.ripeMd160;
} else {
signatureAlgorithm = AlgorithmID.sha1WithRSAEncryption;
digestAlgorithm = AlgorithmID.sha1;
}
} else if (("EC".equals(algorithm)) || ("ECDSA".equals(algorithm))) {
int fieldSize = 0;
if (publicKey instanceof iaik.security.ecc.ecdsa.ECPublicKey) {
ECDSAParams params = ((iaik.security.ecc.ecdsa.ECPublicKey) publicKey).getParameter();
fieldSize = params.getG().getCurve().getField().getSize().bitLength();
} else if (publicKey instanceof ECPublicKey) {
ECParameterSpec params = ((ECPublicKey) publicKey).getParams();
fieldSize = params.getCurve().getField().getFieldSize();
}
if (useStrongHash && fieldSize >= 512) {
signatureAlgorithm = AlgorithmID.ecdsa_With_SHA512;
digestAlgorithm = AlgorithmID.sha512;
} else if (useStrongHash && fieldSize >= 256) {
signatureAlgorithm = AlgorithmID.ecdsa_With_SHA256;
digestAlgorithm = AlgorithmID.sha256;
} else if (useStrongHash) {
signatureAlgorithm = AlgorithmID.ecdsa_plain_With_RIPEMD160;
digestAlgorithm = AlgorithmID.ripeMd160;
} else {
signatureAlgorithm = AlgorithmID.ecdsa_With_SHA1;
digestAlgorithm = AlgorithmID.sha1;
}
} else {
throw new NoSuchAlgorithmException("Public key algorithm '" + algorithm
+ "' not supported.");
}
}
private HashDataInput getHashDataInput() {
return new CMSHashDataInput(signedDocument, mimeType);
}
public byte[] sign(STAL stal, String keyboxIdentifier) throws CMSException, CMSSignatureException, SLCommandException {
signedData.setSecurityProvider(new STALSecurityProvider(
stal, keyboxIdentifier, getHashDataInput(), this.excludedByteRange));
setSignerInfo();
ContentInfo contentInfo = new ContentInfo(signedData);
return contentInfo.getEncoded();
}
}