![JAR search and dependency download from the Maven repository](/logo.png)
com.ksyun.ks3.signer.Ks3V4Signer Maven / Gradle / Ivy
package com.ksyun.ks3.signer;
import com.ksyun.ks3.LengthCheckInputStream;
import com.ksyun.ks3.dto.Authorization;
import com.ksyun.ks3.service.Ks3ClientConfig;
import com.ksyun.ks3.service.request.UploadPartRequest;
import com.ksyun.ks3.signer.internal.*;
import com.ksyun.ks3.utils.*;
import com.ksyun.ks3.http.Request;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.URI;
import java.nio.charset.Charset;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static com.ksyun.ks3.utils.StringUtils.UTF8;
public class Ks3V4Signer{
private static final String LINE_SEPARATOR = "\n";
private static final String KSS4_TERMINATOR = "kss4_request";
private static final String KSS4_SIGNING_ALGORITHM = "KSS4-HMAC-SHA256";
private static final String X_Kss_CREDENTIAL = "X-Kss-Credential";
private static final String X_Kss_DATE = "X-Kss-Date";
private static final String X_Kss_EXPIRES = "X-Kss-Expires";
private static final String X_Kss_SIGNED_HEADER = "X-Kss-SignedHeaders";
private static final String X_Kss_CONTENT_SHA256 = "X-Kss-content-sha256";
private static final String X_Kss_SIGNATURE = "X-Kss-Signature";
private static final String X_Kss_ALGORITHM = "X-Kss-Algorithm";
private static final String AUTHORIZATION = "Authorization";
private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
private static final String HOST = "Host";
private static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
private static final int SIGNER_CACHE_MAX_SIZE = 300;
private static final FIFOCache signerCache = new FIFOCache(SIGNER_CACHE_MAX_SIZE);
private static final Log log = LogFactory.getLog(AuthUtils.class);
public String sign(Authorization auth, Request request,Ks3ClientConfig ks3config) throws SignatureException {
final KSSSignerRequestParams signerParams = new KSSSignerRequestParams(
request, new Date(), request.getRegion(), "ks3",
KSS4_SIGNING_ALGORITHM);
request.addHeader(X_Kss_DATE, signerParams.getFormattedSigningDateTime());
addHost(request,ks3config);
String contentSha256 = calculateContentHash(request);
request.addHeader(X_Kss_CONTENT_SHA256, contentSha256);
final String canonicalRequest = createCanonicalRequest(request,
contentSha256,ks3config.isPathStyleAccess());
final String stringToSign = createStringToSign(canonicalRequest,
signerParams);
final byte[] signingKey = deriveSigningKey(auth,
signerParams);
final byte[] signature = computeSignature(stringToSign, signingKey,
signerParams);
String authorization = buildAuthorizationHeader(request, signature,
auth, signerParams);
request.addHeader(AUTHORIZATION,authorization);
return authorization;
}
protected String calcSignature (Authorization auth, Request request,Ks3ClientConfig ks3config) throws SignatureException {
final KSSSignerRequestParams signerParams = new KSSSignerRequestParams(
request,new Date(), request.getRegion(), "ks3",
KSS4_SIGNING_ALGORITHM);
addHost(request,ks3config);
request.getHeaders().remove("User-Agent");
String contentSha256 = UNSIGNED_PAYLOAD;
final String signingCredentials = auth.getAccessKeyId() + "/"
+ signerParams.getScope();
request.getQueryParams().put(X_Kss_ALGORITHM, KSS4_SIGNING_ALGORITHM);
request.getQueryParams().put(X_Kss_CREDENTIAL,signingCredentials);
request.getQueryParams().put(X_Kss_DATE,signerParams.getFormattedSigningDateTime());
Date date = new Date();
request.getQueryParams().put(X_Kss_EXPIRES, String.valueOf((request.getExpires().getTime()-date.getTime())/1000));
request.addQueryParam(X_Kss_SIGNED_HEADER,getSignedHeadersString(request));
final String canonicalRequest = createCanonicalRequest(request,
contentSha256,ks3config.isPathStyleAccess());
final String stringToSign = createStringToSign(canonicalRequest,
signerParams);
final byte[] signingKey = deriveSigningKey(auth,
signerParams);
final byte[] signature = computeSignature(stringToSign, signingKey,
signerParams);
String signer = BinaryUtils.toHex(signature);
return signer;
}
private void addHost(Request request,Ks3ClientConfig ks3config){
String host = "";
if(!ks3config.isPathStyleAccess() && !ks3config.isDomainMode()) {
if(!StringUtils.isBlank(request.getBucket()))
host += request.getBucket() + ".";
}
request.addHeader(HOST, host+request.getEndpoint());
}
protected String calculateContentHash(Request request) throws java.security.SignatureException{
if(Ks3ClientConfig.SignerVersion.V4_UNSIGNED_PAYLOAD_SIGNER == Ks3ClientConfig.version)
return UNSIGNED_PAYLOAD;
InputStream is = request.getContent();
if (is != null && !is.markSupported())
return UNSIGNED_PAYLOAD;
InputStream payloadStream = getBinaryRequestPayloadStream(request);
payloadStream.mark(0);
String contentSha256 = BinaryUtils.toHex(hash(payloadStream));
try {
payloadStream.reset();
} catch (Exception e) {
throw new SignatureException("Unable to reset stream after calculating kss signature"
+ e);
}
return contentSha256;
}
protected byte[] hash(InputStream input) throws java.security.SignatureException {
try {
MessageDigest md = getMessageDigestInstance();
@SuppressWarnings("resource")
DigestInputStream digestInputStream = new SdkDigestInputStream(
input, md);
byte[] buffer = new byte[1024];
while (digestInputStream.read(buffer) > -1)
;
return digestInputStream.getMessageDigest().digest();
} catch (Exception e) {
throw new SignatureException(
"Unable to compute hash while signing request: "
+ e.getMessage(), e);
}
}
private static MessageDigest getMessageDigestInstance() {
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
;
messageDigest.reset();
return messageDigest;
}
protected String getCanonicalizedQueryString(Request request) {
return this.getCanonicalizedQueryString2(request.getQueryParams());
}
protected String getCanonicalizedQueryString2(Map parameters) {
final SortedMap sorted = new TreeMap();
final List paramNames = new ArrayList();
/**
* Signing protocol expects the param values also to be sorted after url
* encoding in addition to sorted parameter names.
*/
for (Map.Entry entry : parameters.entrySet()) {
paramNames.add(entry.getKey());
}
Collections.sort(paramNames);
for(String paramName : paramNames){
sorted.put(paramName,parameters.get(paramName));
}
final StringBuilder result = new StringBuilder();
for(Map.Entry entry : sorted.entrySet()) {
if (result.length() > 0) {
result.append("&");
}
result.append(HttpUtils.urlEncode(entry.getKey(), false))
.append("=")
.append(HttpUtils.urlEncode(entry.getValue(), false));
}
return result.toString();
}
protected InputStream getBinaryRequestPayloadStream(Request request) throws SignatureException {
return getBinaryRequestPayloadStreamWithoutQueryParams(request);
}
protected InputStream getBinaryRequestPayloadStreamWithoutQueryParams(Request request) throws SignatureException {
try {
InputStream is = request.getContent();
if (is == null)
return new ByteArrayInputStream(new byte[0]);
if (!is.markSupported())//如果进入这个IF,就无法计算签名
throw new Exception("Unable to read request payload to sign request.");
return is;
} catch (SignatureException e) {
throw e;
} catch (Exception e) {
throw new SignatureException("Unable to read request payload to sign request: " + e.getMessage(), e);
}
}
protected String createCanonicalRequest(Request request,
String contentSha256,boolean pathStyleAccess) {
/* This would url-encode the resource path for the first time. */
String path1 = "",path2="";
if(pathStyleAccess) {
if (!StringUtils.isBlank(request.getBucket())) {
path1 += "/" + request.getBucket();
}
}
if (!StringUtils.isBlank(request.getKey())) {
path2 = "/" + request.getKey();
}
final String path = SdkUtils.appendUri(
path1,path2);
final StringBuilder canonicalRequestBuilder = new StringBuilder(request
.getMethod().toString());
canonicalRequestBuilder.append(LINE_SEPARATOR)
// This would optionally double url-encode the resource path
.append(getCanonicalizedResourcePath(path))
.append(LINE_SEPARATOR)
.append(getCanonicalizedQueryString(request))
.append(LINE_SEPARATOR)
.append(getCanonicalizedHeaderString(request))
.append(LINE_SEPARATOR)
.append(getSignedHeadersString(request))
.append(LINE_SEPARATOR)
.append(contentSha256);
final String canonicalRequest = canonicalRequestBuilder.toString();
if (log.isDebugEnabled())
log.debug("kss4 Canonical Request: '\"" + canonicalRequest + "\"");
return canonicalRequest;
}
/**
* Step 2 of the kss Signature version 4 calculation.
*/
protected String createStringToSign(String canonicalRequest,
KSSSignerRequestParams signerParams) throws SignatureException {
final StringBuilder stringToSignBuilder = new StringBuilder(
signerParams.getSigningAlgorithm());
stringToSignBuilder.append(LINE_SEPARATOR)
.append(signerParams.getFormattedSigningDateTime())
.append(LINE_SEPARATOR)
.append(signerParams.getScope())
.append(LINE_SEPARATOR)
.append(BinaryUtils.toHex(hash(canonicalRequest)));
final String stringToSign = stringToSignBuilder.toString();
if (log.isDebugEnabled())
log.debug("kss4 String to Sign: '\"" + stringToSign + "\"");
return stringToSign;
}
public byte[] hash(String text) throws SignatureException {
return doHash(text);
}
private static byte[] doHash(String text) throws SignatureException {
try {
MessageDigest md = getMessageDigestInstance();
md.update(text.getBytes(UTF8));
return md.digest();
} catch (Exception e) {
throw new SignatureException(
"Unable to compute hash while signing request: "
+ e.getMessage(), e);
}
}
protected String getCanonicalizedResourcePath(String resourcePath) {
return getCanonicalizedResourcePath(resourcePath, true);
}
protected String getCanonicalizedResourcePath(String resourcePath, boolean urlEncode) {
if (resourcePath == null || resourcePath.isEmpty()) {
return "/";
} else {
String value = urlEncode ? HttpUtils.urlEncode(resourcePath, true) : resourcePath;
if (value.startsWith("/")) {
return value;
} else {
return "/".concat(value);
}
}
}
private String getCanonicalizedHeaderString(Request request) {
final List sortedHeaders = new ArrayList(request
.getHeaders().keySet());
Map headers = request.getHeaders();
Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
StringBuilder buffer = new StringBuilder();
for (String header : sortedHeaders) {
if (shouldExcludeHeaderFromSigning(header)) {
continue;
}
buffer.append((header.toLowerCase() + ":" + headers.get(header)).trim());
buffer.append("\n");
}
return buffer.toString();
}
protected boolean shouldExcludeHeaderFromSigning(String header) {
return listOfHeadersToIgnoreInLowerCase.contains(header.toLowerCase());
}
private static final List listOfHeadersToIgnoreInLowerCase = Arrays.asList("connection", "x-kss-trace-id");
protected String getSignedHeadersString(Request request) {
final List sortedHeaders = new ArrayList(request
.getHeaders().keySet());
Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
StringBuilder buffer = new StringBuilder();
for (String header : sortedHeaders) {
if (shouldExcludeHeaderFromSigning(header)) {
continue;
}
if (buffer.length() > 0)
buffer.append(";");
buffer.append(StringUtils.lowerCase(header));
}
return buffer.toString();
}
/**
* Step 3 of the kss Signature version 4 calculation. It involves deriving
* the signing key and computing the signature.
*/
private final byte[] deriveSigningKey(Authorization auth,
KSSSignerRequestParams signerRequestParams) throws SignatureException {
final String cacheKey = computeSigningCacheKeyName(auth,
signerRequestParams);
final long daysSinceEpochSigningDate = DateUtils
.numberOfDaysSinceEpoch(signerRequestParams
.getSigningDateTimeMilli());
SignerKey signerKey = signerCache.get(cacheKey);
if (signerKey != null) {
if (daysSinceEpochSigningDate == signerKey
.getNumberOfDaysSinceEpoch()) {
return signerKey.getSigningKey();
}
}
if (log.isDebugEnabled()) {
log.debug("Generating a new signing key as the signing key not available in the cache for the date "
+ TimeUnit.DAYS.toMillis(daysSinceEpochSigningDate));
}
byte[] signingKey = newSigningKey(auth,
signerRequestParams.getFormattedSigningDate(),
signerRequestParams.getRegionName(),
signerRequestParams.getServiceName());
signerCache.add(cacheKey, new SignerKey(
daysSinceEpochSigningDate, signingKey));
return signingKey;
}
/**
* Computes the name to be used to reference the signing key in the cache.
*/
private final String computeSigningCacheKeyName(Authorization auth,
KSSSignerRequestParams signerRequestParams) {
final StringBuilder hashKeyBuilder = new StringBuilder(
auth.getAccessKeySecret());
return hashKeyBuilder.append("-")
.append(signerRequestParams.getRegionName())
.append("-")
.append(signerRequestParams.getServiceName()).toString();
}
/**
* Step 3 of the kss Signature version 4 calculation. It involves deriving
* the signing key and computing the signature.
*/
protected final byte[] computeSignature(String stringToSign,
byte[] signingKey, KSSSignerRequestParams signerRequestParams) throws SignatureException {
return sign(stringToSign.getBytes(Charset.forName("UTF-8")), signingKey,
HMAC_SHA256_ALGORITHM);
}
/**
* Creates the authorization header to be included in the request.
*/
private String buildAuthorizationHeader(Request request,
byte[] signature, Authorization auth,
KSSSignerRequestParams signerParams) {
final String signingCredentials = auth.getAccessKeyId() + "/"
+ signerParams.getScope();
final String credential = "Credential="
+ signingCredentials;
final String signerHeaders = "SignedHeaders="
+ getSignedHeadersString(request);
final String signatureHeader = "Signature="
+ BinaryUtils.toHex(signature);
final StringBuilder authHeaderBuilder = new StringBuilder();
authHeaderBuilder.append(KSS4_SIGNING_ALGORITHM)
.append(" ")
.append(credential)
.append(", ")
.append(signerHeaders)
.append(", ")
.append(signatureHeader);
return authHeaderBuilder.toString();
}
/**
* Generates a new signing key from the given parameters and returns it.
*/
protected byte[] newSigningKey(Authorization auth,
String dateStamp, String regionName, String serviceName) throws SignatureException {
byte[] kSecret = ("KSS4" + auth.getAccessKeySecret())
.getBytes(Charset.forName("UTF-8"));
byte[] kDate = sign(dateStamp, kSecret, HMAC_SHA256_ALGORITHM);
byte[] kRegion = sign(regionName, kDate, HMAC_SHA256_ALGORITHM);
byte[] kService = sign(serviceName, kRegion,
HMAC_SHA256_ALGORITHM);
return sign(KSS4_TERMINATOR, kService, HMAC_SHA256_ALGORITHM);
}
public byte[] sign(String stringData, byte[] key,
String algorithm) throws SignatureException {
try {
byte[] data = stringData.getBytes(UTF8);
return sign(data, key, algorithm);
} catch (Exception e) {
throw new SignatureException(
"Unable to calculate a request signature: "
+ e.getMessage(), e);
}
}
protected byte[] sign(byte[] data, byte[] key,
String algorithm) throws SignatureException {
try {
Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data);
} catch (Exception e) {
throw new SignatureException(
"Unable to calculate a request signature: "
+ e.getMessage(), e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy