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

org.wildfly.security.key.KeyUtil Maven / Gradle / Ivy

There is a newer version: 2.4.1.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2016 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 org.wildfly.security.key;

import static java.security.AccessController.doPrivileged;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PrivilegedAction;
import java.security.interfaces.DSAKey;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAMultiPrimePrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECParameterSpec;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.UnaryOperator;

import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHKey;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.PBEKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.Destroyable;

import org.wildfly.common.Assert;
import org.wildfly.security.password.Password;
import org.wildfly.security.password.spec.DigestPasswordAlgorithmSpec;
import org.wildfly.security.password.spec.IteratedPasswordAlgorithmSpec;
import org.wildfly.security.password.spec.IteratedSaltedPasswordAlgorithmSpec;
import org.wildfly.security.password.spec.MaskedPasswordAlgorithmSpec;
import org.wildfly.security.password.spec.OneTimePasswordAlgorithmSpec;
import org.wildfly.security.password.spec.SaltedPasswordAlgorithmSpec;

/**
 * Key utility methods.
 *
 * @author David M. Lloyd
 */
public final class KeyUtil {
    private KeyUtil() {}

    /**
     * Cache so that we only have to figure out a cloning strategy for a given class one time.
     */
    private static final KeyClonerCreator CLONER_CREATOR = new KeyClonerCreator();

    /**
     * Attempt to acquire parameters from the given key.
     *
     * @param key the key (must not be {@code null})
     * @return the parameters, or {@code null} if no known parameters are available
     */
    public static AlgorithmParameterSpec getParameters(Key key) {
        return getParameters(key, AlgorithmParameterSpec.class);
    }

    /**
     * Attempt to acquire parameters of the given type from the given key.
     *
     * @param key the key (must not be {@code null})
     * @param paramSpecClass the parameter specification class (must not be {@code null})
     * @param 

the parameter specification type * @return the parameters, or {@code null} if no known parameters of the given type are available */ public static

P getParameters(Key key, Class

paramSpecClass) { if (key instanceof Password) { final AlgorithmParameterSpec parameterSpec = ((Password) key).getParameterSpec(); return paramSpecClass.isInstance(parameterSpec) ? paramSpecClass.cast(parameterSpec) : null; } else if (key instanceof RSAKey && paramSpecClass.isAssignableFrom(RSAParameterSpec.class)) { return paramSpecClass.cast(new RSAParameterSpec((RSAKey) key)); } else if (key instanceof DSAKey && paramSpecClass.isAssignableFrom(DSAParams.class)) { return paramSpecClass.cast(((DSAKey) key).getParams()); } else if (key instanceof ECKey && paramSpecClass.isAssignableFrom(ECParameterSpec.class)) { return paramSpecClass.cast(((ECKey) key).getParams()); } else if (key instanceof DHKey && paramSpecClass.isAssignableFrom(DHParameterSpec.class)) { return paramSpecClass.cast(((DHKey) key).getParams()); } else if (key instanceof PBEKey && paramSpecClass.isAssignableFrom(PBEParameterSpec.class)) { final PBEKey pbeKey = (PBEKey) key; final byte[] salt = pbeKey.getSalt(); // TODO: we miss the IV here return salt == null ? null : paramSpecClass.cast(new PBEParameterSpec(salt, pbeKey.getIterationCount())); } else { return null; } } /** * Determine if the given key has parameters which match the given parameters. * * @param key the key (must not be {@code null}) * @param parameters the parameters (must not be {@code null}) * @return {@code true} if the parameters match, {@code false} otherwise */ public static boolean hasParameters(final Key key, final AlgorithmParameterSpec parameters) { Assert.checkNotNullParam("key", key); Assert.checkNotNullParam("parameters", parameters); final AlgorithmParameterSpec keyParameters = getParameters(key, AlgorithmParameterSpec.class); return keyParameters != null && parametersEqual(keyParameters, parameters); } /** * Attempt to determine if two algorithm parameter specifications are equal. This method will return {@code true} * if the parameters are definitely the same, or {@code false} if they are not definitely equal or equivalency cannot be determined. * * @param p1 the first parameter specification (must not be {@code null}) * @param p2 the second parameter specification (must not be {@code null}) * @return {@code true} if the parameters are definitely equal, {@code false} otherwise */ public static boolean parametersEqual(final AlgorithmParameterSpec p1, final AlgorithmParameterSpec p2) { Assert.checkNotNullParam("p1", p1); Assert.checkNotNullParam("p2", p2); if (p1 instanceof DSAParams && p2 instanceof DSAParams) { final DSAParams dsa1 = (DSAParams) p1; final DSAParams dsa2 = (DSAParams) p2; return Objects.equals(dsa1.getG(), dsa2.getG()) && Objects.equals(dsa1.getP(), dsa2.getP()) && Objects.equals(dsa1.getQ(), dsa2.getQ()); } else if (p1 instanceof ECParameterSpec && p2 instanceof ECParameterSpec) { final ECParameterSpec ec1 = (ECParameterSpec) p1; final ECParameterSpec ec2 = (ECParameterSpec) p2; return ec1.getCofactor() == ec2.getCofactor() && Objects.equals(ec1.getCurve(), ec2.getCurve()) && Objects.equals(ec1.getGenerator(), ec2.getGenerator()) && Objects.equals(ec1.getOrder(), ec2.getOrder()); } else if (p1 instanceof DHParameterSpec && p2 instanceof DHParameterSpec) { final DHParameterSpec dh1 = (DHParameterSpec) p1; final DHParameterSpec dh2 = (DHParameterSpec) p2; return dh1.getL() == dh2.getL() && Objects.equals(dh1.getP(), dh2.getP()) && Objects.equals(dh1.getG(), dh2.getG()); } else if (p1 instanceof PBEParameterSpec && p2 instanceof PBEParameterSpec) { final PBEParameterSpec pbe1 = (PBEParameterSpec) p1; final PBEParameterSpec pbe2 = (PBEParameterSpec) p2; final AlgorithmParameterSpec param1 = pbe1.getParameterSpec(); final AlgorithmParameterSpec param2 = pbe2.getParameterSpec(); return pbe1.getIterationCount() == pbe2.getIterationCount() && Arrays.equals(pbe1.getSalt(), pbe2.getSalt()) && (param1 == null ? param2 == null : param2 != null && parametersEqual(param1, param2)); } else if (p1 instanceof IvParameterSpec && p2 instanceof IvParameterSpec) { final IvParameterSpec iv1 = (IvParameterSpec) p1; final IvParameterSpec iv2 = (IvParameterSpec) p2; return Arrays.equals(iv1.getIV(), iv2.getIV()); } else { // best effort return p1.equals(p2); } } /** * Attempt to get a stable hash code for the given parameter specification. If a stable hash code cannot be acquired, * the hash code of the class is returned, which results in correct (if non-optimal) behavior. If the parameter * is {@code null}, a hash code of zero is returned. * * @param param the parameter specification * @return the hash code */ public static int parametersHashCode(final AlgorithmParameterSpec param) { if (param == null) { return 0; } else if (param instanceof DSAParams) { final DSAParams dsaParams = (DSAParams) param; return Objects.hash(dsaParams.getG(), dsaParams.getP(), dsaParams.getQ()); } else if (param instanceof ECParameterSpec) { final ECParameterSpec ecSpec = (ECParameterSpec) param; return ecSpec.getCofactor() * 31 + Objects.hash(ecSpec.getCurve(), ecSpec.getGenerator(), ecSpec.getOrder()); } else if (param instanceof DHParameterSpec) { final DHParameterSpec dhSpec = (DHParameterSpec) param; return dhSpec.getL() * 31 + Objects.hash(dhSpec.getP(), dhSpec.getG()); } else if (param instanceof PBEParameterSpec) { final PBEParameterSpec pbeSpec = (PBEParameterSpec) param; final AlgorithmParameterSpec parameterSpec = pbeSpec.getParameterSpec(); return (pbeSpec.getIterationCount() * 31 + Arrays.hashCode(pbeSpec.getSalt())) * 31 + parametersHashCode(parameterSpec); } else if (param instanceof IvParameterSpec) { return Arrays.hashCode(((IvParameterSpec) param).getIV()); } else if (param instanceof RSAParameterSpec || param instanceof IteratedSaltedPasswordAlgorithmSpec || param instanceof IteratedPasswordAlgorithmSpec || param instanceof SaltedPasswordAlgorithmSpec || param instanceof DigestPasswordAlgorithmSpec || param instanceof MaskedPasswordAlgorithmSpec || param instanceof OneTimePasswordAlgorithmSpec ) { // our types all have proper hash codes return param.hashCode(); } else { return param.getClass().hashCode(); } } /** * Attempt to determine if the two keys have the same parameters. This method returns {@code true} if the keys * definitely have the same parameters, or {@code false} if they do not or if parameter equivalency cannot be determined. * * @param key1 the first key (must not be {@code null}) * @param key2 the second key (must not be {@code null}) * @return {@code true} if the parameters are definitely equal, {@code false} otherwise */ public static boolean hasSameParameters(final Key key1, final Key key2) { Assert.checkNotNullParam("key1", key1); Assert.checkNotNullParam("key2", key2); final AlgorithmParameterSpec param1 = getParameters(key1, AlgorithmParameterSpec.class); final AlgorithmParameterSpec param2 = getParameters(key2, AlgorithmParameterSpec.class); return param1 == null && param2 == null || param1 != null && param2 != null && parametersEqual(param1, param2); } /** * Attempt to create a safe clone of the given key object. * This algorithm first checks to see if the key's class implements {@link Destroyable}; if not, it is returned as-is. * Next it checks to see if the key has been destroyed; if so, it is returned as-is. * Next it determines if the key actually implements the {@link Destroyable} interface; if not, it is returned as-is. * Then it determines if there is a public {@code clone} method that returns a compatible type; if so, that method is used. * Then it determines if the key implements a known key interface; if so, a raw implementation of that interface is produced. * Last it checks to see if the key is some other unknown {@link SecretKey} type; if so, it captures its value using a {@link SecretKeySpec}. * If none of these checks succeed, an exception is thrown. * * @param expectType the expected result type (must not be {@code null}) * @param key the key object * @return the cloned key, or the original if the key type is not destroyable */ public static T cloneKey(Class expectType, T key) { Assert.checkNotNullParam("expectType", expectType); if (key instanceof Destroyable) { // medium path if (((Destroyable) key).isDestroyed()) { return expectType.cast(key); } else { return expectType.cast(CLONER_CREATOR.get(key.getClass()).apply(key)); } } else { // fast path return expectType.cast(key); } } private static class KeyClonerCreator extends ClassValue> { protected UnaryOperator computeValue(final Class type) { // slow path // check to see if it is *really* destroyable final Method method; try { method = type.getMethod("destroy"); } catch (NoSuchMethodException e) { // nope (because somehow there is no destroy method at all) return UnaryOperator.identity(); } if (method.getDeclaringClass() == Destroyable.class) { // also nope return UnaryOperator.identity(); } // now figure out how to actually transform it. // see if there's a clone method. UnaryOperator op = checkForCloneMethod(type, type); if (op != null) return op; // see if there's a copy constructor. op = checkForCopyCtor(type, type); if (op != null) return op; if (PrivateKey.class.isAssignableFrom(type)) { // some private key type... if (DSAPrivateKey.class.isAssignableFrom(type)) { op = checkForCloneMethod(type, DSAPrivateKey.class); if (op != null) return op; op = checkForCopyCtor(type, DSAPrivateKey.class); if (op != null) return op; return RawDSAPrivateKey::new; } else if (ECPrivateKey.class.isAssignableFrom(type)) { op = checkForCloneMethod(type, ECPrivateKey.class); if (op != null) return op; op = checkForCopyCtor(type, ECPrivateKey.class); if (op != null) return op; return RawECPrivateKey::new; } else if (RSAMultiPrimePrivateCrtKey.class.isAssignableFrom(type)) { op = checkForCloneMethod(type, RSAMultiPrimePrivateCrtKey.class); if (op != null) return op; op = checkForCopyCtor(type, RSAMultiPrimePrivateCrtKey.class); if (op != null) return op; return RawRSAMultiPrimePrivateCrtKey::new; } else if (RSAPrivateKey.class.isAssignableFrom(type)) { op = checkForCloneMethod(type, RSAPrivateKey.class); if (op != null) return op; op = checkForCopyCtor(type, RSAPrivateKey.class); if (op != null) return op; return RawRSAPrivateKey::new; } else if (DHPrivateKey.class.isAssignableFrom(type)) { op = checkForCloneMethod(type, DHPrivateKey.class); if (op != null) return op; op = checkForCopyCtor(type, DHPrivateKey.class); if (op != null) return op; return RawDHPrivateKey::new; } op = checkForCloneMethod(type, PrivateKey.class); if (op != null) return op; op = checkForCopyCtor(type, PrivateKey.class); if (op != null) return op; } else if (SecretKey.class.isAssignableFrom(type)) { // some secret key type... if (PBEKey.class.isAssignableFrom(type)) { op = checkForCloneMethod(type, PBEKey.class); if (op != null) return op; op = checkForCopyCtor(type, PBEKey.class); if (op != null) return op; return RawPBEKey::new; } else { op = checkForCloneMethod(type, SecretKey.class); if (op != null) return op; op = checkForCopyCtor(type, SecretKey.class); if (op != null) return op; // best guess return orig -> new SecretKeySpec(orig.getEncoded(), orig.getAlgorithm()); } } else { op = checkForCloneMethod(type, Key.class); if (op != null) return op; } return orig -> { throw Assert.unsupported(); }; } private UnaryOperator checkForCloneMethod(final Class declType, final Class returnType) { final MethodHandles.Lookup lookup = MethodHandles.lookup(); final MethodHandle handle = doPrivileged((PrivilegedAction) () -> { try { return lookup.findVirtual(declType, "clone", MethodType.methodType(returnType)); } catch (NoSuchMethodException | IllegalAccessException e) { return null; } }); return handle == null ? null : produceOp(handle); } private UnaryOperator checkForCopyCtor(final Class declType, final Class paramType) { final MethodHandles.Lookup lookup = MethodHandles.lookup(); final MethodHandle handle = doPrivileged((PrivilegedAction) () -> { try { return lookup.findConstructor(declType, MethodType.methodType(void.class, paramType)); } catch (NoSuchMethodException | IllegalAccessException e) { return null; } }); return handle == null ? null : produceOp(handle); } private static UnaryOperator produceOp(final MethodHandle handle) { return original -> { try { return (Key) handle.invoke(original); } catch (RuntimeException | Error e) { throw e; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy