JWKSet.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.certificate;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

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.JwkReaderWriter;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.UtilsRuntimeException;
import org.openspcoop2.utils.json.JSONUtils;

import com.fasterxml.jackson.databind.JsonNode;

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

	private JwkReaderWriter engineCxf = new JwkReaderWriter();
	private String jwkSetJson;
	private String jwkSetJsonPretty;
	private JsonWebKeys jwkSetCxf;
	private com.nimbusds.jose.jwk.JWKSet jwkSetNimbusds;
	private JsonNode jwkSetNode;
	private List<JWK> jwkSetList = new ArrayList<>();
	
	public JWKSet(String json) {
		this.jwkSetJson = json;
	}
	
	public JWKSet(JsonWebKeys jwk) {
		this.jwkSetCxf = jwk;
	}
	
	public JWKSet(com.nimbusds.jose.jwk.JWKSet jwk) {
		this.jwkSetNimbusds = jwk;
	}
	
	public JWKSet(List<JWK> list) {
		this.jwkSetList = list;
	}
	
	public JWKSet() {

	}
	
	
	public void addJwk(JWK jwk) {
		this.jwkSetList.add(jwk);
		
		// forzo rebuild
		this.jwkSetJson = null;
		this.jwkSetJsonPretty = null;
		this.jwkSetCxf = null;
		this.jwkSetNimbusds = null;
		this.jwkSetNode = null;
				
	}
	
	private synchronized void initJwks() throws UtilsException {
		if(this.jwkSetList==null || this.jwkSetList.isEmpty()) {
			if(this.getJsonWebKeys()==null && this.getJWKSet()==null){
				throw new UtilsException("JWK Set not defined");
			}
			if(this.jwkSetCxf!=null) {
				initJwksFromSetCxf();
			}
			else {
				initJwksFromSetNimbusds();
			}
		}
	}
	private synchronized void initJwksFromSetCxf() throws UtilsException {
		try {
			Iterator<JsonWebKey> it = this.jwkSetCxf.getKeys().iterator();
			while (it.hasNext()) {
				JsonWebKey jsonWebKey = it.next();
				this.jwkSetList.add(new JWK(jsonWebKey));
			}
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	private synchronized void initJwksFromSetNimbusds() throws UtilsException {
		try {
			Iterator<com.nimbusds.jose.jwk.JWK> it = this.jwkSetNimbusds.getKeys().iterator();
			while (it.hasNext()) {
				com.nimbusds.jose.jwk.JWK jwk = it.next();
				this.jwkSetList.add(new JWK(jwk));
			}
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	public List<JWK> getJwks() throws UtilsException {
		if(this.jwkSetList==null || this.jwkSetList.isEmpty()) {
			this.initJwks();
		}
		return this.jwkSetList;
	}
	
	
	
	private synchronized void initCxf() throws UtilsException {
		if(this.jwkSetCxf==null) {
			if(this.jwkSetJson==null){
				throw new UtilsException("Json not defined");
			}
			try {
				this.jwkSetCxf = this.engineCxf.jsonToJwkSet(this.jwkSetJson);
			}catch(Exception e) {
				/* Fix bug cxf che va in errore se il keystore รจ pretty print e la , tra una chiave e l'altra struttura json va a capo.
				 * Esempio:
				 * {
					  "keys" : [ 
					{
					  "kty" : "RSA",
					  "e" : "AQAB",
					  "kid" : "KID-ApplicativoBlockingIDA01",
					  "n" : "8IYnrIeYyuCCZQJvdkxL5bD5-v-L2bwAgiz4ZMJPqWqhbgsGmneyaHQIKL-ihiKRgbDT02HIDlLeJp6uXCxRu6MYVEXkpuI1Kte2xqlGzw2F-WALNkoFgaXeIrEwIugj5eeiqqBbedZdQPr7YmXHiZOSztVsQSeyRTGhIfMtvrqKUa8R4U2gFAp5wo2cgCU2Dk1gJ_B2mooxgvewi2Ea2SSuuOAJThvAEwAk1cxXwZcxVqOAjzgeKe7kPs79VgCpnGuottrhqlLtT0hPpij2T1S_r2ENZrQ9ex4hkFF8q2EpxqveKcF5bmTaNS1ezjKCthJjmq1zu-zELLFAx4bY_w"
					}
					,
					{
					  "kty" : "RSA",
					  "e" : "AQAB",
					  "kid" : "KID-ExampleServer",
					  "n" : "4OKbeAjhuWBnATnd2FjdvRAdyks05AnW5nYNRWVt2RIvBfPsASZD858hv_ts1W2uUN1EbJTSgQzgZskufBKz3KApI1Lq3F3IEH2jLBYGywpCbus6hHNCi8xN1OzRLEDp-uaZvIeP26RDjush53j9YFvVEI5Hic6thLT0zqtFhm-u1VtDH0uEZ_1S5CUspMYbZTOkl-PEz7Y77dIGk0vfhfJ3uW3g1khWQONVHA7X4XOZLKDo1rnQxzZv3l7r__h5GlHqZRopLBwqn6hDRyoeRzZfQtrl_fMp5Pgyg0Fi5hKI5o0YLnOhHzB_MJJrgoXOjirvoFBO-qkET-BAjU6BAQ"
					}
					   ]
					}
				 *  Produce il seguente errore: "String index out of range: 0"
				 * 
				 **/
				if(e.getMessage()!=null && e.getMessage().contains("String index out of range")) {
					try {
						JSONUtils jsonUtils = JSONUtils.getInstance();
						JsonNode node = jsonUtils.getAsNode(this.jwkSetJson);
						String sNonPretty = jsonUtils.toString(node);
						this.jwkSetCxf = this.engineCxf.jsonToJwkSet(sNonPretty);
					}catch(Exception eCxf) {
						// rilancio eccezione originale
						throw e;
					}
				}
			}
		}
	}
	public JsonWebKeys getJsonWebKeys() throws UtilsException {
		if(this.jwkSetCxf==null) {
			this.initCxf();
		}
		return this.jwkSetCxf;
	}
	
	private synchronized void initNimbusds() throws UtilsException {
		if(this.jwkSetNimbusds==null) {
			if(this.jwkSetJson==null){
				throw new UtilsException("Json not defined");
			}
			try {
				this.jwkSetNimbusds = com.nimbusds.jose.jwk.JWKSet.parse(this.jwkSetJson);
			}catch(Exception e) {
				throw new UtilsException(e.getMessage(),e);
			}
		}
	}
	public com.nimbusds.jose.jwk.JWKSet getJWKSet() throws UtilsException {
		if(this.jwkSetNimbusds==null) {
			this.initNimbusds();
		}
		return this.jwkSetNimbusds;
	}
	
	private synchronized void initJson() throws UtilsException {
		if(this.jwkSetJson==null) {
			if( (this.jwkSetList==null || this.jwkSetList.isEmpty()) && this.jwkSetCxf==null && this.jwkSetNimbusds==null){
				throw new UtilsException("JWK Set not defined");
			}
			if(this.jwkSetList!=null && !this.jwkSetList.isEmpty()) {
				initJsonFromJwkSetList();
			}
			else if(this.jwkSetCxf!=null) {
				initJsonFromJwkSetCxf();
			}
			else {
				initJsonFromJwkSetNimbusds();
			}
		}
	}
	private synchronized void initJsonFromJwkSetList() throws UtilsException {
		List<com.nimbusds.jose.jwk.JWK> list = new ArrayList<>();
		for (JWK jwkOp : this.jwkSetList) {
			list.add(jwkOp.getJWK());
		}
		/** NON FUNZIONA 
		com.nimbusds.jose.jwk.JWKSet set = new com.nimbusds.jose.jwk.JWKSet(list);
		this.jwkSetJson = set.toJSONObject().toString(); */
		StringBuilder sb = new StringBuilder("{\"keys\":[");
		boolean first = true;
		for (JWK jwkOp : this.jwkSetList) {
			if(!first) {
				sb.append(",");
			}
			sb.append(jwkOp.getJWK().toJSONString());
			first = false;
		}
		sb.append("]}");
		this.jwkSetJson = sb.toString();
	}
	private synchronized void initJsonFromJwkSetCxf() throws UtilsException {
		try {
			this.jwkSetJson = this.engineCxf.jwkSetToJson(this.jwkSetCxf);
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	private synchronized void initJsonFromJwkSetNimbusds() throws UtilsException {
		try {
			this.jwkSetJson = this.jwkSetNimbusds.toString();
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	public String getJson() throws UtilsException {
		if(this.jwkSetJson==null) {
			this.initJson();
		}
		return this.jwkSetJson;
	}
	
	private synchronized void initJsonPretty() throws UtilsException {
		if(this.jwkSetJsonPretty==null) {
			try {
				if(this.jwkSetNode==null) {
					initNode();
				}
				this.jwkSetJsonPretty = JSONUtils.getInstance(true).toString(this.jwkSetNode);
			}catch(Exception e) {
				throw new UtilsException(e.getMessage(),e);
			}
		}
	}
	public String getJsonPretty() throws UtilsException {
		if(this.jwkSetJsonPretty==null) {
			this.initJsonPretty();
		}
		return this.jwkSetJsonPretty;
	}
	
	private synchronized void initNode() throws UtilsException {
		if(this.jwkSetNode==null) {
			try {
				if(this.jwkSetJson==null) {
					initJson();
				}
				this.jwkSetNode = JSONUtils.getInstance().getAsNode(this.jwkSetJson);
			}catch(Exception e) {
				throw new UtilsException(e.getMessage(),e);
			}
		}
	}
	public JsonNode getNode() throws UtilsException {
		if(this.jwkSetNode==null) {
			this.initNode();
		}
		return this.jwkSetNode;
	}

	@Override
	public String toString() {
		try {
			return this.getJsonPretty();
		}catch(Exception e) {
			throw new UtilsRuntimeException(e.getMessage(),e);
		}
	}
}