Upstream versity/versitygw state of the touched files before 1a2bd1ad85
This commit is contained in:
1 parent
cd3095d5b7
commit
1b64a938ab
4 files changed
+1230
No files matched your search
@@ -0,0 +1,247 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sigv4auth
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
AlgorithmHMACSHA256 = "AWS4-HMAC-SHA256"
|
||||
Terminal = "aws4_request"
|
||||
ServiceS3 = "s3"
|
||||
ServiceIAM = "iam"
|
||||
ServiceSTS = "sts"
|
||||
|
||||
ISO8601Format = "20060102T150405Z"
|
||||
YYYYMMDD = "20060102"
|
||||
|
||||
// HeaderSecurityToken is the header a temporary credential's
|
||||
// SessionToken is presented in, matching AWS's X-Amz-Security-Token.
|
||||
HeaderSecurityToken = "X-Amz-Security-Token"
|
||||
|
||||
// TempAccessKeyIDPrefix marks temporary credentials minted by
|
||||
// AssumeRoleWithWebIdentity, matching AWS's ASIA… convention that
|
||||
// distinguishes them from long-term AKIA… access keys.
|
||||
TempAccessKeyIDPrefix = "ASIA"
|
||||
)
|
||||
|
||||
// IsTempAccessKeyID reports whether accessKeyID is a temporary (session)
|
||||
// credential rather than a long-term AKIA… access key.
|
||||
func IsTempAccessKeyID(accessKeyID string) bool {
|
||||
return strings.HasPrefix(accessKeyID, TempAccessKeyIDPrefix)
|
||||
}
|
||||
|
||||
type ParseErrorKind string
|
||||
|
||||
const (
|
||||
ErrInvalidAuthorizationHeader ParseErrorKind = "invalid_authorization_header"
|
||||
ErrUnsupportedAuthorizationVersion ParseErrorKind = "unsupported_authorization_version"
|
||||
ErrInvalidAuthorizationType ParseErrorKind = "invalid_authorization_type"
|
||||
ErrMissingComponents ParseErrorKind = "missing_components"
|
||||
ErrMissingCredential ParseErrorKind = "missing_credential"
|
||||
ErrMissingSignedHeaders ParseErrorKind = "missing_signed_headers"
|
||||
ErrMissingSignature ParseErrorKind = "missing_signature"
|
||||
ErrMalformedComponent ParseErrorKind = "malformed_component"
|
||||
ErrMalformedCredential ParseErrorKind = "malformed_credential"
|
||||
ErrIncorrectService ParseErrorKind = "incorrect_service"
|
||||
ErrIncorrectTerminal ParseErrorKind = "incorrect_terminal"
|
||||
ErrInvalidDateFormat ParseErrorKind = "invalid_date_format"
|
||||
)
|
||||
|
||||
type ParseError struct {
|
||||
Kind ParseErrorKind
|
||||
Input string
|
||||
Value string
|
||||
Expected string
|
||||
Actual string
|
||||
}
|
||||
|
||||
func (e *ParseError) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
switch e.Kind {
|
||||
case ErrIncorrectService, ErrIncorrectTerminal:
|
||||
return fmt.Sprintf("sigv4 %s: expected %q, got %q", e.Kind, e.Expected, e.Actual)
|
||||
case ErrInvalidAuthorizationType, ErrMalformedComponent, ErrInvalidDateFormat:
|
||||
return fmt.Sprintf("sigv4 %s: %q", e.Kind, e.Value)
|
||||
default:
|
||||
return string(e.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
// AuthData is the parsed authorization data from an AWS SigV4 Authorization header.
|
||||
type AuthData struct {
|
||||
Algorithm string
|
||||
Access string
|
||||
Region string
|
||||
Service string
|
||||
SignedHeaders string
|
||||
Signature string
|
||||
Date string
|
||||
}
|
||||
|
||||
type CredentialsScope struct {
|
||||
Access string
|
||||
Date string
|
||||
Region string
|
||||
Service string
|
||||
}
|
||||
|
||||
// HexBytes returns the hex byte representation used by AWS-style diagnostic
|
||||
// signature mismatch errors.
|
||||
func HexBytes(s string) string {
|
||||
b := []byte(s)
|
||||
|
||||
parts := make([]string, len(b))
|
||||
for i, v := range b {
|
||||
parts[i] = fmt.Sprintf("%02x", v)
|
||||
}
|
||||
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func PayloadSHA256Hex(payload []byte) string {
|
||||
hashedPayload := sha256.Sum256(payload)
|
||||
return hex.EncodeToString(hashedPayload[:])
|
||||
}
|
||||
|
||||
// ParseAuthorization parses and validates an AWS SigV4 Authorization header.
|
||||
// The credential scope service must match expectedService.
|
||||
func ParseAuthorization(authorization, expectedService string) (AuthData, error) {
|
||||
a := AuthData{}
|
||||
|
||||
authParts := strings.SplitN(authorization, " ", 2)
|
||||
for i, el := range authParts {
|
||||
if strings.Contains(el, " ") {
|
||||
authParts[i] = removeSpace(el)
|
||||
}
|
||||
}
|
||||
|
||||
if len(authParts) < 2 {
|
||||
return a, &ParseError{Kind: ErrInvalidAuthorizationHeader, Input: authorization}
|
||||
}
|
||||
|
||||
algo := authParts[0]
|
||||
if algo == "AWS" {
|
||||
return a, &ParseError{Kind: ErrUnsupportedAuthorizationVersion, Value: algo}
|
||||
}
|
||||
if algo != AlgorithmHMACSHA256 {
|
||||
return a, &ParseError{Kind: ErrInvalidAuthorizationType, Value: algo}
|
||||
}
|
||||
|
||||
kvPairs := strings.Split(authParts[1], ",")
|
||||
if len(kvPairs) != 3 {
|
||||
return a, &ParseError{Kind: ErrMissingComponents, Input: authorization}
|
||||
}
|
||||
|
||||
var access, region, service, signedHeaders, signature, date string
|
||||
for i, kv := range kvPairs {
|
||||
keyValue := strings.Split(kv, "=")
|
||||
if len(keyValue) != 2 {
|
||||
return a, &ParseError{Kind: ErrMalformedComponent, Value: kv}
|
||||
}
|
||||
key, value := keyValue[0], keyValue[1]
|
||||
switch i {
|
||||
case 0:
|
||||
if key != "Credential" {
|
||||
return a, &ParseError{Kind: ErrMissingCredential}
|
||||
}
|
||||
case 1:
|
||||
if key != "SignedHeaders" {
|
||||
return a, &ParseError{Kind: ErrMissingSignedHeaders}
|
||||
}
|
||||
case 2:
|
||||
if key != "Signature" {
|
||||
return a, &ParseError{Kind: ErrMissingSignature}
|
||||
}
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "Credential":
|
||||
creds, err := ParseCredentials(value, expectedService)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
access = creds.Access
|
||||
date = creds.Date
|
||||
region = creds.Region
|
||||
service = creds.Service
|
||||
case "SignedHeaders":
|
||||
signedHeaders = value
|
||||
case "Signature":
|
||||
signature = value
|
||||
}
|
||||
}
|
||||
|
||||
return AuthData{
|
||||
Algorithm: algo,
|
||||
Access: access,
|
||||
Region: region,
|
||||
Service: service,
|
||||
SignedHeaders: signedHeaders,
|
||||
Signature: signature,
|
||||
Date: date,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseCredentials(input, expectedService string) (*CredentialsScope, error) {
|
||||
creds := strings.Split(input, "/")
|
||||
if len(creds) != 5 {
|
||||
return nil, &ParseError{Kind: ErrMalformedCredential, Input: input}
|
||||
}
|
||||
if creds[3] != expectedService {
|
||||
return nil, &ParseError{
|
||||
Kind: ErrIncorrectService,
|
||||
Input: input,
|
||||
Expected: expectedService,
|
||||
Actual: creds[3],
|
||||
}
|
||||
}
|
||||
if creds[4] != Terminal {
|
||||
return nil, &ParseError{
|
||||
Kind: ErrIncorrectTerminal,
|
||||
Input: input,
|
||||
Expected: Terminal,
|
||||
Actual: creds[4],
|
||||
}
|
||||
}
|
||||
if _, err := time.Parse(YYYYMMDD, creds[1]); err != nil {
|
||||
return nil, &ParseError{Kind: ErrInvalidDateFormat, Input: input, Value: creds[1]}
|
||||
}
|
||||
|
||||
return &CredentialsScope{
|
||||
Access: creds[0],
|
||||
Date: creds[1],
|
||||
Region: creds[2],
|
||||
Service: creds[3],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func removeSpace(str string) string {
|
||||
var b strings.Builder
|
||||
b.Grow(len(str))
|
||||
for _, ch := range str {
|
||||
if !unicode.IsSpace(ch) {
|
||||
b.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
Reference in new issue
Block a user