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 }