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.federation.entities;
019
020
021import net.jcip.annotations.Immutable;
022
023import com.nimbusds.common.contenttype.ContentType;
024import com.nimbusds.jose.JOSEException;
025import com.nimbusds.jose.JOSEObjectType;
026import com.nimbusds.jose.JWSAlgorithm;
027import com.nimbusds.jose.JWSObject;
028import com.nimbusds.jose.jwk.JWK;
029import com.nimbusds.jose.jwk.JWKSet;
030import com.nimbusds.jose.proc.BadJOSEException;
031import com.nimbusds.jose.util.Base64URL;
032import com.nimbusds.jwt.SignedJWT;
033import com.nimbusds.oauth2.sdk.ParseException;
034import com.nimbusds.openid.connect.sdk.federation.utils.JWTUtils;
035
036
037/**
038 * Federation entity statement / entity configuration.
039 *
040 * <p>Related specifications:
041 *
042 * <ul>
043 *     <li>OpenID Connect Federation 1.0, section 3.1.
044 * </ul>
045 */
046@Immutable
047public final class EntityStatement {
048        
049        
050        /**
051         * The federation entity statement JOSE object type
052         * ({@code entity-statement+jwt}).
053         */
054        public static final JOSEObjectType JOSE_OBJECT_TYPE = new JOSEObjectType("entity-statement+jwt");
055        
056        
057        /**
058         * The federation entity statement content type
059         * ({@code application/entity-statement+jwt}).
060         */
061        public static final ContentType CONTENT_TYPE = new ContentType("application", JOSE_OBJECT_TYPE.getType());
062        
063        
064        /**
065         * The signed statement as signed JWT.
066         */
067        private final SignedJWT statementJWT;
068        
069        
070        /**
071         * The statement claims.
072         */
073        private final EntityStatementClaimsSet claimsSet;
074        
075        
076        /**
077         * Creates a new federation entity statement.
078         *
079         * @param statementJWT The signed statement as signed JWT. Must not be
080         *                     {@code null}.
081         * @param claimsSet    The statement claims. Must not be {@code null}.
082         */
083        private EntityStatement(final SignedJWT statementJWT,
084                                final EntityStatementClaimsSet claimsSet) {
085                
086                if (statementJWT == null) {
087                        throw new IllegalArgumentException("The entity statement must not be null");
088                }
089                if (JWSObject.State.UNSIGNED.equals(statementJWT.getState())) {
090                        throw new IllegalArgumentException("The statement is not signed");
091                }
092                this.statementJWT = statementJWT;
093                
094                if (claimsSet == null) {
095                        throw new IllegalArgumentException("The entity statement claims set must not be null");
096                }
097                this.claimsSet = claimsSet;
098        }
099        
100        
101        /**
102         * Returns the entity ID.
103         *
104         * @return The entity ID.
105         */
106        public EntityID getEntityID() {
107                return getClaimsSet().getSubjectEntityID();
108        }
109        
110        
111        /**
112         * Returns the signed statement.
113         *
114         * @return The signed statement as signed JWT.
115         */
116        public SignedJWT getSignedStatement() {
117                return statementJWT;
118        }
119        
120        
121        /**
122         * Returns the statement claims.
123         *
124         * @return The statement claims.
125         */
126        public EntityStatementClaimsSet getClaimsSet() {
127                return claimsSet;
128        }
129        
130        
131        /**
132         * Verifies the signature for a self-statement (typically for a trust
133         * anchor or leaf) and checks the statement issue and expiration times.
134         *
135         * @return The SHA-256 thumbprint of the key used to successfully
136         *         verify the signature.
137         *
138         * @throws BadJOSEException If the signature is invalid or the
139         *                          statement is expired or before the issue
140         *                          time.
141         * @throws JOSEException    On a internal JOSE exception.
142         */
143        public Base64URL verifySignatureOfSelfStatement() throws BadJOSEException, JOSEException {
144                
145                if (! getClaimsSet().isSelfStatement()) {
146                        throw new BadJOSEException("Entity statement not self-issued");
147                }
148                
149                return verifySignature(getClaimsSet().getJWKSet());
150        }
151        
152        
153        /**
154         * Verifies the signature and checks the statement type, issue and
155         * expiration times.
156         *
157         * @param jwkSet The JWK set to use for the signature verification.
158         *               Must not be {@code null}.
159         *
160         * @return The SHA-256 thumbprint of the key used to successfully
161         *         verify the signature.
162         *
163         * @throws BadJOSEException If the signature is invalid or the
164         *                          statement is expired or before the issue
165         *                          time.
166         * @throws JOSEException    On an internal JOSE exception.
167         */
168        public Base64URL verifySignature(final JWKSet jwkSet)
169                throws BadJOSEException, JOSEException {
170                
171                return JWTUtils.verifySignature(
172                        statementJWT,
173                        JOSE_OBJECT_TYPE,
174                        new EntityStatementClaimsVerifier(null),
175                        jwkSet);
176        }
177        
178        
179        /**
180         * Signs the specified federation entity claims set.
181         *
182         * @param claimsSet  The claims set. Must not be {@code null}.
183         * @param signingJWK The private signing JWK. Must be contained in the
184         *                   entity JWK set and not {@code null}.
185         *
186         * @return The signed federation entity statement.
187         *
188         * @throws JOSEException On a internal signing exception.
189         */
190        public static EntityStatement sign(final EntityStatementClaimsSet claimsSet,
191                                           final JWK signingJWK)
192                throws JOSEException {
193                
194                return sign(claimsSet, signingJWK, JWTUtils.resolveSigningAlgorithm(signingJWK));
195        }
196        
197        
198        /**
199         * Signs the specified federation entity claims set.
200         *
201         * @param claimsSet  The claims set. Must not be {@code null}.
202         * @param signingJWK The private signing JWK. Must be contained in the
203         *                   entity JWK set and not {@code null}.
204         * @param jwsAlg     The signing algorithm. Must be supported by the
205         *                   JWK and not {@code null}.
206         *
207         * @return The signed federation entity statement.
208         *
209         * @throws JOSEException On a internal signing exception.
210         */
211        public static EntityStatement sign(final EntityStatementClaimsSet claimsSet,
212                                           final JWK signingJWK,
213                                           final JWSAlgorithm jwsAlg)
214                throws JOSEException {
215                
216                if (claimsSet.isSelfStatement() && ! claimsSet.getJWKSet().containsJWK(signingJWK)) {
217                        throw new JOSEException("Signing JWK not found in JWK set of self-statement");
218                }
219                
220                try {
221                        return new EntityStatement(
222                                JWTUtils.sign(
223                                        signingJWK,
224                                        jwsAlg,
225                                        JOSE_OBJECT_TYPE,
226                                        claimsSet.toJWTClaimsSet()),
227                                claimsSet);
228                } catch (ParseException e) {
229                        throw new JOSEException(e.getMessage(), e);
230                }
231        }
232        
233        
234        /**
235         * Parses a federation entity statement.
236         *
237         * @param signedStmt The signed statement as a signed JWT. Must not be
238         *                   {@code null}.
239         *
240         * @return The federation entity statement.
241         *
242         * @throws ParseException If parsing failed.
243         */
244        public static EntityStatement parse(final SignedJWT signedStmt)
245                throws ParseException {
246                
247                return new EntityStatement(signedStmt, new EntityStatementClaimsSet(JWTUtils.parseSignedJWTClaimsSet(signedStmt)));
248        }
249        
250        
251        /**
252         * Parses a federation entity statement.
253         *
254         * @param signedStmtString The signed statement as a signed JWT string.
255         *                         Must not be {@code null}.
256         *
257         * @return The federation entity statement.
258         *
259         * @throws ParseException If parsing failed.
260         */
261        public static EntityStatement parse(final String signedStmtString)
262                throws ParseException {
263                
264                try {
265                        return parse(SignedJWT.parse(signedStmtString));
266                } catch (java.text.ParseException e) {
267                        throw new ParseException("Invalid entity statement: " + e.getMessage(), e);
268                }
269        }
270}