Blame vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_credential_shared_key.go

Packit 63bb0d
package azblob
Packit 63bb0d
Packit 63bb0d
import (
Packit 63bb0d
	"bytes"
Packit 63bb0d
	"context"
Packit 63bb0d
	"crypto/hmac"
Packit 63bb0d
	"crypto/sha256"
Packit 63bb0d
	"encoding/base64"
Packit 63bb0d
	"errors"
Packit 63bb0d
	"net/http"
Packit 63bb0d
	"net/url"
Packit 63bb0d
	"sort"
Packit 63bb0d
	"strings"
Packit 63bb0d
	"time"
Packit 63bb0d
Packit 63bb0d
	"github.com/Azure/azure-pipeline-go/pipeline"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
// NewSharedKeyCredential creates an immutable SharedKeyCredential containing the
Packit 63bb0d
// storage account's name and either its primary or secondary key.
Packit 63bb0d
func NewSharedKeyCredential(accountName, accountKey string) (*SharedKeyCredential, error) {
Packit 63bb0d
	bytes, err := base64.StdEncoding.DecodeString(accountKey)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return &SharedKeyCredential{}, err
Packit 63bb0d
	}
Packit 63bb0d
	return &SharedKeyCredential{accountName: accountName, accountKey: bytes}, nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// SharedKeyCredential contains an account's name and its primary or secondary key.
Packit 63bb0d
// It is immutable making it shareable and goroutine-safe.
Packit 63bb0d
type SharedKeyCredential struct {
Packit 63bb0d
	// Only the NewSharedKeyCredential method should set these; all other methods should treat them as read-only
Packit 63bb0d
	accountName string
Packit 63bb0d
	accountKey  []byte
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// AccountName returns the Storage account's name.
Packit 63bb0d
func (f SharedKeyCredential) AccountName() string {
Packit 63bb0d
	return f.accountName
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (f SharedKeyCredential) getAccountKey() []byte {
Packit 63bb0d
	return f.accountKey
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// noop function to satisfy StorageAccountCredential interface
Packit 63bb0d
func (f SharedKeyCredential) getUDKParams() *UserDelegationKey {
Packit 63bb0d
	return nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// New creates a credential policy object.
Packit 63bb0d
func (f *SharedKeyCredential) New(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.Policy {
Packit 63bb0d
	return pipeline.PolicyFunc(func(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
Packit 63bb0d
		// Add a x-ms-date header if it doesn't already exist
Packit 63bb0d
		if d := request.Header.Get(headerXmsDate); d == "" {
Packit 63bb0d
			request.Header[headerXmsDate] = []string{time.Now().UTC().Format(http.TimeFormat)}
Packit 63bb0d
		}
Packit 63bb0d
		stringToSign, err := f.buildStringToSign(request)
Packit 63bb0d
		if err != nil {
Packit 63bb0d
			return nil, err
Packit 63bb0d
		}
Packit 63bb0d
		signature := f.ComputeHMACSHA256(stringToSign)
Packit 63bb0d
		authHeader := strings.Join([]string{"SharedKey ", f.accountName, ":", signature}, "")
Packit 63bb0d
		request.Header[headerAuthorization] = []string{authHeader}
Packit 63bb0d
Packit 63bb0d
		response, err := next.Do(ctx, request)
Packit 63bb0d
		if err != nil && response != nil && response.Response() != nil && response.Response().StatusCode == http.StatusForbidden {
Packit 63bb0d
			// Service failed to authenticate request, log it
Packit 63bb0d
			po.Log(pipeline.LogError, "===== HTTP Forbidden status, String-to-Sign:\n"+stringToSign+"\n===============================\n")
Packit 63bb0d
		}
Packit 63bb0d
		return response, err
Packit 63bb0d
	})
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// credentialMarker is a package-internal method that exists just to satisfy the Credential interface.
Packit 63bb0d
func (*SharedKeyCredential) credentialMarker() {}
Packit 63bb0d
Packit 63bb0d
// Constants ensuring that header names are correctly spelled and consistently cased.
Packit 63bb0d
const (
Packit 63bb0d
	headerAuthorization      = "Authorization"
Packit 63bb0d
	headerCacheControl       = "Cache-Control"
Packit 63bb0d
	headerContentEncoding    = "Content-Encoding"
Packit 63bb0d
	headerContentDisposition = "Content-Disposition"
Packit 63bb0d
	headerContentLanguage    = "Content-Language"
Packit 63bb0d
	headerContentLength      = "Content-Length"
Packit 63bb0d
	headerContentMD5         = "Content-MD5"
Packit 63bb0d
	headerContentType        = "Content-Type"
Packit 63bb0d
	headerDate               = "Date"
Packit 63bb0d
	headerIfMatch            = "If-Match"
Packit 63bb0d
	headerIfModifiedSince    = "If-Modified-Since"
Packit 63bb0d
	headerIfNoneMatch        = "If-None-Match"
Packit 63bb0d
	headerIfUnmodifiedSince  = "If-Unmodified-Since"
Packit 63bb0d
	headerRange              = "Range"
Packit 63bb0d
	headerUserAgent          = "User-Agent"
Packit 63bb0d
	headerXmsDate            = "x-ms-date"
Packit 63bb0d
	headerXmsVersion         = "x-ms-version"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
// ComputeHMACSHA256 generates a hash signature for an HTTP request or for a SAS.
Packit 63bb0d
func (f SharedKeyCredential) ComputeHMACSHA256(message string) (base64String string) {
Packit 63bb0d
	h := hmac.New(sha256.New, f.accountKey)
Packit 63bb0d
	h.Write([]byte(message))
Packit 63bb0d
	return base64.StdEncoding.EncodeToString(h.Sum(nil))
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (f *SharedKeyCredential) buildStringToSign(request pipeline.Request) (string, error) {
Packit 63bb0d
	// https://docs.microsoft.com/en-us/rest/api/storageservices/authentication-for-the-azure-storage-services
Packit 63bb0d
	headers := request.Header
Packit 63bb0d
	contentLength := headers.Get(headerContentLength)
Packit 63bb0d
	if contentLength == "0" {
Packit 63bb0d
		contentLength = ""
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	canonicalizedResource, err := f.buildCanonicalizedResource(request.URL)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return "", err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	stringToSign := strings.Join([]string{
Packit 63bb0d
		request.Method,
Packit 63bb0d
		headers.Get(headerContentEncoding),
Packit 63bb0d
		headers.Get(headerContentLanguage),
Packit 63bb0d
		contentLength,
Packit 63bb0d
		headers.Get(headerContentMD5),
Packit 63bb0d
		headers.Get(headerContentType),
Packit 63bb0d
		"", // Empty date because x-ms-date is expected (as per web page above)
Packit 63bb0d
		headers.Get(headerIfModifiedSince),
Packit 63bb0d
		headers.Get(headerIfMatch),
Packit 63bb0d
		headers.Get(headerIfNoneMatch),
Packit 63bb0d
		headers.Get(headerIfUnmodifiedSince),
Packit 63bb0d
		headers.Get(headerRange),
Packit 63bb0d
		buildCanonicalizedHeader(headers),
Packit 63bb0d
		canonicalizedResource,
Packit 63bb0d
	}, "\n")
Packit 63bb0d
	return stringToSign, nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func buildCanonicalizedHeader(headers http.Header) string {
Packit 63bb0d
	cm := map[string][]string{}
Packit 63bb0d
	for k, v := range headers {
Packit 63bb0d
		headerName := strings.TrimSpace(strings.ToLower(k))
Packit 63bb0d
		if strings.HasPrefix(headerName, "x-ms-") {
Packit 63bb0d
			cm[headerName] = v // NOTE: the value must not have any whitespace around it.
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	if len(cm) == 0 {
Packit 63bb0d
		return ""
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	keys := make([]string, 0, len(cm))
Packit 63bb0d
	for key := range cm {
Packit 63bb0d
		keys = append(keys, key)
Packit 63bb0d
	}
Packit 63bb0d
	sort.Strings(keys)
Packit 63bb0d
	ch := bytes.NewBufferString("")
Packit 63bb0d
	for i, key := range keys {
Packit 63bb0d
		if i > 0 {
Packit 63bb0d
			ch.WriteRune('\n')
Packit 63bb0d
		}
Packit 63bb0d
		ch.WriteString(key)
Packit 63bb0d
		ch.WriteRune(':')
Packit 63bb0d
		ch.WriteString(strings.Join(cm[key], ","))
Packit 63bb0d
	}
Packit 63bb0d
	return string(ch.Bytes())
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (f *SharedKeyCredential) buildCanonicalizedResource(u *url.URL) (string, error) {
Packit 63bb0d
	// https://docs.microsoft.com/en-us/rest/api/storageservices/authentication-for-the-azure-storage-services
Packit 63bb0d
	cr := bytes.NewBufferString("/")
Packit 63bb0d
	cr.WriteString(f.accountName)
Packit 63bb0d
Packit 63bb0d
	if len(u.Path) > 0 {
Packit 63bb0d
		// Any portion of the CanonicalizedResource string that is derived from
Packit 63bb0d
		// the resource's URI should be encoded exactly as it is in the URI.
Packit 63bb0d
		// -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx
Packit 63bb0d
		cr.WriteString(u.EscapedPath())
Packit 63bb0d
	} else {
Packit 63bb0d
		// a slash is required to indicate the root path
Packit 63bb0d
		cr.WriteString("/")
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// params is a map[string][]string; param name is key; params values is []string
Packit 63bb0d
	params, err := url.ParseQuery(u.RawQuery) // Returns URL decoded values
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return "", errors.New("parsing query parameters must succeed, otherwise there might be serious problems in the SDK/generated code")
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	if len(params) > 0 { // There is at least 1 query parameter
Packit 63bb0d
		paramNames := []string{} // We use this to sort the parameter key names
Packit 63bb0d
		for paramName := range params {
Packit 63bb0d
			paramNames = append(paramNames, paramName) // paramNames must be lowercase
Packit 63bb0d
		}
Packit 63bb0d
		sort.Strings(paramNames)
Packit 63bb0d
Packit 63bb0d
		for _, paramName := range paramNames {
Packit 63bb0d
			paramValues := params[paramName]
Packit 63bb0d
			sort.Strings(paramValues)
Packit 63bb0d
Packit 63bb0d
			// Join the sorted key values separated by ','
Packit 63bb0d
			// Then prepend "keyName:"; then add this string to the buffer
Packit 63bb0d
			cr.WriteString("\n" + paramName + ":" + strings.Join(paramValues, ","))
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	return string(cr.Bytes()), nil
Packit 63bb0d
}