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 java.security.Key;
022import java.security.PublicKey;
023import java.util.Collections;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Set;
027import javax.crypto.SecretKey;
028
029import net.jcip.annotations.ThreadSafe;
030
031import com.nimbusds.jose.JWSAlgorithm;
032import com.nimbusds.jose.JWSHeader;
033import com.nimbusds.jose.KeySourceException;
034import com.nimbusds.jose.jwk.JWK;
035import com.nimbusds.jose.jwk.JWKMatcher;
036import com.nimbusds.jose.jwk.JWKSelector;
037import com.nimbusds.jose.jwk.KeyConverter;
038import com.nimbusds.jose.jwk.source.JWKSource;
039
040
041/**
042 * Key selector for verifying JWS objects, where the key candidates are
043 * retrieved from a {@link JWKSource JSON Web Key (JWK) source}.
044 *
045 * @author Vladimir Dzhuvinov
046 * @author Marco Vermeulen
047 * @version 2020-06-02
048 */
049@ThreadSafe
050public class JWSVerificationKeySelector<C extends SecurityContext> extends AbstractJWKSelectorWithSource<C> implements JWSKeySelector<C> {
051
052
053        /**
054         * The allowed JWS algorithms.
055         */
056        private final Set<JWSAlgorithm> jwsAlgs;
057
058        /**
059         * Present to maintain backward compatibility
060         */
061        private final boolean singleJwsAlgConstructorWasCalled;
062
063        /**
064         * Creates a new JWS verification key selector.
065         *
066         * @param jwsAlg    The allowed JWS algorithm for the objects to be
067         *                  verified. Must not be {@code null}.
068         * @param jwkSource The JWK source. Must not be {@code null}.
069         */
070        public JWSVerificationKeySelector(final JWSAlgorithm jwsAlg, final JWKSource<C> jwkSource) {
071                super(jwkSource);
072                if (jwsAlg == null) {
073                        throw new IllegalArgumentException("The JWS algorithm must not be null");
074                }
075                this.jwsAlgs = Collections.singleton(jwsAlg);
076                this.singleJwsAlgConstructorWasCalled = true;
077        }
078
079        
080        /**
081         * Creates a new JWS verification key selector.
082         *
083         * @param jwsAlgs   The allowed JWS algorithms for the objects to be
084         *                  verified. Must not be empty or {@code null}.
085         * @param jwkSource The JWK source. Must not be {@code null}.
086         */
087        public JWSVerificationKeySelector(final Set<JWSAlgorithm> jwsAlgs, final JWKSource<C> jwkSource) {
088                super(jwkSource);
089                if (jwsAlgs == null || jwsAlgs.isEmpty()) {
090                        throw new IllegalArgumentException("The JWS algorithms must not be null or empty");
091                }
092                this.jwsAlgs = Collections.unmodifiableSet(jwsAlgs);
093                this.singleJwsAlgConstructorWasCalled = false;
094        }
095
096        
097        /**
098         * Checks if a JWS algorithm is allowed for key selection.
099         *
100         * @param jwsAlg The JWS algorithm to check.
101         *
102         * @return {@code true} if allowed, else {@code false}.
103         */
104        public boolean isAllowed(final JWSAlgorithm jwsAlg) {
105                return jwsAlgs.contains(jwsAlg);
106        }
107
108
109        /**
110         * Returns the expected JWS algorithm.
111         *
112         * @return The expected JWS algorithm.
113         * @deprecated Use {@link #isAllowed(JWSAlgorithm)} instead
114         */
115        @Deprecated
116        public JWSAlgorithm getExpectedJWSAlgorithm() {
117                if (singleJwsAlgConstructorWasCalled) {
118                        return jwsAlgs.iterator().next();
119                }
120                throw new UnsupportedOperationException("Since this class was constructed with multiple " +
121                                "algorithms, the behavior of this method is undefined.");
122        }
123
124        /**
125         * Creates a JWK matcher for the expected JWS algorithm and the
126         * specified JWS header.
127         *
128         * @param jwsHeader The JWS header. Must not be {@code null}.
129         *
130         * @return The JWK matcher, {@code null} if none could be created.
131         */
132        protected JWKMatcher createJWKMatcher(final JWSHeader jwsHeader) {
133
134                if (! isAllowed(jwsHeader.getAlgorithm())) {
135                        // Unexpected JWS alg
136                        return null;
137                } else {
138                        return JWKMatcher.forJWSHeader(jwsHeader);
139                }
140        }
141
142
143        @Override
144        public List<Key> selectJWSKeys(final JWSHeader jwsHeader, final C context)
145                throws KeySourceException {
146
147                if (! jwsAlgs.contains(jwsHeader.getAlgorithm())) {
148                        // Unexpected JWS alg
149                        return Collections.emptyList();
150                }
151
152                JWKMatcher jwkMatcher = createJWKMatcher(jwsHeader);
153                if (jwkMatcher == null) {
154                        return Collections.emptyList();
155                }
156
157                List<JWK> jwkMatches = getJWKSource().get(new JWKSelector(jwkMatcher), context);
158
159                List<Key> sanitizedKeyList = new LinkedList<>();
160
161                for (Key key: KeyConverter.toJavaKeys(jwkMatches)) {
162                        if (key instanceof PublicKey || key instanceof SecretKey) {
163                                sanitizedKeyList.add(key);
164                        } // skip asymmetric private keys
165                }
166
167                return sanitizedKeyList;
168        }
169}