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 | 
            +
             |