JWKMatcher.java

/*
 * nimbus-jose-jwt
 *
 * Copyright 2012-2019, Connect2id Ltd.
 *
 * 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 com.nimbusds.jose.jwk;


import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;

import net.jcip.annotations.Immutable;

import com.nimbusds.jose.Algorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jose.util.X509CertUtils;


/**
 * JSON Web Key (JWK) matcher. May be used to ensure a JWK matches a set of
 * application-specific criteria.
 *
 * <p>Supported key matching criteria:
 *
 * <ul>
 *     <li>Any, unspecified, one or more key types (typ).
 *     <li>Any, unspecified, one or more key uses (use).
 *     <li>Any, unspecified, one or more key operations (key_ops).
 *     <li>Any, unspecified, one or more key algorithms (alg).
 *     <li>Any, unspecified, one or more key identifiers (kid).
 *     <li>Private only key.
 *     <li>Public only key.
 *     <li>Minimum, maximum or exact key sizes.
 *     <li>Any, unspecified, one or more curves for EC and OKP keys (crv).
 *     <li>X.509 certificate SHA-256 thumbprint.
 *     <li>Has X.509 certificate.
 * </ul>
 *
 * <p>Matching by JWK thumbprint (RFC 7638), X.509 certificate URL and X.509
 * certificate chain is not supported.
 *
 * @author Vladimir Dzhuvinov
 * @author Josh Cummings
 * @author Ben Arena
 * @version 2022-05-28
 */
@Immutable
public class JWKMatcher {


	/**
	 * The key types to match.
	 */
	private final Set<KeyType> types;


	/**
	 * The public key uses to match.
	 */
	private final Set<KeyUse> uses;


	/**
	 * The key operations to match.
	 */
	private final Set<KeyOperation> ops;


	/**
	 * The algorithms to match.
	 */
	private final Set<Algorithm> algs;


	/**
	 * The key IDs to match.
	 */
	private final Set<String> ids;
	
	
	/**
	 * {@code true} to match a key with a set use.
	 */
	private final boolean hasUse;
	
	
	/**
	 * {@code true} to match a key with a set ID.
	 */
	private final boolean hasID;


	/**
	 * {@code true} to match a private key.
	 */
	private final boolean privateOnly;


	/**
	 * {@code true} to match a public only key.
	 */
	private final boolean publicOnly;


	/**
	 * The minimum key size in bits, zero implies no minimum size limit.
	 */
	private final int minSizeBits;


	/**
	 * The maximum key size in bits, zero implies no maximum size limit.
	 */
	private final int maxSizeBits;
	
	
	/**
	 * The key sizes in bits.
	 */
	private final Set<Integer> sizesBits;
	
	
	/**
	 * The curves to match (for EC and OKP keys).
	 */
	private final Set<Curve> curves;

	
	/**
	 * The X.509 certificate SHA-256 thumbprints to match.
	 */
	private final Set<Base64URL> x5tS256s;
	
	
	/**
	 * {@code true} to match a key with a set X.509 certificate chain.
	 */
	private final boolean hasX5C;

	
	/**
	 * Builder for constructing JWK matchers.
	 *
	 * <p>Example usage:
	 *
	 * <pre>
	 * JWKMatcher matcher = new JWKMatcher().keyID("123").build();
	 * </pre>
	 */
	public static class Builder {

		
		/**
		 * The key types to match.
		 */
		private Set<KeyType> types;


		/**
		 * The public key uses to match.
		 */
		private Set<KeyUse> uses;


		/**
		 * The key operations to match.
		 */
		private Set<KeyOperation> ops;


		/**
		 * The algorithms to match.
		 */
		private Set<Algorithm> algs;


		/**
		 * The key IDs to match.
		 */
		private Set<String> ids;
		
		
		/**
		 * {@code true} to match a key with a set use.
		 */
		private boolean hasUse = false;
		
		
		/**
		 * {@code true} to match a key with a set ID.
		 */
		private boolean hasID = false;


		/**
		 * {@code true} to match a private key.
		 */
		private boolean privateOnly = false;


		/**
		 * {@code true} to match a public only key.
		 */
		private boolean publicOnly = false;


		/**
		 * The minimum key size in bits, zero implies no minimum size
		 * limit.
		 */
		private int minSizeBits = 0;


		/**
		 * The maximum key size in bits, zero implies no maximum size
		 * limit.
		 */
		private int maxSizeBits = 0;
		
		
		/**
		 * The key sizes in bits.
		 */
		private Set<Integer> sizesBits;
		
		
		/**
		 * The curves to match (for EC and OKP keys).
		 */
		private Set<Curve> curves;

		
		/**
		 * The X.509 certificate SHA-256 thumbprints to match.
		 */
		private Set<Base64URL> x5tS256s;
		
		
		/**
		 * {@code true} to match a key with a set X.509 certificate
		 * chain.
		 */
		private boolean hasX5C = false;

		
		/**
		 * Sets a single key type to match.
		 *
		 * @param kty The key type, {@code null} if not specified.
		 *            
		 * @return This builder.            
		 */
		public Builder keyType(final KeyType kty) {

			if (kty == null) {
				types = null;
			} else {
				types = new HashSet<>(Collections.singletonList(kty));
			}
			
			return this;
		}


		/**
		 * Sets multiple key types to match.
		 *
		 * @param types The key types.
		 *
		 * @return This builder.
		 */
		public Builder keyTypes(final KeyType ... types) {

			keyTypes(new LinkedHashSet<>(Arrays.asList(types)));
			return this;
		}


		/**
		 * Sets multiple key types to match.
		 *
		 * @param types The key types, {@code null} if not specified.
		 *
		 * @return This builder.
		 */
		public Builder keyTypes(final Set<KeyType> types) {

			this.types = types;
			return this;
		}


		/**
		 * Sets a single public key use to match.
		 *
		 * @param use The public key use, {@code null} if not 
		 *            specified.
		 *
		 * @return This builder.
		 */
		public Builder keyUse(final KeyUse use) {

			if (use == null) {
				uses = null;
			} else {
				uses = new HashSet<>(Collections.singletonList(use));
			}
			return this;
		}


		/**
		 * Sets multiple public key uses to match.
		 *
		 * @param uses The public key uses.
		 *
		 * @return This builder.
		 */
		public Builder keyUses(final KeyUse... uses) {

			keyUses(new LinkedHashSet<>(Arrays.asList(uses)));
			return this;
		}


		/**
		 * Sets multiple public key uses to match.
		 *
		 * @param uses The public key uses, {@code null} if not
		 *             specified.
		 *
		 * @return This builder.
		 */
		public Builder keyUses(final Set<KeyUse> uses) {

			this.uses = uses;
			return this;
		}


		/**
		 * Sets a single key operation to match.
		 *
		 * @param op The key operation, {@code null} if not specified.
		 *
		 * @return This builder.
		 */
		public Builder keyOperation(final KeyOperation op) {

			if (op == null) {
				ops = null;
			} else {
				ops = new HashSet<>(Collections.singletonList(op));
			}
			return this;
		}


		/**
		 * Sets multiple key operations to match.
		 *
		 * @param ops The key operations.
		 *
		 * @return This builder.
		 */
		public Builder keyOperations(final KeyOperation... ops) {

			keyOperations(new LinkedHashSet<>(Arrays.asList(ops)));
			return this;
		}


		/**
		 * Sets multiple key operations to match.
		 *
		 * @param ops The key operations, {@code null} if not
		 *            specified.
		 *
		 * @return This builder.
		 */
		public Builder keyOperations(final Set<KeyOperation> ops) {

			this.ops = ops;
			return this;
		}


		/**
		 * Sets a single JOSE algorithm to match.
		 *
		 * @param alg The JOSE algorithm, {@code null} if not
		 *            specified.
		 *
		 * @return This builder.
		 */
		public Builder algorithm(final Algorithm alg) {

			if (alg == null) {
				algs = null;
			} else {
				algs = new HashSet<>(Collections.singletonList(alg));
			}
			return this;
		}


		/**
		 * Sets multiple JOSE algorithms to match.
		 *
		 * @param algs The JOSE algorithms.
		 *
		 * @return This builder.
		 */
		public Builder algorithms(final Algorithm ... algs) {

			algorithms(new LinkedHashSet<>(Arrays.asList(algs)));
			return this;
		}


		/**
		 * Sets multiple JOSE algorithms to match.
		 *
		 * @param algs The JOSE algorithms, {@code null} if not
		 *             specified.
		 *
		 * @return This builder.
		 */
		public Builder algorithms(final Set<Algorithm> algs) {

			this.algs = algs;
			return this;
		}


		/**
		 * Sets a single key ID to match.
		 *
		 * @param id The key ID, {@code null} if not specified.
		 *
		 * @return This builder.
		 */
		public Builder keyID(final String id) {

			if (id == null) {
				ids = null;
			} else {
				ids = new HashSet<>(Collections.singletonList(id));
			}
			return this;
		}


		/**
		 * Sets multiple key IDs to match.
		 *
		 * @param ids The key IDs.
		 *
		 * @return This builder.
		 */
		public Builder keyIDs(final String ... ids) {

			keyIDs(new LinkedHashSet<>(Arrays.asList(ids)));
			return this;
		}


		/**
		 * Sets multiple key IDs to match.
		 *
		 * @param ids The key IDs, {@code null} if not specified.
		 *
		 * @return This builder.
		 */
		public Builder keyIDs(final Set<String> ids) {

			this.ids = ids;
			return this;
		}
		
		
		/**
		 * Sets key use presence matching.
		 *
		 * @param hasUse {@code true} to match a key with a set use.
		 *
		 * @return This builder.
		 */
		public Builder hasKeyUse(final boolean hasUse) {
			
			this.hasUse = hasUse;
			return this;
		}
		
		
		/**
		 * Sets key ID presence matching.
		 *
		 * @param hasID {@code true} to match a key with a set ID.
		 *
		 * @return This builder.
		 */
		public Builder hasKeyID(final boolean hasID) {
			
			this.hasID = hasID;
			return this;
		}


		/**
		 * Sets the private key matching policy.
		 *
		 * @param privateOnly {@code true} to match a private key.
		 *
		 * @return This builder.
		 */
		public Builder privateOnly(final boolean privateOnly) {

			this.privateOnly = privateOnly;
			return this;
		}


		/**
		 * Sets the public key matching policy.
		 *
		 * @param publicOnly {@code true} to match a public only key.
		 *
		 * @return This builder.
		 */
		public Builder publicOnly(final boolean publicOnly) {

			this.publicOnly = publicOnly;
			return this;
		}


		/**
		 * Sets the minimal key size.
		 *
		 * @param minSizeBits The minimum key size in bits, zero
		 *                    implies no minimum key size limit.
		 *
		 * @return This builder.
		 */
		public Builder minKeySize(final int minSizeBits) {

			this.minSizeBits = minSizeBits;
			return this;
		}


		/**
		 * Sets the maximum key size.
		 *
		 * @param maxSizeBits The maximum key size in bits, zero
		 *                    implies no maximum key size limit.
		 *
		 * @return This builder.
		 */
		public Builder maxKeySize(final int maxSizeBits) {

			this.maxSizeBits = maxSizeBits;
			return this;
		}
		
		
		/**
		 * Sets the key size.
		 *
		 * @param keySizeBits The key size in bits, zero if not
		 *                    specified.
		 *
		 * @return This builder.
		 */
		public Builder keySize(final int keySizeBits) {
			if (keySizeBits <= 0) {
				sizesBits = null;
			} else {
				sizesBits = Collections.singleton(keySizeBits);
			}
			return this;
		}
		
		
		/**
		 * Sets the key sizes.
		 *
		 * @param keySizesBits The key sizes in bits.
		 *
		 * @return This builder.
		 */
		public Builder keySizes(final int... keySizesBits) {
			Set<Integer> sizesSet = new LinkedHashSet<>();
			for (int keySize: keySizesBits) {
				sizesSet.add(keySize);
			}
			keySizes(sizesSet);
			return this;
		}
		
		
		/**
		 * Sets the key sizes.
		 *
		 * @param keySizesBits The key sizes in bits.
		 *
		 * @return This builder.
		 */
		public Builder keySizes(final Set<Integer> keySizesBits) {
			
			this.sizesBits = keySizesBits;
			return this;
		}
		
		
		/**
		 * Sets a single curve to match (for EC and OKP keys).
		 *
		 * @param curve The curve, {@code null} if not specified.
		 *
		 * @return This builder.
		 */
		public Builder curve(final Curve curve) {
			
			if (curve == null) {
				curves = null;
			} else {
				curves = Collections.singleton(curve);
			}
			return this;
		}
		
		
		/**
		 * Sets multiple curves to match (for EC and OKP keys).
		 *
		 * @param curves The curves.
		 *
		 * @return This builder.
		 */
		public Builder curves(final Curve... curves) {
			
			curves(new LinkedHashSet<>(Arrays.asList(curves)));
			return this;
		}
		
		
		/**
		 * Sets multiple curves to match (for EC and OKP keys).
		 *
		 * @param curves The curves, {@code null} if not specified.
		 *
		 * @return This builder.
		 */
		public Builder curves(final Set<Curve> curves) {
			
			this.curves = curves;
			return this;
		}

		
		/**
		 * Sets a single X.509 certificate SHA-256 thumbprint to match.
		 *
		 * @param x5tS256 The thumbprint, {@code null} if not
		 *                specified.
		 *
		 * @return This builder.
		 */
		public Builder x509CertSHA256Thumbprint(final Base64URL x5tS256) {

			if (x5tS256 == null) {
				x5tS256s = null;
			} else {
				x5tS256s = Collections.singleton(x5tS256);
			}
			return this;
		}

		
		/**
		 * Sets multiple X.509 certificate SHA-256 thumbprints to
		 * match.
		 *
		 * @param x5tS256s The thumbprints.
		 *
		 * @return This builder.
		 */
		public Builder x509CertSHA256Thumbprints(final Base64URL... x5tS256s) {
			return x509CertSHA256Thumbprints(new LinkedHashSet<>(Arrays.asList(x5tS256s)));
		}

		
		/**
		 * Sets multiple X.509 certificate SHA-256 thumbprints to
		 * match.
		 *
		 * @param x5tS256s The thumbprints, {@code null} if not
		 *                 specified.
		 *
		 * @return This builder.
		 */
		public Builder x509CertSHA256Thumbprints(final Set<Base64URL> x5tS256s) {
			this.x5tS256s = x5tS256s;
			return this;
		}
		
		
		/**
		 * Sets X.509 certificate chain presence matching.
		 *
		 * @param hasX5C {@code true} to match a key with a set X.509
		 *               certificate chain.
		 *
		 * @return This builder.
		 */
		public Builder hasX509CertChain(final boolean hasX5C) {
			this.hasX5C = hasX5C;
			return this;
		}

		
		/**
		 * Builds a new JWK matcher.
		 *
		 * @return The JWK matcher.
		 */
		public JWKMatcher build() {

			return new JWKMatcher(types, uses, ops, algs, ids, hasUse, hasID, privateOnly, publicOnly, minSizeBits, maxSizeBits, sizesBits, curves, x5tS256s, hasX5C);
		}
	}


	/**
	 * Creates a new JSON Web Key (JWK) matcher.
	 *
	 * @param types       The key types to match, {@code null} if not
	 *                    specified.
	 * @param uses        The public key uses to match, {@code null} if not
	 *                    specified.
	 * @param ops         The key operations to match, {@code null} if not
	 *                    specified.
	 * @param algs        The JOSE algorithms to match, {@code null} if not
	 *                    specified.
	 * @param ids         The key IDs to match, {@code null} if not
	 *                    specified.
	 * @param privateOnly {@code true} to match a private key.
	 * @param publicOnly  {@code true} to match a public only key.
	 */
	@Deprecated
	public JWKMatcher(final Set<KeyType> types,
			  final Set<KeyUse> uses,
			  final Set<KeyOperation> ops,
			  final Set<Algorithm> algs,
			  final Set<String> ids,
			  final boolean privateOnly,
			  final boolean publicOnly) {

		this(types, uses, ops, algs, ids, privateOnly, publicOnly, 0, 0);
	}


	/**
	 * Creates a new JSON Web Key (JWK) matcher.
	 *
	 * @param types       The key types to match, {@code null} if not
	 *                    specified.
	 * @param uses        The public key uses to match, {@code null} if not
	 *                    specified.
	 * @param ops         The key operations to match, {@code null} if not
	 *                    specified.
	 * @param algs        The JOSE algorithms to match, {@code null} if not
	 *                    specified.
	 * @param ids         The key IDs to match, {@code null} if not
	 *                    specified.
	 * @param privateOnly {@code true} to match a private key.
	 * @param publicOnly  {@code true} to match a public only key.
	 * @param minSizeBits The minimum key size in bits, zero implies no
	 *                    minimum size limit.
	 * @param maxSizeBits The maximum key size in bits, zero implies no
	 *                    maximum size limit.
	 */
	@Deprecated
	public JWKMatcher(final Set<KeyType> types,
			  final Set<KeyUse> uses,
			  final Set<KeyOperation> ops,
			  final Set<Algorithm> algs,
			  final Set<String> ids,
			  final boolean privateOnly,
			  final boolean publicOnly,
			  final int minSizeBits,
			  final int maxSizeBits) {
		
		this(types, uses, ops, algs, ids, privateOnly, publicOnly, minSizeBits, maxSizeBits, null);
	}


	/**
	 * Creates a new JSON Web Key (JWK) matcher.
	 *
	 * @param types       The key types to match, {@code null} if not
	 *                    specified.
	 * @param uses        The public key uses to match, {@code null} if not
	 *                    specified.
	 * @param ops         The key operations to match, {@code null} if not
	 *                    specified.
	 * @param algs        The JOSE algorithms to match, {@code null} if not
	 *                    specified.
	 * @param ids         The key IDs to match, {@code null} if not
	 *                    specified.
	 * @param privateOnly {@code true} to match a private key.
	 * @param publicOnly  {@code true} to match a public only key.
	 * @param minSizeBits The minimum key size in bits, zero implies no
	 *                    minimum size limit.
	 * @param maxSizeBits The maximum key size in bits, zero implies no
	 *                    maximum size limit.
	 * @param curves      The curves to match (for EC keys), {@code null}
	 *                    if not specified.
	 */
	@Deprecated
	public JWKMatcher(final Set<KeyType> types,
			  final Set<KeyUse> uses,
			  final Set<KeyOperation> ops,
			  final Set<Algorithm> algs,
			  final Set<String> ids,
			  final boolean privateOnly,
			  final boolean publicOnly,
			  final int minSizeBits,
			  final int maxSizeBits,
			  final Set<Curve> curves) {
		
		this(types, uses, ops, algs, ids, privateOnly, publicOnly, minSizeBits, maxSizeBits, null, curves);
	}


	/**
	 * Creates a new JSON Web Key (JWK) matcher.
	 *
	 * @param types       The key types to match, {@code null} if not
	 *                    specified.
	 * @param uses        The public key uses to match, {@code null} if not
	 *                    specified.
	 * @param ops         The key operations to match, {@code null} if not
	 *                    specified.
	 * @param algs        The JOSE algorithms to match, {@code null} if not
	 *                    specified.
	 * @param ids         The key IDs to match, {@code null} if not
	 *                    specified.
	 * @param privateOnly {@code true} to match a private key.
	 * @param publicOnly  {@code true} to match a public only key.
	 * @param minSizeBits The minimum key size in bits, zero implies no
	 *                    minimum size limit.
	 * @param maxSizeBits The maximum key size in bits, zero implies no
	 *                    maximum size limit.
	 * @param sizesBits   The key sizes in bits, {@code null} if not
	 *                    specified.
	 * @param curves      The curves to match (for EC and OKP keys),
	 *                    {@code null} if not specified.
	 */
	@Deprecated
	public JWKMatcher(final Set<KeyType> types,
			  final Set<KeyUse> uses,
			  final Set<KeyOperation> ops,
			  final Set<Algorithm> algs,
			  final Set<String> ids,
			  final boolean privateOnly,
			  final boolean publicOnly,
			  final int minSizeBits,
			  final int maxSizeBits,
			  final Set<Integer> sizesBits,
			  final Set<Curve> curves) {
		
		this(types, uses, ops, algs, ids, false, false, privateOnly, publicOnly, minSizeBits, maxSizeBits, sizesBits, curves);
	}


	/**
	 * Creates a new JSON Web Key (JWK) matcher.
	 *
	 * @param types       The key types to match, {@code null} if not
	 *                    specified.
	 * @param uses        The public key uses to match, {@code null} if not
	 *                    specified.
	 * @param ops         The key operations to match, {@code null} if not
	 *                    specified.
	 * @param algs        The JOSE algorithms to match, {@code null} if not
	 *                    specified.
	 * @param ids         The key IDs to match, {@code null} if not
	 *                    specified.
	 * @param hasUse      {@code true} to match a key with a set use.
	 * @param hasID       {@code true} to match a key with a set ID.
	 * @param privateOnly {@code true} to match a private key.
	 * @param publicOnly  {@code true} to match a public only key.
	 * @param minSizeBits The minimum key size in bits, zero implies no
	 *                    minimum size limit.
	 * @param maxSizeBits The maximum key size in bits, zero implies no
	 *                    maximum size limit.
	 * @param sizesBits   The key sizes in bits, {@code null} if not
	 *                    specified.
	 * @param curves      The curves to match (for EC and OKP keys),
	 *                    {@code null} if not specified.
	 */
	@Deprecated
	public JWKMatcher(final Set<KeyType> types,
			  final Set<KeyUse> uses,
			  final Set<KeyOperation> ops,
			  final Set<Algorithm> algs,
			  final Set<String> ids,
			  final boolean hasUse,
			  final boolean hasID,
			  final boolean privateOnly,
			  final boolean publicOnly,
			  final int minSizeBits,
			  final int maxSizeBits,
			  final Set<Integer> sizesBits,
			  final Set<Curve> curves) {

		this(types, uses, ops, algs, ids, hasUse, hasID, privateOnly, publicOnly, minSizeBits, maxSizeBits, sizesBits, curves, null);
	}

	
	/**
	 * Creates a new JSON Web Key (JWK) matcher.
	 *
	 * @param types       The key types to match, {@code null} if not
	 *                    specified.
	 * @param uses        The public key uses to match, {@code null} if not
	 *                    specified.
	 * @param ops         The key operations to match, {@code null} if not
	 *                    specified.
	 * @param algs        The JOSE algorithms to match, {@code null} if not
	 *                    specified.
	 * @param ids         The key IDs to match, {@code null} if not
	 *                    specified.
	 * @param hasUse      {@code true} to match a key with a set use.
	 * @param hasID       {@code true} to match a key with a set ID.
	 * @param privateOnly {@code true} to match a private key.
	 * @param publicOnly  {@code true} to match a public only key.
	 * @param minSizeBits The minimum key size in bits, zero implies no
	 *                    minimum size limit.
	 * @param maxSizeBits The maximum key size in bits, zero implies no
	 *                    maximum size limit.
	 * @param sizesBits   The key sizes in bits, {@code null} if not
	 *                    specified.
	 * @param curves      The curves to match (for EC and OKP keys),
	 *                    {@code null} if not specified.
	 * @param x5tS256s    The X.509 certificate thumbprints to match,
	 *                    {@code null} if not specified.
	 */
	@Deprecated
	public JWKMatcher(final Set<KeyType> types,
			  final Set<KeyUse> uses,
			  final Set<KeyOperation> ops,
			  final Set<Algorithm> algs,
			  final Set<String> ids,
			  final boolean hasUse,
			  final boolean hasID,
			  final boolean privateOnly,
			  final boolean publicOnly,
			  final int minSizeBits,
			  final int maxSizeBits,
			  final Set<Integer> sizesBits,
			  final Set<Curve> curves,
			  final Set<Base64URL> x5tS256s) {

		this(types, uses, ops, algs, ids, hasUse, hasID, privateOnly, publicOnly, minSizeBits, maxSizeBits, sizesBits, curves, x5tS256s, false);
	}

	
	/**
	 * Creates a new JSON Web Key (JWK) matcher.
	 *
	 * @param types       The key types to match, {@code null} if not
	 *                    specified.
	 * @param uses        The public key uses to match, {@code null} if not
	 *                    specified.
	 * @param ops         The key operations to match, {@code null} if not
	 *                    specified.
	 * @param algs        The JOSE algorithms to match, {@code null} if not
	 *                    specified.
	 * @param ids         The key IDs to match, {@code null} if not
	 *                    specified.
	 * @param hasUse      {@code true} to match a key with a set use.
	 * @param hasID       {@code true} to match a key with a set ID.
	 * @param privateOnly {@code true} to match a private key.
	 * @param publicOnly  {@code true} to match a public only key.
	 * @param minSizeBits The minimum key size in bits, zero implies no
	 *                    minimum size limit.
	 * @param maxSizeBits The maximum key size in bits, zero implies no
	 *                    maximum size limit.
	 * @param sizesBits   The key sizes in bits, {@code null} if not
	 *                    specified.
	 * @param curves      The curves to match (for EC and OKP keys),
	 *                    {@code null} if not specified.
	 * @param x5tS256s    The X.509 certificate thumbprints to match,
	 *                    {@code null} if not specified.
	 * @param hasX5C      {@code true} to match a key with a set X.509
	 *                    certificate chain.
	 */
	public JWKMatcher(final Set<KeyType> types,
					  final Set<KeyUse> uses,
					  final Set<KeyOperation> ops,
					  final Set<Algorithm> algs,
					  final Set<String> ids,
					  final boolean hasUse,
					  final boolean hasID,
					  final boolean privateOnly,
					  final boolean publicOnly,
					  final int minSizeBits,
					  final int maxSizeBits,
					  final Set<Integer> sizesBits,
					  final Set<Curve> curves,
					  final Set<Base64URL> x5tS256s,
			  		  final boolean hasX5C) {

		this.types = types;
		this.uses = uses;
		this.ops = ops;
		this.algs = algs;
		this.ids = ids;
		this.hasUse = hasUse;
		this.hasID = hasID;
		this.privateOnly = privateOnly;
		this.publicOnly = publicOnly;
		this.minSizeBits = minSizeBits;
		this.maxSizeBits = maxSizeBits;
		this.sizesBits = sizesBits;
		this.curves = curves;
		this.x5tS256s = x5tS256s;
		this.hasX5C = hasX5C;
	}

	
	/**
	 * Returns a {@link JWKMatcher} based on the given {@link JWEHeader}.
	 *
	 * <p>The {@link JWKMatcher} is configured as follows:
	 *
	 * <ul>
	 *     <li>The key type to match is determined by the JWE algorithm
	 *         (alg).
	 *     <li>The key ID to match is set by the JWE header key ID (kid)
	 *         parameter (if set).
	 *     <li>The key uses to match are set to encryption or not
	 *         specified.
	 *     <li>The key algorithm to match is set to the JWE algorithm (alg)
	 *         or not specified.
	 * </ul>
	 *
	 * <p>Other JWE header parameters are not taken into account.
	 *
	 * @param jweHeader The header to use.
	 *
	 * @return A {@code JWKMatcher} based on the given header.
	 */
	public static JWKMatcher forJWEHeader(final JWEHeader jweHeader) {

		return new JWKMatcher.Builder()
			.keyType(KeyType.forAlgorithm(jweHeader.getAlgorithm()))
			.keyID(jweHeader.getKeyID())
			.keyUses(KeyUse.ENCRYPTION, null)
			.algorithms(jweHeader.getAlgorithm(), null)
			.build();
	}

	
	/**
	 * Returns a {@link JWKMatcher} based on the given {@link JWSHeader}.
	 *
	 * <p>The {@link JWKMatcher} is configured as follows:
	 *
	 * <ul>
	 *     <li>The key type to match is determined by the JWS algorithm
	 *         (alg).
	 *     <li>The key ID to match is set by the JWS header key ID (kid)
	 *         parameter (if set).
	 *     <li>The key uses to match are set to signature or not specified.
	 *     <li>The key algorithm to match is set to the JWS algorithm (alg)
	 *         or not specified.
	 *     <li>The X.509 certificate SHA-256 thumbprint to match is set to
	 *         the x5t#S256 parameter (if set).
	 * </ul>
	 *
	 * <p>Other JWS header parameters are not taken into account.
	 *
	 * @param jwsHeader The header to use.
	 *
	 * @return A {@code JWKMatcher} based on the given header, {@code null}
	 *         if the JWS algorithm is not supported.
	 */
	public static JWKMatcher forJWSHeader(final JWSHeader jwsHeader) {

		JWSAlgorithm algorithm = jwsHeader.getAlgorithm();
		if (JWSAlgorithm.Family.RSA.contains(algorithm) || JWSAlgorithm.Family.EC.contains(algorithm)) {
			// RSA or EC key matcher
			return new JWKMatcher.Builder()
				.keyType(KeyType.forAlgorithm(algorithm))
				.keyID(jwsHeader.getKeyID())
				.keyUses(KeyUse.SIGNATURE, null)
				.algorithms(algorithm, null)
				.x509CertSHA256Thumbprint(jwsHeader.getX509CertSHA256Thumbprint())
				.build();
		} else if (JWSAlgorithm.Family.HMAC_SHA.contains(algorithm)) {
			// HMAC secret matcher
			return new JWKMatcher.Builder()
				.keyType(KeyType.forAlgorithm(algorithm))
				.keyID(jwsHeader.getKeyID())
				.privateOnly(true)
				.algorithms(algorithm, null)
				.build();
		} else if (JWSAlgorithm.Family.ED.contains(algorithm)) {
			return new JWKMatcher.Builder()
				.keyType(KeyType.forAlgorithm(algorithm))
				.keyID(jwsHeader.getKeyID())
				.keyUses(KeyUse.SIGNATURE, null)
				.algorithms(algorithm, null)
				.curves(Curve.forJWSAlgorithm(algorithm))
				.build();
		} else {
			return null; // Unsupported algorithm
		}
	}
	

	/**
	 * Returns the key types to match.
	 *
	 * @return The key types, {@code null} if not specified.
	 */
	public Set<KeyType> getKeyTypes() {

		return types;
	}


	/**
	 * Returns the public key uses to match.
	 *
	 * @return The public key uses, {@code null} if not specified.
	 */
	public Set<KeyUse> getKeyUses() {

		return uses;
	}


	/**
	 * Returns the key operations to match.
	 *
	 * @return The key operations, {@code null} if not specified.
	 */
	public Set<KeyOperation> getKeyOperations() {

		return ops;
	}


	/**
	 * Returns the JOSE algorithms to match.
	 *
	 * @return The JOSE algorithms, {@code null} if not specified.
	 */
	public Set<Algorithm> getAlgorithms() {

		return algs;
	}


	/**
	 * Returns the key IDs to match.
	 *
	 * @return The key IDs, {@code null} if not specified.
	 */
	public Set<String> getKeyIDs() {

		return ids;
	}
	
	
	/**
	 * Returns {@code true} if keys with a set use are matched.
	 *
	 * @return {@code true} if keys with a set use are matched, else
	 *         {@code false}.
	 */
	public boolean hasKeyUse() {
		
		return hasUse;
	}
	
	
	/**
	 * Returns {@code true} if keys with a set use are matched.
	 *
	 * @return {@code true} if keys with a set ID are matched, else
	 *         {@code false}.
	 */
	public boolean hasKeyID() {
		
		return hasID;
	}


	/**
	 * Returns {@code true} if only private keys are matched.
	 *
	 * @return {@code true} if only private keys are matched, else 
	 *         {@code false}.
	 */
	public boolean isPrivateOnly() {

		return privateOnly;
	}


	/**
	 * Returns {@code true} if only public keys are matched.
	 *
	 * @return {@code true} if only public keys are selected, else
	 *         {@code false}.
	 */
	public boolean isPublicOnly() {

		return publicOnly;
	}


	/**
	 * Returns the minimum key size. Use {@link #getMinKeySize()} instead.
	 *
	 * @return The minimum key size in bits, zero implies no minimum size
	 *         limit.
	 */
	@Deprecated
	public int getMinSize() {

		return getMinKeySize();
	}


	/**
	 * Returns the minimum key size.
	 *
	 * @return The minimum key size in bits, zero implies no minimum size
	 *         limit.
	 */
	public int getMinKeySize() {

		return minSizeBits;
	}


	/**
	 * Returns the maximum key size. Use {@link #getMaxKeySize()} instead.
	 *
	 * @return The maximum key size in bits, zero implies no maximum size
	 *         limit.
	 */
	@Deprecated
	public int getMaxSize() {

		return getMaxKeySize();
	}


	/**
	 * Returns the maximum key size.
	 *
	 * @return The maximum key size in bits, zero implies no maximum size
	 *         limit.
	 */
	public int getMaxKeySize() {

		return maxSizeBits;
	}
	
	
	/**
	 * Returns the key sizes.
	 *
	 * @return The key sizes in bits, {@code null} if not specified.
	 */
	public Set<Integer> getKeySizes() {
		
		return sizesBits;
	}
	
	
	/**
	 * Returns the curves to match (for EC and OKP keys).
	 *
	 * @return The curves, {@code null} if not specified.
	 */
	public Set<Curve> getCurves() {
		
		return curves;
	}

	/**
	 * Returns the X.509 certificate SHA-256 thumbprints to match.
	 *
	 * @return The thumbprints, {@code null} if not specified.
	 */
	public Set<Base64URL> getX509CertSHA256Thumbprints() {
		
		return x5tS256s;
	}
	
	
	/**
	 * Returns {@code true} if keys with a set X.509 certificate chain are
	 * matched.
	 *
	 * @return {@code true} if keys with a set X.509 certificate are
	 *         matched, else {@code false}.
	 */
	public boolean hasX509CertChain() {
		
		return hasX5C;
	}
	

	/**
	 * Returns {@code true} if the specified JWK matches.
	 *
	 * @param key The JSON Web Key (JWK). Must not  be {@code null}.
	 *
	 * @return {@code true} if the JWK matches, else {@code false}.
	 */
	public boolean matches(final JWK key) {
		
		if (hasUse && key.getKeyUse() == null)
			return false;
		
		if (hasID && (key.getKeyID() == null || key.getKeyID().trim().isEmpty()))
			return false;

		if (privateOnly && ! key.isPrivate())
			return false;

		if (publicOnly && key.isPrivate())
			return false;

		if (types != null && ! types.contains(key.getKeyType()))
			return false;

		if (uses != null && ! uses.contains(key.getKeyUse()))
			return false;

		if (ops != null) {

			if (ops.contains(null) && key.getKeyOperations() == null) {
				// pass
			} else if (key.getKeyOperations() != null && ops.containsAll(key.getKeyOperations())) {
				// pass
			} else {
				return false;
			}
		}

		if (algs != null && ! algs.contains(key.getAlgorithm()))
			return false;

		if (ids != null && ! ids.contains(key.getKeyID()))
			return false;

		if (minSizeBits > 0) {

			if (key.size() < minSizeBits)
				return false;
		}

		if (maxSizeBits > 0) {

			if (key.size() > maxSizeBits)
				return false;
		}
		
		if (sizesBits != null) {
			if (! sizesBits.contains(key.size()))
				return false;
		}
		
		if (curves != null) {
			
			if (! (key instanceof CurveBasedJWK))
				return false;
			
			CurveBasedJWK curveBasedJWK = (CurveBasedJWK) key;
			
			if (! curves.contains(curveBasedJWK.getCurve()))
				return false;
		}

		if (x5tS256s != null) {
			
			boolean matchingCertFound = false;
			
			if (key.getX509CertChain() != null && ! key.getX509CertChain().isEmpty()) {
				try {
					X509Certificate cert = X509CertUtils.parseWithException(key.getX509CertChain().get(0).decode());
					matchingCertFound = x5tS256s.contains(X509CertUtils.computeSHA256Thumbprint(cert));
				} catch (CertificateException e) {
					// Ignore
				}
			}
			
			boolean matchingX5T256Found = x5tS256s.contains(key.getX509CertSHA256Thumbprint());
			
			if (! matchingCertFound && ! matchingX5T256Found) {
				return false;
			}
		}
		
		if (hasX5C) {
			return key.getX509CertChain() != null && !key.getX509CertChain().isEmpty();
		}

		return true;
	}
	
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		
		append(sb, JWKParameterNames.KEY_TYPE, types);
		append(sb, JWKParameterNames.PUBLIC_KEY_USE, uses);
		append(sb, JWKParameterNames.KEY_OPS, ops);
		append(sb, JWKParameterNames.ALGORITHM, algs);
		append(sb, JWKParameterNames.KEY_ID, ids);
		
		if (hasUse) {
			sb.append("has_use=true ");
		}
		
		if (hasID) {
			sb.append("has_id=true ");
		}
		
		if (privateOnly) {
			sb.append("private_only=true ");
		}
		
		if (publicOnly) {
			sb.append("public_only=true ");
		}
		
		if (minSizeBits > 0) {
			sb.append("min_size=" + minSizeBits + " ");
		}
		
		if (maxSizeBits > 0) {
			sb.append("max_size=" + maxSizeBits + " ");
		}
		
		append(sb, "size", sizesBits);
		append(sb, JWKParameterNames.ELLIPTIC_CURVE, curves);
		append(sb, JWKParameterNames.X_509_CERT_SHA_256_THUMBPRINT, x5tS256s);
		if (hasX5C) {
			sb.append("has_x5c=true" );
		}
			
		return sb.toString().trim();
	}
	
	
	/**
	 * Appends the specified JWK matcher parameter to a string builder.
	 *
	 * @param sb     The string builder. Must not be {@code null}.
	 * @param key    The parameter key. Must not be {@code null}.
	 * @param values The parameter value, {@code null} if not specified.
	 */
	private static void append(final StringBuilder sb, final String key, final Set<?> values) {
		
		if (values != null) {
			
			sb.append(key);
			sb.append('=');
			if (values.size() == 1) {
				Object value = values.iterator().next();
				if (value == null) {
					sb.append("ANY");
				} else {
					sb.append(value.toString().trim());
				}
			} else {
				sb.append(values.toString().trim());
			}
			
			sb.append(' ');
		}
	}
}