gohttpsig

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 27, 2026 License: MIT Imports: 15 Imported by: 0

README

gohttpsig

Go Reference Go Report Card License

English | 简体中文

A complete Go implementation of AWS Signature Version 4 for HTTP request signing and verification. This library enables both client-side request signing and server-side signature verification using AWS SigV4, making it easy to implement secure authentication for your HTTP APIs.

Features

  • Client-side signing - Sign outgoing HTTP requests with AWS SigV4 credentials
  • Server-side verification - Validate incoming signed requests for authentication
  • RFC 3986 compliant - Strict URI encoding per AWS SigV4 specification
  • Presigned URLs - Generate time-limited presigned URLs
  • Zero dependencies - Uses only Go standard library
  • Thread-safe - Safe for concurrent use
  • Comprehensive tests - Full test coverage including AWS compliance
  • Constant-time comparison - Prevents timing attacks during verification
  • Session token support - Works with temporary credentials

Installation

go get github.com/muleiwu/gohttpsig

Quick Start

Client: Signing Requests
package main

import (
    "context"
    "net/http"
    "github.com/muleiwu/gohttpsig"
)

func main() {
    // Create credentials
    creds := &gohttpsig.Credentials{
        AccessKeyID:     "AKIAIOSFODNN7EXAMPLE",
        SecretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    }

    // Create signer
    provider := gohttpsig.NewStaticCredentialsProvider(creds)
    signer := gohttpsig.NewSigner(provider)

    // Create and sign request
    req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
    signed, err := signer.Sign(context.Background(), req, "myservice", "us-east-1")
    if err != nil {
        panic(err)
    }

    // Send the signed request
    resp, err := http.DefaultClient.Do(signed.Request)
    // ... handle response
}
Server: Verifying Requests
package main

import (
    "context"
    "net/http"
    "github.com/muleiwu/gohttpsig"
)

func main() {
    // Create credential store
    store := gohttpsig.NewInMemoryCredentialStore()
    store.AddCredentials(&gohttpsig.Credentials{
        AccessKeyID:     "AKIAIOSFODNN7EXAMPLE",
        SecretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    })

    // Create verifier
    verifier := gohttpsig.NewVerifier(store)

    // Use in HTTP handler
    http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
        result, err := verifier.Verify(context.Background(), r)
        if err != nil || !result.Valid {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }

        // Request is authenticated
        w.Write([]byte("Hello, " + result.AccessKeyID))
    })

    http.ListenAndServe(":8080", nil)
}

Usage Guide

Signing Options

The Signer supports various configuration options:

signer := gohttpsig.NewSigner(
    provider,
    gohttpsig.WithUnsignedPayload(),                    // Don't sign payload
    gohttpsig.WithDisableURIPathEscaping(),             // For S3-compatible services
    gohttpsig.WithAdditionalSignedHeaders("X-Custom"),  // Include custom headers
)
Presigned URLs

Generate time-limited URLs that can be used without credentials:

req, _ := http.NewRequest("GET", "https://api.example.com/resource", nil)
presignedURL, err := signer.PresignRequest(
    context.Background(),
    req,
    "myservice",
    "us-east-1",
    15*time.Minute, // Expiration time
)

// Share presignedURL with clients
Verification Options

Configure the Verifier with custom options:

verifier := gohttpsig.NewVerifier(
    store,
    gohttpsig.WithMaxTimestampDrift(5*time.Minute),  // Allow 5 min clock drift
    gohttpsig.WithAllowUnsignedPayload(),            // Accept unsigned payloads
    gohttpsig.WithRequireSecurityToken(),            // Require session tokens
)
Custom Credential Providers

Implement the CredentialsProvider interface for custom credential sources:

type CredentialsProvider interface {
    Retrieve(ctx context.Context) (*Credentials, error)
}

// Example: Environment variable provider
type EnvCredentialsProvider struct{}

func (p *EnvCredentialsProvider) Retrieve(ctx context.Context) (*gohttpsig.Credentials, error) {
    return &gohttpsig.Credentials{
        AccessKeyID:     os.Getenv("AWS_ACCESS_KEY_ID"),
        SecretAccessKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
        SessionToken:    os.Getenv("AWS_SESSION_TOKEN"),
    }, nil
}
Custom Credential Stores

Implement the CredentialStore interface for server-side credential lookup:

type CredentialStore interface {
    GetCredentials(ctx context.Context, accessKeyID string) (*Credentials, error)
}

// Example: Database-backed store
type DatabaseCredentialStore struct {
    db *sql.DB
}

func (s *DatabaseCredentialStore) GetCredentials(ctx context.Context, accessKeyID string) (*gohttpsig.Credentials, error) {
    // Query database for credentials
    var creds gohttpsig.Credentials
    err := s.db.QueryRowContext(ctx,
        "SELECT access_key_id, secret_access_key FROM credentials WHERE access_key_id = $1",
        accessKeyID,
    ).Scan(&creds.AccessKeyID, &creds.SecretAccessKey)

    if err == sql.ErrNoRows {
        return nil, gohttpsig.ErrCredentialNotFound
    }
    return &creds, err
}
Middleware Pattern

Create reusable authentication middleware:

func AuthMiddleware(verifier *gohttpsig.Verifier) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            result, err := verifier.Verify(r.Context(), r)
            if err != nil || !result.Valid {
                http.Error(w, "Unauthorized", http.StatusUnauthorized)
                return
            }

            // Add user info to context
            ctx := context.WithValue(r.Context(), "user", result.AccessKeyID)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// Usage
mux.Handle("/api/", AuthMiddleware(verifier)(apiHandler))

AWS Signature Version 4 Compliance

This library implements the complete AWS Signature Version 4 specification:

  • ✅ RFC 3986 URI encoding with correct unreserved character set: A-Z a-z 0-9 - _ . ~
  • ✅ Canonical request construction (method, URI, query, headers, payload)
  • ✅ String to sign format (algorithm, timestamp, credential scope, hashed canonical request)
  • ✅ 4-step HMAC-SHA256 signing key derivation
  • ✅ Authorization header format: AWS4-HMAC-SHA256 Credential=..., SignedHeaders=..., Signature=...
  • ✅ Required headers: host, x-amz-date, x-amz-content-sha256
  • ✅ Header canonicalization (lowercase, trim, sort)
  • ✅ Query string encoding (sorted, double-encoded values)
  • ✅ ISO8601 timestamp format in UTC
  • ✅ Constant-time signature comparison for security

Examples

See the examples/ directory for complete working examples:

Running the Examples

Terminal 1 - Start the server:

cd examples/server
go run main.go

Terminal 2 - Run the client:

cd examples/client
go run main.go

API Reference

Core Types
// Credentials represents AWS-style credentials
type Credentials struct {
    AccessKeyID     string
    SecretAccessKey string
    SessionToken    string  // Optional
}

// Signer signs HTTP requests
type Signer struct { /* ... */ }

// Verifier verifies HTTP request signatures
type Verifier struct { /* ... */ }

// VerificationResult contains verification details
type VerificationResult struct {
    Valid         bool
    AccessKeyID   string
    SignedHeaders []string
    RequestTime   time.Time
    Service       string
    Region        string
    Error         error
}
Key Functions
// Create a new signer
func NewSigner(creds CredentialsProvider, opts ...SignerOption) *Signer

// Sign an HTTP request
func (s *Signer) Sign(ctx context.Context, req *http.Request, service, region string) (*SignedRequest, error)

// Create a presigned URL
func (s *Signer) PresignRequest(ctx, req, service, region string, expiresIn time.Duration) (*url.URL, error)

// Create a new verifier
func NewVerifier(store CredentialStore, opts ...VerifierOption) *Verifier

// Verify an HTTP request signature
func (v *Verifier) Verify(ctx context.Context, req *http.Request) (*VerificationResult, error)

Performance

Signing and verification are highly optimized:

BenchmarkSignerSign-8              20000    50000 ns/op    8192 B/op    95 allocs/op
BenchmarkDeriveSigningKey-8      200000     7500 ns/op     512 B/op     5 allocs/op
BenchmarkComputeSignature-8      500000     3000 ns/op     256 B/op     3 allocs/op

Typical performance:

  • Signing: ~50µs per request
  • Verification: ~55µs per request

Security Considerations

General Security
  • Constant-time comparison: Signature verification uses subtle.ConstantTimeCompare to prevent timing attacks
  • Timestamp validation: Requests outside the acceptable time drift window are rejected
  • HTTPS recommended: While signatures protect against tampering, use HTTPS to prevent eavesdropping
  • Credential rotation: Regularly rotate access keys and secret keys
  • Session tokens: Use temporary credentials with session tokens for enhanced security
SecretAccessKey Storage - Critical Security Notice ⚠️

Important: The client and server must use the identical SecretAccessKey because AWS Signature V4 uses HMAC (symmetric encryption):

  • Client: SecretAccessKey → HMAC → Signature → Send
  • Server: SecretAccessKey → HMAC → Recompute Signature → Compare
❌ DO NOT Hash SecretAccessKey

Unlike password authentication, you cannot store hashed SecretAccessKey:

// ❌ WRONG - This will NOT work!
hashedSecret := sha256.Sum256([]byte(secretKey))
// Cannot recompute HMAC from hash

Why? HMAC requires the original key to compute signatures. Hashing is one-way and irreversible.

✅ Secure Storage Solutions

Option 1: Database Field Encryption (AES-256-GCM)

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "io"
)

type EncryptedCredentialStore struct {
    db            *sql.DB
    encryptionKey []byte // 32-byte AES-256 key from secure KMS
}

func (s *EncryptedCredentialStore) encryptSecret(plaintext string) (string, error) {
    block, err := aes.NewCipher(s.encryptionKey)
    if err != nil {
        return "", err
    }

    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return "", err
    }

    nonce := make([]byte, gcm.NonceSize())
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return "", err
    }

    ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
    return base64.StdEncoding.EncodeToString(ciphertext), nil
}

func (s *EncryptedCredentialStore) decryptSecret(encrypted string) (string, error) {
    data, err := base64.StdEncoding.DecodeString(encrypted)
    if err != nil {
        return "", err
    }

    block, err := aes.NewCipher(s.encryptionKey)
    if err != nil {
        return "", err
    }

    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return "", err
    }

    nonceSize := gcm.NonceSize()
    nonce, ciphertext := data[:nonceSize], data[nonceSize:]

    plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
    if err != nil {
        return "", err
    }

    return string(plaintext), nil
}

func (s *EncryptedCredentialStore) GetCredentials(ctx context.Context, accessKeyID string) (*gohttpsig.Credentials, error) {
    var encryptedSecret string
    err := s.db.QueryRowContext(ctx,
        "SELECT access_key_id, encrypted_secret_key FROM credentials WHERE access_key_id = $1",
        accessKeyID,
    ).Scan(&accessKeyID, &encryptedSecret)

    if err != nil {
        return nil, err
    }

    // Decrypt the secret key
    secretKey, err := s.decryptSecret(encryptedSecret)
    if err != nil {
        return nil, err
    }

    return &gohttpsig.Credentials{
        AccessKeyID:     accessKeyID,
        SecretAccessKey: secretKey,
    }, nil
}

Option 2: Cloud KMS (AWS KMS, GCP KMS, Azure Key Vault)

type KMSCredentialStore struct {
    db        *sql.DB
    kmsClient *kms.KeyManagementClient
    keyName   string
}

func (s *KMSCredentialStore) GetCredentials(ctx context.Context, accessKeyID string) (*gohttpsig.Credentials, error) {
    var encryptedSecret []byte
    err := s.db.QueryRowContext(ctx,
        "SELECT access_key_id, kms_encrypted_secret FROM credentials WHERE access_key_id = $1",
        accessKeyID,
    ).Scan(&accessKeyID, &encryptedSecret)

    if err != nil {
        return nil, err
    }

    // Decrypt using KMS
    plaintext, err := s.kmsClient.Decrypt(ctx, &kms.DecryptRequest{
        KeyName:    s.keyName,
        Ciphertext: encryptedSecret,
    })

    return &gohttpsig.Credentials{
        AccessKeyID:     accessKeyID,
        SecretAccessKey: string(plaintext),
    }, nil
}

Option 3: Environment Variables + Encrypted Config

// For development/testing environments
type EnvCredentialStore struct {
    credentials map[string]*gohttpsig.Credentials
}

func LoadFromEncryptedConfig(configPath, masterKey string) (*EnvCredentialStore, error) {
    // 1. Read encrypted configuration file
    // 2. Decrypt using master key
    // 3. Load into memory
    encryptedData, err := os.ReadFile(configPath)
    // ... decrypt and parse
}
CREATE TABLE api_credentials (
    id SERIAL PRIMARY KEY,
    access_key_id VARCHAR(128) UNIQUE NOT NULL,
    encrypted_secret_key TEXT NOT NULL,  -- AES-256-GCM encrypted
    encryption_key_version INT NOT NULL DEFAULT 1,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),
    last_rotated_at TIMESTAMP,
    last_used_at TIMESTAMP,
    is_active BOOLEAN DEFAULT true,
    metadata JSONB,

    INDEX idx_access_key (access_key_id),
    INDEX idx_active (is_active)
);

-- Audit log for security monitoring
CREATE TABLE credential_audit_log (
    id SERIAL PRIMARY KEY,
    access_key_id VARCHAR(128),
    action VARCHAR(50),  -- 'created', 'rotated', 'revoked', 'accessed', 'failed'
    ip_address INET,
    user_agent TEXT,
    result VARCHAR(20),  -- 'success', 'failure'
    timestamp TIMESTAMP DEFAULT NOW(),

    INDEX idx_access_key_time (access_key_id, timestamp),
    INDEX idx_timestamp (timestamp)
);
Best Practices

1. Use Temporary Credentials (Recommended)

type TemporaryCredentials struct {
    AccessKeyID     string
    SecretAccessKey string
    SessionToken    string
    Expiration      time.Time
}

func (s *CredentialStore) IssueTemporaryCredentials(userID string, duration time.Duration) (*TemporaryCredentials, error) {
    creds := &TemporaryCredentials{
        AccessKeyID:     generateAccessKeyID(),
        SecretAccessKey: generateSecureSecret(),
        SessionToken:    generateSessionToken(),
        Expiration:      time.Now().Add(duration),
    }

    // Store with expiration
    s.storeTemporary(creds)
    return creds, nil
}

2. Implement Key Rotation

func (s *CredentialStore) RotateCredentials(ctx context.Context, accessKeyID string) error {
    newSecret := generateSecureSecret()
    encryptedSecret, _ := s.encrypt(newSecret)

    // Keep old key for grace period (e.g., 24 hours)
    _, err := s.db.ExecContext(ctx, `
        UPDATE credentials
        SET encrypted_secret_key = $1,
            old_encrypted_secret_key = encrypted_secret_key,
            last_rotated_at = NOW(),
            rotation_grace_period_until = NOW() + INTERVAL '24 hours'
        WHERE access_key_id = $2
    `, encryptedSecret, accessKeyID)

    return err
}

3. Audit Logging

func (s *CredentialStore) GetCredentials(ctx context.Context, accessKeyID string) (*gohttpsig.Credentials, error) {
    // Always log access attempts
    defer func() {
        s.logAccess(ctx, accessKeyID, "accessed")
    }()

    // Check revocation status
    if s.isRevoked(ctx, accessKeyID) {
        s.logAccess(ctx, accessKeyID, "revoked_access_attempt")
        return nil, ErrCredentialRevoked
    }

    // Update last used timestamp
    defer s.updateLastUsed(ctx, accessKeyID)

    // ... fetch and decrypt credentials
}

4. Rate Limiting & Anomaly Detection

func (s *CredentialStore) checkAnomalies(ctx context.Context, accessKeyID string) error {
    // Check for suspicious patterns
    count, err := s.getRecentFailureCount(ctx, accessKeyID, time.Hour)
    if err != nil {
        return err
    }

    if count > 10 {
        // Automatically revoke or require additional verification
        s.flagForReview(ctx, accessKeyID, "high_failure_rate")
        return ErrSuspiciousActivity
    }

    return nil
}
Security Checklist
  • Never store plaintext SecretAccessKey in database
  • Use AES-256-GCM or cloud KMS for encryption
  • Store encryption keys separately (e.g., environment variables, KMS)
  • Implement key rotation every 90 days
  • Use temporary credentials with expiration when possible
  • Log all credential access attempts
  • Monitor for anomalous usage patterns
  • Implement automatic revocation on suspicious activity
  • Use HTTPS for all API communications
  • Regularly audit credential usage logs

Testing

Run the test suite:

# Run all tests
go test -v ./...

# Run tests with coverage
go test -cover ./...

# Run benchmarks
go test -bench=. ./...

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

References

Acknowledgments

This library implements the AWS Signature Version 4 specification as documented by Amazon Web Services. It is designed to be compatible with AWS services and can also be used for custom HTTP API authentication.

Documentation

Index

Constants

View Source
const (
	// Algorithm is the AWS Signature Version 4 algorithm identifier
	Algorithm = "AWS4-HMAC-SHA256"

	// TimeFormat is the ISO8601 basic format used for timestamps
	TimeFormat = "20060102T150405Z"

	// DateFormat is the date format used for credential scope
	DateFormat = "20060102"

	// AWS4Request is the termination string for the credential scope
	AWS4Request = "aws4_request"

	// UnsignedPayload is used when the payload is not signed
	UnsignedPayload = "UNSIGNED-PAYLOAD"
)
View Source
const (
	// HeaderHost is the Host header name
	HeaderHost = "host"

	// HeaderAuthorization is the Authorization header name
	HeaderAuthorization = "Authorization"

	// HeaderXAmzDate is the X-Amz-Date header name for the request timestamp
	HeaderXAmzDate = "X-Amz-Date"

	// HeaderXAmzContentSHA256 is the X-Amz-Content-Sha256 header for payload hash
	HeaderXAmzContentSHA256 = "X-Amz-Content-Sha256"

	// HeaderXAmzSecurityToken is the X-Amz-Security-Token header for session tokens
	HeaderXAmzSecurityToken = "X-Amz-Security-Token"

	// HeaderContentType is the Content-Type header
	HeaderContentType = "Content-Type"
)

Variables

View Source
var (
	// ErrInvalidCredentials indicates that the credentials are invalid or missing required fields
	ErrInvalidCredentials = errors.New("invalid credentials")

	// ErrMissingAuthorizationHeader indicates that the Authorization header is missing from the request
	ErrMissingAuthorizationHeader = errors.New("missing authorization header")

	// ErrInvalidAuthorizationHeader indicates that the Authorization header format is invalid
	ErrInvalidAuthorizationHeader = errors.New("invalid authorization header format")

	// ErrInvalidSignature indicates that the signature does not match the computed signature
	ErrInvalidSignature = errors.New("invalid signature")

	// ErrTimestampOutOfRange indicates that the request timestamp is outside the acceptable range
	ErrTimestampOutOfRange = errors.New("timestamp out of acceptable range")

	// ErrMissingRequiredHeader indicates that a required header is missing from the request
	ErrMissingRequiredHeader = errors.New("missing required header")

	// ErrCredentialNotFound indicates that the credential was not found in the credential store
	ErrCredentialNotFound = errors.New("credential not found")

	// ErrInvalidTimestamp indicates that the timestamp format is invalid
	ErrInvalidTimestamp = errors.New("invalid timestamp format")

	// ErrEmptyPayload indicates that the payload is empty when it should not be
	ErrEmptyPayload = errors.New("empty payload")
)

Sentinel errors

Functions

func BuildAuthorizationHeader

func BuildAuthorizationHeader(accessKeyID, credentialScope, signedHeaders, signature string) string

BuildAuthorizationHeader builds the Authorization header value Format: AWS4-HMAC-SHA256 Credential=ACCESS_KEY/SCOPE, SignedHeaders=HEADERS, Signature=SIGNATURE

func BuildCredentialScope

func BuildCredentialScope(date, region, service string) string

BuildCredentialScope builds the credential scope string Format: YYYYMMDD/region/service/aws4_request

func CanonicalizeHeaders

func CanonicalizeHeaders(headers http.Header, signedHeaderNames []string) (canonical string, signedHeaders string)

CanonicalizeHeaders creates the canonical headers string for AWS Signature Version 4 It takes the HTTP headers and a list of header names to include in the signature Returns the canonical headers string and the signed headers list

Rules: 1. Convert header names to lowercase 2. Trim leading and trailing whitespace from values 3. Convert sequential spaces in values to a single space 4. Sort headers by name (case-insensitive) 5. Format as "name:value\n" for each header 6. The signed headers list is semicolon-separated lowercase header names

func ComputePayloadHash

func ComputePayloadHash(payload io.Reader) (string, error)

ComputePayloadHash computes the SHA256 hash of the request payload and returns it as a lowercase hex string

func ComputePayloadHashFromBytes

func ComputePayloadHashFromBytes(payload []byte) string

ComputePayloadHashFromBytes computes the SHA256 hash of the request payload bytes and returns it as a lowercase hex string

func ComputeSignature

func ComputeSignature(signingKey []byte, stringToSign string) string

ComputeSignature computes the signature for the given string to sign using the provided signing key. Returns the signature as a lowercase hex string.

func ComputeStringHash

func ComputeStringHash(data string) string

ComputeStringHash computes the SHA256 hash of a string and returns it as a lowercase hex string

func DeriveSigningKey

func DeriveSigningKey(secret, date, region, service string) []byte

DeriveSigningKey derives the signing key using the AWS Signature Version 4 algorithm This performs a 4-step HMAC-SHA256 chain: 1. kDate = HMAC-SHA256("AWS4" + secret, date) 2. kRegion = HMAC-SHA256(kDate, region) 3. kService = HMAC-SHA256(kRegion, service) 4. kSigning = HMAC-SHA256(kService, "aws4_request")

func EncodeQueryValue

func EncodeQueryValue(value string) string

EncodeQueryValue encodes a single query parameter value according to RFC 3986 This is used for canonical query string construction

func EncodeQueryValues

func EncodeQueryValues(values url.Values) string

EncodeQueryValues encodes URL query parameters into a canonical query string The query string is sorted by parameter name, then by value if there are multiple values Each parameter is encoded as name=value with proper RFC 3986 encoding Multiple parameters are joined with &

func EncodeURI

func EncodeURI(path string, encodeSlash bool) string

EncodeURI encodes a URI path according to RFC 3986 for AWS Signature Version 4 If encodeSlash is true, forward slashes (/) are also encoded All characters except unreserved characters (A-Z a-z 0-9 - _ . ~) are percent-encoded

func ExtractSignedHeaders

func ExtractSignedHeaders(headers http.Header, additionalHeaders []string) []string

ExtractSignedHeaders determines which headers should be included in the signature By default, includes: - host (always required) - All x-amz-* headers - content-type (if present)

Additional headers can be specified via options

func FormatSigningDate

func FormatSigningDate(t time.Time) string

FormatSigningDate formats a time value into the date format used in the credential scope: YYYYMMDD

func FormatSigningTime

func FormatSigningTime(t time.Time) string

FormatSigningTime formats a time value into the ISO8601 basic format used in AWS Signature Version 4: YYYYMMDDTHHMMSSZ

func GetHeaderValue

func GetHeaderValue(headers http.Header, name string) string

GetHeaderValue retrieves a header value in a case-insensitive manner

func NormalizePath

func NormalizePath(path string) string

NormalizePath normalizes a URI path for canonical request construction It removes redundant slashes and applies RFC 3986 encoding

func ParseAuthorizationHeader

func ParseAuthorizationHeader(authHeader string) (credential, signedHeaders, signature string, err error)

ParseAuthorizationHeader parses the Authorization header Returns credential (access key ID + scope), signed headers list, and signature

func ParseCredential

func ParseCredential(credential string) (accessKeyID, scope string, err error)

ParseCredential parses the credential string from the Authorization header Format: ACCESS_KEY_ID/DATE/REGION/SERVICE/aws4_request Returns access key ID and credential scope

func ParseCredentialScope

func ParseCredentialScope(scope string) (date, region, service string, err error)

ParseCredentialScope parses a credential scope string into its components Returns date, region, service, and an error if parsing fails

func ParseSigningTime

func ParseSigningTime(timestamp string) (time.Time, error)

ParseSigningTime parses a timestamp in ISO8601 basic format

func SetHeaderValue

func SetHeaderValue(headers http.Header, name, value string)

SetHeaderValue sets a header value, replacing any existing values

func VerifySignature

func VerifySignature(expected, actual string) bool

VerifySignature performs constant-time comparison of two signatures to prevent timing attacks

Types

type CanonicalRequest

type CanonicalRequest struct {
	// Method is the HTTP method (GET, POST, etc.)
	Method string

	// CanonicalURI is the RFC 3986 encoded URI path
	CanonicalURI string

	// CanonicalQueryString is the sorted, encoded query parameters
	CanonicalQueryString string

	// CanonicalHeaders is the formatted canonical headers string
	CanonicalHeaders string

	// SignedHeaders is the semicolon-separated list of signed header names
	SignedHeaders string

	// PayloadHash is the SHA256 hash of the payload or "UNSIGNED-PAYLOAD"
	PayloadHash string
}

CanonicalRequest represents a canonical HTTP request for AWS Signature Version 4

func BuildCanonicalRequest

func BuildCanonicalRequest(req *http.Request, payloadHash string, opts *CanonicalRequestOptions) (*CanonicalRequest, error)

BuildCanonicalRequest builds a canonical request from an HTTP request

func (*CanonicalRequest) String

func (cr *CanonicalRequest) String() string

String returns the canonical request as a string in the AWS SigV4 format: HTTPMethod + "\n" + CanonicalURI + "\n" + CanonicalQueryString + "\n" + CanonicalHeaders + "\n" + SignedHeaders + "\n" + HashedPayload

type CanonicalRequestOptions

type CanonicalRequestOptions struct {
	// DisableURIPathEscaping disables URI path encoding (for S3)
	DisableURIPathEscaping bool

	// AdditionalSignedHeaders specifies additional headers to include in the signature
	AdditionalSignedHeaders []string

	// UnsignedPayload indicates the payload should not be signed
	UnsignedPayload bool
}

CanonicalRequestOptions provides options for building canonical requests

type CredentialStore

type CredentialStore interface {
	// GetCredentials retrieves credentials for the given access key ID
	GetCredentials(ctx context.Context, accessKeyID string) (*Credentials, error)
}

CredentialStore is an interface for looking up credentials by access key ID This is used on the server side for signature verification

type Credentials

type Credentials struct {
	// AccessKeyID is the access key identifier
	AccessKeyID string

	// SecretAccessKey is the secret key used for signing
	SecretAccessKey string

	// SessionToken is an optional session token for temporary credentials
	SessionToken string
}

Credentials represents AWS-style credentials used for signing requests

func (*Credentials) Validate

func (c *Credentials) Validate() error

Validate checks if the credentials are valid

type CredentialsProvider

type CredentialsProvider interface {
	// Retrieve retrieves credentials from the provider
	Retrieve(ctx context.Context) (*Credentials, error)
}

CredentialsProvider is an interface for retrieving credentials This allows for different credential sources (static, environment, files, etc.)

type InMemoryCredentialStore

type InMemoryCredentialStore struct {
	// contains filtered or unexported fields
}

InMemoryCredentialStore is a simple in-memory implementation of CredentialStore This is primarily for testing and simple use cases

func NewInMemoryCredentialStore

func NewInMemoryCredentialStore() *InMemoryCredentialStore

NewInMemoryCredentialStore creates a new in-memory credential store

func (*InMemoryCredentialStore) AddCredentials

func (s *InMemoryCredentialStore) AddCredentials(creds *Credentials) error

AddCredentials adds credentials to the store

func (*InMemoryCredentialStore) GetCredentials

func (s *InMemoryCredentialStore) GetCredentials(ctx context.Context, accessKeyID string) (*Credentials, error)

GetCredentials retrieves credentials for the given access key ID

func (*InMemoryCredentialStore) RemoveCredentials

func (s *InMemoryCredentialStore) RemoveCredentials(accessKeyID string)

RemoveCredentials removes credentials from the store

type SignedRequest

type SignedRequest struct {
	// Request is the signed HTTP request
	Request *http.Request

	// Signature is the computed signature
	Signature string

	// SignedHeaders is the list of headers included in the signature
	SignedHeaders string

	// CredentialScope is the credential scope string
	CredentialScope string

	// SigningTime is the time used for signing
	SigningTime time.Time
}

SignedRequest represents a signed HTTP request

type Signer

type Signer struct {
	// contains filtered or unexported fields
}

Signer signs HTTP requests using AWS Signature Version 4

func NewSigner

func NewSigner(creds CredentialsProvider, opts ...SignerOption) *Signer

NewSigner creates a new Signer with the given credentials provider and options

func (*Signer) PresignRequest

func (s *Signer) PresignRequest(ctx context.Context, req *http.Request, service, region string, expiresIn time.Duration) (*url.URL, error)

PresignRequest creates a presigned URL for an HTTP request The presigned URL can be used by clients without credentials

func (*Signer) Sign

func (s *Signer) Sign(ctx context.Context, req *http.Request, service, region string) (*SignedRequest, error)

Sign signs an HTTP request and returns a SignedRequest The original request is cloned and the Authorization header is added

type SignerOption

type SignerOption func(*SignerOptions)

SignerOption is a functional option for configuring the Signer

func WithAdditionalSignedHeaders

func WithAdditionalSignedHeaders(headers ...string) SignerOption

WithAdditionalSignedHeaders adds additional headers to be included in the signature

func WithDisableImplicitContentSHA256

func WithDisableImplicitContentSHA256() SignerOption

WithDisableImplicitContentSHA256 disables automatically adding the X-Amz-Content-Sha256 header

func WithDisableURIPathEscaping

func WithDisableURIPathEscaping() SignerOption

WithDisableURIPathEscaping disables URI path encoding This is typically used for S3-compatible services

func WithSigningTime

func WithSigningTime(t time.Time) SignerOption

WithSigningTime sets a specific signing time (primarily for testing)

func WithUnsignedPayload

func WithUnsignedPayload() SignerOption

WithUnsignedPayload configures the signer to not sign the payload The X-Amz-Content-Sha256 header will be set to "UNSIGNED-PAYLOAD"

type SignerOptions

type SignerOptions struct {
	// UnsignedPayload indicates that the payload should not be signed
	// The payload hash will be set to "UNSIGNED-PAYLOAD"
	UnsignedPayload bool

	// DisableURIPathEscaping disables URI path encoding
	// This is typically used for S3-compatible services
	DisableURIPathEscaping bool

	// AdditionalSignedHeaders specifies additional headers to include in the signature
	// beyond the default set (host, x-amz-*, content-type)
	AdditionalSignedHeaders []string

	// DisableImplicitContentSHA256 disables automatically adding the X-Amz-Content-Sha256 header
	DisableImplicitContentSHA256 bool

	// OverrideSigningTime allows overriding the signing time (primarily for testing)
	OverrideSigningTime *time.Time
}

SignerOptions contains configuration options for the Signer

type SigningError

type SigningError struct {
	Operation string
	Err       error
}

SigningError represents an error that occurred during the signing process

func (*SigningError) Error

func (e *SigningError) Error() string

func (*SigningError) Unwrap

func (e *SigningError) Unwrap() error

type StaticCredentialsProvider

type StaticCredentialsProvider struct {
	// contains filtered or unexported fields
}

StaticCredentialsProvider provides credentials from a static source

func NewStaticCredentialsProvider

func NewStaticCredentialsProvider(creds *Credentials) *StaticCredentialsProvider

NewStaticCredentialsProvider creates a new static credentials provider

func (*StaticCredentialsProvider) Retrieve

Retrieve returns the static credentials

type StringToSign

type StringToSign struct {
	// Algorithm is the signing algorithm (AWS4-HMAC-SHA256)
	Algorithm string

	// RequestDateTime is the ISO8601 timestamp
	RequestDateTime string

	// CredentialScope is the credential scope string
	CredentialScope string

	// HashedCanonicalRequest is the SHA256 hash of the canonical request
	HashedCanonicalRequest string
}

StringToSign represents the string that will be signed

func BuildStringToSign

func BuildStringToSign(canonicalReq *CanonicalRequest, service, region string, signTime time.Time) (*StringToSign, error)

BuildStringToSign builds the string to sign from a canonical request

func (*StringToSign) String

func (sts *StringToSign) String() string

String returns the string to sign in the AWS SigV4 format: Algorithm + "\n" + RequestDateTime + "\n" + CredentialScope + "\n" + HashedCanonicalRequest

type ValidationError

type ValidationError struct {
	Field string
	Err   error
}

ValidationError represents a validation error

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Unwrap

func (e *ValidationError) Unwrap() error

type VerificationError

type VerificationError struct {
	Reason string
	Err    error
}

VerificationError represents an error that occurred during signature verification

func (*VerificationError) Error

func (e *VerificationError) Error() string

func (*VerificationError) Unwrap

func (e *VerificationError) Unwrap() error

type VerificationResult

type VerificationResult struct {
	// Valid indicates whether the signature is valid
	Valid bool

	// AccessKeyID is the access key ID extracted from the request
	AccessKeyID string

	// SignedHeaders is the list of headers that were signed
	SignedHeaders []string

	// RequestTime is the timestamp from the request
	RequestTime time.Time

	// Service is the service name from the credential scope
	Service string

	// Region is the region from the credential scope
	Region string

	// Error contains any error that occurred during verification
	Error error
}

VerificationResult represents the result of signature verification

type Verifier

type Verifier struct {
	// contains filtered or unexported fields
}

Verifier verifies HTTP request signatures using AWS Signature Version 4

func NewVerifier

func NewVerifier(store CredentialStore, opts ...VerifierOption) *Verifier

NewVerifier creates a new Verifier with the given credential store and options

func (*Verifier) Verify

func (v *Verifier) Verify(ctx context.Context, req *http.Request) (*VerificationResult, error)

Verify verifies the signature of an HTTP request

type VerifierOption

type VerifierOption func(*VerifierOptions)

VerifierOption is a functional option for configuring the Verifier

func WithAllowUnsignedPayload

func WithAllowUnsignedPayload() VerifierOption

WithAllowUnsignedPayload allows requests with unsigned payloads

func WithMaxTimestampDrift

func WithMaxTimestampDrift(duration time.Duration) VerifierOption

WithMaxTimestampDrift sets the maximum allowed time drift for request timestamps Default is 5 minutes if not specified

func WithRequireSecurityToken

func WithRequireSecurityToken() VerifierOption

WithRequireSecurityToken requires the X-Amz-Security-Token header to be present

func WithVerifierCurrentTime

func WithVerifierCurrentTime(t time.Time) VerifierOption

WithVerifierCurrentTime sets the current time for timestamp validation (testing)

func WithVerifierDisableURIPathEscaping

func WithVerifierDisableURIPathEscaping() VerifierOption

WithVerifierDisableURIPathEscaping disables URI path encoding verification

type VerifierOptions

type VerifierOptions struct {
	// MaxTimestampDrift is the maximum allowed time difference between
	// the request timestamp and the current time
	MaxTimestampDrift time.Duration

	// DisableURIPathEscaping disables URI path encoding verification
	DisableURIPathEscaping bool

	// RequireSecurityToken indicates whether the X-Amz-Security-Token header is required
	RequireSecurityToken bool

	// AllowUnsignedPayload allows requests with "UNSIGNED-PAYLOAD" in the X-Amz-Content-Sha256 header
	AllowUnsignedPayload bool

	// OverrideCurrentTime allows overriding the current time for timestamp validation (testing)
	OverrideCurrentTime *time.Time
}

VerifierOptions contains configuration options for the Verifier

Directories

Path Synopsis
examples
client command
secure-storage command
server command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL