rnp 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +4 -1
- data/README.adoc +8 -5
- data/lib/rnp/ffi/librnp.rb +161 -36
- data/lib/rnp/input.rb +30 -10
- data/lib/rnp/key.rb +217 -1
- data/lib/rnp/misc.rb +153 -4
- data/lib/rnp/op/encrypt.rb +26 -22
- data/lib/rnp/op/generate.rb +211 -0
- data/lib/rnp/op/sign.rb +25 -25
- data/lib/rnp/output.rb +14 -0
- data/lib/rnp/rnp.rb +170 -4
- data/lib/rnp/signature.rb +106 -0
- data/lib/rnp/userid.rb +77 -0
- data/lib/rnp/version.rb +2 -3
- data/lib/rnp.rb +3 -1
- metadata +29 -15
data/lib/rnp/misc.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# (c) 2018 Ribose Inc.
|
3
|
+
# (c) 2018-2020 Ribose Inc.
|
4
4
|
|
5
|
+
require "json"
|
5
6
|
require 'ffi'
|
6
7
|
|
7
8
|
require 'rnp/utils'
|
@@ -74,6 +75,13 @@ class Rnp
|
|
74
75
|
# @param output [Output] the output to write the armored
|
75
76
|
# data to. If nil, the result will be returned directly
|
76
77
|
# as a String.
|
78
|
+
# @param type [String] the armor type. Valid values include:
|
79
|
+
# * message
|
80
|
+
# * public key
|
81
|
+
# * secret key
|
82
|
+
# * signature
|
83
|
+
# * cleartext
|
84
|
+
# * nil (try to guess the type)
|
77
85
|
# @return [nil, String]
|
78
86
|
def self.enarmor(input:, output: nil, type: nil)
|
79
87
|
Output.default(output) do |output_|
|
@@ -112,8 +120,12 @@ class Rnp
|
|
112
120
|
# stamps generated with {version_for}.
|
113
121
|
#
|
114
122
|
# @return [Integer]
|
115
|
-
def self.version
|
116
|
-
|
123
|
+
def self.version(str = nil)
|
124
|
+
if str.nil?
|
125
|
+
LibRnp.rnp_version
|
126
|
+
else
|
127
|
+
LibRnp.rnp_version_for(*str.split('.').map(&:to_i))
|
128
|
+
end
|
117
129
|
end
|
118
130
|
|
119
131
|
# Encode the given major, minor, and patch numbers into a version
|
@@ -144,5 +156,142 @@ class Rnp
|
|
144
156
|
def self.version_patch(version)
|
145
157
|
LibRnp.rnp_version_patch(version)
|
146
158
|
end
|
147
|
-
end # class
|
148
159
|
|
160
|
+
# Retrieve the commit time of the latest commit.
|
161
|
+
#
|
162
|
+
# This will return 0 for release/non-master builds.
|
163
|
+
#
|
164
|
+
# @return [Integer]
|
165
|
+
def self.commit_time
|
166
|
+
LibRnp.rnp_version_commit_timestamp
|
167
|
+
end
|
168
|
+
|
169
|
+
# Parse OpenPGP data to JSON.
|
170
|
+
#
|
171
|
+
# @param input [Input] the input to read the data
|
172
|
+
# @param mpi [Boolean] if true then MPIs will be included
|
173
|
+
# @param raw [Boolean] if true then raw bytes will be included
|
174
|
+
# @param grip [Boolean] if true then grips will be included
|
175
|
+
# @return [Array]
|
176
|
+
def self.parse(input:, mpi: false, raw: false, grip: false)
|
177
|
+
flags = 0
|
178
|
+
flags |= LibRnp::RNP_JSON_DUMP_MPI if mpi
|
179
|
+
flags |= LibRnp::RNP_JSON_DUMP_RAW if raw
|
180
|
+
flags |= LibRnp::RNP_JSON_DUMP_GRIP if grip
|
181
|
+
pptr = FFI::MemoryPointer.new(:pointer)
|
182
|
+
Rnp.call_ffi(:rnp_dump_packets_to_json, input.ptr, flags, pptr)
|
183
|
+
begin
|
184
|
+
pjson = pptr.read_pointer
|
185
|
+
JSON.parse(pjson.read_string) unless pjson.null?
|
186
|
+
ensure
|
187
|
+
LibRnp.rnp_buffer_destroy(pjson)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Calculate s2k iterations
|
192
|
+
#
|
193
|
+
# @param hash [String] the hash algorithm to use
|
194
|
+
# @param msec [Integer] the desired number of milliseconds
|
195
|
+
# @return [Integer]
|
196
|
+
def self.s2k_iterations(hash:, msec:)
|
197
|
+
piters = FFI::MemoryPointer.new(:size_t)
|
198
|
+
Rnp.call_ffi(:rnp_calculate_iterations, hash, msec, piters)
|
199
|
+
piters.read(:size_t)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Enable debugging
|
203
|
+
#
|
204
|
+
# @param file [String] the file to enable debugging for (or nil for all)
|
205
|
+
# @return [void]
|
206
|
+
def self.enable_debug(file = nil)
|
207
|
+
Rnp.call_ffi(:rnp_enable_debug, file)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Disable previously-enabled debugging
|
211
|
+
#
|
212
|
+
# @return [void]
|
213
|
+
def self.disable_debug
|
214
|
+
Rnp.call_ffi(:rnp_disable_debug)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Guess the contents of an input
|
218
|
+
#
|
219
|
+
# @param input [Input]
|
220
|
+
# @return [String] the guessed content type (or 'unknown'), which may be
|
221
|
+
# used with {enarmor}
|
222
|
+
def self.guess_contents(input)
|
223
|
+
pptr = FFI::MemoryPointer.new(:pointer)
|
224
|
+
Rnp.call_ffi(:rnp_guess_contents, input.ptr, pptr)
|
225
|
+
begin
|
226
|
+
presult = pptr.read_pointer
|
227
|
+
presult.read_string unless presult.null?
|
228
|
+
ensure
|
229
|
+
LibRnp.rnp_buffer_destroy(presult)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Check if a specific feature is supported
|
234
|
+
#
|
235
|
+
# @param type [String] the type of feature ('symmetric algorithm', ...)
|
236
|
+
# @param name [String] the specific feature ('CAST5', ...)
|
237
|
+
# @return [Boolean]
|
238
|
+
def self.supports?(type, name)
|
239
|
+
presult = FFI::MemoryPointer.new(:bool)
|
240
|
+
Rnp.call_ffi(:rnp_supports_feature, type, name, presult)
|
241
|
+
presult.read(:bool)
|
242
|
+
end
|
243
|
+
|
244
|
+
# Get a list of supported features (by type)
|
245
|
+
#
|
246
|
+
# @param type [String] the type of feature ('symmetric algorithm', ...)
|
247
|
+
# @return [Array]
|
248
|
+
def self.supported_features(type)
|
249
|
+
pptr = FFI::MemoryPointer.new(:pointer)
|
250
|
+
Rnp.call_ffi(:rnp_supported_features, type, pptr)
|
251
|
+
begin
|
252
|
+
presult = pptr.read_pointer
|
253
|
+
JSON.parse(presult.read_string) unless presult.null?
|
254
|
+
ensure
|
255
|
+
LibRnp.rnp_buffer_destroy(presult)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# @api private
|
260
|
+
FEATURES = {
|
261
|
+
# Support for setting hash, creation, and expiration time for individual
|
262
|
+
# signatures in a sign operation. Older versions of rnp returned a
|
263
|
+
# "not implemented" error.
|
264
|
+
"per-signature-opts" => Rnp.version > Rnp.version("0.11.0") ||
|
265
|
+
Rnp.commit_time >= 1546035818,
|
266
|
+
# Correct grip calculation for Elgamal/DSA keys. This was actually before
|
267
|
+
# the commit timestamp API was added, so this isn't accurate in one case.
|
268
|
+
"dsa-elg-grip-calc" => Rnp.version > Rnp.version("0.11.0") ||
|
269
|
+
Rnp.commit_time >= 1538219020,
|
270
|
+
# Input reader callback signature was changed:
|
271
|
+
# ssize_t(void *app_ctx, void *buf, size_t len)
|
272
|
+
# bool(void *app_ctx, void *buf, size_t len, size_t *read)
|
273
|
+
"input-reader-cb-no-ssize_t" => Rnp.version >= Rnp.version("0.14.0") ||
|
274
|
+
Rnp.commit_time >= 1585833163,
|
275
|
+
# Behavior on primary userid retrieveing was changed:
|
276
|
+
# Now userid is not considered as primary if it is revoked/expired/etc.
|
277
|
+
"primary-userid-must-be-valid" => Rnp.version >= Rnp.version("0.14.0") ||
|
278
|
+
Rnp.commit_time >= 1605875599,
|
279
|
+
# Behavior was changed in v0.15.2 (issue #1509):
|
280
|
+
# userid is valid even if key expiration in userid expiration expires key.
|
281
|
+
"relax-userid-validity-checks" => Rnp.version >= Rnp.version("0.15.2") ||
|
282
|
+
Rnp.commit_time >= 1624526708,
|
283
|
+
# Behavior on default key expiration time was changed:
|
284
|
+
# Now default key expiration time is 2 years
|
285
|
+
"default-key-expiration-2-years" => Rnp.version >= Rnp.version("0.16.1") ||
|
286
|
+
Rnp.commit_time >= 1645578982,
|
287
|
+
# Behaviour on signature validation was changed:
|
288
|
+
# Now at least one valid signature is required for success
|
289
|
+
"require-single-valid-signature" => Rnp.version >= Rnp.version("0.16.1") ||
|
290
|
+
Rnp.commit_time >= 1661781294,
|
291
|
+
}.freeze
|
292
|
+
|
293
|
+
def self.has?(feature)
|
294
|
+
raise ArgumentError unless FEATURES.include?(feature)
|
295
|
+
FEATURES[feature]
|
296
|
+
end
|
297
|
+
end # class
|
data/lib/rnp/op/encrypt.rb
CHANGED
@@ -43,19 +43,18 @@ class Rnp
|
|
43
43
|
# Add a signer.
|
44
44
|
#
|
45
45
|
# @param signer [Key] the signer
|
46
|
-
# @param
|
47
|
-
# @
|
48
|
-
# @
|
46
|
+
# @param [Hash] opts set several options in one place
|
47
|
+
# @option opts [String] :hash (see #hash=)
|
48
|
+
# @option opts [Time] :creation_time (see #creation_time=)
|
49
|
+
# @option opts [Time] :expiration_time (see #expiration_time=)
|
49
50
|
# @return [self]
|
50
|
-
def add_signer(signer,
|
51
|
+
def add_signer(signer, opts = {})
|
51
52
|
pptr = FFI::MemoryPointer.new(:pointer)
|
52
53
|
Rnp.call_ffi(:rnp_op_encrypt_add_signature, @ptr, signer.ptr, pptr)
|
53
54
|
psig = pptr.read_pointer
|
54
55
|
Sign.set_signature_options(
|
55
56
|
psig,
|
56
|
-
|
57
|
-
creation_time: creation_time,
|
58
|
-
expiration_time: expiration_time
|
57
|
+
**opts,
|
59
58
|
)
|
60
59
|
self
|
61
60
|
end
|
@@ -88,20 +87,19 @@ class Rnp
|
|
88
87
|
# @note Some options are related to signatures and will have no effect if
|
89
88
|
# there are no signers.
|
90
89
|
#
|
91
|
-
# @param
|
92
|
-
# @
|
93
|
-
# @
|
94
|
-
# @
|
95
|
-
# @
|
96
|
-
# @
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
self.expiration_time = expiration_time unless expiration_time.nil?
|
90
|
+
# @param [Hash] opts set several options in one place
|
91
|
+
# @option opts [Boolean] :armored (see #armored=)
|
92
|
+
# @option opts [String] :compression (see #compression=)
|
93
|
+
# @option opts [String] :cipher (see #cipher=)
|
94
|
+
# @option opts [String] :hash (see #hash=)
|
95
|
+
# @option opts [Time] :creation_time (see #creation_time=)
|
96
|
+
# @option opts [Time] :expiration_time (see #expiration_time=)
|
97
|
+
def options=(opts)
|
98
|
+
%i{armored compression cipher aead hash creation_time
|
99
|
+
expiration_time}.each do |prop|
|
100
|
+
value = opts[prop]
|
101
|
+
send("#{prop}=", value) unless value.nil?
|
102
|
+
end
|
105
103
|
end
|
106
104
|
|
107
105
|
# Set whether the output will be ASCII-armored.
|
@@ -135,6 +133,13 @@ class Rnp
|
|
135
133
|
Rnp.call_ffi(:rnp_op_encrypt_set_cipher, @ptr, cipher)
|
136
134
|
end
|
137
135
|
|
136
|
+
# Set the AEAD algorithm for encryption.
|
137
|
+
#
|
138
|
+
# @param mode [String] the AEAD algorithm to use for encryption
|
139
|
+
def aead=(mode)
|
140
|
+
Rnp.call_ffi(:rnp_op_encrypt_set_aead, @ptr, mode.to_s)
|
141
|
+
end
|
142
|
+
|
138
143
|
# Set the hash algorithm used for calculating signatures.
|
139
144
|
#
|
140
145
|
# @note This is only valid when there is one or more signer.
|
@@ -178,4 +183,3 @@ class Rnp
|
|
178
183
|
end
|
179
184
|
end # class
|
180
185
|
end # class
|
181
|
-
|
@@ -0,0 +1,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# (c) 2019 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
|
+
# Key generation operation
|
13
|
+
class Generate
|
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
|
+
|
21
|
+
@ptr = FFI::AutoPointer.new(ptr, self.class.method(:destroy))
|
22
|
+
end
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
def self.destroy(ptr)
|
26
|
+
LibRnp.rnp_op_generate_destroy(ptr)
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
Rnp.inspect_ptr(self)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set a group of options.
|
34
|
+
#
|
35
|
+
# @param [Hash] opts set several options in one place
|
36
|
+
# @option opts [Integer] :bits (see #bits=)
|
37
|
+
# @option opts [Integer] :qbits (see #qbits=)
|
38
|
+
# @option opts [Integer] :curve (see #curve=)
|
39
|
+
# @option opts [String] :hash (see #hash=)
|
40
|
+
# @option opts [String] :s2k_hash (see #s2k_hash=)
|
41
|
+
# @option opts [Integer] :s2k_iterations (see #s2k_iterations=)
|
42
|
+
# @option opts [String] :s2k_cipher (see #s2k_cipher=)
|
43
|
+
# @option opts [String] :password (see #password=)
|
44
|
+
# @option opts [String] :protection_mode (see #protection_mode=)
|
45
|
+
# @option opts [Integer] :lifetime (see #lifetime=)
|
46
|
+
# @option opts [String] :userid (see #userid=)
|
47
|
+
# @option opts [String] :usage (see #usage=)
|
48
|
+
def options=(opts)
|
49
|
+
%i{bits qbits curve hash s2k_hash s2k_iterations
|
50
|
+
s2k_cipher password protection_mode lifetime
|
51
|
+
userid usage preferences}.each do |prop|
|
52
|
+
value = opts[prop]
|
53
|
+
send("#{prop}=", value) unless value.nil?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Set the bit length of the key.
|
58
|
+
#
|
59
|
+
# @param len [Integer] the desired bit length
|
60
|
+
def bits=(len)
|
61
|
+
Rnp.call_ffi(:rnp_op_generate_set_bits, @ptr, len)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Set the bit length of the q parameter for a DSA key.
|
65
|
+
#
|
66
|
+
# @note This is only valid for DSA keys.
|
67
|
+
#
|
68
|
+
# @param len [Integer] the desired bit length
|
69
|
+
def qbits=(len)
|
70
|
+
Rnp.call_ffi(:rnp_op_generate_set_dsa_qbits, @ptr, len)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Set the desired curve for this ECC key.
|
74
|
+
#
|
75
|
+
# @note This is only valid for ECC keys which permit specifying a curve.
|
76
|
+
#
|
77
|
+
# @param curve [String] the curve
|
78
|
+
def curve=(curve)
|
79
|
+
Rnp.call_ffi(:rnp_op_generate_set_curve, @ptr, curve.to_s)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Set the hash algorithm used in the self-signature of the key.
|
83
|
+
#
|
84
|
+
# @param hash [String] the hash algorithm name
|
85
|
+
def hash=(hash)
|
86
|
+
Rnp.call_ffi(:rnp_op_generate_set_hash, @ptr, hash.to_s)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Set the hash algorithm used to protect the key.
|
90
|
+
#
|
91
|
+
# @param hash [String] the hash algorithm name
|
92
|
+
def s2k_hash=(hash)
|
93
|
+
Rnp.call_ffi(:rnp_op_generate_set_protection_hash, @ptr, hash.to_s)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Set the s2k iteration count used to protect the key.
|
97
|
+
#
|
98
|
+
# @param iter [Integer] the hash algorithm name
|
99
|
+
def s2k_iterations=(iter)
|
100
|
+
Rnp.call_ffi(:rnp_op_generate_set_protection_iterations, @ptr, iter)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Set the cipher used to protect the key.
|
104
|
+
#
|
105
|
+
# @param cipher [String] the cipher algorithm name
|
106
|
+
def s2k_cipher=(cipher)
|
107
|
+
Rnp.call_ffi(:rnp_op_generate_set_protection_cipher, @ptr, cipher.to_s)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Set the password used to protect the key.
|
111
|
+
#
|
112
|
+
# @param password [String] the password
|
113
|
+
def password=(password)
|
114
|
+
Rnp.call_ffi(:rnp_op_generate_set_protection_password, @ptr, password)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Set the protection mode for this key.
|
118
|
+
#
|
119
|
+
# @note This is only valid for keys saved in the G10 format.
|
120
|
+
#
|
121
|
+
# @param mode [String] the protection mode (OCB, CBC, etc)
|
122
|
+
def protection_mode=(mode)
|
123
|
+
Rnp.call_ffi(:rnp_op_generate_set_protection_mode, @ptr, mode.to_s)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Set the number of seconds for this key to remain valid.
|
127
|
+
#
|
128
|
+
# This determines the expiration time (creation time + lifetime).
|
129
|
+
#
|
130
|
+
# @param secs [Integer] the number of seconds until this key will be
|
131
|
+
# considered expired. A value of 0 indicates no expiration.
|
132
|
+
# Note that there is an upper limit of 2^32-1.
|
133
|
+
def lifetime=(secs)
|
134
|
+
Rnp.call_ffi(:rnp_op_generate_set_expiration, @ptr, secs)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Set the userid for this key.
|
138
|
+
#
|
139
|
+
# @param userid [String] the userid
|
140
|
+
def userid=(userid)
|
141
|
+
Rnp.call_ffi(:rnp_op_generate_set_userid, @ptr, userid)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Set the usage for this key.
|
145
|
+
#
|
146
|
+
# @param usage [Array<Symbol>,Array<String>,Symbol,String] the usage
|
147
|
+
# (:sign, etc)
|
148
|
+
def usage=(usage)
|
149
|
+
usage = [usage] unless usage.respond_to?(:each)
|
150
|
+
Rnp.call_ffi(:rnp_op_generate_clear_usage, @ptr)
|
151
|
+
usage.each do |usg|
|
152
|
+
Rnp.call_ffi(:rnp_op_generate_add_usage, @ptr, usg.to_s)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Set the preferences for the generated key.
|
157
|
+
|
158
|
+
# @param [Hash] prefs set several preferences in one place
|
159
|
+
# @option prefs [Array<String>, Array<Symbol>] :hashes
|
160
|
+
# @option prefs [Array<String>, Array<Symbol>] :compression
|
161
|
+
# @option prefs [Array<String>, Array<Symbol>] :ciphers
|
162
|
+
# @option prefs [String] :key_server
|
163
|
+
def preferences=(prefs)
|
164
|
+
%i{hashes compression ciphers}.each do |param|
|
165
|
+
Rnp.call_ffi(pref_ffi_call(param, clear: true), @ptr)
|
166
|
+
prefs[param].each do |pref|
|
167
|
+
Rnp.call_ffi(pref_ffi_call(param, add: true),
|
168
|
+
@ptr, pref.to_s)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
Rnp.call_ffi(:rnp_op_generate_set_pref_keyserver, @ptr,
|
172
|
+
prefs[:key_server])
|
173
|
+
end
|
174
|
+
|
175
|
+
# Execute the operation.
|
176
|
+
#
|
177
|
+
# This should only be called once.
|
178
|
+
#
|
179
|
+
# @return [Key] the generated key
|
180
|
+
def execute
|
181
|
+
Rnp.call_ffi(:rnp_op_generate_execute, @ptr)
|
182
|
+
key
|
183
|
+
end
|
184
|
+
|
185
|
+
# Retrieve the key.
|
186
|
+
#
|
187
|
+
# This should only be called after #execute.
|
188
|
+
#
|
189
|
+
# @return [Key]
|
190
|
+
def key
|
191
|
+
pptr = FFI::MemoryPointer.new(:pointer)
|
192
|
+
Rnp.call_ffi(:rnp_op_generate_get_key, @ptr, pptr)
|
193
|
+
pkey = pptr.read_pointer
|
194
|
+
Key.new(pkey) unless pkey.null?
|
195
|
+
end
|
196
|
+
|
197
|
+
# @api private
|
198
|
+
private
|
199
|
+
|
200
|
+
def pref_ffi_call(param, add: false, clear: false)
|
201
|
+
if add
|
202
|
+
fn = { hashes: :hash, ciphers: :cipher }.fetch(param, param)
|
203
|
+
"rnp_op_generate_add_pref_#{fn}".to_sym
|
204
|
+
elsif clear
|
205
|
+
"rnp_op_generate_clear_pref_#{param}".to_sym
|
206
|
+
else
|
207
|
+
raise ArgumentError, "add or clear must be passed"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
data/lib/rnp/op/sign.rb
CHANGED
@@ -34,37 +34,35 @@ class Rnp
|
|
34
34
|
# @note The optional (per-signature) options here are not supported by RNP
|
35
35
|
# internally at the time of this writing.
|
36
36
|
#
|
37
|
-
# @param
|
38
|
-
# @
|
39
|
-
# @
|
40
|
-
# @
|
37
|
+
# @param [Hash] opts set several options in one place
|
38
|
+
# @option opts [Key] :signer the signer
|
39
|
+
# @option opts [String] :hash (see #hash=)
|
40
|
+
# @option opts [Time] :creation_time (see #creation_time=)
|
41
|
+
# @option opts [Time] :expiration_time (see #expiration_time=)
|
41
42
|
# @return [self]
|
42
|
-
def add_signer(signer,
|
43
|
+
def add_signer(signer, opts = {})
|
43
44
|
pptr = FFI::MemoryPointer.new(:pointer)
|
44
45
|
Rnp.call_ffi(:rnp_op_sign_add_signature, @ptr, signer.ptr, pptr)
|
45
46
|
psig = pptr.read_pointer
|
46
47
|
self.class.set_signature_options(
|
47
48
|
psig,
|
48
|
-
|
49
|
-
creation_time: creation_time,
|
50
|
-
expiration_time: expiration_time
|
49
|
+
**opts,
|
51
50
|
)
|
52
51
|
end
|
53
52
|
|
54
53
|
# Set a group of options.
|
55
54
|
#
|
56
|
-
# @param
|
57
|
-
# @
|
58
|
-
# @
|
59
|
-
# @
|
60
|
-
# @
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
self.expiration_time = expiration_time unless expiration_time.nil?
|
55
|
+
# @param [Hash] opts set several options in one place
|
56
|
+
# @option opts [String] :armored see {#armored=}
|
57
|
+
# @option opts [String] :compression see {#compression=}
|
58
|
+
# @option opts [String] :hash see {#hash=}
|
59
|
+
# @option opts [String] :creation_time see {#creation_time=}
|
60
|
+
# @option opts [String] :expiration_time see {#expiration_time=}
|
61
|
+
def options=(opts)
|
62
|
+
%i{armored compression hash creation_time expiration_time}.each do |prop|
|
63
|
+
value = opts[prop]
|
64
|
+
send("#{prop}=", value) unless value.nil?
|
65
|
+
end
|
68
66
|
end
|
69
67
|
|
70
68
|
# Set whether the output will be ASCII-armored.
|
@@ -127,12 +125,14 @@ class Rnp
|
|
127
125
|
end
|
128
126
|
|
129
127
|
# @api private
|
130
|
-
def self.set_signature_options(psig, hash
|
131
|
-
expiration_time:)
|
132
|
-
Rnp.call_ffi(:rnp_op_sign_signature_set_hash, psig,
|
128
|
+
def self.set_signature_options(psig, hash: nil, creation_time: nil,
|
129
|
+
expiration_time: nil)
|
130
|
+
Rnp.call_ffi(:rnp_op_sign_signature_set_hash, psig, hash) unless hash.nil?
|
133
131
|
creation_time = creation_time.to_i if creation_time.is_a?(::Time)
|
134
|
-
Rnp.call_ffi(:rnp_op_sign_signature_set_creation_time, psig,
|
135
|
-
|
132
|
+
Rnp.call_ffi(:rnp_op_sign_signature_set_creation_time, psig,
|
133
|
+
creation_time) unless creation_time.nil?
|
134
|
+
Rnp.call_ffi(:rnp_op_sign_signature_set_expiration_time, psig,
|
135
|
+
expiration_time) unless expiration_time.nil?
|
136
136
|
end
|
137
137
|
end # class
|
138
138
|
end # class
|
data/lib/rnp/output.rb
CHANGED
@@ -86,6 +86,20 @@ class Rnp
|
|
86
86
|
to_callback(io.method(:write))
|
87
87
|
end
|
88
88
|
|
89
|
+
# Write to the output.
|
90
|
+
#
|
91
|
+
# @param strings [String]
|
92
|
+
# @return [Integer] the number of bytes written
|
93
|
+
def write(*strings)
|
94
|
+
total_written = 0
|
95
|
+
pwritten = FFI::MemoryPointer.new(:size_t)
|
96
|
+
strings.each do |string|
|
97
|
+
Rnp.call_ffi(:rnp_output_write, @ptr, string, string.size, pwritten)
|
98
|
+
total_written += pwritten.read(:size_t)
|
99
|
+
end
|
100
|
+
total_written
|
101
|
+
end
|
102
|
+
|
89
103
|
# Retrieve the data written. Only valid for #{to_string}.
|
90
104
|
#
|
91
105
|
# @return [String, nil]
|