rnp 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +26 -0
  8. data/README.adoc +208 -0
  9. data/Rakefile +6 -0
  10. data/Use_Cases.adoc +119 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/example-usage.rb +766 -0
  14. data/examples/highlevel/decrypt_mem.rb +44 -0
  15. data/examples/highlevel/encrypt_mem.rb +46 -0
  16. data/examples/lowlevel/decrypt_file.rb +76 -0
  17. data/examples/lowlevel/decrypt_mem.rb +80 -0
  18. data/examples/lowlevel/encrypt_file.rb +68 -0
  19. data/examples/lowlevel/encrypt_mem.rb +75 -0
  20. data/examples/lowlevel/load_pubkey.rb +118 -0
  21. data/examples/lowlevel/print_keyring_file.rb +68 -0
  22. data/examples/lowlevel/print_keyring_mem.rb +96 -0
  23. data/examples/lowlevel/sign_file.rb +104 -0
  24. data/examples/lowlevel/sign_mem.rb +96 -0
  25. data/examples/lowlevel/verify_file.rb +55 -0
  26. data/examples/lowlevel/verify_mem.rb +61 -0
  27. data/lib/rnp/highlevel/constants.rb +96 -0
  28. data/lib/rnp/highlevel/keyring.rb +259 -0
  29. data/lib/rnp/highlevel/publickey.rb +150 -0
  30. data/lib/rnp/highlevel/secretkey.rb +318 -0
  31. data/lib/rnp/highlevel/utils.rb +119 -0
  32. data/lib/rnp/highlevel.rb +5 -0
  33. data/lib/rnp/lowlevel/constants.rb +11 -0
  34. data/lib/rnp/lowlevel/dynarray.rb +129 -0
  35. data/lib/rnp/lowlevel/enums.rb +243 -0
  36. data/lib/rnp/lowlevel/libc.rb +28 -0
  37. data/lib/rnp/lowlevel/libopenssl.rb +15 -0
  38. data/lib/rnp/lowlevel/librnp.rb +213 -0
  39. data/lib/rnp/lowlevel/structs.rb +541 -0
  40. data/lib/rnp/lowlevel/utils.rb +25 -0
  41. data/lib/rnp/lowlevel.rb +6 -0
  42. data/lib/rnp/version.rb +3 -0
  43. data/lib/rnp.rb +5 -0
  44. data/rnp/lib/rnp.rb +5 -0
  45. data/rnp/spec/rnp_spec.rb +11 -0
  46. data/rnp.gemspec +35 -0
  47. metadata +82 -9
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'io/console'
4
+
5
+ require_relative '../../lib/rnp'
6
+
7
+ options = {keys_armored: false, cleartext: false, output_armored: false, detached: false}
8
+ parser = OptionParser.new do |opts|
9
+ opts.banner = "Usage: #{$0} [options] <seckey> <input-file> <output-file>"
10
+ opts.on('-k', '--keys-armored', 'Seckey is ASCII armored') do
11
+ options[:keys_armored] = true
12
+ end
13
+ opts.on('-c', '--clear-sign', 'Cleartext signature') do
14
+ options[:cleartext] = true
15
+ end
16
+ opts.on('-a', '--armored', 'Output file will be ASCII armored') do
17
+ options[:output_armored] = true
18
+ end
19
+ opts.on('-d', '--detached', 'Detached signature') do
20
+ options[:detached] = true
21
+ end
22
+ opts.on('-h', '--help', 'Print this help') do
23
+ puts opts
24
+ exit
25
+ end
26
+ end
27
+ parser.parse!
28
+
29
+ if ARGV.length != 3
30
+ parser.display
31
+ exit
32
+ end
33
+
34
+ seckey_filename = ARGV.shift
35
+ input_filename = ARGV.shift
36
+ output_filename = ARGV.shift
37
+
38
+ # Load keys/keyring
39
+ keyring_mem = LibC::calloc(1, LibRNP::PGPKeyring.size)
40
+ keyring = LibRNP::PGPKeyring.new(keyring_mem)
41
+ if 1 != LibRNP::pgp_keyring_fileread(keyring, options[:keys_armored] ? 1 : 0, seckey_filename)
42
+ puts 'Errors encountered while loading keyring.'
43
+ exit 1
44
+ end
45
+ # Find first seckey
46
+ keycount = LibRNP::dynarray_count(keyring, 'key')
47
+ seckey = nil
48
+ (0..keycount - 1).each {|keyn|
49
+ key = LibRNP::dynarray_get_item(keyring, 'key', LibRNP::PGPKey, keyn)
50
+ seckey = key if LibRNP::pgp_is_key_secret(key)
51
+ break if seckey != nil
52
+ }
53
+ if seckey == nil
54
+ puts 'No seckey found'
55
+ exit 1
56
+ end
57
+
58
+ '''
59
+ This is a bit convoluted because pgp_decrypt_seckey expects a FILE*.
60
+ It may be cleaner to reimplement the short pgp_decrypt_seckey function
61
+ in ruby as it does not do a lot.
62
+ '''
63
+ rd, wr = IO.pipe
64
+ print 'Enter passphrase: '
65
+ passphrase = $stdin.noecho(&:gets)
66
+ puts ''
67
+ wr.write passphrase
68
+ wr.close
69
+ passfp = LibC::fdopen(rd.to_i, 'r')
70
+ seckey = LibRNP::pgp_decrypt_seckey(seckey, passfp)
71
+ rd.close
72
+ LibC::fclose(passfp)
73
+
74
+ if seckey == nil
75
+ puts 'Invalid passphrase.'
76
+ exit 1
77
+ end
78
+ seckey = LibRNP::PGPSecKey.new(seckey)
79
+
80
+ pgpio = LibRNP::PGPIO.new
81
+ stdout_fp = LibC::fdopen($stdout.to_i, 'w')
82
+ stderr_fp = LibC::fdopen($stderr.to_i, 'w')
83
+ pgpio[:outs] = stdout_fp
84
+ pgpio[:errs] = stderr_fp
85
+ pgpio[:res] = stdout_fp
86
+
87
+ overwrite = 1
88
+ from = Time.now.to_i
89
+ duration = 0
90
+ armored = options[:output_armored] ? 1 : 0
91
+
92
+ # see pgp_str_to_hash_alg
93
+ hashname = 'sha1'
94
+ if options[:detached]
95
+ ret = LibRNP::pgp_sign_detached(pgpio, input_filename, output_filename, seckey, hashname, from, duration, armored, overwrite)
96
+ else
97
+ cleartext = options[:cleartext] ? 1 : 0
98
+ ret = LibRNP::pgp_sign_file(pgpio, input_filename, output_filename, seckey, hashname, from, duration, armored, cleartext, overwrite)
99
+ end
100
+ if ret == 1
101
+ puts 'Success'
102
+ else
103
+ puts 'Failed!'
104
+ end
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'io/console'
4
+
5
+ require_relative '../../lib/rnp'
6
+
7
+ options = {armored: false, keys_armored: false, cleartext: false}
8
+ parser = OptionParser.new do |opts|
9
+ opts.banner = "Usage: #{$0} [options] <seckey> <passphrase>"
10
+ opts.on('-k', '--keys-armored', 'Keys are ASCII armored') do
11
+ options[:keys_armored] = true
12
+ end
13
+ opts.on('-a', '--armored', 'Input is ASCII armored') do
14
+ options[:armored] = true
15
+ end
16
+ opts.on('-c', '--clear-sign', 'Cleartext signature') do
17
+ options[:cleartext] = true
18
+ end
19
+ opts.on('-h', '--help', 'Print this help') do
20
+ puts opts
21
+ exit
22
+ end
23
+ end
24
+ parser.parse!
25
+
26
+ if ARGV.length != 2
27
+ parser.display
28
+ exit
29
+ end
30
+
31
+ seckey_filename = ARGV.shift
32
+ passphrase = ARGV.shift + "\n"
33
+
34
+ # Load seckey/keyring
35
+ seckeyring_mem = LibC::calloc(1, LibRNP::PGPKeyring.size)
36
+ seckeyring = LibRNP::PGPKeyring.new(seckeyring_mem)
37
+ if 1 != LibRNP::pgp_keyring_fileread(seckeyring, options[:keys_armored] ? 1 : 0, seckey_filename)
38
+ puts 'Errors encountered while loading secret keyring.'
39
+ exit 1
40
+ end
41
+ # Find first seckey
42
+ keycount = LibRNP::dynarray_count(seckeyring, 'key')
43
+ seckey = nil
44
+ (0..keycount - 1).each {|keyn|
45
+ key = LibRNP::dynarray_get_item(seckeyring, 'key', LibRNP::PGPKey, keyn)
46
+ seckey = key if LibRNP::pgp_is_key_secret(key)
47
+ break if seckey != nil
48
+ }
49
+ if seckey == nil
50
+ puts 'No seckey found'
51
+ exit 1
52
+ end
53
+
54
+ pgpio = LibRNP::PGPIO.new
55
+ stderr_fp = LibC::fdopen($stderr.to_i, 'w')
56
+ # send all to stderr
57
+ pgpio[:outs] = stderr_fp
58
+ pgpio[:errs] = stderr_fp
59
+ pgpio[:res] = stderr_fp
60
+
61
+ rd, wr = IO.pipe
62
+ wr.write passphrase
63
+ wr.close
64
+ passfp = LibC::fdopen(rd.to_i, 'r')
65
+ seckey = LibRNP::pgp_decrypt_seckey(seckey, passfp)
66
+ rd.close
67
+ LibC::fclose(passfp)
68
+
69
+ if seckey == nil
70
+ puts 'Invalid passphrase.'
71
+ exit 1
72
+ end
73
+ seckey = LibRNP::PGPSecKey.new(seckey)
74
+
75
+ armored = options[:armored] ? 1 : 0
76
+ cleartext = options[:cleartext] ? 1 : 0
77
+ from = Time.now.to_i
78
+ duration = 0
79
+ # see pgp_str_to_hash_alg
80
+ hashname = 'sha1'
81
+
82
+ $stdin.binmode
83
+ data = $stdin.read
84
+ data_buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
85
+ data_buf.put_bytes(0, data)
86
+ memory_ptr = LibRNP::pgp_sign_buf(pgpio, data_buf, data_buf.size, seckey, from, duration, hashname, armored, cleartext)
87
+ if not memory_ptr.null?
88
+ memory = LibRNP::PGPMemory.new(memory_ptr)
89
+ $stdout.binmode
90
+ $stdout.puts memory[:buf].read_bytes(memory[:length])
91
+ LibRNP::pgp_memory_free(memory)
92
+ $stderr.puts 'Success'
93
+ else
94
+ $stderr.puts 'Failed!'
95
+ end
96
+
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'io/console'
4
+
5
+ require_relative '../../lib/rnp'
6
+
7
+ options = {keys_armored: false, armored: false}
8
+ parser = OptionParser.new do |opts|
9
+ opts.banner = "Usage: #{$0} [options] <pubkey> <input-file>"
10
+ opts.on('-k', '--keys-armored', 'Pubkey is ASCII armored') do
11
+ options[:keys_armored] = true
12
+ end
13
+ opts.on('-a', '--armored', 'Input file is ASCII armored') do
14
+ options[:armored] = true
15
+ end
16
+ opts.on('-h', '--help', 'Print this help') do
17
+ puts opts
18
+ exit
19
+ end
20
+ end
21
+ parser.parse!
22
+
23
+ if ARGV.length != 2
24
+ parser.display
25
+ exit
26
+ end
27
+
28
+ pubkey_filename = ARGV.shift
29
+ input_filename = ARGV.shift
30
+
31
+ # Load keys/keyring
32
+ keyring_mem = LibC::calloc(1, LibRNP::PGPKeyring.size)
33
+ keyring = LibRNP::PGPKeyring.new(keyring_mem)
34
+ if 1 != LibRNP::pgp_keyring_fileread(keyring, options[:keys_armored] ? 1 : 0, pubkey_filename)
35
+ puts 'Errors encountered while loading keyring.'
36
+ exit 1
37
+ end
38
+
39
+ pgpio = LibRNP::PGPIO.new
40
+ stdout_fp = LibC::fdopen($stdout.to_i, 'w')
41
+ stderr_fp = LibC::fdopen($stderr.to_i, 'w')
42
+ pgpio[:outs] = stdout_fp
43
+ pgpio[:errs] = stderr_fp
44
+ pgpio[:res] = stdout_fp
45
+
46
+ armored = options[:armored] ? 1 : 0
47
+
48
+ validation = LibRNP::PGPValidation.new
49
+ ret = LibRNP::pgp_validate_file(pgpio, validation, input_filename, nil, armored, keyring)
50
+ if ret == 1
51
+ puts 'Success'
52
+ else
53
+ puts 'Failed!'
54
+ end
55
+
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'io/console'
4
+
5
+ require_relative '../../lib/rnp'
6
+
7
+ options = {keys_armored: false, armored: false}
8
+ parser = OptionParser.new do |opts|
9
+ opts.banner = "Usage: #{$0} [options] <pubkey>"
10
+ opts.on('-k', '--keys-armored', 'Pubkey is ASCII armored') do
11
+ options[:keys_armored] = true
12
+ end
13
+ opts.on('-a', '--armored', 'Input file is ASCII armored') do
14
+ options[:armored] = true
15
+ end
16
+ opts.on('-h', '--help', 'Print this help') do
17
+ puts opts
18
+ exit
19
+ end
20
+ end
21
+ parser.parse!
22
+
23
+ if ARGV.length != 1
24
+ parser.display
25
+ exit
26
+ end
27
+
28
+ pubkey_filename = ARGV.shift
29
+
30
+ # Load keys/keyring
31
+ keyring_mem = LibC::calloc(1, LibRNP::PGPKeyring.size)
32
+ keyring = LibRNP::PGPKeyring.new(keyring_mem)
33
+ if 1 != LibRNP::pgp_keyring_fileread(keyring, options[:keys_armored] ? 1 : 0, pubkey_filename)
34
+ puts 'Errors encountered while loading keyring.'
35
+ exit 1
36
+ end
37
+
38
+ pgpio = LibRNP::PGPIO.new
39
+ stderr_fp = LibC::fdopen($stderr.to_i, 'w')
40
+ pgpio[:outs] = stderr_fp
41
+ pgpio[:errs] = stderr_fp
42
+ pgpio[:res] = stderr_fp
43
+
44
+ armored = options[:armored] ? 1 : 0
45
+
46
+ validation = LibRNP::PGPValidation.new
47
+ mem_ptr = LibC::calloc(1, LibRNP::PGPMemory.size)
48
+ mem = LibRNP::PGPMemory.new(mem_ptr)
49
+
50
+ $stdin.binmode
51
+ data = $stdin.read
52
+ data_buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
53
+ data_buf.put_bytes(0, data)
54
+ LibRNP::pgp_memory_add(mem, data_buf, data_buf.size)
55
+ ret = LibRNP::pgp_validate_mem(pgpio, validation, mem, nil, armored, keyring)
56
+ if ret == 1
57
+ puts 'Success'
58
+ else
59
+ puts 'Failed!'
60
+ end
61
+
@@ -0,0 +1,96 @@
1
+ require 'rubygems'
2
+
3
+ module RNP
4
+
5
+ class PublicKeyAlgorithm
6
+ NONE = 0
7
+ RSA = 1
8
+ RSA_ENCRYPT_ONLY = 2
9
+ RSA_SIGN_ONLY = 3
10
+ ELGAMAL = 16
11
+ DSA = 17
12
+ ECDH = 18
13
+ ECDSA = 19
14
+ FORMERLY_ELGAMAL = 20
15
+
16
+ def self.from_native(alg)
17
+ raise if alg.class != Symbol
18
+ LibRNP::PGP_PUBKEY_ALG_T[alg]
19
+ end
20
+
21
+ def self.to_native(alg)
22
+ # avoid a warning on newer versions of ruby
23
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.4.0')
24
+ raise if alg.class != Integer
25
+ else
26
+ raise if alg.class != Fixnum
27
+ end
28
+ LibRNP::PGP_PUBKEY_ALG_T[alg]
29
+ end
30
+
31
+ end
32
+
33
+ class HashAlgorithm
34
+ MD5 = 1
35
+ SHA1 = 2
36
+ RIPEMD = 3
37
+ SHA256 = 8
38
+ SHA384 = 9
39
+ SHA512 = 10
40
+ SHA224 = 11
41
+
42
+ # see pgp_str_to_hash_alg
43
+ STRING_MAPPING = {
44
+ MD5 => 'md5',
45
+ SHA1 => 'sha1',
46
+ SHA256 => 'sha256',
47
+ SHA384 => 'sha384',
48
+ SHA512 => 'sha512'
49
+ }
50
+
51
+ def self.to_s(alg)
52
+ STRING_MAPPING[alg]
53
+ end
54
+
55
+ end
56
+
57
+ class SymmetricKeyAlgorithm
58
+ PLAINTEXT = 0
59
+ IDEA = 1
60
+ TRIPLEDES = 2
61
+ CAST5 = 3
62
+ BLOWFISH = 4
63
+ AES128 = 7
64
+ AES192 = 8
65
+ AES256 = 9
66
+ TWOFISH256 = 10
67
+
68
+ # see pgp_str_to_cipher
69
+ STRING_MAPPING = {
70
+ IDEA => 'idea',
71
+ TRIPLEDES => 'tripledes',
72
+ CAST5 => 'cast5',
73
+ AES128 => 'aes128',
74
+ AES256 => 'aes256'
75
+ }
76
+
77
+ def self.to_s(alg)
78
+ STRING_MAPPING[alg]
79
+ end
80
+
81
+ end
82
+
83
+ class StringToKeyUsage
84
+ NONE = 0
85
+ ENCRYPTED_AND_HASHED = 254
86
+ ENCRYPTED = 255
87
+ end
88
+
89
+ class StringToKeySpecifier
90
+ SIMPLE = 0
91
+ SALTED = 1
92
+ ITERATED_AND_SALTED = 3
93
+ end
94
+
95
+ end
96
+
@@ -0,0 +1,259 @@
1
+ require 'English'
2
+ require 'forwardable'
3
+
4
+ module RNP
5
+
6
+ class Keyring
7
+ extend Forwardable
8
+ delegate [:size, :each, :select, :[], :push, :clear] => :@keys
9
+
10
+ attr_reader :keys
11
+
12
+ def initialize
13
+ @keys = []
14
+ end
15
+
16
+ def self.load(data, armored=true, &passphrase_provider)
17
+ kr = Keyring.new
18
+ kr.add(data, armored, &passphrase_provider)
19
+ kr
20
+ end
21
+
22
+ def add(data, armored=true, &passphrase_provider)
23
+ keys = RNP::load_keys(data, armored, &passphrase_provider)
24
+ @keys.push(*keys)
25
+ keys.size
26
+ end
27
+
28
+ def verify(data, armored=true)
29
+ RNP::verify(@keys, data, armored)
30
+ end
31
+
32
+ def export(key, armored=true)
33
+ raise if key.parent
34
+ is_public = key.is_a?(PublicKey)
35
+ if is_public
36
+ seckey = secret_keys.find {|sk| sk.key_id == key.key_id}
37
+ else
38
+ seckey = key
39
+ end
40
+ return nil if not seckey
41
+ output_ptr = FFI::MemoryPointer.new(:pointer)
42
+ mem_ptr = FFI::MemoryPointer.new(:pointer)
43
+ output = nil
44
+ mem = nil
45
+ decrypted_seckey = nil
46
+ begin
47
+ LibRNP::pgp_setup_memory_write(output_ptr, mem_ptr, 4096)
48
+ output = LibRNP::PGPOutput.new(output_ptr.read_pointer)
49
+ mem = LibRNP::PGPMemory.new(mem_ptr.read_pointer)
50
+ native_ptr = LibC::calloc(1, LibRNP::PGPKey.size)
51
+ native = LibRNP::PGPKey.new(native_ptr)
52
+ native_auto = FFI::AutoPointer.new(native_ptr, LibRNP::PGPKey.method(:release))
53
+ key.to_native_key(native)
54
+ decrypted_seckey = seckey.decrypted_seckey
55
+ return nil if not decrypted_seckey
56
+ # this is necessary for signatures
57
+ seckey = SecretKey.from_native(decrypted_seckey)
58
+ seckey.to_native(native[:key][:seckey])
59
+ native[:type] = :PGP_PTAG_CT_SECRET_KEY
60
+ if is_public
61
+ LibRNP::dynarray_clear(native, 'uid', :string)
62
+ key.userids.each {|userid|
63
+ LibRNP::pgp_add_selfsigned_userid(native, userid)
64
+ }
65
+ end
66
+ # PGPKeyring is a ManagedStruct
67
+ subkeysring_ptr = LibC::calloc(1, LibRNP::PGPKeyring.size)
68
+ subkeysring = LibRNP::PGPKeyring.new(subkeysring_ptr)
69
+ RNP::keys_to_native_keyring(key.subkeys, subkeysring)
70
+ # add a binding signature to each subkey
71
+ (0..LibRNP::dynarray_count(subkeysring, 'key') - 1).each {|n|
72
+ subkey = LibRNP::dynarray_get_item(subkeysring, 'key', LibRNP::PGPKey, n)
73
+ LibRNP::dynarray_clear(subkey, 'packet', LibRNP::PGPSubPacket)
74
+ RNP::add_subkey_signature(native, subkey)
75
+ }
76
+ if is_public
77
+ ret = LibRNP::pgp_write_xfer_pubkey(output, native, subkeysring, armored ? 1 : 0)
78
+ else
79
+ decrypted_key_ptr = LibC::calloc(1, LibRNP::PGPKey.size)
80
+ decrypted_key = LibRNP::PGPKey.new(decrypted_key_ptr)
81
+ decrypted_key_auto = FFI::AutoPointer.new(decrypted_key_ptr, LibRNP::PGPKey.method(:release))
82
+ seckey.to_native_key(decrypted_key)
83
+ key.userids.each {|userid|
84
+ LibRNP::pgp_add_selfsigned_userid(decrypted_key, userid)
85
+ }
86
+ decrypted_key[:key][:seckey][:s2k_usage] = :PGP_S2KU_ENCRYPTED_AND_HASHED
87
+ decrypted_key[:key][:seckey][:alg] = :PGP_SA_CAST5
88
+ decrypted_key[:key][:seckey][:s2k_specifier] = :PGP_S2KS_SALTED
89
+ (0..LibRNP::dynarray_count(subkeysring, 'key') - 1).each {|n|
90
+ subkey = LibRNP::dynarray_get_item(subkeysring, 'key', LibRNP::PGPKey, n)
91
+ subkey[:key][:seckey][:s2k_usage] = :PGP_S2KU_ENCRYPTED_AND_HASHED
92
+ subkey[:key][:seckey][:alg] = :PGP_SA_CAST5
93
+ subkey[:key][:seckey][:s2k_specifier] = :PGP_S2KS_SALTED
94
+ }
95
+ ret = LibRNP::pgp_write_xfer_seckey(output, decrypted_key, key.passphrase, key.passphrase.size, subkeysring, armored ? 1 : 0)
96
+ end
97
+ return nil if ret != 1
98
+ data = mem[:buf].read_bytes(mem[:length])
99
+ data
100
+ ensure
101
+ LibRNP::pgp_teardown_memory_write(output, mem) if mem
102
+ LibRNP::pgp_seckey_free(decrypted_seckey) if decrypted_seckey
103
+ end
104
+ end
105
+
106
+ def public_keys
107
+ self.select {|key|
108
+ key.is_a?(PublicKey)
109
+ }
110
+ end
111
+
112
+ def secret_keys
113
+ self.select {|key|
114
+ key.is_a?(SecretKey)
115
+ }
116
+ end
117
+
118
+ def to_native(native)
119
+ keys_to_native_keyring(@keys, native)
120
+ end
121
+
122
+ end
123
+
124
+ PARSE_KEYRING = Proc.new do |state, passphrase_provider, pkt, data|
125
+ next :PGP_RELEASE_MEMORY if state[:errors].any?
126
+
127
+ begin
128
+ lastkey = state[:keys].last
129
+ case pkt[:tag]
130
+ when :PGP_PTAG_CT_PUBLIC_KEY
131
+ key = PublicKey::from_native(pkt[:u][:pubkey])
132
+ state[:keys].push(key)
133
+ when :PGP_PTAG_CT_PUBLIC_SUBKEY
134
+ key = PublicKey::from_native(pkt[:u][:pubkey])
135
+ lastkey.add_subkey(key)
136
+ state[:keys].push(key)
137
+ when :PGP_PTAG_CT_ENCRYPTED_SECRET_KEY
138
+ key = SecretKey::from_native(pkt[:u][:seckey], true)
139
+ state[:keys].push(key)
140
+ when :PGP_PTAG_CT_ENCRYPTED_SECRET_SUBKEY
141
+ key = SecretKey::from_native(pkt[:u][:seckey], true)
142
+ lastkey.add_subkey(key)
143
+ state[:keys].push(key)
144
+ when :PGP_PTAG_CT_SECRET_KEY
145
+ key = SecretKey::from_native(pkt[:u][:seckey])
146
+ if state[:passphrase]
147
+ key.passphrase = state[:passphrase]
148
+ state[:passphrase] = nil
149
+ end
150
+ state[:keys].push(key)
151
+ when :PGP_PTAG_CT_SECRET_SUBKEY
152
+ key = SecretKey::from_native(pkt[:u][:seckey])
153
+ lastkey.add_subkey(key)
154
+ state[:keys].push(key)
155
+ when :PGP_GET_PASSPHRASE
156
+ seckey_ptr = pkt[:u][:skey_passphrase][:seckey]
157
+ seckey = LibRNP::PGPSecKey.new(seckey_ptr)
158
+ key = SecretKey::from_native(seckey)
159
+ passphrase = passphrase_provider.call(key)
160
+ if passphrase and passphrase != ''
161
+ passphrase_mem = LibC::calloc(1, passphrase.bytesize + 1)
162
+ passphrase_mem.write_bytes(passphrase)
163
+ pkt[:u][:skey_passphrase][:passphrase].write_pointer(passphrase_mem)
164
+ state[:passphrase] = passphrase
165
+ next :PGP_KEEP_MEMORY
166
+ end
167
+ when :PGP_PARSER_PACKET_END
168
+ if lastkey.is_a? RNP::SecretKey
169
+ raw_packet = pkt[:u][:packet]
170
+ bytes = raw_packet[:raw].read_bytes(raw_packet[:length])
171
+ lastkey.raw_subpackets.push(bytes)
172
+ end
173
+ when :PGP_PTAG_CT_USER_ID
174
+ lastkey.userids.push(pkt[:u][:userid].force_encoding('utf-8'))
175
+ when :PGP_PTAG_SS_KEY_EXPIRY
176
+ lastkey.expiration_time = lastkey.creation_time + pkt[:u][:ss_time]
177
+ else
178
+ # For debugging
179
+ #puts "Unhandled tag: #{pkt[:tag]}"
180
+ end # case
181
+ rescue
182
+ state[:errors].push($ERROR_INFO)
183
+ end
184
+ next :PGP_RELEASE_MEMORY
185
+ end
186
+
187
+ DEFAULT_PASSPHRASE_PROVIDER = Proc.new do |seckey|
188
+ nil
189
+ end
190
+
191
+ def self.load_keys(data, armored=true, &passphrase_provider)
192
+ # Just for readability
193
+ print_errors = 0
194
+ stream_mem = LibC::calloc(1, LibRNP::PGPStream.size)
195
+ # This will free the above memory (PGPStream is a ManagedStruct)
196
+ stream = LibRNP::PGPStream.new(stream_mem)
197
+ stream[:readinfo][:accumulate] = 1
198
+ LibRNP::pgp_parse_options(stream, :PGP_PTAG_SS_ALL, :PGP_PARSE_PARSED)
199
+
200
+ # This memory will be GC'd
201
+ mem = FFI::MemoryPointer.new(:uint8, data.bytesize)
202
+ mem.write_bytes(data)
203
+
204
+ LibRNP::pgp_reader_set_memory(stream, mem, mem.size)
205
+ state = {keys: [], errors: []}
206
+ provider = block_given? ? passphrase_provider : DEFAULT_PASSPHRASE_PROVIDER
207
+ callback = RNP::PARSE_KEYRING.curry[state][provider]
208
+ LibRNP::pgp_set_callback(stream, callback, nil)
209
+ LibRNP::pgp_reader_push_dearmour(stream) if armored
210
+ if LibRNP::pgp_parse(stream, print_errors) != 1
211
+ state[:errors].push('pgp_parse failed')
212
+ end
213
+ LibRNP::pgp_reader_pop_dearmour(stream) if armored
214
+
215
+ errors = stream_errors(stream)
216
+ state[:errors].push(errors) if errors.any?
217
+
218
+ raise state[:errors].join("\n") if state[:errors].any?
219
+ state[:keys]
220
+ end
221
+
222
+ def self.keys_to_native_keyring(keys, native)
223
+ raise if not native[:keys].null?
224
+
225
+ for key in keys
226
+ native_key = LibRNP::PGPKey.new
227
+ key.to_native_key(native_key)
228
+ LibRNP::dynarray_append_item(native, 'key', LibRNP::PGPKey, native_key)
229
+ end
230
+ end
231
+
232
+ def self.verify(keys, data, armored=true)
233
+ native_keyring_ptr = LibC::calloc(1, LibRNP::PGPKeyring.size)
234
+ native_keyring = LibRNP::PGPKeyring.new(native_keyring_ptr)
235
+ RNP::keys_to_native_keyring(keys, native_keyring)
236
+
237
+ pgpio = LibRNP::PGPIO.new
238
+ pgpio[:outs] = LibC::fdopen($stdout.to_i, 'w')
239
+ pgpio[:errs] = LibC::fdopen($stderr.to_i, 'w')
240
+ pgpio[:res] = pgpio[:errs]
241
+
242
+ data_buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
243
+ data_buf.write_bytes(data)
244
+
245
+ # pgp_validate_mem frees this
246
+ mem_ptr = LibC::calloc(1, LibRNP::PGPMemory.size)
247
+ mem = LibRNP::PGPMemory.new(mem_ptr)
248
+ LibRNP::pgp_memory_add(mem, data_buf, data_buf.size)
249
+
250
+ # ManagedStruct, this frees itself
251
+ result_ptr = LibC::calloc(1, LibRNP::PGPValidation.size)
252
+ result = LibRNP::PGPValidation.new(result_ptr)
253
+
254
+ ret = LibRNP::pgp_validate_mem(pgpio, result, mem, nil, armored ? 1 : 0, native_keyring)
255
+ ret == 1
256
+ end
257
+
258
+ end # module RNP
259
+