package v4a
import (
signerCrypto
v4Internal
)
const (
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 = v4Internal.EmptyStringSHA256
Version = "SigV4A"
)
var (
p256 elliptic.Curve
nMinusTwoP256 *big.Int
one = new(big.Int).SetInt64(1)
)
func () {
p256 = elliptic.P256()
nMinusTwoP256 = new(big.Int).SetBytes(p256.Params().N.Bytes())
nMinusTwoP256 = nMinusTwoP256.Sub(nMinusTwoP256, new(big.Int).SetInt64(2))
}
type SignerOptions struct {
Logger logging.Logger
LogSigning bool
DisableHeaderHoisting bool
DisableURIPathEscaping bool
}
type Signer struct {
options SignerOptions
}
func ( ...func(*SignerOptions)) *Signer {
:= SignerOptions{}
for , := range {
(&)
}
return &Signer{options: }
}
func (, string) (*ecdsa.PrivateKey, error) {
:= p256.Params()
:= .BitSize
:= 0x01
:= make([]byte, 1+len())
:= 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,
}
, := 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 string
DisableHeaderHoisting bool
DisableURIPathEscaping bool
}
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
}
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 , := 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(, )
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
}
:= strings.ToLower()
if , := []; {
[] = append([], ...)
continue
}
= append(, )
[] =
}
sort.Strings()
= strings.Join(, ";")
var strings.Builder
:= len()
const = ':'
for := 0; < ; ++ {
if [] == {
.WriteString()
.WriteRune()
.WriteString(v4Internal.StripExcessSpaces())
} else {
.WriteString([])
.WriteRune()
:= [[]]
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
}