signer.go raw

   1  /*
   2   * Copyright 2017 Baidu, Inc.
   3   *
   4   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
   5   * except in compliance with the License. You may obtain a copy of the License at
   6   *
   7   * http://www.apache.org/licenses/LICENSE-2.0
   8   *
   9   * Unless required by applicable law or agreed to in writing, software distributed under the
  10   * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  11   * either express or implied. See the License for the specific language governing permissions
  12   * and limitations under the License.
  13   */
  14  
  15  // signer.go - implement the specific sign algorithm of BCE V1 protocol
  16  
  17  package auth
  18  
  19  import (
  20  	"fmt"
  21  	"sort"
  22  	"strings"
  23  
  24  	"github.com/baidubce/bce-sdk-go/http"
  25  	"github.com/baidubce/bce-sdk-go/util"
  26  	"github.com/baidubce/bce-sdk-go/util/log"
  27  )
  28  
  29  var (
  30  	BCE_AUTH_VERSION        = "bce-auth-v1"
  31  	SIGN_JOINER             = "\n"
  32  	SIGN_HEADER_JOINER      = ";"
  33  	DEFAULT_EXPIRE_SECONDS  = 1800
  34  	DEFAULT_HEADERS_TO_SIGN = map[string]struct{}{
  35  		strings.ToLower(http.HOST):           {},
  36  		strings.ToLower(http.CONTENT_LENGTH): {},
  37  		strings.ToLower(http.CONTENT_TYPE):   {},
  38  		strings.ToLower(http.CONTENT_MD5):    {},
  39  	}
  40  )
  41  
  42  // Signer abstracts the entity that implements the `Sign` method
  43  type Signer interface {
  44  	// Sign the given Request with the Credentials and SignOptions
  45  	Sign(*http.Request, *BceCredentials, *SignOptions)
  46  }
  47  
  48  // SignOptions defines the data structure used by Signer
  49  type SignOptions struct {
  50  	HeadersToSign map[string]struct{}
  51  	Timestamp     int64
  52  	ExpireSeconds int
  53  }
  54  
  55  func (opt *SignOptions) String() string {
  56  	return fmt.Sprintf(`SignOptions [
  57          HeadersToSign=%s;
  58          Timestamp=%d;
  59          ExpireSeconds=%d
  60      ]`, opt.HeadersToSign, opt.Timestamp, opt.ExpireSeconds)
  61  }
  62  
  63  // BceV1Signer implements the v1 sign algorithm
  64  type BceV1Signer struct{}
  65  
  66  // Sign - generate the authorization string from the BceCredentials and SignOptions
  67  //
  68  // PARAMS:
  69  //   - req: *http.Request for this sign
  70  //   - cred: *BceCredentials to access the serice
  71  //   - opt: *SignOptions for this sign algorithm
  72  func (b *BceV1Signer) Sign(req *http.Request, cred *BceCredentials, opt *SignOptions) {
  73  	if req == nil {
  74  		log.Fatal("request should not be null for sign")
  75  		return
  76  	}
  77  	if cred == nil {
  78  		log.Fatal("credentials should not be null for sign")
  79  		return
  80  	}
  81  
  82  	// Prepare parameters
  83  	accessKeyId := cred.AccessKeyId
  84  	secretAccessKey := cred.SecretAccessKey
  85  	signDate := util.FormatISO8601Date(util.NowUTCSeconds())
  86  	// Modify the sign time if it is not the default value but specified by client
  87  	if opt.Timestamp != 0 {
  88  		signDate = util.FormatISO8601Date(opt.Timestamp)
  89  	}
  90  
  91  	// Set security token if using session credentials and session token not in param
  92  	if len(cred.SessionToken) != 0 && req.Param(http.BCE_SECURITY_TOKEN) == "" {
  93  		req.SetHeader(http.BCE_SECURITY_TOKEN, cred.SessionToken)
  94  	}
  95  
  96  	// Prepare the canonical request components
  97  	signKeyInfo := fmt.Sprintf("%s/%s/%s/%d",
  98  		BCE_AUTH_VERSION,
  99  		accessKeyId,
 100  		signDate,
 101  		opt.ExpireSeconds)
 102  	signKey := util.HmacSha256Hex(secretAccessKey, signKeyInfo)
 103  	canonicalUri := getCanonicalURIPath(req.Uri())
 104  	canonicalQueryString := getCanonicalQueryString(req.Params())
 105  	canonicalHeaders, signedHeadersArr := getCanonicalHeaders(req.Headers(), opt.HeadersToSign)
 106  
 107  	// Generate signed headers string
 108  	signedHeaders := ""
 109  	if len(signedHeadersArr) > 0 {
 110  		sort.Strings(signedHeadersArr)
 111  		signedHeaders = strings.Join(signedHeadersArr, SIGN_HEADER_JOINER)
 112  	}
 113  
 114  	// Generate signature
 115  	canonicalParts := []string{req.Method(), canonicalUri, canonicalQueryString, canonicalHeaders}
 116  	canonicalReq := strings.Join(canonicalParts, SIGN_JOINER)
 117  	log.Debug("CanonicalRequest data:\n" + canonicalReq)
 118  	signature := util.HmacSha256Hex(signKey, canonicalReq)
 119  
 120  	// Generate auth string and add to the reqeust header
 121  	authStr := signKeyInfo + "/" + signedHeaders + "/" + signature
 122  	log.Info("Authorization=" + authStr)
 123  
 124  	req.SetHeader(http.AUTHORIZATION, authStr)
 125  }
 126  
 127  func getCanonicalURIPath(path string) string {
 128  	if len(path) == 0 {
 129  		return "/"
 130  	}
 131  	canonical_path := path
 132  	if strings.HasPrefix(path, "/") {
 133  		canonical_path = path[1:]
 134  	}
 135  	canonical_path = util.UriEncode(canonical_path, false)
 136  	return "/" + canonical_path
 137  }
 138  
 139  func getCanonicalQueryString(params map[string]string) string {
 140  	if len(params) == 0 {
 141  		return ""
 142  	}
 143  
 144  	result := make([]string, 0, len(params))
 145  	for k, v := range params {
 146  		if strings.ToLower(k) == strings.ToLower(http.AUTHORIZATION) {
 147  			continue
 148  		}
 149  		item := ""
 150  		if len(v) == 0 {
 151  			item = fmt.Sprintf("%s=", util.UriEncode(k, true))
 152  		} else {
 153  			item = fmt.Sprintf("%s=%s", util.UriEncode(k, true), util.UriEncode(v, true))
 154  		}
 155  		result = append(result, item)
 156  	}
 157  	sort.Strings(result)
 158  	return strings.Join(result, "&")
 159  }
 160  
 161  func getCanonicalHeaders(headers map[string]string,
 162  	headersToSign map[string]struct{}) (string, []string) {
 163  	canonicalHeaders := make([]string, 0, len(headers))
 164  	signHeaders := make([]string, 0, len(headersToSign))
 165  	for k, v := range headers {
 166  		headKey := strings.ToLower(k)
 167  		if headKey == strings.ToLower(http.AUTHORIZATION) {
 168  			continue
 169  		}
 170  		_, headExists := headersToSign[headKey]
 171  		if headExists ||
 172  			(strings.HasPrefix(headKey, http.BCE_PREFIX) &&
 173  				(headKey != http.BCE_REQUEST_ID)) {
 174  
 175  			headVal := strings.TrimSpace(v)
 176  			encoded := util.UriEncode(headKey, true) + ":" + util.UriEncode(headVal, true)
 177  			canonicalHeaders = append(canonicalHeaders, encoded)
 178  			signHeaders = append(signHeaders, headKey)
 179  		}
 180  	}
 181  	sort.Strings(canonicalHeaders)
 182  	sort.Strings(signHeaders)
 183  	return strings.Join(canonicalHeaders, SIGN_JOINER), signHeaders
 184  }
 185