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.openid.connect.sdk.rp.OIDCClientMetadata; 048 049 050/** 051 * Processor of software statements for client registrations. 052 * 053 * <p>Related specifications: 054 * 055 * <ul> 056 * <li>OAuth 2.0 Dynamic Client Registration Protocol (RFC 7591), sections 057 * 2.3 and 3.1.1. 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 for (String claimName: additionalRequiredClaims) { 207 allRequiredClaims.add(claimName); 208 } 209 } 210 211 processor = new DefaultJWTProcessor<>(); 212 processor.setJWSKeySelector(new JWSVerificationKeySelector<>(jwsAlgs, jwkSource)); 213 processor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<C>( 214 new JWTClaimsSet.Builder() 215 .issuer(issuer.getValue()) 216 .build(), 217 allRequiredClaims)); 218 } 219 220 221 /** 222 * Processes an optional software statement in the specified client 223 * metadata. 224 * 225 * @param clientMetadata The client metadata, must not be {@code null}. 226 * 227 * @return The processed client metadata, with the merged software 228 * statement. 229 * 230 * @throws InvalidSoftwareStatementException On a invalid or missing 231 * required software 232 * statement. 233 * @throws JOSEException On a internal JOSE 234 * signature verification 235 * exception. 236 */ 237 public OIDCClientMetadata process(final OIDCClientMetadata clientMetadata) 238 throws InvalidSoftwareStatementException, JOSEException { 239 240 return process(clientMetadata, null); 241 } 242 243 244 /** 245 * Processes an optional software statement in the specified client 246 * metadata. 247 * 248 * @param clientMetadata The client metadata, must not be {@code null}. 249 * @param context Optional security context to pass to the 250 * underlying JWK source, {@code null} if not 251 * specified. 252 * 253 * @return The processed client metadata, with the merged software 254 * statement. 255 * 256 * @throws InvalidSoftwareStatementException On a invalid or missing 257 * required software 258 * statement. 259 * @throws JOSEException On a internal JOSE 260 * signature verification 261 * exception. 262 */ 263 public OIDCClientMetadata process(final OIDCClientMetadata clientMetadata, C context) 264 throws InvalidSoftwareStatementException, JOSEException { 265 266 SignedJWT softwareStatement = clientMetadata.getSoftwareStatement(); 267 268 if (softwareStatement == null) { 269 270 if (required) { 271 throw new InvalidSoftwareStatementException("Missing required software statement"); 272 } 273 274 return clientMetadata; 275 } 276 277 JWTClaimsSet statementClaims; 278 try { 279 statementClaims = processor.process(softwareStatement, context); 280 } catch (BadJOSEException e) { 281 throw new InvalidSoftwareStatementException("Invalid software statement JWT: " + e.getMessage(), e); 282 } catch (RemoteKeySourceException e) { 283 throw new InvalidSoftwareStatementException("Software statement JWT validation failed: " + e.getMessage(), e); 284 } 285 286 JSONObject mergedMetadataJSONObject = new JSONObject(); 287 mergedMetadataJSONObject.putAll(clientMetadata.toJSONObject()); 288 mergedMetadataJSONObject.remove("software_statement"); 289 290 JSONObject statementJSONObject = statementClaims.toJSONObject(); 291 statementJSONObject.remove("iss"); 292 mergedMetadataJSONObject.putAll(statementJSONObject); 293 294 try { 295 return OIDCClientMetadata.parse(mergedMetadataJSONObject); 296 } catch (ParseException e) { 297 throw new InvalidSoftwareStatementException("Error merging software statement: " + e.getMessage(), e); 298 } 299 } 300}