ruby-paseto 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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