/* CVS Header $ $ */ package org.guanxi.common.job; import org.apache.log4j.Logger; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.signature.XMLSignatureException; import org.apache.xml.security.keys.KeyInfo; import org.apache.xml.security.exceptions.XMLSecurityException; import org.apache.xml.security.Init; import org.apache.xmlbeans.XmlException; import org.guanxi.xal.saml_2_0.metadata.EntitiesDescriptorDocument; import org.guanxi.xal.saml_2_0.metadata.EntityDescriptorType; import org.guanxi.xal.saml_2_0.metadata.ExtensionsType; import org.guanxi.xal.w3.xmldsig.SignatureType; import org.guanxi.xal.w3.xmldsig.KeyInfoType; import org.guanxi.xal.w3.xmldsig.X509DataType; import org.guanxi.xal.shibboleth_1_0.metadata.KeyAuthorityDocument; import org.guanxi.common.Utils; import org.guanxi.common.GuanxiException; import org.guanxi.common.entity.EntityManager; import org.guanxi.common.entity.EntityFarm; import org.guanxi.common.trust.TrustUtils; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.security.cert.X509Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.io.IOException; import java.io.ByteArrayInputStream; public abstract class ShibbolethSAML2MetadataParser { /** Our logger */ protected Logger logger = null; protected SAML2MetadataParserConfig config = null; protected EntitiesDescriptorDocument doc = null; protected EntityDescriptorType[] entityDescriptors = null; static { // Initialise xml-security library Init.init(); } /** * Initialises the SAML2 parsing operation */ public void init() { logger = Logger.getLogger(config.getJobClass()); try { // Load the metadata from the URL doc = Utils.parseSAML2Metadata(config.getMetadataURL()); } catch(GuanxiException ge) { logger.error("Error parsing metadata. Loading from cache", ge); try { // Load the metadata from the cache doc = Utils.parseSAML2Metadata("file:///" + config.getMetadataCacheFile()); } catch(GuanxiException gex) { logger.error("Could not load metadata from cache : " + config.getMetadataCacheFile(), gex); } } } /** * Loads the SAML2 entities from the metadata and caches them locally */ protected void loadAndCacheEntities() { entityDescriptors = doc.getEntitiesDescriptor().getEntityDescriptorArray(); // Cache the metadata locally try { Utils.writeSAML2MetadataToDisk(doc, config.getMetadataCacheFile()); } catch(GuanxiException ge) { logger.error("Could not cache metadata to : " + config.getMetadataCacheFile(), ge); } } /** * Verifies the fingerprint of the signing certificate in the metadata * with a known certificate fingerprint. * * @return true if the fingerprints match, otherwise false */ protected boolean verifyMetadataFingerprint() { X509Certificate metadataCert = getX509FromMetadataSignature(); if (metadataCert != null) { try { return TrustUtils.checkCertfingerprints(metadataCert, TrustUtils.pem2x509(config.getPemLocation())); } catch(GuanxiException ge) { logger.error("error checking metadata cert fingerprint", ge); } } else { logger.error("no X509 in metadata signature"); } return false; } /** * Verifies the signature on the SAML2 metadata * * @return true if the signature verifies, otherwise false */ protected boolean verifyMetadataSignature() { SignatureType sig = getSignatureFromMetadata(); X509Certificate metadataCert = getX509FromMetadataSignature(); if (metadataCert != null) { try { XMLSignature signature = new XMLSignature((Element)sig.getDomNode(),""); return signature.checkSignatureValue(metadataCert); } catch(XMLSignatureException xse) { logger.error("Failed to get signature of metadata", xse); } catch(XMLSecurityException xse) { logger.error("Problem with the signature of metadata", xse); } } else { logger.error("no X509 in metadata signature"); } return false; } /** * Extracts the signature block from the metadata * * @return Signature block from the metadata */ protected SignatureType getSignatureFromMetadata() { return doc.getEntitiesDescriptor().getSignature(); } /** * Extracts the X509Certificate wrapping the public key to be used * to verify the signature on SAML2 metadata. * * @return X509Certificate of the signing public key */ protected X509Certificate getX509FromMetadataSignature() { SignatureType sig = getSignatureFromMetadata(); if (sig == null) { logger.error("Metadata is not signed"); } else { try { XMLSignature signature = new XMLSignature((Element)sig.getDomNode(),""); KeyInfo keyInfo = signature.getKeyInfo(); if (keyInfo != null) { if(keyInfo.containsX509Data()) { return signature.getKeyInfo().getX509Certificate(); } else { logger.error("no x509 data in metadata signature"); } } else { logger.error("no key info in metadata signature"); } } catch(XMLSignatureException xse) { logger.error("Failed to get signature of metadata", xse); } catch(XMLSecurityException xse) { logger.error("Problem with the signature of metadata", xse); } } return null; } /** * Loads all the shibmeta:KeyAuthority nodes from the SAML2 metadata * * @param manager EntityManager instance for this metadata * @return true if the CA lists was loaded, otherwise false */ protected boolean loadCAListFromMetadata(EntityManager manager) { boolean loaded = false; /* Are there any extensions? If there aren't, it doesn't matter * as the trust engine handling this metadata might not need them. */ if (doc.getEntitiesDescriptor().getExtensions() == null) { return false; } try { CertificateFactory certFactory = CertificateFactory.getInstance("x.509"); ExtensionsType extensions = doc.getEntitiesDescriptor().getExtensions(); /* Find the shibmeta:KeyAuthority node. This lists all the root CAs * that we trust. */ Node keyAuthorityNode = null; NodeList nodes = extensions.getDomNode().getChildNodes(); for (int c=0; c < nodes.getLength(); c++) { if (nodes.item(c).getLocalName() != null) { if (nodes.item(c).getLocalName().equals("KeyAuthority")) { keyAuthorityNode = nodes.item(c); } } } // Load all the root CAs into the trust engine if (keyAuthorityNode != null) { KeyAuthorityDocument keyAuthDoc = KeyAuthorityDocument.Factory.parse(keyAuthorityNode); KeyInfoType[] keyInfos = keyAuthDoc.getKeyAuthority().getKeyInfoArray(); for (KeyInfoType keyInfo : keyInfos) { X509DataType[] x509Datas = keyInfo.getX509DataArray(); for (X509DataType x509Data : x509Datas) { byte[][] x509Certs = x509Data.getX509CertificateArray(); for (byte[] x509CertBytes : x509Certs) { ByteArrayInputStream certByteStream = new ByteArrayInputStream(x509CertBytes); manager.getTrustEngine().addCACert((X509Certificate)certFactory.generateCertificate(certByteStream)); certByteStream.close(); loaded = true; } } } } else { logger.error("Could not find shibmeta:KeyAuthority in metadata"); } } catch(CertificateException ce) { logger.error("Could not prepare certificate factory", ce); } catch(IOException ioe) { logger.error("Could not close byte stream", ioe); } catch(XmlException xe) { logger.error("Could not load shibboleth extensions from metadata", xe); } return loaded; } /** * Loads the appropriate EntityManager for the current metadata source and * empties it of any previous metadata. * * @param contextKey The key in the servlet context under which the manager is hiding * @return EntityManager for the current metadata source */ protected EntityManager loadEmptyEntityManager(String contextKey) { EntityFarm farm = (EntityFarm)config.getServletContext().getAttribute(contextKey); EntityManager manager = farm.getEntityManagerForSource(config.getMetadataURL()); manager.removeAllMetadata(); return manager; } /** * Loads the appropriate EntityManager for the current metadata source * * @param contextKey The key in the servlet context under which the manager is hiding * @return EntityManager for the current metadata source */ protected EntityManager loadEntityManager(String contextKey) { EntityFarm farm = (EntityFarm)config.getServletContext().getAttribute(contextKey); return farm.getEntityManagerForSource(config.getMetadataURL()); } }