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

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