· 7 years ago · Jan 25, 2019, 11:14 AM
1package com.neklo.ustaxi.common.jwt.service.impl;
2
3import com.neklo.ustaxi.common.exception.AuthenticationException;
4import com.neklo.ustaxi.common.jwt.model.TokenPayload;
5import com.neklo.ustaxi.common.jwt.model.TokenProtocolError;
6import com.neklo.ustaxi.common.jwt.service.EncryptionService;
7import com.nimbusds.jose.*;
8import com.nimbusds.jose.crypto.DirectDecrypter;
9import com.nimbusds.jose.crypto.DirectEncrypter;
10import com.nimbusds.jose.crypto.MACSigner;
11import com.nimbusds.jose.crypto.MACVerifier;
12import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton;
13import com.nimbusds.jose.util.Base64URL;
14import com.nimbusds.jwt.JWTClaimsSet;
15import com.nimbusds.jwt.SignedJWT;
16import org.apache.commons.codec.binary.Base64;
17import org.apache.commons.lang3.StringUtils;
18import org.bouncycastle.jce.provider.BouncyCastleProvider;
19import org.slf4j.Logger;
20import org.slf4j.LoggerFactory;
21
22import javax.crypto.SecretKey;
23import java.nio.charset.Charset;
24import java.security.Security;
25import java.text.ParseException;
26import java.util.Map;
27import java.util.regex.Pattern;
28
29/**
30 * @author timur
31 */
32public class EncryptionServiceImpl implements EncryptionService {
33 private static final String SIGNATURE_TEMPLATE = "%s.%s.%s";
34 private static final String EMPTY_REQ_BODY = "EMPTY_REQ_BODY";
35
36 private static final Logger log = LoggerFactory.getLogger(EncryptionServiceImpl.class);
37 private static final String PREFIX = "jwt_";
38 private static final String SUFFIX = "_token";
39 public static final String TOKEN_TEMPLATE = PREFIX + "%s" + SUFFIX + ".%s";
40 private final SecretKey secretKey;
41 private final Pattern JWT_PATTERN = Pattern.compile("^jwt_[^\\s\\:]*_token.[^\\s\\:]*$");
42 private MACSigner signer;
43 private BouncyCastleProvider bc;
44
45 public EncryptionServiceImpl(final SecretKey secretKey) {
46 this.secretKey = secretKey;
47 init();
48 }
49
50 private void init() {
51 bc = BouncyCastleProviderSingleton.getInstance();
52 Security.addProvider(bc);
53 try {
54 signer = new MACSigner(secretKey.getEncoded());
55 signer.getJCAContext().setProvider(BouncyCastleProviderSingleton.getInstance());
56 } catch (final KeyLengthException e) {
57 final String errMsg = "Error while initializing EncryptionService";
58 log.error(errMsg, e);
59 throw new RuntimeException(errMsg, e);
60 }
61 }
62
63 @Override
64 public String createSignedToken(final TokenPayload payload) {
65 try {
66 // Prepare JWT with claims set
67 final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().expirationTime(payload.getExpirationDate());
68 for (final Map.Entry<String, Object> entry : payload.getPayload().entrySet()) {
69 builder.claim(entry.getKey(), entry.getValue());
70 }
71
72 return createJweSignedToken(builder, payload);
73 } catch (final Exception e) {
74 final String errMsg = "Can't create token";
75 log.error(errMsg, e);
76 throw new RuntimeException(errMsg, e);
77 }
78 }
79
80 @Override
81 public String createJwtToken(final TokenPayload payload) {
82 try {
83 final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().expirationTime(payload.getExpirationDate());
84 for (final Map.Entry<String, Object> entry : payload.getPayload().entrySet()) {
85 builder.claim(entry.getKey(), entry.getValue());
86 }
87 return createJwtToken(builder);
88 } catch (final Exception ex) {
89 final String errMsg = "Can't create JWT token";
90 log.error(errMsg, ex);
91 throw new RuntimeException(errMsg, ex);
92 }
93 }
94
95 private String createJweSignedToken(final JWTClaimsSet.Builder builder, final TokenPayload payload) throws Exception {
96 final JWSHeader header = new JWSHeader(JWSAlgorithm.HS256);
97 final SignedJWT signedJWT = new SignedJWT(header, builder.build());
98
99 final String jweString = encryptToken(signedJWT);
100
101 final String expDateTimeMillis = Base64.encodeBase64String(String.valueOf(payload.getExpirationDate().getTime()).getBytes());
102 // Serialise to JWE compact form
103 return String.format(TOKEN_TEMPLATE, jweString, expDateTimeMillis);
104 }
105
106 private String createJwtToken(final JWTClaimsSet.Builder builder) throws Exception {
107 final JWSHeader.Builder headerBuilder = new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT);
108 final SignedJWT signedJWT = new SignedJWT(headerBuilder.build(), builder.build());
109
110 return encryptToken(signedJWT);
111 }
112
113 private String encryptToken(final SignedJWT signedJWT) throws Exception {
114 // Apply the HMAC protection
115 signedJWT.sign(signer);
116
117 final JWSVerifier verifier = new MACVerifier(secretKey.getEncoded());
118 signedJWT.verify(verifier);
119
120 // Create JWE object with signed JWT as payload
121 final JWEObject jweObject = new JWEObject(
122 new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
123 .contentType("JWT") // required to signal nested JWT
124 .build(),
125 new Payload(signedJWT));
126
127 // Perform encryption
128 final DirectEncrypter encrypter = new DirectEncrypter(secretKey.getEncoded());
129 encrypter.getJCAContext().setProvider(bc);
130
131 jweObject.encrypt(encrypter);
132
133 // Serialise to JWE compact form
134 return jweObject.serialize();
135 }
136
137 @Override
138 public SignedJWT decryptToken(final String token) {
139 try {
140 if (JWT_PATTERN.matcher(token).matches()) {
141 final String jwtToken = StringUtils.substringBetween(token, PREFIX, SUFFIX);
142 return decryptJweString(jwtToken);
143 } else {
144 return decryptJweString(token);
145 }
146 } catch (final Exception e) {
147 log.error(String.format("Cant decrypt token [%s]", token), e);
148 throw new AuthenticationException("Can not decrypt token", TokenProtocolError.TOKEN_INVALID.getCode());
149 }
150 }
151
152 private SignedJWT decryptJweString(final String token) throws Exception {
153 // Parse the JWE string
154 final JWEObject jweObject = JWEObject.parse(token);
155
156 // Decrypt with shared key
157 final DirectDecrypter decrypter = new DirectDecrypter(secretKey.getEncoded());
158 decrypter.getJCAContext().setProvider(bc);
159
160 jweObject.decrypt(decrypter);
161
162 // Extract payload
163 final SignedJWT signedJWT = jweObject.getPayload().toSignedJWT();
164
165 return signedJWT;
166 }
167
168 @Override
169 public boolean isJwtToken(final String token) {
170 if (StringUtils.isBlank(token)) {
171 return false;
172 }
173 return JWT_PATTERN.matcher(token).matches() ||
174 StringUtils.isNotBlank(token) && StringUtils.split(token, ".").length == 3;
175 }
176
177 @Override
178 public String sign(String signingStr, final String timestamp, final String uri) {
179 try {
180 signingStr = composeSigningStr(signingStr, timestamp, uri);
181 return signer.sign(new JWSHeader(JWSAlgorithm.HS256), signingStr.getBytes(Charset.forName("UTF-8"))).toString();
182 } catch (final Exception e) {
183 final String errMsg = "Can't sign " + signingStr;
184 log.error(errMsg, e);
185 throw new RuntimeException(errMsg, e);
186 }
187 }
188
189 @Override
190 public void verifySignature(final String signature, String signingStr, final String timestamp, final String uri) {
191 try {
192 signingStr = composeSigningStr(signingStr, timestamp, uri);
193 final byte[] signingStrBytes = signingStr.getBytes(Charset.forName("UTF-8"));
194 final JWSVerifier verifier = new MACVerifier(secretKey);
195 final boolean ok = verifier.verify(new JWSHeader(JWSAlgorithm.HS256), signingStrBytes, new Base64URL(signature));
196 if (!ok) {
197 throw new RuntimeException("Incorrect signature, verification failed.");
198 }
199 } catch (final Exception e) {
200 final String errMsg = "Error while verifying " + signature;
201 log.error(errMsg, e);
202 throw new AuthenticationException("Invalid signature", TokenProtocolError.INVALID_SIGNATURE.getCode());
203 }
204 }
205
206 @Override
207 public TokenPayload extractPayload(final String token) {
208 final SignedJWT jwtToken = decryptToken(token);
209 final JWTClaimsSet claimsSet = extractClaimsSet(jwtToken);
210 return new TokenPayload(claimsSet.getClaims(), claimsSet.getExpirationTime());
211 }
212
213 private String composeSigningStr(String signingStr, final String timestamp, final String uri) {
214 signingStr = StringUtils.isBlank(signingStr) ? EMPTY_REQ_BODY : signingStr;
215 return String.format(SIGNATURE_TEMPLATE, signingStr, timestamp, uri);
216 }
217
218 private JWTClaimsSet extractClaimsSet(final SignedJWT jwt) {
219 try {
220 return jwt.getJWTClaimsSet();
221 } catch (final ParseException e) {
222 log.error("Can't parse jwt", e);
223 throw new AuthenticationException(TokenProtocolError.INVALID_SIGNATURE.getCode());
224 }
225 }
226}