/*
* 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.
*
*
* - Replace the attribute node
URN
of the
* NamedCurve
element of an ECDSAKeyValue
element by
* a child text-node with the same content.
* - Replace the attribute nodes
Value
of the X
* and Y
elements of an ECDSAKeyValue
element by a
* child text-node with the same content.
* - 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());
}
}
}
}