rnp 0.2.0 → 1.0.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +5 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.adoc +3 -182
  5. data/lib/rnp.rb +12 -3
  6. data/lib/rnp/error.rb +40 -0
  7. data/lib/rnp/ffi/librnp.rb +306 -0
  8. data/lib/rnp/input.rb +99 -0
  9. data/lib/rnp/key.rb +275 -0
  10. data/lib/rnp/misc.rb +71 -0
  11. data/lib/rnp/op/encrypt.rb +181 -0
  12. data/lib/rnp/op/sign.rb +139 -0
  13. data/lib/rnp/op/verify.rb +147 -0
  14. data/lib/rnp/output.rb +121 -0
  15. data/lib/rnp/rnp.rb +595 -0
  16. data/lib/rnp/utils.rb +44 -0
  17. data/lib/rnp/version.rb +8 -3
  18. metadata +124 -50
  19. data/.gitignore +0 -12
  20. data/.rspec +0 -2
  21. data/.travis.yml +0 -5
  22. data/CODE_OF_CONDUCT.md +0 -74
  23. data/Gemfile +0 -4
  24. data/Rakefile +0 -6
  25. data/Use_Cases.adoc +0 -119
  26. data/bin/console +0 -14
  27. data/bin/setup +0 -8
  28. data/example-usage.rb +0 -766
  29. data/examples/highlevel/decrypt_mem.rb +0 -44
  30. data/examples/highlevel/encrypt_mem.rb +0 -46
  31. data/examples/lowlevel/decrypt_file.rb +0 -76
  32. data/examples/lowlevel/decrypt_mem.rb +0 -80
  33. data/examples/lowlevel/encrypt_file.rb +0 -68
  34. data/examples/lowlevel/encrypt_mem.rb +0 -75
  35. data/examples/lowlevel/load_pubkey.rb +0 -118
  36. data/examples/lowlevel/print_keyring_file.rb +0 -68
  37. data/examples/lowlevel/print_keyring_mem.rb +0 -96
  38. data/examples/lowlevel/sign_file.rb +0 -104
  39. data/examples/lowlevel/sign_mem.rb +0 -96
  40. data/examples/lowlevel/verify_file.rb +0 -55
  41. data/examples/lowlevel/verify_mem.rb +0 -61
  42. data/lib/rnp/highlevel.rb +0 -5
  43. data/lib/rnp/highlevel/constants.rb +0 -96
  44. data/lib/rnp/highlevel/keyring.rb +0 -259
  45. data/lib/rnp/highlevel/publickey.rb +0 -150
  46. data/lib/rnp/highlevel/secretkey.rb +0 -318
  47. data/lib/rnp/highlevel/utils.rb +0 -119
  48. data/lib/rnp/lowlevel.rb +0 -6
  49. data/lib/rnp/lowlevel/constants.rb +0 -11
  50. data/lib/rnp/lowlevel/dynarray.rb +0 -129
  51. data/lib/rnp/lowlevel/enums.rb +0 -243
  52. data/lib/rnp/lowlevel/libc.rb +0 -28
  53. data/lib/rnp/lowlevel/libopenssl.rb +0 -15
  54. data/lib/rnp/lowlevel/librnp.rb +0 -213
  55. data/lib/rnp/lowlevel/structs.rb +0 -541
  56. data/lib/rnp/lowlevel/utils.rb +0 -25
  57. data/rnp.gemspec +0 -35
  58. data/rnp/lib/rnp.rb +0 -5
  59. data/rnp/spec/rnp_spec.rb +0 -11
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) 2018 Ribose Inc.
4
+
5
+ require 'ffi'
6
+
7
+ require 'rnp/error'
8
+ require 'rnp/ffi/librnp'
9
+ require 'rnp/utils'
10
+
11
+ class Rnp
12
+ # Signing operation
13
+ class Sign
14
+ # @api private
15
+ attr_reader :ptr
16
+
17
+ # @api private
18
+ def initialize(ptr)
19
+ raise Rnp::Error, 'NULL pointer' if ptr.null?
20
+ @ptr = FFI::AutoPointer.new(ptr, self.class.method(:destroy))
21
+ end
22
+
23
+ # @api private
24
+ def self.destroy(ptr)
25
+ LibRnp.rnp_op_sign_destroy(ptr)
26
+ end
27
+
28
+ def inspect
29
+ Rnp.inspect_ptr(self)
30
+ end
31
+
32
+ # Add a signer.
33
+ #
34
+ # @note The optional (per-signature) options here are not supported by RNP
35
+ # internally at the time of this writing.
36
+ #
37
+ # @param signer [Key] the signer
38
+ # @param hash [String] (see #hash=)
39
+ # @param creation_time (see #creation_time=)
40
+ # @param expiration_time (see #expiration_time=)
41
+ # @return [self]
42
+ def add_signer(signer, hash: nil, creation_time: nil, expiration_time: nil)
43
+ pptr = FFI::MemoryPointer.new(:pointer)
44
+ Rnp.call_ffi(:rnp_op_sign_add_signature, @ptr, signer.ptr, pptr)
45
+ psig = pptr.read_pointer
46
+ self.class.set_signature_options(
47
+ psig,
48
+ hash: hash,
49
+ creation_time: creation_time,
50
+ expiration_time: expiration_time
51
+ )
52
+ end
53
+
54
+ # Set a group of options.
55
+ #
56
+ # @param armored see {#armored=}
57
+ # @param compression see {#compression=}
58
+ # @param hash see {#hash=}
59
+ # @param creation_time see {#creation_time=}
60
+ # @param expiration_time see {#expiration_time=}
61
+ def options=(armored: nil, compression: nil, hash: nil,
62
+ creation_time: nil, expiration_time: nil)
63
+ self.armored = armored unless armored.nil?
64
+ self.compression = compression unless compression.nil?
65
+ self.hash = hash unless hash.nil?
66
+ self.creation_time = creation_time unless creation_time.nil?
67
+ self.expiration_time = expiration_time unless expiration_time.nil?
68
+ end
69
+
70
+ # Set whether the output will be ASCII-armored.
71
+ #
72
+ # @param armored [Boolean] true if the output should be
73
+ # ASCII-armored, false otherwise.
74
+ def armored=(armored)
75
+ Rnp.call_ffi(:rnp_op_sign_set_armor, @ptr, armored)
76
+ end
77
+
78
+ # Set the compression algorithm and level.
79
+ #
80
+ # @param [Hash<Symbol>] compression
81
+ # @option compression [String] :algorithm the compression algorithm
82
+ # (bzip2, etc)
83
+ # @option compression [Integer] :level the compression level. This should
84
+ # generally be between 0 (no compression) and 9 (best compression).
85
+ def compression=(compression)
86
+ if !compression.is_a?(Hash) || Set.new(compression.keys) != Set.new(%i[algorithm level])
87
+ raise ArgumentError,
88
+ 'Compression option must be of the form: {algorithm: \'zlib\', level: 5}'
89
+ end
90
+ Rnp.call_ffi(:rnp_op_sign_set_compression, @ptr, compression[:algorithm],
91
+ compression[:level])
92
+ end
93
+
94
+ # Set the hash algorithm used for the signatures.
95
+ #
96
+ # @param hash [String] the hash algorithm name
97
+ def hash=(hash)
98
+ Rnp.call_ffi(:rnp_op_sign_set_hash, @ptr, hash)
99
+ end
100
+
101
+ # Set the creation time for signatures.
102
+ #
103
+ # @param creation_time [Time, Integer] the creation time to use for all
104
+ # signatures. As an integer, this is the number of seconds since the
105
+ # unix epoch.
106
+ def creation_time=(creation_time)
107
+ creation_time = creation_time.to_i if creation_time.is_a?(::Time)
108
+ Rnp.call_ffi(:rnp_op_sign_set_creation_time, @ptr, creation_time)
109
+ end
110
+
111
+ # Set the expiration time for signatures.
112
+ #
113
+ # @param expiration_time [Integer] the lifetime of the signature(s), as the
114
+ # number of seconds. The actual expiration date/time is the creation time
115
+ # plus this value. A value of 0 will create signatures that do not expire.
116
+ def expiration_time=(expiration_time)
117
+ Rnp.call_ffi(:rnp_op_sign_set_expiration_time, @ptr, expiration_time)
118
+ end
119
+
120
+ # Execute the operation.
121
+ #
122
+ # This should only be called once.
123
+ #
124
+ # @return [void]
125
+ def execute
126
+ Rnp.call_ffi(:rnp_op_sign_execute, @ptr)
127
+ end
128
+
129
+ # @api private
130
+ def self.set_signature_options(psig, hash:, creation_time:,
131
+ expiration_time:)
132
+ Rnp.call_ffi(:rnp_op_sign_signature_set_hash, psig, value) unless hash.nil?
133
+ creation_time = creation_time.to_i if creation_time.is_a?(::Time)
134
+ Rnp.call_ffi(:rnp_op_sign_signature_set_creation_time, psig, value) unless creation_time.nil?
135
+ Rnp.call_ffi(:rnp_op_sign_signature_set_expiration_time, psig, value) unless expiration_time.nil?
136
+ end
137
+ end # class
138
+ end # class
139
+
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) 2018 Ribose Inc.
4
+
5
+ require 'ffi'
6
+
7
+ require 'rnp/error'
8
+ require 'rnp/ffi/librnp'
9
+ require 'rnp/utils'
10
+
11
+ class Rnp
12
+ # Verification operation
13
+ class Verify
14
+ # @api private
15
+ attr_reader :ptr
16
+
17
+ # @api private
18
+ def initialize(ptr)
19
+ raise Rnp::Error, 'NULL pointer' if ptr.null?
20
+ @ptr = FFI::AutoPointer.new(ptr, self.class.method(:destroy))
21
+ @signatures = nil
22
+ end
23
+
24
+ # @api private
25
+ def self.destroy(ptr)
26
+ LibRnp.rnp_op_verify_destroy(ptr)
27
+ end
28
+
29
+ def inspect
30
+ Rnp.inspect_ptr(self)
31
+ end
32
+
33
+ # Execute the operation.
34
+ #
35
+ # This should only be called once.
36
+ #
37
+ # @return [void]
38
+ # @raise [InvalidSignatureError, BadFormatError, ...] if the signature is
39
+ # expired, not correctly formed, invalid, etc.
40
+ def execute
41
+ Rnp.call_ffi(:rnp_op_verify_execute, @ptr)
42
+ end
43
+
44
+ # Check if all signatures are good.
45
+ #
46
+ # @return [Boolean] true if all signatures are valid and not expired.
47
+ def good?
48
+ sigs = signatures
49
+ sigs.size && sigs.all?(&:good?)
50
+ end
51
+
52
+ # Get a list of the checked signatures.
53
+ #
54
+ # @return [Array<Signature>]
55
+ def signatures
56
+ return @signatures unless @signatures.nil?
57
+ @signatures = []
58
+ pptr = FFI::MemoryPointer.new(:pointer)
59
+ (0...signature_count).each do |i|
60
+ Rnp.call_ffi(:rnp_op_verify_get_signature_at, @ptr, i, pptr)
61
+ psig = pptr.read_pointer
62
+ @signatures << Signature.new(psig)
63
+ end
64
+ @signatures
65
+ end
66
+
67
+ # Class representing an individual signature.
68
+ class Signature
69
+ # @api private
70
+ attr_reader :status
71
+ # The hash algorithm used for the signature
72
+ # @return [String]
73
+ attr_reader :hash
74
+ # The key that created the signature
75
+ # @return [Key]
76
+ attr_reader :key
77
+ # The creation time of the signature
78
+ # @return [Time]
79
+ attr_reader :creation_time
80
+ # The expiration (as the number of seconds after {creation_time})
81
+ # @return [Integer]
82
+ attr_reader :expiration_time
83
+
84
+ # @api private
85
+ def initialize(ptr)
86
+ # status
87
+ @status = LibRnp.rnp_op_verify_signature_get_status(ptr)
88
+ pptr = FFI::MemoryPointer.new(:pointer)
89
+
90
+ # creation and expiration
91
+ pcreation_time = FFI::MemoryPointer.new(:uint32)
92
+ pexpiration_time = FFI::MemoryPointer.new(:uint32)
93
+ Rnp.call_ffi(:rnp_op_verify_signature_get_times, ptr, pcreation_time,
94
+ pexpiration_time)
95
+ @creation_time = Time.at(pcreation_time.read(:uint32))
96
+ @expiration_time = pexpiration_time.read(:uint32)
97
+
98
+ # hash
99
+ Rnp.call_ffi(:rnp_op_verify_signature_get_hash, ptr, pptr)
100
+ begin
101
+ phash = pptr.read_pointer
102
+ @hash = phash.read_string unless phash.null?
103
+ ensure
104
+ LibRnp.rnp_buffer_destroy(phash)
105
+ end
106
+
107
+ # key
108
+ Rnp.call_ffi(:rnp_op_verify_signature_get_key, ptr, pptr)
109
+ pkey = pptr.read_pointer
110
+ @key = Key.new(pkey) unless pkey.null?
111
+ end
112
+
113
+ # Check if this signature is good.
114
+ #
115
+ # @return [Boolean] true if the signature is valid and not expired
116
+ def good?
117
+ @status == LibRnp::RNP_SUCCESS
118
+ end
119
+
120
+ # Check if this signature is valid.
121
+ #
122
+ # @note A valid signature may also be expired.
123
+ #
124
+ # @return [Boolean] true if the signature is valid
125
+ def valid?
126
+ @status == LibRnp::RNP_SUCCESS ||
127
+ @status == LibRnp::RNP_ERROR_SIGNATURE_EXPIRED
128
+ end
129
+
130
+ # Check if this signature is expired.
131
+ #
132
+ # @return [Boolean] true if the signature is expired
133
+ def expired?
134
+ @status == LibRnp::RNP_ERROR_SIGNATURE_EXPIRED
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ def signature_count
141
+ pcount = FFI::MemoryPointer.new(:size_t)
142
+ Rnp.call_ffi(:rnp_op_verify_get_signature_count, @ptr, pcount)
143
+ pcount.read(:size_t)
144
+ end
145
+ end # class
146
+ end # class
147
+
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) 2018 Ribose Inc.
4
+
5
+ require 'English'
6
+
7
+ require 'ffi'
8
+
9
+ require 'rnp/error'
10
+ require 'rnp/ffi/librnp'
11
+ require 'rnp/utils'
12
+
13
+ class Rnp
14
+ # Class used to feed data out of RNP.
15
+ #
16
+ # @note When dealing with very large data, prefer {to_path} which should
17
+ # be the most efficient. {to_io} is likely to have more overhead.
18
+ #
19
+ # @example output to a string
20
+ # output = Rnp::Input.to_string('my data')
21
+ # # ... after performing operations
22
+ # output.string
23
+ #
24
+ # @example output to a file
25
+ # Rnp::Input.to_path('/path/to/my/file')
26
+ #
27
+ # @example output to a Ruby IO object
28
+ # Rnp::Input.to_io(File.open('/path/to/file', 'wb'))
29
+ class Output
30
+ # @api private
31
+ attr_reader :ptr
32
+
33
+ # @api private
34
+ def initialize(ptr, writer = nil)
35
+ raise Rnp::Error, 'NULL pointer' if ptr.null?
36
+ @ptr = FFI::AutoPointer.new(ptr, self.class.method(:destroy))
37
+ @writer = writer
38
+ end
39
+
40
+ # @api private
41
+ def self.destroy(ptr)
42
+ LibRnp.rnp_output_destroy(ptr)
43
+ end
44
+
45
+ def inspect
46
+ Rnp.inspect_ptr(self)
47
+ end
48
+
49
+ # Create an Output to write to a string.
50
+ #
51
+ # The resulting string can later be retrieved with {#string}.
52
+ #
53
+ # @param max_alloc [Integer] the maximum amount of memory to allocate,
54
+ # or 0 for unlimited
55
+ # @return [Output]
56
+ def self.to_string(max_alloc = 0)
57
+ pptr = FFI::MemoryPointer.new(:pointer)
58
+ Rnp.call_ffi(:rnp_output_to_memory, pptr, max_alloc)
59
+ Output.new(pptr.read_pointer)
60
+ end
61
+
62
+ # Create an Output to write to a path.
63
+ #
64
+ # @param path [String] the path
65
+ # @return [Output]
66
+ def self.to_path(path)
67
+ pptr = FFI::MemoryPointer.new(:pointer)
68
+ Rnp.call_ffi(:rnp_output_to_path, pptr, path)
69
+ Output.new(pptr.read_pointer)
70
+ end
71
+
72
+ # Create an Output to discard all writes.
73
+ #
74
+ # @return [Output]
75
+ def self.to_null
76
+ pptr = FFI::MemoryPointer.new(:pointer)
77
+ Rnp.call_ffi(:rnp_output_to_null, pptr)
78
+ Output.new(pptr.read_pointer)
79
+ end
80
+
81
+ # Create an Output to write to an IO object.
82
+ #
83
+ # @param io [IO, #write] the IO object
84
+ # @return [Output]
85
+ def self.to_io(io)
86
+ to_callback(io.method(:write))
87
+ end
88
+
89
+ # Retrieve the data written. Only valid for #{to_string}.
90
+ #
91
+ # @return [String, nil]
92
+ def string
93
+ pptr = FFI::MemoryPointer.new(:pointer)
94
+ len = FFI::MemoryPointer.new(:size_t)
95
+ Rnp.call_ffi(:rnp_output_memory_get_buf, @ptr, pptr, len, false)
96
+ buf = pptr.read_pointer
97
+ buf.read_bytes(len.read(:size_t)) unless buf.null?
98
+ end
99
+
100
+ # @api private
101
+ WRITER = lambda do |writer, _ctx, buf, buf_len|
102
+ begin
103
+ data = buf.read_bytes(buf_len)
104
+ written = writer.call(data)
105
+ return written == data.bytesize
106
+ rescue
107
+ puts $ERROR_INFO
108
+ return false
109
+ end
110
+ end
111
+
112
+ # @api private
113
+ def self.to_callback(writer)
114
+ pptr = FFI::MemoryPointer.new(:pointer)
115
+ writercb = WRITER.curry[writer]
116
+ Rnp.call_ffi(:rnp_output_to_callback, pptr, writercb, nil, nil)
117
+ Output.new(pptr.read_pointer, writercb)
118
+ end
119
+ end # class
120
+ end # class
121
+
@@ -0,0 +1,595 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) 2018 Ribose Inc.
4
+
5
+ require 'English'
6
+ require 'json'
7
+
8
+ require 'ffi'
9
+
10
+ require 'rnp/error'
11
+ require 'rnp/ffi/librnp'
12
+ require 'rnp/utils'
13
+ require 'rnp/key'
14
+ require 'rnp/op/sign'
15
+ require 'rnp/op/verify'
16
+ require 'rnp/op/encrypt'
17
+
18
+ # Class used for interacting with RNP.
19
+ class Rnp
20
+ # @api private
21
+ attr_reader :ptr
22
+
23
+ # Create a new interface to RNP.
24
+ #
25
+ # @param pubfmt [String] the public keyring format
26
+ # @param secfmt [String] the secret keyring format
27
+ def initialize(pubfmt = 'GPG', secfmt = 'GPG')
28
+ pptr = FFI::MemoryPointer.new(:pointer)
29
+ Rnp.call_ffi(:rnp_ffi_create, pptr, pubfmt, secfmt)
30
+ @ptr = FFI::AutoPointer.new(pptr.read_pointer, self.class.method(:destroy))
31
+ @key_provider = nil
32
+ @password_provider = nil
33
+ end
34
+
35
+ # @api private
36
+ def self.destroy(ptr)
37
+ LibRnp.rnp_ffi_destroy(ptr)
38
+ end
39
+
40
+ def inspect
41
+ Rnp.inspect_ptr(self)
42
+ end
43
+
44
+ # Set a logging destination.
45
+ #
46
+ # @param fd [Integer, IO] the file descriptor to log to. This will be closed
47
+ # when this object is destroyed.
48
+ def log=(fd)
49
+ fd = fd.to_i if fd.is_a(::IO)
50
+ Rnp.call_ffi(:rnp_ffi_set_log_fd, @ptr, fd)
51
+ end
52
+
53
+ # Set a key provider.
54
+ #
55
+ # The key provider is useful if, for example, you have a database of keys and
56
+ # you do not want to load all of them, and you don't know which will be needed
57
+ # for a given operation.
58
+ #
59
+ # The key provider will be called to request that a key be loaded, and the
60
+ # key provider is responsible for loading the appropriate key (if available)
61
+ # using {#load_keys}.
62
+ #
63
+ # The provider may be called multiple times for the same key, but with
64
+ # different identifiers. For example, it may first be called with
65
+ # a fingerprint, then (if the key was not loaded), it may be called with a
66
+ # keyid.
67
+ #
68
+ # == Examples
69
+ # === examples/key_provider.rb
70
+ # {include:file:examples/key_provider.rb}
71
+ #
72
+ # @param provider [Proc, #call] a callable object
73
+ def key_provider=(provider)
74
+ @key_provider = provider
75
+ @key_provider = KEY_PROVIDER.curry[provider] if provider
76
+ Rnp.call_ffi(:rnp_ffi_set_key_provider, @ptr, @key_provider, nil)
77
+ end
78
+
79
+ # Set a password provider.
80
+ #
81
+ # The password provider is used for retrieving passwords for various
82
+ # operations, including:
83
+ # * Signing data
84
+ # * Decrypting data (public-key or symmetric)
85
+ # * Adding a userid to a key
86
+ # * Unlocking a key
87
+ # * Unprotecting a key
88
+ #
89
+ # == Examples
90
+ # === examples/password_provider.rb
91
+ # {include:file:examples/password_provider.rb}
92
+ #
93
+ # @param provider [Proc, #call, String] a callable object, or a password
94
+ def password_provider=(provider)
95
+ @password_provider = provider
96
+ @password_provider = PASS_PROVIDER.curry[provider] if provider
97
+ Rnp.call_ffi(:rnp_ffi_set_pass_provider, @ptr, @password_provider, nil)
98
+ end
99
+
100
+ # Generate a new key or pair of keys.
101
+ #
102
+ # @note The generated key(s) will be unprotected and unlocked.
103
+ # The application should protect and lock the keys with
104
+ # {Key#protect} and {Key#lock}.
105
+ #
106
+ # == Examples
107
+ # === examples/key_generation.rb
108
+ # {include:file:examples/key_generation.rb}
109
+ #
110
+ # @param description [String, Hash]
111
+ # @return [Hash<Symbol, Key>] a hash containing the generated key(s)
112
+ def generate_key(description)
113
+ description = JSON.generate(description) unless description.is_a?(String)
114
+ pptr = FFI::MemoryPointer.new(:pointer)
115
+ Rnp.call_ffi(:rnp_generate_key_json, @ptr, description, pptr)
116
+ begin
117
+ presults = pptr.read_pointer
118
+ return nil if presults.null?
119
+ results = JSON.parse(presults.read_string)
120
+ generated = {}
121
+ results.each do |k, v|
122
+ key = find_key(v.keys[0].to_sym => v.values[0])
123
+ generated[k.to_sym] = key
124
+ end
125
+ generated
126
+ ensure
127
+ LibRnp.rnp_buffer_destroy(presults)
128
+ end
129
+ end
130
+
131
+ # Load keys.
132
+ #
133
+ # @param format [String] the format of the keys to load (GPG, KBX, G10).
134
+ # @param input [Input] the input to read the keys from
135
+ # @param public_keys [Boolean] whether to load public keys
136
+ # @param secret_keys [Boolean] whether to load secret keys
137
+ # @return [void]
138
+ def load_keys(input:, format:, public_keys: true, secret_keys: true)
139
+ raise ArgumentError, 'At least one of public_keys or secret_keys must be true' if !public_keys && !secret_keys
140
+ flags = load_save_flags(public_keys: public_keys, secret_keys: secret_keys)
141
+ Rnp.call_ffi(:rnp_load_keys, @ptr, format, input.ptr, flags)
142
+ end
143
+
144
+ # Save keys.
145
+ #
146
+ # @param format [String] the format to save the keys in (GPG, KBX, G10).
147
+ # @param output [Output] the output to write the keys to
148
+ # @param public_keys [Boolean] whether to load public keys
149
+ # @param secret_keys [Boolean] whether to load secret keys
150
+ # @return [void]
151
+ def save_keys(output:, format:, public_keys: false, secret_keys: false)
152
+ raise ArgumentError, 'At least one of public_keys or secret_keys must be true' if !public_keys && !secret_keys
153
+ flags = load_save_flags(public_keys: public_keys, secret_keys: secret_keys)
154
+ Rnp.call_ffi(:rnp_save_keys, @ptr, format, output.ptr, flags)
155
+ end
156
+
157
+ # Find a key.
158
+ #
159
+ # @param criteria [Hash] the search criteria. Some examples would be:
160
+ # * !{keyid: '2FCADF05FFA501BB'}
161
+ # * !{'userid': 'user0'}
162
+ # * !{fingerprint: 'BE1C4AB951F4C2F6B604'}
163
+ # Only *one* criteria can be specified.
164
+ # @return [Key, nil]
165
+ def find_key(criteria)
166
+ raise Rnp::Error, 'Invalid search criteria' if !criteria.is_a?(::Hash) ||
167
+ criteria.size != 1
168
+ pptr = FFI::MemoryPointer.new(:pointer)
169
+ Rnp.call_ffi(:rnp_locate_key, @ptr, criteria.keys[0].to_s,
170
+ criteria.values[0], pptr)
171
+ pkey = pptr.read_pointer
172
+ Rnp::Key.new(pkey) unless pkey.null?
173
+ end
174
+
175
+ # @!method userids
176
+ # Get a list of all userids.
177
+ #
178
+ # @return [Array<String>]
179
+
180
+ # @!method each_userid(&block)
181
+ # Enumerate all userids.
182
+ #
183
+ # @return [self, Enumerator]
184
+
185
+ # @!method keyids
186
+ # Get a list of all keyids.
187
+ #
188
+ # @return [Array<String>]
189
+
190
+ # @!method each_keyid(&block)
191
+ # Enumerate all keyids.
192
+ #
193
+ # @return [self, Enumerator]
194
+
195
+ # @!method fingerprints
196
+ # Get a list of all fingerprints.
197
+ #
198
+ # @return [Array<String>]
199
+
200
+ # @!method each_fingerprint(&block)
201
+ # Enumerate all fingerprints.
202
+ #
203
+ # @return [self, Enumerator]
204
+
205
+ # @!method grips
206
+ # Get a list of all grips.
207
+ #
208
+ # @return [Array<String>]
209
+
210
+ # @!method each_grip(&block)
211
+ # Enumerate all grips.
212
+ #
213
+ # @return [self, Enumerator]
214
+
215
+ %w[userid keyid fingerprint grip].each do |identifier_type|
216
+ define_method("each_#{identifier_type}".to_sym) do |&block|
217
+ each_identifier(identifier_type, &block)
218
+ end
219
+
220
+ define_method("#{identifier_type}s".to_sym) do
221
+ each_identifier(identifier_type).to_a
222
+ end
223
+ end
224
+
225
+ def public_key_count
226
+ pcount = FFI::MemoryPointer.new(:size_t)
227
+ Rnp.call_ffi(:rnp_get_public_key_count, pcount)
228
+ pcount.read(:size_t)
229
+ end
230
+
231
+ def secret_key_count
232
+ pcount = FFI::MemoryPointer.new(:size_t)
233
+ Rnp.call_ffi(:rnp_get_secret_key_count, pcount)
234
+ pcount.read(:size_t)
235
+ end
236
+
237
+ # Create a signature.
238
+ #
239
+ # @param input [Input] the input to read the data to be signed
240
+ # @param output [Output] the output to write the signatures.
241
+ # If nil, the result will be returned directly as a String.
242
+ # @param signers [Key, Array<Key>] the keys to sign with
243
+ # @param armored (see Sign#armored=)
244
+ # @param compression (see Sign#compression=)
245
+ # @param creation_time (see Sign#creation_time=)
246
+ # @param expiration_time (see Sign#expiration_time=)
247
+ # @param hash (see Sign#hash=)
248
+ # @return [nil, String]
249
+ def sign(input:, output: nil, signers:,
250
+ armored: nil,
251
+ compression: nil,
252
+ creation_time: nil,
253
+ expiration_time: nil,
254
+ hash: nil)
255
+ default_output(output) do |output_|
256
+ sign = start_sign(input: input, output: output_)
257
+ sign.options = {
258
+ armored: armored,
259
+ compression: compression,
260
+ creation_time: creation_time,
261
+ expiration_time: expiration_time,
262
+ hash: hash
263
+ }
264
+ simple_sign(sign, signers)
265
+ end
266
+ end
267
+
268
+ # Create a cleartext signature.
269
+ #
270
+ # @param input (see #sign)
271
+ # @param output (see #sign)
272
+ # @param signers [Key, Array<Key>] the keys to sign with
273
+ # @param compression (see Sign#compression=)
274
+ # @param creation_time (see Sign#creation_time=)
275
+ # @param expiration_time (see Sign#expiration_time=)
276
+ # @param hash (see Sign#hash=)
277
+ # @return [nil, String]
278
+ def cleartext_sign(input:, output: nil, signers:,
279
+ compression: nil,
280
+ creation_time: nil,
281
+ expiration_time: nil,
282
+ hash: nil)
283
+ default_output(output) do |output_|
284
+ sign = start_cleartext_sign(input: input, output: output_)
285
+ sign.options = {
286
+ compression: compression,
287
+ creation_time: creation_time,
288
+ expiration_time: expiration_time,
289
+ hash: hash
290
+ }
291
+ simple_sign(sign, signers)
292
+ end
293
+ end
294
+
295
+ # Create a detached signature.
296
+ #
297
+ # @param input (see #sign)
298
+ # @param output (see #sign)
299
+ # @param signers [Key, Array<Key>] the keys to sign with
300
+ # @param armored (see Sign#armored=)
301
+ # @param compression (see Sign#compression=)
302
+ # @param creation_time (see Sign#creation_time=)
303
+ # @param expiration_time (see Sign#expiration_time=)
304
+ # @param hash (see Sign#hash=)
305
+ # @return [nil, String]
306
+ def detached_sign(input:, output: nil, signers:,
307
+ armored: nil,
308
+ compression: nil,
309
+ creation_time: nil,
310
+ expiration_time: nil,
311
+ hash: nil)
312
+ default_output(output) do |output_|
313
+ sign = start_detached_sign(input: input, output: output_)
314
+ sign.options = {
315
+ armored: armored,
316
+ compression: compression,
317
+ creation_time: creation_time,
318
+ expiration_time: expiration_time,
319
+ hash: hash
320
+ }
321
+ simple_sign(sign, signers)
322
+ end
323
+ end
324
+
325
+ # Verify a signature.
326
+ #
327
+ # @param input [Input] the input to read the signatures
328
+ # @param output [Output] the output (if any) to write the verified data
329
+ def verify(input:, output: nil)
330
+ verify = start_verify(input: input, output: output)
331
+ verify.execute
332
+ end
333
+
334
+ # Verify a detached signature.
335
+ #
336
+ # @param data [Input] the input to read the data
337
+ # @param signature [Input] the input to read the signatures
338
+ def detached_verify(data:, signature:)
339
+ verify = start_detached_verify(data: data, signature: signature)
340
+ verify.execute
341
+ end
342
+
343
+ # Encrypt data with a public key.
344
+ #
345
+ # @param input [Input] the input to read the plaintext
346
+ # @param output [Output] the output to write the encrypted data.
347
+ # If nil, the result will be returned directly as a String.
348
+ # @param recipients [Key, Array<Key>] list of recipients keys
349
+ # @param armored (see Encrypt#armored=)
350
+ # @param compression (see Encrypt#compression=)
351
+ # @param cipher (see Encrypt#cipher=)
352
+ def encrypt(input:, output: nil, recipients:,
353
+ armored: nil,
354
+ compression: nil,
355
+ cipher: nil)
356
+ default_output(output) do |output_|
357
+ enc = start_encrypt(input: input, output: output_)
358
+ enc.options = {
359
+ armored: armored,
360
+ compression: compression,
361
+ cipher: cipher
362
+ }
363
+ simple_encrypt(enc, recipients: recipients)
364
+ end
365
+ end
366
+
367
+ # Encrypt and sign data with a public key.
368
+ #
369
+ # @param input (see #encrypt)
370
+ # @param output (see #encrypt)
371
+ # @param recipients (see #encrypt)
372
+ # @param signers [Key, Array<Key>] list of keys to sign with
373
+ # @param armored (see Encrypt#armored=)
374
+ # @param compression (see Encrypt#compression=)
375
+ # @param cipher (see Encrypt#cipher=)
376
+ # @param hash (see Encrypt#hash=)
377
+ # @param creation_time (see Encrypt#creation_time=)
378
+ # @param expiration_time (see Encrypt#expiration_time=)
379
+ def encrypt_and_sign(input:, output: nil, recipients:, signers:,
380
+ armored: nil,
381
+ compression: nil,
382
+ cipher: nil,
383
+ hash: nil,
384
+ creation_time: nil,
385
+ expiration_time: nil)
386
+ default_output(output) do |output_|
387
+ enc = start_encrypt(input: input, output: output_)
388
+ enc.options = {
389
+ armored: armored,
390
+ compression: compression,
391
+ cipher: cipher,
392
+ hash: hash,
393
+ creation_time: creation_time,
394
+ expiration_time: expiration_time
395
+ }
396
+ simple_encrypt(enc, recipients: recipients, signers: signers)
397
+ end
398
+ end
399
+
400
+ # Encrypt with a password only.
401
+ #
402
+ # @param input (see #encrypt)
403
+ # @param output (see #encrypt)
404
+ # @param passwords [String, Array<String>] list of passwords to encrypt with.
405
+ # Any (single) one of the passwords can be used to decrypt.
406
+ # @param armored (see Encrypt#armored=)
407
+ # @param compression (see Encrypt#compression=)
408
+ # @param cipher (see Encrypt#cipher=)
409
+ # @param s2k_hash (see Encrypt#add_password)
410
+ # @param s2k_iterations (see Encrypt#add_password)
411
+ # @param s2k_cipher (see Encrypt#add_password)
412
+ # @return [void]
413
+ def symmetric_encrypt(input:, output: nil, passwords:,
414
+ armored: nil,
415
+ compression: nil,
416
+ cipher: nil,
417
+ s2k_hash: nil,
418
+ s2k_iterations: 0,
419
+ s2k_cipher: nil)
420
+ default_output(output) do |output_|
421
+ enc = start_encrypt(input: input, output: output_)
422
+ enc.options = {
423
+ armored: armored,
424
+ compression: compression,
425
+ cipher: cipher
426
+ }
427
+ passwords = [passwords] if passwords.is_a?(String)
428
+ passwords.each do |password|
429
+ enc.add_password(password,
430
+ s2k_hash: s2k_hash,
431
+ s2k_iterations: s2k_iterations,
432
+ s2k_cipher: s2k_cipher)
433
+ end
434
+ enc.execute
435
+ end
436
+ end
437
+
438
+ # Decrypt encrypted data.
439
+ #
440
+ # @param input [Input] the input to read the encrypted data
441
+ # @param output [Output] the output to write the decrypted data.
442
+ # If nil, the result will be returned directly as a String.
443
+ # @return [nil, String]
444
+ def decrypt(input:, output: nil)
445
+ default_output(output) do |output_|
446
+ Rnp.call_ffi(:rnp_decrypt, @ptr, input.ptr, output_.ptr)
447
+ end
448
+ end
449
+
450
+ # Create a {Sign} operation.
451
+ #
452
+ # @param input [Input] the input to read the data to be signed
453
+ # @param output [Output] the output to write the signatures
454
+ def start_sign(input:, output:)
455
+ _start_sign(:rnp_op_sign_create, input, output)
456
+ end
457
+
458
+ # Create a cleartext {Sign} operation.
459
+ #
460
+ # @param input (see #start_sign)
461
+ # @param output (see #start_sign)
462
+ def start_cleartext_sign(input:, output:)
463
+ _start_sign(:rnp_op_sign_cleartext_create, input, output)
464
+ end
465
+
466
+ # Create a detached {Sign} operation.
467
+ #
468
+ # @param input (see #start_sign)
469
+ # @param output (see #start_sign)
470
+ def start_detached_sign(input:, output:)
471
+ _start_sign(:rnp_op_sign_detached_create, input, output)
472
+ end
473
+
474
+ # Create a {Verify} operation.
475
+ #
476
+ # @param input [Input] the input to read the signatures
477
+ # @param output [Output] the output (if any) to write the verified data
478
+ def start_verify(input:, output: nil)
479
+ output = Output.to_null unless output
480
+ _start_verify(:rnp_op_verify_create, input, output)
481
+ end
482
+
483
+ # Create a detached {Verify} operation.
484
+ #
485
+ # @param data [Input] the input to read the signed data
486
+ # @param signature [Input] the input to read the signatures
487
+ def start_detached_verify(data:, signature:)
488
+ _start_verify(:rnp_op_verify_detached_create, data, signature)
489
+ end
490
+
491
+ # Create an {Encrypt} operation.
492
+ #
493
+ # @param input [Input] the input to read the plaintext
494
+ # @param output [Output] the output to write the encrypted data
495
+ def start_encrypt(input:, output:)
496
+ pptr = FFI::MemoryPointer.new(:pointer)
497
+ Rnp.call_ffi(:rnp_op_encrypt_create, pptr, @ptr, input.ptr, output.ptr)
498
+ pencrypt = pptr.read_pointer
499
+ Encrypt.new(pencrypt) unless pencrypt.null?
500
+ end
501
+
502
+ private
503
+
504
+ KEY_PROVIDER = lambda do |provider, _rnp, _ctx, identifier_type, identifier, secret|
505
+ provider.call(identifier_type, identifier, secret)
506
+ end
507
+
508
+ PASS_PROVIDER = lambda do |provider, _rnp, _ctx, pkey, reason, buf, buf_len|
509
+ begin
510
+ if provider.is_a?(String)
511
+ # we were provided a a literal password
512
+ password = provider
513
+ else
514
+ key = Key.new(pkey, false) unless pkey.null?
515
+ password = provider.call(key, reason)
516
+ end
517
+ return false unless password && password.size < buf_len
518
+ buf.write_string(password)
519
+ return true
520
+ rescue
521
+ puts $ERROR_INFO
522
+ return false
523
+ end
524
+ end
525
+
526
+ def simple_sign(sign, signers)
527
+ signers = [signers] if signers.is_a?(Key)
528
+ signers.each do |signer|
529
+ sign.add_signer(signer)
530
+ end
531
+ sign.execute
532
+ end
533
+
534
+ def _start_sign(func, input, output)
535
+ pptr = FFI::MemoryPointer.new(:pointer)
536
+ Rnp.call_ffi(func, pptr, @ptr, input.ptr, output.ptr)
537
+ psign = pptr.read_pointer
538
+ Rnp::Sign.new(psign) unless psign.null?
539
+ end
540
+
541
+ def _start_verify(func, io1, io2)
542
+ pptr = FFI::MemoryPointer.new(:pointer)
543
+ Rnp.call_ffi(func, pptr, @ptr, io1.ptr, io2.ptr)
544
+ pverify = pptr.read_pointer
545
+ Verify.new(pverify) unless pverify.null?
546
+ end
547
+
548
+ def simple_encrypt(enc, recipients: nil, signers: nil)
549
+ recipients = [recipients] if recipients.is_a?(Key)
550
+ recipients&.each do |recipient|
551
+ enc.add_recipient(recipient)
552
+ end
553
+ signers = [signers] if signers.is_a?(Key)
554
+ signers&.each do |signer|
555
+ enc.add_signer(signer)
556
+ end
557
+ enc.execute
558
+ end
559
+
560
+ def each_identifier(type, &block)
561
+ block or return enum_for(:identifier_iterator, type)
562
+ identifier_iterator(type, &block)
563
+ self
564
+ end
565
+
566
+ def identifier_iterator(identifier_type)
567
+ pptr = FFI::MemoryPointer.new(:pointer)
568
+ piterator = nil
569
+ Rnp.call_ffi(:rnp_identifier_iterator_create, @ptr, pptr, identifier_type)
570
+ piterator = pptr.read_pointer
571
+ loop do
572
+ Rnp.call_ffi(:rnp_identifier_iterator_next, piterator, pptr)
573
+ pidentifier = pptr.read_pointer
574
+ break if pidentifier.null?
575
+ yield pidentifier.read_string
576
+ end
577
+ ensure
578
+ LibRnp.rnp_identifier_iterator_destroy(piterator) if piterator
579
+ end
580
+
581
+ def load_save_flags(public_keys:, secret_keys:)
582
+ flags = 0
583
+ flags |= LibRnp::RNP_LOAD_SAVE_PUBLIC_KEYS if public_keys
584
+ flags |= LibRnp::RNP_LOAD_SAVE_SECRET_KEYS if secret_keys
585
+ flags
586
+ end
587
+
588
+ def default_output(output)
589
+ to_str = output.nil?
590
+ output = Output.to_string if to_str
591
+ yield output
592
+ output.string if to_str
593
+ end
594
+ end # class
595
+