@@ -5,38 +5,69 @@ module Algos
55 module Hmac
66 module_function
77
8- SUPPORTED = %w[ HS256 HS512256 HS384 HS512 ] . freeze
8+ MAPPING = {
9+ 'HS256' => OpenSSL ::Digest ::SHA256 ,
10+ 'HS384' => OpenSSL ::Digest ::SHA384 ,
11+ 'HS512' => OpenSSL ::Digest ::SHA512
12+ } . freeze
13+
14+ SUPPORTED = MAPPING . keys
915
1016 def sign ( algorithm , msg , key )
1117 key ||= ''
12- authenticator , padded_key = SecurityUtils . rbnacl_fixup ( algorithm , key )
13- if authenticator && padded_key
14- authenticator . auth ( padded_key , msg . encode ( 'binary' ) )
15- else
16- begin
17- OpenSSL ::HMAC . digest ( OpenSSL ::Digest . new ( algorithm . sub ( 'HS' , 'sha' ) ) , key , msg )
18- rescue OpenSSL ::HMACError => e
19- if key == '' && e . message == 'EVP_PKEY_new_mac_key: malloc failure'
20- raise JWT ::DecodeError . new ( 'OpenSSL 3.0 does not support nil or empty hmac_secret' )
21- end
22-
23- raise e
24- end
18+
19+ raise JWT ::DecodeError , 'HMAC key expected to be a String' unless key . is_a? ( String )
20+
21+ OpenSSL ::HMAC . digest ( MAPPING [ algorithm ] . new , key , msg )
22+ rescue OpenSSL ::HMACError => e
23+ if key == '' && e . message == 'EVP_PKEY_new_mac_key: malloc failure'
24+ raise JWT ::DecodeError , 'OpenSSL 3.0 does not support nil or empty hmac_secret'
2525 end
26+
27+ raise e
28+ end
29+
30+ def verify ( algorithm , key , signing_input , signature )
31+ SecurityUtils . secure_compare ( signature , sign ( algorithm , signing_input , key ) )
2632 end
2733
28- def verify ( algorithm , public_key , signing_input , signature )
29- authenticator , padded_key = SecurityUtils . rbnacl_fixup ( algorithm , public_key )
30- if authenticator && padded_key
31- begin
32- authenticator . verify ( padded_key , signature . encode ( 'binary' ) , signing_input . encode ( 'binary' ) )
33- rescue RbNaCl ::BadAuthenticatorError
34- false
34+ # Copy of https://github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb
35+ # rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
36+ module SecurityUtils
37+ # Constant time string comparison, for fixed length strings.
38+ #
39+ # The values compared should be of fixed length, such as strings
40+ # that have already been processed by HMAC. Raises in case of length mismatch.
41+
42+ if defined? ( OpenSSL . fixed_length_secure_compare )
43+ def fixed_length_secure_compare ( a , b )
44+ OpenSSL . fixed_length_secure_compare ( a , b )
3545 end
3646 else
37- SecurityUtils . secure_compare ( signature , sign ( algorithm , signing_input , public_key ) )
47+ def fixed_length_secure_compare ( a , b )
48+ raise ArgumentError , "string length mismatch." unless a . bytesize == b . bytesize
49+
50+ l = a . unpack "C#{ a . bytesize } "
51+
52+ res = 0
53+ b . each_byte { |byte | res |= byte ^ l . shift }
54+ res == 0
55+ end
56+ end
57+ module_function :fixed_length_secure_compare
58+
59+ # Secure string comparison for strings of variable length.
60+ #
61+ # While a timing attack would not be able to discern the content of
62+ # a secret compared via secure_compare, it is possible to determine
63+ # the secret length. This should be considered when using secure_compare
64+ # to compare weak, short secrets to user input.
65+ def secure_compare ( a , b )
66+ a . bytesize == b . bytesize && fixed_length_secure_compare ( a , b )
3867 end
68+ module_function :secure_compare
3969 end
70+ # rubocop:enable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate
4071 end
4172 end
4273end
0 commit comments