ruby-paseto 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -1
- data/README.md +55 -50
- data/lib/paseto/asn1/ecdsa_signature.rb +2 -2
- data/lib/paseto/asymmetric_key.rb +7 -4
- data/lib/paseto/configuration/decode_configuration.rb +7 -7
- data/lib/paseto/interface/key.rb +5 -2
- data/lib/paseto/interface/pbkd.rb +4 -20
- data/lib/paseto/interface/pie.rb +5 -26
- data/lib/paseto/interface/pke.rb +15 -51
- data/lib/paseto/interface/version.rb +30 -117
- data/lib/paseto/operations/id/i_dv3.rb +1 -1
- data/lib/paseto/operations/id/i_dv4.rb +1 -1
- data/lib/paseto/operations/pbkd/p_b_k_dv3.rb +4 -5
- data/lib/paseto/operations/pbkd/p_b_k_dv4.rb +3 -4
- data/lib/paseto/operations/pbkw.rb +6 -6
- data/lib/paseto/operations/pke/p_k_ev3.rb +23 -25
- data/lib/paseto/operations/pke/p_k_ev4.rb +33 -34
- data/lib/paseto/operations/pke.rb +9 -10
- data/lib/paseto/operations/wrap.rb +1 -1
- data/lib/paseto/paserk.rb +1 -1
- data/lib/paseto/paserk_types.rb +3 -3
- data/lib/paseto/protocol/version3.rb +17 -16
- data/lib/paseto/protocol/version4.rb +17 -16
- data/lib/paseto/symmetric_key.rb +16 -10
- data/lib/paseto/token.rb +17 -15
- data/lib/paseto/token_types.rb +2 -2
- data/lib/paseto/util.rb +2 -3
- data/lib/paseto/v3/local.rb +1 -1
- data/lib/paseto/v3/public.rb +8 -69
- data/lib/paseto/v4/local.rb +3 -3
- data/lib/paseto/v4/public.rb +3 -6
- data/lib/paseto/validator.rb +1 -1
- data/lib/paseto/version.rb +1 -1
- data/lib/paseto/versions.rb +2 -2
- data/lib/paseto/wrappers/pie/pie_v3.rb +18 -21
- data/lib/paseto/wrappers/pie/pie_v4.rb +17 -20
- data/lib/paseto/wrappers/pie.rb +3 -17
- data/lib/paseto.rb +2 -5
- data/paseto.gemspec +5 -17
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/rainbow.rbi +4 -4
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1 -1
- data/sorbet/rbi/gems/docile@1.4.0.rbi +1 -1
- data/sorbet/rbi/gems/{ffi@1.15.5.rbi → ffi@1.16.3.rbi} +260 -117
- data/sorbet/rbi/gems/{oj@3.13.23.rbi → oj@3.16.1.rbi} +28 -37
- data/sorbet/rbi/gems/{rake@13.0.6.rbi → rake@13.1.0.rbi} +75 -69
- data/sorbet/rbi/gems/rbnacl@7.1.1.rbi +2 -2
- data/sorbet/rbi/gems/{rspec-core@3.12.0.rbi → rspec-core@3.12.2.rbi} +31 -113
- data/sorbet/rbi/gems/{rspec-expectations@3.12.0.rbi → rspec-expectations@3.12.3.rbi} +204 -194
- data/sorbet/rbi/gems/{rspec-mocks@3.12.0.rbi → rspec-mocks@3.12.6.rbi} +225 -215
- data/sorbet/rbi/gems/{rspec-support@3.12.0.rbi → rspec-support@3.12.1.rbi} +35 -43
- data/sorbet/rbi/gems/rspec@3.12.0.rbi +22 -28
- data/sorbet/rbi/gems/simplecov-html@0.12.3.rbi +41 -44
- data/sorbet/rbi/gems/{simplecov@0.21.2.rbi → simplecov@0.22.0.rbi} +62 -49
- data/sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi +232 -2
- data/sorbet/rbi/gems/{timecop@0.9.6.rbi → timecop@0.9.8.rbi} +13 -16
- data/sorbet/rbi/shims/multi_json.rbi +2 -0
- data/sorbet/rbi/shims/openssl.rbi +0 -8
- data/sorbet/rbi/todo.rbi +5 -1
- data/sorbet/tapioca/config.yml +1 -1
- metadata +20 -191
- data/sorbet/rbi/gems/ast@2.4.2.rbi +0 -584
- data/sorbet/rbi/gems/io-console@0.5.11.rbi +0 -8
- data/sorbet/rbi/gems/irb@1.5.1.rbi +0 -342
- data/sorbet/rbi/gems/json@2.6.3.rbi +0 -1541
- data/sorbet/rbi/gems/multi_json@1.15.0.rbi +0 -267
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +0 -158
- data/sorbet/rbi/gems/openssl@3.0.1.rbi +0 -1735
- data/sorbet/rbi/gems/parallel@1.22.1.rbi +0 -277
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +0 -407
- data/sorbet/rbi/gems/regexp_parser@2.6.1.rbi +0 -3481
- data/sorbet/rbi/gems/reline@0.3.1.rbi +0 -8
- data/sorbet/rbi/gems/rexml@3.2.5.rbi +0 -4717
- data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +0 -1239
- data/sorbet/rbi/gems/thor@1.2.1.rbi +0 -3956
- data/sorbet/rbi/gems/unicode-display_width@2.3.0.rbi +0 -48
- data/sorbet/rbi/gems/webrick@1.7.0.rbi +0 -2555
- data/sorbet/rbi/gems/yard-sorbet@0.7.0.rbi +0 -391
- data/sorbet/rbi/gems/yard@0.9.28.rbi +0 -17816
- data/sorbet/rbi/gems/zeitwerk@2.6.6.rbi +0 -950
@@ -7,30 +7,31 @@ module Paseto
|
|
7
7
|
extend T::Sig
|
8
8
|
extend T::Helpers
|
9
9
|
|
10
|
+
include Singleton
|
10
11
|
include Interface::Version
|
11
12
|
|
12
13
|
sig(:final) { override.params(key: String, nonce: String, payload: String).returns(String) }
|
13
|
-
def
|
14
|
+
def crypt(key:, nonce:, payload:)
|
14
15
|
Paseto::Sodium::Stream::XChaCha20Xor.new(key).encrypt(nonce, payload)
|
15
16
|
end
|
16
17
|
|
17
18
|
sig(:final) { override.params(data: String, digest_size: Integer).returns(String) }
|
18
|
-
def
|
19
|
-
RbNaCl::Hash.blake2b(data, digest_size:
|
19
|
+
def digest(data, digest_size: 32)
|
20
|
+
RbNaCl::Hash.blake2b(data, digest_size:)
|
20
21
|
end
|
21
22
|
|
22
23
|
sig(:final) { override.returns(Integer) }
|
23
|
-
def
|
24
|
+
def digest_bytes
|
24
25
|
32
|
25
26
|
end
|
26
27
|
|
27
28
|
sig(:final) { override.params(data: String, key: String, digest_size: Integer).returns(String) }
|
28
|
-
def
|
29
|
-
RbNaCl::Hash.blake2b(data, key
|
29
|
+
def hmac(data, key:, digest_size: 32)
|
30
|
+
RbNaCl::Hash.blake2b(data, key:, digest_size:)
|
30
31
|
end
|
31
32
|
|
32
33
|
sig(:final) { override.returns(T.class_of(Operations::ID::IDv4)) }
|
33
|
-
def
|
34
|
+
def id
|
34
35
|
Operations::ID::IDv4
|
35
36
|
end
|
36
37
|
|
@@ -42,7 +43,7 @@ module Paseto
|
|
42
43
|
parameters: T.any(Symbol, Integer)
|
43
44
|
).returns(String)
|
44
45
|
end
|
45
|
-
def
|
46
|
+
def kdf(password, salt:, length:, **parameters)
|
46
47
|
memlimit = RbNaCl::PasswordHash::Argon2.memlimit_value(parameters[:memlimit])
|
47
48
|
opslimit = RbNaCl::PasswordHash::Argon2.opslimit_value(parameters[:opslimit])
|
48
49
|
|
@@ -56,42 +57,42 @@ module Paseto
|
|
56
57
|
end
|
57
58
|
|
58
59
|
sig(:final) { override.returns(String) }
|
59
|
-
def
|
60
|
+
def paserk_version
|
60
61
|
'k4'
|
61
62
|
end
|
62
63
|
|
63
64
|
sig(:final) { override.returns(String) }
|
64
|
-
def
|
65
|
+
def pbkd_local_header
|
65
66
|
'k4.local-pw'
|
66
67
|
end
|
67
68
|
|
68
69
|
sig(:final) { override.returns(String) }
|
69
|
-
def
|
70
|
+
def pbkd_secret_header
|
70
71
|
'k4.secret-pw'
|
71
72
|
end
|
72
73
|
|
73
74
|
sig(:final) { override.params(password: String).returns(Operations::PBKD::PBKDv4) }
|
74
|
-
def
|
75
|
+
def pbkw(password)
|
75
76
|
Operations::PBKD::PBKDv4.new(password)
|
76
77
|
end
|
77
78
|
|
78
79
|
sig(:final) { override.params(key: SymmetricKey).returns(Wrappers::PIE::PieV4) }
|
79
|
-
def
|
80
|
+
def pie(key)
|
80
81
|
Wrappers::PIE::PieV4.new(key)
|
81
82
|
end
|
82
83
|
|
83
84
|
sig(:final) { override.params(key: AsymmetricKey).returns(Operations::PKE::PKEv4) }
|
84
|
-
def
|
85
|
+
def pke(key)
|
85
86
|
Operations::PKE::PKEv4.new(key)
|
86
87
|
end
|
87
88
|
|
88
89
|
sig(:final) { override.params(size: Integer).returns(String) }
|
89
|
-
def
|
90
|
+
def random(size)
|
90
91
|
RbNaCl::Random.random_bytes(size)
|
91
92
|
end
|
92
93
|
|
93
94
|
sig(:final) { override.returns(String) }
|
94
|
-
def
|
95
|
+
def version
|
95
96
|
'v4'
|
96
97
|
end
|
97
98
|
end
|
data/lib/paseto/symmetric_key.rb
CHANGED
@@ -34,14 +34,14 @@ module Paseto
|
|
34
34
|
Util.pre_auth_encode(pae_header, n, c, footer, implicit_assertion)
|
35
35
|
.then { |pre_auth| protocol.hmac(pre_auth, key: ak) }
|
36
36
|
.then { |t| "#{n}#{c}#{t}" }
|
37
|
-
.then { |payload| Token.new(payload
|
37
|
+
.then { |payload| Token.new(payload:, version:, purpose:, footer:) }
|
38
38
|
end
|
39
39
|
|
40
40
|
# Verify and decrypt an encrypted Token, with an optional string `implicit_assertion`, and return the plaintext.
|
41
41
|
# If `token` includes a footer, it is treated as authenticated data to be verified but not returned.
|
42
42
|
# `token` must be a `v4.local` type Token.
|
43
43
|
sig(:final) { params(token: Token, implicit_assertion: String).returns(String) }
|
44
|
-
def decrypt(token:, implicit_assertion: '')
|
44
|
+
def decrypt(token:, implicit_assertion: '') # rubocop:disable Metrics/AbcSize
|
45
45
|
raise LucidityError unless header == token.header
|
46
46
|
|
47
47
|
n, c, t = split_payload(token.raw_payload)
|
@@ -52,9 +52,12 @@ module Paseto
|
|
52
52
|
t2 = protocol.hmac(pre_auth, key: ak)
|
53
53
|
raise InvalidAuthenticator unless Util.constant_compare(t, t2)
|
54
54
|
|
55
|
-
protocol.crypt(payload: c, key: ek, nonce: n2)
|
56
|
-
|
57
|
-
|
55
|
+
decrypted = protocol.crypt(payload: c, key: ek, nonce: n2)
|
56
|
+
decrypted.force_encoding('UTF-8')
|
57
|
+
|
58
|
+
raise ParseError, 'invalid payload encoding' unless decrypted.valid_encoding?
|
59
|
+
|
60
|
+
decrypted
|
58
61
|
end
|
59
62
|
|
60
63
|
sig(:final) do
|
@@ -68,7 +71,7 @@ module Paseto
|
|
68
71
|
def encode!(payload, footer: '', implicit_assertion: '', **options)
|
69
72
|
n = T.cast(options.delete(:nonce), T.nilable(String))
|
70
73
|
MultiJson.dump(payload, options)
|
71
|
-
.then { |message| encrypt(message
|
74
|
+
.then { |message| encrypt(message:, footer:, implicit_assertion:, n:) }
|
72
75
|
.then(&:to_s)
|
73
76
|
end
|
74
77
|
|
@@ -82,9 +85,9 @@ module Paseto
|
|
82
85
|
def decode!(payload, implicit_assertion: '', **options)
|
83
86
|
token = Token.parse(payload)
|
84
87
|
|
85
|
-
decrypt(token
|
88
|
+
decrypt(token:, implicit_assertion:)
|
86
89
|
.then { |json| MultiJson.load(json, **options) }
|
87
|
-
.then { |claims| Result.new(claims
|
90
|
+
.then { |claims| Result.new(claims:, footer: token.footer) }
|
88
91
|
end
|
89
92
|
|
90
93
|
sig(:final) { override.returns(String) }
|
@@ -93,6 +96,9 @@ module Paseto
|
|
93
96
|
sig(:final) { override.returns(String) }
|
94
97
|
def pbkw_header = protocol.pbkd_local_header
|
95
98
|
|
99
|
+
sig(:final) { override.returns(String) }
|
100
|
+
def pie_header = "#{paserk_version}.local-wrap.pie."
|
101
|
+
|
96
102
|
sig(:final) { returns(Interface::PIE) }
|
97
103
|
def pie = protocol.pie(self)
|
98
104
|
|
@@ -103,10 +109,10 @@ module Paseto
|
|
103
109
|
def to_bytes = key
|
104
110
|
|
105
111
|
sig(:final) { params(paserk: String).returns(Interface::Key) }
|
106
|
-
def unwrap(paserk) = Paserk.from_paserk(paserk
|
112
|
+
def unwrap(paserk) = Paserk.from_paserk(paserk:, wrapping_key: self)
|
107
113
|
|
108
114
|
sig(:final) { params(key: Interface::Key, nonce: T.nilable(String)).returns(String) }
|
109
|
-
def wrap(key, nonce: nil) = Paserk.wrap(key
|
115
|
+
def wrap(key, nonce: nil) = Paserk.wrap(key:, wrapping_key: self, nonce:)
|
110
116
|
|
111
117
|
private
|
112
118
|
|
data/lib/paseto/token.rb
CHANGED
@@ -34,7 +34,7 @@ module Paseto
|
|
34
34
|
payload = Util.decode64(payload)
|
35
35
|
Util.decode64(footer)
|
36
36
|
.then { |f| serializer.deserialize(f, options) }
|
37
|
-
.then { |f| new(version
|
37
|
+
.then { |f| new(version:, purpose:, payload:, footer: f) }
|
38
38
|
end
|
39
39
|
|
40
40
|
sig { returns(Paseto::Interface::Serializer) }
|
@@ -60,13 +60,14 @@ module Paseto
|
|
60
60
|
.then { |data| "#{version}.#{purpose}.#{data}" }
|
61
61
|
.then(&:freeze)
|
62
62
|
|
63
|
-
@version
|
64
|
-
@purpose
|
65
|
-
@raw_payload = T.let(payload.freeze,
|
66
|
-
@type
|
67
|
-
@footer
|
68
|
-
@raw_footer
|
69
|
-
@str
|
63
|
+
@version = T.let(version.freeze, String)
|
64
|
+
@purpose = T.let(purpose.freeze, String)
|
65
|
+
@raw_payload = T.let(payload.freeze, String)
|
66
|
+
@type = T.let(validate_header, T.class_of(Interface::Key))
|
67
|
+
@footer = T.let(footer, T.any(String, T::Hash[String, T.untyped]))
|
68
|
+
@raw_footer = T.let(raw_footer, String)
|
69
|
+
@str = T.let(paseto, String)
|
70
|
+
@result = T.let(nil, T.nilable(Result))
|
70
71
|
end
|
71
72
|
|
72
73
|
sig do
|
@@ -79,8 +80,8 @@ module Paseto
|
|
79
80
|
def decode!(key, implicit_assertion: '', **options)
|
80
81
|
return @result.claims if @result
|
81
82
|
|
82
|
-
key.decode(@str, implicit_assertion
|
83
|
-
.then { |result| @result =
|
83
|
+
key.decode(@str, implicit_assertion:, **options)
|
84
|
+
.then { |result| @result = result }
|
84
85
|
.then(&:claims)
|
85
86
|
end
|
86
87
|
|
@@ -116,12 +117,13 @@ module Paseto
|
|
116
117
|
|
117
118
|
sig { returns(T.class_of(Interface::Key)) }
|
118
119
|
def validate_header
|
119
|
-
type =
|
120
|
-
|
120
|
+
type = begin
|
121
|
+
TokenTypes.deserialize(header).key_klass
|
122
|
+
rescue KeyError
|
123
|
+
nil
|
124
|
+
end
|
121
125
|
|
122
|
-
raise UnsupportedToken, header
|
123
|
-
rescue KeyError
|
124
|
-
raise UnsupportedToken, header
|
126
|
+
type or raise UnsupportedToken, header
|
125
127
|
end
|
126
128
|
end
|
127
129
|
end
|
data/lib/paseto/token_types.rb
CHANGED
@@ -17,9 +17,9 @@ module Paseto
|
|
17
17
|
case self
|
18
18
|
in V3Local then V3::Local
|
19
19
|
in V3Public then V3::Public
|
20
|
-
in V4Local if Paseto
|
20
|
+
in V4Local if Paseto::HAS_RBNACL
|
21
21
|
V4::Local
|
22
|
-
in V4Public if Paseto
|
22
|
+
in V4Public if Paseto::HAS_RBNACL
|
23
23
|
V4::Public
|
24
24
|
else
|
25
25
|
nil
|
data/lib/paseto/util.rb
CHANGED
@@ -76,7 +76,7 @@ module Paseto
|
|
76
76
|
# Moving the sig out of the conditional triggers a bug in rubocop-sorbet
|
77
77
|
|
78
78
|
# Use a faster comparison when RbNaCl is available
|
79
|
-
if Paseto
|
79
|
+
if Paseto::HAS_RBNACL
|
80
80
|
sig { params(a: String, b: String).returns(T::Boolean) }
|
81
81
|
def self.constant_compare(a, b)
|
82
82
|
h_a = RbNaCl::Hash.blake2b(a)
|
@@ -98,8 +98,7 @@ module Paseto
|
|
98
98
|
def self.openssl?(major, minor = 0, fix = 0, patch = 0)
|
99
99
|
return false if OpenSSL::OPENSSL_VERSION.include?('LibreSSL')
|
100
100
|
|
101
|
-
OpenSSL::OPENSSL_VERSION_NUMBER
|
102
|
-
(major * 0x10000000) + (minor * 0x100000) + (fix * 0x1000) + (patch * 0x10)
|
101
|
+
(major * 0x10000000) + (minor * 0x100000) + (fix * 0x1000) + (patch * 0x10) <= OpenSSL::OPENSSL_VERSION_NUMBER
|
103
102
|
end
|
104
103
|
end
|
105
104
|
end
|
data/lib/paseto/v3/local.rb
CHANGED
data/lib/paseto/v3/public.rb
CHANGED
@@ -50,7 +50,7 @@ module Paseto
|
|
50
50
|
raise LucidityError unless @key.group.curve_name == 'secp384r1'
|
51
51
|
raise InvalidKeyPair unless custom_check_key
|
52
52
|
|
53
|
-
@protocol = T.let(Protocol::Version3.
|
53
|
+
@protocol = T.let(Protocol::Version3.instance, Protocol::Version3)
|
54
54
|
|
55
55
|
super
|
56
56
|
rescue OpenSSL::PKey::ECError => e
|
@@ -68,7 +68,7 @@ module Paseto
|
|
68
68
|
.then { |data| @key.sign_raw(nil, data) }
|
69
69
|
.then { |sig_asn| ASN1::ECDSASignature.from_asn1(sig_asn) }
|
70
70
|
.then { |ecdsa_sig| ecdsa_sig.to_rs(SIGNATURE_PART_LEN) }
|
71
|
-
.then { |sig| Token.new(payload: "#{message}#{sig}", purpose
|
71
|
+
.then { |sig| Token.new(payload: "#{message}#{sig}", purpose:, version:, footer:) }
|
72
72
|
rescue Encoding::CompatibilityError
|
73
73
|
raise ParseError, 'invalid message encoding, must be UTF-8'
|
74
74
|
end
|
@@ -80,9 +80,10 @@ module Paseto
|
|
80
80
|
raise LucidityError unless header == token.header
|
81
81
|
|
82
82
|
payload = token.raw_payload
|
83
|
-
|
83
|
+
signature_end = payload.bytesize - SIGNATURE_BYTE_LEN
|
84
|
+
raise ParseError, 'message too short' if signature_end <= 0
|
84
85
|
|
85
|
-
m = T.must(payload.slice(0,
|
86
|
+
m = T.must(payload.slice(0, signature_end))
|
86
87
|
|
87
88
|
s = T.must(payload.slice(-SIGNATURE_BYTE_LEN, SIGNATURE_BYTE_LEN))
|
88
89
|
.then { |bytes| ASN1::ECDSASignature.from_rs(bytes, SIGNATURE_PART_LEN).to_der }
|
@@ -131,74 +132,12 @@ module Paseto
|
|
131
132
|
|
132
133
|
private
|
133
134
|
|
134
|
-
# TODO: Figure out how to get SimpleCov to cover this consistently. With OSSL1.1.1, most of
|
135
|
-
# this doesn't run. With OSSL3, check_key never raises...
|
136
|
-
# :nocov:
|
137
|
-
|
138
|
-
# The openssl gem as of 3.0.0 will prefer EVP_PKEY_public_check over EC_KEY_check_key
|
139
|
-
# whenever the EVP api is available, which is always for the library here as we're requiring
|
140
|
-
# 3.0.0 or greater. However, this has some problems.
|
141
|
-
#
|
142
|
-
# The behavior of EVP_PKEY_public_check is different between 1.1.1 and 3.x. Specifically,
|
143
|
-
# it no longer calls the custom verifier method in EVP_PKEY_METHOD, and only checks the
|
144
|
-
# correctness of the public component. This leads to a problem when calling EC#key_check,
|
145
|
-
# as the private component is NEVER verified for an ECDSA key through the APIs that the gem
|
146
|
-
# makes available to us.
|
147
|
-
#
|
148
|
-
# Until this is fixed in ruby/openssl, I am working around this by implementing the algorithm
|
149
|
-
# used by EVP_PKEY_pairwise_check through the OpenSSL API.
|
150
|
-
#
|
151
|
-
# BUG: https://github.com/ruby/openssl/issues/563
|
152
|
-
# https://www.openssl.org/docs/man1.1.1/man3/EVP_PKEY_public_check.html
|
153
|
-
# https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_public_check.html
|
154
135
|
sig(:final) { returns(T::Boolean) }
|
155
136
|
def custom_check_key
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
return false
|
160
|
-
end
|
161
|
-
|
162
|
-
return true unless private? && Util.openssl?(3)
|
163
|
-
|
164
|
-
priv_key = @key.private_key
|
165
|
-
group = @key.group
|
166
|
-
|
167
|
-
# int ossl_ec_key_private_check(const EC_KEY *eckey)
|
168
|
-
# {
|
169
|
-
# ...
|
170
|
-
# if (BN_cmp(eckey->priv_key, BN_value_one()) < 0
|
171
|
-
# || BN_cmp(eckey->priv_key, eckey->group->order) >= 0) {
|
172
|
-
# ERR_raise(ERR_LIB_EC, EC_R_INVALID_PRIVATE_KEY);
|
173
|
-
# return 0;
|
174
|
-
# }
|
175
|
-
# ...
|
176
|
-
# }
|
177
|
-
#
|
178
|
-
# https://github.com/openssl/openssl/blob/5ac7cfb56211d18596e3c35baa942542f3c0189a/crypto/ec/ec_key.c#L510
|
179
|
-
# private keys must be in range [1, order-1]
|
180
|
-
return false if priv_key < OpenSSL::BN.new(1) || priv_key > group.order
|
181
|
-
|
182
|
-
# int ossl_ec_key_pairwise_check(const EC_KEY *eckey, BN_CTX *ctx)
|
183
|
-
# {
|
184
|
-
# ...
|
185
|
-
# if (!EC_POINT_mul(eckey->group, point, eckey->priv_key, NULL, NULL, ctx)) {
|
186
|
-
# ERR_raise(ERR_LIB_EC, ERR_R_EC_LIB);
|
187
|
-
# goto err;
|
188
|
-
# }
|
189
|
-
# if (EC_POINT_cmp(eckey->group, point, eckey->pub_key, ctx) != 0) {
|
190
|
-
# ERR_raise(ERR_LIB_EC, EC_R_INVALID_PRIVATE_KEY);
|
191
|
-
# goto err;
|
192
|
-
# }
|
193
|
-
# ...
|
194
|
-
# }
|
195
|
-
#
|
196
|
-
# https://github.com/openssl/openssl/blob/5ac7cfb56211d18596e3c35baa942542f3c0189a/crypto/ec/ec_key.c#L529
|
197
|
-
# Check generator * priv_key = pub_key
|
198
|
-
@key.public_key == group.generator.mul(priv_key)
|
137
|
+
@key.check_key
|
138
|
+
rescue StandardError
|
139
|
+
false
|
199
140
|
end
|
200
|
-
|
201
|
-
# :nocov:
|
202
141
|
end
|
203
142
|
end
|
204
143
|
end
|
data/lib/paseto/v4/local.rb
CHANGED
@@ -21,7 +21,7 @@ module Paseto
|
|
21
21
|
|
22
22
|
sig(:final) { params(ikm: String).void }
|
23
23
|
def initialize(ikm:)
|
24
|
-
@protocol = T.let(Protocol::Version4.
|
24
|
+
@protocol = T.let(Protocol::Version4.instance, Paseto::Protocol::Version4)
|
25
25
|
|
26
26
|
super(ikm)
|
27
27
|
end
|
@@ -31,10 +31,10 @@ module Paseto
|
|
31
31
|
# Derive an encryption key, nonce, and authentication key from an input nonce.
|
32
32
|
sig(:final) { override.params(nonce: String).returns([String, String, String]) }
|
33
33
|
def calc_keys(nonce)
|
34
|
-
tmp = protocol.hmac("paseto-encryption-key#{nonce}", key
|
34
|
+
tmp = protocol.hmac("paseto-encryption-key#{nonce}", key:, digest_size: 56)
|
35
35
|
ek = T.must(tmp[0, 32])
|
36
36
|
n2 = T.must(tmp[-24, 24])
|
37
|
-
ak = protocol.hmac("paseto-auth-key-for-aead#{nonce}", key
|
37
|
+
ak = protocol.hmac("paseto-auth-key-for-aead#{nonce}", key:, digest_size: 32)
|
38
38
|
[ek, n2, ak]
|
39
39
|
end
|
40
40
|
|
data/lib/paseto/v4/public.rb
CHANGED
@@ -44,7 +44,7 @@ module Paseto
|
|
44
44
|
@key = T.let(key, T.any(RbNaCl::SigningKey, RbNaCl::VerifyKey))
|
45
45
|
|
46
46
|
@private = T.let(@key.is_a?(RbNaCl::SigningKey), T::Boolean)
|
47
|
-
@protocol = T.let(Protocol::Version4.
|
47
|
+
@protocol = T.let(Protocol::Version4.instance, Paseto::Protocol::Version4)
|
48
48
|
|
49
49
|
super
|
50
50
|
end
|
@@ -58,7 +58,7 @@ module Paseto
|
|
58
58
|
Util.pre_auth_encode(pae_header, message, footer, implicit_assertion)
|
59
59
|
.then { |m2| @key.sign(m2) }
|
60
60
|
.then { |sig| "#{message}#{sig}" }
|
61
|
-
.then { |payload| Token.new(payload
|
61
|
+
.then { |payload| Token.new(payload:, purpose:, version:, footer:) }
|
62
62
|
end
|
63
63
|
|
64
64
|
# Verify the signature of `token`, with an optional binding `implicit_assertion`. `token` must be a `v4.public`` type Token.
|
@@ -152,10 +152,7 @@ module Paseto
|
|
152
152
|
def ossl_ed25519_private_key?(key)
|
153
153
|
raise LucidityError, "expected Ed25519 key, got #{key.oid}" unless key.oid == 'ED25519'
|
154
154
|
|
155
|
-
|
156
|
-
return key.to_text != "<INVALID PRIVATE KEY>\n" if Util.openssl?(1, 1, 1)
|
157
|
-
|
158
|
-
false
|
155
|
+
key.to_text.start_with?('ED25519 Private-Key')
|
159
156
|
end
|
160
157
|
|
161
158
|
sig(:final) { returns(RbNaCl::VerifyKey) }
|
data/lib/paseto/validator.rb
CHANGED
@@ -29,7 +29,7 @@ module Paseto
|
|
29
29
|
return unless (aud = options[:verify_aud])
|
30
30
|
|
31
31
|
given = payload['aud']
|
32
|
-
raise InvalidAudience, "Invalid audience. Expected #{aud}, got #{given || '<none>'}"
|
32
|
+
raise InvalidAudience, "Invalid audience. Expected #{aud}, got #{given || '<none>'}" unless [*aud].intersect?([*given])
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
data/lib/paseto/version.rb
CHANGED
data/lib/paseto/versions.rb
CHANGED
@@ -17,8 +17,8 @@ module Paseto
|
|
17
17
|
sig { returns(Interface::Version) }
|
18
18
|
def instance
|
19
19
|
case self
|
20
|
-
when V3Version, V3Str, K3Str then Protocol::Version3.
|
21
|
-
when V4Version, V4Str, K4Str then Protocol::Version4.
|
20
|
+
when V3Version, V3Str, K3Str then Protocol::Version3.instance
|
21
|
+
when V4Version, V4Str, K4Str then Protocol::Version4.instance
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -13,33 +13,20 @@ module Paseto
|
|
13
13
|
DOMAIN_SEPARATOR_AUTH = "\x81"
|
14
14
|
DOMAIN_SEPARATOR_ENCRYPT = "\x80"
|
15
15
|
|
16
|
-
sig { override.
|
17
|
-
|
18
|
-
b = Util.decode64(data)
|
19
|
-
{
|
20
|
-
t: T.must(b.byteslice(0, 48)),
|
21
|
-
n: T.must(b.byteslice(48, 32)),
|
22
|
-
c: T.must(b.byteslice(80..))
|
23
|
-
}
|
24
|
-
end
|
16
|
+
sig { override.returns(String) }
|
17
|
+
attr_reader :local_header
|
25
18
|
|
26
19
|
sig { override.returns(Protocol::Version3) }
|
27
|
-
|
28
|
-
Protocol::Version3.new
|
29
|
-
end
|
20
|
+
attr_reader :protocol
|
30
21
|
|
31
22
|
sig { override.returns(String) }
|
32
|
-
|
33
|
-
'k3.local-wrap.pie.'
|
34
|
-
end
|
35
|
-
|
36
|
-
sig { override.returns(String) }
|
37
|
-
def secret_header
|
38
|
-
'k3.secret-wrap.pie.'
|
39
|
-
end
|
23
|
+
attr_reader :secret_header
|
40
24
|
|
41
25
|
sig { params(wrapping_key: SymmetricKey).void }
|
42
26
|
def initialize(wrapping_key)
|
27
|
+
@local_header = T.let('k3.local-wrap.pie.', String)
|
28
|
+
@protocol = T.let(Protocol::Version3.instance, Protocol::Version3)
|
29
|
+
@secret_header = T.let('k3.secret-wrap.pie.', String)
|
43
30
|
@wrapping_key = wrapping_key
|
44
31
|
end
|
45
32
|
|
@@ -53,6 +40,16 @@ module Paseto
|
|
53
40
|
protocol.hmac(payload, key: auth_key)
|
54
41
|
end
|
55
42
|
|
43
|
+
sig { override.params(data: String).returns({ t: String, n: String, c: String }) }
|
44
|
+
def decode_and_split(data)
|
45
|
+
b = Util.decode64(data)
|
46
|
+
{
|
47
|
+
t: T.must(b.byteslice(0, 48)),
|
48
|
+
n: T.must(b.byteslice(48, 32)),
|
49
|
+
c: T.must(b.byteslice(80..))
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
56
53
|
sig { override.returns(String) }
|
57
54
|
def random_nonce
|
58
55
|
protocol.random(32)
|
@@ -64,7 +61,7 @@ module Paseto
|
|
64
61
|
ek = T.must(x[0, 32])
|
65
62
|
n2 = T.must(x[32..])
|
66
63
|
|
67
|
-
protocol.crypt(key: ek, nonce: n2, payload:
|
64
|
+
protocol.crypt(key: ek, nonce: n2, payload:)
|
68
65
|
end
|
69
66
|
end
|
70
67
|
end
|
@@ -13,33 +13,20 @@ module Paseto
|
|
13
13
|
DOMAIN_SEPARATOR_AUTH = "\x81"
|
14
14
|
DOMAIN_SEPARATOR_ENCRYPT = "\x80"
|
15
15
|
|
16
|
-
sig { override.params(data: String).returns({ t: String, n: String, c: String }) }
|
17
|
-
def self.decode_and_split(data)
|
18
|
-
b = Util.decode64(data)
|
19
|
-
{
|
20
|
-
t: T.must(b.byteslice(0, 32)),
|
21
|
-
n: T.must(b.byteslice(32, 32)),
|
22
|
-
c: T.must(b.byteslice(64..))
|
23
|
-
}
|
24
|
-
end
|
25
|
-
|
26
16
|
sig { override.returns(Protocol::Version4) }
|
27
|
-
|
28
|
-
Protocol::Version4.new
|
29
|
-
end
|
17
|
+
attr_reader :protocol
|
30
18
|
|
31
19
|
sig { override.returns(String) }
|
32
|
-
|
33
|
-
'k4.local-wrap.pie.'
|
34
|
-
end
|
20
|
+
attr_reader :local_header
|
35
21
|
|
36
22
|
sig { override.returns(String) }
|
37
|
-
|
38
|
-
'k4.secret-wrap.pie.'
|
39
|
-
end
|
23
|
+
attr_reader :secret_header
|
40
24
|
|
41
25
|
sig { params(wrapping_key: SymmetricKey).void }
|
42
26
|
def initialize(wrapping_key)
|
27
|
+
@local_header = T.let('k4.local-wrap.pie.', String)
|
28
|
+
@protocol = T.let(Protocol::Version4.instance, Protocol::Version4)
|
29
|
+
@secret_header = T.let('k4.secret-wrap.pie.', String)
|
43
30
|
@wrapping_key = wrapping_key
|
44
31
|
end
|
45
32
|
|
@@ -53,6 +40,16 @@ module Paseto
|
|
53
40
|
protocol.hmac(payload, key: auth_key, digest_size: 32)
|
54
41
|
end
|
55
42
|
|
43
|
+
sig { override.params(data: String).returns({ t: String, n: String, c: String }) }
|
44
|
+
def decode_and_split(data)
|
45
|
+
b = Util.decode64(data)
|
46
|
+
{
|
47
|
+
t: T.must(b.byteslice(0, 32)),
|
48
|
+
n: T.must(b.byteslice(32, 32)),
|
49
|
+
c: T.must(b.byteslice(64..))
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
56
53
|
sig { override.returns(String) }
|
57
54
|
def random_nonce
|
58
55
|
protocol.random(32)
|
@@ -64,7 +61,7 @@ module Paseto
|
|
64
61
|
ek = T.must(x[0, 32])
|
65
62
|
n2 = T.must(x[32..])
|
66
63
|
|
67
|
-
protocol.crypt(key: ek, nonce: n2, payload:
|
64
|
+
protocol.crypt(key: ek, nonce: n2, payload:)
|
68
65
|
end
|
69
66
|
end
|
70
67
|
end
|
data/lib/paseto/wrappers/pie.rb
CHANGED
@@ -20,11 +20,11 @@ module Paseto
|
|
20
20
|
raise LucidityError unless key.version == @wrapping_key.version
|
21
21
|
|
22
22
|
nonce ||= @coder.random_nonce
|
23
|
-
header = pie_header
|
23
|
+
header = key.pie_header
|
24
24
|
|
25
|
-
c = @coder.crypt(nonce
|
25
|
+
c = @coder.crypt(nonce:, payload: key.to_bytes)
|
26
26
|
|
27
|
-
ak = @coder.authentication_key(nonce:
|
27
|
+
ak = @coder.authentication_key(nonce:)
|
28
28
|
t = @coder.authentication_tag(payload: "#{header}#{nonce}#{c}", auth_key: ak)
|
29
29
|
|
30
30
|
[header, Util.encode64("#{t}#{nonce}#{c}")].join
|
@@ -52,20 +52,6 @@ module Paseto
|
|
52
52
|
|
53
53
|
PaserkTypes.deserialize("#{version}.#{type}").generate(ptk)
|
54
54
|
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
sig { params(key: Interface::Key).returns(String) }
|
59
|
-
def pie_header(key)
|
60
|
-
case key
|
61
|
-
when SymmetricKey then @coder.local_header
|
62
|
-
when AsymmetricKey then @coder.secret_header
|
63
|
-
else
|
64
|
-
# :nocov:
|
65
|
-
raise ArgumentError, 'not a valid type of key'
|
66
|
-
# :nocov:
|
67
|
-
end
|
68
|
-
end
|
69
55
|
end
|
70
56
|
end
|
71
57
|
end
|
data/lib/paseto.rb
CHANGED
@@ -42,6 +42,8 @@ module Paseto
|
|
42
42
|
extend T::Sig
|
43
43
|
extend Configuration
|
44
44
|
|
45
|
+
HAS_RBNACL = T.let(!defined?(RbNaCl).nil?, T::Boolean)
|
46
|
+
|
45
47
|
class Error < StandardError; end
|
46
48
|
|
47
49
|
class AlgorithmError < Error; end
|
@@ -89,11 +91,6 @@ module Paseto
|
|
89
91
|
class ParseError < Error; end
|
90
92
|
# Tried to work with a V4 token without RbNaCl loaded
|
91
93
|
class UnsupportedToken < ParseError; end
|
92
|
-
|
93
|
-
sig { returns(T::Boolean) }
|
94
|
-
def self.rbnacl?
|
95
|
-
!!defined?(RbNaCl)
|
96
|
-
end
|
97
94
|
end
|
98
95
|
|
99
96
|
loader.eager_load
|