Class: CoreLibrary::HmacSignatureVerifier
- Inherits:
-
SignatureVerifier
- Object
- SignatureVerifier
- CoreLibrary::HmacSignatureVerifier
- Defined in:
- lib/apimatic-core/security/signature_verification/hmac_signature_verifier.rb
Overview
HmacSignatureVerifier ===
Verifies HMAC signatures for incoming requests.
Works with Rack::Request or any object exposing Rack-like ‘env` or `headers`/`raw_body`.
Example:
verifier = HmacSignatureVerifier.new(
secret_key: "supersecret",
signature_header: "X-Signature"
)
result = verifier.verify(rack_request)
Class Method Summary collapse
-
.fixed_length_secure_compare(a, b) ⇒ Boolean
Compares two strings in constant time to prevent timing attacks.
Instance Method Summary collapse
-
#initialize(secret_key:, signature_header:, canonical_message_builder: nil, hash_algorithm: 'sha256', encoder: HexEncoder.new, signature_value_template: DIGEST_PLACEHOLDER) ⇒ HmacSignatureVerifier
constructor
A new instance of HmacSignatureVerifier.
-
#verify(request) ⇒ CoreLibrary::SignatureVerificationResult
Verifies the HMAC signature for the request.
Constructor Details
#initialize(secret_key:, signature_header:, canonical_message_builder: nil, hash_algorithm: 'sha256', encoder: HexEncoder.new, signature_value_template: DIGEST_PLACEHOLDER) ⇒ HmacSignatureVerifier
Returns a new instance of HmacSignatureVerifier.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/apimatic-core/security/signature_verification/hmac_signature_verifier.rb', line 49 def initialize(secret_key:, signature_header:, canonical_message_builder: nil, hash_algorithm: 'sha256', encoder: HexEncoder.new, signature_value_template: DIGEST_PLACEHOLDER) raise ArgumentError, 'secret_key must be a non-empty string' unless secret_key.is_a?(String) && !secret_key.empty? unless signature_header.is_a?(String) && !signature_header.strip.empty? raise ArgumentError, 'signature_header must be a non-empty string' end @secret_key = secret_key @signature_header_lc = signature_header.strip.downcase.tr('_', '-') @canonical_message_builder = @hash_alg = hash_algorithm @encoder = encoder @signature_value_template = signature_value_template end |
Class Method Details
.fixed_length_secure_compare(a, b) ⇒ Boolean
Compares two strings in constant time to prevent timing attacks. Uses OpenSSL.fixed_length_secure_compare when available (Ruby ≥ 3 with openssl gem ≥ 3.x); falls back to a pure Ruby implementation otherwise.
112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/apimatic-core/security/signature_verification/hmac_signature_verifier.rb', line 112 def self.fixed_length_secure_compare(a, b) if RUBY_VERSION >= '3.0' && OpenSSL.respond_to?(:fixed_length_secure_compare) OpenSSL.fixed_length_secure_compare(a, b) else raise ArgumentError, 'inputs must be same length' unless a.bytesize == b.bytesize res = 0 a.bytes.zip(b.bytes) { |x, y| res |= (x ^ y) } res.zero? end end |
Instance Method Details
#verify(request) ⇒ CoreLibrary::SignatureVerificationResult
Verifies the HMAC signature for the request.
70 71 72 73 74 75 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/apimatic-core/security/signature_verification/hmac_signature_verifier.rb', line 70 def verify(request) headers = RackRequestHelper.extract_headers_hash(request) provided_signature = headers[@signature_header_lc] if provided_signature.nil? return CoreLibrary::SignatureVerificationResult.failed( ["Signature header '#{@signature_header_lc}' is missing"] ) end = (request) digest = OpenSSL::HMAC.digest(@hash_alg, @secret_key, ) encoded_digest = @encoder.encode(digest) unless @encoder.nil? expected_signature = if @signature_value_template.include?(DIGEST_PLACEHOLDER) @signature_value_template.gsub(DIGEST_PLACEHOLDER, encoded_digest) else @signature_value_template end if HmacSignatureVerifier.fixed_length_secure_compare(provided_signature, expected_signature) CoreLibrary::SignatureVerificationResult.passed else CoreLibrary::SignatureVerificationResult.failed( ['Signature mismatch'] ) end rescue StandardError => e CoreLibrary::SignatureVerificationResult.failed( ["Signature verification failed: #{e.}"] ) end |