/**
 * 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.cxf.rs.security.jose.jws;

import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;

import org.apache.cxf.Bus;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.common.util.PropertyUtils;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageUtils;
import org.apache.cxf.phase.PhaseInterceptorChain;
import org.apache.cxf.rs.security.jose.common.JoseConstants;
import org.apache.cxf.rs.security.jose.common.JoseUtils;
import org.apache.cxf.rs.security.jose.common.KeyManagementUtils;
import org.apache.cxf.rs.security.jose.jwa.AlgorithmUtils;
import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
import org.apache.cxf.rs.security.jose.jwk.JsonWebKey;
import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys;
import org.apache.cxf.rs.security.jose.jwk.JwkUtils;
import org.apache.cxf.rs.security.jose.jwk.KeyOperation;
import org.apache.cxf.rs.security.jose.jwk.KeyType;
import org.apache.cxf.rs.security.jose.jwk.PublicKeyUse;
import org.apache.cxf.rt.security.crypto.MessageDigestUtils;

public final class JwsUtils {
    private static final Logger LOG = LogUtils.getL7dLogger(JwsUtils.class);
    
    private JwsUtils() {
        
    }
    public static String sign(PrivateKey key, SignatureAlgorithm algo, String content) {
        return sign(key, algo, content, null);
    }
    
    
    public static String sign(PrivateKey key, SignatureAlgorithm algo, String content, String ct) {
        return sign(getPrivateKeySignatureProvider(key, algo), content, ct);
    }
    public static String sign(String encodedKey, SignatureAlgorithm algo, String content) {
        return sign(JoseUtils.decode(encodedKey), algo, content);
    }
    public static String sign(byte[] key, SignatureAlgorithm algo, String content) {
        return sign(key, algo, content, null);
    }
    public static String sign(byte[] key, SignatureAlgorithm algo, String content, String ct) {
        return sign(getHmacSignatureProvider(key, algo), content, ct);
    }
    public static String verify(PublicKey key, SignatureAlgorithm algo, String content) {
        JwsCompactConsumer jws = verify(getPublicKeySignatureVerifier(key, algo), content);
        return jws.getDecodedJwsPayload();
    }
    public static String verify(String encodedKey, SignatureAlgorithm algo, String content) {
        return verify(JoseUtils.decode(encodedKey), algo, content);
    }
    public static String verify(byte[] key, SignatureAlgorithm algo, String content) {
        JwsCompactConsumer jws = verify(getHmacSignatureVerifier(key, algo), content);
        return jws.getDecodedJwsPayload();
    }
    public static JwsSignatureProvider getSignatureProvider(JsonWebKey jwk) {
        return getSignatureProvider(jwk, null);
    }
    public static JwsSignatureProvider getSignatureProvider(JsonWebKey jwk, 
                                                            SignatureAlgorithm defaultAlgorithm) {
        SignatureAlgorithm sigAlgo = jwk.getAlgorithm() == null ? defaultAlgorithm 
            : SignatureAlgorithm.getAlgorithm(jwk.getAlgorithm());
        JwsSignatureProvider theSigProvider = null;
        KeyType keyType = jwk.getKeyType();
        if (KeyType.RSA == keyType) {
            theSigProvider = getPrivateKeySignatureProvider(JwkUtils.toRSAPrivateKey(jwk),
                                                            sigAlgo);
        } else if (KeyType.OCTET == keyType) { 
            byte[] key = JoseUtils.decode((String)jwk.getProperty(JsonWebKey.OCTET_KEY_VALUE));
            theSigProvider = getHmacSignatureProvider(key, sigAlgo);
        } else if (KeyType.EC == jwk.getKeyType()) {
            theSigProvider = getPrivateKeySignatureProvider(JwkUtils.toECPrivateKey(jwk),
                                                            sigAlgo);
        }
        return theSigProvider;
    }
    public static JwsSignatureProvider getPrivateKeySignatureProvider(PrivateKey key, SignatureAlgorithm algo) {
        if (algo == null) {
            LOG.warning("No signature algorithm was defined");
            throw new JwsException(JwsException.Error.ALGORITHM_NOT_SET);
        }
        if (key instanceof ECPrivateKey) {
            return new EcDsaJwsSignatureProvider((ECPrivateKey)key, algo);
        } else if (key instanceof RSAPrivateKey) {
            return new PrivateKeyJwsSignatureProvider(key, algo);
        }
        
        return null;
    }
    public static JwsSignatureProvider getHmacSignatureProvider(String encodedKey, SignatureAlgorithm algo) {
        return getHmacSignatureProvider(JoseUtils.decode(encodedKey), algo);
    }
    public static JwsSignatureProvider getHmacSignatureProvider(byte[] key, SignatureAlgorithm algo) {
        if (algo == null) {
            LOG.warning("No signature algorithm was defined");
            throw new JwsException(JwsException.Error.ALGORITHM_NOT_SET);
        }
        if (AlgorithmUtils.isHmacSign(algo.getJwaName())) {
            return new HmacJwsSignatureProvider(key, algo);
        }
        return null;
    }
    public static JwsSignatureVerifier getSignatureVerifier(JsonWebKey jwk) {
        return getSignatureVerifier(jwk, null);
    }
    public static JwsSignatureVerifier getSignatureVerifier(JsonWebKey jwk, SignatureAlgorithm defaultAlgorithm) {
        SignatureAlgorithm sigAlgo = jwk.getAlgorithm() == null ? defaultAlgorithm 
            : SignatureAlgorithm.getAlgorithm(jwk.getAlgorithm());
        JwsSignatureVerifier theVerifier = null;
        KeyType keyType = jwk.getKeyType();
        if (KeyType.RSA == keyType) {
            theVerifier = getPublicKeySignatureVerifier(JwkUtils.toRSAPublicKey(jwk, true), sigAlgo);
        } else if (KeyType.OCTET == keyType) { 
            byte[] key = JoseUtils.decode((String)jwk.getProperty(JsonWebKey.OCTET_KEY_VALUE));
            theVerifier = getHmacSignatureVerifier(key, sigAlgo);
        } else if (KeyType.EC == keyType) {
            theVerifier = getPublicKeySignatureVerifier(JwkUtils.toECPublicKey(jwk), sigAlgo);
        }
        return theVerifier;
    }
    public static JwsSignatureVerifier getPublicKeySignatureVerifier(X509Certificate cert, SignatureAlgorithm algo) {
        if (cert != null) {
            if (algo == null) {
                algo = getDefaultPublicKeyAlgorithm(cert.getPublicKey());
            }
            if (algo == null) {
                LOG.warning("No signature algorithm was defined");
                throw new JwsException(JwsException.Error.ALGORITHM_NOT_SET);
            }    
            
            if (cert.getPublicKey() instanceof RSAPublicKey) {
                return new PublicKeyJwsSignatureVerifier(cert, algo);
            } else if (cert.getPublicKey() instanceof ECPublicKey) {
                return new EcDsaJwsSignatureVerifier(cert, algo);
            }
        }
        
        return null;
    }
    public static JwsSignatureVerifier getPublicKeySignatureVerifier(PublicKey key, SignatureAlgorithm algo) {
        if (algo == null) {
            LOG.warning("No signature algorithm was defined");
            throw new JwsException(JwsException.Error.ALGORITHM_NOT_SET);
        }
        
        if (key instanceof RSAPublicKey) {
            return new PublicKeyJwsSignatureVerifier(key, algo);
        } else if (key instanceof ECPublicKey) {
            return new EcDsaJwsSignatureVerifier(key, algo);
        }
        
        return null;
    }
    public static JwsSignatureVerifier getHmacSignatureVerifier(String encodedKey, SignatureAlgorithm algo) {
        return getHmacSignatureVerifier(JoseUtils.decode(encodedKey), algo);    
    }
    public static JwsSignatureVerifier getHmacSignatureVerifier(byte[] key, SignatureAlgorithm algo) {
        if (algo == null) {
            LOG.warning("No signature algorithm was defined");
            throw new JwsException(JwsException.Error.ALGORITHM_NOT_SET);
        }
        
        if (AlgorithmUtils.isHmacSign(algo.getJwaName())) {
            return new HmacJwsSignatureVerifier(key, algo);
        }
        return null;
    }
    
    public static Map<SignatureAlgorithm, List<JwsJsonSignatureEntry>> getJwsJsonSignatureMap(
        List<JwsJsonSignatureEntry> signatures) {
        Map<SignatureAlgorithm, List<JwsJsonSignatureEntry>> map = new HashMap<>();
        for (JwsJsonSignatureEntry entry : signatures) {
            SignatureAlgorithm sigAlgorithm = entry.getUnionHeader().getSignatureAlgorithm();
            List<JwsJsonSignatureEntry> entries = map.get(sigAlgorithm);
            if (entries == null) {
                entries = new ArrayList<>();
            }
            entries.add(entry);
            map.put(sigAlgorithm, entries);
        }
        return map;
    }
    
    public static JwsSignatureProvider loadSignatureProvider(boolean required) {
        return loadSignatureProvider(null, required);    
    }
    
    public static JwsSignatureProvider loadSignatureProvider(JwsHeaders headers, boolean required) {
        Properties props = loadSignatureOutProperties(required);
        if (props == null) {
            return null;
        }
        JwsSignatureProvider theSigProvider = loadSignatureProvider(props, headers);
        if (headers != null) {
            headers.setSignatureAlgorithm(theSigProvider.getAlgorithm());
        }
        return theSigProvider;
    }
    public static Properties loadSignatureOutProperties(boolean required) {
        Message m = PhaseInterceptorChain.getCurrentMessage();
        return KeyManagementUtils.loadStoreProperties(m, required, 
                                                      JoseConstants.RSSEC_SIGNATURE_OUT_PROPS, 
                                                      JoseConstants.RSSEC_SIGNATURE_PROPS);
        
    }
    public static Properties loadSignatureInProperties(boolean required) {
        Message m = PhaseInterceptorChain.getCurrentMessage();
        return KeyManagementUtils.loadStoreProperties(m, required, 
                                                      JoseConstants.RSSEC_SIGNATURE_IN_PROPS, 
                                                      JoseConstants.RSSEC_SIGNATURE_PROPS);
        
    }
    public static Properties loadSignatureProperties(String propertiesName, boolean required) {
        Message m = PhaseInterceptorChain.getCurrentMessage();
        return KeyManagementUtils.loadStoreProperties(m, required, propertiesName, null);
        
    }
    public static Properties loadSignatureProperties(String propertiesLoc, Bus bus) {
        try {
            return JoseUtils.loadProperties(propertiesLoc, bus);
        } catch (Exception ex) {
            throw new JwsException(JwsException.Error.NO_INIT_PROPERTIES, ex);
        }
    }
    public static JwsSignatureVerifier loadSignatureVerifier(boolean required) {
        return loadSignatureVerifier(null, required);
    }
    public static JwsSignatureVerifier loadSignatureVerifier(String propertiesLoc, Bus bus) {
        Properties props = loadSignatureProperties(propertiesLoc, bus);
        return loadSignatureVerifier(props, null);
    }
    public static JwsSignatureVerifier loadSignatureVerifier(JwsHeaders headers, boolean required) {
        Properties props = loadSignatureInProperties(required);
        return loadSignatureVerifier(props, headers);
    }
    public static List<JwsSignatureProvider> loadSignatureProviders(String propLoc, Message m) {
        Properties props = loadJwsProperties(m, propLoc);
        JwsSignatureProvider theSigProvider = loadSignatureProvider(m, props, null, true);
        if (theSigProvider != null) {
            return Collections.singletonList(theSigProvider);
        }
        List<JwsSignatureProvider> theSigProviders = null; 
        if (JoseConstants.HEADER_JSON_WEB_KEY.equals(props.get(JoseConstants.RSSEC_KEY_STORE_TYPE))) {
            List<JsonWebKey> jwks = JwkUtils.loadJsonWebKeys(m, props, KeyOperation.SIGN);
            if (jwks != null) {
                theSigProviders = new ArrayList<JwsSignatureProvider>(jwks.size());
                for (JsonWebKey jwk : jwks) {
                    theSigProviders.add(JwsUtils.getSignatureProvider(jwk));
                }
            }
        }
        if (theSigProviders == null) {
            LOG.warning("Providers are not available");
            throw new JwsException(JwsException.Error.NO_PROVIDER);
        }
        return theSigProviders;
    }
    
    public static List<JwsSignatureVerifier> loadSignatureVerifiers(String propLoc, Message m) {
        Properties props = loadJwsProperties(m, propLoc);
        JwsSignatureVerifier theVerifier = loadSignatureVerifier(m, props, null, true);
        if (theVerifier != null) {
            return Collections.singletonList(theVerifier);
        }
        List<JwsSignatureVerifier> theVerifiers = null; 
        if (JoseConstants.HEADER_JSON_WEB_KEY.equals(props.get(JoseConstants.RSSEC_KEY_STORE_TYPE))) {
            List<JsonWebKey> jwks = JwkUtils.loadJsonWebKeys(m, props, KeyOperation.VERIFY);
            if (jwks != null) {
                theVerifiers = new ArrayList<JwsSignatureVerifier>(jwks.size());
                for (JsonWebKey jwk : jwks) {
                    theVerifiers.add(JwsUtils.getSignatureVerifier(jwk));
                }
            }
        }
        if (theVerifiers == null) {
            LOG.warning("Verifiers are not available");
            throw new JwsException(JwsException.Error.NO_VERIFIER);
        }
        return theVerifiers;
    }
    public static boolean validateCriticalHeaders(JwsHeaders headers) {
        //TODO: validate JWS specific constraints
        return JoseUtils.validateCriticalHeaders(headers);
    }
    public static JwsSignatureProvider loadSignatureProvider(Properties props,
                                                             JwsHeaders headers) {
        return loadSignatureProvider(PhaseInterceptorChain.getCurrentMessage(),
                                     props, headers);
    }
    public static JwsSignatureProvider loadSignatureProvider(Message m, 
                                                              Properties props,
                                                              JwsHeaders headers) {
        return loadSignatureProvider(m, props, headers, false);
    }
    public static JwsSignatureProvider loadSignatureProvider(String propertiesLoc, Bus bus) {
        Properties props = loadSignatureProperties(propertiesLoc, bus);
        return loadSignatureProvider(props, null);
    }
    
    private static JwsSignatureProvider loadSignatureProvider(Message m, 
                                                             Properties props,
                                                             JwsHeaders headers,
                                                             boolean ignoreNullProvider) {
        JwsSignatureProvider theSigProvider = null;
        
        boolean includeCert = headers != null && MessageUtils.getContextualBoolean(
                m, JoseConstants.RSSEC_SIGNATURE_INCLUDE_CERT, false);
        boolean includeCertSha1 = headers != null && MessageUtils.getContextualBoolean(
                m, JoseConstants.RSSEC_SIGNATURE_INCLUDE_CERT_SHA1, false);
        boolean includeCertSha256 = !includeCertSha1 && headers != null && MessageUtils.getContextualBoolean(
                                  m, JoseConstants.RSSEC_SIGNATURE_INCLUDE_CERT_SHA256, false);
        boolean includeKeyId = headers != null && MessageUtils.getContextualBoolean(
                m, JoseConstants.RSSEC_SIGNATURE_INCLUDE_KEY_ID, false);
                                                                                
        if (JoseConstants.HEADER_JSON_WEB_KEY.equals(props.get(JoseConstants.RSSEC_KEY_STORE_TYPE))) {
            JsonWebKey jwk = JwkUtils.loadJsonWebKey(m, props, KeyOperation.SIGN);
            if (jwk != null) {
                SignatureAlgorithm signatureAlgo = getSignatureAlgorithm(m, 
                                                             props, 
                                                             SignatureAlgorithm.getAlgorithm(jwk.getAlgorithm()), 
                                                             getDefaultKeyAlgorithm(jwk));
                theSigProvider = JwsUtils.getSignatureProvider(jwk, signatureAlgo);
                
                boolean includePublicKey = headers != null && MessageUtils.getContextualBoolean(
                    m, JoseConstants.RSSEC_SIGNATURE_INCLUDE_PUBLIC_KEY, false);
                
                if (includeCert) {
                    JwkUtils.includeCertChain(jwk, headers, signatureAlgo.getJwaName());
                }
                if (includeCertSha1) {
                    String digest = KeyManagementUtils.loadDigestAndEncodeX509Certificate(m, 
                                        props, MessageDigestUtils.ALGO_SHA_1);
                    if (digest != null) {
                        headers.setX509Thumbprint(digest);
                    }
                } else if (includeCertSha256) {
                    String digest = KeyManagementUtils.loadDigestAndEncodeX509Certificate(m, 
                                        props, MessageDigestUtils.ALGO_SHA_256);
                    if (digest != null) {
                        headers.setX509ThumbprintSHA256(digest);
                    }
                }
                if (includePublicKey) {
                    JwkUtils.includePublicKey(jwk, headers, signatureAlgo.getJwaName());
                }
                if (includeKeyId && jwk.getKeyId() != null) {
                    headers.setKeyId(jwk.getKeyId());
                }
            }
        } else {
            SignatureAlgorithm signatureAlgo = getSignatureAlgorithm(m, props, null, null);
            if (signatureAlgo == SignatureAlgorithm.NONE) {
                theSigProvider = new NoneJwsSignatureProvider();
            } else {
                PrivateKey pk = KeyManagementUtils.loadPrivateKey(m, props, KeyOperation.SIGN);
                if (signatureAlgo == null) {
                    signatureAlgo = getDefaultPrivateKeyAlgorithm(pk);
                }
                
                theSigProvider = getPrivateKeySignatureProvider(pk, signatureAlgo);
                if (includeCert) {
                    headers.setX509Chain(KeyManagementUtils.loadAndEncodeX509CertificateOrChain(m, props));
                }
                if (includeCertSha1) {
                    String digest = KeyManagementUtils.loadDigestAndEncodeX509Certificate(m, 
                                        props, MessageDigestUtils.ALGO_SHA_1);
                    if (digest != null) {
                        headers.setX509Thumbprint(digest);
                    }
                } else if (includeCertSha256) {
                    String digest = KeyManagementUtils.loadDigestAndEncodeX509Certificate(m, 
                                        props, MessageDigestUtils.ALGO_SHA_256);
                    if (digest != null) {
                        headers.setX509ThumbprintSHA256(digest);
                    }
                }  
                if (includeKeyId && props.containsKey(JoseConstants.RSSEC_KEY_STORE_ALIAS)) {
                    headers.setKeyId(props.getProperty(JoseConstants.RSSEC_KEY_STORE_ALIAS));
                }
            }
        }
        if (theSigProvider == null && !ignoreNullProvider) {
            LOG.warning("Provider is not available");
            throw new JwsException(JwsException.Error.NO_PROVIDER);
        }
        return theSigProvider;
    }
    public static JwsSignatureVerifier loadSignatureVerifier(Properties props,
                                                             JwsHeaders inHeaders) {
        return loadSignatureVerifier(PhaseInterceptorChain.getCurrentMessage(),
                                     props, inHeaders, false);
    }
    public static JwsSignatureVerifier loadSignatureVerifier(Message m,
                                                              Properties props,
                                                              JwsHeaders inHeaders, 
                                                              boolean ignoreNullVerifier) {
        JwsSignatureVerifier theVerifier = null;
        String inHeaderKid = null;
        if (inHeaders != null) {
            inHeaderKid = inHeaders.getKeyId();
            //TODO: optionally validate inHeaders.getAlgorithm against a property in props
            if (inHeaders.getHeader(JoseConstants.HEADER_JSON_WEB_KEY) != null) {
                JsonWebKey publicJwk = inHeaders.getJsonWebKey();
                if (inHeaderKid != null && !inHeaderKid.equals(publicJwk.getKeyId())
                    || !MessageUtils.getContextualBoolean(m, JoseConstants.RSSEC_ACCEPT_PUBLIC_KEY, false)) {
                    throw new JwsException(JwsException.Error.INVALID_KEY);
                }
                return getSignatureVerifier(publicJwk, 
                                            inHeaders.getSignatureAlgorithm());
            } else if (inHeaders.getHeader(JoseConstants.HEADER_X509_CHAIN) != null) {
                List<X509Certificate> chain = KeyManagementUtils.toX509CertificateChain(inHeaders.getX509Chain());
                KeyManagementUtils.validateCertificateChain(props, chain);
                return getPublicKeySignatureVerifier(chain.get(0), 
                                                     inHeaders.getSignatureAlgorithm());
            } else if (inHeaders.getHeader(JoseConstants.HEADER_X509_THUMBPRINT) != null) {
                X509Certificate foundCert = 
                    KeyManagementUtils.getCertificateFromThumbprint(inHeaders.getX509Thumbprint(), 
                                                                    MessageDigestUtils.ALGO_SHA_1,
                                                                    m, props);
                if (foundCert != null) {
                    return getPublicKeySignatureVerifier(foundCert, 
                                                         inHeaders.getSignatureAlgorithm());
                }
            } else if (inHeaders.getHeader(JoseConstants.HEADER_X509_THUMBPRINT_SHA256) != null) {
                X509Certificate foundCert =
                    KeyManagementUtils.getCertificateFromThumbprint(inHeaders.getX509ThumbprintSHA256(),
                                                                    MessageDigestUtils.ALGO_SHA_256,
                                                                    m, props);
                if (foundCert != null) {
                    return getPublicKeySignatureVerifier(foundCert,
                                                         inHeaders.getSignatureAlgorithm());
                }
            }
        }
        
        if (JoseConstants.HEADER_JSON_WEB_KEY.equals(props.get(JoseConstants.RSSEC_KEY_STORE_TYPE))) {
            JsonWebKey jwk = JwkUtils.loadJsonWebKey(m, props, KeyOperation.VERIFY, inHeaderKid);
            if (jwk != null) {
                SignatureAlgorithm signatureAlgo = getSignatureAlgorithm(m, props, 
                                                             SignatureAlgorithm.getAlgorithm(jwk.getAlgorithm()), 
                                                             getDefaultKeyAlgorithm(jwk));
                theVerifier = getSignatureVerifier(jwk, signatureAlgo);
            }
            
        } else {
            SignatureAlgorithm signatureAlgo = getSignatureAlgorithm(m, props, null, null);
            if (signatureAlgo == SignatureAlgorithm.NONE 
                && SignatureAlgorithm.NONE.getJwaName().equals(inHeaders.getAlgorithm())) {
                theVerifier = new NoneJwsSignatureVerifier();
            } else {
                X509Certificate[] certs = KeyManagementUtils.loadX509CertificateOrChain(m, props);
                if (certs != null && certs.length > 0) {
                    theVerifier = getPublicKeySignatureVerifier(certs[0], signatureAlgo);
                }
            }
        }
        if (theVerifier == null && !ignoreNullVerifier) {
            LOG.warning("Verifier is not available");
            throw new JwsException(JwsException.Error.NO_VERIFIER);
        }
        return theVerifier;
    }
    private static Properties loadJwsProperties(Message m, String propLoc) {
        try {
            return JoseUtils.loadProperties(propLoc, m.getExchange().getBus());
        } catch (Exception ex) {
            LOG.warning("JWS init properties are not available");
            throw new JwsException(JwsException.Error.NO_INIT_PROPERTIES);
        }
    }

    public static SignatureAlgorithm getSignatureAlgorithm(Message m, Properties props,
                                               SignatureAlgorithm algo,
                                               SignatureAlgorithm defaultAlgo) {
        if (algo == null) {
            algo = getSignatureAlgorithm(m, props, defaultAlgo);
        }
        return algo;
    }
    public static SignatureAlgorithm getSignatureAlgorithm(Properties props,
                                                           SignatureAlgorithm defaultAlgo) {
        return getSignatureAlgorithm(PhaseInterceptorChain.getCurrentMessage(),
                                     props, defaultAlgo);
    }
    public static SignatureAlgorithm getSignatureAlgorithm(Message m,
                                                           Properties props,
                                                           SignatureAlgorithm defaultAlgo) {
        String algo = KeyManagementUtils.getKeyAlgorithm(m,
                                                  props,
                                                  JoseConstants.RSSEC_SIGNATURE_ALGORITHM,
                                                  defaultAlgo == null ? null : defaultAlgo.getJwaName());
        return SignatureAlgorithm.getAlgorithm(algo);
    }
    private static SignatureAlgorithm getDefaultKeyAlgorithm(JsonWebKey jwk) {
        KeyType keyType = jwk.getKeyType();
        if (KeyType.OCTET == keyType) {
            return SignatureAlgorithm.HS256;
        } else if (KeyType.EC == keyType) {
            return SignatureAlgorithm.ES256;
        } else {
            return SignatureAlgorithm.RS256;
        }
    }
    private static SignatureAlgorithm getDefaultPrivateKeyAlgorithm(PrivateKey key) {
        if (key instanceof RSAPrivateKey) {
            return SignatureAlgorithm.RS256;
        } else if (key instanceof ECPrivateKey) {
            return SignatureAlgorithm.ES256;
        } else {
            return null;
        }
    }
    private static SignatureAlgorithm getDefaultPublicKeyAlgorithm(PublicKey key) {
        if (key instanceof RSAPublicKey) {
            return SignatureAlgorithm.RS256;
        } else if (key instanceof ECPublicKey) {
            return SignatureAlgorithm.ES256;
        } else {
            return null;
        }
    }
    public static JwsCompactConsumer verify(JwsSignatureVerifier v, String content) {
        JwsCompactConsumer jws = new JwsCompactConsumer(content);
        if (!jws.verifySignatureWith(v)) {
            throw new JwsException(JwsException.Error.INVALID_SIGNATURE);
        }
        return jws;
    }
    public static String sign(JwsSignatureProvider jwsSig, String content, String ct) {
        JwsHeaders headers = new JwsHeaders();
        if (ct != null) {
            headers.setContentType(ct);
        }
        JwsCompactProducer jws = new JwsCompactProducer(headers, content);
        jws.signWith(jwsSig);
        return jws.getSignedEncodedJws();
    }
    public static void validateJwsCertificateChain(List<X509Certificate> certs) {
        
        Properties props = loadSignatureInProperties(true);
        KeyManagementUtils.validateCertificateChain(props, certs);
    }
    public static boolean isPayloadUnencoded(JwsHeaders jwsHeaders) {
        return jwsHeaders.getPayloadEncodingStatus() == Boolean.FALSE;
    }
    
    public static void checkSignatureKeySize(Key key) {
        if (key instanceof RSAKey && ((RSAKey)key).getModulus().bitLength() < 2048) {
            LOG.fine("A key of size: " + ((RSAKey)key).getModulus().bitLength()
                     + " was used with an RSA signature algorithm. 2048 is the minimum size that is accepted");
            throw new JwsException(JwsException.Error.INVALID_KEY);
        }
    }
    public static JsonWebKeys loadPublicVerificationKeys(Message m, Properties props) {
        String storeType = props.getProperty(JoseConstants.RSSEC_KEY_STORE_TYPE);
        if ("jwk".equals(storeType)) {
            return JwkUtils.loadPublicJwkSet(m, props);
        } else {
            X509Certificate[] certs = null;
            if (PropertyUtils.isTrue(props.get(JoseConstants.RSSEC_SIGNATURE_INCLUDE_CERT))) {
                certs = KeyManagementUtils.loadX509CertificateOrChain(m, props);
            }
            PublicKey key = certs != null && certs.length > 0 
                ? certs[0].getPublicKey() : KeyManagementUtils.loadPublicKey(m, props); 
            JsonWebKey jwk = JwkUtils.fromPublicKey(key, props, JoseConstants.RSSEC_SIGNATURE_ALGORITHM);
            jwk.setPublicKeyUse(PublicKeyUse.SIGN);
            if (certs != null) {
                jwk.setX509Chain(KeyManagementUtils.encodeX509CertificateChain(certs));
            }
            return new JsonWebKeys(jwk);
        }
    }
}
