001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2019, Connect2id Ltd.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.proc;
019
020
021import com.nimbusds.jose.JWSAlgorithm;
022import com.nimbusds.jose.JWSHeader;
023import com.nimbusds.jose.KeySourceException;
024import com.nimbusds.jose.jwk.JWK;
025import com.nimbusds.jose.jwk.JWKMatcher;
026import com.nimbusds.jose.jwk.JWKSelector;
027import com.nimbusds.jose.jwk.KeyConverter;
028import com.nimbusds.jose.jwk.source.JWKSource;
029import net.jcip.annotations.ThreadSafe;
030
031import javax.crypto.SecretKey;
032import java.security.Key;
033import java.security.PublicKey;
034import java.util.*;
035
036
037/**
038 * Key selector for verifying JWS objects, where the key candidates are
039 * retrieved from a {@link JWKSource JSON Web Key (JWK) source}.
040 *
041 * @author Vladimir Dzhuvinov
042 * @author Marco Vermeulen
043 * @version 2024-04-20
044 */
045@ThreadSafe
046public class JWSVerificationKeySelector<C extends SecurityContext> extends AbstractJWKSelectorWithSource<C> implements JWSKeySelector<C> {
047
048
049        /**
050         * The allowed JWS algorithms.
051         */
052        private final Set<JWSAlgorithm> jwsAlgs;
053
054        /**
055         * Present to maintain backward compatibility
056         */
057        private final boolean singleJwsAlgConstructorWasCalled;
058
059        /**
060         * Creates a new JWS verification key selector.
061         *
062         * @param jwsAlg    The allowed JWS algorithm for the objects to be
063         *                  verified. Must not be {@code null}.
064         * @param jwkSource The JWK source. Must not be {@code null}.
065         */
066        public JWSVerificationKeySelector(final JWSAlgorithm jwsAlg, final JWKSource<C> jwkSource) {
067                super(jwkSource);
068                this.jwsAlgs = Collections.singleton(Objects.requireNonNull(jwsAlg));
069                this.singleJwsAlgConstructorWasCalled = true;
070        }
071
072        
073        /**
074         * Creates a new JWS verification key selector.
075         *
076         * @param jwsAlgs   The allowed JWS algorithms for the objects to be
077         *                  verified. Must not be empty or {@code null}.
078         * @param jwkSource The JWK source. Must not be {@code null}.
079         */
080        public JWSVerificationKeySelector(final Set<JWSAlgorithm> jwsAlgs, final JWKSource<C> jwkSource) {
081                super(jwkSource);
082                if (jwsAlgs.isEmpty()) {
083                        throw new IllegalArgumentException("The JWS algorithms must not be empty");
084                }
085                this.jwsAlgs = Collections.unmodifiableSet(jwsAlgs);
086                this.singleJwsAlgConstructorWasCalled = false;
087        }
088
089        
090        /**
091         * Checks if a JWS algorithm is allowed for key selection.
092         *
093         * @param jwsAlg The JWS algorithm to check.
094         *
095         * @return {@code true} if allowed, else {@code false}.
096         */
097        public boolean isAllowed(final JWSAlgorithm jwsAlg) {
098                return jwsAlgs.contains(jwsAlg);
099        }
100
101
102        /**
103         * Returns the expected JWS algorithm.
104         *
105         * @return The expected JWS algorithm.
106         * @deprecated Use {@link #isAllowed(JWSAlgorithm)} instead
107         */
108        @Deprecated
109        public JWSAlgorithm getExpectedJWSAlgorithm() {
110                if (singleJwsAlgConstructorWasCalled) {
111                        return jwsAlgs.iterator().next();
112                }
113                throw new UnsupportedOperationException("Since this class was constructed with multiple " +
114                                "algorithms, the behavior of this method is undefined.");
115        }
116
117        /**
118         * Creates a JWK matcher for the expected JWS algorithm and the
119         * specified JWS header.
120         *
121         * @param jwsHeader The JWS header. Must not be {@code null}.
122         *
123         * @return The JWK matcher, {@code null} if none could be created.
124         */
125        protected JWKMatcher createJWKMatcher(final JWSHeader jwsHeader) {
126
127                if (! isAllowed(jwsHeader.getAlgorithm())) {
128                        // Unexpected JWS alg
129                        return null;
130                } else {
131                        return JWKMatcher.forJWSHeader(jwsHeader);
132                }
133        }
134
135
136        @Override
137        public List<Key> selectJWSKeys(final JWSHeader jwsHeader, final C context)
138                throws KeySourceException {
139
140                if (! jwsAlgs.contains(jwsHeader.getAlgorithm())) {
141                        // Unexpected JWS alg
142                        return Collections.emptyList();
143                }
144
145                JWKMatcher jwkMatcher = createJWKMatcher(jwsHeader);
146                if (jwkMatcher == null) {
147                        return Collections.emptyList();
148                }
149
150                List<JWK> jwkMatches = getJWKSource().get(new JWKSelector(jwkMatcher), context);
151
152                List<Key> sanitizedKeyList = new LinkedList<>();
153
154                for (Key key: KeyConverter.toJavaKeys(jwkMatches)) {
155                        if (key instanceof PublicKey || key instanceof SecretKey) {
156                                sanitizedKeyList.add(key);
157                        } // skip asymmetric private keys
158                }
159
160                return sanitizedKeyList;
161        }
162}