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