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}