1// Package storage provides clients for Microsoft Azure Storage Services. 2package storage 3 4// Copyright 2017 Microsoft Corporation 5// 6// Licensed under the Apache License, Version 2.0 (the "License"); 7// you may not use this file except in compliance with the License. 8// You may obtain a copy of the License at 9// 10// http://www.apache.org/licenses/LICENSE-2.0 11// 12// Unless required by applicable law or agreed to in writing, software 13// distributed under the License is distributed on an "AS IS" BASIS, 14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15// See the License for the specific language governing permissions and 16// limitations under the License. 17 18import ( 19 "bytes" 20 "fmt" 21 "net/url" 22 "sort" 23 "strings" 24) 25 26// See: https://docs.microsoft.com/rest/api/storageservices/fileservices/authentication-for-the-azure-storage-services 27 28type authentication string 29 30const ( 31 sharedKey authentication = "sharedKey" 32 sharedKeyForTable authentication = "sharedKeyTable" 33 sharedKeyLite authentication = "sharedKeyLite" 34 sharedKeyLiteForTable authentication = "sharedKeyLiteTable" 35 36 // headers 37 headerAcceptCharset = "Accept-Charset" 38 headerAuthorization = "Authorization" 39 headerContentLength = "Content-Length" 40 headerDate = "Date" 41 headerXmsDate = "x-ms-date" 42 headerXmsVersion = "x-ms-version" 43 headerContentEncoding = "Content-Encoding" 44 headerContentLanguage = "Content-Language" 45 headerContentType = "Content-Type" 46 headerContentMD5 = "Content-MD5" 47 headerIfModifiedSince = "If-Modified-Since" 48 headerIfMatch = "If-Match" 49 headerIfNoneMatch = "If-None-Match" 50 headerIfUnmodifiedSince = "If-Unmodified-Since" 51 headerRange = "Range" 52 headerDataServiceVersion = "DataServiceVersion" 53 headerMaxDataServiceVersion = "MaxDataServiceVersion" 54 headerContentTransferEncoding = "Content-Transfer-Encoding" 55) 56 57func (c *Client) addAuthorizationHeader(verb, url string, headers map[string]string, auth authentication) (map[string]string, error) { 58 if !c.sasClient { 59 authHeader, err := c.getSharedKey(verb, url, headers, auth) 60 if err != nil { 61 return nil, err 62 } 63 headers[headerAuthorization] = authHeader 64 } 65 return headers, nil 66} 67 68func (c *Client) getSharedKey(verb, url string, headers map[string]string, auth authentication) (string, error) { 69 canRes, err := c.buildCanonicalizedResource(url, auth, false) 70 if err != nil { 71 return "", err 72 } 73 74 canString, err := buildCanonicalizedString(verb, headers, canRes, auth) 75 if err != nil { 76 return "", err 77 } 78 return c.createAuthorizationHeader(canString, auth), nil 79} 80 81func (c *Client) buildCanonicalizedResource(uri string, auth authentication, sas bool) (string, error) { 82 errMsg := "buildCanonicalizedResource error: %s" 83 u, err := url.Parse(uri) 84 if err != nil { 85 return "", fmt.Errorf(errMsg, err.Error()) 86 } 87 88 cr := bytes.NewBufferString("") 89 if c.accountName != StorageEmulatorAccountName || !sas { 90 cr.WriteString("/") 91 cr.WriteString(c.getCanonicalizedAccountName()) 92 } 93 94 if len(u.Path) > 0 { 95 // Any portion of the CanonicalizedResource string that is derived from 96 // the resource's URI should be encoded exactly as it is in the URI. 97 // -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx 98 cr.WriteString(u.EscapedPath()) 99 } 100 101 params, err := url.ParseQuery(u.RawQuery) 102 if err != nil { 103 return "", fmt.Errorf(errMsg, err.Error()) 104 } 105 106 // See https://github.com/Azure/azure-storage-net/blob/master/Lib/Common/Core/Util/AuthenticationUtility.cs#L277 107 if auth == sharedKey { 108 if len(params) > 0 { 109 cr.WriteString("\n") 110 111 keys := []string{} 112 for key := range params { 113 keys = append(keys, key) 114 } 115 sort.Strings(keys) 116 117 completeParams := []string{} 118 for _, key := range keys { 119 if len(params[key]) > 1 { 120 sort.Strings(params[key]) 121 } 122 123 completeParams = append(completeParams, fmt.Sprintf("%s:%s", key, strings.Join(params[key], ","))) 124 } 125 cr.WriteString(strings.Join(completeParams, "\n")) 126 } 127 } else { 128 // search for "comp" parameter, if exists then add it to canonicalizedresource 129 if v, ok := params["comp"]; ok { 130 cr.WriteString("?comp=" + v[0]) 131 } 132 } 133 134 return string(cr.Bytes()), nil 135} 136 137func (c *Client) getCanonicalizedAccountName() string { 138 // since we may be trying to access a secondary storage account, we need to 139 // remove the -secondary part of the storage name 140 return strings.TrimSuffix(c.accountName, "-secondary") 141} 142 143func buildCanonicalizedString(verb string, headers map[string]string, canonicalizedResource string, auth authentication) (string, error) { 144 contentLength := headers[headerContentLength] 145 if contentLength == "0" { 146 contentLength = "" 147 } 148 date := headers[headerDate] 149 if v, ok := headers[headerXmsDate]; ok { 150 if auth == sharedKey || auth == sharedKeyLite { 151 date = "" 152 } else { 153 date = v 154 } 155 } 156 var canString string 157 switch auth { 158 case sharedKey: 159 canString = strings.Join([]string{ 160 verb, 161 headers[headerContentEncoding], 162 headers[headerContentLanguage], 163 contentLength, 164 headers[headerContentMD5], 165 headers[headerContentType], 166 date, 167 headers[headerIfModifiedSince], 168 headers[headerIfMatch], 169 headers[headerIfNoneMatch], 170 headers[headerIfUnmodifiedSince], 171 headers[headerRange], 172 buildCanonicalizedHeader(headers), 173 canonicalizedResource, 174 }, "\n") 175 case sharedKeyForTable: 176 canString = strings.Join([]string{ 177 verb, 178 headers[headerContentMD5], 179 headers[headerContentType], 180 date, 181 canonicalizedResource, 182 }, "\n") 183 case sharedKeyLite: 184 canString = strings.Join([]string{ 185 verb, 186 headers[headerContentMD5], 187 headers[headerContentType], 188 date, 189 buildCanonicalizedHeader(headers), 190 canonicalizedResource, 191 }, "\n") 192 case sharedKeyLiteForTable: 193 canString = strings.Join([]string{ 194 date, 195 canonicalizedResource, 196 }, "\n") 197 default: 198 return "", fmt.Errorf("%s authentication is not supported yet", auth) 199 } 200 return canString, nil 201} 202 203func buildCanonicalizedHeader(headers map[string]string) string { 204 cm := make(map[string]string) 205 206 for k, v := range headers { 207 headerName := strings.TrimSpace(strings.ToLower(k)) 208 if strings.HasPrefix(headerName, "x-ms-") { 209 cm[headerName] = v 210 } 211 } 212 213 if len(cm) == 0 { 214 return "" 215 } 216 217 keys := []string{} 218 for key := range cm { 219 keys = append(keys, key) 220 } 221 222 sort.Strings(keys) 223 224 ch := bytes.NewBufferString("") 225 226 for _, key := range keys { 227 ch.WriteString(key) 228 ch.WriteRune(':') 229 ch.WriteString(cm[key]) 230 ch.WriteRune('\n') 231 } 232 233 return strings.TrimSuffix(string(ch.Bytes()), "\n") 234} 235 236func (c *Client) createAuthorizationHeader(canonicalizedString string, auth authentication) string { 237 signature := c.computeHmac256(canonicalizedString) 238 var key string 239 switch auth { 240 case sharedKey, sharedKeyForTable: 241 key = "SharedKey" 242 case sharedKeyLite, sharedKeyLiteForTable: 243 key = "SharedKeyLite" 244 } 245 return fmt.Sprintf("%s %s:%s", key, c.getCanonicalizedAccountName(), signature) 246} 247