rnp 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +26 -0
- data/README.adoc +208 -0
- data/Rakefile +6 -0
- data/Use_Cases.adoc +119 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/example-usage.rb +766 -0
- data/examples/highlevel/decrypt_mem.rb +44 -0
- data/examples/highlevel/encrypt_mem.rb +46 -0
- data/examples/lowlevel/decrypt_file.rb +76 -0
- data/examples/lowlevel/decrypt_mem.rb +80 -0
- data/examples/lowlevel/encrypt_file.rb +68 -0
- data/examples/lowlevel/encrypt_mem.rb +75 -0
- data/examples/lowlevel/load_pubkey.rb +118 -0
- data/examples/lowlevel/print_keyring_file.rb +68 -0
- data/examples/lowlevel/print_keyring_mem.rb +96 -0
- data/examples/lowlevel/sign_file.rb +104 -0
- data/examples/lowlevel/sign_mem.rb +96 -0
- data/examples/lowlevel/verify_file.rb +55 -0
- data/examples/lowlevel/verify_mem.rb +61 -0
- data/lib/rnp/highlevel/constants.rb +96 -0
- data/lib/rnp/highlevel/keyring.rb +259 -0
- data/lib/rnp/highlevel/publickey.rb +150 -0
- data/lib/rnp/highlevel/secretkey.rb +318 -0
- data/lib/rnp/highlevel/utils.rb +119 -0
- data/lib/rnp/highlevel.rb +5 -0
- data/lib/rnp/lowlevel/constants.rb +11 -0
- data/lib/rnp/lowlevel/dynarray.rb +129 -0
- data/lib/rnp/lowlevel/enums.rb +243 -0
- data/lib/rnp/lowlevel/libc.rb +28 -0
- data/lib/rnp/lowlevel/libopenssl.rb +15 -0
- data/lib/rnp/lowlevel/librnp.rb +213 -0
- data/lib/rnp/lowlevel/structs.rb +541 -0
- data/lib/rnp/lowlevel/utils.rb +25 -0
- data/lib/rnp/lowlevel.rb +6 -0
- data/lib/rnp/version.rb +3 -0
- data/lib/rnp.rb +5 -0
- data/rnp/lib/rnp.rb +5 -0
- data/rnp/spec/rnp_spec.rb +11 -0
- data/rnp.gemspec +35 -0
- metadata +82 -9
@@ -0,0 +1,150 @@
|
|
1
|
+
module RNP
|
2
|
+
|
3
|
+
require_relative 'utils'
|
4
|
+
|
5
|
+
class PublicKey
|
6
|
+
attr_accessor :version,
|
7
|
+
:creation_time,
|
8
|
+
:expiration_time,
|
9
|
+
:public_key_algorithm,
|
10
|
+
:mpi,
|
11
|
+
:userids,
|
12
|
+
:parent,
|
13
|
+
:subkeys
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@version = nil
|
17
|
+
@creation_time = nil
|
18
|
+
@expiration_time = 0
|
19
|
+
@public_key_algorithm = nil
|
20
|
+
@mpi = {}
|
21
|
+
@userids = []
|
22
|
+
@parent = nil
|
23
|
+
@subkeys = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def fingerprint
|
27
|
+
fp = LibRNP::PGPFingerprint.new
|
28
|
+
native_pubkey_ptr = LibC::calloc(1, LibRNP::PGPPubKey.size)
|
29
|
+
native_pubkey = LibRNP::PGPPubKey.new(native_pubkey_ptr)
|
30
|
+
native_pubkey_auto = FFI::AutoPointer.new(native_pubkey_ptr, LibRNP::PGPPubKey.method(:release))
|
31
|
+
to_native(native_pubkey)
|
32
|
+
hash = @version == 3 ? :PGP_HASH_MD5 : :PGP_HASH_SHA1
|
33
|
+
ret = LibRNP::pgp_fingerprint(fp, native_pubkey, hash)
|
34
|
+
raise 'pgp_fingerprint failed' if ret != 1
|
35
|
+
fp[:fingerprint].to_s[0, fp[:length]]
|
36
|
+
end
|
37
|
+
|
38
|
+
def fingerprint_hex
|
39
|
+
fingerprint.bytes.collect {|byte| '%02X' % byte}.join
|
40
|
+
end
|
41
|
+
|
42
|
+
def key_id
|
43
|
+
keyid_ptr = FFI::MemoryPointer.new(:uint8, LibRNP::PGP_KEY_ID_SIZE)
|
44
|
+
native_pubkey = LibRNP::PGPPubKey.new
|
45
|
+
to_native(native_pubkey)
|
46
|
+
ret = LibRNP::pgp_keyid(keyid_ptr, LibRNP::PGP_KEY_ID_SIZE, native_pubkey, :PGP_HASH_SHA1)
|
47
|
+
raise 'pgp_keyid failed' if ret != 1
|
48
|
+
keyid_ptr.read_bytes(LibRNP::PGP_KEY_ID_SIZE)
|
49
|
+
end
|
50
|
+
|
51
|
+
def key_id_hex
|
52
|
+
key_id.bytes.collect {|byte| '%02X' % byte}.join
|
53
|
+
end
|
54
|
+
|
55
|
+
def key_length
|
56
|
+
case @public_key_algorithm
|
57
|
+
when PublicKeyAlgorithm::RSA,
|
58
|
+
PublicKeyAlgorithm::RSA_ENCRYPT_ONLY,
|
59
|
+
PublicKeyAlgorithm::RSA_SIGN_ONLY
|
60
|
+
return RNP::bignum_byte_count(@mpi[:n]) * 8
|
61
|
+
when PublicKeyAlgorithm::DSA
|
62
|
+
case RNP::bignum_byte_count(@mpi[:q])
|
63
|
+
when 20
|
64
|
+
1024
|
65
|
+
when 28
|
66
|
+
2048
|
67
|
+
when 32
|
68
|
+
3072
|
69
|
+
end
|
70
|
+
when PublicKeyAlgorithm::ELGAMAL
|
71
|
+
RNP::bignum_byte_count(@mpi[:y]) * 8
|
72
|
+
end
|
73
|
+
0
|
74
|
+
end
|
75
|
+
|
76
|
+
def encrypt(data, armored=true, sk_algorithm=SymmetricKeyAlgorithm::CAST5)
|
77
|
+
cipher = SymmetricKeyAlgorithm::to_s(sk_algorithm)
|
78
|
+
memory = nil
|
79
|
+
|
80
|
+
begin
|
81
|
+
pubkey_ptr = LibC::calloc(1, LibRNP::PGPKey.size)
|
82
|
+
pubkey = LibRNP::PGPKey.new(pubkey_ptr)
|
83
|
+
pubkey_auto = FFI::AutoPointer.new(pubkey_ptr, LibRNP::PGPKey.method(:release))
|
84
|
+
|
85
|
+
to_native_key(pubkey)
|
86
|
+
data_buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
|
87
|
+
data_buf.write_bytes(data)
|
88
|
+
pgpio = LibRNP::PGPIO.new
|
89
|
+
pgpio[:outs] = LibC::fdopen($stdout.to_i, 'w')
|
90
|
+
pgpio[:errs] = LibC::fdopen($stderr.to_i, 'w')
|
91
|
+
pgpio[:res] = pgpio[:errs]
|
92
|
+
memory_ptr = LibRNP::pgp_encrypt_buf(pgpio, data_buf, data_buf.size, pubkey, armored ? 1 : 0, cipher)
|
93
|
+
return nil if memory_ptr.null?
|
94
|
+
memory = LibRNP::PGPMemory.new(memory_ptr)
|
95
|
+
memory[:buf].read_bytes(memory[:length])
|
96
|
+
ensure
|
97
|
+
LibRNP::pgp_memory_free(memory) if memory
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def verify(data, armored=true)
|
102
|
+
RNP::verify([self], data, armored)
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_subkey(subkey)
|
106
|
+
raise if subkey.subkeys.any?
|
107
|
+
subkey.parent = self
|
108
|
+
subkey.userids = @userids
|
109
|
+
@subkeys.push(subkey)
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.from_native(native)
|
113
|
+
pubkey = PublicKey.new
|
114
|
+
pubkey.version = LibRNP::enum_value(native[:version])
|
115
|
+
pubkey.creation_time = Time.at(native[:birthtime])
|
116
|
+
if pubkey.version == 3
|
117
|
+
pubkey.expiration_time = Time.at(native[:birthtime]) + (native[:days_valid] * 86400)
|
118
|
+
end
|
119
|
+
pubkey.public_key_algorithm = PublicKeyAlgorithm::from_native(native[:alg])
|
120
|
+
pubkey.mpi = RNP::mpis_from_native(native[:alg], native)
|
121
|
+
pubkey
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_native(native)
|
125
|
+
native[:version] = @version
|
126
|
+
native[:birthtime] = @creation_time.to_i
|
127
|
+
if @version == 3 and @expiration_time
|
128
|
+
native[:days_valid] = ((@expiration_time.to_i - @creation_time.to_i) / 86400).to_i
|
129
|
+
else
|
130
|
+
native[:duration] = (@expiration_time.to_i - @creation_time.to_i).to_i
|
131
|
+
end
|
132
|
+
native[:alg] = @public_key_algorithm
|
133
|
+
RNP::mpis_to_native(native[:alg], @mpi, native)
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_native_key(native_key)
|
137
|
+
native_key[:type] = :PGP_PTAG_CT_PUBLIC_KEY
|
138
|
+
native_key[:sigid] = key_id
|
139
|
+
to_native(native_key[:key][:pubkey])
|
140
|
+
if not @parent
|
141
|
+
@userids.each {|userid|
|
142
|
+
LibRNP::dynarray_append_item(native_key, 'uid', :string, userid)
|
143
|
+
}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end # module RNP
|
150
|
+
|
@@ -0,0 +1,318 @@
|
|
1
|
+
module RNP
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require_relative 'publickey'
|
6
|
+
require_relative 'utils'
|
7
|
+
|
8
|
+
# Secret key
|
9
|
+
#
|
10
|
+
class SecretKey
|
11
|
+
extend Forwardable
|
12
|
+
delegate [:creation_time, :expiration_time, :expiration_time=,
|
13
|
+
:fingerprint, :fingerprint_hex, :key_id, :key_id_hex] => :@public_key
|
14
|
+
|
15
|
+
attr_accessor :public_key,
|
16
|
+
:string_to_key_usage,
|
17
|
+
:string_to_key_specifier,
|
18
|
+
:symmetric_key_algorithm,
|
19
|
+
:hash_algorithm,
|
20
|
+
:iv,
|
21
|
+
:check_hash,
|
22
|
+
:mpi,
|
23
|
+
:userids,
|
24
|
+
:parent,
|
25
|
+
:subkeys,
|
26
|
+
:raw_subpackets,
|
27
|
+
:encrypted,
|
28
|
+
:passphrase
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@public_key = nil
|
32
|
+
@string_to_key_usage = nil
|
33
|
+
@string_to_key_specifier = nil
|
34
|
+
@symmetric_key_algorithm = nil
|
35
|
+
@hash_algorithm = nil
|
36
|
+
@iv = nil
|
37
|
+
@check_hash = nil
|
38
|
+
@mpi = {}
|
39
|
+
@userids = []
|
40
|
+
@parent = nil
|
41
|
+
@subkeys = []
|
42
|
+
@raw_subpackets = []
|
43
|
+
@encrypted = false
|
44
|
+
@passphrase = ''
|
45
|
+
end
|
46
|
+
|
47
|
+
# Checks if a key is encrypted. An encrypted key requires a
|
48
|
+
# passphrase for signing/decrypting/etc and will have nil values
|
49
|
+
# for key material/mpis.
|
50
|
+
#
|
51
|
+
# @return [Boolean]
|
52
|
+
def encrypted?
|
53
|
+
@encrypted
|
54
|
+
end
|
55
|
+
|
56
|
+
# Decrypts data using this secret key.
|
57
|
+
#
|
58
|
+
# Note: {#passphrase} must be set to the correct passphrase prior
|
59
|
+
# to this call. If no passphrase is required, it should be set to
|
60
|
+
# '' (not nil).
|
61
|
+
#
|
62
|
+
# @param data [String] the encrypted data to be decrypted.
|
63
|
+
# @param armored [Boolean] whether the encrypted data is ASCII armored.
|
64
|
+
def decrypt(data, armored=true)
|
65
|
+
begin
|
66
|
+
rd, wr = IO.pipe
|
67
|
+
wr.write(@passphrase + "\n")
|
68
|
+
native_keyring_ptr = LibC::calloc(1, LibRNP::PGPKeyring.size)
|
69
|
+
native_keyring = LibRNP::PGPKeyring.new(native_keyring_ptr)
|
70
|
+
RNP::keys_to_native_keyring([self], native_keyring)
|
71
|
+
pgpio = create_pgpio
|
72
|
+
data_ptr = FFI::MemoryPointer.new(:uint8, data.bytesize)
|
73
|
+
data_ptr.write_bytes(data)
|
74
|
+
passfp = LibC::fdopen(rd.to_i, 'r')
|
75
|
+
mem_ptr = LibRNP::pgp_decrypt_buf(pgpio, data_ptr, data_ptr.size,
|
76
|
+
native_keyring, nil,
|
77
|
+
armored ? 1 : 0, 0, passfp, 1, nil)
|
78
|
+
return nil if mem_ptr.null?
|
79
|
+
mem = LibRNP::PGPMemory.new(mem_ptr)
|
80
|
+
mem[:buf].read_bytes(mem[:length])
|
81
|
+
ensure
|
82
|
+
rd.close
|
83
|
+
wr.close
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Signs data using this secret key.
|
88
|
+
#
|
89
|
+
# Note: {#passphrase} must be set to the correct passphrase prior
|
90
|
+
# to this call. If no passphrase is required, it should be set to ''.
|
91
|
+
#
|
92
|
+
# @param data [String] the data to be signed.
|
93
|
+
# @param armored [Boolean] whether the output should be ASCII armored.
|
94
|
+
# @param options [Hash] less-often used options that override defaults.
|
95
|
+
# * :from [Time] (defaults to Time.now) - signature creation time
|
96
|
+
# * :duration [Numeric] (defaults to 0) - signature duration/expiration
|
97
|
+
# * :hash_algorithm [RNP::HashAlgorithm] (defaults to SHA1) -
|
98
|
+
# hash algorithm to use
|
99
|
+
# * :cleartext [Boolean] (defaults to false) - whether this should be
|
100
|
+
# a cleartext/clearsign signature, which includes the original
|
101
|
+
# data in cleartext in the same document.
|
102
|
+
# @return [String] the signed data, or nil on error.
|
103
|
+
def sign(data, armored=true, options={})
|
104
|
+
valid_options = [:from, :duration, :hash_algorithm, :cleartext]
|
105
|
+
for option in options.keys
|
106
|
+
raise if not valid_options.include?(option)
|
107
|
+
end
|
108
|
+
|
109
|
+
armored = armored ? 1 : 0
|
110
|
+
from = options[:from] || Time.now
|
111
|
+
duration = options[:duration] || 0
|
112
|
+
hashalg = options[:hash_algorithm] || HashAlgorithm::SHA1
|
113
|
+
cleartext = options[:cleartext] ? 1 : 0
|
114
|
+
|
115
|
+
from = from.to_i
|
116
|
+
hashname = HashAlgorithm::to_s(hashalg)
|
117
|
+
|
118
|
+
pgpio = create_pgpio
|
119
|
+
data_buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
|
120
|
+
data_buf.write_bytes(data)
|
121
|
+
seckey = decrypted_seckey
|
122
|
+
return nil if not seckey
|
123
|
+
memory = nil
|
124
|
+
begin
|
125
|
+
memory_ptr = LibRNP::pgp_sign_buf(pgpio, data_buf, data_buf.size, seckey, from, duration, hashname, armored, cleartext)
|
126
|
+
return nil if not memory_ptr or memory_ptr.null?
|
127
|
+
memory = LibRNP::PGPMemory.new(memory_ptr)
|
128
|
+
signed_data = memory[:buf].read_bytes(memory[:length])
|
129
|
+
signed_data
|
130
|
+
ensure
|
131
|
+
LibRNP::pgp_memory_free(memory) if memory
|
132
|
+
LibRNP::pgp_seckey_free(seckey) if seckey
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Cleartext signs data using this secret key.
|
137
|
+
# This is a shortcut for {#sign}.
|
138
|
+
#
|
139
|
+
# Note: {#passphrase} must be set to the correct passphrase prior
|
140
|
+
# to this call. If no passphrase is required, it should be set to ''.
|
141
|
+
#
|
142
|
+
# @param data [String] the data to be signed.
|
143
|
+
# @param armored [Boolean] whether the output should be ASCII armored.
|
144
|
+
# @param options [Hash] less-often used options that override defaults.
|
145
|
+
# * :from [Time] (defaults to Time.now) - signature creation time
|
146
|
+
# * :duration [Integer] (defaults to 0) - signature duration/expiration
|
147
|
+
# * :hash_algorithm [{RNP::HashAlgorithm}] (defaults to SHA1) -
|
148
|
+
# hash algorithm to use
|
149
|
+
# @return [String] the signed data, or nil on error.
|
150
|
+
def clearsign(data, armored=true, options={})
|
151
|
+
options[:cleartext] = true
|
152
|
+
sign(data, armored, options)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Creates a detached signature of a file.
|
156
|
+
#
|
157
|
+
# Note: {#passphrase} must be set to the correct passphrase prior
|
158
|
+
# to this call. If no passphrase is required, it should be set to ''.
|
159
|
+
#
|
160
|
+
# @param infile [String] the path to the input file for which a
|
161
|
+
# signature will be created.
|
162
|
+
# @param sigfile [String] the path to the signature file that will
|
163
|
+
# be created.
|
164
|
+
#
|
165
|
+
# This can be nil, in which case the filename will be the infile
|
166
|
+
# parameter with '.asc' appended.
|
167
|
+
#
|
168
|
+
# @param armored [Boolean] whether the output should be ASCII armored.
|
169
|
+
# @param options [Hash] less-often used options that override defaults.
|
170
|
+
# * :from [Time] (defaults to Time.now) - signature creation time
|
171
|
+
# * :duration [Integer] (defaults to 0) - signature duration/expiration
|
172
|
+
# * :hash_algorithm [{RNP::HashAlgorithm}] (defaults to SHA1) -
|
173
|
+
# hash algorithm to use
|
174
|
+
# @return [Boolean] whether the signing was successful.
|
175
|
+
def detached_sign(infile, sigfile=nil, armored=true, options={})
|
176
|
+
valid_options = [:from, :duration, :hash_algorithm]
|
177
|
+
for option in options.keys
|
178
|
+
raise if not valid_options.include?(option)
|
179
|
+
end
|
180
|
+
|
181
|
+
armored = armored ? 1 : 0
|
182
|
+
from = options[:from] || Time.now
|
183
|
+
duration = options[:duration] || 0
|
184
|
+
hashalg = options[:hash_algorithm] || HashAlgorithm::SHA1
|
185
|
+
|
186
|
+
hashname = HashAlgorithm::to_s(hashalg)
|
187
|
+
from = from.to_i
|
188
|
+
|
189
|
+
pgpio = create_pgpio
|
190
|
+
# Note: pgp_sign_detached calls pgp_seckey_free for us
|
191
|
+
seckey = decrypted_seckey
|
192
|
+
return false if not seckey
|
193
|
+
ret = LibRNP::pgp_sign_detached(pgpio, infile, sigfile, seckey, hashname, from, duration, armored, 1)
|
194
|
+
return ret == 1
|
195
|
+
end
|
196
|
+
|
197
|
+
def add_subkey(subkey)
|
198
|
+
raise if subkey.subkeys.any?
|
199
|
+
subkey.parent = self
|
200
|
+
subkey.userids = @userids
|
201
|
+
@subkeys.push(subkey)
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.generate(passphrase, options={})
|
205
|
+
valid_options = [:key_length, :public_key_algorithm, :algorithm_params,
|
206
|
+
:hash_algorithm, :symmetric_key_algorithm]
|
207
|
+
for option in options.keys
|
208
|
+
raise if not valid_options.include?(option)
|
209
|
+
end
|
210
|
+
|
211
|
+
key_length = options[:key_length] || 4096
|
212
|
+
pkalg = options[:public_key_algorithm] || PublicKeyAlgorithm::RSA
|
213
|
+
pkalg_params = options[:algorithm_params] || {e: 65537}
|
214
|
+
hashalg = options[:hash_algorithm] || HashAlgorithm::SHA1
|
215
|
+
skalg = options[:symmetric_key_algorithm] || SymmetricKeyAlgorithm::CAST5
|
216
|
+
hashalg_s = HashAlgorithm::to_s(hashalg)
|
217
|
+
skalg_s = SymmetricKeyAlgorithm::to_s(skalg)
|
218
|
+
|
219
|
+
native_key = nil
|
220
|
+
begin
|
221
|
+
native_key = LibRNP::pgp_rsa_new_key(key_length, pkalg_params[:e], hashalg_s, skalg_s)
|
222
|
+
key = SecretKey::from_native(native_key[:key][:seckey])
|
223
|
+
key.passphrase = passphrase
|
224
|
+
key
|
225
|
+
ensure
|
226
|
+
LibRNP::pgp_keydata_free(native_key) if native_key
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def self.from_native(sk, encrypted=false)
|
231
|
+
seckey = SecretKey.new
|
232
|
+
seckey.public_key = PublicKey::from_native(sk[:pubkey])
|
233
|
+
seckey.string_to_key_usage = LibRNP::enum_value(sk[:s2k_usage])
|
234
|
+
seckey.string_to_key_specifier = LibRNP::enum_value(sk[:s2k_specifier])
|
235
|
+
seckey.symmetric_key_algorithm = LibRNP::enum_value(sk[:alg])
|
236
|
+
seckey.hash_algorithm = LibRNP::enum_value(sk[:hash_alg]) || HashAlgorithm::SHA1
|
237
|
+
seckey.iv = sk[:iv].to_ptr.read_bytes(sk[:iv].size)
|
238
|
+
if not sk[:checkhash].null?
|
239
|
+
seckey.check_hash = sk[:checkhash].read_bytes(LibRNP::PGP_CHECKHASH_SIZE)
|
240
|
+
end
|
241
|
+
seckey.mpi = RNP::mpis_from_native(sk[:pubkey][:alg], sk)
|
242
|
+
seckey.encrypted = encrypted
|
243
|
+
seckey
|
244
|
+
end
|
245
|
+
|
246
|
+
def to_native(native)
|
247
|
+
@public_key.to_native(native[:pubkey])
|
248
|
+
native[:s2k_usage] = @string_to_key_usage
|
249
|
+
native[:s2k_specifier] = @string_to_key_specifier
|
250
|
+
native[:alg] = @symmetric_key_algorithm
|
251
|
+
native[:hash_alg] = @hash_algorithm
|
252
|
+
# zero IV, then copy
|
253
|
+
native[:iv].to_ptr.write_bytes("\x00" * native[:iv].size)
|
254
|
+
native[:iv].to_ptr.write_bytes(@iv) if @iv
|
255
|
+
RNP::mpis_to_native(PublicKeyAlgorithm::to_native(@public_key.public_key_algorithm), @mpi, native)
|
256
|
+
# note: this has to come after mpis_to_native because that frees ptrs
|
257
|
+
if @check_hash
|
258
|
+
native[:checkhash] = LibC::calloc(1, LibRNP::PGP_CHECKHASH_SIZE)
|
259
|
+
native[:checkhash].write_bytes(@check_hash)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def to_native_key(native_key)
|
264
|
+
raise if not native_key[:packets].null?
|
265
|
+
native_key[:type] = :PGP_PTAG_CT_SECRET_KEY
|
266
|
+
native_key[:sigid] = @public_key.key_id
|
267
|
+
to_native(native_key[:key][:seckey])
|
268
|
+
if not @parent
|
269
|
+
@userids.each {|userid|
|
270
|
+
LibRNP::dynarray_append_item(native_key, 'uid', :string, userid)
|
271
|
+
}
|
272
|
+
end
|
273
|
+
@raw_subpackets.each {|bytes|
|
274
|
+
packet = LibRNP::PGPSubPacket.new
|
275
|
+
length = bytes.bytesize
|
276
|
+
packet[:length] = length
|
277
|
+
packet[:raw] = LibC::calloc(1, length)
|
278
|
+
packet[:raw].write_bytes(bytes)
|
279
|
+
LibRNP::dynarray_append_item(native_key, 'packet', LibRNP::PGPSubPacket, packet)
|
280
|
+
}
|
281
|
+
end
|
282
|
+
|
283
|
+
def decrypted_seckey
|
284
|
+
if encrypted?
|
285
|
+
native_ptr = LibC::calloc(1, LibRNP::PGPKey.size)
|
286
|
+
native = LibRNP::PGPKey.new(native_ptr)
|
287
|
+
native_auto = FFI::AutoPointer.new(native_ptr, LibRNP::PGPKey.method(:release))
|
288
|
+
to_native_key(native)
|
289
|
+
rd, wr = IO.pipe
|
290
|
+
wr.write(@passphrase + "\n")
|
291
|
+
wr.close
|
292
|
+
passfp = LibC::fdopen(rd.to_i, 'r')
|
293
|
+
decrypted = LibRNP::pgp_decrypt_seckey(native, passfp)
|
294
|
+
rd.close
|
295
|
+
LibC::fclose(passfp)
|
296
|
+
return nil if not decrypted or decrypted.null?
|
297
|
+
LibRNP::PGPSecKey.new(decrypted)
|
298
|
+
else
|
299
|
+
native_ptr = LibC::calloc(1, LibRNP::PGPSecKey.size)
|
300
|
+
native = LibRNP::PGPSecKey.new(native_ptr)
|
301
|
+
to_native(native)
|
302
|
+
native
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
private
|
307
|
+
def create_pgpio
|
308
|
+
pgpio = LibRNP::PGPIO.new
|
309
|
+
pgpio[:outs] = LibC::fdopen($stdout.to_i, 'w')
|
310
|
+
pgpio[:errs] = LibC::fdopen($stderr.to_i, 'w')
|
311
|
+
pgpio[:res] = pgpio[:errs]
|
312
|
+
pgpio
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
316
|
+
|
317
|
+
end # module RNP
|
318
|
+
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module RNP
|
2
|
+
|
3
|
+
def self.bignum_byte_count(bn)
|
4
|
+
# Note: This probably assumes that the ruby implementation
|
5
|
+
# uses the same BN representation that libnetpgp does.
|
6
|
+
# It may be better to convert and use BN_num_bytes (or bits).
|
7
|
+
bn.to_s(16).length / 2
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.stream_errors(stream)
|
11
|
+
error_ptr = stream[:errors]
|
12
|
+
|
13
|
+
errors = []
|
14
|
+
until error_ptr.null?
|
15
|
+
error = LibRNP::PGPError.new(error_ptr)
|
16
|
+
|
17
|
+
error_desc = "#{error[:file]}:#{error[:line]}: #{error[:errcode]} #{error[:comment]}"
|
18
|
+
errors.push(error_desc)
|
19
|
+
|
20
|
+
error_ptr = error[:next]
|
21
|
+
end
|
22
|
+
errors
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.mpi_from_native(native)
|
26
|
+
mpi = {}
|
27
|
+
native.members.each {|member|
|
28
|
+
if native[member].null?
|
29
|
+
mpi[member] = nil
|
30
|
+
else
|
31
|
+
mpi[member] = LibRNP::bn2hex(native[member]).hex
|
32
|
+
end
|
33
|
+
}
|
34
|
+
mpi
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.mpis_from_native(alg, native)
|
38
|
+
case alg
|
39
|
+
when :PGP_PKA_RSA, :PGP_PKA_RSA_ENCRYPT_ONLY, :PGP_PKA_RSA_SIGN_ONLY
|
40
|
+
material = native[:key][:rsa]
|
41
|
+
when :PGP_PKA_DSA
|
42
|
+
material = native[:key][:dsa]
|
43
|
+
when :PGP_PKA_ELGAMAL
|
44
|
+
material = native[:key][:elgamal]
|
45
|
+
else
|
46
|
+
raise "Unsupported PK algorithm: #{alg}"
|
47
|
+
end
|
48
|
+
RNP::mpi_from_native(material)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.mpi_to_native(mpi, native)
|
52
|
+
mpi.each {|name,value|
|
53
|
+
if mpi[name] == nil
|
54
|
+
native[name] = nil
|
55
|
+
else
|
56
|
+
native[name] = LibRNP::num2bn(value)
|
57
|
+
end
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.mpis_to_native(alg, mpi, native)
|
62
|
+
case alg
|
63
|
+
when :PGP_PKA_RSA, :PGP_PKA_RSA_ENCRYPT_ONLY, :PGP_PKA_RSA_SIGN_ONLY
|
64
|
+
material = native[:key][:rsa]
|
65
|
+
when :PGP_PKA_DSA
|
66
|
+
material = native[:key][:dsa]
|
67
|
+
when :PGP_PKA_ELGAMAL
|
68
|
+
material = native[:key][:elgamal]
|
69
|
+
else
|
70
|
+
raise "Unsupported PK algorithm: #{alg}"
|
71
|
+
end
|
72
|
+
# Ensure we're not leaking memory from a prior call.
|
73
|
+
# This just frees all the BNs.
|
74
|
+
if native.is_a?(LibRNP::PGPSecKey)
|
75
|
+
LibRNP::pgp_seckey_free(native)
|
76
|
+
elsif native.is_a?(LibRNP::PGPPubKey)
|
77
|
+
LibRNP::pgp_pubkey_free(native)
|
78
|
+
else
|
79
|
+
raise
|
80
|
+
end
|
81
|
+
RNP::mpi_to_native(mpi, material)
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
# Add a subkey binding signature (type 0x18) to a key.
|
86
|
+
# Note that this should be used for encryption subkeys.
|
87
|
+
#
|
88
|
+
# @param key [LibRNP::PGPKey]
|
89
|
+
# @param subkey [LibRNP::PGPKey]
|
90
|
+
def self.add_subkey_signature(key, subkey)
|
91
|
+
sig = nil
|
92
|
+
sigoutput = nil
|
93
|
+
mem_sig = nil
|
94
|
+
begin
|
95
|
+
sig = LibRNP::pgp_create_sig_new
|
96
|
+
LibRNP::pgp_sig_start_subkey_sig(sig, key[:key][:pubkey], subkey[:key][:pubkey], :PGP_SIG_SUBKEY)
|
97
|
+
LibRNP::pgp_add_time(sig, subkey[:key][:pubkey][:birthtime], 'birth')
|
98
|
+
# TODO expiration
|
99
|
+
LibRNP::pgp_add_issuer_keyid(sig, key[:sigid])
|
100
|
+
LibRNP::pgp_end_hashed_subpkts(sig)
|
101
|
+
|
102
|
+
sigoutput_ptr = FFI::MemoryPointer.new(:pointer)
|
103
|
+
mem_sig_ptr = FFI::MemoryPointer.new(:pointer)
|
104
|
+
LibRNP::pgp_setup_memory_write(sigoutput_ptr, mem_sig_ptr, 128)
|
105
|
+
sigoutput = LibRNP::PGPOutput.new(sigoutput_ptr.read_pointer)
|
106
|
+
LibRNP::pgp_write_sig(sigoutput, sig, key[:key][:pubkey], key[:key][:seckey])
|
107
|
+
mem_sig = LibRNP::PGPMemory.new(mem_sig_ptr.read_pointer)
|
108
|
+
sigpkt = LibRNP::PGPSubPacket.new
|
109
|
+
sigpkt[:length] = LibRNP::pgp_mem_len(mem_sig)
|
110
|
+
sigpkt[:raw] = LibRNP::pgp_mem_data(mem_sig)
|
111
|
+
LibRNP::pgp_add_subpacket(subkey, sigpkt)
|
112
|
+
ensure
|
113
|
+
LibRNP::pgp_create_sig_delete(sig) if sig
|
114
|
+
LibRNP::pgp_teardown_memory_write(sigoutput, mem_sig) if mem_sig
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end # module RNP
|
119
|
+
|