Class: TOS::Signer

Inherits:
Object
  • Object
show all
Defined in:
lib/tos/signer.rb

Overview

Signs HTTP requests for the TOS object service using the TOS4-HMAC-SHA256 scheme (analogous to AWS SigV4, but with the “tos” service name and a slightly different signed-header policy: by default only ‘content-type` and any `x-tos-*` headers are signed).

Reference: github.com/volcengine/ve-tos-golang-sdk/blob/master/tos/sign_v4.go

Constant Summary collapse

ALGORITHM =
"TOS4-HMAC-SHA256"
SERVICE =
"tos"
REQUEST =
"request"
EMPTY_SHA256 =
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
UNSIGNED_PAYLOAD =
"UNSIGNED-PAYLOAD"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(credentials:, region:) ⇒ Signer

Returns a new instance of Signer.

Raises:



24
25
26
27
28
29
30
# File 'lib/tos/signer.rb', line 24

def initialize(credentials:, region:)
  @credentials = Credentials.from(credentials)
  raise ConfigError, "TOS credentials are missing access_key_id/secret_access_key" unless @credentials.valid?
  raise ConfigError, "TOS region is required" if region.to_s.empty?

  @region = region.to_s
end

Instance Attribute Details

#credentialsObject (readonly)

Returns the value of attribute credentials.



22
23
24
# File 'lib/tos/signer.rb', line 22

def credentials
  @credentials
end

#regionObject (readonly)

Returns the value of attribute region.



22
23
24
# File 'lib/tos/signer.rb', line 22

def region
  @region
end

Instance Method Details

#presign(method:, host:, path:, query: {}, expires_in: 3600, now: Time.now.utc) ⇒ Object

Build a presigned URL with all signing material in the query string. Returns a fully-qualified URL.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/tos/signer.rb', line 76

def presign(method:, host:, path:, query: {}, expires_in: 3600, now: Time.now.utc)
  datetime = now.strftime("%Y%m%dT%H%M%SZ")
  date = datetime[0, 8]
  credential_scope = "#{date}/#{@region}/#{SERVICE}/#{REQUEST}"

  query_with_sig = query.dup
  query_with_sig["X-Tos-Algorithm"] = ALGORITHM
  query_with_sig["X-Tos-Credential"] = "#{@credentials.access_key_id}/#{credential_scope}"
  query_with_sig["X-Tos-Date"] = datetime
  query_with_sig["X-Tos-Expires"] = expires_in.to_s
  query_with_sig["X-Tos-SignedHeaders"] = ""
  query_with_sig["X-Tos-Security-Token"] = @credentials.security_token if @credentials.security_token

  canonical_request = build_canonical_request(
    method: method.to_s.upcase,
    path: path,
    query: query_with_sig,
    signed_headers: [],
    content_sha: UNSIGNED_PAYLOAD
  )

  string_to_sign = [ALGORITHM, datetime, credential_scope, sha256_hex(canonical_request)].join("\n")
  signing_key = derive_signing_key(date)
  query_with_sig["X-Tos-Signature"] = hmac_hex(signing_key, string_to_sign)

  "https://#{host}#{path}?#{encode_query(query_with_sig)}"
end

#sign_headers(method:, path:, query: {}, headers: {}, body: nil, now: Time.now.utc) ⇒ Object

Sign a request and return a Hash of headers to add (Authorization, X-Tos-Date, X-Tos-Content-Sha256, plus X-Tos-Security-Token when STS credentials are used).

Parameters:

  • method (String)

    HTTP method, uppercased (e.g. “PUT”)

  • path (String)

    URI path starting with “/” (e.g. “/bucket/key”)

  • query (Hash{String=>String,Array<String>}) (defaults to: {})

    query params

  • headers (Hash) (defaults to: {})

    existing request headers (case-insensitive keys)

  • body (String, nil) (defaults to: nil)

    request body — may be nil for GET/HEAD/DELETE

  • now (Time) (defaults to: Time.now.utc)

    override for testing



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/tos/signer.rb', line 42

def sign_headers(method:, path:, query: {}, headers: {}, body: nil, now: Time.now.utc)
  datetime = now.strftime("%Y%m%dT%H%M%SZ")
  date = datetime[0, 8]
  content_sha = body ? sha256_hex(body) : EMPTY_SHA256

  working_headers = headers.dup
  working_headers["X-Tos-Date"] = datetime
  working_headers["X-Tos-Content-Sha256"] = content_sha
  working_headers["X-Tos-Security-Token"] = @credentials.security_token if @credentials.security_token

  signed_headers = filter_signed_headers(working_headers)

  canonical_request = build_canonical_request(
    method: method.to_s.upcase,
    path: path,
    query: query,
    signed_headers: signed_headers,
    content_sha: content_sha
  )

  credential_scope = "#{date}/#{@region}/#{SERVICE}/#{REQUEST}"
  string_to_sign = [ALGORITHM, datetime, credential_scope, sha256_hex(canonical_request)].join("\n")

  signing_key = derive_signing_key(date)
  signature = hmac_hex(signing_key, string_to_sign)

  authorization = build_authorization_header(signed_headers, credential_scope, signature)
  working_headers["Authorization"] = authorization

  working_headers
end