All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.asynchttpclient.oauth.OAuthSignatureCalculatorInstance Maven / Gradle / Ivy

/*
 * Copyright (c) 2017 AsyncHttpClient Project. All rights reserved.
 *
 * This program is licensed to you under the Apache License Version 2.0,
 * and you may not use this file except in compliance with the Apache License Version 2.0.
 * You may obtain a copy of the Apache License Version 2.0 at
 *     http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Apache License Version 2.0 is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
 */
package org.asynchttpclient.oauth;

import org.asynchttpclient.Param;
import org.asynchttpclient.SignatureCalculator;
import org.asynchttpclient.uri.Uri;
import org.asynchttpclient.util.StringBuilderPool;
import org.asynchttpclient.util.StringUtils;
import org.asynchttpclient.util.Utf8UrlEncoder;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * Non thread-safe {@link SignatureCalculator} for OAuth1.
 * 

* Supports most common signature inclusion and calculation methods: HMAC-SHA1 for calculation, and Header inclusion as inclusion method. Nonce generation uses simple random * numbers with base64 encoding. */ public class OAuthSignatureCalculatorInstance { private static final Pattern STAR_CHAR_PATTERN = Pattern.compile("*", Pattern.LITERAL); private static final Pattern PLUS_CHAR_PATTERN = Pattern.compile("+", Pattern.LITERAL); private static final Pattern ENCODED_TILDE_PATTERN = Pattern.compile("%7E", Pattern.LITERAL); private static final String KEY_OAUTH_CONSUMER_KEY = "oauth_consumer_key"; private static final String KEY_OAUTH_NONCE = "oauth_nonce"; private static final String KEY_OAUTH_SIGNATURE = "oauth_signature"; private static final String KEY_OAUTH_SIGNATURE_METHOD = "oauth_signature_method"; private static final String KEY_OAUTH_TIMESTAMP = "oauth_timestamp"; private static final String KEY_OAUTH_TOKEN = "oauth_token"; private static final String KEY_OAUTH_VERSION = "oauth_version"; private static final String OAUTH_VERSION_1_0 = "1.0"; private static final String OAUTH_SIGNATURE_METHOD = "HMAC-SHA1"; private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; private final Mac mac; private final byte[] nonceBuffer = new byte[16]; private final Parameters parameters = new Parameters(); public OAuthSignatureCalculatorInstance() throws NoSuchAlgorithmException { mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); } public String computeAuthorizationHeader(ConsumerKey consumerAuth, RequestToken userAuth, Uri uri, String method, List formParams, List queryParams) throws InvalidKeyException { String nonce = generateNonce(); long timestamp = generateTimestamp(); return computeAuthorizationHeader(consumerAuth, userAuth, uri, method, formParams, queryParams, timestamp, nonce); } private String generateNonce() { ThreadLocalRandom.current().nextBytes(nonceBuffer); // let's use base64 encoding over hex, slightly more compact than hex or decimals return Base64.getEncoder().encodeToString(nonceBuffer); } private static long generateTimestamp() { return System.currentTimeMillis() / 1000L; } String computeAuthorizationHeader(ConsumerKey consumerAuth, RequestToken userAuth, Uri uri, String method, List formParams, List queryParams, long timestamp, String nonce) throws InvalidKeyException { String percentEncodedNonce = Utf8UrlEncoder.percentEncodeQueryElement(nonce); String signature = computeSignature(consumerAuth, userAuth, uri, method, formParams, queryParams, timestamp, percentEncodedNonce); return computeAuthorizationHeader(consumerAuth, userAuth, signature, timestamp, percentEncodedNonce); } String computeSignature(ConsumerKey consumerAuth, RequestToken userAuth, Uri uri, String method, List formParams, List queryParams, long oauthTimestamp, String percentEncodedNonce) throws InvalidKeyException { StringBuilder sb = signatureBaseString( consumerAuth, userAuth, uri, method, formParams, queryParams, oauthTimestamp, percentEncodedNonce); ByteBuffer rawBase = StringUtils.charSequence2ByteBuffer(sb, UTF_8); byte[] rawSignature = digest(consumerAuth, userAuth, rawBase); // and finally, base64 encoded... phew! return Base64.getEncoder().encodeToString(rawSignature); } StringBuilder signatureBaseString(ConsumerKey consumerAuth, RequestToken userAuth, Uri uri, String method, List formParams, List queryParams, long oauthTimestamp, String percentEncodedNonce) { // beware: must generate first as we're using pooled StringBuilder String baseUrl = uri.toBaseUrl(); String encodedParams = encodedParams(consumerAuth, userAuth, oauthTimestamp, percentEncodedNonce, formParams, queryParams); StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); sb.append(method); // POST / GET etc (nothing to URL encode) sb.append('&'); Utf8UrlEncoder.encodeAndAppendPercentEncoded(sb, baseUrl); // and all that needs to be URL encoded (... again!) sb.append('&'); Utf8UrlEncoder.encodeAndAppendPercentEncoded(sb, encodedParams); return sb; } private String encodedParams(ConsumerKey consumerAuth, RequestToken userAuth, long oauthTimestamp, String percentEncodedNonce, List formParams, List queryParams) { parameters.reset(); // List of all query and form parameters added to this request; needed for calculating request signature // Start with standard OAuth parameters we need parameters.add(KEY_OAUTH_CONSUMER_KEY, consumerAuth.getPercentEncodedKey()) .add(KEY_OAUTH_NONCE, percentEncodedNonce) .add(KEY_OAUTH_SIGNATURE_METHOD, OAUTH_SIGNATURE_METHOD) .add(KEY_OAUTH_TIMESTAMP, String.valueOf(oauthTimestamp)); if (userAuth.getKey() != null) { parameters.add(KEY_OAUTH_TOKEN, userAuth.getPercentEncodedKey()); } parameters.add(KEY_OAUTH_VERSION, OAUTH_VERSION_1_0); if (formParams != null) { for (Param param : formParams) { // formParams are not already encoded parameters.add(Utf8UrlEncoder.percentEncodeQueryElement(param.getName()), Utf8UrlEncoder.percentEncodeQueryElement(param.getValue())); } } if (queryParams != null) { for (Param param : queryParams) { // queryParams are already form-url-encoded // but OAuth1 uses RFC3986_UNRESERVED_CHARS so * and + have to be encoded parameters.add(percentEncodeAlreadyFormUrlEncoded(param.getName()), percentEncodeAlreadyFormUrlEncoded(param.getValue())); } } return parameters.sortAndConcat(); } private String percentEncodeAlreadyFormUrlEncoded(String s) { s = STAR_CHAR_PATTERN.matcher(s).replaceAll("%2A"); s = PLUS_CHAR_PATTERN.matcher(s).replaceAll("%20"); s = ENCODED_TILDE_PATTERN.matcher(s).replaceAll("~"); return s; } private byte[] digest(ConsumerKey consumerAuth, RequestToken userAuth, ByteBuffer message) throws InvalidKeyException { StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); Utf8UrlEncoder.encodeAndAppendQueryElement(sb, consumerAuth.getSecret()); sb.append('&'); if (userAuth != null && userAuth.getSecret() != null) { Utf8UrlEncoder.encodeAndAppendQueryElement(sb, userAuth.getSecret()); } byte[] keyBytes = StringUtils.charSequence2Bytes(sb, UTF_8); SecretKeySpec signingKey = new SecretKeySpec(keyBytes, HMAC_SHA1_ALGORITHM); mac.init(signingKey); mac.update(message); return mac.doFinal(); } String computeAuthorizationHeader(ConsumerKey consumerAuth, RequestToken userAuth, String signature, long oauthTimestamp, String percentEncodedNonce) { StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); sb.append("OAuth "); sb.append(KEY_OAUTH_CONSUMER_KEY).append("=\"").append(consumerAuth.getPercentEncodedKey()).append("\", "); if (userAuth.getKey() != null) { sb.append(KEY_OAUTH_TOKEN).append("=\"").append(userAuth.getPercentEncodedKey()).append("\", "); } sb.append(KEY_OAUTH_SIGNATURE_METHOD).append("=\"").append(OAUTH_SIGNATURE_METHOD).append("\", "); // careful: base64 has chars that need URL encoding: sb.append(KEY_OAUTH_SIGNATURE).append("=\""); Utf8UrlEncoder.encodeAndAppendPercentEncoded(sb, signature).append("\", "); sb.append(KEY_OAUTH_TIMESTAMP).append("=\"").append(oauthTimestamp).append("\", "); sb.append(KEY_OAUTH_NONCE).append("=\"").append(percentEncodedNonce).append("\", "); sb.append(KEY_OAUTH_VERSION).append("=\"").append(OAUTH_VERSION_1_0).append("\""); return sb.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy