/**
  * Updates signing or encryption key info for SP or IDP. This will update both signing/encryption
  * alias on extended metadata and certificates in standard metadata.
  *
  * @param realm Realm the entity resides.
  * @param entityID ID of the entity to be updated.
  * @param certAlias Alias of the certificate to be set to the entity. If null, will remove
  *     existing key information from the SP or IDP.
  * @param isSigning true if this is signing certificate alias, false if this is encryption
  *     certification alias.
  * @param isIDP true if this is for IDP signing/encryption alias, false if this is for SP
  *     signing/encryption alias
  * @param encAlgo Encryption algorithm URI, this is applicable for encryption cert only.
  * @param keySize Encryption key size, this is applicable for encryption cert only.
  * @throws SAML2MetaException if failed to update the certificate alias for the entity.
  */
 public static void updateProviderKeyInfo(
     String realm,
     String entityID,
     String certAlias,
     boolean isSigning,
     boolean isIDP,
     String encAlgo,
     int keySize)
     throws SAML2MetaException {
   SAML2MetaManager metaManager = new SAML2MetaManager();
   EntityConfigElement config = metaManager.getEntityConfig(realm, entityID);
   if (!config.isHosted()) {
     String[] args = {entityID, realm};
     throw new SAML2MetaException("entityNotHosted", args);
   }
   EntityDescriptorElement desp = metaManager.getEntityDescriptor(realm, entityID);
   if (isIDP) {
     IDPSSOConfigElement idpConfig = SAML2MetaUtils.getIDPSSOConfig(config);
     IDPSSODescriptorElement idpDesp = SAML2MetaUtils.getIDPSSODescriptor(desp);
     if ((idpConfig == null) || (idpDesp == null)) {
       String[] args = {entityID, realm};
       throw new SAML2MetaException("entityNotIDP", args);
     }
     // update standard metadata
     if ((certAlias == null) || (certAlias.length() == 0)) {
       // remove key info
       removeKeyDescriptor(idpDesp, isSigning);
       if (isSigning) {
         setExtendedAttributeValue(idpConfig, SAML2Constants.SIGNING_CERT_ALIAS, null);
       } else {
         setExtendedAttributeValue(idpConfig, SAML2Constants.ENCRYPTION_CERT_ALIAS, null);
       }
     } else {
       KeyDescriptorElement kde = getKeyDescriptor(certAlias, isSigning, encAlgo, keySize);
       updateKeyDescriptor(idpDesp, kde);
       // update extended metadata
       Set value = new HashSet();
       value.add(certAlias);
       if (isSigning) {
         setExtendedAttributeValue(idpConfig, SAML2Constants.SIGNING_CERT_ALIAS, value);
       } else {
         setExtendedAttributeValue(idpConfig, SAML2Constants.ENCRYPTION_CERT_ALIAS, value);
       }
     }
     metaManager.setEntityDescriptor(realm, desp);
     metaManager.setEntityConfig(realm, config);
   } else {
     SPSSOConfigElement spConfig = SAML2MetaUtils.getSPSSOConfig(config);
     SPSSODescriptorElement spDesp = SAML2MetaUtils.getSPSSODescriptor(desp);
     if ((spConfig == null) || (spDesp == null)) {
       String[] args = {entityID, realm};
       throw new SAML2MetaException("entityNotSP", args);
     }
     // update standard metadata
     if ((certAlias == null) || (certAlias.length() == 0)) {
       // remove key info
       removeKeyDescriptor(spDesp, isSigning);
       if (isSigning) {
         setExtendedAttributeValue(spConfig, SAML2Constants.SIGNING_CERT_ALIAS, null);
       } else {
         setExtendedAttributeValue(spConfig, SAML2Constants.ENCRYPTION_CERT_ALIAS, null);
       }
     } else {
       KeyDescriptorElement kde = getKeyDescriptor(certAlias, isSigning, encAlgo, keySize);
       updateKeyDescriptor(spDesp, kde);
       // update extended metadata
       Set value = new HashSet();
       value.add(certAlias);
       if (isSigning) {
         setExtendedAttributeValue(spConfig, SAML2Constants.SIGNING_CERT_ALIAS, value);
       } else {
         setExtendedAttributeValue(spConfig, SAML2Constants.ENCRYPTION_CERT_ALIAS, value);
       }
     }
     metaManager.setEntityDescriptor(realm, desp);
     metaManager.setEntityConfig(realm, config);
   }
 }
  /**
   * Signs the entity descriptor root element by the following rules:
   *
   * <ul>
   *   <li>Hosted Entity
   *       <ul>
   *         <li>If there is a signature already on the EntityDescriptor, removes it, then signs the
   *             EntityDescriptor.
   *         <li>Simply signs the EntityDescriptor otherwise.
   *       </ul>
   *   <li>Remote Entity
   *       <ul>
   *         <li>If there is a signature already on the EntityDescriptor, then does not change it,
   *             but returns the Document with the original signature.
   *         <li>Simply signs the EntityDescriptor otherwise
   *       </ul>
   * </ul>
   *
   * If there is no extended metadata for the entity, the entity is considered as remote.
   *
   * @param realm The realm where the EntityDescriptor belongs to.
   * @param descriptor The entity descriptor.
   * @return Signed <code>Document</code> for the entity descriptor or null if no metadata signing
   *     key is found in the configuration.
   * @throws SAML2MetaException if unable to sign the entity descriptor.
   * @throws JAXBException if the entity descriptor is invalid.
   */
  public static Document sign(String realm, EntityDescriptorElement descriptor)
      throws JAXBException, SAML2MetaException {
    if (descriptor == null) {
      throw new SAML2MetaException("Unable to sign null descriptor");
    }

    SAML2MetaManager metaManager = new SAML2MetaManager();
    EntityConfigElement cfgElem = metaManager.getEntityConfig(realm, descriptor.getEntityID());
    boolean isHosted;
    if (cfgElem == null) {
      // if there is no EntityConfig, this is considered as a remote entity
      isHosted = false;
    } else {
      isHosted = cfgElem.isHosted();
    }

    String signingCert = getRealmSetting(METADATA_SIGNING_KEY, realm);
    if (signingCert == null) {
      return null;
    }

    initializeKeyStore();

    String xmlstr = SAML2MetaUtils.convertJAXBToString(descriptor);
    xmlstr = formatBase64BinaryElement(xmlstr);

    Document doc = XMLUtils.toDOMDocument(xmlstr, debug);
    NodeList childNodes = doc.getDocumentElement().getChildNodes();
    for (int i = 0; i < childNodes.getLength(); i++) {
      Node node = childNodes.item(i);
      if (node.getLocalName() != null
          && node.getLocalName().equals("Signature")
          && node.getNamespaceURI().equals(NS_XMLSIG)) {
        if (isHosted) {
          node.getParentNode().removeChild(node);
          break;
        } else {
          // one signature found for this remote entity on the root element,
          // in this case returning the entry with the original signature
          // as that may be judged more accurately
          return doc;
        }
      }
    }

    // we need to sign or re-sign the document, let's generate a new ID
    String descriptorId = SAMLUtils.generateID();
    doc.getDocumentElement().setAttribute(ATTR_ID, descriptorId);

    XMLSignatureManager sigManager = XMLSignatureManager.getInstance();
    try {
      String xpath =
          "//*[local-name()=\""
              + TAG_ENTITY_DESCRIPTOR
              + "\" and namespace-uri()=\""
              + NS_META
              + "\"]/*[1]";
      sigManager.signXMLUsingKeyPass(
          doc,
          signingCert,
          getRealmSetting(METADATA_SIGNING_KEY_PASS, realm),
          null,
          SAML2Constants.ID,
          descriptorId,
          true,
          xpath);
    } catch (XMLSignatureException xmlse) {
      if (debug.messageEnabled()) {
        debug.message("SAML2MetaSecurityUtils.sign:", xmlse);
      }
    }

    return doc;
  }