gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

request-signature-v2.go (9033B)


      1 /*
      2  * MinIO Go Library for Amazon S3 Compatible Cloud Storage
      3  * Copyright 2015-2017 MinIO, Inc.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package signer
     19 
     20 import (
     21 	"bytes"
     22 	"crypto/hmac"
     23 	"crypto/sha1"
     24 	"encoding/base64"
     25 	"fmt"
     26 	"net/http"
     27 	"net/url"
     28 	"sort"
     29 	"strconv"
     30 	"strings"
     31 	"time"
     32 
     33 	"github.com/minio/minio-go/v7/pkg/s3utils"
     34 )
     35 
     36 // Signature and API related constants.
     37 const (
     38 	signV2Algorithm = "AWS"
     39 )
     40 
     41 // Encode input URL path to URL encoded path.
     42 func encodeURL2Path(req *http.Request, virtualHost bool) (path string) {
     43 	if virtualHost {
     44 		reqHost := getHostAddr(req)
     45 		dotPos := strings.Index(reqHost, ".")
     46 		if dotPos > -1 {
     47 			bucketName := reqHost[:dotPos]
     48 			path = "/" + bucketName
     49 			path += req.URL.Path
     50 			path = s3utils.EncodePath(path)
     51 			return
     52 		}
     53 	}
     54 	path = s3utils.EncodePath(req.URL.Path)
     55 	return
     56 }
     57 
     58 // PreSignV2 - presign the request in following style.
     59 // https://${S3_BUCKET}.s3.amazonaws.com/${S3_OBJECT}?AWSAccessKeyId=${S3_ACCESS_KEY}&Expires=${TIMESTAMP}&Signature=${SIGNATURE}.
     60 func PreSignV2(req http.Request, accessKeyID, secretAccessKey string, expires int64, virtualHost bool) *http.Request {
     61 	// Presign is not needed for anonymous credentials.
     62 	if accessKeyID == "" || secretAccessKey == "" {
     63 		return &req
     64 	}
     65 
     66 	d := time.Now().UTC()
     67 	// Find epoch expires when the request will expire.
     68 	epochExpires := d.Unix() + expires
     69 
     70 	// Add expires header if not present.
     71 	if expiresStr := req.Header.Get("Expires"); expiresStr == "" {
     72 		req.Header.Set("Expires", strconv.FormatInt(epochExpires, 10))
     73 	}
     74 
     75 	// Get presigned string to sign.
     76 	stringToSign := preStringToSignV2(req, virtualHost)
     77 	hm := hmac.New(sha1.New, []byte(secretAccessKey))
     78 	hm.Write([]byte(stringToSign))
     79 
     80 	// Calculate signature.
     81 	signature := base64.StdEncoding.EncodeToString(hm.Sum(nil))
     82 
     83 	query := req.URL.Query()
     84 	// Handle specially for Google Cloud Storage.
     85 	if strings.Contains(getHostAddr(&req), ".storage.googleapis.com") {
     86 		query.Set("GoogleAccessId", accessKeyID)
     87 	} else {
     88 		query.Set("AWSAccessKeyId", accessKeyID)
     89 	}
     90 
     91 	// Fill in Expires for presigned query.
     92 	query.Set("Expires", strconv.FormatInt(epochExpires, 10))
     93 
     94 	// Encode query and save.
     95 	req.URL.RawQuery = s3utils.QueryEncode(query)
     96 
     97 	// Save signature finally.
     98 	req.URL.RawQuery += "&Signature=" + s3utils.EncodePath(signature)
     99 
    100 	// Return.
    101 	return &req
    102 }
    103 
    104 // PostPresignSignatureV2 - presigned signature for PostPolicy
    105 // request.
    106 func PostPresignSignatureV2(policyBase64, secretAccessKey string) string {
    107 	hm := hmac.New(sha1.New, []byte(secretAccessKey))
    108 	hm.Write([]byte(policyBase64))
    109 	signature := base64.StdEncoding.EncodeToString(hm.Sum(nil))
    110 	return signature
    111 }
    112 
    113 // Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;
    114 // Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );
    115 //
    116 // StringToSign = HTTP-Verb + "\n" +
    117 //  	Content-Md5 + "\n" +
    118 //  	Content-Type + "\n" +
    119 //  	Date + "\n" +
    120 //  	CanonicalizedProtocolHeaders +
    121 //  	CanonicalizedResource;
    122 //
    123 // CanonicalizedResource = [ "/" + Bucket ] +
    124 //  	<HTTP-Request-URI, from the protocol name up to the query string> +
    125 //  	[ subresource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
    126 //
    127 // CanonicalizedProtocolHeaders = <described below>
    128 
    129 // SignV2 sign the request before Do() (AWS Signature Version 2).
    130 func SignV2(req http.Request, accessKeyID, secretAccessKey string, virtualHost bool) *http.Request {
    131 	// Signature calculation is not needed for anonymous credentials.
    132 	if accessKeyID == "" || secretAccessKey == "" {
    133 		return &req
    134 	}
    135 
    136 	// Initial time.
    137 	d := time.Now().UTC()
    138 
    139 	// Add date if not present.
    140 	if date := req.Header.Get("Date"); date == "" {
    141 		req.Header.Set("Date", d.Format(http.TimeFormat))
    142 	}
    143 
    144 	// Calculate HMAC for secretAccessKey.
    145 	stringToSign := stringToSignV2(req, virtualHost)
    146 	hm := hmac.New(sha1.New, []byte(secretAccessKey))
    147 	hm.Write([]byte(stringToSign))
    148 
    149 	// Prepare auth header.
    150 	authHeader := new(bytes.Buffer)
    151 	authHeader.WriteString(fmt.Sprintf("%s %s:", signV2Algorithm, accessKeyID))
    152 	encoder := base64.NewEncoder(base64.StdEncoding, authHeader)
    153 	encoder.Write(hm.Sum(nil))
    154 	encoder.Close()
    155 
    156 	// Set Authorization header.
    157 	req.Header.Set("Authorization", authHeader.String())
    158 
    159 	return &req
    160 }
    161 
    162 // From the Amazon docs:
    163 //
    164 // StringToSign = HTTP-Verb + "\n" +
    165 //
    166 //	Content-Md5 + "\n" +
    167 //	Content-Type + "\n" +
    168 //	Expires + "\n" +
    169 //	CanonicalizedProtocolHeaders +
    170 //	CanonicalizedResource;
    171 func preStringToSignV2(req http.Request, virtualHost bool) string {
    172 	buf := new(bytes.Buffer)
    173 	// Write standard headers.
    174 	writePreSignV2Headers(buf, req)
    175 	// Write canonicalized protocol headers if any.
    176 	writeCanonicalizedHeaders(buf, req)
    177 	// Write canonicalized Query resources if any.
    178 	writeCanonicalizedResource(buf, req, virtualHost)
    179 	return buf.String()
    180 }
    181 
    182 // writePreSignV2Headers - write preSign v2 required headers.
    183 func writePreSignV2Headers(buf *bytes.Buffer, req http.Request) {
    184 	buf.WriteString(req.Method + "\n")
    185 	buf.WriteString(req.Header.Get("Content-Md5") + "\n")
    186 	buf.WriteString(req.Header.Get("Content-Type") + "\n")
    187 	buf.WriteString(req.Header.Get("Expires") + "\n")
    188 }
    189 
    190 // From the Amazon docs:
    191 //
    192 // StringToSign = HTTP-Verb + "\n" +
    193 //
    194 //	Content-Md5 + "\n" +
    195 //	Content-Type + "\n" +
    196 //	Date + "\n" +
    197 //	CanonicalizedProtocolHeaders +
    198 //	CanonicalizedResource;
    199 func stringToSignV2(req http.Request, virtualHost bool) string {
    200 	buf := new(bytes.Buffer)
    201 	// Write standard headers.
    202 	writeSignV2Headers(buf, req)
    203 	// Write canonicalized protocol headers if any.
    204 	writeCanonicalizedHeaders(buf, req)
    205 	// Write canonicalized Query resources if any.
    206 	writeCanonicalizedResource(buf, req, virtualHost)
    207 	return buf.String()
    208 }
    209 
    210 // writeSignV2Headers - write signV2 required headers.
    211 func writeSignV2Headers(buf *bytes.Buffer, req http.Request) {
    212 	buf.WriteString(req.Method + "\n")
    213 	buf.WriteString(req.Header.Get("Content-Md5") + "\n")
    214 	buf.WriteString(req.Header.Get("Content-Type") + "\n")
    215 	buf.WriteString(req.Header.Get("Date") + "\n")
    216 }
    217 
    218 // writeCanonicalizedHeaders - write canonicalized headers.
    219 func writeCanonicalizedHeaders(buf *bytes.Buffer, req http.Request) {
    220 	var protoHeaders []string
    221 	vals := make(map[string][]string)
    222 	for k, vv := range req.Header {
    223 		// All the AMZ headers should be lowercase
    224 		lk := strings.ToLower(k)
    225 		if strings.HasPrefix(lk, "x-amz") {
    226 			protoHeaders = append(protoHeaders, lk)
    227 			vals[lk] = vv
    228 		}
    229 	}
    230 	sort.Strings(protoHeaders)
    231 	for _, k := range protoHeaders {
    232 		buf.WriteString(k)
    233 		buf.WriteByte(':')
    234 		for idx, v := range vals[k] {
    235 			if idx > 0 {
    236 				buf.WriteByte(',')
    237 			}
    238 			buf.WriteString(v)
    239 		}
    240 		buf.WriteByte('\n')
    241 	}
    242 }
    243 
    244 // AWS S3 Signature V2 calculation rule is give here:
    245 // http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationStringToSign
    246 
    247 // Whitelist resource list that will be used in query string for signature-V2 calculation.
    248 //
    249 // This list should be kept alphabetically sorted, do not hastily edit.
    250 var resourceList = []string{
    251 	"acl",
    252 	"cors",
    253 	"delete",
    254 	"encryption",
    255 	"legal-hold",
    256 	"lifecycle",
    257 	"location",
    258 	"logging",
    259 	"notification",
    260 	"partNumber",
    261 	"policy",
    262 	"replication",
    263 	"requestPayment",
    264 	"response-cache-control",
    265 	"response-content-disposition",
    266 	"response-content-encoding",
    267 	"response-content-language",
    268 	"response-content-type",
    269 	"response-expires",
    270 	"retention",
    271 	"select",
    272 	"select-type",
    273 	"tagging",
    274 	"torrent",
    275 	"uploadId",
    276 	"uploads",
    277 	"versionId",
    278 	"versioning",
    279 	"versions",
    280 	"website",
    281 }
    282 
    283 // From the Amazon docs:
    284 //
    285 // CanonicalizedResource = [ "/" + Bucket ] +
    286 //
    287 //	<HTTP-Request-URI, from the protocol name up to the query string> +
    288 //	[ sub-resource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
    289 func writeCanonicalizedResource(buf *bytes.Buffer, req http.Request, virtualHost bool) {
    290 	// Save request URL.
    291 	requestURL := req.URL
    292 	// Get encoded URL path.
    293 	buf.WriteString(encodeURL2Path(&req, virtualHost))
    294 	if requestURL.RawQuery != "" {
    295 		var n int
    296 		vals, _ := url.ParseQuery(requestURL.RawQuery)
    297 		// Verify if any sub resource queries are present, if yes
    298 		// canonicallize them.
    299 		for _, resource := range resourceList {
    300 			if vv, ok := vals[resource]; ok && len(vv) > 0 {
    301 				n++
    302 				// First element
    303 				switch n {
    304 				case 1:
    305 					buf.WriteByte('?')
    306 				// The rest
    307 				default:
    308 					buf.WriteByte('&')
    309 				}
    310 				buf.WriteString(resource)
    311 				// Request parameters
    312 				if len(vv[0]) > 0 {
    313 					buf.WriteByte('=')
    314 					buf.WriteString(vv[0])
    315 				}
    316 			}
    317 		}
    318 	}
    319 }