001/*******************************************************************************
002 * Copyright 2017 The MIT Internet Trust Consortium
003 *
004 * Portions copyright 2011-2013 The MITRE Corporation
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the 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
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 *******************************************************************************/
018/**
019 *
020 */
021package org.mitre.jwt.signer.service.impl;
022
023import java.util.concurrent.ExecutionException;
024import java.util.concurrent.TimeUnit;
025
026import org.apache.http.client.HttpClient;
027import org.apache.http.impl.client.HttpClientBuilder;
028import org.mitre.jose.keystore.JWKSetKeyStore;
029import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService;
030import org.mitre.jwt.encryption.service.impl.DefaultJWTEncryptionAndDecryptionService;
031import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
035import org.springframework.stereotype.Service;
036import org.springframework.web.client.RestClientException;
037import org.springframework.web.client.RestTemplate;
038
039import com.google.common.cache.CacheBuilder;
040import com.google.common.cache.CacheLoader;
041import com.google.common.cache.LoadingCache;
042import com.google.common.util.concurrent.UncheckedExecutionException;
043import com.google.gson.JsonParseException;
044import com.nimbusds.jose.jwk.JWKSet;
045
046/**
047 *
048 * Creates a caching map of JOSE signers/validators and encrypters/decryptors
049 * keyed on the JWK Set URI. Dynamically loads JWK Sets to create the services.
050 *
051 * @author jricher
052 *
053 */
054@Service
055public class JWKSetCacheService {
056
057        /**
058         * Logger for this class
059         */
060        private static final Logger logger = LoggerFactory.getLogger(JWKSetCacheService.class);
061
062        // map of jwk set uri -> signing/validation service built on the keys found in that jwk set
063        private LoadingCache<String, JWTSigningAndValidationService> validators;
064
065        // map of jwk set uri -> encryption/decryption service built on the keys found in that jwk set
066        private LoadingCache<String, JWTEncryptionAndDecryptionService> encrypters;
067
068        public JWKSetCacheService() {
069                this.validators = CacheBuilder.newBuilder()
070                                .expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch
071                                .maximumSize(100)
072                                .build(new JWKSetVerifierFetcher(HttpClientBuilder.create().useSystemProperties().build()));
073                this.encrypters = CacheBuilder.newBuilder()
074                                .expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch
075                                .maximumSize(100)
076                                .build(new JWKSetEncryptorFetcher(HttpClientBuilder.create().useSystemProperties().build()));
077        }
078
079        /**
080         * @param jwksUri
081         * @return
082         * @throws ExecutionException
083         * @see com.google.common.cache.Cache#get(java.lang.Object)
084         */
085        public JWTSigningAndValidationService getValidator(String jwksUri) {
086                try {
087                        return validators.get(jwksUri);
088                } catch (UncheckedExecutionException | ExecutionException e) {
089                        logger.warn("Couldn't load JWK Set from " + jwksUri + ": " + e.getMessage());
090                        return null;
091                }
092        }
093
094        public JWTEncryptionAndDecryptionService getEncrypter(String jwksUri) {
095                try {
096                        return encrypters.get(jwksUri);
097                } catch (UncheckedExecutionException | ExecutionException e) {
098                        logger.warn("Couldn't load JWK Set from " + jwksUri + ": " + e.getMessage());
099                        return null;
100                }
101        }
102
103        /**
104         * @author jricher
105         *
106         */
107        private class JWKSetVerifierFetcher extends CacheLoader<String, JWTSigningAndValidationService> {
108                private HttpComponentsClientHttpRequestFactory httpFactory;
109                private RestTemplate restTemplate;
110
111                JWKSetVerifierFetcher(HttpClient httpClient) {
112                        this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
113                        this.restTemplate = new RestTemplate(httpFactory);
114                }
115
116                /**
117                 * Load the JWK Set and build the appropriate signing service.
118                 */
119                @Override
120                public JWTSigningAndValidationService load(String key) throws Exception {
121                        String jsonString = restTemplate.getForObject(key, String.class);
122                        JWKSet jwkSet = JWKSet.parse(jsonString);
123
124                        JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet);
125
126                        JWTSigningAndValidationService service = new DefaultJWTSigningAndValidationService(keyStore);
127
128                        return service;
129                }
130
131        }
132
133        /**
134         * @author jricher
135         *
136         */
137        private class JWKSetEncryptorFetcher extends CacheLoader<String, JWTEncryptionAndDecryptionService> {
138                private HttpComponentsClientHttpRequestFactory httpFactory;
139                private RestTemplate restTemplate;
140
141                public JWKSetEncryptorFetcher(HttpClient httpClient) {
142                        this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
143                        this.restTemplate = new RestTemplate(httpFactory);
144                }
145
146                /* (non-Javadoc)
147                 * @see com.google.common.cache.CacheLoader#load(java.lang.Object)
148                 */
149                @Override
150                public JWTEncryptionAndDecryptionService load(String key) throws Exception {
151                        try {
152                                String jsonString = restTemplate.getForObject(key, String.class);
153                                JWKSet jwkSet = JWKSet.parse(jsonString);
154
155                                JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet);
156
157                                JWTEncryptionAndDecryptionService service = new DefaultJWTEncryptionAndDecryptionService(keyStore);
158
159                                return service;
160                        } catch (JsonParseException | RestClientException e) {
161                                throw new IllegalArgumentException("Unable to load JWK Set");
162                        }
163                }
164        }
165
166}