blob: 21d093399753488293606b98df816118241ef47a [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.wss4j.common.util;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.xml.security.algorithms.JCEMapper;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.utils.JavaUtils;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.HashMap;
import java.util.Map;
public final class KeyUtils {
private static final org.slf4j.Logger LOG =
org.slf4j.LoggerFactory.getLogger(KeyUtils.class);
private static final int MAX_SYMMETRIC_KEY_SIZE = 1024;
private static final Map<String, Integer> DEFAULT_DERIVED_KEY_LENGTHS = new HashMap<>();
public static final String RSA_ECB_OAEPWITH_SHA1_AND_MGF1_PADDING = "RSA/ECB/OAEPWithSHA1AndMGF1Padding";
/**
* A cached MessageDigest object
*/
private static MessageDigest digest;
static {
DEFAULT_DERIVED_KEY_LENGTHS.put(XMLSignature.ALGO_ID_MAC_HMAC_NOT_RECOMMENDED_MD5, 128);
DEFAULT_DERIVED_KEY_LENGTHS.put(XMLSignature.ALGO_ID_MAC_HMAC_RIPEMD160, 160);
DEFAULT_DERIVED_KEY_LENGTHS.put(XMLSignature.ALGO_ID_MAC_HMAC_SHA1, 160);
DEFAULT_DERIVED_KEY_LENGTHS.put(XMLSignature.ALGO_ID_MAC_HMAC_SHA224, 224);
DEFAULT_DERIVED_KEY_LENGTHS.put(XMLSignature.ALGO_ID_MAC_HMAC_SHA256, 256);
DEFAULT_DERIVED_KEY_LENGTHS.put(XMLSignature.ALGO_ID_MAC_HMAC_SHA384, 384);
DEFAULT_DERIVED_KEY_LENGTHS.put(XMLSignature.ALGO_ID_MAC_HMAC_SHA512, 512);
}
private KeyUtils() {
// complete
}
/**
* Returns the length of the key in # of bytes. For the HMAC algorithms it guesses a default value that can be used
* based on the algorithm.
*
* @param algorithm the URI of the algorithm. See http://www.w3.org/TR/xmlenc-core1/
* @return the key length
*/
public static int getKeyLength(String algorithm) throws WSSecurityException {
if (algorithm == null) {
return 0;
}
int size = JCEMapper.getKeyLengthFromURI(algorithm);
if (size == 0 && DEFAULT_DERIVED_KEY_LENGTHS.containsKey(algorithm)) {
// Use a default derived key length for algorithms such as HMAC-SHA1, if none is specified
size = DEFAULT_DERIVED_KEY_LENGTHS.get(algorithm);
}
return size / 8;
}
/**
* Convert the raw key bytes into a SecretKey object of type algorithm.
*/
public static SecretKey prepareSecretKey(String algorithm, byte[] rawKey) {
// Do an additional check on the keysize required by the encryption algorithm
int size = 0;
try {
size = JCEMapper.getKeyLengthFromURI(algorithm) / 8;
} catch (Exception e) {
// ignore - some unknown (to JCEMapper) encryption algorithm
LOG.debug(e.getMessage());
}
String keyAlgorithm = JCEMapper.getJCEKeyAlgorithmFromURI(algorithm);
SecretKeySpec keySpec;
if (size > 0 && !algorithm.endsWith("gcm") && !algorithm.contains("hmac-")) {
keySpec =
new SecretKeySpec(
rawKey, 0, rawKey.length > size ? size : rawKey.length, keyAlgorithm
);
} else if (rawKey.length > MAX_SYMMETRIC_KEY_SIZE) {
// Prevent a possible attack where a huge secret key is specified
keySpec =
new SecretKeySpec(
rawKey, 0, MAX_SYMMETRIC_KEY_SIZE, keyAlgorithm
);
} else {
keySpec = new SecretKeySpec(rawKey, keyAlgorithm);
}
return keySpec;
}
public static KeyGenerator getKeyGenerator(String algorithm) throws WSSecurityException {
try {
//
// Assume AES as default, so initialize it
//
String keyAlgorithm = JCEMapper.getJCEKeyAlgorithmFromURI(algorithm);
if (keyAlgorithm == null || "".equals(keyAlgorithm)) {
keyAlgorithm = JCEMapper.translateURItoJCEID(algorithm);
}
KeyGenerator keyGen = KeyGenerator.getInstance(keyAlgorithm);
if (algorithm.equalsIgnoreCase(XMLCipher.AES_128)
|| algorithm.equalsIgnoreCase(XMLCipher.AES_128_GCM)) {
keyGen.init(128);
} else if (algorithm.equalsIgnoreCase(XMLCipher.AES_192)
|| algorithm.equalsIgnoreCase(XMLCipher.AES_192_GCM)) {
keyGen.init(192);
} else if (algorithm.equalsIgnoreCase(XMLCipher.AES_256)
|| algorithm.equalsIgnoreCase(XMLCipher.AES_256_GCM)) {
keyGen.init(256);
}
return keyGen;
} catch (NoSuchAlgorithmException e) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, e
);
}
}
/**
* Translate the "cipherAlgo" URI to a JCE ID, and return a javax.crypto.Cipher instance
* of this type.
* @param cipherAlgo The cipher in it's WSS URI form,
* ref. https://www.w3.org/TR/xmlenc-core1/#sec-Algorithms
*/
public static Cipher getCipherInstance(String cipherAlgo)
throws WSSecurityException {
return getCipherInstance(cipherAlgo, null);
}
/**
* Translate the "cipherAlgo" URI to a JCE ID, and request a javax.crypto.Cipher instance
* of this type from the given provider.
*
* @param cipherAlgo The cipher in it's WSS URI form, ref. https://www.w3.org/TR/xmlenc-core1/#sec-Algorithms
* @param provider The provider which shall instantiate the cipher.
*/
public static Cipher getCipherInstance(String cipherAlgo, String provider)
throws WSSecurityException {
String keyAlgorithm = JCEMapper.translateURItoJCEID(cipherAlgo);
if (keyAlgorithm == null) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, "unsupportedKeyTransp",
new Object[]{"No such algorithm: \"" + cipherAlgo + "\""});
}
if (provider == null) {
provider = JCEMapper.getProviderId();
} else {
JavaUtils.checkRegisterPermission();
}
try {
if (provider == null) {
return Cipher.getInstance(keyAlgorithm);
} else {
return Cipher.getInstance(keyAlgorithm, provider);
}
} catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
if (XMLCipher.RSA_OAEP.equals(cipherAlgo)) {
// Check to see if an RSA OAEP MGF-1 with SHA-1 algorithm was requested
// Some JCE implementations don't support RSA/ECB/OAEPPadding (e.g. nCipherKM of Thales)
try {
if (provider == null) {
return Cipher.getInstance(RSA_ECB_OAEPWITH_SHA1_AND_MGF1_PADDING);
} else {
return Cipher.getInstance(RSA_ECB_OAEPWITH_SHA1_AND_MGF1_PADDING, provider);
}
} catch (NoSuchProviderException ex1) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, ex1, "unsupportedKeyTransp",
new Object[]{
"No such provider \"" + JCEMapper.getProviderId() + "\" for \""
+ RSA_ECB_OAEPWITH_SHA1_AND_MGF1_PADDING + "\""
});
} catch (NoSuchPaddingException ex1) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, e, "unsupportedKeyTransp",
new Object[]{"No such padding: \"" + RSA_ECB_OAEPWITH_SHA1_AND_MGF1_PADDING + "\""});
} catch (NoSuchAlgorithmException ex1) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, e, "unsupportedKeyTransp",
new Object[]{"No such algorithm: \"" + RSA_ECB_OAEPWITH_SHA1_AND_MGF1_PADDING + "\""});
}
} else {
if (e instanceof NoSuchAlgorithmException) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, e, "unsupportedKeyTransp",
new Object[]{"No such algorithm: \"" + keyAlgorithm + "\""});
} else {
throw new WSSecurityException(
WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, e, "unsupportedKeyTransp",
new Object[]{"No such padding: \"" + keyAlgorithm + "\""});
}
}
} catch (NoSuchProviderException ex) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, ex, "unsupportedKeyTransp",
new Object[]{"No such provider \"" + JCEMapper.getProviderId() + "\" for \"" + keyAlgorithm + "\""});
}
}
/**
* Generate a (SHA1) digest of the input bytes. The MessageDigest instance that backs this
* method is cached for efficiency.
* @param inputBytes the bytes to digest
* @return the digest of the input bytes
* @throws WSSecurityException
*/
public static synchronized byte[] generateDigest(byte[] inputBytes) throws WSSecurityException {
try {
if (digest == null) {
digest = MessageDigest.getInstance("SHA-1");
}
return digest.digest(inputBytes);
} catch (Exception e) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, e, "empty",
new Object[] {"Error in generating digest"}
);
}
}
}