All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.swedenconnect.opensaml.xmlsec.signature.support.provider.ExtendedSignerProvider Maven / Gradle / Ivy

/*
 * Copyright 2019-2024 Sweden Connect
 *
 * 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 se.swedenconnect.opensaml.xmlsec.signature.support.provider;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.util.Optional;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import jakarta.annotation.Nonnull;
import net.shibboleth.shared.logic.Constraint;
import org.apache.xml.security.Init;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.signature.SignedInfo;
import org.apache.xml.security.signature.XMLSignature;
import org.bouncycastle.util.encoders.Base64;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.CredentialSupport;
import org.opensaml.xmlsec.algorithm.AlgorithmDescriptor;
import org.opensaml.xmlsec.algorithm.AlgorithmRegistry;
import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
import org.opensaml.xmlsec.algorithm.SignatureAlgorithm;
import org.opensaml.xmlsec.signature.Signature;
import org.opensaml.xmlsec.signature.impl.SignatureImpl;
import org.opensaml.xmlsec.signature.support.SignatureConstants;
import org.opensaml.xmlsec.signature.support.SignatureException;
import org.opensaml.xmlsec.signature.support.impl.provider.ApacheSantuarioSignerProviderImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.NodeList;

import se.swedenconnect.opensaml.xmlsec.algorithm.ExtendedAlgorithmSupport;
import se.swedenconnect.opensaml.xmlsec.signature.support.provider.padding.SCPSSPadding;

/**
 * The Sun PKCS#11 crypto provider does not have support for PSS padding which makes HSM RSA-PSS signing impossible
 * using the standard OpenSAML signer provider ({@link ApacheSantuarioSignerProviderImpl}). Therefore, the
 * {@code ExtendedSignerProvider} overrides {@link ApacheSantuarioSignerProviderImpl} with functionality that performs
 * the PSS padding in software and only the raw RSA encryption operation is done in the HSM. This enables RSA-PSS
 * signing with RSA keys in HSM even when RSA-PSS is not supported by the PKCS#11 API.
 *
 * @author Martin Lindström ([email protected])
 * @author Stefan Santesson ([email protected])
 * @see ApacheSantuarioSignerProviderImpl
 */
public class ExtendedSignerProvider extends ApacheSantuarioSignerProviderImpl {

  /** Class logger. */
  private static final Logger log = LoggerFactory.getLogger(ExtendedSignerProvider.class);

  /**
   * The PKCS#11 fixes of this provider may be disabled by setting the system property
   * {@code se.swedenconnect.opensaml.xmlsec.signature.support.provider.ExtendedSignerProvider.disabled} to
   * {@code true}.
   */
  private final boolean disabled;

  /**
   * Default constructor.
   */
  public ExtendedSignerProvider() {
    this.disabled = Boolean.parseBoolean(System.getProperty(
        "se.swedenconnect.opensaml.xmlsec.signature.support.provider.ExtendedSignerProvider.disabled", "false"));
    if (this.disabled) {
      log.info("The ExtendedSignerProvider has been disabled - {} will be active",
          ApacheSantuarioSignerProviderImpl.class.getName());
    }
  }

  /**
   * Tests if the signing key is a SUN PKCS#11 key and the signing algorithm is RSA-PSS. If this is the case, then PSS
   * padding is performed in software and only the raw RSA encryption operation is done in the HSM. This enables RSA-PSS
   * signing with RSA keys in HSM even when RSA-PSS is not supported by the PKCS#11 API.
   */
  @Override
  public void signObject(@Nonnull final Signature signature) throws SignatureException {
    if (this.disabled) {
      super.signObject(signature);
      return;
    }
    Constraint.isNotNull(signature, "Signature cannot be null");
    Constraint.isTrue(Init.isInitialized(), "Apache XML security library is not initialized");

    final XMLSignature xmlSignature = ((SignatureImpl) signature).getXMLSignature();
    final Credential signingCredential = signature.getSigningCredential();
    final Key signingKey = CredentialSupport.extractSigningKey(signingCredential);

    // Should we intercept this call and provider our own implementation?
    //
    if (!this.shouldOverride(signingKey, xmlSignature)) {
      // Nope, let the default implementation handle this operation.
      super.signObject(signature);
      return;
    }

    final SignedInfo signedInfo = xmlSignature.getSignedInfo();
    if (signedInfo == null) {
      final String msg = "Bad XMLSignature - missing SignedInfo";
      log.error(msg);
      throw new SignatureException(msg);
    }

    log.debug("{} executing during signature with {}", ExtendedSignerProvider.class.getSimpleName(),
        signedInfo.getSignatureMethodURI());

    try {
      // Calculate digest values ...
      signedInfo.generateDigestValues();

      // Get canonicalized bytes ...
      final byte[] signedInfoBytes = signedInfo.getCanonicalizedOctetStream();

      // Perform RSA-PSS padding ...
      //
      final RSAPublicKey publicKey = (RSAPublicKey) signingCredential.getPublicKey();
      if (publicKey == null) {
        final String msg = "No RSA public key found in signing credential - Actual type is: %s"
            .formatted(Optional.ofNullable(signingCredential.getPublicKey())
                .map(k -> k.getClass().getName())
                .orElse("null"));
        log.error(msg);
        throw new SignatureException(msg);
      }
      final SCPSSPadding pssPadding = new SCPSSPadding(
          this.getDigest(signedInfo.getSignatureMethodURI()),
          publicKey.getModulus().bitLength());

      final byte[] emBytes = pssPadding.getPaddingFromMessage(signedInfoBytes);

      // Next, perform a raw RSA transform (the signing) ...
      //
      final Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
      cipher.init(Cipher.DECRYPT_MODE, signingKey);
      final byte[] signatureBytes = cipher.doFinal(emBytes);

      // Finally, place the signature in its correct place in the XML signature object ...
      //
      final NodeList signatureValue = xmlSignature.getElement()
          .getElementsByTagNameNS(SignatureConstants.XMLSIG_NS, "SignatureValue");
      if (signatureValue.getLength() == 0) {
        throw new SignatureException("Invalid XMLSignature - missing SignatureValue element");
      }
      signatureValue.item(0).setTextContent(Base64.toBase64String(signatureBytes));
    }
    catch (final XMLSecurityException e) {
      log.error("Failure during digest calculation - {}", e.getMessage(), e);
      throw new SignatureException("Failure during digest calculation", e);
    }
    catch (final NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | BadPaddingException |
        IllegalBlockSizeException | IOException e) {
      log.error("RSA transform failed - {}", e.getMessage(), e);
      throw new SignatureException("RSA signature failure", e);
    }
  }

  /**
   * Predicate that tells us whether our implementation should kick-in, or whether we should let
   * {@link ApacheSantuarioSignerProviderImpl} process the call.
   * 

* If the signing key is an RSA SunPKCS11 private key and the algorithm is an RSA-PSS algorithm we let our override * take control. We can also force our implementation by setting the system property * {@code se.swedenconnect.opensaml.xmlsec.signature.support.provider.ExtendedSignerProvider.testmode} to * {@code true}. *

* * @param signingKey the signing key * @param xmlSignature the XML signature object * @return whether we should override or not */ private boolean shouldOverride(final Key signingKey, final XMLSignature xmlSignature) { if (signingKey != null && !"RSA".equals(signingKey.getAlgorithm())) { return false; } if (this.isTestMode()) { return true; } final String signingAlgorithm = xmlSignature != null && xmlSignature.getSignedInfo() != null ? xmlSignature.getSignedInfo().getSignatureMethodURI() : null; return signingKey != null && "RSA".equals(signingKey.getAlgorithm()) && isP11PrivateKey(signingKey) && ExtendedAlgorithmSupport.isRSAPSS(signingAlgorithm); } /** * Predicate that tells whether the supplied {@link Key} is a PKCS#11 private key. * * @param key the key to test * @return {@code true} if the supplied key is a PKCS#11 private key, and {@code false} otherwise */ private static boolean isP11PrivateKey(final Key key) { return "sun.security.pkcs11.P11Key$P11PrivateKey".equals(key.getClass().getName()) || "sun.security.pkcs11.P11Key$P11RSAPrivateKeyInternal".equals(key.getClass().getName()); } /** * Return the digest function specified by a signature algorithm. * * @param signatureAlgorithm signature algorithm * @return the digest algorithm * @throws NoSuchAlgorithmException if no support for the algorithm is available */ private MessageDigest getDigest(final String signatureAlgorithm) throws NoSuchAlgorithmException { final AlgorithmRegistry algorithmRegistry = AlgorithmSupport.getGlobalAlgorithmRegistry(); final AlgorithmDescriptor algorithmDescriptor = algorithmRegistry.get(signatureAlgorithm); if (algorithmDescriptor == null || !AlgorithmDescriptor.AlgorithmType.Signature.equals( algorithmDescriptor.getType())) { log.error("Unsupported signature algorithm - {}", signatureAlgorithm); throw new NoSuchAlgorithmException("Unsupported signature algorithm - " + signatureAlgorithm); } final String jcaDigest = ((SignatureAlgorithm) algorithmDescriptor).getDigest(); log.debug("Getting digest algorithm for '{}'", jcaDigest); return MessageDigest.getInstance(jcaDigest); } /** * Predicate that tells if this instance is running in test mode. Using test mode we can test the PKCS#11 workaround * even without an installed HSM. * * @return test mode flag */ private boolean isTestMode() { return Boolean.parseBoolean( System.getProperty( "se.swedenconnect.opensaml.xmlsec.signature.support.provider.ExtendedSignerProvider.testmode", "false")); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy