001/*******************************************************************************
002 * Copyright 2014 The MITRE Corporation
003 *   and the MIT Kerberos and Internet Trust Consortium
004 * 
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 * 
009 *   http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 ******************************************************************************/
017package org.mitre.jwt.signer.service.impl;
018
019import java.security.NoSuchAlgorithmException;
020import java.security.spec.InvalidKeySpecException;
021import java.util.Collection;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.Map;
025import java.util.Set;
026import java.util.UUID;
027
028import org.mitre.jose.keystore.JWKSetKeyStore;
029import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import com.google.common.base.Strings;
034import com.nimbusds.jose.JOSEException;
035import com.nimbusds.jose.JWSAlgorithm;
036import com.nimbusds.jose.JWSSigner;
037import com.nimbusds.jose.JWSVerifier;
038import com.nimbusds.jose.crypto.MACSigner;
039import com.nimbusds.jose.crypto.MACVerifier;
040import com.nimbusds.jose.crypto.RSASSASigner;
041import com.nimbusds.jose.crypto.RSASSAVerifier;
042import com.nimbusds.jose.jwk.ECKey;
043import com.nimbusds.jose.jwk.JWK;
044import com.nimbusds.jose.jwk.OctetSequenceKey;
045import com.nimbusds.jose.jwk.RSAKey;
046import com.nimbusds.jwt.SignedJWT;
047
048public class DefaultJwtSigningAndValidationService implements JwtSigningAndValidationService {
049
050        // map of identifier to signer
051        private Map<String, JWSSigner> signers = new HashMap<String, JWSSigner>();
052
053        // map of identifier to verifier
054        private Map<String, JWSVerifier> verifiers = new HashMap<String, JWSVerifier>();
055
056        private static Logger logger = LoggerFactory.getLogger(DefaultJwtSigningAndValidationService.class);
057
058        private String defaultSignerKeyId;
059
060        private JWSAlgorithm defaultAlgorithm;
061
062        // map of identifier to key
063        private Map<String, JWK> keys = new HashMap<String, JWK>();
064
065        /**
066         * Build this service based on the keys given. All public keys will be used
067         * to make verifiers, all private keys will be used to make signers.
068         * 
069         * @param keys
070         *            A map of key identifier to key
071         * 
072         * @throws InvalidKeySpecException
073         *             If the keys in the JWKs are not valid
074         * @throws NoSuchAlgorithmException
075         *             If there is no appropriate algorithm to tie the keys to.
076         */
077        public DefaultJwtSigningAndValidationService(Map<String, JWK> keys) throws NoSuchAlgorithmException, InvalidKeySpecException {
078                this.keys = keys;
079                buildSignersAndVerifiers();
080        }
081
082        /**
083         * Build this service based on the given keystore. All keys must have a key
084         * id ({@code kid}) field in order to be used.
085         * 
086         * @param keyStore
087         *            the keystore to load all keys from
088         * 
089         * @throws InvalidKeySpecException
090         *             If the keys in the JWKs are not valid
091         * @throws NoSuchAlgorithmException
092         *             If there is no appropriate algorithm to tie the keys to.
093         */
094        public DefaultJwtSigningAndValidationService(JWKSetKeyStore keyStore) throws NoSuchAlgorithmException, InvalidKeySpecException {
095                // convert all keys in the keystore to a map based on key id
096                if (keyStore!= null && keyStore.getJwkSet() != null) {
097                        for (JWK key : keyStore.getKeys()) {
098                                if (!Strings.isNullOrEmpty(key.getKeyID())) {
099                                        // use the key ID that's built into the key itself
100                                        // TODO (#641): deal with JWK thumbprints
101                                        this.keys.put(key.getKeyID(), key);
102                                } else {
103                                        // create a random key id
104                                        String fakeKid = UUID.randomUUID().toString();
105                                        this.keys.put(fakeKid, key);
106                                }
107                        }
108                }
109                buildSignersAndVerifiers();
110        }
111
112
113        /**
114         * @return the defaultSignerKeyId
115         */
116        @Override
117        public String getDefaultSignerKeyId() {
118                        return defaultSignerKeyId;
119        }
120
121        /**
122         * @param defaultSignerKeyId the defaultSignerKeyId to set
123         */
124        public void setDefaultSignerKeyId(String defaultSignerId) {
125                this.defaultSignerKeyId = defaultSignerId;
126        }
127
128        /**
129         * @return
130         */
131        @Override
132        public JWSAlgorithm getDefaultSigningAlgorithm() {
133                return defaultAlgorithm;
134        }
135
136        public void setDefaultSigningAlgorithmName(String algName) {
137                defaultAlgorithm = JWSAlgorithm.parse(algName);
138        }
139
140        public String getDefaultSigningAlgorithmName() {
141                if (defaultAlgorithm != null) {
142                        return defaultAlgorithm.getName();
143                } else {
144                        return null;
145                }
146        }
147
148        /**
149         * Build all of the signers and verifiers for this based on the key map.
150         * @throws InvalidKeySpecException If the keys in the JWKs are not valid
151         * @throws NoSuchAlgorithmException If there is no appropriate algorithm to tie the keys to.
152         */
153        private void buildSignersAndVerifiers() throws NoSuchAlgorithmException, InvalidKeySpecException {
154                for (Map.Entry<String, JWK> jwkEntry : keys.entrySet()) {
155
156                        String id = jwkEntry.getKey();
157                        JWK jwk = jwkEntry.getValue();
158
159                        if (jwk instanceof RSAKey) {
160                                // build RSA signers & verifiers
161
162                                if (jwk.isPrivate()) { // only add the signer if there's a private key
163                                        RSASSASigner signer = new RSASSASigner(((RSAKey) jwk).toRSAPrivateKey());
164                                        signers.put(id, signer);
165                                }
166
167                                RSASSAVerifier verifier = new RSASSAVerifier(((RSAKey) jwk).toRSAPublicKey());
168                                verifiers.put(id, verifier);
169
170                        } else if (jwk instanceof ECKey) {
171                                // build EC signers & verifiers
172
173                                // TODO: add support for EC keys
174                                logger.warn("EC Keys are not yet supported.");
175
176                        } else if (jwk instanceof OctetSequenceKey) {
177                                // build HMAC signers & verifiers
178
179                                if (jwk.isPrivate()) { // technically redundant check because all HMAC keys are private
180                                        MACSigner signer = new MACSigner(((OctetSequenceKey) jwk).toByteArray());
181                                        signers.put(id, signer);
182                                }
183
184                                MACVerifier verifier = new MACVerifier(((OctetSequenceKey) jwk).toByteArray());
185                                verifiers.put(id, verifier);
186
187                        } else {
188                                logger.warn("Unknown key type: " + jwk);
189                        }
190                }
191                
192                if (defaultSignerKeyId == null && keys.size() == 1) {
193                        // if there's only one key, it's the default
194                        setDefaultSignerKeyId(keys.keySet().iterator().next());
195                }
196        }
197
198        /**
199         * Sign a jwt in place using the configured default signer.
200         */
201        @Override
202        public void signJwt(SignedJWT jwt) {
203                if (getDefaultSignerKeyId() == null) {
204                        throw new IllegalStateException("Tried to call default signing with no default signer ID set");
205                }
206
207                JWSSigner signer = signers.get(getDefaultSignerKeyId());
208
209                try {
210                        jwt.sign(signer);
211                } catch (JOSEException e) {
212
213                        logger.error("Failed to sign JWT, error was: ", e);
214                }
215
216        }
217
218        @Override
219        public void signJwt(SignedJWT jwt, JWSAlgorithm alg) {
220
221                JWSSigner signer = null;
222
223                for (JWSSigner s : signers.values()) {
224                        if (s.supportedAlgorithms().contains(alg)) {
225                                signer = s;
226                                break;
227                        }
228                }
229
230                if (signer == null) {
231                        //If we can't find an algorithm that matches, we can't sign
232                        logger.error("No matching algirthm found for alg=" + alg);
233
234                }
235
236                try {
237                        jwt.sign(signer);
238                } catch (JOSEException e) {
239
240                        logger.error("Failed to sign JWT, error was: ", e);
241                }
242
243        }
244
245        @Override
246        public boolean validateSignature(SignedJWT jwt) {
247
248                for (JWSVerifier verifier : verifiers.values()) {
249                        try {
250                                if (jwt.verify(verifier)) {
251                                        return true;
252                                }
253                        } catch (JOSEException e) {
254
255                                logger.error("Failed to validate signature, error was: ", e);
256                        }
257                }
258                return false;
259        }
260
261        @Override
262        public Map<String, JWK> getAllPublicKeys() {
263                Map<String, JWK> pubKeys = new HashMap<String, JWK>();
264
265                // pull all keys out of the verifiers if we know how
266                for (String keyId : keys.keySet()) {
267                        JWK key = keys.get(keyId);
268                        JWK pub = key.toPublicJWK();
269                        if (pub != null) {
270                                pubKeys.put(keyId, pub);
271                        }
272                }
273
274                return pubKeys;
275        }
276
277        /* (non-Javadoc)
278         * @see org.mitre.jwt.signer.service.JwtSigningAndValidationService#getAllSigningAlgsSupported()
279         */
280        @Override
281        public Collection<JWSAlgorithm> getAllSigningAlgsSupported() {
282
283                Set<JWSAlgorithm> algs = new HashSet<JWSAlgorithm>();
284
285                for (JWSSigner signer : signers.values()) {
286                        algs.addAll(signer.supportedAlgorithms());
287                }
288
289                for (JWSVerifier verifier : verifiers.values()) {
290                        algs.addAll(verifier.supportedAlgorithms());
291                }
292
293                return algs;
294
295        }
296
297}