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 Copyrighted [year] [name of copyright owner]". 013 * 014 * Copyright 2013-2017 ForgeRock AS. 015 */ 016 017package org.forgerock.json.jose.jwk; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.Iterator; 023import java.util.LinkedList; 024import java.util.List; 025import java.util.Map; 026 027import org.forgerock.json.JsonException; 028import org.forgerock.json.JsonValue; 029import org.forgerock.json.jose.jwt.Algorithm; 030import org.forgerock.json.jose.jwt.JWObject; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034import com.fasterxml.jackson.databind.ObjectMapper; 035 036/** 037 * Holds a Set of JWKs. 038 */ 039public class JWKSet extends JWObject { 040 041 private static final Logger logger = LoggerFactory.getLogger(JWKSet.class); 042 043 /** 044 * Constructs an empty JWKSet. 045 */ 046 public JWKSet() { 047 put("keys", Collections.EMPTY_LIST); 048 } 049 050 /** 051 * Construct a JWKSet from a single JWK. 052 * @param jwk the jwk to construct the set from 053 */ 054 public JWKSet(JWK jwk) { 055 if (jwk == null) { 056 throw new JsonException("JWK must not be null"); 057 } 058 put("keys", Collections.singletonList(jwk.toJsonValue().asMap())); 059 } 060 061 /** 062 * Construct a JWKSet from a single JWK. 063 * @param jwks contains a list of json web keys 064 */ 065 public JWKSet(JsonValue jwks) { 066 if (jwks == null) { 067 throw new JsonException("JWK set must not be null"); 068 } 069 put("keys", jwks.expect(List.class)); 070 } 071 072 /** 073 * Construct a JWKSet from a List of JWKs. 074 * @param jwkList a list of jwks 075 */ 076 public JWKSet(List<JWK> jwkList) { 077 if (jwkList == null) { 078 throw new JsonException("The list cannot be null"); 079 } 080 //transform to json, as it's our current way of storing jwks 081 List<Map<String, Object>> jwkListAsJson = new ArrayList<>(); 082 for (JWK jwk : jwkList) { 083 jwkListAsJson.add(jwk.toJsonValue().asMap()); 084 } 085 put("keys", jwkListAsJson); 086 } 087 088 /** 089 * Get the JWKs in the set. 090 * @return a list of JWKs 091 */ 092 public List<JWK> getJWKsAsList() { 093 List<JWK> listOfJWKs = new LinkedList<>(); 094 JsonValue jwks = get("keys"); 095 Iterator<JsonValue> i = jwks.iterator(); 096 while (i.hasNext()) { 097 listOfJWKs.add(JWK.parse(i.next())); 098 } 099 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}