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), sections 058 * 2.3 and 3.1.1. 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 expected 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 expected 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 expected 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 expected 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 expected 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 expected 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 expected 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 expected 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 public SoftwareStatementProcessor(final Issuer issuer, 197 final boolean required, 198 final Set<JWSAlgorithm> jwsAlgs, 199 final JWKSource<C> jwkSource, 200 final Set<String> additionalRequiredClaims) { 201 202 this.required = required; 203 204 Set<String> allRequiredClaims = new HashSet<>(); 205 allRequiredClaims.add("iss"); 206 if (CollectionUtils.isNotEmpty(additionalRequiredClaims)) { 207 allRequiredClaims.addAll(additionalRequiredClaims); 208 } 209 210 processor = new DefaultJWTProcessor<>(); 211 processor.setJWSKeySelector(new JWSVerificationKeySelector<>(jwsAlgs, jwkSource)); 212 processor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<C>( 213 new JWTClaimsSet.Builder() 214 .issuer(issuer.getValue()) 215 .build(), 216 allRequiredClaims)); 217 } 218 219 220 /** 221 * Processes an optional software statement in the specified client 222 * metadata. 223 * 224 * @param clientMetadata The client metadata, must not be {@code null}. 225 * 226 * @return The processed client metadata, with the merged software 227 * statement. 228 * 229 * @throws InvalidSoftwareStatementException On a invalid or missing 230 * required software 231 * statement. 232 * @throws JOSEException On a internal JOSE 233 * signature verification 234 * exception. 235 */ 236 public OIDCClientMetadata process(final OIDCClientMetadata clientMetadata) 237 throws InvalidSoftwareStatementException, JOSEException { 238 239 return process(clientMetadata, null); 240 } 241 242 243 /** 244 * Processes an optional software statement in the specified client 245 * metadata. 246 * 247 * @param clientMetadata The client metadata, must not be {@code null}. 248 * @param context Optional security context to pass to the 249 * underlying JWK source, {@code null} if not 250 * specified. 251 * 252 * @return The processed client metadata, with the merged software 253 * statement. 254 * 255 * @throws InvalidSoftwareStatementException On a invalid or missing 256 * required software 257 * statement. 258 * @throws JOSEException On a internal JOSE 259 * signature verification 260 * exception. 261 */ 262 public OIDCClientMetadata process(final OIDCClientMetadata clientMetadata, C context) 263 throws InvalidSoftwareStatementException, JOSEException { 264 265 SignedJWT softwareStatement = clientMetadata.getSoftwareStatement(); 266 267 if (softwareStatement == null) { 268 269 if (required) { 270 throw new InvalidSoftwareStatementException("Missing required software statement"); 271 } 272 273 return clientMetadata; 274 } 275 276 JWTClaimsSet statementClaims; 277 try { 278 statementClaims = processor.process(softwareStatement, context); 279 } catch (BadJOSEException e) { 280 throw new InvalidSoftwareStatementException("Invalid software statement JWT: " + e.getMessage(), e); 281 } catch (RemoteKeySourceException e) { 282 throw new InvalidSoftwareStatementException("Software statement JWT validation failed: " + e.getMessage(), e); 283 } 284 285 JSONObject mergedMetadataJSONObject = new JSONObject(); 286 mergedMetadataJSONObject.putAll(clientMetadata.toJSONObject()); 287 mergedMetadataJSONObject.remove("software_statement"); 288 289 JSONObject statementJSONObject = JSONObjectUtils.toJSONObject(statementClaims); 290 statementJSONObject.remove("iss"); 291 mergedMetadataJSONObject.putAll(statementJSONObject); 292 293 try { 294 return OIDCClientMetadata.parse(mergedMetadataJSONObject); 295 } catch (ParseException e) { 296 throw new InvalidSoftwareStatementException("Error merging software statement: " + e.getMessage(), e); 297 } 298 } 299}