rnp 0.2.0 → 1.0.0

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