001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2020, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.openid.connect.sdk.rp.statement; 019 020 021import com.nimbusds.jose.JOSEException; 022import com.nimbusds.jose.JOSEObjectType; 023import com.nimbusds.jose.JWSAlgorithm; 024import com.nimbusds.jose.RemoteKeySourceException; 025import com.nimbusds.jose.jwk.JWKSet; 026import com.nimbusds.jose.jwk.source.ImmutableJWKSet; 027import com.nimbusds.jose.jwk.source.JWKSource; 028import com.nimbusds.jose.jwk.source.RemoteJWKSet; 029import com.nimbusds.jose.proc.BadJOSEException; 030import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier; 031import com.nimbusds.jose.proc.JWSVerificationKeySelector; 032import com.nimbusds.jose.proc.SecurityContext; 033import com.nimbusds.jose.util.DefaultResourceRetriever; 034import com.nimbusds.jwt.JWTClaimsSet; 035import com.nimbusds.jwt.SignedJWT; 036import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier; 037import com.nimbusds.jwt.proc.DefaultJWTProcessor; 038import com.nimbusds.oauth2.sdk.ParseException; 039import com.nimbusds.oauth2.sdk.id.Issuer; 040import com.nimbusds.oauth2.sdk.util.CollectionUtils; 041import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 042import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata; 043import net.jcip.annotations.ThreadSafe; 044import net.minidev.json.JSONObject; 045 046import java.net.URL; 047import java.util.Collections; 048import java.util.HashSet; 049import java.util.Set; 050 051 052/** 053 * Processor of software statements for client registrations. 054 * 055 * <p>Related specifications: 056 * 057 * <ul> 058 * <li>OAuth 2.0 Dynamic Client Registration Protocol (RFC 7591) 059 * </ul> 060 * 061 * @param <C> Optional security context to pass to the underlying JWK source. 062 */ 063@ThreadSafe 064public class SoftwareStatementProcessor <C extends SecurityContext> { 065 066 067 private final boolean required; 068 069 070 private final DefaultJWTProcessor<C> processor; 071 072 073 /** 074 * Creates a new software statement processor. 075 * 076 * @param issuer The allowed software statement issuer. Must not be 077 * {@code null}. 078 * @param required If {@code true} the processed client metadata must 079 * include a software statement and if missing this 080 * will result in a {@code invalid_software_statement} 081 * error. If {@code false} client metadata with missing 082 * software statement will be returned unmodified by 083 * the processor. 084 * @param jwsAlgs The allowed JWS algorithms of the software 085 * statements. Must not be empty or {@code null}. 086 * @param jwkSet The public JWK set for verifying the software 087 * statement signatures. 088 */ 089 public SoftwareStatementProcessor(final Issuer issuer, 090 final boolean required, 091 final Set<JWSAlgorithm> jwsAlgs, 092 final JWKSet jwkSet) { 093 094 this(issuer, required, jwsAlgs, new ImmutableJWKSet<C>(jwkSet)); 095 } 096 097 098 /** 099 * Creates a new software statement processor. 100 * 101 * @param issuer The allowed software statement issuer. Must 102 * not be {@code null}. 103 * @param required If {@code true} the processed client 104 * metadata must include a software statement 105 * and if missing this will result in a 106 * {@code invalid_software_statement} error. If 107 * {@code false} client metadata with missing 108 * software statement will be returned 109 * unmodified by the processor. 110 * @param jwsAlgs The allowed JWS algorithms of the software 111 * statements. Must not be empty or 112 * {@code null}. 113 * @param jwkSetURL The public JWK set URL for verifying the 114 * software statement signatures. 115 * @param connectTimeoutMs The HTTP connect timeout in milliseconds for 116 * retrieving the JWK set, zero implies no 117 * timeout (determined by the underlying HTTP 118 * client). 119 * @param readTimeoutMs The HTTP read timeout in milliseconds for 120 * retrieving the JWK set, zero implies no 121 * timeout (determined by the underlying HTTP 122 * client). 123 * @param sizeLimitBytes The HTTP entity size limit in bytes when 124 * retrieving the JWK set, zero implies no 125 * limit. 126 */ 127 public SoftwareStatementProcessor(final Issuer issuer, 128 final boolean required, 129 final Set<JWSAlgorithm> jwsAlgs, 130 final URL jwkSetURL, 131 final int connectTimeoutMs, 132 final int readTimeoutMs, 133 final int sizeLimitBytes) { 134 135 this(issuer, required, jwsAlgs, 136 new RemoteJWKSet<C>( 137 jwkSetURL, 138 new DefaultResourceRetriever( 139 connectTimeoutMs, 140 readTimeoutMs, 141 sizeLimitBytes))); 142 } 143 144 145 /** 146 * Creates a new software statement processor. 147 * 148 * @param issuer The allowed software statement issuer. Must not be 149 * {@code null}. 150 * @param required If {@code true} the processed client metadata must 151 * include a software statement and if missing this 152 * will result in a {@code invalid_software_statement} 153 * error. If {@code false} client metadata with 154 * missing software statement will be returned 155 * unmodified by the processor. 156 * @param jwsAlgs The allowed JWS algorithms of the software 157 * statements. Must not be empty or {@code null}. 158 * @param jwkSource The public JWK source to use for verifying the 159 * software statement signatures. 160 */ 161 public SoftwareStatementProcessor(final Issuer issuer, 162 final boolean required, 163 final Set<JWSAlgorithm> jwsAlgs, 164 final JWKSource<C> jwkSource) { 165 166 this(issuer, required, jwsAlgs, jwkSource, Collections.<String>emptySet()); 167 } 168 169 170 /** 171 * Creates a new software statement processor. 172 * 173 * @param issuer The allowed software statement 174 * issuer. Must not be {@code null}. 175 * @param required If {@code true} the processed client 176 * metadata must include a software 177 * statement and if missing this will 178 * result in a 179 * {@code invalid_software_statement} 180 * error. If {@code false} client 181 * metadata with missing software 182 * statement will be returned 183 * unmodified by the processor. 184 * @param jwsAlgs The allowed JWS algorithms of the 185 * software statements. Must not be 186 * empty or {@code null}. 187 * @param jwkSource The public JWK source to use for 188 * verifying the software statement 189 * signatures. 190 * @param additionalRequiredClaims The names of any additional JWT 191 * claims other than "iss" (issuer) 192 * that must be present in the software 193 * statement, empty or {@code null} if 194 * none. 195 */ 196 @Deprecated 197 public SoftwareStatementProcessor(final Issuer issuer, 198 final boolean required, 199 final Set<JWSAlgorithm> jwsAlgs, 200 final JWKSource<C> jwkSource, 201 final Set<String> additionalRequiredClaims) { 202 203 this(issuer, required, jwsAlgs, null, jwkSource, additionalRequiredClaims); 204 } 205 206 207 /** 208 * Creates a new software statement processor. 209 * 210 * @param issuer The allowed software statement 211 * issuer. Must not be {@code null}. 212 * @param required If {@code true} the processed client 213 * metadata must include a software 214 * statement and if missing this will 215 * result in a 216 * {@code invalid_software_statement} 217 * error. If {@code false} client 218 * metadata with missing software 219 * statement will be returned 220 * unmodified by the processor. 221 * @param jwsAlgs The allowed JWS algorithms of the 222 * software statements. Must not be 223 * empty or {@code null}. 224 * @param jwtTypes The allowed JWT "typ" (type) header 225 * values of the software statements, 226 * {@code null} or empty to accept 227 * {@code JWT} or none. 228 * @param jwkSource The public JWK source to use for 229 * verifying the software statement 230 * signatures. 231 * @param additionalRequiredClaims The names of any additional JWT 232 * claims other than "iss" (issuer) 233 * that must be present in the software 234 * statement, empty or {@code null} if 235 * none. 236 */ 237 public SoftwareStatementProcessor(final Issuer issuer, 238 final boolean required, 239 final Set<JWSAlgorithm> jwsAlgs, 240 final Set<JOSEObjectType> jwtTypes, 241 final JWKSource<C> jwkSource, 242 final Set<String> additionalRequiredClaims) { 243 244 this.required = required; 245 246 Set<String> allRequiredClaims = new HashSet<>(); 247 allRequiredClaims.add("iss"); 248 if (CollectionUtils.isNotEmpty(additionalRequiredClaims)) { 249 allRequiredClaims.addAll(additionalRequiredClaims); 250 } 251 252 processor = new DefaultJWTProcessor<>(); 253 254 if (CollectionUtils.isNotEmpty(jwtTypes)) { 255 processor.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<C>(jwtTypes)); 256 } 257 processor.setJWSKeySelector(new JWSVerificationKeySelector<>(jwsAlgs, jwkSource)); 258 processor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<C>( 259 new JWTClaimsSet.Builder() 260 .issuer(issuer.getValue()) 261 .build(), 262 allRequiredClaims)); 263 } 264 265 266 /** 267 * Processes an optional software statement in the specified client 268 * metadata. 269 * 270 * @param clientMetadata The client metadata, must not be {@code null}. 271 * 272 * @return The processed client metadata, with the merged software 273 * statement. 274 * 275 * @throws InvalidSoftwareStatementException On a invalid or missing 276 * required software 277 * statement. 278 * @throws JOSEException On a internal JOSE 279 * signature verification 280 * exception. 281 */ 282 public OIDCClientMetadata process(final OIDCClientMetadata clientMetadata) 283 throws InvalidSoftwareStatementException, JOSEException { 284 285 return process(clientMetadata, null); 286 } 287 288 289 /** 290 * Processes an optional software statement in the specified client 291 * metadata. 292 * 293 * @param clientMetadata The client metadata, must not be {@code null}. 294 * @param context Optional security context to pass to the 295 * underlying JWK source, {@code null} if not 296 * specified. 297 * 298 * @return The processed client metadata, with the merged software 299 * statement. 300 * 301 * @throws InvalidSoftwareStatementException On a invalid or missing 302 * required software 303 * statement. 304 * @throws JOSEException On a internal JOSE 305 * signature verification 306 * exception. 307 */ 308 public OIDCClientMetadata process(final OIDCClientMetadata clientMetadata, C context) 309 throws InvalidSoftwareStatementException, JOSEException { 310 311 SignedJWT softwareStatement = clientMetadata.getSoftwareStatement(); 312 313 if (softwareStatement == null) { 314 315 if (required) { 316 throw new InvalidSoftwareStatementException("Missing required software statement"); 317 } 318 319 return clientMetadata; 320 } 321 322 JWTClaimsSet statementClaims; 323 try { 324 statementClaims = processor.process(softwareStatement, context); 325 } catch (BadJOSEException e) { 326 throw new InvalidSoftwareStatementException("Invalid software statement JWT: " + e.getMessage(), e); 327 } catch (RemoteKeySourceException e) { 328 throw new InvalidSoftwareStatementException("Software statement JWT validation failed: " + e.getMessage(), e); 329 } 330 331 JSONObject mergedMetadataJSONObject = new JSONObject(); 332 mergedMetadataJSONObject.putAll(clientMetadata.toJSONObject()); 333 mergedMetadataJSONObject.remove("software_statement"); 334 335 JSONObject statementJSONObject = JSONObjectUtils.toJSONObject(statementClaims); 336 statementJSONObject.remove("iss"); 337 mergedMetadataJSONObject.putAll(statementJSONObject); 338 339 try { 340 return OIDCClientMetadata.parse(mergedMetadataJSONObject); 341 } catch (ParseException e) { 342 throw new InvalidSoftwareStatementException("Error merging software statement: " + e.getMessage(), e); 343 } 344 } 345}