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}