package v4a

import (
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	

	signerCrypto 
	v4Internal 
	
	
)

const (
	// AmzRegionSetKey represents the region set header used for sigv4a
	AmzRegionSetKey     = "X-Amz-Region-Set"
	amzAlgorithmKey     = v4Internal.AmzAlgorithmKey
	amzSecurityTokenKey = v4Internal.AmzSecurityTokenKey
	amzDateKey          = v4Internal.AmzDateKey
	amzCredentialKey    = v4Internal.AmzCredentialKey
	amzSignedHeadersKey = v4Internal.AmzSignedHeadersKey
	authorizationHeader = "Authorization"

	signingAlgorithm = "AWS4-ECDSA-P256-SHA256"

	timeFormat      = "20060102T150405Z"
	shortTimeFormat = "20060102"

	// EmptyStringSHA256 is a hex encoded SHA-256 hash of an empty string
	EmptyStringSHA256 = v4Internal.EmptyStringSHA256

	// Version of signing v4a
	Version = "SigV4A"
)

var (
	p256          elliptic.Curve
	nMinusTwoP256 *big.Int

	one = new(big.Int).SetInt64(1)
)

func () {
	// Ensure the elliptic curve parameters are initialized on package import rather then on first usage
	p256 = elliptic.P256()

	nMinusTwoP256 = new(big.Int).SetBytes(p256.Params().N.Bytes())
	nMinusTwoP256 = nMinusTwoP256.Sub(nMinusTwoP256, new(big.Int).SetInt64(2))
}

// SignerOptions is the SigV4a signing options for constructing a Signer.
type SignerOptions struct {
	Logger     logging.Logger
	LogSigning bool

	// Disables the Signer's moving HTTP header key/value pairs from the HTTP
	// request header to the request's query string. This is most commonly used
	// with pre-signed requests preventing headers from being added to the
	// request's query string.
	DisableHeaderHoisting bool

	// Disables the automatic escaping of the URI path of the request for the
	// siganture's canonical string's path. For services that do not need additional
	// escaping then use this to disable the signer escaping the path.
	//
	// S3 is an example of a service that does not need additional escaping.
	//
	// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
	DisableURIPathEscaping bool
}

// Signer is a SigV4a HTTP signing implementation
type Signer struct {
	options SignerOptions
}

// NewSigner constructs a SigV4a Signer.
func ( ...func(*SignerOptions)) *Signer {
	 := SignerOptions{}

	for ,  := range  {
		(&)
	}

	return &Signer{options: }
}

// deriveKeyFromAccessKeyPair derives a NIST P-256 PrivateKey from the given
// IAM AccessKey and SecretKey pair.
//
// Based on FIPS.186-4 Appendix B.4.2
func (,  string) (*ecdsa.PrivateKey, error) {
	 := p256.Params()
	 := .BitSize // Testing random candidates does not require an additional 64 bits
	 := 0x01

	 := make([]byte, 1+len()) // 1 byte counter + len(accessKey)
	 := bytes.NewBuffer()

	 := append([]byte("AWS4A"), []byte()...)

	 := new(big.Int)
	for {
		.Reset()
		.WriteString()
		.WriteByte(byte())

		,  := signerCrypto.HMACKeyDerivation(sha256.New, , , []byte(signingAlgorithm), .Bytes())
		if  != nil {
			return nil, 
		}

		// Check key first before calling SetBytes if key key is in fact a valid candidate.
		// This ensures the byte slice is the correct length (32-bytes) to compare in constant-time
		,  := signerCrypto.ConstantTimeByteCompare(, nMinusTwoP256.Bytes())
		if  != nil {
			return nil, 
		}
		if  == -1 {
			.SetBytes()
			break
		}

		++
		if  > 0xFF {
			return nil, fmt.Errorf("exhausted single byte external counter")
		}
	}
	 = .Add(, one)

	 := new(ecdsa.PrivateKey)
	.PublicKey.Curve = p256
	.D = 
	.PublicKey.X, .PublicKey.Y = p256.ScalarBaseMult(.Bytes())

	return , nil
}

type httpSigner struct {
	Request     *http.Request
	ServiceName string
	RegionSet   []string
	Time        time.Time
	Credentials Credentials
	IsPreSign   bool

	Logger logging.Logger
	Debug  bool

	// PayloadHash is the hex encoded SHA-256 hash of the request payload
	// If len(PayloadHash) == 0 the signer will attempt to send the request
	// as an unsigned payload. Note: Unsigned payloads only work for a subset of services.
	PayloadHash string

	DisableHeaderHoisting  bool
	DisableURIPathEscaping bool
}

// SignHTTP takes the provided http.Request, payload hash, service, regionSet, and time and signs using SigV4a.
// The passed in request will be modified in place.
func ( *Signer) ( context.Context,  Credentials,  *http.Request,  string,  string,  []string,  time.Time,  ...func(*SignerOptions)) error {
	 := .options
	for ,  := range  {
		(&)
	}

	 := &httpSigner{
		Request:                ,
		PayloadHash:            ,
		ServiceName:            ,
		RegionSet:              ,
		Credentials:            ,
		Time:                   .UTC(),
		DisableHeaderHoisting:  .DisableHeaderHoisting,
		DisableURIPathEscaping: .DisableURIPathEscaping,
	}

	,  := .Build()
	if  != nil {
		return 
	}

	logHTTPSigningInfo(, , )

	return nil
}

// PresignHTTP takes the provided http.Request, payload hash, service, regionSet, and time and presigns using SigV4a
// Returns the presigned URL along with the headers that were signed with the request.
//
// PresignHTTP will not set the expires time of the presigned request
// automatically. To specify the expire duration for a request add the
// "X-Amz-Expires" query parameter on the request with the value as the
// duration in seconds the presigned URL should be considered valid for. This
// parameter is not used by all AWS services, and is most notable used by
// Amazon S3 APIs.
func ( *Signer) ( context.Context,  Credentials,  *http.Request,  string,  string,  []string,  time.Time,  ...func(*SignerOptions)) ( string,  http.Header,  error) {
	 := .options
	for ,  := range  {
		(&)
	}

	 := &httpSigner{
		Request:                ,
		PayloadHash:            ,
		ServiceName:            ,
		RegionSet:              ,
		Credentials:            ,
		Time:                   .UTC(),
		IsPreSign:              true,
		DisableHeaderHoisting:  .DisableHeaderHoisting,
		DisableURIPathEscaping: .DisableURIPathEscaping,
	}

	,  := .Build()
	if  != nil {
		return "", nil, 
	}

	logHTTPSigningInfo(, , )

	 = make(http.Header)

	// For the signed headers we canonicalize the header keys in the returned map.
	// This avoids situations where can standard library double headers like host header. For example the standard
	// library will set the Host header, even if it is present in lower-case form.
	for ,  := range .SignedHeaders {
		 := textproto.CanonicalMIMEHeaderKey()
		[] = append([], ...)
	}

	return .Request.URL.String(), , nil
}

func ( *httpSigner) ( http.Header,  url.Values) {
	 := .Time.Format(timeFormat)

	if .IsPreSign {
		.Set(AmzRegionSetKey, strings.Join(.RegionSet, ","))
		.Set(amzDateKey, )
		.Set(amzAlgorithmKey, signingAlgorithm)
		if len(.Credentials.SessionToken) > 0 {
			.Set(amzSecurityTokenKey, .Credentials.SessionToken)
		}
		return
	}

	.Set(AmzRegionSetKey, strings.Join(.RegionSet, ","))
	.Set(amzDateKey, )
	if len(.Credentials.SessionToken) > 0 {
		.Set(amzSecurityTokenKey, .Credentials.SessionToken)
	}
}

func ( *httpSigner) () (signedRequest, error) {
	 := .Request

	 := .URL.Query()
	 := .Header

	.setRequiredSigningFields(, )

	// Sort Each Query Key's Values
	for  := range  {
		sort.Strings([])
	}

	v4Internal.SanitizeHostForHeader()

	 := .buildCredentialScope()
	 := .Credentials.Context + "/" + 
	if .IsPreSign {
		.Set(amzCredentialKey, )
	}

	 := 
	if .IsPreSign && !.DisableHeaderHoisting {
		 := url.Values{}
		,  = buildQuery(v4Internal.AllowedQueryHoisting, )
		for  := range  {
			[] = []
		}
	}

	 := .URL.Host
	if len(.Host) > 0 {
		 = .Host
	}

	, ,  := .buildCanonicalHeaders(, v4Internal.IgnoredHeaders, , .Request.ContentLength)

	if .IsPreSign {
		.Set(amzSignedHeadersKey, )
	}

	 := strings.Replace(.Encode(), "+", "%20", -1)

	 := v4Internal.GetURIPath(.URL)
	if !.DisableURIPathEscaping {
		 = httpbinding.EscapePath(, false)
	}

	 := .buildCanonicalString(
		.Method,
		,
		,
		,
		,
	)

	 := .buildStringToSign(, )
	,  := .buildSignature()
	if  != nil {
		return signedRequest{}, 
	}

	if .IsPreSign {
		 += "&X-Amz-Signature=" + 
	} else {
		[authorizationHeader] = append([authorizationHeader][:0], buildAuthorizationHeader(, , ))
	}

	.URL.RawQuery = 

	return signedRequest{
		Request:         ,
		SignedHeaders:   ,
		CanonicalString: ,
		StringToSign:    ,
		PreSigned:       .IsPreSign,
	}, nil
}

func (, ,  string) string {
	const  = "Credential="
	const  = "SignedHeaders="
	const  = "Signature="
	const  = ", "

	var  strings.Builder
	.Grow(len(signingAlgorithm) + 1 +
		len() + len() + len() +
		len() + len() + len() +
		len() + len(),
	)
	.WriteString(signingAlgorithm)
	.WriteRune(' ')
	.WriteString()
	.WriteString()
	.WriteString()
	.WriteString()
	.WriteString()
	.WriteString()
	.WriteString()
	.WriteString()
	return .String()
}

func ( *httpSigner) () string {
	return strings.Join([]string{
		.Time.Format(shortTimeFormat),
		.ServiceName,
		"aws4_request",
	}, "/")

}

func ( v4Internal.Rule,  http.Header) (url.Values, http.Header) {
	 := url.Values{}
	 := http.Header{}
	for ,  := range  {
		if .IsValid() {
			[] = 
		} else {
			[] = 
		}
	}

	return , 
}

func ( *httpSigner) ( string,  v4Internal.Rule,  http.Header,  int64) ( http.Header, ,  string) {
	 = make(http.Header)

	var  []string
	const  = "host"
	 = append(, )
	[] = append([], )

	if  > 0 {
		const  = "content-length"
		 = append(, )
		[] = append([], strconv.FormatInt(, 10))
	}

	for ,  := range  {
		if !.IsValid() {
			continue // ignored header
		}

		 := strings.ToLower()
		if ,  := [];  {
			// include additional values
			[] = append([], ...)
			continue
		}

		 = append(, )
		[] = 
	}
	sort.Strings()

	 = strings.Join(, ";")

	var  strings.Builder
	 := len()
	const  = ':'
	for  := 0;  < ; ++ {
		if [] ==  {
			.WriteString()
			.WriteRune()
			.WriteString(v4Internal.StripExcessSpaces())
		} else {
			.WriteString([])
			.WriteRune()
			// Trim out leading, trailing, and dedup inner spaces from signed header values.
			 := [[]]
			for ,  := range  {
				 := strings.TrimSpace(v4Internal.StripExcessSpaces())
				.WriteString()
				if  < len()-1 {
					.WriteRune(',')
				}
			}
		}
		.WriteRune('\n')
	}
	 = .String()

	return , , 
}

func ( *httpSigner) (, , , ,  string) string {
	return strings.Join([]string{
		,
		,
		,
		,
		,
		.PayloadHash,
	}, "\n")
}

func ( *httpSigner) (,  string) string {
	return strings.Join([]string{
		signingAlgorithm,
		.Time.Format(timeFormat),
		,
		hex.EncodeToString(makeHash(sha256.New(), []byte())),
	}, "\n")
}

func ( hash.Hash,  []byte) []byte {
	.Reset()
	.Write()
	return .Sum(nil)
}

func ( *httpSigner) ( string) (string, error) {
	,  := .Credentials.PrivateKey.Sign(rand.Reader, makeHash(sha256.New(), []byte()), crypto.SHA256)
	if  != nil {
		return "", 
	}
	return hex.EncodeToString(), nil
}

const logSignInfoMsg = `Request Signature:
---[ CANONICAL STRING  ]-----------------------------
%s
---[ STRING TO SIGN ]--------------------------------
%s%s
-----------------------------------------------------`
const logSignedURLMsg = `
---[ SIGNED URL ]------------------------------------
%s`

func ( context.Context,  SignerOptions,  signedRequest) {
	if !.LogSigning {
		return
	}
	 := ""
	if .PreSigned {
		 = fmt.Sprintf(logSignedURLMsg, .Request.URL.String())
	}
	 := logging.WithContext(, .Logger)
	.Logf(logging.Debug, logSignInfoMsg, .CanonicalString, .StringToSign, )
}

type signedRequest struct {
	Request         *http.Request
	SignedHeaders   http.Header
	CanonicalString string
	StringToSign    string
	PreSigned       bool
}