lib/rnp/rnp.rb in rnp-1.0.4 vs lib/rnp/rnp.rb in rnp-1.0.5

- old
+ new

@@ -1,8 +1,8 @@ # frozen_string_literal: true -# (c) 2018 Ribose Inc. +# (c) 2018,2019 Ribose Inc. require 'English' require 'json' require 'ffi' @@ -12,10 +12,11 @@ require 'rnp/utils' require 'rnp/key' require 'rnp/op/sign' require 'rnp/op/verify' require 'rnp/op/encrypt' +require "rnp/op/generate" # Class used for interacting with RNP. class Rnp # @api private attr_reader :ptr @@ -126,10 +127,93 @@ ensure LibRnp.rnp_buffer_destroy(presults) end end + # Generate an RSA key (w/optional subkey). + # + # @param userid [String] the userid for the key + # @param bits [Integer] the bit length for the primary key + # @param subbits [Integer] the bit length for the subkey + # (0 if no subkey should be generated) + # @param password [String] the password to protect the key(s) + # (nil for no protection) + def generate_rsa(userid:, bits:, subbits: 0, password:) + pptr = FFI::MemoryPointer.new(:pointer) + Rnp.call_ffi(:rnp_generate_key_rsa, @ptr, bits, subbits, userid, password, + pptr) + pkey = pptr.read_pointer + Key.new(pkey) unless pkey.null? + end + + # Generate a DSA (w/optional ElGamal subkey) key. + # + # @param userid [String] the userid for the key + # @param bits [Integer] the bit length for the primary key + # @param subbits [Integer] the bit length for the subkey + # (0 if no subkey should be generated) + # @param password [String] the password to protect the key(s) + # (nil for no protection) + def generate_dsa_elgamal(userid:, bits:, subbits: 0, password:) + pptr = FFI::MemoryPointer.new(:pointer) + Rnp.call_ffi(:rnp_generate_key_dsa_eg, @ptr, bits, subbits, userid, + password, pptr) + pkey = pptr.read_pointer + Key.new(pkey) unless pkey.null? + end + + # Generate an ECDSA+ECDH key pair. + # + # @param userid [String] the userid for the key + # @param curve [String] the name of the curve + # @param password [String] the password to protect the key(s) + # (nil for no protection) + def generate_ecdsa_ecdh(userid:, curve:, password:) + pptr = FFI::MemoryPointer.new(:pointer) + Rnp.call_ffi(:rnp_generate_key_ec, @ptr, curve, userid, password, pptr) + pkey = pptr.read_pointer + Key.new(pkey) unless pkey.null? + end + + # Generate an EdDSA+x25519 key pair. + # + # @param userid [String] the userid for the key + # @param password [String] the password to protect the key(s) + # (nil for no protection) + def generate_eddsa_25519(userid:, password:) + pptr = FFI::MemoryPointer.new(:pointer) + Rnp.call_ffi(:rnp_generate_key_25519, @ptr, userid, password, pptr) + pkey = pptr.read_pointer + Key.new(pkey) unless pkey.null? + end + + # Generate an SM2 key pair. + # + # @param userid [String] the userid for the key + # @param password [String] the password to protect the key(s) + # (nil for no protection) + def generate_sm2(userid:, password:) + pptr = FFI::MemoryPointer.new(:pointer) + Rnp.call_ffi(:rnp_generate_key_sm2, @ptr, userid, password, pptr) + pkey = pptr.read_pointer + Key.new(pkey) unless pkey.null? + end + + # Generate a key and optional subkey. + # + # @param userid [String] the userid for the key + # @param password [String] the password to protect the key(s) + # (nil for no protection) + def generate(type:, userid:, bits:, curve: nil, password:, + subtype: nil, subbits: 0, subcurve: nil) + pptr = FFI::MemoryPointer.new(:pointer) + Rnp.call_ffi(:rnp_generate_key_ex, @ptr, type, subtype, bits, subbits, + curve, subcurve, userid, password, pptr) + pkey = pptr.read_pointer + Key.new(pkey) unless pkey.null? + end + # 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 @@ -139,10 +223,17 @@ 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 + def unload_keys(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 = unload_keys_flags(public_keys: public_keys, secret_keys: secret_keys) + Rnp.call_ffi(:rnp_unload_keys, @ptr, flags) + end + # 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 @@ -347,20 +438,23 @@ # 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=) + # @param aead (see Encrypt#aead=) def encrypt(input:, output: nil, recipients:, armored: nil, compression: nil, - cipher: nil) + cipher: nil, + aead: nil) Output.default(output) do |output_| enc = start_encrypt(input: input, output: output_) enc.options = { armored: armored, compression: compression, - cipher: cipher + cipher: cipher, + aead: aead, } simple_encrypt(enc, recipients: recipients) end end @@ -371,26 +465,29 @@ # @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 aead (see Encrypt#aead=) # @param hash (see Encrypt#hash=) # @param creation_time (see Encrypt#creation_time=) # @param expiration_time (see Encrypt#expiration_time=) def encrypt_and_sign(input:, output: nil, recipients:, signers:, armored: nil, compression: nil, cipher: nil, + aead: 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, + aead: aead, hash: hash, creation_time: creation_time, expiration_time: expiration_time } simple_encrypt(enc, recipients: recipients, signers: signers) @@ -404,27 +501,30 @@ # @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 aead (see Encrypt#aead=) # @param s2k_hash (see Encrypt#add_password) # @param s2k_iterations (see Encrypt#add_password) # @param s2k_cipher (see Encrypt#add_password) # @return [void] def symmetric_encrypt(input:, output: nil, passwords:, armored: nil, compression: nil, cipher: nil, + aead: 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 + cipher: cipher, + aead: aead, } passwords = [passwords] if passwords.is_a?(String) passwords.each do |password| enc.add_password(password, s2k_hash: s2k_hash, @@ -445,10 +545,34 @@ Output.default(output) do |output_| Rnp.call_ffi(:rnp_decrypt, @ptr, input.ptr, output_.ptr) end end + # Start a {Generate} operation. + # + # @param type [String, Symbol] the key type to generate (RSA, DSA, etc) + # @return [Generate] + def start_generate(type:) + pptr = FFI::MemoryPointer.new(:pointer) + Rnp.call_ffi(:rnp_op_generate_create, pptr, @ptr, type.to_s) + pgen = pptr.read_pointer + Generate.new(pgen) unless pgen.null? + end + + # Start a {Generate} operation. + # + # @param primary [Key] the primary key for which to generate a subkey + # @param type [String, Symbol] the key type to generate (RSA, DSA, etc) + # @return [Generate] + def start_generate_subkey(primary:, type:) + pptr = FFI::MemoryPointer.new(:pointer) + Rnp.call_ffi(:rnp_op_generate_subkey_create, pptr, @ptr, primary.ptr, + type.to_s) + pgen = pptr.read_pointer + Generate.new(pgen) unless pgen.null? + end + # 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 def start_sign(input:, output:) @@ -497,10 +621,45 @@ Rnp.call_ffi(:rnp_op_encrypt_create, pptr, @ptr, input.ptr, output.ptr) pencrypt = pptr.read_pointer Encrypt.new(pencrypt) unless pencrypt.null? end + # Import keys + # + # @param input [Input] the input to read the (OpenPGP-format) keys from + # @param public_keys [Boolean] whether to load public keys + # @param secret_keys [Boolean] whether to load secret keys + # @return [Hash] information on the imported keys + def import_keys(input:, public_keys: true, secret_keys: true) + flags = 0 + flags |= LibRnp::RNP_LOAD_SAVE_PUBLIC_KEYS if public_keys + flags |= LibRnp::RNP_LOAD_SAVE_SECRET_KEYS if secret_keys + pptr = FFI::MemoryPointer.new(:pointer) + Rnp.call_ffi(:rnp_import_keys, @ptr, input.ptr, flags, pptr) + begin + presults = pptr.read_pointer + JSON.parse(presults.read_string) unless pptr.null? + ensure + LibRnp.rnp_buffer_destroy(presults) + end + end + + # Import signatures + # + # @param input [Input] the input to read the (OpenPGP-format) keys from + # @return [Hash] information on the imported keys + def import_signatures(input:) + pptr = FFI::MemoryPointer.new(:pointer) + Rnp.call_ffi(:rnp_import_signatures, @ptr, input.ptr, 0, pptr) + begin + presults = pptr.read_pointer + JSON.parse(presults.read_string) unless pptr.null? + ensure + LibRnp.rnp_buffer_destroy(presults) + end + end + private KEY_PROVIDER = lambda do |provider, _rnp, _ctx, identifier_type, identifier, secret| provider.call(identifier_type, identifier, secret) end @@ -580,9 +739,16 @@ 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 + + def unload_keys_flags(public_keys:, secret_keys:) + flags = 0 + flags |= LibRnp::RNP_KEY_UNLOAD_PUBLIC if public_keys + flags |= LibRnp::RNP_KEY_UNLOAD_SECRET if secret_keys flags end end # class