/* * Copyright (c) 2016 Network New Technologies Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.networknt.security; import com.networknt.config.Config; import com.networknt.exception.ExpiredTokenException; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.MalformedClaimException; import org.jose4j.jwt.NumericDate; import org.jose4j.jwt.consumer.InvalidJwtException; import org.jose4j.jwt.consumer.JwtConsumer; import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.jwt.consumer.JwtContext; import org.jose4j.jwx.JsonWebStructure; import org.jose4j.keys.resolvers.X509VerificationKeyResolver; import org.jose4j.lang.JoseException; import org.owasp.encoder.Encode; import org.slf4j.ext.XLogger; import org.slf4j.ext.XLoggerFactory; import java.io.IOException; import java.io.InputStream; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; /** * Created by steve on 01/09/16. */ public class JwtHelper { static final XLogger logger = XLoggerFactory.getXLogger(JwtHelper.class); public static final String JWT_CONFIG = "jwt"; public static final String KEY = "key"; public static final String FILENAME = "filename"; public static final String PASSWORD = "password"; public static final String KEY_NAME = "keyName"; public static final String KID = "kid"; public static final String SECURITY_CONFIG = "security"; public static final String JWT_CERTIFICATE = "certificate"; public static final String JwT_CLOCK_SKEW_IN_SECONDS = "clockSkewInSeconds"; public static final String ENABLE_VERIFY_JWT = "enableVerifyJwt"; static Map<String, X509Certificate> certMap; static Map<String, Object> securityConfig = (Map)Config.getInstance().getJsonMapConfig(SECURITY_CONFIG); static Map<String, Object> securityJwtConfig = (Map)securityConfig.get(JWT_CONFIG); static JwtConfig jwtConfig = (JwtConfig) Config.getInstance().getJsonObjectConfig(JWT_CONFIG, JwtConfig.class); public static String getJwt(JwtClaims claims) throws JoseException { String jwt; RSAPrivateKey privateKey = (RSAPrivateKey) getPrivateKey( jwtConfig.getKey().getFilename(), jwtConfig.getKey().getPassword(), jwtConfig.getKey().getKeyName()); // A JWT is a JWS and/or a JWE with JSON claims as the payload. // In this example it is a JWS nested inside a JWE // So we first create a JsonWebSignature object. JsonWebSignature jws = new JsonWebSignature(); // The payload of the JWS is JSON content of the JWT Claims jws.setPayload(claims.toJson()); // The JWT is signed using the sender's private key jws.setKey(privateKey); jws.setKeyIdHeaderValue(jwtConfig.getKey().getKid()); // Set the signature algorithm on the JWT/JWS that will integrity protect the claims jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); // Sign the JWS and produce the compact serialization, which will be the inner JWT/JWS // representation, which is a string consisting of three dot ('.') separated // base64url-encoded parts in the form Header.Payload.Signature jwt = jws.getCompactSerialization(); return jwt; } public static JwtClaims getDefaultJwtClaims() { JwtConfig config = (JwtConfig) Config.getInstance().getJsonObjectConfig(JWT_CONFIG, JwtConfig.class); JwtClaims claims = new JwtClaims(); claims.setIssuer(config.getIssuer()); claims.setAudience(config.getAudience()); claims.setExpirationTimeMinutesInTheFuture(config.getExpiredInMinutes()); claims.setGeneratedJwtId(); // a unique identifier for the token claims.setIssuedAtToNow(); // when the token was issued/created (now) claims.setNotBeforeMinutesInThePast(2); // time before which the token is not yet valid (2 minutes ago) claims.setClaim("version", config.getVersion()); return claims; } private static PrivateKey getPrivateKey(String filename, String password, String key) { PrivateKey privateKey = null; try { KeyStore keystore = KeyStore.getInstance("JKS"); keystore.load(JwtHelper.class.getResourceAsStream(filename), password.toCharArray()); privateKey = (PrivateKey) keystore.getKey(key, password.toCharArray()); } catch (Exception e) { logger.error("Exception:", e); } if (privateKey == null) { logger.error("Failed to retrieve private key from keystore"); } return privateKey; } static public X509Certificate readCertificate(String filename) throws Exception { InputStream inStream = null; X509Certificate cert = null; try { inStream = Config.getInstance().getInputStreamFromFile(filename); if (inStream != null) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); cert = (X509Certificate) cf.generateCertificate(inStream); } else { logger.info("Certificate " + Encode.forJava(filename) + " not found."); } } catch (Exception e) { logger.error("Exception: ", e); } finally { if (inStream != null) { try { inStream.close(); } catch (IOException ioe) { logger.error("Exception: ", ioe); } } } return cert; } static { certMap = new HashMap<>(); Map<String, Object> keyMap = (Map<String, Object>) securityJwtConfig.get(JwtHelper.JWT_CERTIFICATE); for(String kid: keyMap.keySet()) { X509Certificate cert = null; try { cert = JwtHelper.readCertificate((String)keyMap.get(kid)); } catch (Exception e) { logger.error("Exception:", e); } certMap.put(kid, cert); } } public static String getJwtFromAuthorization(String authorization) { String jwt = null; if(authorization != null) { String[] parts = authorization.split(" "); if (parts.length == 2) { String scheme = parts[0]; String credentials = parts[1]; Pattern pattern = Pattern.compile("^Bearer$", Pattern.CASE_INSENSITIVE); if (pattern.matcher(scheme).matches()) { jwt = credentials; } } } return jwt; } public static JwtClaims verifyJwt(String jwt) throws InvalidJwtException, ExpiredTokenException { JwtClaims claims; JwtConsumer consumer = new JwtConsumerBuilder() .setSkipAllValidators() .setDisableRequireSignature() .setSkipSignatureVerification() .build(); JwtContext jwtContext = consumer.process(jwt); JwtClaims jwtClaims = jwtContext.getJwtClaims(); JsonWebStructure structure = jwtContext.getJoseObjects().get(0); String kid = structure.getKeyIdHeaderValue(); int secondsOfAllowedClockSkew = 30; try { if ((NumericDate.now().getValue() - secondsOfAllowedClockSkew) >= jwtClaims.getExpirationTime().getValue()) { logger.info("jwt token is expired!"); throw new ExpiredTokenException("Token is expired"); } } catch (MalformedClaimException e) { logger.error("MalformedClaimException:", e); throw new InvalidJwtException("MalformedClaimException", e); } X509VerificationKeyResolver x509VerificationKeyResolver = new X509VerificationKeyResolver(certMap.get(kid)); x509VerificationKeyResolver.setTryAllOnNoThumbHeader(true); consumer = new JwtConsumerBuilder() .setRequireExpirationTime() .setAllowedClockSkewInSeconds( (Integer) securityJwtConfig.get(JwT_CLOCK_SKEW_IN_SECONDS)) .setSkipDefaultAudienceValidation() .setVerificationKeyResolver(x509VerificationKeyResolver) .build(); // Validate the JWT and process it to the Claims jwtContext = consumer.process(jwt); claims = jwtContext.getJwtClaims(); return claims; } }