/* * Copyright 2008 Federal Chancellery Austria and * Graz University of Technology * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package at.gv.egiz.idlink; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.List; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.PropertyException; import javax.xml.bind.Unmarshaller; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMResult; import org.w3._2000._09.xmldsig_.KeyValueType; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import at.buergerkarte.namespaces.personenbindung._20020506_.CompressedIdentityLinkType; import at.gv.e_government.reference.namespace.persondata._20020228_.AbstractPersonType; import at.gv.e_government.reference.namespace.persondata._20020228_.IdentificationType; import at.gv.e_government.reference.namespace.persondata._20020228_.PersonNameType; import at.gv.e_government.reference.namespace.persondata._20020228_.PhysicalPersonType; import at.gv.e_government.reference.namespace.persondata._20020228_.IdentificationType.Value; import at.gv.e_government.reference.namespace.persondata._20020228_.PersonNameType.FamilyName; import at.gv.egiz.idlink.ans1.CitizenPublicKey; import at.gv.egiz.idlink.ans1.IdentityLink; import at.gv.egiz.idlink.ans1.PersonData; import at.gv.egiz.idlink.ans1.PhysicalPersonData; import at.gv.egiz.xmldsig.KeyTypeNotSupportedException; import at.gv.egiz.xmldsig.KeyValueFactory; public class CompressedIdentityLinkFactory { /** * The instance returned by {@link #getInstance()}. */ private static CompressedIdentityLinkFactory instance; /** * The JAXBContext. */ private static JAXBContext jaxbContext; /** * The KeyValueFactory. */ private static KeyValueFactory keyValueFactory; /** * Get an instance of this CompressedIdentityLinkFactory. * * @return an instance of this CompressedIdentityLinkFactory */ public synchronized static CompressedIdentityLinkFactory getInstance() { if (instance == null) { instance = new CompressedIdentityLinkFactory(); } return instance; } /** * Private constructor. */ private CompressedIdentityLinkFactory() { keyValueFactory = new KeyValueFactory(); StringBuffer packageNames = new StringBuffer(); packageNames.append(at.gv.e_government.reference.namespace.persondata._20020228_.ObjectFactory.class.getPackage().getName()); packageNames.append(":"); packageNames.append(org.w3._2000._09.xmldsig_.ObjectFactory.class.getPackage().getName()); packageNames.append(":"); packageNames.append(org.w3._2001._04.xmldsig_more_.ObjectFactory.class.getPackage().getName()); packageNames.append(":"); packageNames.append(at.buergerkarte.namespaces.personenbindung._20020506_.ObjectFactory.class.getPackage().getName()); try { jaxbContext = JAXBContext.newInstance(packageNames.toString()); } catch (JAXBException e) { // we should not get an JAXBException initializing the JAXBContext throw new RuntimeException(e); } } public IdentityLink createIdLink(CompressedIdentityLinkType compressedIdentityLinkType) { // IssuerTemplate String issuerTemplate = compressedIdentityLinkType.getIssuerTemplate(); // AssertionId String assertionID = compressedIdentityLinkType.getAssertionID(); // IssueInstant String issueInstant = compressedIdentityLinkType.getIssueInstant(); AbstractPersonType personDataType = compressedIdentityLinkType.getPersonData(); String baseId = null; List identifications = personDataType.getIdentification(); for (IdentificationType identificationType : identifications) { String type = identificationType.getType(); if ("urn:publicid:gv.at:baseid".equals(type)) { baseId = identificationType.getValue().getValue(); } } String givenName = null; String familyName = null; String dateOfBirth = null; if (personDataType instanceof PhysicalPersonType) { PhysicalPersonType physicalPersonType = (PhysicalPersonType) personDataType; PersonNameType name = physicalPersonType.getName(); List givenNames = name.getGivenName(); if (!givenNames.isEmpty()) { givenName = givenNames.get(0); } List familyNames = name.getFamilyName(); if (!familyNames.isEmpty()) { familyName = familyNames.get(0).getValue(); } dateOfBirth = physicalPersonType.getDateOfBirth(); } PhysicalPersonData physicalPersonData = new PhysicalPersonData(baseId, givenName, familyName, dateOfBirth); PersonData personData = new PersonData(physicalPersonData); int numKeys = compressedIdentityLinkType.getCitizenPublicKey().size(); CitizenPublicKey[] citizenPublicKeys = new CitizenPublicKey[numKeys]; for (int i = 0; i < numKeys;) { citizenPublicKeys[i] = new CitizenPublicKey(++i); } byte[] signatureValue = compressedIdentityLinkType.getSignatureValue(); byte[] referenceDigest = compressedIdentityLinkType.getReferenceDigest(); byte[] referenceManifestDigest = compressedIdentityLinkType.getReferenceManifestDigest(); byte[] manifestReferenceDigest = compressedIdentityLinkType.getManifestReferenceDigest(); IdentityLink idLink = new IdentityLink(issuerTemplate, assertionID, issueInstant, personData, citizenPublicKeys, signatureValue); idLink.setReferenceDigest(referenceDigest); idLink.setReferenceManifestDigest(referenceManifestDigest); idLink.setManifestReferenceDigest(manifestReferenceDigest); return idLink; } /** * Creates a new CompressedIdentityLink element from the given * ASN.1 representation of an idLink. * * @param idLink * the ASN.1 representation of an IdentityLink * @param certificates * a list of {@link X509Certificate}s containing the corresponding * public keys * @param domainId TODO * @return a new CompressedIdentityLink element * * @throws NullPointerException * if idLink or certificates is * null * @throws IllegalArgumentException * if idLink references certificates not in the range * of the certificates list */ public JAXBElement createCompressedIdentityLink( at.gv.egiz.idlink.ans1.IdentityLink idLink, List certificates, String domainId) { at.gv.e_government.reference.namespace.persondata._20020228_.ObjectFactory prFactory = new at.gv.e_government.reference.namespace.persondata._20020228_.ObjectFactory(); at.buergerkarte.namespaces.personenbindung._20020506_.ObjectFactory pbFactory = new at.buergerkarte.namespaces.personenbindung._20020506_.ObjectFactory(); org.w3._2000._09.xmldsig_.ObjectFactory dsFactory = new org.w3._2000._09.xmldsig_.ObjectFactory(); // PersonData PhysicalPersonData __physicalPersonData = idLink.getPersonData() .getPhysicalPerson(); Value identificationTypeValue = prFactory.createIdentificationTypeValue(); identificationTypeValue.setValue(__physicalPersonData.getBaseId()); IdentificationType identificationType = prFactory .createIdentificationType(); identificationType.setValue(identificationTypeValue); if (domainId != null) { identificationType.setType(domainId); } else { identificationType.setType("urn:publicid:gv.at:baseid"); } PersonNameType personNameType = prFactory.createPersonNameType(); FamilyName personNameTypeFamilyName = prFactory .createPersonNameTypeFamilyName(); personNameTypeFamilyName.setValue(__physicalPersonData.getFamilyName()); personNameType.getFamilyName().add(personNameTypeFamilyName); personNameType.getGivenName().add(__physicalPersonData.getGivenName()); PhysicalPersonType physicalPersonType = prFactory .createPhysicalPersonType(); physicalPersonType.getIdentification().add(identificationType); physicalPersonType.setName(personNameType); physicalPersonType.setDateOfBirth(__physicalPersonData.getDateOfBirth()); // CompressedIdentityLink CompressedIdentityLinkType compressedIdentityLinkType = pbFactory .createCompressedIdentityLinkType(); compressedIdentityLinkType.setIssuerTemplate(idLink.getIssuerTemplate()); compressedIdentityLinkType.setAssertionID(idLink.getAssertionID()); compressedIdentityLinkType.setIssueInstant(idLink.getIssueInstant()); compressedIdentityLinkType.setPersonData(physicalPersonType); // CitizenPublicKey CitizenPublicKey[] __citizenPublicKeys = idLink.getCitizenPublicKeys(); for (CitizenPublicKey __citizenPublicKey : __citizenPublicKeys) { X509Certificate certificate = certificates.get(__citizenPublicKey.getOnToken()); PublicKey publicKey = certificate.getPublicKey(); JAXBElement keyValue; try { keyValue = keyValueFactory.createKeyValue(publicKey); } catch (KeyTypeNotSupportedException e) { // TODO: handle exception properly throw new RuntimeException(e); } KeyValueType keyValueType = dsFactory.createKeyValueType(); keyValueType.getContent().add(keyValue); compressedIdentityLinkType.getCitizenPublicKey().add(keyValueType); } compressedIdentityLinkType.setSignatureValue(idLink.getSignatureValue()); compressedIdentityLinkType.setReferenceDigest(idLink.getReferenceDigest()); compressedIdentityLinkType.setReferenceManifestDigest(idLink .getReferenceManifestDigest()); compressedIdentityLinkType.setManifestReferenceDigest(idLink .getManifestReferenceDigest()); JAXBElement compressedIdentityLink = pbFactory .createCompressedIdentityLink(compressedIdentityLinkType); return compressedIdentityLink; } /** * Marshall the given compressedIdentityLink into a DOM document * with the given Nodes as parent and nextSibling * nodes. * * @param compressedIdentityLink * the CompressedIdentityLink element * @param parent * the parent node * @param nextSibling * the next sibling node (may be null) * @param applyWorkarounds * apply workarounds as spefiyed by * {@link #applyWorkarounds(Element, int)} * * @throws JAXBException * if an unexpected error occurs while marshalling * @throws NullPointerException * if compressdIdentityLink or parent is * null */ public void marshallCompressedIdentityLink( JAXBElement compressedIdentityLink, Node parent, Node nextSibling, boolean applyWorkarounds) throws JAXBException { DOMResult result = new DOMResult(parent, nextSibling); try { Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.marshal(compressedIdentityLink, result); } catch (PropertyException e) { throw new RuntimeException(e); } if (applyWorkarounds) { Element element = (Element) ((nextSibling != null) ? nextSibling.getPreviousSibling() : parent.getFirstChild()); applyWorkarounds(element, 76); } } @SuppressWarnings("unchecked") public CompressedIdentityLinkType unmarshallCompressedIdentityLink(Source source) throws JAXBException { Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); return ((JAXBElement) unmarshaller.unmarshal(source)).getValue(); } /** * Apply some workarounds to the given CompressedIdentityLink * element to achieve compatibility with IdentityLink * transformation stylesheets that have been designed for a (buggy) form of * the CompressedIdentityLink as produced by a well-known citizen card * environment implementation. * *
    *
  1. Replace the attribute node URN of the * NamedCurve element of an ECDSAKeyValue element by * a child text-node with the same content.
  2. *
  3. Replace the attribute nodes Value of the X * and Y elements of an ECDSAKeyValue element by a * child text-node with the same content.
  4. *
  5. Insert "\n" at base64LineLength into the Base64 * content of the Modulus element of an RSAKeyValue * element. *
* * @param element * the CompressedIdentityLink element * @param base64LineLength * the line length of Base64 content */ public void applyWorkarounds(Element element, int base64LineLength) { Document document = element.getOwnerDocument(); NodeList nodeList = element.getElementsByTagNameNS( "http://www.w3.org/2001/04/xmldsig-more#", "NamedCurve"); for (int i = 0; i < nodeList.getLength(); i++) { Node ecdsaNameCurve = nodeList.item(i); Attr attrNode = ((Element) ecdsaNameCurve).getAttributeNodeNS(null, "URN"); ecdsaNameCurve .appendChild(document.createTextNode(attrNode.getValue())); ((Element) ecdsaNameCurve).removeAttributeNode(attrNode); } nodeList = document.getElementsByTagNameNS( "http://www.w3.org/2001/04/xmldsig-more#", "X"); for (int i = 0; i < nodeList.getLength(); i++) { Node x = nodeList.item(i); Attr attrNode = ((Element) x).getAttributeNodeNS(null, "Value"); x.appendChild(document.createTextNode(attrNode.getValue())); ((Element) x).removeAttributeNode(attrNode); } nodeList = document.getElementsByTagNameNS( "http://www.w3.org/2001/04/xmldsig-more#", "Y"); for (int i = 0; i < nodeList.getLength(); i++) { Node y = nodeList.item(i); Attr attrNode = ((Element) y).getAttributeNodeNS(null, "Value"); y.appendChild(document.createTextNode(attrNode.getValue())); ((Element) y).removeAttributeNode(attrNode); } if (base64LineLength > 0) { nodeList = document.getElementsByTagNameNS( "http://www.w3.org/2000/09/xmldsig#", "Modulus"); for (int i = 0; i < nodeList.getLength(); i++) { Node modulus = nodeList.item(i); String value = ((Element) modulus).getTextContent(); BufferedReader reader = new BufferedReader(new InputStreamReader( new ByteArrayInputStream(value.getBytes()))); char[] buff = new char[base64LineLength]; StringBuffer newValue = new StringBuffer(); int found = 0; try { while ((found = reader.read(buff)) > 0) { newValue.append(buff, 0, found); if (found == base64LineLength) newValue.append('\n'); } } catch (IOException e) { // this should never happen, as we are reading from a ByteArrayInputStream throw new RuntimeException(e); } ((Element) modulus).setTextContent(newValue.toString()); } } } }