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}