summaryrefslogtreecommitdiff
path: root/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/JoseUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/JoseUtils.java')
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/JoseUtils.java373
1 files changed, 373 insertions, 0 deletions
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/JoseUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/JoseUtils.java
new file mode 100644
index 00000000..a67f3523
--- /dev/null
+++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/JoseUtils.java
@@ -0,0 +1,373 @@
+package at.gv.egiz.eaaf.core.impl.utils;
+
+import java.io.IOException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.annotation.Nonnull;
+
+import org.apache.commons.lang3.StringUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.jose4j.jca.ProviderContext;
+import org.jose4j.jwa.AlgorithmConstraints;
+import org.jose4j.jws.AlgorithmIdentifiers;
+import org.jose4j.jws.JsonWebSignature;
+import org.jose4j.jwx.Headers;
+import org.jose4j.jwx.JsonWebStructure;
+import org.jose4j.keys.resolvers.X509VerificationKeyResolver;
+import org.jose4j.lang.JoseException;
+import org.springframework.util.Base64Utils;
+
+import at.gv.egiz.eaaf.core.exception.EaafKeyUsageException;
+import at.gv.egiz.eaaf.core.exceptions.EaafException;
+import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils;
+import at.gv.egiz.eaaf.core.impl.data.Pair;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * {@link JoseUtils} provides static methods JWS and JWE processing.
+ *
+ * @author tlenz
+ *
+ */
+@Slf4j
+public class JoseUtils {
+
+ private static final Provider provider = new BouncyCastleProvider();
+
+ /**
+ * Create a JWS signature.
+ *
+ * <p>
+ * Use {@link org.jose4j.jws.AlgorithmIdentifiers.RSA_PSS_USING_SHA256} in case
+ * of a RSA based key and
+ * {@link org.jose4j.jws.AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256}
+ * in case of an ECC based key.
+ * </p>
+ *
+ * @param keyStore KeyStore that should be used
+ * @param keyAlias Alias of the private key
+ * @param keyPassword Password to access the key
+ * @param payLoad PayLoad to sign
+ * @param addFullCertChain If true the full certificate chain will be
+ * added, otherwise only the
+ * X509CertSha256Fingerprint is added into JOSE
+ * header
+ * @param friendlyNameForLogging FriendlyName for the used KeyStore for logging
+ * purposes only
+ * @return Signed PayLoad in serialized form
+ * @throws EaafException In case of a key-access or key-usage error
+ * @throws JoseException In case of a JOSE error
+ */
+ public static String createSignature(@Nonnull Pair<KeyStore, Provider> keyStore,
+ @Nonnull final String keyAlias, @Nonnull final char[] keyPassword,
+ @Nonnull final String payLoad, boolean addFullCertChain,
+ @Nonnull String friendlyNameForLogging) throws EaafException, JoseException {
+ return createSignature(keyStore, keyAlias, keyPassword, payLoad, addFullCertChain, Collections.emptyMap(),
+ AlgorithmIdentifiers.RSA_PSS_USING_SHA256, AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256,
+ friendlyNameForLogging);
+
+ }
+
+ /**
+ * Create a JWS signature.
+ *
+ * <p>
+ * Use {@link org.jose4j.jws.AlgorithmIdentifiers.RSA_PSS_USING_SHA256} in case
+ * of a RSA based key and
+ * {@link org.jose4j.jws.AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256}
+ * in case of an ECC based key.
+ * </p>
+ *
+ * @param keyStore KeyStore that should be used
+ * @param keyAlias Alias of the private key
+ * @param keyPassword Password to access the key
+ * @param payLoad PayLoad to sign
+ * @param addFullCertChain If true the full certificate chain will be
+ * added, otherwise only the
+ * X509CertSha256Fingerprint is added into JOSE
+ * header
+ * @param joseHeaders HeaderName and HeaderValue that should be set
+ * into JOSE header
+ * @param friendlyNameForLogging FriendlyName for the used KeyStore for logging
+ * purposes only
+ * @return Signed PayLoad in serialized form
+ * @throws EaafException In case of a key-access or key-usage error
+ * @throws JoseException In case of a JOSE error
+ */
+ public static String createSignature(@Nonnull Pair<KeyStore, Provider> keyStore,
+ @Nonnull final String keyAlias, @Nonnull final char[] keyPassword,
+ @Nonnull final String payLoad, boolean addFullCertChain,
+ @Nonnull final Map<String, String> joseHeaders,
+ @Nonnull String friendlyNameForLogging) throws EaafException, JoseException {
+ return createSignature(keyStore, keyAlias, keyPassword, payLoad, addFullCertChain, joseHeaders,
+ AlgorithmIdentifiers.RSA_PSS_USING_SHA256, AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256,
+ friendlyNameForLogging);
+
+ }
+
+ /**
+ * Create a JWS signature.
+ *
+ * @param keyStore KeyStore that should be used
+ * @param keyAlias Alias of the private key
+ * @param keyPassword Password to access the key
+ * @param payLoad PayLoad to sign
+ * @param addFullCertChain If true the full certificate chain will be
+ * added, otherwise only the
+ * X509CertSha256Fingerprint is added into JOSE
+ * header
+ * @param joseHeaders HeaderName and HeaderValue that should be set
+ * into JOSE header
+ * @param rsaAlgToUse Signing algorithm that should be used in case
+ * of a signing key based on RSA
+ * @param eccAlgToUse Signing algorithm that should be used in case
+ * of a signing key based on ECC
+ * @param friendlyNameForLogging FriendlyName for the used KeyStore for logging
+ * purposes only
+ * @return Signed PayLoad in serialized form
+ * @throws EaafException In case of a key-access or key-usage error
+ * @throws JoseException In case of a JOSE error
+ */
+ public static String createSignature(@Nonnull Pair<KeyStore, Provider> keyStore,
+ @Nonnull final String keyAlias, @Nonnull final char[] keyPassword,
+ @Nonnull final String payLoad, boolean addFullCertChain,
+ @Nonnull final Map<String, String> joseHeaders,
+ @Nonnull final String rsaAlgToUse, @Nonnull final String eccAlgToUse,
+ @Nonnull String friendlyNameForLogging) throws EaafException, JoseException {
+
+ final JsonWebSignature jws = new JsonWebSignature();
+
+ // set payload
+ jws.setPayload(payLoad);
+
+ // set JOSE headers
+ for (final Entry<String, String> el : joseHeaders.entrySet()) {
+ log.trace("Set JOSE header: {} with value: {} into JWS", el.getKey(), el.getValue());
+ jws.setHeader(el.getKey(), el.getValue());
+
+ }
+
+ // set signing information
+ final Pair<Key, X509Certificate[]> signingCred = EaafKeyStoreUtils.getPrivateKeyAndCertificates(
+ keyStore.getFirst(), keyAlias, keyPassword, true, friendlyNameForLogging);
+
+ // set verification key
+ jws.setKey(convertToBcKeyIfRequired(signingCred.getFirst()));
+
+ jws.setAlgorithmHeaderValue(getKeyOperationAlgorithmFromCredential(
+ jws.getKey(), rsaAlgToUse, eccAlgToUse, friendlyNameForLogging));
+
+ // set special provider if required
+ if (keyStore.getSecond() != null) {
+ log.trace("Injecting special Java Security Provider: {}", keyStore.getSecond().getName());
+ final ProviderContext providerCtx = new ProviderContext();
+ providerCtx.getSuppliedKeyProviderContext().setSignatureProvider(keyStore.getSecond().getName());
+ providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME);
+ jws.setProviderContext(providerCtx);
+
+ } else {
+ final ProviderContext providerCtx = new ProviderContext();
+ providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME);
+ jws.setProviderContext(providerCtx);
+
+ }
+
+ if (addFullCertChain) {
+ jws.setCertificateChainHeaderValue(signingCred.getSecond());
+
+ }
+
+ jws.setX509CertSha256ThumbprintHeaderValue(signingCred.getSecond()[0]);
+
+ return jws.getCompactSerialization();
+
+ }
+
+ /**
+ * Verify a JOSE signature.
+ *
+ * @param serializedContent Serialized content that should be verified
+ * @param trustedCerts Trusted certificates that should be used for
+ * verification
+ * @param constraints {@link AlgorithmConstraints} for verification
+ * @return {@link JwsResult} object
+ * @throws JoseException In case of a signature verification error
+ * @throws IOException In case of a general error
+ */
+ public static JwsResult validateSignature(@Nonnull final String serializedContent,
+ @Nonnull final List<X509Certificate> trustedCerts, @Nonnull final AlgorithmConstraints constraints)
+ throws JoseException, IOException {
+ final JsonWebSignature jws = new JsonWebSignature();
+ // set payload
+ jws.setCompactSerialization(serializedContent);
+
+ // set security constrains
+ jws.setAlgorithmConstraints(constraints);
+
+ // load signinc certs
+ Key selectedKey = null;
+ final List<X509Certificate> x5cCerts = jws.getCertificateChainHeaderValue();
+ final String x5t256 = jws.getX509CertSha256ThumbprintHeaderValue();
+ if (x5cCerts != null) {
+ log.debug("Found x509 certificate in JOSE header ... ");
+ log.trace("Sorting received X509 certificates ... ");
+ final List<X509Certificate> sortedX5cCerts = X509Utils.sortCertificates(x5cCerts);
+
+
+
+ if (trustedCerts.contains(sortedX5cCerts.get(0))) {
+ selectedKey = sortedX5cCerts.get(0).getPublicKey();
+
+ } else {
+ log.info("Can NOT find JOSE certificate in truststore.");
+ if (log.isDebugEnabled()) {
+ try {
+ log.debug("Cert: {}", Base64Utils.encodeToString(sortedX5cCerts.get(0).getEncoded()));
+
+ } catch (final CertificateEncodingException e) {
+ log.warn("Can not create DEBUG output", e);
+
+ }
+ }
+ }
+
+ } else if (StringUtils.isNotEmpty(x5t256)) {
+ log.debug("Found x5t256 fingerprint in JOSE header .... ");
+ final X509VerificationKeyResolver x509VerificationKeyResolver = new X509VerificationKeyResolver(
+ trustedCerts);
+ selectedKey = x509VerificationKeyResolver.resolveKey(jws, Collections.<JsonWebStructure>emptyList());
+
+ } else {
+ throw new JoseException("JWS contains NO signature certificate or NO certificate fingerprint");
+
+ }
+
+ if (selectedKey == null) {
+ throw new JoseException("Can NOT select verification key for JWS. Signature verification FAILED");
+
+ }
+
+ //set BouncyCastleProvider as default provider
+ final ProviderContext providerCtx = new ProviderContext();
+ providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME);
+ jws.setProviderContext(providerCtx);
+
+ // set verification key
+ jws.setKey(convertToBcKeyIfRequired(selectedKey));
+
+ // load payLoad
+ return new JwsResult(
+ jws.verifySignature(),
+ jws.getUnverifiedPayload(),
+ jws.getHeaders(),
+ x5cCerts);
+
+ }
+
+
+ /**
+ * Convert an ECC public-key into BouncyCastle implementation.
+ *
+ * <p> IAIK JCE / Eccelerate ECC Keys are not compatible to JWS impl.</p>
+ * @param input Key
+ * @return input Key, or BC ECC-Key in case of a ECC Key
+ */
+ public static Key convertToBcKeyIfRequired(Key input) {
+ try {
+ if (input instanceof ECPublicKey
+ && "iaik.security.ec.common.ECPublicKey".equals(input.getClass().getName())) {
+
+ //convert Key to BouncyCastle KeyImplemenation because there is an
+ //incompatibility with IAIK EC Keys and JWS signature-verfification implementation
+ PublicKey publicKey = KeyFactory.getInstance(
+ input.getAlgorithm(), provider).generatePublic(
+ new X509EncodedKeySpec(input.getEncoded()));
+ return publicKey;
+
+ } else if (input instanceof ECPrivateKey
+ && "iaik.security.ec.common.ECPrivateKey".equals(input.getClass().getName())) {
+ //convert Key to BouncyCastle KeyImplemenation because there is an
+ //incompatibility with IAIK EC Keys and JWS signature-creation implementation
+ Key privateKey = KeyFactory.getInstance(
+ input.getAlgorithm(), provider).generatePrivate(
+ new PKCS8EncodedKeySpec(input.getEncoded()));
+
+ return privateKey;
+
+ }
+
+ } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+ log.warn("Can NOT convert {} to {}. The verification may FAIL.",
+ input.getClass().getName(), PublicKey.class.getName(), e);
+
+ }
+
+ return input;
+
+ }
+
+ /**
+ * Select signature algorithm for a given credential.
+ *
+ * @param key {@link X509Credential} that will be used for
+ * key operations
+ * @param rsaSigAlgorithm RSA based algorithm that should be used in case
+ * of RSA credential
+ * @param ecSigAlgorithm EC based algorithm that should be used in case
+ * of RSA credential
+ * @param friendlyNameForLogging KeyStore friendlyName for logging purposes
+ * @return either the RSA based algorithm or the EC based algorithm
+ * @throws EaafKeyUsageException In case of an unsupported private-key type
+ */
+ private static String getKeyOperationAlgorithmFromCredential(Key key,
+ String rsaSigAlgorithm, String ecSigAlgorithm, String friendlyNameForLogging)
+ throws EaafKeyUsageException {
+ if (key instanceof RSAPrivateKey) {
+ return rsaSigAlgorithm;
+
+ } else if (key instanceof ECPrivateKey) {
+ return ecSigAlgorithm;
+
+ } else {
+ log.warn("Could NOT select the cryptographic algorithm from Private-Key type");
+ throw new EaafKeyUsageException(EaafKeyUsageException.ERROR_CODE_01,
+ friendlyNameForLogging,
+ "Can not select cryptographic algorithm");
+
+ }
+
+ }
+
+ private JoseUtils() {
+
+ }
+
+ @Getter
+ @AllArgsConstructor
+ public static class JwsResult {
+ final boolean valid;
+ final String payLoad;
+ final Headers fullJoseHeader;
+ final List<X509Certificate> x5cCerts;
+
+ }
+}