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 ******************************************************************************/
017/**
018 * 
019 */
020package org.mitre.jwt.signer.service.impl;
021
022import java.util.concurrent.ExecutionException;
023import java.util.concurrent.TimeUnit;
024
025import org.apache.http.client.HttpClient;
026import org.apache.http.impl.client.HttpClientBuilder;
027import org.mitre.jose.keystore.JWKSetKeyStore;
028import org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService;
029import org.mitre.jwt.encryption.service.impl.DefaultJwtEncryptionAndDecryptionService;
030import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
034import org.springframework.stereotype.Service;
035import org.springframework.web.client.RestTemplate;
036
037import com.google.common.cache.CacheBuilder;
038import com.google.common.cache.CacheLoader;
039import com.google.common.cache.LoadingCache;
040import com.google.common.util.concurrent.UncheckedExecutionException;
041import com.nimbusds.jose.jwk.JWKSet;
042
043/**
044 * 
045 * Creates a caching map of JOSE signers/validators and encrypters/decryptors
046 * keyed on the JWK Set URI. Dynamically loads JWK Sets to create the services.
047 * 
048 * @author jricher
049 * 
050 */
051@Service
052public class JWKSetCacheService {
053
054        private static Logger logger = LoggerFactory.getLogger(JWKSetCacheService.class);
055
056        // map of jwk set uri -> signing/validation service built on the keys found in that jwk set
057        private LoadingCache<String, JwtSigningAndValidationService> validators;
058
059        // map of jwk set uri -> encryption/decryption service built on the keys found in that jwk set
060        private LoadingCache<String, JwtEncryptionAndDecryptionService> encrypters;
061
062        public JWKSetCacheService() {
063                this.validators = CacheBuilder.newBuilder()
064                                .expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch
065                                .maximumSize(100)
066                                .build(new JWKSetVerifierFetcher());
067                this.encrypters = CacheBuilder.newBuilder()
068                                .expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch
069                                .maximumSize(100)
070                                .build(new JWKSetEncryptorFetcher());
071        }
072
073        /**
074         * @param jwksUri
075         * @return
076         * @throws ExecutionException
077         * @see com.google.common.cache.Cache#get(java.lang.Object)
078         */
079        public JwtSigningAndValidationService getValidator(String jwksUri) {
080                try {
081                        return validators.get(jwksUri);
082                } catch (UncheckedExecutionException ue) {
083                        logger.warn("Couldn't load JWK Set from " + jwksUri, ue);
084                        return null;
085                } catch (ExecutionException e) {
086                        logger.warn("Couldn't load JWK Set from " + jwksUri, e);
087                        return null;
088                }
089        }
090
091        public JwtEncryptionAndDecryptionService getEncrypter(String jwksUri) {
092                try {
093                        return encrypters.get(jwksUri);
094                } catch (UncheckedExecutionException ue) {
095                        logger.warn("Couldn't load JWK Set from " + jwksUri, ue);
096                        return null;
097                } catch (ExecutionException e) {
098                        logger.warn("Couldn't load JWK Set from " + jwksUri, e);
099                        return null;
100                }
101        }
102
103        /**
104         * @author jricher
105         *
106         */
107        private class JWKSetVerifierFetcher extends CacheLoader<String, JwtSigningAndValidationService> {
108                private HttpClient httpClient = HttpClientBuilder.create()
109                .useSystemProperties()
110                .build();
111                private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
112                private RestTemplate restTemplate = new RestTemplate(httpFactory);
113
114                /**
115                 * Load the JWK Set and build the appropriate signing service.
116                 */
117                @Override
118                public JwtSigningAndValidationService load(String key) throws Exception {
119
120                        String jsonString = restTemplate.getForObject(key, String.class);
121                        JWKSet jwkSet = JWKSet.parse(jsonString);
122
123                        JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet);
124
125                        JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService(keyStore);
126
127                        return service;
128
129                }
130
131        }
132
133        /**
134         * @author jricher
135         *
136         */
137        private class JWKSetEncryptorFetcher extends CacheLoader<String, JwtEncryptionAndDecryptionService> {
138                private HttpClient httpClient = HttpClientBuilder.create()
139                .useSystemProperties()
140                .build();
141                private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
142                private RestTemplate restTemplate = new RestTemplate(httpFactory);
143                /* (non-Javadoc)
144                 * @see com.google.common.cache.CacheLoader#load(java.lang.Object)
145                 */
146                @Override
147                public JwtEncryptionAndDecryptionService load(String key) throws Exception {
148                        String jsonString = restTemplate.getForObject(key, String.class);
149                        JWKSet jwkSet = JWKSet.parse(jsonString);
150
151                        JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet);
152
153                        JwtEncryptionAndDecryptionService service = new DefaultJwtEncryptionAndDecryptionService(keyStore);
154
155                        return service;
156                }
157        }
158
159}