ruby-paseto 0.1.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.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +8 -0
  3. data/CODE_OF_CONDUCT.md +84 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +549 -0
  6. data/lib/paseto/asn1/algorithm_identifier.rb +17 -0
  7. data/lib/paseto/asn1/curve_private_key.rb +22 -0
  8. data/lib/paseto/asn1/ec_private_key.rb +27 -0
  9. data/lib/paseto/asn1/ecdsa_full_r.rb +26 -0
  10. data/lib/paseto/asn1/ecdsa_sig_value.rb +23 -0
  11. data/lib/paseto/asn1/ecdsa_signature.rb +49 -0
  12. data/lib/paseto/asn1/ed25519_identifier.rb +15 -0
  13. data/lib/paseto/asn1/named_curve.rb +17 -0
  14. data/lib/paseto/asn1/one_asymmetric_key.rb +32 -0
  15. data/lib/paseto/asn1/private_key.rb +17 -0
  16. data/lib/paseto/asn1/private_key_algorithm_identifier.rb +17 -0
  17. data/lib/paseto/asn1/public_key.rb +17 -0
  18. data/lib/paseto/asn1/subject_public_key_info.rb +28 -0
  19. data/lib/paseto/asn1.rb +101 -0
  20. data/lib/paseto/asymmetric_key.rb +100 -0
  21. data/lib/paseto/configuration/box.rb +23 -0
  22. data/lib/paseto/configuration/decode_configuration.rb +68 -0
  23. data/lib/paseto/configuration.rb +18 -0
  24. data/lib/paseto/interface/i_d.rb +23 -0
  25. data/lib/paseto/interface/key.rb +113 -0
  26. data/lib/paseto/interface/pbkd.rb +83 -0
  27. data/lib/paseto/interface/pie.rb +59 -0
  28. data/lib/paseto/interface/pke.rb +86 -0
  29. data/lib/paseto/interface/serializer.rb +19 -0
  30. data/lib/paseto/interface/version.rb +161 -0
  31. data/lib/paseto/interface/wrapper.rb +20 -0
  32. data/lib/paseto/operations/i_d.rb +48 -0
  33. data/lib/paseto/operations/id/i_dv3.rb +20 -0
  34. data/lib/paseto/operations/id/i_dv4.rb +20 -0
  35. data/lib/paseto/operations/pbkd/p_b_k_dv3.rb +85 -0
  36. data/lib/paseto/operations/pbkd/p_b_k_dv4.rb +94 -0
  37. data/lib/paseto/operations/pbkw.rb +73 -0
  38. data/lib/paseto/operations/pke/p_k_ev3.rb +97 -0
  39. data/lib/paseto/operations/pke/p_k_ev4.rb +95 -0
  40. data/lib/paseto/operations/pke.rb +57 -0
  41. data/lib/paseto/operations/wrap.rb +29 -0
  42. data/lib/paseto/paserk.rb +55 -0
  43. data/lib/paseto/paserk_types.rb +46 -0
  44. data/lib/paseto/protocol/version3.rb +100 -0
  45. data/lib/paseto/protocol/version4.rb +99 -0
  46. data/lib/paseto/result.rb +9 -0
  47. data/lib/paseto/serializer/optional_json.rb +30 -0
  48. data/lib/paseto/serializer/raw.rb +23 -0
  49. data/lib/paseto/sodium/curve_25519.rb +46 -0
  50. data/lib/paseto/sodium/safe_ed25519_loader.rb +19 -0
  51. data/lib/paseto/sodium/stream/base.rb +82 -0
  52. data/lib/paseto/sodium/stream/x_cha_cha20_xor.rb +31 -0
  53. data/lib/paseto/sodium.rb +5 -0
  54. data/lib/paseto/symmetric_key.rb +119 -0
  55. data/lib/paseto/token.rb +127 -0
  56. data/lib/paseto/token_types.rb +29 -0
  57. data/lib/paseto/util.rb +105 -0
  58. data/lib/paseto/v3/local.rb +63 -0
  59. data/lib/paseto/v3/public.rb +204 -0
  60. data/lib/paseto/v4/local.rb +56 -0
  61. data/lib/paseto/v4/public.rb +169 -0
  62. data/lib/paseto/validator.rb +154 -0
  63. data/lib/paseto/verifiers/footer.rb +30 -0
  64. data/lib/paseto/verifiers/payload.rb +42 -0
  65. data/lib/paseto/verify.rb +48 -0
  66. data/lib/paseto/version.rb +6 -0
  67. data/lib/paseto/versions.rb +25 -0
  68. data/lib/paseto/wrappers/pie/pie_v3.rb +72 -0
  69. data/lib/paseto/wrappers/pie/pie_v4.rb +72 -0
  70. data/lib/paseto/wrappers/pie.rb +71 -0
  71. data/lib/paseto.rb +99 -0
  72. data/paseto.gemspec +58 -0
  73. data/sorbet/config +3 -0
  74. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  75. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  76. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1083 -0
  77. data/sorbet/rbi/gems/docile@1.4.0.rbi +376 -0
  78. data/sorbet/rbi/gems/ffi@1.15.5.rbi +1994 -0
  79. data/sorbet/rbi/gems/io-console@0.5.11.rbi +8 -0
  80. data/sorbet/rbi/gems/irb@1.5.1.rbi +342 -0
  81. data/sorbet/rbi/gems/json@2.6.3.rbi +1541 -0
  82. data/sorbet/rbi/gems/multi_json@1.15.0.rbi +267 -0
  83. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  84. data/sorbet/rbi/gems/oj@3.13.23.rbi +603 -0
  85. data/sorbet/rbi/gems/openssl@3.0.1.rbi +1735 -0
  86. data/sorbet/rbi/gems/parallel@1.22.1.rbi +277 -0
  87. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +407 -0
  88. data/sorbet/rbi/gems/rake@13.0.6.rbi +3021 -0
  89. data/sorbet/rbi/gems/rbnacl@7.1.1.rbi +3218 -0
  90. data/sorbet/rbi/gems/regexp_parser@2.6.1.rbi +3481 -0
  91. data/sorbet/rbi/gems/reline@0.3.1.rbi +8 -0
  92. data/sorbet/rbi/gems/rexml@3.2.5.rbi +4717 -0
  93. data/sorbet/rbi/gems/rspec-core@3.12.0.rbi +10887 -0
  94. data/sorbet/rbi/gems/rspec-expectations@3.12.0.rbi +8090 -0
  95. data/sorbet/rbi/gems/rspec-mocks@3.12.0.rbi +5300 -0
  96. data/sorbet/rbi/gems/rspec-support@3.12.0.rbi +1617 -0
  97. data/sorbet/rbi/gems/rspec@3.12.0.rbi +88 -0
  98. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +1239 -0
  99. data/sorbet/rbi/gems/simplecov-html@0.12.3.rbi +219 -0
  100. data/sorbet/rbi/gems/simplecov@0.21.2.rbi +2135 -0
  101. data/sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi +8 -0
  102. data/sorbet/rbi/gems/thor@1.2.1.rbi +3956 -0
  103. data/sorbet/rbi/gems/timecop@0.9.6.rbi +350 -0
  104. data/sorbet/rbi/gems/unicode-display_width@2.3.0.rbi +48 -0
  105. data/sorbet/rbi/gems/webrick@1.7.0.rbi +2555 -0
  106. data/sorbet/rbi/gems/yard-sorbet@0.7.0.rbi +391 -0
  107. data/sorbet/rbi/gems/yard@0.9.28.rbi +17816 -0
  108. data/sorbet/rbi/gems/zeitwerk@2.6.6.rbi +950 -0
  109. data/sorbet/rbi/shims/multi_json.rbi +19 -0
  110. data/sorbet/rbi/shims/openssl.rbi +111 -0
  111. data/sorbet/rbi/shims/rbnacl.rbi +65 -0
  112. data/sorbet/rbi/shims/zeitwerk.rbi +6 -0
  113. data/sorbet/rbi/todo.rbi +7 -0
  114. data/sorbet/tapioca/config.yml +30 -0
  115. data/sorbet/tapioca/require.rb +12 -0
  116. metadata +376 -0
@@ -0,0 +1,85 @@
1
+ # encoding: binary
2
+ # typed: strict
3
+ # frozen_string_literal: true
4
+
5
+ module Paseto
6
+ module Operations
7
+ module PBKD
8
+ class PBKDv3
9
+ extend T::Sig
10
+
11
+ include Interface::PBKD
12
+
13
+ sig { override.returns(Protocol::Version3) }
14
+ def self.protocol
15
+ Protocol::Version3.new
16
+ end
17
+
18
+ sig { params(password: String).void }
19
+ def initialize(password)
20
+ @password = password
21
+ end
22
+
23
+ sig do
24
+ override.params(
25
+ header: String,
26
+ pre_key: String,
27
+ salt: String,
28
+ nonce: String,
29
+ edk: String,
30
+ params: T::Hash[Symbol, Integer]
31
+ ).returns([String, String])
32
+ end
33
+ def authenticate(header:, pre_key:, salt:, nonce:, edk:, params:) # rubocop:disable Metrics/ParameterLists
34
+ iterations = Util.int_to_be32(T.must(params[:iterations]))
35
+
36
+ message = "#{salt}#{iterations}#{nonce}#{edk}"
37
+
38
+ ak = protocol.digest("#{Operations::PBKW::DOMAIN_SEPARATOR_AUTH}#{pre_key}")
39
+ tag = protocol.hmac("#{header}.#{message}", key: ak)
40
+ [message, tag]
41
+ end
42
+
43
+ sig { override.params(salt: String, params: T::Hash[Symbol, Integer]).returns(String) }
44
+ def pre_key(salt:, params:)
45
+ iterations = T.must(params[:iterations])
46
+ protocol.kdf(@password, salt: salt, length: 32, iterations: iterations)
47
+ end
48
+
49
+ sig { override.returns(String) }
50
+ def random_nonce
51
+ protocol.random(16)
52
+ end
53
+
54
+ sig { override.returns(String) }
55
+ def random_salt
56
+ protocol.random(32)
57
+ end
58
+
59
+ sig do
60
+ override.params(payload: String).returns(
61
+ {
62
+ salt: String,
63
+ nonce: String,
64
+ edk: String,
65
+ tag: String,
66
+ params: T::Hash[Symbol, Integer]
67
+ }
68
+ )
69
+ end
70
+ def decode(payload)
71
+ data = Util.decode64(payload)
72
+ edk_len = data.bytesize - 100
73
+ iterations = Util.be32_to_int(T.must(data.byteslice(32, 4)))
74
+ {
75
+ salt: T.must(data.byteslice(0, 32)),
76
+ nonce: T.must(data.byteslice(36, 16)),
77
+ edk: T.must(data.byteslice(52, edk_len)),
78
+ tag: T.must(data.byteslice(-48, 48)),
79
+ params: { iterations: iterations }
80
+ }
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,94 @@
1
+ # encoding: binary
2
+ # typed: strict
3
+ # frozen_string_literal: true
4
+
5
+ module Paseto
6
+ module Operations
7
+ module PBKD
8
+ class PBKDv4
9
+ extend T::Sig
10
+
11
+ include Interface::PBKD
12
+
13
+ sig { override.returns(Protocol::Version4) }
14
+ def self.protocol
15
+ Protocol::Version4.new
16
+ end
17
+
18
+ sig { params(password: String).void }
19
+ def initialize(password)
20
+ @password = password
21
+ end
22
+
23
+ sig do
24
+ override.params(
25
+ header: String,
26
+ pre_key: String,
27
+ salt: String,
28
+ nonce: String,
29
+ edk: String,
30
+ params: T::Hash[Symbol, Integer]
31
+ ).returns([String, String])
32
+ end
33
+ def authenticate(header:, pre_key:, salt:, nonce:, edk:, params:) # rubocop:disable Metrics/ParameterLists
34
+ memlimit_int = RbNaCl::PasswordHash::Argon2.memlimit_value(params[:memlimit])
35
+ opslimit_int = RbNaCl::PasswordHash::Argon2.opslimit_value(params[:opslimit])
36
+ memlimit = Util.int_to_be64(memlimit_int)
37
+ opslimit = Util.int_to_be32(opslimit_int)
38
+ para = Util.int_to_be32(1)
39
+
40
+ message = "#{salt}#{memlimit}#{opslimit}#{para}#{nonce}#{edk}"
41
+
42
+ ak = protocol.digest("#{Operations::PBKW::DOMAIN_SEPARATOR_AUTH}#{pre_key}", digest_size: 32)
43
+ tag = protocol.hmac("#{header}.#{message}", key: ak, digest_size: 32)
44
+
45
+ [message, tag]
46
+ end
47
+
48
+ sig { override.params(salt: String, params: T::Hash[Symbol, Integer]).returns(String) }
49
+ def pre_key(salt:, params:)
50
+ opslimit = T.must(params[:opslimit])
51
+ memlimit = T.must(params[:memlimit])
52
+ protocol.kdf(@password, salt: salt, length: 32, opslimit: opslimit, memlimit: memlimit)
53
+ end
54
+
55
+ sig { override.returns(String) }
56
+ def random_nonce
57
+ protocol.random(24)
58
+ end
59
+
60
+ sig { override.returns(String) }
61
+ def random_salt
62
+ protocol.random(16)
63
+ end
64
+
65
+ sig do
66
+ override.params(payload: String).returns(
67
+ {
68
+ salt: String,
69
+ nonce: String,
70
+ edk: String,
71
+ tag: String,
72
+ params: T::Hash[Symbol, Integer]
73
+ }
74
+ )
75
+ end
76
+ def decode(payload) # rubocop:disable Metrics/AbcSize
77
+ data = Util.decode64(payload)
78
+ edk_len = data.bytesize - 88
79
+ {
80
+ salt: T.must(data.byteslice(0, 16)),
81
+ nonce: T.must(data.byteslice(32, 24)),
82
+ edk: T.must(data.byteslice(56, edk_len)),
83
+ tag: T.must(data.byteslice(-32, 32)),
84
+ params: {
85
+ memlimit: Util.be64_to_int(T.must(data.byteslice(16, 8))),
86
+ opslimit: Util.be32_to_int(T.must(data.byteslice(24, 4))),
87
+ para: Util.be32_to_int(T.must(data.byteslice(28, 4)))
88
+ }
89
+ }
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,73 @@
1
+ # encoding: binary
2
+ # typed: strict
3
+ # frozen_string_literal: true
4
+
5
+ module Paseto
6
+ module Operations
7
+ class PBKW
8
+ DOMAIN_SEPARATOR_ENCRYPT = T.let("\xFF", String)
9
+ DOMAIN_SEPARATOR_AUTH = T.let("\xFE", String)
10
+
11
+ extend T::Sig
12
+
13
+ sig { params(key: Interface::Key, password: String, options: T::Hash[Symbol, T.any(Integer, Symbol)]).returns(String) }
14
+ def self.pbkw(key, password, options = {})
15
+ new(key.protocol, password).encode(key, options)
16
+ end
17
+
18
+ sig { params(version: Interface::Version, password: String).void }
19
+ def initialize(version, password)
20
+ @coder = T.let(version.pbkw(password), Interface::PBKD)
21
+ end
22
+
23
+ sig { params(key: Interface::Key, options: T::Hash[Symbol, T.any(Integer, Symbol)]).returns(String) }
24
+ def encode(key, options)
25
+ raise LucidityError unless key.protocol == @coder.protocol
26
+
27
+ opts = default_options.merge(options)
28
+
29
+ h = key.pbkw_header
30
+ salt = @coder.random_salt
31
+ nonce = @coder.random_nonce
32
+
33
+ pre_key = @coder.pre_key(salt: salt, params: opts)
34
+
35
+ edk = @coder.crypt(payload: key.to_bytes, key: pre_key, nonce: nonce)
36
+
37
+ message, t = @coder.authenticate(header: h, pre_key: pre_key, salt: salt, nonce: nonce, edk: edk, params: opts)
38
+
39
+ data = Util.encode64("#{message}#{t}")
40
+ "#{h}.#{data}"
41
+ end
42
+
43
+ sig { params(paserk: String).returns(Interface::Key) }
44
+ def decode(paserk)
45
+ paserk.split('.') => [version, type, data]
46
+ raise LucidityError unless version == @coder.paserk_version
47
+
48
+ header = "#{version}.#{type}"
49
+
50
+ @coder.decode(data) => {salt:, nonce:, edk:, tag:, params:}
51
+
52
+ pre_key = @coder.pre_key(salt: salt, params: params)
53
+
54
+ _, t2 = @coder.authenticate(header: header, pre_key: pre_key, salt: salt, nonce: nonce, edk: edk, params: params)
55
+ raise InvalidAuthenticator unless Util.constant_compare(t2, tag)
56
+
57
+ ptk = @coder.crypt(payload: edk, key: pre_key, nonce: nonce)
58
+ PaserkTypes.deserialize(header).generate(ptk)
59
+ end
60
+
61
+ private
62
+
63
+ sig { returns({ iterations: Integer, memlimit: Symbol, opslimit: Symbol }) }
64
+ def default_options
65
+ {
66
+ iterations: 100_000,
67
+ memlimit: :interactive,
68
+ opslimit: :interactive
69
+ }
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,97 @@
1
+ # encoding: binary
2
+ # typed: strict
3
+ # frozen_string_literal: true
4
+
5
+ module Paseto
6
+ module Operations
7
+ class PKE
8
+ class PKEv3
9
+ extend T::Sig
10
+
11
+ include Interface::PKE
12
+
13
+ sig { override.params(esk: OpenSSL::PKey::EC).returns(String) }
14
+ def self.epk_bytes_from_esk(esk)
15
+ esk.public_key.to_octet_string(:compressed)
16
+ end
17
+
18
+ sig(:final) { override.returns(OpenSSL::PKey::EC) }
19
+ def self.generate_ephemeral_key
20
+ OpenSSL::PKey::EC.generate('secp384r1')
21
+ end
22
+
23
+ sig(:final) { override.returns(String) }
24
+ def self.header
25
+ 'k3.seal.'
26
+ end
27
+
28
+ sig { override.returns(Protocol::Version3) }
29
+ def self.protocol
30
+ Protocol::Version3.new
31
+ end
32
+
33
+ sig { override.params(encoded_data: String).returns([String, OpenSSL::PKey::EC::Point, String]) }
34
+ def self.split(encoded_data)
35
+ data = Util.decode64(encoded_data)
36
+
37
+ t = T.must(data.slice(0, 48))
38
+
39
+ epk_bytes = T.must(data.slice(48, 49))
40
+ epk = OpenSSL::PKey::EC::Point.new(OpenSSL::PKey::EC::Group.new('secp384r1'), epk_bytes)
41
+
42
+ edk = T.must(data.slice(97, 32))
43
+
44
+ [t, epk, edk]
45
+ end
46
+
47
+ sig { params(sealing_key: AsymmetricKey).void }
48
+ def initialize(sealing_key)
49
+ raise LucidityError unless sealing_key.is_a? V3::Public
50
+
51
+ @sealing_key = T.let(sealing_key, V3::Public)
52
+ @pk = T.let(@sealing_key.public_bytes, String)
53
+ end
54
+
55
+ sig { override.params(message: String, ek: String, n: String).returns(SymmetricKey) }
56
+ def decrypt(message:, ek:, n:)
57
+ pdk = protocol.crypt(key: ek, nonce: n, payload: message)
58
+ V3::Local.new(ikm: pdk)
59
+ end
60
+
61
+ sig { override.params(xk: String, epk: OpenSSL::PKey::EC::Point).returns({ ek: String, n: String }) }
62
+ def derive_ek_n(xk:, epk:)
63
+ epk_bytes = epk.to_octet_string(:compressed)
64
+
65
+ x = protocol.digest("#{DOMAIN_SEPARATOR_ENCRYPT}#{header}#{xk}#{epk_bytes}#{@pk}")
66
+
67
+ ek = T.must(x[0, 32])
68
+ n = T.must(x[32, 16])
69
+
70
+ { ek: ek, n: n }
71
+ end
72
+
73
+ sig { override.params(xk: String, epk: OpenSSL::PKey::EC::Point).returns(String) }
74
+ def derive_ak(xk:, epk:)
75
+ epk_bytes = epk.to_octet_string(:compressed)
76
+ protocol.digest("#{DOMAIN_SEPARATOR_AUTH}#{header}#{xk}#{epk_bytes}#{@pk}")
77
+ end
78
+
79
+ sig { override.params(message: String, ek: String, n: String).returns(String) }
80
+ def encrypt(message:, ek:, n:)
81
+ protocol.crypt(payload: message, key: ek, nonce: n)
82
+ end
83
+
84
+ sig { override.params(ak: String, epk: OpenSSL::PKey::EC::Point, edk: String).returns(String) }
85
+ def tag(ak:, epk:, edk:)
86
+ epk_bytes = epk.to_octet_string(:compressed)
87
+ protocol.hmac("#{header}#{epk_bytes}#{edk}", key: ak)
88
+ end
89
+
90
+ private
91
+
92
+ sig { override.returns(Paseto::V3::Public) }
93
+ attr_reader :sealing_key
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,95 @@
1
+ # encoding: binary
2
+ # typed: strict
3
+ # frozen_string_literal: true
4
+
5
+ module Paseto
6
+ module Operations
7
+ class PKE
8
+ class PKEv4
9
+ extend T::Sig
10
+
11
+ include Interface::PKE
12
+
13
+ sig { override.params(esk: RbNaCl::PrivateKey).returns(String) }
14
+ def self.epk_bytes_from_esk(esk)
15
+ esk.public_key.to_bytes
16
+ end
17
+
18
+ sig(:final) { override.returns(RbNaCl::PrivateKey) }
19
+ def self.generate_ephemeral_key
20
+ RbNaCl::PrivateKey.generate
21
+ end
22
+
23
+ sig(:final) { override.returns(String) }
24
+ def self.header
25
+ 'k4.seal.'
26
+ end
27
+
28
+ sig { override.returns(Protocol::Version4) }
29
+ def self.protocol
30
+ Protocol::Version4.new
31
+ end
32
+
33
+ sig { override.params(encoded_data: String).returns([String, RbNaCl::PublicKey, String]) }
34
+ def self.split(encoded_data)
35
+ data = Util.decode64(encoded_data)
36
+
37
+ t = T.must(data.slice(0, 32))
38
+
39
+ epk_bytes = T.must(data.slice(32, 32))
40
+ epk = RbNaCl::PublicKey.new(epk_bytes)
41
+
42
+ edk = T.must(data.slice(64, 32))
43
+
44
+ [t, epk, edk]
45
+ end
46
+
47
+ sig { params(sealing_key: AsymmetricKey).void }
48
+ def initialize(sealing_key)
49
+ raise LucidityError unless sealing_key.is_a? V4::Public
50
+
51
+ @sealing_key = T.let(sealing_key, V4::Public)
52
+ @pk = T.let(@sealing_key.x25519_public_key, RbNaCl::PublicKey)
53
+ @pk_bytes = T.let(@pk.to_bytes, String)
54
+ end
55
+
56
+ sig { override.params(message: String, ek: String, n: String).returns(SymmetricKey) }
57
+ def decrypt(message:, ek:, n:)
58
+ pdk = protocol.crypt(payload: message, key: ek, nonce: n)
59
+ V4::Local.new(ikm: pdk)
60
+ end
61
+
62
+ sig { override.params(xk: String, epk: RbNaCl::PublicKey).returns({ ek: String, n: String }) }
63
+ def derive_ek_n(xk:, epk:)
64
+ ek = protocol.digest(
65
+ "#{DOMAIN_SEPARATOR_ENCRYPT}#{header}#{xk}#{epk.to_bytes}#{@pk_bytes}",
66
+ digest_size: 32
67
+ )
68
+ n = protocol.digest("#{epk.to_bytes}#{@pk_bytes}", digest_size: 24)
69
+
70
+ { ek: ek, n: n }
71
+ end
72
+
73
+ sig { override.params(xk: String, epk: RbNaCl::PublicKey).returns(String) }
74
+ def derive_ak(xk:, epk:)
75
+ protocol.digest([DOMAIN_SEPARATOR_AUTH, header, xk, epk.to_bytes, @pk_bytes].join, digest_size: 32)
76
+ end
77
+
78
+ sig { override.params(message: String, ek: String, n: String).returns(String) }
79
+ def encrypt(message:, ek:, n:)
80
+ protocol.crypt(payload: message, key: ek, nonce: n)
81
+ end
82
+
83
+ sig { override.params(ak: String, epk: RbNaCl::PublicKey, edk: String).returns(String) }
84
+ def tag(ak:, epk:, edk:)
85
+ protocol.hmac("#{header}#{epk.to_bytes}#{edk}", key: ak, digest_size: 32)
86
+ end
87
+
88
+ private
89
+
90
+ sig { override.returns(Paseto::V4::Public) }
91
+ attr_reader :sealing_key
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: binary
2
+ # typed: strict
3
+ # frozen_string_literal: true
4
+
5
+ module Paseto
6
+ module Operations
7
+ class PKE
8
+ extend T::Sig
9
+
10
+ sig { params(sealing_key: AsymmetricKey).void }
11
+ def initialize(sealing_key)
12
+ @sealing_key = sealing_key
13
+ @coder = T.let(@sealing_key.pke, Paseto::Interface::PKE)
14
+ end
15
+
16
+ sig { params(key: SymmetricKey).returns(String) }
17
+ def seal(key)
18
+ raise LucidityError unless key.protocol == @sealing_key.protocol
19
+
20
+ esk = @coder.generate_ephemeral_key
21
+ epk = esk.public_key
22
+
23
+ xk = @sealing_key.ecdh(esk)
24
+
25
+ @coder.derive_ek_n(xk: xk, epk: epk) => {ek:, n:}
26
+
27
+ edk = @coder.encrypt(message: key.to_bytes, ek: ek, n: n)
28
+
29
+ ak = @coder.derive_ak(xk: xk, epk: epk)
30
+ t = @coder.tag(ak: ak, epk: epk, edk: edk)
31
+
32
+ epk_bytes = @coder.epk_bytes_from_esk(esk)
33
+ data = Util.encode64("#{t}#{epk_bytes}#{edk}")
34
+ "#{@coder.header}#{data}"
35
+ end
36
+
37
+ sig { params(paserk: String).returns(Interface::Key) }
38
+ def unseal(paserk)
39
+ paserk.split('.') => [version, type, encoded_data]
40
+ raise LucidityError unless version == @sealing_key.paserk_version
41
+ raise LucidityError unless type == 'seal'
42
+
43
+ t, epk, edk = @coder.split(encoded_data)
44
+
45
+ xk = @sealing_key.ecdh(epk)
46
+
47
+ ak = @coder.derive_ak(xk: xk, epk: epk)
48
+ t2 = @coder.tag(ak: ak, epk: epk, edk: edk)
49
+ raise InvalidAuthenticator unless Util.constant_compare(t, t2)
50
+
51
+ @coder.derive_ek_n(xk: xk, epk: epk) => {ek:, n:}
52
+
53
+ @coder.decrypt(message: edk, ek: ek, n: n)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: binary
2
+ # typed: strict
3
+ # frozen_string_literal: true
4
+
5
+ module Paseto
6
+ module Operations
7
+ class Wrap
8
+ extend T::Sig
9
+
10
+ DOMAIN_SEPARATOR_AUTH = "\x81"
11
+ DOMAIN_SEPARATOR_ENCRYPT = "\x80"
12
+
13
+ sig { params(wrapping_key: SymmetricKey, paserk: [String, String, String, String]).returns(Interface::Key) }
14
+ def self.unwrap(wrapping_key, paserk)
15
+ case paserk
16
+ in [_, _, String => protocol, _] if protocol == 'pie'
17
+ Wrappers::PIE.new(wrapping_key).decode(paserk)
18
+ else
19
+ raise UnknownProtocol, protocol
20
+ end
21
+ end
22
+
23
+ sig { params(key: Interface::Key, wrapping_key: SymmetricKey, nonce: T.nilable(String)).returns(String) }
24
+ def self.wrap(key, wrapping_key:, nonce: nil)
25
+ Wrappers::PIE.new(wrapping_key).encode(key, nonce: nonce)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,55 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Paseto
5
+ module Paserk
6
+ extend T::Sig
7
+ extend T::Helpers
8
+
9
+ requires_ancestor { Kernel }
10
+
11
+ sig do
12
+ params(
13
+ paserk: String,
14
+ wrapping_key: T.nilable(SymmetricKey),
15
+ password: T.nilable(String),
16
+ unsealing_key: T.nilable(AsymmetricKey)
17
+ ).returns(T.untyped)
18
+ end
19
+ def self.from_paserk(paserk:, wrapping_key: nil, password: nil, unsealing_key: nil)
20
+ parts = paserk.split('.')
21
+ case parts
22
+ in [String => version, String => type, String => protocol, String => data] if wrapping_key
23
+ Operations::Wrap.unwrap(T.must(wrapping_key), [version, type, protocol, data])
24
+
25
+ in [String => version, String => type, String => data] if password
26
+ version = Versions.deserialize(version).instance
27
+ Operations::PBKW.new(version, T.must(password)).decode(paserk)
28
+
29
+ in [String => version, String => type, String => data] if unsealing_key
30
+ Operations::PKE.new(T.must(unsealing_key)).unseal(paserk)
31
+
32
+ in [String => version, String => type, String => data] if %w[local secret public].include?(type)
33
+ PaserkTypes.deserialize(paserk.rpartition('.').first).generate(Util.decode64(data))
34
+
35
+ else
36
+ raise UnknownOperation
37
+ end
38
+ end
39
+
40
+ sig { params(key: Interface::Key, wrapping_key: SymmetricKey, nonce: T.nilable(String)).returns(String) }
41
+ def self.wrap(key:, wrapping_key:, nonce: nil)
42
+ Operations::Wrap.wrap(key, wrapping_key: wrapping_key, nonce: nonce)
43
+ end
44
+
45
+ sig { params(key: Interface::Key, password: String, options: T::Hash[Symbol, T.any(Integer, Symbol)]).returns(String) }
46
+ def self.pbkw(key:, password:, options: {})
47
+ Operations::PBKW.pbkw(key, password, options)
48
+ end
49
+
50
+ sig { params(key: SymmetricKey, sealing_key: AsymmetricKey).returns(String) }
51
+ def self.seal(key:, sealing_key:)
52
+ Operations::PKE.new(sealing_key).seal(key)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,46 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Paseto
5
+ class PaserkTypes < T::Enum
6
+ extend T::Sig
7
+
8
+ enums do
9
+ K3Local = new('k3.local')
10
+ K3Secret = new('k3.secret')
11
+ K3Public = new('k3.public')
12
+ K3LocalWrap = new('k3.local-wrap')
13
+ K3SecretWrap = new('k3.secret-wrap')
14
+ K3LocalPBKW = new('k3.local-pw')
15
+ K3SecretPBKW = new('k3.secret-pw')
16
+
17
+ K4Local = new('k4.local')
18
+ K4Secret = new('k4.secret')
19
+ K4Public = new('k4.public')
20
+ K4LocalWrap = new('k4.local-wrap')
21
+ K4SecretWrap = new('k4.secret-wrap')
22
+ K4LocalPBKW = new('k4.local-pw')
23
+ K4SecretPBKW = new('k4.secret-pw')
24
+ end
25
+
26
+ sig { params(input: String).returns(Interface::Key) }
27
+ def generate(input) # rubocop:disable Metrics/MethodLength
28
+ case self
29
+ in K3LocalWrap | K3LocalPBKW | K3Local if input.bytesize == 32
30
+ V3::Local.new(ikm: input)
31
+ in K3SecretWrap | K3SecretPBKW | K3Secret if input.bytesize == 48
32
+ V3::Public.from_scalar_bytes(input)
33
+ in K3Public
34
+ V3::Public.from_public_bytes(input)
35
+ in K4LocalWrap | K4LocalPBKW | K4Local if Paseto.rbnacl? && input.bytesize == 32
36
+ V4::Local.new(ikm: input)
37
+ in K4SecretWrap | K4SecretPBKW | K4Secret if Paseto.rbnacl? && input.bytesize == 64
38
+ V4::Public.from_keypair(input)
39
+ in K4Public
40
+ V4::Public.from_public_bytes(input)
41
+ else
42
+ raise InvalidKeyPair
43
+ end
44
+ end
45
+ end
46
+ end