package at.knowcenter.wag.egov.egiz.sig.connectors.mocca; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import at.gv.egiz.pdfas.exceptions.ErrorCode; import at.gv.egiz.pdfas.framework.ConnectorParameters; import at.gv.egiz.pdfas.web.helper.SigningTimeHelper; import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; import at.knowcenter.wag.egov.egiz.exceptions.ConnectorException; import at.knowcenter.wag.egov.egiz.exceptions.PresentableException; import at.knowcenter.wag.egov.egiz.exceptions.SettingsException; import at.knowcenter.wag.egov.egiz.exceptions.SignatureException; import at.knowcenter.wag.egov.egiz.sig.SignatureData; import at.knowcenter.wag.egov.egiz.sig.SignatureObject; import at.knowcenter.wag.egov.egiz.sig.SignatureResponse; import at.knowcenter.wag.egov.egiz.sig.X509Cert; import at.knowcenter.wag.egov.egiz.sig.connectors.Connector; import at.knowcenter.wag.egov.egiz.sig.connectors.LocalConnector; import at.knowcenter.wag.egov.egiz.sig.connectors.TemplateReplaces; import at.knowcenter.wag.egov.egiz.sig.connectors.bku.BKUHelper; import at.knowcenter.wag.egov.egiz.sig.connectors.bku.BKUPostConnection; import at.knowcenter.wag.egov.egiz.sig.connectors.bku.SignSignatureObject; import at.knowcenter.wag.egov.egiz.sig.sigid.DetachedMOCIdFormatter; import at.knowcenter.wag.egov.egiz.sig.sigid.IdFormatter; import at.knowcenter.wag.egov.egiz.tools.CodingHelper; import at.knowcenter.wag.egov.egiz.tools.FileHelper; /** * Connector for MOCCA. * @author tknall */ public class LocRefDetachedMOCCAConnector implements Connector, LocalConnector { private static Log log = LogFactory.getLog(LocRefDetachedMOCCAConnector.class); /** * The connector parameters. */ protected ConnectorParameters params = null; /** * The environment of this connector containing templates. */ protected Environment environment = null; /** * Constructor that builds the configuration environment for this connector according to the * given profile. * @param connectorParameters The connectot parameters. * @throws ConnectorException Thrown in case of error. */ public LocRefDetachedMOCCAConnector(ConnectorParameters connectorParameters, String loc_ref_content) throws ConnectorException { this.params = connectorParameters; this.environment = new Environment(this.params.getProfileId(), loc_ref_content); } /** * Sends the request to the given URL. This method handles communication exceptions. * The actual send work is done by doPostRequestMultipart. * @see BKUPostConnection#doPostRequestMultipart(String, String, SignatureData) * @param url The URL to send the request to. * @param request_string The request XML. * @param data The data. * @return Returns the response properties containing among others the response XML. * @throws ConnectorException Thrown in case of an error. */ protected Properties sendRequest(String url, String request_string, SignatureData data) throws ConnectorException { try { Properties response_properties = BKUPostConnection.doPostRequestMultipart(url, request_string, data); return response_properties; } catch (Exception e) { ConnectorException se = new ConnectorException(320, e); throw se; } } /** * Starts a signature process. * @param data The data to be signed. * @return Returns the signature object containing the signed data. * @throws ConnectorException Thrown in case of an error. */ public SignSignatureObject doSign(SignatureData data) throws ConnectorException { log.debug("doSign:"); String sign_request_xml = prepareSignRequest(data); log.debug("sign_request_xml = " + sign_request_xml); String url = this.environment.getSignURL(); Properties response_properties = sendRequest(url, sign_request_xml, data); SignSignatureObject sso = analyzeSignResponse(response_properties); sso.response_properties = response_properties; log.debug("doSign finished."); return sso; } /** * Verification is not supported by MOCCA. Therefore this method always throws a * {@link ConnectorException} with error code {@link ErrorCode#SIGNATURE_VERIFICATION_NOT_SUPPORTED}. */ public SignatureResponse doVerify(SignatureData data, SignSignatureObject so) throws ConnectorException { throw new ConnectorException(ErrorCode.SIGNATURE_VERIFICATION_NOT_SUPPORTED, "Signature Verification is not supported by MOCCA."); } /** * This method analyzes a signature response of the signature device. * @param response_properties The response elements of the signature device. * @return The parsed signed signature object. * @throws ConnectorException Thrown in case of an error. */ public SignSignatureObject analyzeSignResponse(Properties response_properties) throws ConnectorException { log.debug("analyzeSignResponse:"); String response_string = response_properties.getProperty(BKUPostConnection.RESPONSE_STRING_KEY); BKUHelper.checkResponseForError(response_string); SignSignatureObject so = this.parseCreateXMLResponse(response_string, new DetachedMOCIdFormatter()); so.response_properties = response_properties; log.debug("analyzeSignResponse finished."); return so; } /** * This method parses the signature creation response of the signature device. * @param xmlResponse The response string. * @return Returns the parsed signature object holding the data. * @see SignatureObject * @see CodingHelper * @see X509Cert */ public SignSignatureObject parseCreateXMLResponse(String xmlResponse, IdFormatter id_formatter) throws ConnectorException { Pattern iss_nam_p_s = Pattern.compile("<[\\w]*:?X509IssuerName>"); Pattern iss_nam_p_e = Pattern.compile(""); Pattern sig_tim_p_s = Pattern.compile("<[\\w]*:?SigningTime>"); Pattern sig_tim_p_e = Pattern.compile(""); Pattern ser_num_p_s = Pattern.compile("<[\\w]*:?X509SerialNumber>"); Pattern ser_num_p_e = Pattern.compile(""); Pattern sig_cer_p_s = Pattern.compile("<[\\w]*:?X509Certificate>"); Pattern sig_cer_p_e = Pattern.compile(""); Matcher iss_nam_m_s = iss_nam_p_s.matcher(xmlResponse); Matcher iss_nam_m_e = iss_nam_p_e.matcher(xmlResponse); Matcher sig_tim_m_s = sig_tim_p_s.matcher(xmlResponse); Matcher sig_tim_m_e = sig_tim_p_e.matcher(xmlResponse); Matcher ser_num_m_s = ser_num_p_s.matcher(xmlResponse); Matcher ser_num_m_e = ser_num_p_e.matcher(xmlResponse); Matcher sig_cer_m_s = sig_cer_p_s.matcher(xmlResponse); Matcher sig_cer_m_e = sig_cer_p_e.matcher(xmlResponse); // SignatureValue String sig_val = null; Matcher signatureValueMatcher = Pattern.compile("<(\\w+:)?SignatureValue( Id=\"[\\w-]+\")?>\\s*(.*)\\s*").matcher(xmlResponse); if (signatureValueMatcher.find()) { sig_val = signatureValueMatcher.group(3); } log.debug("sig_val = " + sig_val); // X509IssuerName String iss_nam = null; if (iss_nam_m_s.find() && iss_nam_m_e.find()) { iss_nam = xmlResponse.substring(iss_nam_m_s.end(), iss_nam_m_e.start()); } log.debug("iss_nam = " + iss_nam); // X509SerialNumber String ser_num = null; if (ser_num_m_s.find() && ser_num_m_e.find()) { ser_num = BKUHelper.removeAllWhitespace(xmlResponse.substring(ser_num_m_s.end(), ser_num_m_e.start())); } log.debug("ser_num = " + ser_num); // SigningTime String sig_tim = null; if (sig_tim_m_s.find() && sig_tim_m_e.find()) { sig_tim = xmlResponse.substring(sig_tim_m_s.end(), sig_tim_m_e.start()); } log.debug("sig_tim = " + sig_tim); // X509Certificate X509Certificate cert = null; if (sig_cer_m_s.find() && sig_cer_m_e.find()) { String sig_cer = BKUHelper.removeAllWhitespace(xmlResponse.substring(sig_cer_m_s.end(), sig_cer_m_e.start())); try { byte[] der = CodingHelper.decodeBase64(sig_cer); ByteArrayInputStream bais = new ByteArrayInputStream(der); CertificateFactory cf = CertificateFactory.getInstance("X.509"); cert = (X509Certificate) cf.generateCertificate(bais); bais.close(); } catch (UnsupportedEncodingException e) { throw new ConnectorException(300, e); } catch (CertificateException e) { throw new ConnectorException(300, e); } catch (IOException e) { throw new ConnectorException(300, e); } } log.debug("X509Certificate = " + cert); if (log.isDebugEnabled()) { String cert_iss = cert.getIssuerDN().getName(); log.debug("certificate's issuer = " + cert_iss); log.debug("response's issuer = " + iss_nam); log.debug("issuer matches = " + cert_iss.equals(iss_nam)); log.debug("ser number matches = " + cert.getSerialNumber().toString().equals(ser_num)); } // extract Signature Id's String[] ids = extractIds(xmlResponse); String final_ids = id_formatter.formatIds(ids); SignSignatureObject so = new SignSignatureObject(); so.date = sig_tim; so.issuer = iss_nam; so.signatureValue = sig_val; so.x509Certificate = cert; so.id = final_ids; return so; } /** * Extraction of the id attributes from the xml response. * @param xmlResponse The xml response. * @return The parsed id attributes. */ public final static String[] extractIds(String xmlResponse) { return new String[] { extractId(xmlResponse) }; } /** * There is only one special common part of all id attributes of this connector that has to be * stored. This method returns that single part. * @param xmlResponse The xml response. * @return The parsed common part of all id attributes. */ private final static String extractId(String xmlResponse) { final Pattern ID_PATTERN = Pattern.compile("Id\\s*=\\s*\"\\s*Signature-([\\p{XDigit}]+)-\\d+\\s*\""); Matcher matcher = ID_PATTERN.matcher(xmlResponse); if (matcher.find() && matcher.groupCount() > 0) { return matcher.group(1); } return null; } /** * Verification is not supported by MOCCA. Therefore this method always throws a * {@link ConnectorException} with error code {@link ErrorCode#SIGNATURE_VERIFICATION_NOT_SUPPORTED}. */ public SignatureResponse analyzeVerifyResponse(Properties response_properties) throws ConnectorException { throw new ConnectorException(ErrorCode.SIGNATURE_VERIFICATION_NOT_SUPPORTED, "Signature Verification is not supported by MOCCA."); } /** * Prepares the signature request xml to be sent using the sign request template. * @param data The signature data. * @return Returns the sign request xml to be sent. * @throws ConnectorException Thrown in case of an error. */ public String prepareSignRequest(SignatureData data) throws ConnectorException { log.debug("prepareSignRequestDetached:"); String sign_request_template = this.environment.getSignRequestTemplate(); String sign_keybox_identifier = this.environment.getSignKeyboxIdentifier(); String mime_type = data.getMimeType(); String loc_ref_content = this.environment.getLocRefContent(); if (log.isDebugEnabled()) { log.debug("sign keybox identifier = " + sign_keybox_identifier); log.debug("mime type = " + mime_type); log.debug("loc_ref_content = " + loc_ref_content); } String sign_request_xml = sign_request_template.replaceFirst(TemplateReplaces.KEYBOX_IDENTIFIER_REPLACE, sign_keybox_identifier); sign_request_xml = sign_request_xml.replaceFirst(TemplateReplaces.MIME_TYPE_REPLACE, mime_type); sign_request_xml = sign_request_xml.replaceFirst(TemplateReplaces.LOC_REF_CONTENT_REPLACE, loc_ref_content); log.debug("sign_request_xml = " + sign_request_xml); log.debug("prepareSignRequestDetached finished."); return sign_request_xml; } /** * Verification is not supported by MOCCA. Therefore this method always throws a * {@link ConnectorException} with error code {@link ErrorCode#SIGNATURE_VERIFICATION_NOT_SUPPORTED}. */ public String prepareVerifyRequest(SignatureData data, SignSignatureObject so) throws ConnectorException { throw new ConnectorException(ErrorCode.SIGNATURE_VERIFICATION_NOT_SUPPORTED, "Signature Verification is not supported by MOCCA."); } /** * Prepares the xml content of a signature creation request including the link to the signature data. * @param data The signature data. * @param so The signature object containing the signature information. * @return Returns the xml content. * @throws ConnectorException Thrown in case of an error. */ public String prepareXMLContent(SignatureData data, SignSignatureObject so) throws ConnectorException { log.debug("prepareXMLContent:"); try { String verify_template = this.environment.getVerifyTemplate(); String ids_string = so.getSigID(); String sigId = this.parseSigId(ids_string); X509Certificate cert = so.getX509Certificate(); String cert_alg = this.environment.getCertAlgEcdsa(); if (cert.getPublicKey().getAlgorithm().indexOf("RSA") >= 0) { cert_alg = this.environment.getCertAlgRsa(); } // cert alg replace String verify_xml = verify_template.replaceFirst(TemplateReplaces.CERT_ALG_REPLACE, cert_alg); // data digest replace byte[] data_value_hash = CodingHelper.buildDigest(data.getDataSource()); String object_data_hash = CodingHelper.encodeBase64(data_value_hash); // template replacements verify_xml = verify_xml.replaceFirst(TemplateReplaces.DIGEST_VALUE_SIGNED_DATA_REPLACE, object_data_hash); verify_xml = verify_xml.replaceFirst(TemplateReplaces.SIGNATURE_VALUE_REPLACE, so.getSignatureValue()); // X.509 Certificate replace byte[] der = cert.getEncoded(); byte[] cert_hash = CodingHelper.buildDigest(der); String certDigest = CodingHelper.encodeBase64(cert_hash); String x509_cert_string = CodingHelper.encodeBase64(der); verify_xml = verify_xml.replaceFirst(TemplateReplaces.X509_CERTIFICATE_REPLACE, x509_cert_string); // Qualified Properties replaces verify_xml = verify_xml.replaceAll(TemplateReplaces.SIG_ID_REPLACE, sigId); verify_xml = verify_xml.replaceFirst(TemplateReplaces.SIGNING_TIME_REPLACE, so.getDate()); verify_xml = verify_xml.replaceFirst(TemplateReplaces.DIGEST_VALUE_CERTIFICATE_REPLACE, certDigest); verify_xml = verify_xml.replaceFirst(TemplateReplaces.X509_ISSUER_NAME_REPLACE, so.getIssuer()); verify_xml = verify_xml.replaceFirst(TemplateReplaces.X509_SERIAL_NUMBER_REPLACE, so.getSerialNumber()); // SigDataRefReplace already done above verify_xml = verify_xml.replaceFirst(TemplateReplaces.MIME_TYPE_REPLACE, data.getMimeType()); // Signed Properties hash Pattern spPattern = Pattern.compile("(<(\\w+:)?SignedProperties.*>.*)"); Matcher matcher = spPattern.matcher(verify_xml); if (matcher.find()) { log.debug("SignedProperties found."); String string_to_be_hashed = matcher.group(1); log.debug("SignedProperties string to be hashed: " + string_to_be_hashed); final byte[] bytes_to_be_hashed = string_to_be_hashed.getBytes("UTF-8"); byte[] sig_prop_code = CodingHelper.buildDigest(bytes_to_be_hashed); String sig_prop_hash = CodingHelper.encodeBase64(sig_prop_code); verify_xml = verify_xml.replaceFirst(TemplateReplaces.DIGEST_VALUE_SIGNED_PROPERTIES_REPLACE, sig_prop_hash); } log.debug("prepareXMLContent finished."); return verify_xml; } catch (Exception e) { log.debug(e); throw new ConnectorException(310, e); } } /** * Holds environment configuration information like templates. * @author wprinz */ public static class Environment { /** * The configuration key of the sign keybox identifier. */ protected static final String SIGN_KEYBOX_IDENTIFIER_KEY = "moc.sign.KeyboxIdentifier"; /** * The configuration key of the sign request template. */ protected static final String SIGN_REQUEST_TEMPLATE_KEY = "moc.sign.request.detached"; /** * The configuration key of the sign URL. */ protected static final String SIGN_URL_KEY = "moc.sign.url"; /** * BKU template file prefix */ protected static final String TEMPLATE_FILE_PREFIX = "./templates/moc."; /** * signing file template sufix */ protected static final String SIGN_TEMPLATE_FILE_SUFIX = ".sign.request.xml"; /** * verifing template file sufix */ /* signature verification is not supported by mocca protected static final String VERIFY_REQUEST_TEMPLATE_FILE_SUFIX = ".verify.request.xml"; */ /** * verifing file template key sufix */ protected static final String VERIFY_TEMPLATE_SUFIX = ".verify.template.xml"; /** * The configuration key of the verify request template. */ /* signature verification is not supported by mocca protected static final String VERIFY_REQUEST_TEMPLATE_KEY = "moc.verify.request.detached"; */ /** * The configuration key of the verify template. */ protected static final String VERIFY_TEMPLATE_KEY = "moc.verify.template.detached"; /** * The configuration key of the verify URL. */ /* signature verification is not supported by mocca protected static final String xxxVERIFY_URL_KEY = "moc.verify.url"; */ /** * The configuration key for the ECDSA cert alg property. */ protected static final String ECDSA_CERT_ALG_KEY = "cert.alg.ecdsa"; /** * The configuration key for the RSA cert alg property. */ protected static final String RSA_CERT_ALG_KEY = "cert.alg.rsa"; protected String profile = null; protected String loc_ref_content = null; protected String sign_keybox_identifier = null; protected String sign_request_template = null; protected String sign_url = null; /* signature verification is not supported by mocca protected String verify_request_template = null; */ protected String verify_template = null; /* signature verification is not supported by mocca protected String verify_url = null; */ protected String cert_alg_ecdsa = null; protected String cert_alg_rsa = null; /** * Initializes the environment with a given profile. * @param profile The configuration profile. * @throws ConnectorException Thrown in case of an error. */ public Environment(String profile, String loc_ref_content) throws ConnectorException { this.profile = profile; this.loc_ref_content = loc_ref_content; SettingsReader settings = null; try { settings = SettingsReader.getInstance(); } catch (SettingsException e) { throw new ConnectorException(300, e); } this.sign_keybox_identifier = getConnectorValueFromProfile(settings, profile, SIGN_KEYBOX_IDENTIFIER_KEY); // SIGN REQUEST // try specific file String sign_request_filename = TEMPLATE_FILE_PREFIX + settings.getValueFromKey("default.moc.algorithm.id") + SIGN_TEMPLATE_FILE_SUFIX; log.debug("Trying to load specific sign request file " + sign_request_filename); this.sign_request_template = FileHelper.readFromFile(SettingsReader.relocateFile(sign_request_filename)); // try default request file if (this.sign_request_template == null) { sign_request_filename = getConnectorValueFromProfile(settings, profile, SIGN_REQUEST_TEMPLATE_KEY); log.debug("Specific file not found. Trying default sign request file " + sign_request_filename); this.sign_request_template = FileHelper.readFromFile(SettingsReader.relocateFile(sign_request_filename)); } // request file is needed !!! if (this.sign_request_template == null) { throw new ConnectorException(300, "Can not read the create xml request template"); } this.sign_url = getConnectorValueFromProfile(settings, profile, SIGN_URL_KEY); // VERIFY REQUEST /* signature verification is not supported by mocca // try specific file String verify_request_filename = TEMPLATE_FILE_PREFIX + settings.getValueFromKey("default.moc.algorithm.id") + VERIFY_REQUEST_TEMPLATE_FILE_SUFIX; log.debug("Trying to load specific verify request file " + verify_request_filename); this.verify_request_template = FileHelper.readFromFile(SettingsReader.relocateFile(verify_request_filename)); // try default request file if (this.verify_request_template == null) { verify_request_filename = getConnectorValueFromProfile(settings, profile, VERIFY_REQUEST_TEMPLATE_KEY); log.debug("Specific file not found. Trying default verify request file " + verify_request_filename); this.verify_request_template = FileHelper.readFromFile(SettingsReader.relocateFile(verify_request_filename)); } // request file is needed !!! if (this.verify_request_template == null) { throw new ConnectorException(ErrorCode.SETTING_NOT_FOUND, "Can not read the verify xml request template"); } */ // load template file // try specific file String verify_filename = TEMPLATE_FILE_PREFIX + settings.getValueFromKey("default.moc.algorithm.id") + VERIFY_TEMPLATE_SUFIX; log.debug("Trying to load specific signature template file " + verify_filename); this.verify_template = FileHelper.readFromFile(SettingsReader.relocateFile(verify_filename)); // try default signature template file if (this.verify_template == null) { verify_filename = getConnectorValueFromProfile(settings, profile, VERIFY_TEMPLATE_KEY); log.debug("Specific signature template file not found. Trying default signature template file " + verify_filename); this.verify_template = FileHelper.readFromFile(SettingsReader.relocateFile(verify_filename)); } // signature template is needed !!! if (this.verify_template == null) { throw new ConnectorException(ErrorCode.SETTING_NOT_FOUND, "Can not read the verify template"); } /* signature verification is not supported by mocca this.verify_url = getConnectorValueFromProfile(settings, profile, VERIFY_URL_KEY); */ this.cert_alg_ecdsa = settings.getValueFromKey(ECDSA_CERT_ALG_KEY); this.cert_alg_rsa = settings.getValueFromKey(RSA_CERT_ALG_KEY); } /** * Returns the profile name. * @return The profile name. */ public String getProfile() { return this.profile; } /** * Returns the LocRef content. * * @return Returns the LocRef content. */ public String getLocRefContent() { return this.loc_ref_content; } /** * Returns the sign keybox identifier. * * @return Returns the sign keybox identifier. */ public String getSignKeyboxIdentifier() { return this.sign_keybox_identifier; } /** * Returns the sign request template. * * @return Returns the sign request template. */ public String getSignRequestTemplate() { return this.sign_request_template; } /** * Returns the sign URL. * * @return Returns the sign URL. */ public String getSignURL() { return this.sign_url; } /** * Returns the verify request template. * * @return Returns the verify request template. */ /* signature verification is not supported by mocca public String getVerifyRequestTemplate() { return this.verify_request_template; } */ /** * Returns the verify template. * * @return Returns the verify template. */ public String getVerifyTemplate() { return this.verify_template; } /** * Returns the verify URL. * * @return Returns the verify URL. */ /* signature verification is not supported by mocca public String getVerifyURL() { return this.verify_url; } */ /** * Returns the ecdsa cert alg property. * * @return Returns the ecdsa cert alg property. */ public String getCertAlgEcdsa() { return this.cert_alg_ecdsa; } /** * Returns the rsa cert alg property. * * @return Returns the rsa cert alg property. */ public String getCertAlgRsa() { return this.cert_alg_rsa; } /** * Reads the configuration entry given by the key, first from the given * profile, if not found from the defaults. * * @param settings * The settings. * @param profile * The profile. * @param key * The configuration key. * @return Returns the configuration entry. */ public static String getConnectorValueFromProfile(SettingsReader settings, String profile, String key) { String value = settings.getValueFromKey("sig_obj." + profile + "." + key); //$NON-NLS-2$ if (value == null) { value = settings.getValueFromKey(key); } return value; } } /** * Parses the common part for all id attributes from a given signature parameter string. * @param sigIdString The given signature parameter string. * @return The common part of all id attributes. */ protected String parseSigId(String sigIdString) { int pos = sigIdString.indexOf("@"); String result = null; if (pos != -1) { result = sigIdString.substring(pos+1).trim(); } return result; } }