/** * Canonicalize the given {@link CanonicalHttpRequest} and hash it. * This request hash can be included as a JWT claim to verify that request components are genuine. * @param request {@link CanonicalHttpRequest} to be canonicalized and hashed * @return {@link String} hash suitable for use as a JWT claim value * @throws UnsupportedEncodingException if the {@link java.net.URLEncoder} cannot encode the request's field's characters * @throws NoSuchAlgorithmException if the hashing algorithm does not exist at runtime */ public static String computeCanonicalRequestHash(CanonicalHttpRequest request) throws UnsupportedEncodingException, NoSuchAlgorithmException { // prevent the code in this method being repeated in every call site that needs a request hash, // encapsulate the knowledge of the type of hash that we are using return JwtUtil.computeSha256Hash(canonicalize(request)); }
/** * Convenience method for building and appending JWT claims related to a {@link com.atlassian.jwt.CanonicalHttpRequest}. * Encapsulates the knowledge of what claims we make regarding the request. * @param jsonBuilder {@link com.atlassian.jwt.writer.JwtJsonBuilder} that constructs the JWT payload * @param request {@link com.atlassian.jwt.CanonicalHttpRequest} representing the incoming or outgoing HTTP request * @throws UnsupportedEncodingException if {@link java.net.URLEncoder} cannot encode the request's characters * @throws NoSuchAlgorithmException if the hashing algorithm does not exist at runtime */ public static void appendHttpRequestClaims(JwtJsonBuilder jsonBuilder, CanonicalHttpRequest request) throws UnsupportedEncodingException, NoSuchAlgorithmException { jsonBuilder.queryHash(HttpRequestCanonicalizer.computeCanonicalRequestHash(request)); } }
/** * Assemble the components of the HTTP request into the correct format so that they can be signed or hashed. * @param request {@link CanonicalHttpRequest} that provides the necessary components * @return {@link String} encoding the canonical form of this request as required for constructing {@link JwtConstants.Claims#QUERY_HASH} values * @throws UnsupportedEncodingException {@link UnsupportedEncodingException} if the {@link java.net.URLEncoder} cannot encode the request's field's characters */ public static String canonicalize(CanonicalHttpRequest request) throws UnsupportedEncodingException { return String.format("%s%s%s%s%s", canonicalizeMethod(request), CANONICAL_REQUEST_PART_SEPARATOR, canonicalizeUri(request), CANONICAL_REQUEST_PART_SEPARATOR, canonicalizeQueryParameters(request)); }
private static String canonicalizeQueryParameters(CanonicalHttpRequest request) throws UnsupportedEncodingException { String result = ""; if (null != request.getParameterMap()) { List<ComparableParameter> parameterList = new ArrayList<ComparableParameter>(request.getParameterMap().size()); for (Map.Entry<String, String[]> parameter : request.getParameterMap().entrySet()) { if (!JwtConstants.JWT_PARAM_NAME.equals(parameter.getKey())) { parameterList.add(new ComparableParameter(parameter)); } } Collections.sort(parameterList); result = percentEncode(getParameters(parameterList)); } return result; }
/** * Construct a form-urlencoded document containing the given sequence of * name/parameter pairs. */ private static String percentEncode(Iterable<? extends Map.Entry<String, String[]>> parameters) { ByteArrayOutputStream b = new ByteArrayOutputStream(); // IOException should not be throws as we are not messing around with it between creation and use // (e.g. by closing it) but the methods on the OutputStream interface don't know that try { percentEncode(parameters, b); return new String(b.toByteArray()); } catch (IOException e) { throw new RuntimeException(e); } }
/** * Write a form-urlencoded document into the given stream, containing the * given sequence of name/parameter pairs. */ private static void percentEncode(Iterable<? extends Map.Entry<String, String[]>> parameters, OutputStream into) throws IOException { if (parameters != null) { boolean first = true; for (Map.Entry<String, String[]> parameter : parameters) { if (first) { first = false; } else { into.write(JwtUtil.QUERY_PARAMS_SEPARATOR); } into.write(JwtUtil.percentEncode(safeToString(parameter.getKey())).getBytes()); into.write('='); List<String> percentEncodedValues = new ArrayList<String>(parameter.getValue().length); for (String value : parameter.getValue()) { percentEncodedValues.add(JwtUtil.percentEncode(value)); } into.write(StringUtils.join(percentEncodedValues, ENCODED_PARAM_VALUE_SEPARATOR).getBytes()); } } }
/** * Encapsulate the building of requirements that we place upon JWTs in incoming requests. * @param request incoming request * @return {@link Map} of claim name to verifier for claims upon which we place requirements * @throws UnsupportedEncodingException if {@link java.net.URLEncoder} cannot encode the request's characters * @throws NoSuchAlgorithmException if the hashing algorithm does not exist at runtime */ public static Map<String, ? extends JwtClaimVerifier> build(CanonicalHttpRequest request) throws UnsupportedEncodingException, NoSuchAlgorithmException { return Collections.singletonMap(JwtConstants.Claims.QUERY_HASH, new JwtClaimEqualityVerifier(JwtConstants.Claims.QUERY_HASH, HttpRequestCanonicalizer.computeCanonicalRequestHash(request))); } }
public String encodeJwt(HttpMethod httpMethod, URI targetPath, URI addonBaseUrl, Map<String, String[]> params, String issuerId, String secret, Optional<UserProfile> user) { checkArgument(null != httpMethod, "HttpMethod argument cannot be null"); checkArgument(null != targetPath, "URI argument cannot be null"); checkArgument(null != addonBaseUrl, "base URI argument cannot be null"); checkArgument(null != secret, "secret argument cannot be null"); final long currentTime = TimeUtil.currentTimeSeconds(); JwtJsonBuilder jsonBuilder = jwtBuilderFactory.jsonBuilder() .issuedAt(currentTime) .expirationTime(currentTime + JWT_EXPIRY_WINDOW_SECONDS) .issuer(issuerId); Map<String, String[]> completeParams = params; try { if (!StringUtils.isEmpty(targetPath.getQuery())) { completeParams = new HashMap<>(params); completeParams.putAll(constructParameterMap(targetPath)); } CanonicalHttpUriRequest request = new CanonicalHttpUriRequest(httpMethod.toString(), extractRelativePath(targetPath, addonBaseUrl), "", completeParams); log.debug("Canonical request is: " + HttpRequestCanonicalizer.canonicalize(request)); JwtClaimsBuilder.appendHttpRequestClaims(jsonBuilder, request); } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } JwtUserContextBuilder.addUserContextObject(jsonBuilder, user); return jwtService.issueJwt(jsonBuilder.build(), secret); }
public static String generateJwtSignature(HttpMethod httpMethod, URI uri, String addonKey, String secret, String contextPath, String subject) throws UnsupportedEncodingException, NoSuchAlgorithmException { JwtWriterFactory jwtWriterFactory = new NimbusJwtWriterFactory(); JwtWriter jwtWriter = jwtWriterFactory.macSigningWriter(SigningAlgorithm.HS256, secret); // Parse param values and build a map final List<NameValuePair> rawParams = URLEncodedUtils.parse(uri, "UTF-8"); final ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder(); for (NameValuePair rawParam : rawParams) { builder.put(rawParam.getName(), rawParam.getValue()); } final ImmutableMap.Builder<String, String[]> paramsMap = ImmutableMap.builder(); for (Map.Entry<String, Collection<String>> stringCollectionEntry : builder.build().asMap().entrySet()) { final Collection<String> collection = stringCollectionEntry.getValue(); paramsMap.put(stringCollectionEntry.getKey(), collection.toArray(new String[collection.size()])); } final JwtJsonBuilder jsonBuilder = new JsonSmartJwtJsonBuilder() .issuer(addonKey) .queryHash(HttpRequestCanonicalizer.computeCanonicalRequestHash(new CanonicalHttpUriRequest(httpMethod.name(), uri.getPath(), URI.create(contextPath).getPath(), paramsMap.build()))); if (null != subject) { jsonBuilder.subject(subject); } return jwtWriter.jsonToJwt(jsonBuilder.build()); } }
public String encodeJwt(HttpMethod httpMethod, URI targetPath, URI addonBaseUrl, Map<String, String[]> params, String issuerId, String secret, Optional<UserProfile> user) { checkArgument(null != httpMethod, "HttpMethod argument cannot be null"); checkArgument(null != targetPath, "URI argument cannot be null"); checkArgument(null != addonBaseUrl, "base URI argument cannot be null"); checkArgument(null != secret, "secret argument cannot be null"); final long currentTime = TimeUtil.currentTimeSeconds(); JwtJsonBuilder jsonBuilder = jwtBuilderFactory.jsonBuilder() .issuedAt(currentTime) .expirationTime(currentTime + JWT_EXPIRY_WINDOW_SECONDS) .issuer(issuerId); Map<String, String[]> completeParams = params; try { if (!StringUtils.isEmpty(targetPath.getQuery())) { completeParams = new HashMap<>(params); completeParams.putAll(constructParameterMap(targetPath)); } CanonicalHttpUriRequest request = new CanonicalHttpUriRequest(httpMethod.toString(), extractRelativePath(targetPath, addonBaseUrl), "", completeParams); log.debug("Canonical request is: " + HttpRequestCanonicalizer.canonicalize(request)); JwtClaimsBuilder.appendHttpRequestClaims(jsonBuilder, request); } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } JwtUserContextBuilder.addUserContextObject(jsonBuilder, user); return jwtService.issueJwt(jsonBuilder.build(), secret); }
public static String generateJwtSignature(HttpMethod httpMethod, URI uri, String addonKey, String secret, String contextPath, String subject) throws UnsupportedEncodingException, NoSuchAlgorithmException { JwtWriterFactory jwtWriterFactory = new NimbusJwtWriterFactory(); JwtWriter jwtWriter = jwtWriterFactory.macSigningWriter(SigningAlgorithm.HS256, secret); // Parse param values and build a map final List<NameValuePair> rawParams = URLEncodedUtils.parse(uri, "UTF-8"); final ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder(); for (NameValuePair rawParam : rawParams) { builder.put(rawParam.getName(), rawParam.getValue()); } final ImmutableMap.Builder<String, String[]> paramsMap = ImmutableMap.builder(); for (Map.Entry<String, Collection<String>> stringCollectionEntry : builder.build().asMap().entrySet()) { final Collection<String> collection = stringCollectionEntry.getValue(); paramsMap.put(stringCollectionEntry.getKey(), collection.toArray(new String[collection.size()])); } final JwtJsonBuilder jsonBuilder = new JsonSmartJwtJsonBuilder() .issuer(addonKey) .queryHash(HttpRequestCanonicalizer.computeCanonicalRequestHash(new CanonicalHttpUriRequest(httpMethod.name(), uri.getPath(), URI.create(contextPath).getPath(), paramsMap.build()))); if (null != subject) { jsonBuilder.subject(subject); } return jwtWriter.jsonToJwt(jsonBuilder.build()); } }