· 8 years ago · Jan 16, 2018, 04:44 PM
1package lu.excellium.trainingkit.util;
2
3import com.auth0.jwt.JWT;
4import com.auth0.jwt.JWTVerifier;
5import com.auth0.jwt.algorithms.Algorithm;
6import com.auth0.jwt.exceptions.JWTVerificationException;
7import com.auth0.jwt.interfaces.DecodedJWT;
8import net.sf.ehcache.Cache;
9import net.sf.ehcache.CacheManager;
10import net.sf.ehcache.Element;
11import org.owasp.encoder.Encode;
12import org.slf4j.Logger;
13import org.slf4j.LoggerFactory;
14
15import javax.crypto.SecretKey;
16import javax.crypto.SecretKeyFactory;
17import javax.crypto.spec.PBEKeySpec;
18import javax.servlet.http.HttpServletResponseWrapper;
19import javax.xml.bind.DatatypeConverter;
20import java.security.MessageDigest;
21import java.security.NoSuchAlgorithmException;
22import java.security.SecureRandom;
23import java.security.spec.InvalidKeySpecException;
24import java.util.Calendar;
25import java.util.regex.Matcher;
26import java.util.regex.Pattern;
27
28/**
29 * Class in charge of all operations related to the security of the application.
30 * Must be implemented by the student in order to handle the different identified abuse cases.
31 */
32public abstract class SecurityUtils {
33
34 /**
35 * Logger for the security event tracing
36 */
37 private static final Logger TRACE = LoggerFactory.getLogger(SecurityUtils.class);
38
39 private static final SecureRandom RND = new SecureRandom();
40
41 /**
42 * Cache used to manage IP brute force protection
43 */
44 public static final Cache BANNED_IP;
45
46 /**
47 * Cache used to manage access token flagged as no more usable due to logout
48 */
49 public static final Cache BANNED_ACCESS_TOKEN;
50
51 static {
52 CacheManager singletonManager = CacheManager.create();
53 Cache memoryOnlyCacheForIP = new Cache("IPCache", 1000, true, false, 600, 600);
54 singletonManager.addCache(memoryOnlyCacheForIP);
55 BANNED_IP = singletonManager.getCache("IPCache");
56
57 Cache memoryOnlyCacheForAT = new Cache("ATCache", 1000, true, false, 7200, 7200);
58 singletonManager.addCache(memoryOnlyCacheForAT);
59 BANNED_ACCESS_TOKEN = singletonManager.getCache("ATCache");
60 }
61
62 /****************[SECTION FOR ABUSE CASE 1 HANDLING]**************/
63
64 /**
65 * Apply input validation on the user login
66 *
67 * @param login User login
68 * @throws SecurityException Exception raised if the validation failed
69 */
70 public static void inputValidationForUserCredential(String login) throws SecurityException {
71 //REGEX: [a-zA-Z]{1,20}
72 //See https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html
73 //TODO: Implement the method
74 Pattern pattern = Pattern.compile("[a-zA-Z]{1,20}");
75 Matcher matcher = pattern.matcher(login);
76 if(!matcher.matches()) {
77 throw new SecurityException();
78 }
79 }
80
81 /**
82 * Apply input validation on a tweet body
83 *
84 * @param tweetBody Content of the tweet
85 * @throws SecurityException Exception raised if the validation failed
86 */
87 public static void inputValidationForTweetBody(String tweetBody) throws SecurityException {
88 //REGEX: [a-zA-Z0-9\s]{1,140}
89 //See https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html
90 //TODO: Implement the method
91 Pattern pattern = Pattern.compile("[a-zA-Z0-9\\s]{1,140}");
92 Matcher matcher = pattern.matcher(tweetBody);
93 if(!matcher.matches()) {
94 throw new SecurityException();
95 }
96 }
97
98 /**
99 * Apply input validation on a tweet ID
100 *
101 * @param tweetId Identifier of the tweet
102 * @throws SecurityException Exception raised if the validation failed
103 */
104 public static void inputValidationForTweetId(String tweetId) throws SecurityException {
105 //REGEX: [0-9a-z]{32}
106 //See https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html
107 //TODO: Implement the method
108 Pattern pattern = Pattern.compile("[0-9a-z]{32}");
109 Matcher matcher = pattern.matcher(tweetId);
110 if(!matcher.matches()) {
111 throw new SecurityException();
112 }
113 }
114
115
116 /****************[SECTION FOR ABUSE CASE 2 HANDLING]**************/
117
118 /**
119 * Generate the "hashed" version of the password (in fact is a derivated key used as a hash)
120 *
121 * @param pwd User password provided
122 * @param pwdSalt Salt associated with the password
123 * @return The password hashed using the PBKDF2 algorithm and encoded in HEX
124 * @throws SecurityException Exception raised if the generation failed
125 */
126 public static String hashPassword(String pwd, byte[] pwdSalt) throws SecurityException {
127 //TIP:
128 // Use PBKDF2 algorithm with:
129 //- Algorithm “PBKDF2WithHmacSHA512â€
130 //- 1000000 iterations
131 //- Provided pepper in the method (32 bytes)
132 //- Unique random salt by password provided (32 bytes)
133 //- 256 bits output key size
134 //- The computing must take at least 2 seconds to process
135 //- Salt[64] used for password hashing = Pepper[32] + PwdSalt[32]
136 // - The computing must take at least 2 seconds to process
137 //See https://www.owasp.org/index.php/Hashing_Java for code
138 //TODO: Implement the method
139 byte[] pepper = System.getProperty("pbkdf2.pepper").getBytes();
140 byte[] salt = new byte[64];
141 System.arraycopy(pepper, 0, salt, 0, 32);
142 System.arraycopy(pwdSalt, 0, salt, 32, 32);
143 String hashToReturn;
144 try {
145 SecretKeyFactory skf = SecretKeyFactory.getInstance( "PBKDF2WithHmacSHA512" );
146 PBEKeySpec spec = new PBEKeySpec(pwd.toCharArray(), salt, 1000000, 256);
147 SecretKey key = skf.generateSecret( spec );
148 byte[] res = key.getEncoded();
149 // Hex encoding
150 hashToReturn = DatatypeConverter.printHexBinary(res);
151 } catch( NoSuchAlgorithmException | InvalidKeySpecException e ) {
152 throw new SecurityException();
153 }
154 return hashToReturn;
155 }
156
157 /****************[SECTION FOR ABUSE CASE 3 HANDLING]**************/
158
159 /**
160 * Generate a JWT token that will be used as access token for the user
161 *
162 * @param login User login
163 * @param role Access role
164 * @param userAgent User agent used by the user to access to the application
165 * @param userIPAddress User IP address
166 * @return the access token as JWT token
167 * @throws SecurityException Exception raised if the generation failed
168 */
169 public static String generateAccessToken(String login, String role, String userAgent, String userIPAddress) throws SecurityException {
170 String accessToken;
171
172 //Generate the fingerprint using the user agent, a salt and the user ip address
173 String value = userAgent + System.getProperty("salt") + userIPAddress;
174 String fingerprint = "";
175 try {
176 fingerprint = DatatypeConverter.printHexBinary(MessageDigest.getInstance("sha-256").digest(value.getBytes()));
177 } catch (NoSuchAlgorithmException nse) {
178 throw new SecurityException(nse);
179 }
180
181 //TODO: Add the fingerprint claim to the JWT token
182 //See https://jwt.io/#debugger-io to see the content of a token
183
184 //Build the access token
185 Algorithm algorithm = Algorithm.HMAC256(System.getProperty("jwt.secret").getBytes());
186 Calendar c = Calendar.getInstance();
187 c.add(Calendar.MINUTE, 60);
188 accessToken = JWT.create().withIssuer("CODE2").withSubject(login).withExpiresAt(c.getTime()).withClaim("role", role.trim()).withClaim("fgp", fingerprint).sign(algorithm);
189
190 return accessToken;
191 }
192
193 /****************[SECTION FOR ABUSE CASE 4 HANDLING]**************/
194
195 /**
196 * Validate a received JWT token, the user context and check that the user role is equals to one expected
197 *
198 * @param accessToken Access token to validate
199 * @param expectedRole Expected user role that the token must contains
200 * @return The user login if the token is valid
201 * @throws SecurityException Exception raised if the verification failed (invalid token / role not equals / user context fingerprint do not match)
202 */
203 public static String validateAccessToken(String accessToken, String expectedRole, String userAgent, String userIPAddress) throws SecurityException {
204
205 //Generate the fingerprint using the user agent, a salt and the user ip address
206 String value = userAgent + System.getProperty("salt") + userIPAddress;
207 String fingerprint = "";
208 try {
209 fingerprint = DatatypeConverter.printHexBinary(MessageDigest.getInstance("sha-256").digest(value.getBytes()));
210 } catch (NoSuchAlgorithmException nse) {
211 throw new SecurityException(nse);
212 }
213
214 //TODO: Add the role and the fingerprint to the claim to verify in the JWT token verifier
215 //See https://jwt.io/#debugger-io to see the content of a token
216
217 //Validate the token
218 Algorithm algorithm = Algorithm.HMAC256(System.getProperty("jwt.secret").getBytes());
219 JWTVerifier verifier = JWT.require(algorithm).withIssuer("CODE2").withClaim("role", expectedRole.trim()).withClaim("fgp", fingerprint).build();
220 DecodedJWT token;
221 try {
222 token = verifier.verify(accessToken);
223 } catch (JWTVerificationException ve) {
224 throw new SecurityException(ve);
225 }
226
227 return token.getSubject();
228 }
229
230 /****************[SECTION FOR ABUSE CASE 5 HANDLING]**************/
231
232 /**
233 * Increment the login failed attempt counter for the user and flag the source IP has must be blocked or not.
234 *
235 * @param userIPAddress User source IP address
236 * @param login User login
237 * @throws SecurityException Exception raised if the verification failed
238 */
239 public static void checkAntiBruteForceAccess(String userIPAddress, String login) throws SecurityException {
240 String keyInCacheToAddToEnableIPBanning = (userIPAddress + "_BANNED").toUpperCase();
241 String keyInCacheToStoreTheCounter = (login + "_" + userIPAddress).toUpperCase();
242 Element ipBanFlagToAddInCacheToEnableIPBanning = new Element(keyInCacheToAddToEnableIPBanning, keyInCacheToAddToEnableIPBanning);
243 //TODO: implements the method using the BANNED_IP cache reference item to store the counter and enable or not the IP ban fla
244 ipBanFlagToAddInCacheToEnableIPBanning.setTimeToLive(ipBanFlagToAddInCacheToEnableIPBanning.getTimeToLive() - 1);
245 if(ipBanFlagToAddInCacheToEnableIPBanning.isExpired()) {
246 throw new SecurityException();
247 }
248 }
249
250
251 /****************[SECTION FOR ABUSE CASE 6 HANDLING]**************/
252
253 /**
254 * Send a trace to logging system about a security event (do not forget to escape dangerous content)
255 *
256 * @param information Informations about the event
257 * @throws SecurityException Exception raised if the tracing failed
258 */
259 public static void logSecurityEvent(String... information) throws SecurityException {
260 //TODO: Implement the method using logback logger TRACE instance to send trace
261 for(String info : information) {
262 LoggerFactory.getLogger("Logback").trace(escapeContent(info));
263 }
264 // System.out.println(information[0]);
265 }
266
267 /**
268 * Apply output escaping on a value
269 *
270 * @param unsafeInput Unsafe input value to escape
271 * @return The input value escaped
272 * @throws SecurityException Exception raised if the escaping failed
273 */
274 public static String escapeContent(String unsafeInput) throws SecurityException {
275 //TIP: Use OWASP Encoder API here (already available in classpath)...
276 //See https://www.owasp.org/index.php/OWASP_Java_Encoder_Project#tab=Use_the_Java_Encoder_Project
277 //TODO: Implement the method
278 String res = Encode.forHtmlContent(unsafeInput);
279 res = Encode.forJavaScriptBlock(res);
280 return res;
281 }
282
283 /****************[SECTION FOR ABUSE CASE 7 HANDLING]**************/
284 /**
285 * Define the caching policy of all HTTP responses in order to prevent that any devices store information in cache using the headers “Cache-Control†+ “Pragma†+ “Expiresâ€
286 * @param httpResponse Ref on HTTP response object
287 * @throws SecurityException Exception raised if the caching policy cannot be set
288 */
289 public static void defineCachingPolicy(HttpServletResponseWrapper httpResponse) throws SecurityException {
290 //See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
291 //See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Pragma
292 //See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires
293 //TODO: Add the headers with the correct value
294 httpResponse.addHeader("Cache-Control", "no-store");
295 httpResponse.addHeader("Pragma", "no-cache");
296 httpResponse.addHeader("Expires", "0");
297 }
298
299
300 /****************[SECTION FOR ABUSE CASE 8 HANDLING]**************/
301 /**
302 * Define the browser behavior policy using a Content-Security-Policy HTTP response header that:
303 * - Allow only the loading of information from the current domain or https//code.jquery.com or https://maxcdn.bootstrapcdn.com
304 * - Allow inline script for JavaScript
305 * - Allow inline style for CSS
306 * @param httpResponse Ref on HTTP response object
307 * @throws SecurityException Exception raised if the behavior policy cannot be set
308 */
309 public static void defineBrowserBehaviorPolicy(HttpServletResponseWrapper httpResponse) throws SecurityException {
310 //See https://content-security-policy.com/
311 //TODO: Add the header with the correct policy
312 httpResponse.addHeader("Content-Security-Policy", "script-src 'self' https//code.jquery.com https://maxcdn.bootstrapcdn.com 'unsafe-inline'; style-src 'self' 'unsafe-inline'");
313 }
314
315}