netpgp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,318 @@
1
+ module NetPGP
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, LibNetPGP::PGPKeyring.size)
69
+ native_keyring = LibNetPGP::PGPKeyring.new(native_keyring_ptr)
70
+ NetPGP::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 = LibNetPGP::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 = LibNetPGP::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 [NetPGP::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 = LibNetPGP::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 = LibNetPGP::PGPMemory.new(memory_ptr)
128
+ signed_data = memory[:buf].read_bytes(memory[:length])
129
+ signed_data
130
+ ensure
131
+ LibNetPGP::pgp_memory_free(memory) if memory
132
+ LibNetPGP::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 [{NetPGP::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 [{NetPGP::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 = LibNetPGP::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 = LibNetPGP::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
+ LibNetPGP::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 = LibNetPGP::enum_value(sk[:s2k_usage])
234
+ seckey.string_to_key_specifier = LibNetPGP::enum_value(sk[:s2k_specifier])
235
+ seckey.symmetric_key_algorithm = LibNetPGP::enum_value(sk[:alg])
236
+ seckey.hash_algorithm = LibNetPGP::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(LibNetPGP::PGP_CHECKHASH_SIZE)
240
+ end
241
+ seckey.mpi = NetPGP::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
+ NetPGP::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, LibNetPGP::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
+ LibNetPGP::dynarray_append_item(native_key, 'uid', :string, userid)
271
+ }
272
+ end
273
+ @raw_subpackets.each {|bytes|
274
+ packet = LibNetPGP::PGPSubPacket.new
275
+ length = bytes.bytesize
276
+ packet[:length] = length
277
+ packet[:raw] = LibC::calloc(1, length)
278
+ packet[:raw].write_bytes(bytes)
279
+ LibNetPGP::dynarray_append_item(native_key, 'packet', LibNetPGP::PGPSubPacket, packet)
280
+ }
281
+ end
282
+
283
+ def decrypted_seckey
284
+ if encrypted?
285
+ native_ptr = LibC::calloc(1, LibNetPGP::PGPKey.size)
286
+ native = LibNetPGP::PGPKey.new(native_ptr)
287
+ native_auto = FFI::AutoPointer.new(native_ptr, LibNetPGP::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 = LibNetPGP::pgp_decrypt_seckey(native, passfp)
294
+ rd.close
295
+ LibC::fclose(passfp)
296
+ return nil if not decrypted or decrypted.null?
297
+ LibNetPGP::PGPSecKey.new(decrypted)
298
+ else
299
+ native_ptr = LibC::calloc(1, LibNetPGP::PGPSecKey.size)
300
+ native = LibNetPGP::PGPSecKey.new(native_ptr)
301
+ to_native(native)
302
+ native
303
+ end
304
+ end
305
+
306
+ private
307
+ def create_pgpio
308
+ pgpio = LibNetPGP::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 NetPGP
318
+
@@ -0,0 +1,119 @@
1
+ module NetPGP
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 = LibNetPGP::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] = LibNetPGP::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
+ NetPGP::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] = LibNetPGP::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?(LibNetPGP::PGPSecKey)
75
+ LibNetPGP::pgp_seckey_free(native)
76
+ elsif native.is_a?(LibNetPGP::PGPPubKey)
77
+ LibNetPGP::pgp_pubkey_free(native)
78
+ else
79
+ raise
80
+ end
81
+ NetPGP::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 [LibNetPGP::PGPKey]
89
+ # @param subkey [LibNetPGP::PGPKey]
90
+ def self.add_subkey_signature(key, subkey)
91
+ sig = nil
92
+ sigoutput = nil
93
+ mem_sig = nil
94
+ begin
95
+ sig = LibNetPGP::pgp_create_sig_new
96
+ LibNetPGP::pgp_sig_start_subkey_sig(sig, key[:key][:pubkey], subkey[:key][:pubkey], :PGP_SIG_SUBKEY)
97
+ LibNetPGP::pgp_add_time(sig, subkey[:key][:pubkey][:birthtime], 'birth')
98
+ # TODO expiration
99
+ LibNetPGP::pgp_add_issuer_keyid(sig, key[:sigid])
100
+ LibNetPGP::pgp_end_hashed_subpkts(sig)
101
+
102
+ sigoutput_ptr = FFI::MemoryPointer.new(:pointer)
103
+ mem_sig_ptr = FFI::MemoryPointer.new(:pointer)
104
+ LibNetPGP::pgp_setup_memory_write(sigoutput_ptr, mem_sig_ptr, 128)
105
+ sigoutput = LibNetPGP::PGPOutput.new(sigoutput_ptr.read_pointer)
106
+ LibNetPGP::pgp_write_sig(sigoutput, sig, key[:key][:pubkey], key[:key][:seckey])
107
+ mem_sig = LibNetPGP::PGPMemory.new(mem_sig_ptr.read_pointer)
108
+ sigpkt = LibNetPGP::PGPSubPacket.new
109
+ sigpkt[:length] = LibNetPGP::pgp_mem_len(mem_sig)
110
+ sigpkt[:raw] = LibNetPGP::pgp_mem_data(mem_sig)
111
+ LibNetPGP::pgp_add_subpacket(subkey, sigpkt)
112
+ ensure
113
+ LibNetPGP::pgp_create_sig_delete(sig) if sig
114
+ LibNetPGP::pgp_teardown_memory_write(sigoutput, mem_sig) if mem_sig
115
+ end
116
+ end
117
+
118
+ end # module NetPGP
119
+
@@ -0,0 +1,6 @@
1
+ require_relative 'lowlevel/libnetpgp'
2
+ require_relative 'lowlevel/libc'
3
+ require_relative 'lowlevel/libopenssl'
4
+ require_relative 'lowlevel/dynarray'
5
+ require_relative 'lowlevel/utils'
6
+
@@ -0,0 +1,11 @@
1
+ module LibNetPGP
2
+ PGP_KEY_ID_SIZE = 8
3
+ PGP_FINGERPRINT_SIZE = 20
4
+ PGP_MAX_KEY_SIZE = 32
5
+ PGP_SALT_SIZE = 8
6
+ PGP_MAX_BLOCK_SIZE = 16
7
+ SHA_DIGEST_LENGTH = 20
8
+ PGP_SHA1_HASH_SIZE = SHA_DIGEST_LENGTH
9
+ PGP_CHECKHASH_SIZE = PGP_SHA1_HASH_SIZE
10
+ end
11
+