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 java.net.URL; 022import java.util.Collections; 023import java.util.HashSet; 024import java.util.Set; 025 026import net.jcip.annotations.ThreadSafe; 027import net.minidev.json.JSONObject; 028 029import com.nimbusds.jose.JOSEException; 030import com.nimbusds.jose.JWSAlgorithm; 031import com.nimbusds.jose.RemoteKeySourceException; 032import com.nimbusds.jose.jwk.JWKSet; 033import com.nimbusds.jose.jwk.source.ImmutableJWKSet; 034import com.nimbusds.jose.jwk.source.JWKSource; 035import com.nimbusds.jose.jwk.source.RemoteJWKSet; 036import com.nimbusds.jose.proc.BadJOSEException; 037import com.nimbusds.jose.proc.JWSVerificationKeySelector; 038import com.nimbusds.jose.proc.SecurityContext; 039import com.nimbusds.jose.util.DefaultResourceRetriever; 040import com.nimbusds.jwt.JWTClaimsSet; 041import com.nimbusds.jwt.SignedJWT; 042import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier; 043import com.nimbusds.jwt.proc.DefaultJWTProcessor; 044import com.nimbusds.oauth2.sdk.ParseException; 045import com.nimbusds.oauth2.sdk.id.Issuer; 046import com.nimbusds.oauth2.sdk.util.CollectionUtils; 047import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 048import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata; 049 050 051/** 052 * Processor of software statements for client registrations. 053 * 054 * <p>Related specifications: 055 * 056 * <ul> 057 * <li>OAuth 2.0 Dynamic Client Registration Protocol (RFC 7591) 058 * </ul> 059 * 060 * @param <C> Optional security context to pass to the underlying JWK source. 061 */ 062@ThreadSafe 063public class SoftwareStatementProcessor <C extends SecurityContext> { 064 065 066 private final boolean required; 067 068 069 private final DefaultJWTProcessor<C> processor; 070 071 072 /** 073 * Creates a new software statement processor. 074 * 075 * @param issuer The expected software statement issuer. Must not be 076 * {@code null}. 077 * @param required If {@code true} the processed client metadata must 078 * include a software statement and if missing this 079 * will result in a {@code invalid_software_statement} 080 * error. If {@code false} client metadata with missing 081 * software statement will be returned unmodified by 082 * the processor. 083 * @param jwsAlgs The expected JWS algorithms of the software 084 * statements. Must not be empty or {@code null}. 085 * @param jwkSet The public JWK set for verifying the software 086 * statement signatures. 087 */ 088 public SoftwareStatementProcessor(final Issuer issuer, 089 final boolean required, 090 final Set<JWSAlgorithm> jwsAlgs, 091 final JWKSet jwkSet) { 092 093 this(issuer, required, jwsAlgs, new ImmutableJWKSet<C>(jwkSet)); 094 } 095 096 097 /** 098 * Creates a new software statement processor. 099 * 100 * @param issuer The expected software statement issuer. Must 101 * not be {@code null}. 102 * @param required If {@code true} the processed client 103 * metadata must include a software statement 104 * and if missing this will result in a 105 * {@code invalid_software_statement} error. If 106 * {@code false} client metadata with missing 107 * software statement will be returned 108 * unmodified by the processor. 109 * @param jwsAlgs The expected JWS algorithms of the software 110 * statements. Must not be empty or 111 * {@code null}. 112 * @param jwkSetURL The public JWK set URL for verifying the 113 * software statement signatures. 114 * @param connectTimeoutMs The HTTP connect timeout in milliseconds for 115 * retrieving the JWK set, zero implies no 116 * timeout (determined by the underlying HTTP 117 * client). 118 * @param readTimeoutMs The HTTP read timeout in milliseconds for 119 * retrieving the JWK set, zero implies no 120 * timeout (determined by the underlying HTTP 121 * client). 122 * @param sizeLimitBytes The HTTP entity size limit in bytes when 123 * retrieving the JWK set, zero implies no 124 * limit. 125 */ 126 public SoftwareStatementProcessor(final Issuer issuer, 127 final boolean required, 128 final Set<JWSAlgorithm> jwsAlgs, 129 final URL jwkSetURL, 130 final int connectTimeoutMs, 131 final int readTimeoutMs, 132 final int sizeLimitBytes) { 133 134 this(issuer, required, jwsAlgs, 135 new RemoteJWKSet<C>( 136 jwkSetURL, 137 new DefaultResourceRetriever( 138 connectTimeoutMs, 139 readTimeoutMs, 140 sizeLimitBytes))); 141 } 142 143 144 /** 145 * Creates a new software statement processor. 146 * 147 * @param issuer The expected software statement issuer. Must not be 148 * {@code null}. 149 * @param required If {@code true} the processed client metadata must 150 * include a software statement and if missing this 151 * will result in a {@code invalid_software_statement} 152 * error. If {@code false} client metadata with 153 * missing software statement will be returned 154 * unmodified by the processor. 155 * @param jwsAlgs The expected JWS algorithms of the software 156 * statements. Must not be empty or {@code null}. 157 * @param jwkSource The public JWK source to use for verifying the 158 * software statement signatures. 159 */ 160 public SoftwareStatementProcessor(final Issuer issuer, 161 final boolean required, 162 final Set<JWSAlgorithm> jwsAlgs, 163 final JWKSource<C> jwkSource) { 164 165 this(issuer, required, jwsAlgs, jwkSource, Collections.<String>emptySet()); 166 } 167 168 169 /** 170 * Creates a new software statement processor. 171 * 172 * @param issuer The expected software statement 173 * issuer. Must not be {@code null}. 174 * @param required If {@code true} the processed client 175 * metadata must include a software 176 * statement and if missing this will 177 * result in a 178 * {@code invalid_software_statement} 179 * error. If {@code false} client 180 * metadata with missing software 181 * statement will be returned 182 * unmodified by the processor. 183 * @param jwsAlgs The expected JWS algorithms of the 184 * software statements. Must not be 185 * empty or {@code null}. 186 * @param jwkSource The public JWK source to use for 187 * verifying the software statement 188 * signatures. 189 * @param additionalRequiredClaims The names of any additional JWT 190 * claims other than "iss" (issuer) 191 * that must be present in the software 192 * statement, empty or {@code null} if 193 * none. 194 */ 195 public SoftwareStatementProcessor(final Issuer issuer, 196 final boolean required, 197 final Set<JWSAlgorithm> jwsAlgs, 198 final JWKSource<C> jwkSource, 199 final Set<String> additionalRequiredClaims) { 200 201 this.required = required; 202 203 Set<String> allRequiredClaims = new HashSet<>(); 204 allRequiredClaims.add("iss"); 205 if (CollectionUtils.isNotEmpty(additionalRequiredClaims)) { 206 allRequiredClaims.addAll(additionalRequiredClaims); 207 } 208 209 processor = new DefaultJWTProcessor<>(); 210 processor.setJWSKeySelector(new JWSVerificationKeySelector<>(jwsAlgs, jwkSource)); 211 processor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<C>( 212 new JWTClaimsSet.Builder() 213 .issuer(issuer.getValue()) 214 .build(), 215 allRequiredClaims)); 216 } 217 218 219 /** 220 * Processes an optional software statement in the specified client 221 * metadata. 222 * 223 * @param clientMetadata The client metadata, must not be {@code null}. 224 * 225 * @return The processed client metadata, with the merged software 226 * statement. 227 * 228 * @throws InvalidSoftwareStatementException On a invalid or missing 229 * required software 230 * statement. 231 * @throws JOSEException On a internal JOSE 232 * signature verification 233 * exception. 234 */ 235 public OIDCClientMetadata process(final OIDCClientMetadata clientMetadata) 236 throws InvalidSoftwareStatementException, JOSEException { 237 238 return process(clientMetadata, null); 239 } 240 241 242 /** 243 * Processes an optional software statement in the specified client 244 * metadata. 245 * 246 * @param clientMetadata The client metadata, must not be {@code null}. 247 * @param context Optional security context to pass to the 248 * underlying JWK source, {@code null} if not 249 * specified. 250 * 251 * @return The processed client metadata, with the merged software 252 * statement. 253 * 254 * @throws InvalidSoftwareStatementException On a invalid or missing 255 * required software 256 * statement. 257 * @throws JOSEException On a internal JOSE 258 * signature verification 259 * exception. 260 */ 261 public OIDCClientMetadata process(final OIDCClientMetadata clientMetadata, C context) 262 throws InvalidSoftwareStatementException, JOSEException { 263 264 SignedJWT softwareStatement = clientMetadata.getSoftwareStatement(); 265 266 if (softwareStatement == null) { 267 268 if (required) { 269 throw new InvalidSoftwareStatementException("Missing required software statement"); 270 } 271 272 return clientMetadata; 273 } 274 275 JWTClaimsSet statementClaims; 276 try { 277 statementClaims = processor.process(softwareStatement, context); 278 } catch (BadJOSEException e) { 279 throw new InvalidSoftwareStatementException("Invalid software statement JWT: " + e.getMessage(), e); 280 } catch (RemoteKeySourceException e) { 281 throw new InvalidSoftwareStatementException("Software statement JWT validation failed: " + e.getMessage(), e); 282 } 283 284 JSONObject mergedMetadataJSONObject = new JSONObject(); 285 mergedMetadataJSONObject.putAll(clientMetadata.toJSONObject()); 286 mergedMetadataJSONObject.remove("software_statement"); 287 288 JSONObject statementJSONObject = JSONObjectUtils.toJSONObject(statementClaims); 289 statementJSONObject.remove("iss"); 290 mergedMetadataJSONObject.putAll(statementJSONObject); 291 292 try { 293 return OIDCClientMetadata.parse(mergedMetadataJSONObject); 294 } catch (ParseException e) { 295 throw new InvalidSoftwareStatementException("Error merging software statement: " + e.getMessage(), e); 296 } 297 } 298}