/*
* SignedProperties.java
* PROJECT: JDigiDoc
* DESCRIPTION: Digi Doc functions for creating
* and reading signed documents.
* AUTHOR: Veiko Sinivee, S|E|B IT Partner Estonia
*==================================================
* Copyright (C) AS Sertifitseerimiskeskus
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library 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
* Lesser General Public License for more details.
* GNU Lesser General Public Licence is available at
* http://www.gnu.org/copyleft/lesser.html
*==================================================
*/
package es.uji.security.crypto.openxades.digidoc;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import es.uji.security.crypto.openxades.digidoc.factory.CanonicalizationFactory;
import es.uji.security.crypto.openxades.digidoc.factory.FactoryManager;
import es.uji.security.crypto.openxades.digidoc.utils.ConvertUtils;
import es.uji.security.util.Base64;
/**
* Models the SignedProperties element of an XML-DSIG/ETSI Signature.
*
* @author Veiko Sinivee
* @version 1.0
*/
public class SignedProperties implements Serializable
{
/** signature object to which this belongs */
private Signature m_sig;
/** id attribute */
private String m_id;
/** target attribute */
private String m_target;
/** signing time measured by signers own computer */
private Date m_signingTime;
/** signers certs digest algorithm */
private String m_certDigestAlgorithm;
/** signers cert id */
private String m_certId;
/** signers certs digest data */
private byte[] m_certDigestValue;
/** signers certs issuer serial number */
private BigInteger m_certSerial;
/** signature production place */
private SignatureProductionPlace m_address;
/** claimed roles */
private ArrayList m_claimedRoles;
/** digest over the original bytes read from XML file */
private byte[] m_origDigest;
/**
* Creates new SignedProperties. Initializes everything to null
*
* @param sig
* parent signature
*/
public SignedProperties(Signature sig)
{
m_sig = sig;
m_id = null;
m_target = null;
m_signingTime = null;
m_certDigestAlgorithm = null;
m_certDigestValue = null;
m_certSerial = null;
m_claimedRoles = null;
m_address = null;
m_certId = null;
m_origDigest = null;
}
/**
* Creates new SignedProperties.
*
* @param sig
* parent signature
* @param id
* id attribute value
* @param target
* target attribute value
* @param signingTime
* signing timestamp
* @param certId
* signers cert id (in XML)
* @param certDigAlg
* signers cert digest algorithm id/uri
* @param digest
* signers cert digest value
* @param serial
* signers cert serial number
* @throws DigiDocException
* for validation errors
*/
public SignedProperties(Signature sig, String id, String target, Date signingTime,
String certId, String certDigAlg, byte[] digest, BigInteger serial)
throws DigiDocException
{
m_sig = sig;
setId(id);
setTarget(target);
setSigningTime(signingTime);
setCertId(certId);
setCertDigestAlgorithm(certDigAlg);
setCertDigestValue(digest);
setCertSerial(serial);
m_claimedRoles = null;
m_address = null;
m_origDigest = null;
}
/**
* Creates new SignedProperties with default values taken from signers certificate and signature
*
* @param sig
* Signature reference
* @param cert
* signers certificate
* @param claimedRoles
* signers claimed roles
* @param adr
* signers address
* @throws DigiDocException
* for validation errors
*/
public SignedProperties(Signature sig, X509Certificate cert, String[] claimedRoles,
SignatureProductionPlace adr) throws DigiDocException
{
m_sig = sig;
setId(sig.getId() + "-SignedProperties");
setTarget("#" + sig.getId());
setSigningTime(new Date());
setCertId(sig.getId() + "-CERTINFO");
setCertDigestAlgorithm(SignedDoc.SHA1_DIGEST_ALGORITHM);
try
{
setCertDigestValue(SignedDoc.digest(cert.getEncoded()));
}
catch (Exception ex)
{
DigiDocException.handleException(ex, DigiDocException.ERR_CALCULATE_DIGEST);
}
setCertSerial(cert.getSerialNumber());
if ((claimedRoles != null) && (claimedRoles.length > 0))
{
for (int i = 0; i < claimedRoles.length; i++)
addClaimedRole(claimedRoles[i]);
}
if (adr != null)
setSignatureProductionPlace(adr);
m_origDigest = null;
}
/**
* Accessor for id attribute
*
* @return value of id attribute
*/
public String getId()
{
return m_id;
}
/**
* Mutator for id attribute
*
* @param str
* new value for id attribute
* @throws DigiDocException
* for validation errors
*/
public void setId(String str) throws DigiDocException
{
DigiDocException ex = validateId(str);
if (ex != null)
throw ex;
m_id = str;
}
/**
* Accessor for origDigest attribute
*
* @return value of origDigest attribute
*/
public byte[] getOrigDigest()
{
return m_origDigest;
}
/**
* Mutator for origDigest attribute
*
* @param str
* new value for origDigest attribute
*/
public void setOrigDigest(byte[] data)
{
m_origDigest = data;
}
/**
* Helper method to validate an id
*
* @param str
* input data
* @return exception or null for ok
*/
private DigiDocException validateId(String str)
{
DigiDocException ex = null;
if (str == null)
ex = new DigiDocException(DigiDocException.ERR_SIGPROP_ID,
"Id must be in form: <signature-id>-SignedProperties", null);
return ex;
}
/**
* Accessor for target attribute
*
* @return value of target attribute
*/
public String getTarget()
{
return m_target;
}
/**
* Mutator for target attribute
*
* @param str
* new value for target attribute
* @throws DigiDocException
* for validation errors
*/
public void setTarget(String str) throws DigiDocException
{
DigiDocException ex = validateTarget(str);
if (ex != null)
throw ex;
m_target = str;
}
/**
* Helper method to validate a target
*
* @param str
* input data
* @return exception or null for ok
*/
private DigiDocException validateTarget(String str)
{
DigiDocException ex = null;
if (str == null && !m_sig.getSignedDoc().getVersion().equals(SignedDoc.VERSION_1_3))
ex = new DigiDocException(DigiDocException.ERR_SIGPROP_TARGET,
"Target must be in form: #<signature-id>", null);
return ex;
}
/**
* Accessor for certId attribute
*
* @return value of certId attribute
*/
public String getCertId()
{
return m_certId;
}
/**
* Mutator for certId attribute
*
* @param str
* new value for certId attribute
* @throws DigiDocException
* for validation errors
*/
public void setCertId(String str) throws DigiDocException
{
if (!m_sig.getSignedDoc().getVersion().equals(SignedDoc.VERSION_1_3))
{
DigiDocException ex = validateCertId(str);
if (ex != null)
throw ex;
}
m_certId = str;
}
/**
* Helper method to validate an certificate id
*
* @param str
* input data
* @return exception or null for ok
*/
private DigiDocException validateCertId(String str)
{
DigiDocException ex = null;
if (str == null)
ex = new DigiDocException(DigiDocException.ERR_SIGPROP_CERT_ID,
"Cert Id must be in form: <signature-id>-CERTINFO", null);
return ex;
}
/**
* Accessor for signatureProductionPlace attribute
*
* @return value of signatureProductionPlace attribute
*/
public SignatureProductionPlace getSignatureProductionPlace()
{
return m_address;
}
/**
* Mutator for signatureProductionPlace attribute
*
* @param str
* new value for signatureProductionPlace attribute
*/
public void setSignatureProductionPlace(SignatureProductionPlace adr) throws DigiDocException
{
m_address = adr;
}
/**
* Accessor for signingTime attribute
*
* @return value of signingTime attribute
*/
public Date getSigningTime()
{
return m_signingTime;
}
/**
* Mutator for signingTime attribute
*
* @param str
* new value for signingTime attribute
* @throws DigiDocException
* for validation errors
*/
public void setSigningTime(Date d) throws DigiDocException
{
DigiDocException ex = validateSigningTime(d);
if (ex != null)
throw ex;
m_signingTime = d;
}
/**
* Helper method to validate a signingTime
*
* @param str
* input data
* @return exception or null for ok
*/
private DigiDocException validateSigningTime(Date d)
{
DigiDocException ex = null;
if (d == null) // check the uri somehow ???
ex = new DigiDocException(DigiDocException.ERR_SIGNING_TIME,
"Singing time cannot be empty!", null);
return ex;
}
/**
* Accessor for certDigestAlgorithm attribute
*
* @return value of certDigestAlgorithm attribute
*/
public String getCertDigestAlgorithm()
{
return m_certDigestAlgorithm;
}
/**
* Mutator for certDigestAlgorithm attribute
*
* @param str
* new value for certDigestAlgorithm attribute
* @throws DigiDocException
* for validation errors
*/
public void setCertDigestAlgorithm(String str) throws DigiDocException
{
DigiDocException ex = validateCertDigestAlgorithm(str);
if (ex != null)
throw ex;
m_certDigestAlgorithm = str;
}
/**
* Helper method to validate a digest algorithm
*
* @param str
* input data
* @return exception or null for ok
*/
private DigiDocException validateCertDigestAlgorithm(String str)
{
DigiDocException ex = null;
if (str == null || !str.equals(SignedDoc.SHA1_DIGEST_ALGORITHM))
ex = new DigiDocException(DigiDocException.ERR_CERT_DIGEST_ALGORITHM,
"Currently supports only SHA1 digest algorithm", null);
return ex;
}
/**
* Accessor for certDigestValue attribute
*
* @return value of certDigestValue attribute
*/
public byte[] getCertDigestValue()
{
return m_certDigestValue;
}
/**
* Mutator for certDigestValue attribute
*
* @param data
* new value for certDigestValue attribute
* @throws DigiDocException
* for validation errors
*/
public void setCertDigestValue(byte[] data) throws DigiDocException
{
DigiDocException ex = validateCertDigestValue(data);
if (ex != null)
throw ex;
m_certDigestValue = data;
}
/**
* Helper method to validate a digest value
*
* @param data
* input data
* @return exception or null for ok
*/
private DigiDocException validateCertDigestValue(byte[] data)
{
DigiDocException ex = null;
if (data == null || data.length != SignedDoc.SHA1_DIGEST_LENGTH)
ex = new DigiDocException(DigiDocException.ERR_DIGEST_LENGTH,
"SHA1 digest data is allways 20 bytes of length", null);
return ex;
}
/**
* Accessor for certSerial attribute
*
* @return value of certSerial attribute
*/
public BigInteger getCertSerial()
{
return m_certSerial;
}
/**
* Mutator for certSerial attribute
*
* @param str
* new value for certSerial attribute
* @throws DigiDocException
* for validation errors
*/
public void setCertSerial(BigInteger i) throws DigiDocException
{
DigiDocException ex = validateCertSerial(i);
if (ex != null)
throw ex;
m_certSerial = i;
}
/**
* Helper method to validate a certSerial
*
* @param str
* input data
* @return exception or null for ok
*/
private DigiDocException validateCertSerial(BigInteger i)
{
DigiDocException ex = null;
if (i == null) // check the uri somehow ???
ex = new DigiDocException(DigiDocException.ERR_CERT_SERIAL,
"Certificates serial number cannot be empty!", null);
return ex;
}
/**
* Returns the count of claimedRole objects
*
* @return count of Reference objects
*/
public int countClaimedRoles()
{
return ((m_claimedRoles == null) ? 0 : m_claimedRoles.size());
}
/**
* Adds a new reference object
*
* @param ref
* Reference object to add
*/
public void addClaimedRole(String role)
{
if (m_claimedRoles == null)
m_claimedRoles = new ArrayList();
m_claimedRoles.add(role);
}
/**
* Returns the desired claimedRole object
*
* @param idx
* index of the claimedRole object
* @return desired claimedRole object
*/
public String getClaimedRole(int idx)
{
return (String) m_claimedRoles.get(idx);
}
/**
* Helper method to validate the whole SignedProperties object
*
* @return a possibly empty list of DigiDocException objects
*/
public ArrayList validate()
{
ArrayList errs = new ArrayList();
DigiDocException ex = validateId(m_id);
if (ex != null)
errs.add(ex);
ex = validateTarget(m_target);
if (ex != null)
errs.add(ex);
if (!m_sig.getSignedDoc().getVersion().equals(SignedDoc.VERSION_1_3))
{
ex = validateCertId(m_certId);
if (ex != null)
errs.add(ex);
}
ex = validateSigningTime(m_signingTime);
if (ex != null)
errs.add(ex);
ex = validateCertDigestAlgorithm(m_certDigestAlgorithm);
if (ex != null)
errs.add(ex);
ex = validateCertDigestValue(m_certDigestValue);
if (ex != null)
errs.add(ex);
ex = validateCertSerial(m_certSerial);
if (ex != null)
errs.add(ex);
// claimed roles
// and signature production place are optional
return errs;
}
/*
* private void debugWriteFile(String name, byte[] data) { try { String str =
* "C:\\veiko\\work\\sk\\JDigiDoc\\" + name; System.out.println("Writing debug file: " + str);
* java.io.FileOutputStream fos = new java.io.FileOutputStream(str); fos.write(data);
* fos.close(); } catch(Exception ex) { System.out.println("Error: " + ex);
* ex.printStackTrace(System.out); } }
*/
/**
* Calculates the digest of SignedProperties block
*
* @return SignedProperties block digest
*/
public byte[] calculateDigest() throws DigiDocException
{
if (m_origDigest == null)
{
CanonicalizationFactory canFac = FactoryManager.getCanonicalizationFactory();
byte[] tmp = canFac.canonicalize(toXML(), SignedDoc.CANONICALIZATION_METHOD_20010315);
// debugWriteFile("SigProp2.xml", tmp);
// System.out.println("SigProp2: " + tmp.length
// + " digest" + Base64Util.encode(SignedDoc.digest(tmp), 0));
return SignedDoc.digest(tmp);
}
else
return m_origDigest;
}
/**
* Converts the SignedProperties to XML form
*
* @return XML representation of SignedProperties
*/
public byte[] toXML() throws DigiDocException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try
{
// In version 1.3 we use xmlns atributes like specified in XAdES
if (m_sig.getSignedDoc().getVersion().equals(SignedDoc.VERSION_1_3))
{
bos.write(ConvertUtils.str2data("<SignedProperties xmlns=\""));
bos.write(ConvertUtils.str2data(SignedDoc.xmlns_etsi));
bos.write(ConvertUtils.str2data("\" Id=\""));
bos.write(ConvertUtils.str2data(m_id));
bos.write(ConvertUtils.str2data("\">\n"));
}
else
{ // in prior versions we used the wrong namespace
bos.write(ConvertUtils.str2data("<SignedProperties xmlns=\""));
bos.write(ConvertUtils.str2data(SignedDoc.xmlns_xmldsig));
bos.write(ConvertUtils.str2data("\" Id=\""));
bos.write(ConvertUtils.str2data(m_id));
bos.write(ConvertUtils.str2data("\" Target=\""));
bos.write(ConvertUtils.str2data(m_target));
bos.write(ConvertUtils.str2data("\">\n"));
}
bos.write(ConvertUtils.str2data("<SignedSignatureProperties>\n<SigningTime>"));
bos.write(ConvertUtils.str2data(ConvertUtils.date2string(m_signingTime, m_sig
.getSignedDoc())));
bos.write(ConvertUtils.str2data("</SigningTime>\n<SigningCertificate>\n"));
if (m_sig.getSignedDoc().getVersion().equals(SignedDoc.VERSION_1_3))
{
bos.write(ConvertUtils.str2data("<Cert>"));
}
else
{
bos.write(ConvertUtils.str2data("<Cert Id=\""));
bos.write(ConvertUtils.str2data(m_certId));
bos.write(ConvertUtils.str2data("\">"));
}
bos.write(ConvertUtils.str2data("\n<CertDigest>\n<DigestMethod xmlns=\"" + SignedDoc.xmlns_xmldsig + "\" Algorithm=\""));
bos.write(ConvertUtils.str2data(m_certDigestAlgorithm));
bos.write(ConvertUtils.str2data("\">\n</DigestMethod>\n<DigestValue xmlns=\"" + SignedDoc.xmlns_xmldsig + "\">"));
bos.write(ConvertUtils.str2data(Base64.encodeBytes(m_certDigestValue)));
bos.write(ConvertUtils.str2data("</DigestValue>\n</CertDigest>\n"));
// In version 1.3 we use correct <IssuerSerial> content
// e.g. subelements <X509IssuerName> and <X509SerialNumber>
if (m_sig.getSignedDoc().getVersion().equals(SignedDoc.VERSION_1_3))
{
bos.write(ConvertUtils.str2data("<IssuerSerial>"));
bos.write(ConvertUtils.str2data("\n<X509IssuerName xmlns=\""));
bos.write(ConvertUtils.str2data(SignedDoc.xmlns_xmldsig));
bos.write(ConvertUtils.str2data("\">"));
bos.write(ConvertUtils.str2data(m_sig.getKeyInfo().getSignersCertificate()
.getIssuerDN().getName()));
bos.write(ConvertUtils.str2data("</X509IssuerName>"));
bos.write(ConvertUtils.str2data("\n<X509SerialNumber xmlns=\""));
bos.write(ConvertUtils.str2data(SignedDoc.xmlns_xmldsig));
bos.write(ConvertUtils.str2data("\">"));
bos.write(ConvertUtils.str2data(m_certSerial.toString()));
bos.write(ConvertUtils.str2data("</X509SerialNumber>\n"));
bos.write(ConvertUtils.str2data("</IssuerSerial>"));
}
else
{ // in prior versions we used wrong <IssuerSerial> content
bos.write(ConvertUtils.str2data("<IssuerSerial>"));
bos.write(ConvertUtils.str2data(m_certSerial.toString()));
bos.write(ConvertUtils.str2data("</IssuerSerial>"));
}
bos.write(ConvertUtils.str2data("</Cert></SigningCertificate>\n"));
bos
.write(ConvertUtils
.str2data("<SignaturePolicyIdentifier>\n<SignaturePolicyImplied>\n</SignaturePolicyImplied>\n</SignaturePolicyIdentifier>"));
if (m_address != null)
{
bos.write(ConvertUtils.str2data("\n"));
bos.write(m_address.toXML());
}
if (countClaimedRoles() > 0)
{
if (m_address != null)
bos.write(ConvertUtils.str2data("\n"));
bos.write(ConvertUtils.str2data("<SignerRole>\n<ClaimedRoles>\n"));
for (int i = 0; i < countClaimedRoles(); i++)
{
bos.write(ConvertUtils.str2data("<ClaimedRole>"));
bos.write(ConvertUtils.str2data(getClaimedRole(i)));
bos.write(ConvertUtils.str2data("</ClaimedRole>\n"));
}
bos.write(ConvertUtils.str2data("</ClaimedRoles>\n</SignerRole>"));
}
bos.write(ConvertUtils.str2data("\n</SignedSignatureProperties>"));
bos.write(ConvertUtils
.str2data("\n<SignedDataObjectProperties>\n</SignedDataObjectProperties>"));
bos.write(ConvertUtils.str2data("\n</SignedProperties>"));
}
catch (IOException ex)
{
DigiDocException.handleException(ex, DigiDocException.ERR_XML_CONVERT);
}
return bos.toByteArray();
}
/**
* Returns the stringified form of SignedProperties
*
* @return SignedProperties string representation
*/
public String toString()
{
String str = null;
try
{
str = new String(toXML());
}
catch (Exception ex)
{
}
return str;
}
}