/* * 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 iaik.xml.crypto.XmldsigMore; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringWriter; import java.math.BigInteger; import java.nio.charset.Charset; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TimeZone; 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.crypto.MarshalException; import javax.xml.crypto.dsig.CanonicalizationMethod; import javax.xml.crypto.dsig.DigestMethod; import javax.xml.crypto.dsig.Manifest; import javax.xml.crypto.dsig.Reference; import javax.xml.crypto.dsig.SignatureMethod; import javax.xml.crypto.dsig.SignedInfo; import javax.xml.crypto.dsig.Transform; import javax.xml.crypto.dsig.XMLObject; import javax.xml.crypto.dsig.XMLSignature; import javax.xml.crypto.dsig.XMLSignatureException; import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.dom.DOMSignContext; import javax.xml.crypto.dsig.keyinfo.KeyInfo; import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; import javax.xml.crypto.dsig.keyinfo.X509Data; import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; import javax.xml.crypto.dsig.spec.TransformParameterSpec; import javax.xml.crypto.dsig.spec.XPathFilterParameterSpec; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import oasis.names.tc.saml._1_0.assertion.AssertionType; import oasis.names.tc.saml._1_0.assertion.AttributeStatementType; import oasis.names.tc.saml._1_0.assertion.AttributeType; import oasis.names.tc.saml._1_0.assertion.SubjectConfirmationType; import oasis.names.tc.saml._1_0.assertion.SubjectType; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Element; import org.w3c.dom.Node; 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.xmldsig.KeyTypeNotSupportedException; import at.gv.egiz.xmldsig.KeyValueFactory; import oasis.names.tc.saml._1_0.assertion.AnyType; public class IdentityLinkFactory { private static Log log = LogFactory.getLog(IdentityLinkFactory.class); /** * The instance returned by {@link #getInstance()}. */ private static IdentityLinkFactory 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 IdentityLinkFactory getInstance() { if (instance == null) { instance = new IdentityLinkFactory(); } return instance; } /** * Private constructor. */ private IdentityLinkFactory() { 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()); packageNames.append(":"); packageNames.append(oasis.names.tc.saml._1_0.assertion.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 JAXBElement createAssertion(String assertionId, Date issueInstant, String issuer, long majorVersion, long minorVersion, AttributeStatementType attributeStatement) { oasis.names.tc.saml._1_0.assertion.ObjectFactory asFactory = new oasis.names.tc.saml._1_0.assertion.ObjectFactory(); AssertionType assertionType = asFactory.createAssertionType(); assertionType.setAssertionID(assertionId); GregorianCalendar gregorianCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); gregorianCalendar.setTime(issueInstant); try { DatatypeFactory datatypeFactory = DatatypeFactory.newInstance(); assertionType.setIssueInstant(datatypeFactory.newXMLGregorianCalendar(gregorianCalendar)); } catch (DatatypeConfigurationException e) { throw new RuntimeException(e); } assertionType.setIssuer(issuer); assertionType.setMajorVersion(BigInteger.valueOf(majorVersion)); assertionType.setMinorVersion(BigInteger.valueOf(minorVersion)); assertionType.getStatementOrSubjectStatementOrAuthenticationStatement().add(attributeStatement); return asFactory.createAssertion(assertionType); } public AttributeStatementType createAttributeStatement(String idValue, String idType, String givenName, String familyName, String dateOfBirth, PublicKey[] publicKeys) throws KeyTypeNotSupportedException { oasis.names.tc.saml._1_0.assertion.ObjectFactory asFactory = new oasis.names.tc.saml._1_0.assertion.ObjectFactory(); at.gv.e_government.reference.namespace.persondata._20020228_.ObjectFactory prFactory = new at.gv.e_government.reference.namespace.persondata._20020228_.ObjectFactory(); AttributeStatementType attributeStatementType = asFactory.createAttributeStatementType(); // saml:Subject SubjectConfirmationType subjectConfirmationType = asFactory.createSubjectConfirmationType(); subjectConfirmationType.getConfirmationMethod().add("urn:oasis:names:tc:SAML:1.0:cm:sender-vouches"); // pr:Person Value identificationTypeValue = prFactory.createIdentificationTypeValue(); identificationTypeValue.setValue(idValue); IdentificationType identificationType = prFactory .createIdentificationType(); identificationType.setValue(identificationTypeValue); identificationType.setType(idType); PersonNameType personNameType = prFactory.createPersonNameType(); FamilyName personNameTypeFamilyName = prFactory .createPersonNameTypeFamilyName(); personNameTypeFamilyName.setValue(familyName); personNameTypeFamilyName.setPrimary("undefined"); personNameType.getFamilyName().add(personNameTypeFamilyName); personNameType.getGivenName().add(givenName); PhysicalPersonType physicalPersonType = prFactory .createPhysicalPersonType(); physicalPersonType.getIdentification().add(identificationType); physicalPersonType.setName(personNameType); physicalPersonType.setDateOfBirth(dateOfBirth); JAXBElement physicalPerson = prFactory.createPhysicalPerson(physicalPersonType); AnyType personType = asFactory.createAnyType(); personType.getContent().add(physicalPerson); subjectConfirmationType.setSubjectConfirmationData(personType); JAXBElement subjectConfirmation = asFactory.createSubjectConfirmation(subjectConfirmationType); SubjectType subjectType = asFactory.createSubjectType(); subjectType.getContent().add(subjectConfirmation); attributeStatementType.setSubject(subjectType); // saml:Attribute CitizenPublicKey for (int i = 0; i < publicKeys.length; i++) { JAXBElement createKeyValue = keyValueFactory.createKeyValue(publicKeys[i]); AttributeType attributeType = asFactory.createAttributeType(); attributeType.setAttributeName("CitizenPublicKey"); attributeType.setAttributeNamespace("urn:publicid:gv.at:namespaces:identitylink:1.2"); AnyType attributeValueType = asFactory.createAnyType(); attributeValueType.getContent().add(createKeyValue); attributeType.getAttributeValue().add(attributeValueType); attributeStatementType.getAttribute().add(attributeType); } return attributeStatementType; } /** * Marshall the given compressedIdentityLink into a DOM document * with the given Nodes as parent and nextSibling * nodes. * * @param identityLink * 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 marshallIdentityLink( JAXBElement identityLink, Node parent, Node nextSibling) throws JAXBException { DOMResult result = new DOMResult(parent, nextSibling); try { Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.marshal(identityLink, result); } catch (PropertyException e) { throw new RuntimeException(e); } } public void signIdentityLink(Element assertion, X509Certificate certificate, PrivateKey key) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, XMLSignatureException, MarshalException { XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance(); KeyInfoFactory keyInfoFactory = KeyInfoFactory.getInstance(); List references = new ArrayList(); // Reference #1 Map prefixMap = new HashMap(); prefixMap.put("pr", "http://reference.e-government.gv.at/namespace/persondata/20020228#"); List transforms1 = new ArrayList(); transforms1.add(signatureFactory.newTransform(Transform.XPATH, new XPathFilterParameterSpec( "not(ancestor-or-self::pr:Identification)", prefixMap))); transforms1.add(signatureFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)); DigestMethod digestMethod1 = signatureFactory.newDigestMethod(DigestMethod.SHA1, null); references.add(signatureFactory.newReference("", digestMethod1, transforms1, null, null)); // Reference (Manifest) DigestMethod digestMethod2 = signatureFactory.newDigestMethod(DigestMethod.SHA1, null); references.add(signatureFactory.newReference("#manifest", digestMethod2, null, Manifest.TYPE, null)); CanonicalizationMethod canonicalizationMethod = signatureFactory .newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec) null); SignatureMethod signatureMethod; String algorithm = key.getAlgorithm(); if ("RSA".equalsIgnoreCase(algorithm)) { signatureMethod = signatureFactory.newSignatureMethod(SignatureMethod.RSA_SHA1, null); } else if ("ECDSA".equalsIgnoreCase(algorithm) || "EC".equalsIgnoreCase(algorithm)) { signatureMethod = signatureFactory.newSignatureMethod(XmldsigMore.SIGNATURE_ECDSA_SHA1, null); } else if ("DSA".equalsIgnoreCase(algorithm)) { signatureMethod = signatureFactory.newSignatureMethod(SignatureMethod.DSA_SHA1, null); } else { throw new NoSuchAlgorithmException("Algorithm '" + algorithm + "' not supported."); } SignedInfo signedInfo = signatureFactory.newSignedInfo(canonicalizationMethod, signatureMethod, references); X509Data x509Data = keyInfoFactory.newX509Data(Collections.singletonList(certificate)); KeyInfo keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(x509Data)); // Manifest Map manifestPrefixMap = new HashMap(); manifestPrefixMap.put("dsig", XMLSignature.XMLNS); List manifestTransforms = Collections .singletonList(signatureFactory.newTransform(Transform.XPATH, new XPathFilterParameterSpec( "not(ancestor-or-self::dsig:Signature)", manifestPrefixMap))); Reference manifestReference = signatureFactory.newReference("", signatureFactory.newDigestMethod(DigestMethod.SHA1, null), manifestTransforms, null, null); Manifest manifest = signatureFactory.newManifest(Collections .singletonList(manifestReference), "manifest"); XMLObject xmlObject = signatureFactory.newXMLObject(Collections .singletonList(manifest), null, null, null); XMLSignature xmlSignature = signatureFactory.newXMLSignature(signedInfo, keyInfo, Collections.singletonList(xmlObject), null, null); DOMSignContext signContext = new DOMSignContext(key, assertion); if (log.isTraceEnabled()) { signContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE); } xmlSignature.sign(signContext); if (log.isDebugEnabled()) { try { TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); StringWriter writer = new StringWriter(); transformer.transform(new DOMSource(assertion), new StreamResult(writer)); log.debug(writer.toString()); } catch (Exception e) { log.debug(e); } } if (log.isTraceEnabled()) { StringBuilder sb = new StringBuilder(); sb.append("Digest input data:\n\n"); try { Iterator refs = references.iterator(); for (int i = 0; refs.hasNext(); i++) { Reference reference = (Reference) refs.next(); sb.append("Reference " + i + "\n"); Reader reader = new InputStreamReader(reference .getDigestInputStream(), Charset.forName("UTF-8")); char c[] = new char[512]; for (int l; (l = reader.read(c)) != -1;) { sb.append(c, 0, l); } sb.append("\n"); } sb.append("Manifest Reference\n"); Reader reader = new InputStreamReader(manifestReference .getDigestInputStream(), Charset.forName("UTF-8")); char c[] = new char[512]; for (int l; (l = reader.read(c)) != -1;) { sb.append(c, 0, l); } } catch (Exception e) { sb.append(e.getMessage()); } log.trace(sb.toString()); } } }