001/*
002* The contents of this file are subject to the terms of the Common Development and
003* Distribution License (the License). You may not use this file except in compliance with the
004* License.
005*
006* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007* specific language governing permission and limitations under the License.
008*
009* When distributing Covered Software, include this CDDL Header Notice in each file and include
010* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011* Header, with the fields enclosed by brackets [] replaced by your own identifying
012* information: "Portions copyright [year] [name of copyright owner]".
013*
014* Copyright 2014-2017 ForgeRock AS.
015*/
016
017package org.forgerock.json.jose.jwk;
018
019import java.io.IOException;
020import java.net.URL;
021import java.security.Key;
022import java.util.HashMap;
023import java.util.Map;
024
025import org.forgerock.json.jose.exceptions.FailedToLoadJWKException;
026import org.forgerock.util.SimpleHTTPClient;
027
028/**
029 * Provides methods to gather a JWKSet from a URL and return
030 * a map of key ids to keys as dictated by that JWKS.
031 */
032public class JWKSetParser {
033
034    private SimpleHTTPClient simpleHTTPClient;
035    private JWKLookup jwkLookup;
036
037    /**
038     * Constructor allowing the configuration of the read and connection timeouts used
039     * by the HTTP client for this parser.
040     *
041     * @param readTimeout read timeout in ms
042     * @param connTimeout connection timeout in ms
043     */
044    public JWKSetParser(final int readTimeout, final int connTimeout) {
045        this(new SimpleHTTPClient(readTimeout, connTimeout));
046    }
047
048    /**
049     * Alternative constructor allowing the calling class to pass in an
050     * already-configured {@link SimpleHTTPClient}.
051     *
052     * @param simpleHTTPClient {@link SimpleHTTPClient} used to gather HTTP information
053     */
054    public JWKSetParser(final SimpleHTTPClient simpleHTTPClient) {
055        this(simpleHTTPClient, new JWKLookup());
056    }
057
058    /**
059     * Alternative constructor allowing the calling class to pass in an
060     * already-configured {@link SimpleHTTPClient}.
061     *
062     * @param simpleHTTPClient {@link SimpleHTTPClient} used to gather HTTP information
063     * @param jwkLookup to convert the jwk into a real key
064     */
065    public JWKSetParser(final SimpleHTTPClient simpleHTTPClient, final JWKLookup jwkLookup) {
066        this.simpleHTTPClient = simpleHTTPClient;
067        this.jwkLookup = jwkLookup;
068    }
069
070    /**
071     * Provides a Map of KeyId:Keys as indicated by the JWKSet's URL.
072     *
073     * @param url The URL from which to gather the JWKSet
074     * @return a map of currently valid KeyId:Keys for the provider associated with this URL
075     * @throws FailedToLoadJWKException If there are problems connecting to or parsing the response
076     */
077    public Map<String, Key> generateMapFromJWK(URL url) throws FailedToLoadJWKException {
078        return jwkSetToMap(jwkSet(url));
079    }
080
081    /**
082     * Uses the SimpleHTTPClient to gather HTTP information.
083     *
084     * @param url The URL from which to read the information
085     * @return a String containing the returned JSON
086     * @throws FailedToLoadJWKException If there are problems connecting to the URL
087     */
088    private String gatherHttpContents(URL url) throws FailedToLoadJWKException {
089        try {
090            return simpleHTTPClient.get(url);
091        } catch (IOException e) {
092            throw new FailedToLoadJWKException("Unable to load the JWK location over HTTP", e);
093        }
094    }
095
096    /**
097     * Provides a jwks set as indicated by the JWKSet's URL.
098     *
099     * @param url The URL from which to gather the JWKSet
100     * @return a jwks set valid for the provider associated with this URL
101     * @throws FailedToLoadJWKException If there are problems connecting to or parsing the response
102     */
103    public JWKSet jwkSet(URL url) throws FailedToLoadJWKException {
104        final String jwksContents = gatherHttpContents(url);
105        return JWKSet.parse(jwksContents);
106    }
107
108    /**
109     * Converts a supplied JWKSet into a map of key:values, where the keys are the keyIds and the
110     * values are verification keys.
111     *
112     * @param jwkSet The JWKSet to convert
113     * @return A map of key ids to their respective keys
114     * @throws FailedToLoadJWKException If there are issues parsing the JWKSet's contents
115     */
116    public Map<String, Key> jwkSetToMap(JWKSet jwkSet) throws FailedToLoadJWKException {
117
118        final Map<String, Key> keyMap = new HashMap<>();
119
120        //store the retrieved JSON as String (kid) : Key (having converted) in this resolver
121        for (JWK jwk : jwkSet.getJWKsAsList()) {
122            final Key key = jwkLookup.lookup(jwk.toJsonString(), jwk.getKeyType());
123            keyMap.put(jwk.getKeyId(), key);
124        }
125        return keyMap;
126    }
127}