class Rnp

Class used for interacting with RNP.

© 2018 Ribose Inc.

Constants

ERRORS_MAP

@api private

KEY_PROVIDER
PASS_PROVIDER
VERSION

Attributes

ptr[R]

@api private

Public Class Methods

call_ffi(fn, *args) click to toggle source

@api private

Calls the LibRnp FFI function indicated. If the return code is <0, an error will be raised.

@param fn [Symbol] the name of the function to call @param args the arguments to pass to the FFI function @return [void]

# File lib/rnp/utils.rb, line 19
def self.call_ffi(fn, *args)
  rc = LibRnp.method(fn).call(*args)
  Rnp.raise_error("#{fn} failed", rc) unless rc.zero?
  nil
end
dearmor(input:, output: nil) click to toggle source

Remove ASCII Armor from data.

@param input [Input] the input to read the ASCII-Armored data from @param output [Output] the output to write the dearmored data to. If

nil, the result will be returned directly as a String.

@return [nil, String]

# File lib/rnp/misc.rb, line 90
def self.dearmor(input:, output: nil)
  Output.default(output) do |output_|
    Rnp.call_ffi(:rnp_dearmor, input.ptr, output_.ptr)
  end
end
default_homedir() click to toggle source

Get the default homedir for RNP.

@return [String]

# File lib/rnp/misc.rb, line 14
def self.default_homedir
  pptr = FFI::MemoryPointer.new(:pointer)
  Rnp.call_ffi(:rnp_get_default_homedir, pptr)
  begin
    phomedir = pptr.read_pointer
    phomedir.read_string unless phomedir.null?
  ensure
    LibRnp.rnp_buffer_destroy(phomedir)
  end
end
destroy(ptr) click to toggle source

@api private

# File lib/rnp/rnp.rb, line 36
def self.destroy(ptr)
  LibRnp.rnp_ffi_destroy(ptr)
end
enarmor(input:, output: nil, type: nil) click to toggle source

Add ASCII Armor to data.

@param input [Input] the input to read data from @param output [Output] the output to write the armored

data to. If nil, the result will be returned directly
as a String.

@return [nil, String]

# File lib/rnp/misc.rb, line 78
def self.enarmor(input:, output: nil, type: nil)
  Output.default(output) do |output_|
    Rnp.call_ffi(:rnp_enarmor, input.ptr, output_.ptr, type)
  end
end
homedir_info(homedir) click to toggle source

Attempt to detect information about a homedir.

@param homedir [String] the homedir @return [Hash<Symbol>]

* :public [Hash<Symbol>]
  * :format [String]
  * :path [String]
* :secret [Hash<Symbol>]
  * :format [String]
  * :path [String]
# File lib/rnp/misc.rb, line 35
def self.homedir_info(homedir)
  pptrs = FFI::MemoryPointer.new(:pointer, 4)
  Rnp.call_ffi(:rnp_detect_homedir_info, homedir, pptrs[0], pptrs[1],
               pptrs[2], pptrs[3])
  ptrs = (0..3).collect { |i| pptrs[i] }.map(&:read_pointer)
  return if ptrs.all?(&:null?)
  {
    public: {
      format: ptrs[0].read_string,
      path: ptrs[1].read_string
    },
    secret: {
      format: ptrs[2].read_string,
      path: ptrs[3].read_string
    }
  }
ensure
  ptrs&.each { |ptr| LibRnp.rnp_buffer_destroy(ptr) }
end
inspect_ptr(myself) click to toggle source

@api private

# File lib/rnp/utils.rb, line 26
def self.inspect_ptr(myself)
  ptr_format = "0x%0#{FFI::Pointer.size * 2}x"
  ptr_s = format(ptr_format, myself.instance_variable_get(:@ptr).address)
  class_name = myself.class.to_s
  "#<#{class_name}:#{ptr_s}>"
end
key_format(key_data) click to toggle source

Attempt to detect the format of a key.

@param key_data [String] the key data @return [String]

# File lib/rnp/misc.rb, line 59
def self.key_format(key_data)
  pptr = FFI::MemoryPointer.new(:pointer)
  data = FFI::MemoryPointer.from_data(key_data)
  Rnp.call_ffi(:rnp_detect_key_format, data, data.size, pptr)
  begin
    pformat = pptr.read_pointer
    pformat.read_string unless pformat.null?
  ensure
    LibRnp.rnp_buffer_destroy(pformat)
  end
end
new(pubfmt = 'GPG', secfmt = 'GPG') click to toggle source

Create a new interface to RNP.

@param pubfmt [String] the public keyring format @param secfmt [String] the secret keyring format

# File lib/rnp/rnp.rb, line 27
def initialize(pubfmt = 'GPG', secfmt = 'GPG')
  pptr = FFI::MemoryPointer.new(:pointer)
  Rnp.call_ffi(:rnp_ffi_create, pptr, pubfmt, secfmt)
  @ptr = FFI::AutoPointer.new(pptr.read_pointer, self.class.method(:destroy))
  @key_provider = nil
  @password_provider = nil
end
raise_error(msg, rc = nil) click to toggle source

@api private

# File lib/rnp/error.rb, line 33
def self.raise_error(msg, rc = nil)
  klass = ERRORS_MAP.fetch(rc, Error)
  raise klass.new(msg, rc)
end
version() click to toggle source

Get the version stamp of the rnp library as an unsigned 32-bit integer. This number can be compared against other stamps generated with {version_for}.

@return [Integer]

# File lib/rnp/misc.rb, line 115
def self.version
  LibRnp.rnp_version
end
version_for(major, minor, patch) click to toggle source

Encode the given major, minor, and patch numbers into a version stamp.

@return [Integer]

# File lib/rnp/misc.rb, line 123
def self.version_for(major, minor, patch)
  LibRnp.rnp_version_for(major, minor, patch)
end
version_major(version) click to toggle source

Extract the major version component from the given version stamp.

@return [Integer]

# File lib/rnp/misc.rb, line 130
def self.version_major(version)
  LibRnp.rnp_version_major(version)
end
version_minor(version) click to toggle source

Extract the minor version component from the given version stamp.

@return [Integer]

# File lib/rnp/misc.rb, line 137
def self.version_minor(version)
  LibRnp.rnp_version_minor(version)
end
version_patch(version) click to toggle source

Extract the patch version component from the given version stamp.

@return [Integer]

# File lib/rnp/misc.rb, line 144
def self.version_patch(version)
  LibRnp.rnp_version_patch(version)
end
version_string() click to toggle source

Get the version of the rnp library as a string.

@return [String]

# File lib/rnp/misc.rb, line 99
def self.version_string
  LibRnp.rnp_version_string
end
version_string_full() click to toggle source

Get the detailed version of the rnp library as a string.

@return [String]

# File lib/rnp/misc.rb, line 106
def self.version_string_full
  LibRnp.rnp_version_string_full
end

Public Instance Methods

cleartext_sign(input:, output: nil, signers:, compression: nil, creation_time: nil, expiration_time: nil, hash: nil) click to toggle source

Create a cleartext signature.

@param input (see sign) @param output (see sign) @param signers [Key, Array<Key>] the keys to sign with @param compression (see Sign#compression=) @param creation_time (see Sign#creation_time=) @param expiration_time (see Sign#expiration_time=) @param hash (see Sign#hash=) @return [nil, String]

# File lib/rnp/rnp.rb, line 278
def cleartext_sign(input:, output: nil, signers:,
                   compression: nil,
                   creation_time: nil,
                   expiration_time: nil,
                   hash: nil)
  Output.default(output) do |output_|
    sign = start_cleartext_sign(input: input, output: output_)
    sign.options = {
      compression: compression,
      creation_time: creation_time,
      expiration_time: expiration_time,
      hash: hash
    }
    simple_sign(sign, signers)
  end
end
decrypt(input:, output: nil) click to toggle source

Decrypt encrypted data.

@param input [Input] the input to read the encrypted data @param output [Output] the output to write the decrypted data.

If nil, the result will be returned directly as a String.

@return [nil, String]

# File lib/rnp/rnp.rb, line 444
def decrypt(input:, output: nil)
  Output.default(output) do |output_|
    Rnp.call_ffi(:rnp_decrypt, @ptr, input.ptr, output_.ptr)
  end
end
detached_sign(input:, output: nil, signers:, armored: nil, compression: nil, creation_time: nil, expiration_time: nil, hash: nil) click to toggle source

Create a detached signature.

@param input (see sign) @param output (see sign) @param signers [Key, Array<Key>] the keys to sign with @param armored (see Sign#armored=) @param compression (see Sign#compression=) @param creation_time (see Sign#creation_time=) @param expiration_time (see Sign#expiration_time=) @param hash (see Sign#hash=) @return [nil, String]

# File lib/rnp/rnp.rb, line 306
def detached_sign(input:, output: nil, signers:,
                  armored: nil,
                  compression: nil,
                  creation_time: nil,
                  expiration_time: nil,
                  hash: nil)
  Output.default(output) do |output_|
    sign = start_detached_sign(input: input, output: output_)
    sign.options = {
      armored: armored,
      compression: compression,
      creation_time: creation_time,
      expiration_time: expiration_time,
      hash: hash
    }
    simple_sign(sign, signers)
  end
end
detached_verify(data:, signature:) click to toggle source

Verify a detached signature.

@param data [Input] the input to read the data @param signature [Input] the input to read the signatures

# File lib/rnp/rnp.rb, line 338
def detached_verify(data:, signature:)
  verify = start_detached_verify(data: data, signature: signature)
  verify.execute
end
encrypt(input:, output: nil, recipients:, armored: nil, compression: nil, cipher: nil) click to toggle source

Encrypt data with a public key.

@param input [Input] the input to read the plaintext @param output [Output] the output to write the encrypted data.

If nil, the result will be returned directly as a String.

@param recipients [Key, Array<Key>] list of recipients keys @param armored (see Encrypt#armored=) @param compression (see Encrypt#compression=) @param cipher (see Encrypt#cipher=)

# File lib/rnp/rnp.rb, line 352
def encrypt(input:, output: nil, recipients:,
            armored: nil,
            compression: nil,
            cipher: nil)
  Output.default(output) do |output_|
    enc = start_encrypt(input: input, output: output_)
    enc.options = {
      armored: armored,
      compression: compression,
      cipher: cipher
    }
    simple_encrypt(enc, recipients: recipients)
  end
end
encrypt_and_sign(input:, output: nil, recipients:, signers:, armored: nil, compression: nil, cipher: nil, hash: nil, creation_time: nil, expiration_time: nil) click to toggle source

Encrypt and sign data with a public key.

@param input (see encrypt) @param output (see encrypt) @param recipients (see encrypt) @param signers [Key, Array<Key>] list of keys to sign with @param armored (see Encrypt#armored=) @param compression (see Encrypt#compression=) @param cipher (see Encrypt#cipher=) @param hash (see Encrypt#hash=) @param creation_time (see Encrypt#creation_time=) @param expiration_time (see Encrypt#expiration_time=)

# File lib/rnp/rnp.rb, line 379
def encrypt_and_sign(input:, output: nil, recipients:, signers:,
                     armored: nil,
                     compression: nil,
                     cipher: nil,
                     hash: nil,
                     creation_time: nil,
                     expiration_time: nil)
  Output.default(output) do |output_|
    enc = start_encrypt(input: input, output: output_)
    enc.options = {
      armored: armored,
      compression: compression,
      cipher: cipher,
      hash: hash,
      creation_time: creation_time,
      expiration_time: expiration_time
    }
    simple_encrypt(enc, recipients: recipients, signers: signers)
  end
end
find_key(criteria) click to toggle source

Find a key.

@param criteria [Hash] the search criteria. Some examples would be:

* !{keyid: '2FCADF05FFA501BB'}
* !{'userid': 'user0'}
* !{fingerprint: 'BE1C4AB951F4C2F6B604'}
Only *one* criteria can be specified.

@return [Key, nil]

# File lib/rnp/rnp.rb, line 165
def find_key(criteria)
  raise Rnp::Error, 'Invalid search criteria' if !criteria.is_a?(::Hash) ||
                                                 criteria.size != 1
  pptr = FFI::MemoryPointer.new(:pointer)
  Rnp.call_ffi(:rnp_locate_key, @ptr, criteria.keys[0].to_s,
               criteria.values[0], pptr)
  pkey = pptr.read_pointer
  Rnp::Key.new(pkey) unless pkey.null?
end
generate_key(description) click to toggle source

Generate a new key or pair of keys.

@note The generated key(s) will be unprotected and unlocked.

The application should protect and lock the keys with
{Key#protect} and {Key#lock}.

Examples

examples/key_generation.rb

{include:file:examples/key_generation.rb}

@param description [String, Hash] @return [Hash<Symbol, Key>] a hash containing the generated key(s)

# File lib/rnp/rnp.rb, line 112
def generate_key(description)
  description = JSON.generate(description) unless description.is_a?(String)
  pptr = FFI::MemoryPointer.new(:pointer)
  Rnp.call_ffi(:rnp_generate_key_json, @ptr, description, pptr)
  begin
    presults = pptr.read_pointer
    return nil if presults.null?
    results = JSON.parse(presults.read_string)
    generated = {}
    results.each do |k, v|
      key = find_key(v.keys[0].to_sym => v.values[0])
      generated[k.to_sym] = key
    end
    generated
  ensure
    LibRnp.rnp_buffer_destroy(presults)
  end
end
inspect() click to toggle source
# File lib/rnp/rnp.rb, line 40
def inspect
  Rnp.inspect_ptr(self)
end
key_provider=(provider) click to toggle source

Set a key provider.

The key provider is useful if, for example, you have a database of keys and you do not want to load all of them, and you don't know which will be needed for a given operation.

The key provider will be called to request that a key be loaded, and the key provider is responsible for loading the appropriate key (if available) using {#load_keys}.

The provider may be called multiple times for the same key, but with different identifiers. For example, it may first be called with a fingerprint, then (if the key was not loaded), it may be called with a keyid.

Examples

examples/key_provider.rb

{include:file:examples/key_provider.rb}

@param provider [Proc, call] a callable object

# File lib/rnp/rnp.rb, line 73
def key_provider=(provider)
  @key_provider = provider
  @key_provider = KEY_PROVIDER.curry[provider] if provider
  Rnp.call_ffi(:rnp_ffi_set_key_provider, @ptr, @key_provider, nil)
end
load_keys(input:, format:, public_keys: true, secret_keys: true) click to toggle source

Load keys.

@param format [String] the format of the keys to load (GPG, KBX, G10). @param input [Input] the input to read the keys from @param public_keys [Boolean] whether to load public keys @param secret_keys [Boolean] whether to load secret keys @return [void]

# File lib/rnp/rnp.rb, line 138
def load_keys(input:, format:, public_keys: true, secret_keys: true)
  raise ArgumentError, 'At least one of public_keys or secret_keys must be true' if !public_keys && !secret_keys
  flags = load_save_flags(public_keys: public_keys, secret_keys: secret_keys)
  Rnp.call_ffi(:rnp_load_keys, @ptr, format, input.ptr, flags)
end
log=(fd) click to toggle source

Set a logging destination.

@param fd [Integer, IO] the file descriptor to log to. This will be closed

when this object is destroyed.
# File lib/rnp/rnp.rb, line 48
def log=(fd)
  fd = fd.to_i if fd.is_a(::IO)
  Rnp.call_ffi(:rnp_ffi_set_log_fd, @ptr, fd)
end
password_provider=(provider) click to toggle source

Set a password provider.

The password provider is used for retrieving passwords for various operations, including:

  • Signing data

  • Decrypting data (public-key or symmetric)

  • Adding a userid to a key

  • Unlocking a key

  • Unprotecting a key

Examples

examples/password_provider.rb

{include:file:examples/password_provider.rb}

@param provider [Proc, call, String] a callable object, or a password

# File lib/rnp/rnp.rb, line 94
def password_provider=(provider)
  @password_provider = provider
  @password_provider = PASS_PROVIDER.curry[provider] if provider
  Rnp.call_ffi(:rnp_ffi_set_pass_provider, @ptr, @password_provider, nil)
end
public_key_count() click to toggle source
# File lib/rnp/rnp.rb, line 225
def public_key_count
  pcount = FFI::MemoryPointer.new(:size_t)
  Rnp.call_ffi(:rnp_get_public_key_count, @ptr, pcount)
  pcount.read(:size_t)
end
save_keys(output:, format:, public_keys: false, secret_keys: false) click to toggle source

Save keys.

@param format [String] the format to save the keys in (GPG, KBX, G10). @param output [Output] the output to write the keys to @param public_keys [Boolean] whether to load public keys @param secret_keys [Boolean] whether to load secret keys @return [void]

# File lib/rnp/rnp.rb, line 151
def save_keys(output:, format:, public_keys: false, secret_keys: false)
  raise ArgumentError, 'At least one of public_keys or secret_keys must be true' if !public_keys && !secret_keys
  flags = load_save_flags(public_keys: public_keys, secret_keys: secret_keys)
  Rnp.call_ffi(:rnp_save_keys, @ptr, format, output.ptr, flags)
end
secret_key_count() click to toggle source
# File lib/rnp/rnp.rb, line 231
def secret_key_count
  pcount = FFI::MemoryPointer.new(:size_t)
  Rnp.call_ffi(:rnp_get_secret_key_count, @ptr, pcount)
  pcount.read(:size_t)
end
sign(input:, output: nil, signers:, armored: nil, compression: nil, creation_time: nil, expiration_time: nil, hash: nil) click to toggle source

Create a signature.

@param input [Input] the input to read the data to be signed @param output [Output] the output to write the signatures.

If nil, the result will be returned directly as a String.

@param signers [Key, Array<Key>] the keys to sign with @param armored (see Sign#armored=) @param compression (see Sign#compression=) @param creation_time (see Sign#creation_time=) @param expiration_time (see Sign#expiration_time=) @param hash (see Sign#hash=) @return [nil, String]

# File lib/rnp/rnp.rb, line 249
def sign(input:, output: nil, signers:,
         armored: nil,
         compression: nil,
         creation_time: nil,
         expiration_time: nil,
         hash: nil)
  Output.default(output) do |output_|
    sign = start_sign(input: input, output: output_)
    sign.options = {
      armored: armored,
      compression: compression,
      creation_time: creation_time,
      expiration_time: expiration_time,
      hash: hash
    }
    simple_sign(sign, signers)
  end
end
start_cleartext_sign(input:, output:) click to toggle source

Create a cleartext {Sign} operation.

@param input (see start_sign) @param output (see start_sign)

# File lib/rnp/rnp.rb, line 462
def start_cleartext_sign(input:, output:)
  _start_sign(:rnp_op_sign_cleartext_create, input, output)
end
start_detached_sign(input:, output:) click to toggle source

Create a detached {Sign} operation.

@param input (see start_sign) @param output (see start_sign)

# File lib/rnp/rnp.rb, line 470
def start_detached_sign(input:, output:)
  _start_sign(:rnp_op_sign_detached_create, input, output)
end
start_detached_verify(data:, signature:) click to toggle source

Create a detached {Verify} operation.

@param data [Input] the input to read the signed data @param signature [Input] the input to read the signatures

# File lib/rnp/rnp.rb, line 487
def start_detached_verify(data:, signature:)
  _start_verify(:rnp_op_verify_detached_create, data, signature)
end
start_encrypt(input:, output:) click to toggle source

Create an {Encrypt} operation.

@param input [Input] the input to read the plaintext @param output [Output] the output to write the encrypted data

# File lib/rnp/rnp.rb, line 495
def start_encrypt(input:, output:)
  pptr = FFI::MemoryPointer.new(:pointer)
  Rnp.call_ffi(:rnp_op_encrypt_create, pptr, @ptr, input.ptr, output.ptr)
  pencrypt = pptr.read_pointer
  Encrypt.new(pencrypt) unless pencrypt.null?
end
start_sign(input:, output:) click to toggle source

Create a {Sign} operation.

@param input [Input] the input to read the data to be signed @param output [Output] the output to write the signatures

# File lib/rnp/rnp.rb, line 454
def start_sign(input:, output:)
  _start_sign(:rnp_op_sign_create, input, output)
end
start_verify(input:, output: nil) click to toggle source

Create a {Verify} operation.

@param input [Input] the input to read the signatures @param output [Output] the output (if any) to write the verified data

# File lib/rnp/rnp.rb, line 478
def start_verify(input:, output: nil)
  output = Output.to_null unless output
  _start_verify(:rnp_op_verify_create, input, output)
end
symmetric_encrypt(input:, output: nil, passwords:, armored: nil, compression: nil, cipher: nil, s2k_hash: nil, s2k_iterations: 0, s2k_cipher: nil) click to toggle source

Encrypt with a password only.

@param input (see encrypt) @param output (see encrypt) @param passwords [String, Array<String>] list of passwords to encrypt with.

Any (single) one of the passwords can be used to decrypt.

@param armored (see Encrypt#armored=) @param compression (see Encrypt#compression=) @param cipher (see Encrypt#cipher=) @param s2k_hash (see Encrypt#add_password) @param s2k_iterations (see Encrypt#add_password) @param s2k_cipher (see Encrypt#add_password) @return [void]

# File lib/rnp/rnp.rb, line 413
def symmetric_encrypt(input:, output: nil, passwords:,
                      armored: nil,
                      compression: nil,
                      cipher: nil,
                      s2k_hash: nil,
                      s2k_iterations: 0,
                      s2k_cipher: nil)
  Output.default(output) do |output_|
    enc = start_encrypt(input: input, output: output_)
    enc.options = {
      armored: armored,
      compression: compression,
      cipher: cipher
    }
    passwords = [passwords] if passwords.is_a?(String)
    passwords.each do |password|
      enc.add_password(password,
                       s2k_hash: s2k_hash,
                       s2k_iterations: s2k_iterations,
                       s2k_cipher: s2k_cipher)
    end
    enc.execute
  end
end
verify(input:, output: nil) click to toggle source

Verify a signature.

@param input [Input] the input to read the signatures @param output [Output] the output (if any) to write the verified data

# File lib/rnp/rnp.rb, line 329
def verify(input:, output: nil)
  verify = start_verify(input: input, output: output)
  verify.execute
end

Private Instance Methods

_start_sign(func, input, output) click to toggle source
# File lib/rnp/rnp.rb, line 534
def _start_sign(func, input, output)
  pptr = FFI::MemoryPointer.new(:pointer)
  Rnp.call_ffi(func, pptr, @ptr, input.ptr, output.ptr)
  psign = pptr.read_pointer
  Rnp::Sign.new(psign) unless psign.null?
end
_start_verify(func, io1, io2) click to toggle source
# File lib/rnp/rnp.rb, line 541
def _start_verify(func, io1, io2)
  pptr = FFI::MemoryPointer.new(:pointer)
  Rnp.call_ffi(func, pptr, @ptr, io1.ptr, io2.ptr)
  pverify = pptr.read_pointer
  Verify.new(pverify) unless pverify.null?
end
each_identifier(type, &block) click to toggle source
# File lib/rnp/rnp.rb, line 560
def each_identifier(type, &block)
  block or return enum_for(:identifier_iterator, type)
  identifier_iterator(type, &block)
  self
end
identifier_iterator(identifier_type) { |read_string| ... } click to toggle source
# File lib/rnp/rnp.rb, line 566
def identifier_iterator(identifier_type)
  pptr = FFI::MemoryPointer.new(:pointer)
  piterator = nil
  Rnp.call_ffi(:rnp_identifier_iterator_create, @ptr, pptr, identifier_type)
  piterator = pptr.read_pointer
  loop do
    Rnp.call_ffi(:rnp_identifier_iterator_next, piterator, pptr)
    pidentifier = pptr.read_pointer
    break if pidentifier.null?
    yield pidentifier.read_string
  end
ensure
  LibRnp.rnp_identifier_iterator_destroy(piterator) if piterator
end
load_save_flags(public_keys:, secret_keys:) click to toggle source
# File lib/rnp/rnp.rb, line 581
def load_save_flags(public_keys:, secret_keys:)
  flags = 0
  flags |= LibRnp::RNP_LOAD_SAVE_PUBLIC_KEYS if public_keys
  flags |= LibRnp::RNP_LOAD_SAVE_SECRET_KEYS if secret_keys
  flags
end
simple_encrypt(enc, recipients: nil, signers: nil) click to toggle source
# File lib/rnp/rnp.rb, line 548
def simple_encrypt(enc, recipients: nil, signers: nil)
  recipients = [recipients] if recipients.is_a?(Key)
  recipients&.each do |recipient|
    enc.add_recipient(recipient)
  end
  signers = [signers] if signers.is_a?(Key)
  signers&.each do |signer|
    enc.add_signer(signer)
  end
  enc.execute
end
simple_sign(sign, signers) click to toggle source
# File lib/rnp/rnp.rb, line 526
def simple_sign(sign, signers)
  signers = [signers] if signers.is_a?(Key)
  signers.each do |signer|
    sign.add_signer(signer)
  end
  sign.execute
end