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}