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

com.huaweicloud.sdk.obs.v1.sts.OBSSigner Maven / Gradle / Ivy

There is a newer version: 3.1.114
Show newest version
/*
 * Copyright 2023 Huawei Technologies Co.,Ltd.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.huaweicloud.sdk.obs.v1.sts;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

public class OBSSigner {
    private static final Logger logger = LoggerFactory.getLogger(OBSSigner.class);

    public static final List ALLOWED_RESOURCE_PARAMTER_NAMES = Collections.unmodifiableList(
            Arrays.asList("acl", "backtosource", "policy", "torrent", "logging", "location", "storageinfo", "quota",
                    "storagepolicy", "storageclass", "requestpayment", "versions", "versioning", "versionid", "uploads",
                    "uploadid", "partnumber", "website", "notification", "lifecycle", "deletebucket", "delete", "cors",
                    "restore", "tagging", "replication", "metadata", "encryption", "directcoldaccess", "mirrorrefresh",
                    "mirrorbacktosource", "obsbucketalias", "obsalias", "replication_progress", "inventory",
                    "obsworkflowtriggerpolicy", "x-workflow-limit", "x-workflow-prefix", "x-workflow-start",
                    "x-workflow-template-name", "x-workflow-graph-name", "x-workflow-execution-state", "x-obs-security-token",
                    "x-workflow-category", "x-workflow-create", "customdomain", "cdnnotifyconfiguration", "dispolicy",
                    "obscompresspolicy", "template_name", "template_name_prefix", "x-workflow-status", "sfsumount",
                    "x-workflow-type", "x-workflow-forbid", "sfsacl", "attname", "object-lock", "retention",
                    "x_obs_resource_type", "x_obs_dnslevel", "extension_policy", "x_obs_resource_id", "synccallback",
                    "orchestration", "fscreatehardlink", "asyncfetchjob", "asynccallback", "sfsmount", "asyncfetchpolicy",
                    /**
                     * File System API
                     */
                    "append", "position", "truncate", "modify", "rename", "length", "name", "fileinterface",
                    "readahead", "response-content-type", "response-content-language", "response-expires",
                    "response-cache-control", "response-content-disposition", "response-content-encoding",
                    "x-image-save-bucket", "x-image-save-object", "x-image-process", "x-obs-sse-kms-key-project-id",
                    "x-oss-process", "ignore-sign-in-query", "listcontentsummary", "multilistcontentsummary",
                    "getcontentsummary", "select", "select-type"));

    private static Class jdkBase64EncoderClass;

    private static Object jdkNewEncoder;

    static {
        try {
            Class base64 = Class.forName("java.util.Base64");
            jdkNewEncoder = base64.getMethod("getEncoder").invoke(null);
        } catch (ClassNotFoundException e) {
            logger.warn("class not found exception.");
        } catch (IllegalAccessException e) {
            logger.warn("illegal access exception.");
        } catch (IllegalArgumentException e) {
            logger.warn("illegal argument exception.");
        } catch (InvocationTargetException e) {
            logger.warn("invocation target exception.");
        } catch (NoSuchMethodException e) {
            logger.warn("no such method exception.");
        } catch (SecurityException e) {
            logger.warn("security exception.");
        }

        try {
            jdkBase64EncoderClass = Class.forName("sun.misc.BASE64Encoder");
        } catch (ClassNotFoundException e) {
            logger.trace("class not found exception.");
        }
    }

    public void sign(Request request) throws Exception {
        String stringToSign = makeServiceCanonicalString(request.getMethod(), request.getUrl(), request.getHeaders());
        String auth = "OBS " + request.getKey() + ":" + calculateSignature(stringToSign, request.getSecret());
        Map headers = request.getHeaders();
        headers.put("Authorization", auth);
        request.setHeaders(headers);
    }

    private final String makeServiceCanonicalString(String method, String resource, Map headersMap) throws UnsupportedEncodingException {
        String headerPrefix = "x-obs-";
        SortedMap interestingHeaders = transHeaders(headersMap, headerPrefix, null);
        StringBuilder canonicalStringBuf = transCanonicalString(method, headerPrefix, interestingHeaders);

        int queryIndex = resource.indexOf('?');
        if (queryIndex < 0) {
            canonicalStringBuf.append(resource);
        } else {
            canonicalStringBuf.append(resource, 0, queryIndex);

            SortedMap sortedResourceParams = new TreeMap<>();

            String query = resource.substring(queryIndex + 1);
            for (String paramPair : query.split("&")) {
                String[] paramNameValue = paramPair.split("=");
                String name = URLDecoder.decode(paramNameValue[0], "UTF-8");
                String value = null;
                if (paramNameValue.length > 1) {
                    value = URLDecoder.decode(paramNameValue[1], "UTF-8");
                }
                if (ALLOWED_RESOURCE_PARAMTER_NAMES.contains(name.toLowerCase(Locale.ROOT))
                        || name.toLowerCase(Locale.ROOT).startsWith(headerPrefix)) {
                    sortedResourceParams.put(name, value);
                }
            }

            if (sortedResourceParams.size() > 0) {
                canonicalStringBuf.append("?");
            }
            boolean addedParam = false;
            for (Map.Entry entry : sortedResourceParams.entrySet()) {
                if (addedParam) {
                    canonicalStringBuf.append("&");
                }
                canonicalStringBuf.append(entry.getKey());
                if (null != entry.getValue()) {
                    canonicalStringBuf.append("=").append(entry.getValue());
                }
                addedParam = true;
            }
        }

        return canonicalStringBuf.toString();
    }

    @SuppressWarnings("unchecked")
    private StringBuilder transCanonicalString(String method, String headerPrefix,
                                               SortedMap interestingHeaders) {
        StringBuilder canonicalStringBuf = new StringBuilder();
        canonicalStringBuf.append(method).append("\n");

        String headerMetaPrefix = "x-obs-meta-";

        for (Map.Entry entry : interestingHeaders.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            if (value instanceof List) {
                value = join((List) value, ",", true);
            } else if (value == null) {
                value = "";
            }

            if (key.startsWith(headerMetaPrefix)) {
                canonicalStringBuf.append(key).append(':').append(value.toString().trim());
            } else if (key.startsWith(headerPrefix)) {
                canonicalStringBuf.append(key).append(':').append(value);
            } else {
                canonicalStringBuf.append(value);
            }
            canonicalStringBuf.append("\n");
        }
        return canonicalStringBuf;
    }

    private String join(List items, String delimiter, boolean needTrim) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < items.size(); i++) {
            String item = items.get(i).toString();
            sb.append(needTrim ? item.trim() : item);
            if (i < items.size() - 1) {
                sb.append(delimiter);
            }
        }
        return sb.toString();
    }

    @SuppressWarnings("unchecked")
    private SortedMap transHeaders(Map headersMap,
                                                   String headerPrefix, String expires) {
        String dateHeader = "date";
        String contentTypeHeader = "content-type";
        String contentMd5Header = "content-md5";

        SortedMap interestingHeaders = new TreeMap<>();
        if (headersMap != null) {
            for (Map.Entry entry : headersMap.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();

                if (null == key) {
                    continue;
                }

                String lk = key.toLowerCase(Locale.getDefault());

                if (lk.equals(contentTypeHeader) || lk.equals(contentMd5Header) || lk.equals(dateHeader)) {
                    interestingHeaders.put(lk, value);
                } else if (lk.startsWith(headerPrefix)) {
                    List values;
                    if (interestingHeaders.containsKey(lk)) {
                        values = (List) interestingHeaders.get(lk);
                    } else {
                        values = new ArrayList<>();
                        interestingHeaders.put(lk, values);
                    }
                    values.add(value);
                }
            }
        }
        if (interestingHeaders.containsKey("x-obs-date")) {
            interestingHeaders.put(dateHeader, "");
        }

        if (expires != null) {
            interestingHeaders.put(dateHeader, expires);
        }

        if (!interestingHeaders.containsKey(contentTypeHeader)) {
            interestingHeaders.put(contentTypeHeader, "");
        }
        if (!interestingHeaders.containsKey(contentMd5Header)) {
            interestingHeaders.put(contentMd5Header, "");
        }

        return interestingHeaders;
    }

    private String calculateSignature(String stringToSign, String sk) throws Exception {
        return signWithHmacSha1(sk, stringToSign);
    }

    private String signWithHmacSha1(String sk, String canonicalString) throws Exception {
        SecretKeySpec signingKey;
        signingKey = new SecretKeySpec(sk.getBytes(StandardCharsets.UTF_8), "HmacSHA1");

        Mac mac;
        try {
            mac = Mac.getInstance("HmacSHA1");
        } catch (NoSuchAlgorithmException e) {
            throw new NoSuchAlgorithmException("Could not find sha1 algorithm", e);
        }
        try {
            mac.init(signingKey);
        } catch (InvalidKeyException e) {
            throw new InvalidKeyException("Could not initialize the MAC algorithm", e);
        }

        return toBase64(mac.doFinal(canonicalString.getBytes(StandardCharsets.UTF_8)));
    }

    private String toBase64(byte[] data) throws Exception {
        if (jdkNewEncoder != null) {
            Method m = jdkNewEncoder.getClass().getMethod("encode", byte[].class);
            return new String((byte[]) m.invoke(jdkNewEncoder, data), StandardCharsets.UTF_8).replaceAll("\\s", "");
        }

        if (jdkBase64EncoderClass != null) {
            Method m = jdkBase64EncoderClass.getMethod("encode", byte[].class);
            return ((String) m.invoke(jdkBase64EncoderClass.getConstructor().newInstance(), data)).replaceAll("\\s",
                    "");
        }

        throw new ClassNotFoundException("Failed to find a base64 encoder");
    }

    public static class Request {
        private String key = null;
        private String secret = null;
        private String method = null;
        private URI uri = null;
        private String body = null;
        private String fragment = null;
        Map headers = new Hashtable<>();
        private Map> queryString = new Hashtable<>();
        private String bucketName;

        public String getBucketName() {
            return bucketName;
        }

        public void setBucketName(String bucketName) {
            this.bucketName = bucketName;
        }

        public String getKey() {
            return key;
        }

        public void setKey(String key) {
            this.key = key;
        }

        public String getSecret() {
            return secret;
        }

        public void setSecret(String secret) {
            this.secret = secret;
        }

        public String getMethod() {
            return method;
        }

        public void setMethod(String method) {
            this.method = method;
        }

        public URI getUri() {
            return uri;
        }

        public void setUri(URI uri) {
            this.uri = uri;
        }

        public String getUrl() {
            if (isNotBlank(uri.getRawQuery())) {
                if (isNotBlank(bucketName)) {
                    return "/" + bucketName + uri.getRawPath() + "?" + uri.getRawQuery();
                } else {
                    return uri.getRawPath() + "?" + uri.getRawQuery();
                }
            } else {
                if (isNotBlank(bucketName)) {
                    return "/" + bucketName + uri.getRawPath();
                } else {
                    return uri.getRawPath();
                }
            }
        }

        public String getBody() {
            return body;
        }

        public void setBody(String body) {
            this.body = body;
        }

        public String getFragment() {
            return fragment;
        }

        public void setFragment(String fragment) {
            this.fragment = fragment;
        }

        public Map getHeaders() {
            return headers;
        }

        public void setHeaders(Map headers) {
            this.headers = headers;
        }

        public Map> getQueryString() {
            return queryString;
        }

        public void setQueryString(Map> queryString) {
            this.queryString = queryString;
        }

        @Override
        public String toString() {
            return "Request{" +
                    "key='" + key + '\'' +
                    ", secret='" + secret + '\'' +
                    ", method='" + method + '\'' +
                    ", url='" + uri.toString() + '\'' +
                    ", body='" + body + '\'' +
                    ", fragment='" + fragment + '\'' +
                    ", headers=" + headers +
                    ", queryString=" + queryString +
                    '}';
        }

        public static boolean isNotBlank(String str) {
            return str != null && str.trim().length() > 0;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy