View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions Copyrighted [year] [name of copyright owner]".
13   *
14   * Copyright 2013-2017 ForgeRock AS.
15   */
16  
17  package org.forgerock.json.jose.jwk;
18  
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.Iterator;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.forgerock.json.JsonException;
28  import org.forgerock.json.JsonValue;
29  import org.forgerock.json.jose.jwt.Algorithm;
30  import org.forgerock.json.jose.jwt.JWObject;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  import com.fasterxml.jackson.databind.ObjectMapper;
35  
36  /**
37   * Holds a Set of JWKs.
38   */
39  public class JWKSet extends JWObject {
40  
41      private static final Logger logger = LoggerFactory.getLogger(JWKSet.class);
42  
43      /**
44       * Constructs an empty JWKSet.
45       */
46      public JWKSet() {
47          put("keys", Collections.EMPTY_LIST);
48      }
49  
50      /**
51       * Construct a JWKSet from a single JWK.
52       * @param jwk the jwk to construct the set from
53       */
54      public JWKSet(JWK jwk) {
55          if (jwk == null) {
56              throw new JsonException("JWK must not be null");
57          }
58          put("keys", Collections.singletonList(jwk.toJsonValue().asMap()));
59      }
60  
61      /**
62       * Construct a JWKSet from a single JWK.
63       * @param jwks contains a list of json web keys
64       */
65      public JWKSet(JsonValue jwks) {
66          if (jwks == null) {
67              throw new JsonException("JWK set must not be null");
68          }
69          put("keys", jwks.expect(List.class));
70      }
71  
72      /**
73       * Construct a JWKSet from a List of JWKs.
74       * @param jwkList a list of jwks
75       */
76      public JWKSet(List<JWK> jwkList) {
77          if (jwkList == null) {
78              throw new JsonException("The list cannot be null");
79          }
80          //transform to json, as it's our current way of storing jwks
81          List<Map<String, Object>> jwkListAsJson = new ArrayList<>();
82          for (JWK jwk : jwkList) {
83              jwkListAsJson.add(jwk.toJsonValue().asMap());
84          }
85          put("keys", jwkListAsJson);
86      }
87  
88      /**
89       * Get the JWKs in the set.
90       * @return a list of JWKs
91       */
92      public List<JWK> getJWKsAsList() {
93          List<JWK> listOfJWKs = new LinkedList<>();
94          JsonValue jwks = get("keys");
95          Iterator<JsonValue> i = jwks.iterator();
96          while (i.hasNext()) {
97              listOfJWKs.add(JWK.parse(i.next()));
98          }
99          return listOfJWKs;
100     }
101 
102     /**
103      * Get the JWKs in the set.
104      * @return a list of JWKs as JsonValues
105      */
106     public JsonValue getJWKsAsJsonValue() {
107         return get("keys");
108     }
109 
110     /**
111      * Converts a json string to a jsonValue.
112      * @param json a json jwk set object string
113      * @return a json value of the son string
114      * @throws JsonException if unable to parse
115      */
116     protected static JsonValue toJsonValue(String json) {
117         ObjectMapper mapper = new ObjectMapper();
118         try {
119             return new JsonValue(mapper.readValue(json, Map.class));
120         } catch (IOException e) {
121             throw new JsonException("Failed to parse json", e);
122         }
123     }
124 
125     /**
126      * Parses a JWKSet object from a string json object.
127      * @param json string json object
128      * @return a JWKSet
129      */
130     public static JWKSet parse(String json) {
131         JsonValue jwkSet = new JsonValue(toJsonValue(json));
132         return parse(jwkSet);
133     }
134 
135     /**
136      * Parses a JWKSet object from a jsonValue object.
137      * @param json an JsonValue object
138      * @return a JWKSet
139      */
140     public static JWKSet parse(JsonValue json) {
141         if (json == null) {
142             throw new JsonException("Cant parse JWKSet. No json data.");
143         }
144         return new JWKSet(json.get("keys"));
145     }
146 
147     /**
148      * Prints the JWK Set as a json string.
149      * @return A String representing JWK
150      */
151     public String toJsonString() {
152         return super.toString();
153     }
154 
155     /**
156      * Search for a JWK that matches the algorithm and the key usage.
157      *
158      * @param algorithm the algorithm needed
159      * @param keyUse the key usage. If null, only the algorithm will be used as a search criteria.
160      * @return A jwk that matches the search criteria. If no JWK found for the key usage, then it searches for a JWK
161      * without key usage defined. If still no JWK found, then returns null.
162      */
163     public JWK findJwk(Algorithm algorithm, KeyUse keyUse) {
164         //First, we try to find a JWK that matches the keyUse
165         for (JWK jwk : getJWKsAsList()) {
166             try {
167                 if (algorithm.getJwaAlgorithmName().equalsIgnoreCase(jwk.getAlgorithm()) && (keyUse == jwk.getUse())) {
168                     return jwk;
169                 }
170             } catch (IllegalArgumentException e) {
171                 // We raise a warning as the JWKs could be the client one, with some non-compliant JWK.
172                 logger.warn("Can't load JWK with kid'" + jwk.getKeyId() + "'", e);
173             }
174         }
175 
176         //At this point, no jwk was found. We can try to find a JWK without a keyUse now
177         return keyUse != null ? findJwk(algorithm, null) :  null;
178     }
179 
180     /**
181      * Search for a JWK that matches the kid.
182      *
183      * @param kid Key ID
184      * @return A jwk that matches the kid. If no JWK found, returns null
185      */
186     public JWK findJwk(String kid) {
187         for (JWK jwk : getJWKsAsList()) {
188             if (kid.equals(jwk.getKeyId())) {
189                 return jwk;
190             }
191         }
192         return null;
193     }
194 }