1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package fr.paris.lutece.plugins.franceconnect.oidc.jwt;
35
36 import com.nimbusds.jose.Algorithm;
37 import com.nimbusds.jose.JWSAlgorithm;
38
39 import com.nimbusds.jwt.JWT;
40 import com.nimbusds.jwt.PlainJWT;
41 import com.nimbusds.jwt.ReadOnlyJWTClaimsSet;
42 import com.nimbusds.jwt.SignedJWT;
43
44 import fr.paris.lutece.plugins.franceconnect.oidc.AuthClientConf;
45 import fr.paris.lutece.plugins.franceconnect.oidc.AuthServerConf;
46 import fr.paris.lutece.plugins.franceconnect.oidc.IDToken;
47 import fr.paris.lutece.plugins.franceconnect.oidc.Token;
48 import fr.paris.lutece.plugins.franceconnect.web.Constants;
49
50 import org.apache.log4j.Logger;
51
52 import java.text.ParseException;
53
54 import java.util.Date;
55
56
57
58
59
60
61
62
63 public class MitreJWTParser implements JWTParser
64 {
65
66 private int _nTimeSkewAllowance = 300;
67
68
69
70
71 @Override
72 public void parseJWT( Token token, AuthClientConf clientConfig, AuthServerConf serverConfig, String strStoredNonce,
73 Logger logger ) throws TokenValidationException
74 {
75 JWT jwt;
76 IDToken idToken = new IDToken( );
77
78 try
79 {
80 jwt = com.nimbusds.jwt.JWTParser.parse( token.getIdTokenString( ) );
81 }
82 catch ( ParseException ex )
83 {
84 throw new TokenValidationException( "Unable to parse JWT : " + ex.getMessage( ), ex );
85 }
86
87
88 ReadOnlyJWTClaimsSet idClaims;
89
90 try
91 {
92 idClaims = jwt.getJWTClaimsSet( );
93 }
94 catch ( ParseException ex )
95 {
96 throw new TokenValidationException( "Unable to get Claims set from JWT : " + ex.getMessage( ), ex );
97 }
98
99 Algorithm tokenAlg = jwt.getHeader( ).getAlgorithm( );
100
101 Algorithm clientAlg = clientConfig.getIdTokenSignedResponseAlg( );
102
103 if ( clientAlg != null )
104 {
105 if ( !clientAlg.equals( tokenAlg ) )
106 {
107 throw new TokenValidationException( "Token algorithm " + tokenAlg +
108 " does not match expected algorithm " + clientAlg );
109 }
110 }
111
112 if ( jwt instanceof PlainJWT )
113 {
114 logger.debug( "ID token is a Plain JWT" );
115
116 if ( clientAlg == null )
117 {
118 throw new TokenValidationException(
119 "Unsigned ID tokens can only be used if explicitly configured in client." );
120 }
121
122 if ( ( tokenAlg != null ) && !tokenAlg.equals( JWSAlgorithm.NONE ) )
123 {
124 throw new TokenValidationException( "Unsigned token received, expected signature with " + tokenAlg );
125 }
126 }
127 else if ( jwt instanceof SignedJWT )
128 {
129 logger.debug( "ID token is a signed JWT" );
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164 }
165
166
167 if ( idClaims.getIssuer( ) == null )
168 {
169 throw new TokenValidationException( "Id Token Issuer is null" );
170 }
171 else if ( !idClaims.getIssuer( ).equals( serverConfig.getIssuer( ) ) )
172 {
173 throw new TokenValidationException( "Issuers do not match, expected " + serverConfig.getIssuer( ) +
174 " got " + idClaims.getIssuer( ) );
175 }
176
177
178 if ( idClaims.getExpirationTime( ) == null )
179 {
180 throw new TokenValidationException( "Id Token does not have required expiration claim" );
181 }
182 else
183 {
184
185 Date now = new Date( System.currentTimeMillis( ) - ( _nTimeSkewAllowance * 1000 ) );
186
187 if ( now.after( idClaims.getExpirationTime( ) ) )
188 {
189 throw new TokenValidationException( "Id Token is expired: " + idClaims.getExpirationTime( ) );
190 }
191 }
192
193
194 if ( idClaims.getNotBeforeTime( ) != null )
195 {
196 Date now = new Date( System.currentTimeMillis( ) + ( _nTimeSkewAllowance * 1000 ) );
197
198 if ( now.before( idClaims.getNotBeforeTime( ) ) )
199 {
200 throw new TokenValidationException( "Id Token not valid untill: " + idClaims.getNotBeforeTime( ) );
201 }
202 }
203
204
205 if ( idClaims.getIssueTime( ) == null )
206 {
207 throw new TokenValidationException( "Id Token does not have required issued-at claim" );
208 }
209 else
210 {
211
212 Date now = new Date( System.currentTimeMillis( ) + ( _nTimeSkewAllowance * 1000 ) );
213
214 if ( now.before( idClaims.getIssueTime( ) ) )
215 {
216 throw new TokenValidationException( "Id Token was issued in the future: " + idClaims.getIssueTime( ) );
217 }
218 }
219
220
221 if ( idClaims.getAudience( ) == null )
222 {
223 throw new TokenValidationException( "Id token audience is null" );
224 }
225 else if ( !idClaims.getAudience( ).contains( clientConfig.getClientId( ) ) )
226 {
227 throw new TokenValidationException( "Audience does not match, expected " + clientConfig.getClientId( ) +
228 " got " + idClaims.getAudience( ) );
229 }
230
231
232 String strNonce = null;
233
234 try
235 {
236 strNonce = idClaims.getStringClaim( "nonce" );
237 }
238 catch ( ParseException ex )
239 {
240 throw new TokenValidationException( "ID token did not contain a nonce claim." );
241 }
242
243 if ( ( strNonce == null ) || strNonce.equals( "" ) )
244 {
245 logger.error( "ID token did not contain a nonce claim." );
246
247 throw new TokenValidationException( "ID token did not contain a nonce claim." );
248 }
249
250 if ( !strNonce.equals( strStoredNonce ) )
251 {
252 logger.error( "Possible replay attack detected! The comparison of the nonce in the returned " +
253 "ID Token to the session " + Constants.NONCE_SESSION_VARIABLE + " failed. Expected " + strStoredNonce +
254 " got " + strNonce + "." );
255
256 throw new TokenValidationException(
257 "Possible replay attack detected! The comparison of the nonce in the returned " +
258 "ID Token to the session " + Constants.NONCE_SESSION_VARIABLE + " failed. Expected " + strStoredNonce +
259 " got " + strNonce + "." );
260 }
261
262 logger.debug( "Nonce has been validated" );
263
264
265 String strIdp;
266
267 try
268 {
269 strIdp = idClaims.getStringClaim( "idp" );
270 }
271 catch ( ParseException ex )
272 {
273 throw new TokenValidationException( "ID token did not contain an idp claim.", ex );
274 }
275
276
277 String strAcr;
278
279 try
280 {
281 strAcr = idClaims.getStringClaim( "acr" );
282 }
283 catch ( ParseException ex )
284 {
285 throw new TokenValidationException( "ID token did not contain an acr claim.", ex );
286 }
287
288 idToken.setNonce( strNonce );
289 idToken.setSubject( idClaims.getSubject( ) );
290 idToken.setIdProvider( strIdp );
291 idToken.setExpiration( String.valueOf( idClaims.getExpirationTime( ).getTime( ) / 1000L ) );
292 idToken.setIssueAt( String.valueOf( idClaims.getIssueTime( ).getTime( ) / 1000L ) );
293 idToken.setIssuer( idClaims.getIssuer( ) );
294 idToken.setAudience( idClaims.getAudience( ).get( 0 ) );
295 idToken.setAcr( strAcr );
296 logger.debug( "ID Token retrieved : " + idToken );
297
298 token.setIdToken( idToken );
299 }
300 }