JsonSignature.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */


package org.openspcoop2.utils.security;

import java.security.PrivateKey;
import java.util.Iterator;
import java.util.Properties;

import javax.crypto.SecretKey;

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.jws.JwsCompactProducer;
import org.apache.cxf.rs.security.jose.jws.JwsException;
import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
import org.apache.cxf.rs.security.jose.jws.JwsJsonProducer;
import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider;
import org.apache.cxf.rs.security.jose.jws.JwsUtils;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.certificate.KeyStore;
import org.openspcoop2.utils.certificate.KeystoreType;

/**	
 * JsonSignature
 *
 * @author Poli Andrea (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class JsonSignature {

	private JwsSignatureProvider provider;
	private JWSOptions options;
	private JwsHeaders headers;
	private JwtHeaders jwtHeaders;
	
	public JsonSignature(Properties props, JWSOptions options) throws UtilsException{
		this(props, null, options);
	}
	public JsonSignature(Properties props, JwtHeaders jwtHeaders, JWSOptions options) throws UtilsException{
		try {
			this.headers = new JwsHeaders(props);
			this.provider = JsonUtils.getJwsSymmetricProvider(props);
			if(this.provider==null) {
				try {
					this.provider = JwsUtils.loadSignatureProvider(JsonUtils.newMessage(), props, this.headers);
				}catch(JwsException jwsExc) {
					if(jwsExc.getMessage()!=null && jwsExc.getMessage().contains("NO_PROVIDER")) {
						// caso in cui la chiave privata PKCS11 non e' stata mappata in un PrivateKeyJwsSignatureProvider
						// caso pkcs11 dove la chiave privata non e' ne ECPrivateKey ne RSAPrivateKey gestita dal metodo getPrivateKeySignatureProvider
						// ad es. può essere sun.security.pkcs11.P11Key$P11PrivateKey
						this.provider = JsonUtils.getJwsAsymmetricProvider(props);
						if(this.provider==null) {
							// rilancio eccezione precedente
							throw jwsExc;
						}
					}
				}
			}
			this.options=options;
			this.jwtHeaders = jwtHeaders;
		}catch(Exception t) {
			throw JsonUtils.convert(options.getSerialization(), JsonUtils.SIGNATURE,JsonUtils.SENDER,t);
		}
	}

	public JsonSignature(java.security.KeyStore keystore, boolean secretKey, String alias, String passwordPrivateKey, String signatureAlgorithm, JWSOptions options) throws UtilsException{
		this(new KeyStore(keystore), secretKey, alias, passwordPrivateKey, signatureAlgorithm, 
				null, options);
	}
	public JsonSignature(KeyStore keystore, boolean secretKey, String alias, String passwordPrivateKey, String signatureAlgorithm, JWSOptions options) throws UtilsException{
		this(keystore, secretKey, alias, passwordPrivateKey, signatureAlgorithm, 
				null, options);
	}
	public JsonSignature(java.security.KeyStore keystore, boolean secretKey, String alias, String passwordPrivateKey, String signatureAlgorithm, 
			JwtHeaders jwtHeaders, JWSOptions options) throws UtilsException{
		this(new KeyStore(keystore),secretKey, alias, passwordPrivateKey, signatureAlgorithm, jwtHeaders, options);
	}
	public JsonSignature(KeyStore keystore, boolean secretKey, String alias, String passwordPrivateKey, String signatureAlgorithm, 
			JwtHeaders jwtHeaders, JWSOptions options) throws UtilsException{
		try {
			
			org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm algo = org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm.getAlgorithm(signatureAlgorithm);
			if(secretKey) {
				String tipo = KeystoreType.JCEKS.getLabel();
				SecretKey secret = keystore.getSecretKey(alias, passwordPrivateKey);
				if(KeystoreType.PKCS11.getNome().equalsIgnoreCase(keystore.getKeystoreType())) {
					tipo = KeystoreType.PKCS11.getLabel();
					SecretKeyPkcs11 secretKeyPkcs11 = new SecretKeyPkcs11(keystore.getKeystoreProvider(), secret);
					this.provider = new HmacJwsSignatureProviderExtended(secretKeyPkcs11, algo);
				}
				else {
					this.provider = JwsUtils.getHmacSignatureProvider(secret.getEncoded(), algo);
				}
				if(this.provider==null) {
					throw new UtilsException("("+tipo+") JwsSignatureProvider init failed; check signature algorithm ("+signatureAlgorithm+")");
				}
			}
			else {
				PrivateKey pKey = keystore.getPrivateKey(alias, passwordPrivateKey);
				this.provider = JwsUtils.getPrivateKeySignatureProvider(pKey, algo);
				if(this.provider==null) {
					// caso in cui la chiave privata PKCS11 non e' stata mappata in un PrivateKeyJwsSignatureProvider
					// caso pkcs11 dove la chiave privata non e' ne ECPrivateKey ne RSAPrivateKey gestita dal metodo getPrivateKeySignatureProvider
					// ad es. può essere sun.security.pkcs11.P11Key$P11PrivateKey
					this.provider = JsonUtils.getJwsAsymmetricProvider(pKey, algo);
					if(this.provider==null) {
						throw new UtilsException("("+keystore.getKeystore().getType()+") JwsSignatureProvider init failed; check signature algorithm ("+signatureAlgorithm+")");
					}
				}
			}
			this.options=options;
			this.jwtHeaders = jwtHeaders;
		}catch(Exception t) {
			throw JsonUtils.convert(options.getSerialization(), JsonUtils.SIGNATURE,JsonUtils.SENDER,t);
		}
	}
	
	public JsonSignature(JsonWebKeys jsonWebKeys, boolean secretKey, String alias, String signatureAlgorithm, JWSOptions options) throws UtilsException{
		this(jsonWebKeys, secretKey, alias, signatureAlgorithm, 
				null, options);
	}
	public JsonSignature(JsonWebKeys jsonWebKeys, boolean secretKey, String alias, String signatureAlgorithm, 
			JwtHeaders jwtHeaders, JWSOptions options) throws UtilsException{
		this(JsonUtils.readKey(jsonWebKeys, alias),secretKey,signatureAlgorithm,jwtHeaders,options);
	}
	
	public JsonSignature(JsonWebKey jsonWebKey, boolean secretKey, String signatureAlgorithm, JWSOptions options) throws UtilsException{
		this(jsonWebKey, secretKey, signatureAlgorithm, 
				null, options);
	}
	public JsonSignature(JsonWebKey jsonWebKey, boolean secretKey, String signatureAlgorithm, 
			JwtHeaders jwtHeaders, JWSOptions options) throws UtilsException{
		try {
			org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm algo = org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm.getAlgorithm(signatureAlgorithm);
			if(secretKey) {
				this.provider = JwsUtils.getSignatureProvider(jsonWebKey, algo);
				if(this.provider==null) {
					throw new UtilsException("(JsonWebKey) JwsSignatureProvider init failed; check signature algorithm ("+signatureAlgorithm+")");
				}
			}else {
				this.provider = JwsUtils.getPrivateKeySignatureProvider(JwkUtils.toRSAPrivateKey(jsonWebKey), algo);
			}
			this.options=options;
			this.jwtHeaders = jwtHeaders;
		}catch(Exception t) {
			throw JsonUtils.convert(options.getSerialization(), JsonUtils.SIGNATURE,JsonUtils.SENDER,t);
		}
	}

	public JsonSignature(String secret,  String signatureAlgorithm, JWSOptions options) throws UtilsException{
		this(secret, signatureAlgorithm, null, options);
	}
	public JsonSignature(String secret,  String signatureAlgorithm, JwtHeaders jwtHeaders, JWSOptions options) throws UtilsException{
		try {
			
			org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm algo = org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm.getAlgorithm(signatureAlgorithm);
			this.provider = JwsUtils.getHmacSignatureProvider(secret.getBytes(), algo);
			if(this.provider==null) {
				throw new UtilsException("(Secret) JwsSignatureProvider init failed; check signature algorithm ("+signatureAlgorithm+")");
			}
			this.options=options;
			this.jwtHeaders = jwtHeaders;
		}catch(Exception t) {
			throw JsonUtils.convert(options.getSerialization(), JsonUtils.SIGNATURE,JsonUtils.SENDER,t);
		}
	}

	public String sign(String jsonString) throws UtilsException{
		try {
			switch(this.options.getSerialization()) {
				case JSON: return signJson(jsonString);
				case COMPACT: return signCompact(jsonString);
				default: throw new UtilsException("Unsupported serialization '"+this.options.getSerialization()+"'");
			}
		}
		catch(Exception t) {
			throw JsonUtils.convert(this.options.getSerialization(), JsonUtils.SIGNATURE,JsonUtils.SENDER,t);
		}
	}

	private String signCompact(String jsonString) throws Exception {
		JwsHeaders headersInternal = new JwsHeaders(this.provider.getAlgorithm());
		// utilizzabile solamente per JSON. Per COMPACT è sconsigliato poichè limitato nei caratteri, non utilizzabile quindi per un json https://tools.ietf.org/html/rfc7797#page-8
		// Infatti JwsCompactProducer di cxf non lo implementa proprio se si va a vedere il codice, il metodo 'getUnsignedEncodedJws' utilizzato per comporre il jwt non prevede il caso di lasciarlo in chiaro
/**		if(this.options.isPayloadEncoding()==false) {
//			headers.setPayloadEncodingStatus(this.options.isPayloadEncoding()); // RFC: https://tools.ietf.org/html/rfc7797
//		}*/
		JwsCompactProducer jwsProducer = new JwsCompactProducer(headersInternal, jsonString, this.options.isDetached());
		fillJwtHeaders(jwsProducer.getJwsHeaders(), this.provider.getAlgorithm());
		return jwsProducer.signWith(this.provider);
	}

	private String signJson(String jsonString) throws Exception {
		JwsJsonProducer jwsProducer = new JwsJsonProducer(jsonString, false, this.options.isDetached());
		JwsHeaders headersInternal = new JwsHeaders(this.provider.getAlgorithm());
		if(!this.options.isPayloadEncoding()) {
			headersInternal.setPayloadEncodingStatus(this.options.isPayloadEncoding()); // RFC: https://tools.ietf.org/html/rfc7797
		}
		fillJwtHeaders(headersInternal, this.provider.getAlgorithm());
		return jwsProducer.signWith(this.provider, headersInternal);
	}

	
	private void fillJwtHeaders(JwsHeaders headers,
			org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm signatureAlgo) throws Exception {
		if(this.headers!=null &&
			this.headers.asMap()!=null && !this.headers.asMap().isEmpty()) {
			Iterator<String> itKeys = this.headers.asMap().keySet().iterator();
			while (itKeys.hasNext()) {
				String key = itKeys.next();
				if(!headers.containsHeader(key)) {
					headers.setHeader(key, this.headers.getHeader(key));
				}
			}
		}
		if(this.jwtHeaders!=null) {
			this.jwtHeaders.fillJwsHeaders(headers, false, signatureAlgo.getJwaName());
		}
	}
}