diff --git a/app/models/rails/keyserver/key/pgp.rb b/app/models/rails/keyserver/key/pgp.rb
index 66e8ccb..3d88e24 100644
--- a/app/models/rails/keyserver/key/pgp.rb
+++ b/app/models/rails/keyserver/key/pgp.rb
@@ -25,25 +25,14 @@ def url
)
end
+ def to_gpgkey
+ # self.class.gpgkey_from_key_string(self.public)
+ @to_gpgkey ||= self.class.gpgkey_from_key_string(private || public).first
+ end
+
def derive_metadata_if_empty
- # jsons usually has 2 items. Which one to use?
- # Match keyid? fingerprint? grip? TODO: match by grip
- # TODO: or, use the one with nil primary key grip?
- # or. just pick one, doesn't matter???
if metadata.empty?
- jsons = derive_rnp_jsons
- primary_jsons = jsons.select { |j| j["primary key grip"].nil? }
- primary_secret_json = primary_jsons.detect { |j| j["secret key"]["present"] }
- primary_public_json = primary_jsons.detect { |j| j["public key"]["present"] }
-
- if primary_secret_json
- json = primary_secret_json
- if primary_public_json
- json["public key"] = primary_public_json["public key"]
- end
- else
- json = primary_public_json
- end
+ json = to_gpgkey.as_json
update_column(:metadata, json)
end
@@ -55,18 +44,6 @@ def save_expiration_date
update_column(:expiration_date, expiry_date)
end
- def save_primary_key_grip
- super
- derive_metadata_if_empty
- update_column(:primary_key_grip, metadata["primary key grip"])
- end
-
- def save_grip
- super
- derive_metadata_if_empty
- update_column(:grip, metadata["grip"])
- end
-
def save_fingerprint
super
derive_metadata_if_empty
@@ -83,48 +60,53 @@ def save_fingerprint
# end
def key_id
- metadata["keyid"]
+ metadata["subkeys"][0]["keyid"]
end
def key_type
- metadata["type"]
+ GPGME.gpgme_pubkey_algo_name metadata["subkeys"][0]["pubkey_algo"]
end
def generation_date
- Time.at(metadata["creation time"])
+ Time.at(metadata["subkeys"][0]["timestamp"])
end
def expiry_date
- Time.at(metadata["creation time"] + metadata["expiration"]) if expires?
+ # TODO: verify
+ if expires?
+ read_attribute(:expiration_date) || Time.at(to_gpgkey.expires)
+ end
end
def expires?
- metadata["expiration"] != 0
+ # puts "will expire...?"
+ # pp to_gpgkey
+ to_gpgkey.expires?
end
def expired?
- expires? && expiry_date < Time.now
+ # metadata["expired"] != 0
+ # expires? && expiry_date < Time.now
+ to_gpgkey.expired
end
def key_size
- metadata["length"]
+ metadata["subkeys"][0]["length"].to_i
end
def fingerprint
- read_attribute(:fingerprint) || metadata["fingerprint"]
+ # read_attribute(:fingerprint) || metadata["subkeys"][0]["fpr"]
+ read_attribute(:fingerprint) || to_gpgkey.fingerprint
end
- # TODO? are we aggregating all user ids of the same key group?
def userids
- metadata["userids"]
+ metadata["uids"].map { |uid| uid["uid"] }
end
- # TODO? same question as above
def userid
userids&.first
end
- # TODO? same question as above
def email
userid.match(/<(.*@.*)>/)[1] if userid
end
@@ -137,11 +119,12 @@ def first?
# Else, return nil? empty collection?
def subkeys
return self.class.none unless primary?
- self.class.where(primary_key_grip: grip)
+ # TODO: GPGME: meaningful to ask for subkeys?
+ # self.class.where(primary_key_grip: grip)
end
def primary?
- primary_key_grip.blank?
+ metadata["subkeys"][0]["fpr"] == fingerprint
end
def has_public?
@@ -160,47 +143,6 @@ def active?
end
alias active active?
- # TODO: make it private.?
- # Internal(?) method to serialize DB key record back to an Rnp key object.
- def derive_rnp_keys
- # First, collect all primary key / subkeys
- # XXX: For Factory-created keys where metadata is incomplete: Don't rely
- # on metadata alone.
- # Export... using....
- # Query all keys for matching grips?
- # 1) export self public & private to Rnp
- [public, private].compact.map do |data|
- rnp = self.class.load_key_string(data)
- self.class.all_keys(rnp)
- end.flatten
- end
-
- def derive_rnp_jsons
- # 2) Get back metadata for subkey / primary key
- # derive_rnp_keys.map(&:json).uniq { |j| j.values_at('keyid') }
- # XXX: half is public, half is secret
- derive_rnp_keys.map(&:json)
- end
-
- def all_related_grips
- # 3) query DB for such extra grips
- derive_rnp_json.map do |json|
- ["primary key grip", "subkey grips"].map do |attr|
- json[attr]
- end.compact
- end.flatten.uniq
- end
-
- # TODO: needed?
- def derive_related_records
- self.class.where(grip: all_related_grips)
- end
-
- # TODO: needed?
- def derive_related_jsons
- derive_related_records.map(&:metadata)
- end
-
# TODO: Move these to config/initializers
UID_KEY_EMAIL_FIRST = "notifications-noreply@example.com"
UID_KEY_NAME_FIRST = "Rails Notifications"
@@ -210,112 +152,230 @@ def derive_related_jsons
UID_KEY_NAME_SECOND = "Rails Security"
UID_KEY_COMMENT_SECOND = "for security advisories"
+ # -
+ class Fakelog
+ def puts(_stuff); end
+
+ def write(_stuff); end
+
+ def flush; end
+ end
+
class << self
- def build_rnp
- Rnp.new
- end
- attr_reader :rnp
+ def debug_log
+ @debug_log ||= Fakelog.new
+ # $stderr
+ end
- # TODO: spec it
- def build_rnp_and_load_keys(homedir = Rnp.default_homedir)
- homedir_info = ::Rnp.homedir_info(homedir)
- public_info, secret_info = homedir_info.values_at(:public, :secret)
+ # def get_generated_key(email: UID_KEY_EMAIL_FIRST)
+ # {
+ # public: public_key_from_keyring(email),
+ # secret: secret_key_from_keyring(email),
+ # }
+ # end
- rnp = Rnp.new(public_info[:format], secret_info[:format])
+ # URL:
+ # https://github.com/ueno/ruby-gpgme/blob/master/examples/genkey.rb
+ def progfunc(_hook, what, _type, current, total)
+ debug_log.write("#{what}: #{current}/#{total}\r")
+ debug_log.flush
+ end
- [public_info, secret_info].each do |keyring_info|
- input = ::Rnp::Input.from_path(keyring_info[:path])
- rnp.load_keys(format: keyring_info[:format], input: input)
- end
+ # Return first public key with matching +email+
+ #
+ # NOTE: "first" assume there are no other keys with the same
+ # email
+ def public_key_from_keyring(email)
+ # puts "pubemail is #{email}"
+ # Note: "first" assume there are no other keys with the same email
+ public_key = GPGME::Key.find(:public, email).first
+ public_key.export(armor: true).to_s
+ end
- rnp
+ # Return first private key with matching +email+
+ #
+ # NOTE: "first" assume there are no other keys with the same
+ # email
+ def secret_key_from_keyring(email)
+ # puts "secemail is #{email}"
+ secret_key = GPGME::Key.find(:secret, email).first
+ return nil unless secret_key
+
+ # GPGME does not allow exporting of private keys
+ # Unsafe:
+ # `gpg --export-secret-keys -a #{secret_key.fingerprint}`
+ # Doesn't return STDOUT:
+ # system('gpg', *(%w[--export-secret-keys -a] << secret_key.fingerprint))
+ f = IO.popen(%w[gpg --export-secret-keys -a] << secret_key.fingerprint)
+ f.readlines.join
+ ensure
+ f&.close
end
- # Load into default RNP instance as well as to a new RNP
- # instance just to differentiate between imported ones from
- # existing ones.
- # TODO: spec it
- def load_key_string(key_string)
- rnp = Rnp.new
- rnp.load_keys(
- format: "GPG",
- input: Rnp::Input.from_string(key_string),
- public_keys: true,
- secret_keys: true,
+ def add_uid_to_key(email: UID_KEY_EMAIL_FIRST)
+ ctx = GPGME::Ctx.new(
+ progress_callback: method(:progfunc),
+ passphrase_callback: method(:passfunc),
)
+ Thread.current["rk-gpg-editkey-working"] = true
+ ctx.edit_key(ctx.keys(email).first, method(:add_uid_editfunc))
+ end
+
+ # Necessary for editfunc
+ def passfunc(_hook, _uid_hint, _passphrase_info, _prev_was_bad, file_descriptor)
+ io = IO.for_fd(file_descriptor, "w")
+ # Returns empty passphrase
+ io.puts("")
+ io.flush
+ end
+
+ # SECOND UID
+ def add_uid_params
+ {
+ "keyedit.prompt" => "adduid",
+ "keygen.name" => UID_KEY_NAME_SECOND,
+ "keygen.email" => UID_KEY_EMAIL_SECOND,
+ "keygen.comment" => UID_KEY_COMMENT_SECOND,
+ }
+ end
+
+ def add_uid_editfunc(_hook, status, args, file_descriptor)
+ # return if fd == "-1"
+ case status
+ when GPGME::GPGME_STATUS_GET_BOOL
+ debug_log.puts("# GPGME_STATUS_GET_BOOL")
+ io = IO.for_fd(file_descriptor)
+ # we always answer yes here
+ io.puts("Y")
+ io.flush
+ when GPGME::GPGME_STATUS_GET_LINE,
+ GPGME::GPGME_STATUS_GET_HIDDEN
+
+ debug_log.puts("# GPGME_STATUS_GET_(LINE/HIDDEN)")
+ debug_log.flush
+
+ input = add_uid_params[args]
+
+ if args == "keyedit.prompt"
+ if Thread.current["rk-gpg-editkey-working"]
+ Thread.current["rk-gpg-editkey-working"] = nil
+ else
+ input = "quit"
+ end
+ end
- rnp
+ debug_log.puts(" $ #{args} => typing '#{input}'")
+ io = IO.for_fd(file_descriptor)
+ io.puts(input)
+ io.flush
+ when GPGME::GPGME_STATUS_GOT_IT
+ debug_log.puts("# GPGME_STATUS_GOT_IT")
+ when GPGME::GPGME_STATUS_GOOD_PASSPHRASE
+ debug_log.puts("# GPGME_STATUS_GOOD_PASSPHRASE, command complete")
+ when GPGME::GPGME_STATUS_EOF
+ debug_log.puts("# GPGME_STATUS_EOF, exiting now")
+ else
+ debug_log.puts("# error: unknown status from GPGME editkey. status(#{status}) args(#{args.inspect})")
+ end
end
# Actually save key_string into new record
def import_key_string(key_string, activation_date: Time.now)
- rnp = load_key_string(key_string)
- all_keys(rnp).map do |raw|
- metadata = raw.json
+ # puts "importing yo , #{key_string[0..10]}"
+ gpgkey_from_key_string(key_string).map do |raw|
+ metadata = raw.as_json
+ raw_expiration_date = metadata["subkeys"][0]["expires"]
+ expiration_date = raw_expiration_date == 0 ? nil : Time.at(raw_expiration_date)
+
+ # require 'pry'
+ # binding.pry
creation_hash = {
- private: raw.secret_key_present? ? raw.secret_key_data : nil,
- public: raw.public_key_present? ? raw.public_key_data : nil,
+ # TODO:
+ private: secret_key_from_keyring(raw.email),
+ public: public_key_from_keyring(raw.email),
activation_date: activation_date,
+ expiration_date: expiration_date,
metadata: metadata,
- primary_key_grip: metadata["primary key grip"],
- grip: metadata["grip"],
- fingerprint: metadata["fingerprint"],
+ fingerprint: raw.fingerprint,
}.reject { |_k, v| v.nil? }
create(creation_hash)
end
end
- def all_keys(rnp_instance)
- rnp_instance.each_keyid.map { |k| rnp_instance.find_key(keyid: k) }
+ def gpgkey_from_key_string(key_string)
+ setup_gpghome
+ s = GPGME::Key.import(key_string)
+ # pp "imports plzx"
+ # pp s.imports
+ # require 'pry'
+ # binding.pry
+ GPGME::Key.find(:secret, s.imports.first.fpr) +
+ GPGME::Key.find(:public, s.imports.first.fpr)
end
# Expiration means the *duration*, not the actual point in
# time.
- # Use +key_expiration_time(rnp_key)+ for that purpose.
- def key_validity_seconds(rnp_key)
- rnp_key.json["expiration"]
+ # Use +key_expiration_time(gpg_key)+ for that purpose.
+ def key_validity_seconds(gpg_key)
+ gpg_key.as_json["expiration"]
end
private :key_validity_seconds
- def key_creation_time(rnp_key)
- Time.at(rnp_key.json["creation time"])
+ def key_creation_time(gpg_key)
+ Time.at(gpg_key.as_json["creation time"])
end
private :key_creation_time
# +key_expiration_time+ is the actual point in time.
# NOTE: This is different from the terminology used in RFC4880.
# They use "expiration time" as the "validity period".
- def key_expiration_time(rnp_key)
- Time.at(key_creation_time(rnp_key) + key_validity_seconds(rnp_key))
+ def key_expiration_time(gpg_key)
+ Time.at(key_creation_time(gpg_key) + key_validity_seconds(gpg_key))
end
private :key_expiration_time
- def key_expired?(rnp_key)
- key_expiration_time(rnp_key) != 0 &&
- key_expiration_time(rnp_key) > Time.now
+ def key_expired?(gpg_key)
+ key_expiration_time(gpg_key) != 0 &&
+ key_expiration_time(gpg_key) > Time.now
end
private :key_expired?
+ # URL:
+ # https://github.com/jkraemer/mail-gpg/blob/8ee91e49bdcff0a59a9952d45bb4f2c23525747d/Rakefile
+ def setup_gpghome
+ gpghome = Dir.mktmpdir("rails-keyserver-gpghome")
+ ENV["GNUPGHOME"] = gpghome
+ ENV["GPG_AGENT_INFO"] = "" # disable gpg agent
+
+ Rails.logger.info "[rails-keyserver] created temporary GNUPGHOME at #{gpghome}"
+ debug_log.puts "[rails-keyserver] created temporary GNUPGHOME at #{gpghome}"
+ end
+
# URL:
# http://security.stackexchange.com/questions/31594/what-is-a-good-general-purpose-gnupg-key-setup
def generate_new_key(
email: UID_KEY_EMAIL_FIRST,
creation_date: Time.now
)
- rnp = Rnp.new
- generated = rnp.generate_key(
- default_key_params(email: email, creation_date: creation_date),
+ ctx = GPGME::Ctx.new(
+ # progress_callback: method(:progfunc)
+ #passphrase_callback: method(:passfunc)
+ )
+ ctx.genkey(
+ default_key_params(email: email, creation_date: creation_date), nil, nil
)
activation_date = creation_date
+ pubkey = public_key_from_keyring(email)
+ seckey = secret_key_from_keyring(email)
key_records = %i[primary sub].map do |key_type|
raw = generated[key_type]
metadata = raw.json
creation_hash = {
- private: raw.secret_key_present? ? raw.secret_key_data : nil,
- public: raw.public_key_present? ? raw.public_key_data : nil,
+ private: seckey,
+ public: pubkey,
activation_date: activation_date,
metadata: metadata,
primary_key_grip: metadata["primary key grip"],
@@ -383,27 +443,22 @@ def default_key_params(email: UID_KEY_EMAIL_FIRST, creation_date:)
expiry_date = creation_date + 1.year
# expiry_date = creation_date + 365 * 60 * 60 * 24
- {
- primary: {
- type: "RSA",
- length: 4096,
- userid: "#{UID_KEY_NAME_FIRST}#{email.present? ? " <#{email}>" : ''} #{UID_KEY_COMMENT_FIRST}".strip,
- usage: [:sign],
- expiration: date_format(expiry_date),
- # These are the ruby-rnp defaults:
- # preferences: { 'ciphers' => %w[AES256 AES192 AES128 TRIPLEDES],
- # 'hashes' => %w[SHA256 SHA384 SHA512 SHA224 SHA1],
- # 'compression' => %w[ZLIB BZip2 ZIP Uncompressed] },
- preferences: { "ciphers" => %w[AES256 AES192 AES128 CAST5],
- "hashes" => %w[SHA512 SHA384 SHA256 SHA224],
- "compression" => %w[ZLIB BZip2 ZIP Uncompressed] },
- },
- sub: {
- type: "RSA",
- length: 4096,
- usage: [:encrypt],
- },
- }
+ <<~EOOPTS
+
+ Key-Type: RSA
+ Key-Length: 4096
+ Key-Usage: sign
+ Subkey-Type: RSA
+ Subkey-Length: 4096
+ Subkey-Usage: encrypt
+ Name-Real: #{UID_KEY_NAME_FIRST}
+ Name-Comment: #{UID_KEY_COMMENT_FIRST}
+ Name-Email: #{email}
+ Expire-Date: #{gnupg_date_format(expiry_date)}
+ Creation-Date: #{gnupg_date_format(creation_date)}
+ Preferences: SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
+
+ EOOPTS
end
end
end
diff --git a/spec/controllers/rails/keyserver/keys_controller_spec.rb b/spec/controllers/rails/keyserver/keys_controller_spec.rb
index 5081dfc..b493019 100644
--- a/spec/controllers/rails/keyserver/keys_controller_spec.rb
+++ b/spec/controllers/rails/keyserver/keys_controller_spec.rb
@@ -128,7 +128,6 @@ module Rails::Keyserver::Api::V1
end
it "returns an ASCII-armoured public key" do
- pending "implementation in ruby-rnp"
expect(response.body).to match(/\A-----BEGIN PGP PUBLIC KEY BLOCK-----/)
expect(response.body).to match(/-----END PGP PUBLIC KEY BLOCK-----(?:\n)?\z/)
end
@@ -438,7 +437,7 @@ module Rails::Keyserver::Api::V1
end
end
- let(:date_from) { Time.now - 3.years }
+ let(:date_from) { now - 3.years }
let(:date_to) { date_from + 6.years }
before do
@@ -446,11 +445,11 @@ module Rails::Keyserver::Api::V1
key = timecopped(time) do
# FactoryBot.create :rails_keyserver_key_pgp,
# activation_date: time
- (RK::Key::PGP.import_key_string key_string_1,
- activation_date: time).first
+ RK::Key::PGP.import_key_string(key_string_1,
+ activation_date: time.round(0)).first
end
- expect(key.activation_date.to_i).to eq time.to_i
+ expect(key.activation_date.utc.round(0)).to eq time.utc.round(0)
end
action[]
end
@@ -550,7 +549,7 @@ module Rails::Keyserver::Api::V1
end
end
- let(:date_from) { Time.now - 3.years }
+ let(:date_from) { now - 3.years }
let(:date_to) { date_from + 6.years }
before do
@@ -559,10 +558,10 @@ module Rails::Keyserver::Api::V1
# FactoryBot.create :rails_keyserver_key_pgp,
# activation_date: time
RK::Key::PGP.import_key_string(key_string_1,
- activation_date: time).first
+ activation_date: time.round(0)).first
end
- expect(key.activation_date.to_i).to eq time.to_i
+ expect(key.activation_date.utc.round(0)).to eq time.utc.round(0)
end
action[]
end
diff --git a/spec/models/rails/key/pgp_spec.rb b/spec/models/rails/key/pgp_spec.rb
index cfd93bc..214f950 100644
--- a/spec/models/rails/key/pgp_spec.rb
+++ b/spec/models/rails/key/pgp_spec.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "rails_helper"
+
RSpec.describe Rails::Keyserver::Key::PGP, type: :model do
let(:pub_key_path_1) { "spec/data/gpg/spec.pub" }
let(:pub_key_string_1) { File.read pub_key_path_1 }
@@ -15,6 +17,28 @@
let(:key) { keys.first }
+ describe ".setup_gpghome" do
+ it "changes ENV['GNUPGHOME']" do
+ expect { described_class.setup_gpghome }.to change { ENV["GNUPGHOME"] }
+ end
+
+ it "changes ENV['GNUPGHOME'] to a valid file path" do
+ ENV["GNUPGHOME"] = "not valid path"
+ expect(File).to_not be_readable File.expand_path(ENV["GNUPGHOME"])
+
+ described_class.setup_gpghome
+ expect(File).to be_readable File.expand_path(ENV["GNUPGHOME"])
+ end
+
+ it "resets ENV['GPG_AGENT_INFO']" do
+ ENV["GPG_AGENT_INFO"] = "non-nil"
+ expect(ENV["GPG_AGENT_INFO"]).to_not be_blank
+
+ described_class.setup_gpghome
+ expect(ENV["GPG_AGENT_INFO"]).to be_blank
+ end
+ end
+
describe ".import_key_string" do
# let(:key) do
# FactoryBot.create :rails_keyserver_key_pgp
@@ -38,7 +62,7 @@
# XXX: What about:
# let(:key_found_from_imported_keys) { keys.first }
- context "the internal rnp instance" do
+ context "the internal GPGME instance" do
it "contains 2 keys" do
expect(keys.length).to eq 2
end
@@ -47,9 +71,9 @@
expect(keys.first.fingerprint).to eq first_imported_key.fingerprint
end
- it "contains a key with matching grip" do
- expect(keys.first.grip).to eq first_imported_key.grip
- end
+ # it "contains a key with matching grip" do
+ # expect(keys.first.grip).to eq first_imported_key.grip
+ # end
it "contains a key with matching keyid" do
expect(keys.first.key_id).to eq first_imported_key.key_id
@@ -117,6 +141,18 @@
end
end
+ describe ".passfunc" do
+ it "has arity of 5" do
+ expect(described_class.method(:passfunc).arity).to eq 5
+ end
+ end
+
+ describe ".progfunc" do
+ it "has arity of 5" do
+ expect(described_class.method(:progfunc).arity).to eq 5
+ end
+ end
+
describe ".generate_new_key" do
it "has an arity of 2" do
# .arity returns -1 for variable params
@@ -147,7 +183,7 @@
email = "test #{rand}"
expect do
described_class.generate_new_key(email: email)
- end.to change { described_class.all.map(&:grip).length }.by(2)
+ end.to change { described_class.all.map(&:fingerprint).length }.by(2)
end
it 'increases the number of keys found in "userids"' do
@@ -215,29 +251,14 @@
described_class.default_key_params(creation_date: creation_date, email: "random")
end
- it "returns a Hash" do
- expect(result).to be_instance_of Hash
- end
-
- it 'returns a Hash with "primary" and "sub"' do
- expect(result).
- to include(:primary).and include(:sub)
- end
-
- it 'returns a "primary" Hash with :type, :length, :userid, :usage, :expiration' do
- expect(result[:primary]).to(
- %i[type length userid usage expiration].
- map { |key| include key }.
- reduce { |acc, constraint| acc.and(constraint) },
- )
+ it "returns a String" do
+ expect(result).to be_instance_of String
end
- it 'returns a "sub" Hash with :type, :length, :usage' do
- expect(result[:sub]).to(
- %i[type length usage].
- map { |key| include key }.
- reduce { |acc, constraint| acc.and(constraint) },
- )
+ context "with extra params" do
+ it "returns a String" do
+ expect(described_class.default_key_params(creation_date: DateTime.now, email: "there")).to be_instance_of String
+ end
end
end
@@ -265,7 +286,7 @@
let(:email) { e }
it "(#{e}) would contain it" do
- expect(result[:primary][:userid]).to match(/#{e}/)
+ expect(result).to match(/Name-Email:\s*#{e}/)
end
end
end
@@ -278,24 +299,23 @@
].each do |d|
context "with different creation_date (#{d})" do
let(:creation_date) { d }
- let(:creation_date_int) { creation_date.to_i }
+ let(:creation_date_string) { creation_date.utc.iso8601.gsub(/-|:/, "")[0..-6] }
- it "would contain it (#{d.to_i})" do
- pending "Rnp Key creation parameters do not support creation date time?"
- expect(result[:primary][:creation_date]).to eq creation_date_int
+ it "would contain it (#{d.utc.iso8601.gsub(/-|:/, '')[0..-6]})" do
+ expect(result).to match /Creation-Date:\s*#{creation_date_string}/
end
- it "would contain a corresponding expiration time (#{(d + 1.year).to_i})" do
- expect(result[:primary][:expiration]).to eq((creation_date + 1.year).to_i)
+ it "would contain a corresponding expiration time (#{(d + 1.year).utc.iso8601.gsub(/-|:/, '')[0..-6]})" do
+ expect(result).to match(/#{(creation_date + 1.year).utc.iso8601.gsub(/-|:/, "")[0..-6]}/)
end
end
end
- %i[
- UID_KEY_NAME_FIRST
- UID_KEY_COMMENT_FIRST
- UID_KEY_EMAIL_FIRST
- ].each do |param_name|
+ {
+ "Name-Real" => "UID_KEY_NAME_FIRST",
+ "Name-Comment" => "UID_KEY_COMMENT_FIRST",
+ "Name-Email" => "UID_KEY_EMAIL_FIRST",
+ }.each do |field_name, param_name|
[
"sdflkj",
23,
@@ -308,8 +328,8 @@
stub_const("#{described_class}::#{param_name}", value)
end
- it "contains it in the :primary.:userid" do
- expect(result[:primary][:userid]).to match(/#{value}/)
+ it "contains it in the #{field_name}" do
+ expect(result).to match(/#{field_name}:\s*#{value}/)
end
end
end
@@ -359,7 +379,7 @@
end
end
- xdescribe ".public_key_from_keyring" do
+ describe ".public_key_from_keyring" do
it "has an arity of 1" do
expect(described_class.method(:public_key_from_keyring).arity).to eq 1
end
@@ -370,15 +390,15 @@
let(:email) { key.email }
- it "returns a Rnp::Key" do
- expect(result).to be_a Rnp::Key
+ it "returns a String" do
+ expect(result).to be_a String
end
context "for a non-existent email" do
let(:email) { key.email + "1" }
- it "returns nil" do
- expect(result).to be_nil
+ it "raises NoMethodError" do
+ expect { result }.to raise_error NoMethodError
end
end
end
@@ -392,11 +412,11 @@
let(:result) do
described_class.generate_new_key(email: email)
- described_class.get_generated_key(email: email)[:secret]
+ described_class.secret_key_from_keyring(email)
end
- it "returns a Rnp::Key" do
- expect(result).to be_a Rnp::Key
+ it "returns a String" do
+ expect(result).to be_a String
end
context "for a non-existent email" do
@@ -459,17 +479,13 @@
end
end
- xdescribe ".add_uid_to_key" do
+ describe ".add_uid_to_key" do
it "has an arity of 1" do
- expect(described_class.method(:add_uid_to_key).parameters.length).to eq 2
+ expect(described_class.method(:add_uid_to_key).parameters.length).to eq 1
end
- it "has a specific set of parameters" do
- expect(described_class.method(:add_uid_to_key).parameters).to(
- [%i[keyreq userid], %i[key target_email]].map do |cons|
- include cons
- end.reduce { |acc, cons| acc.and(cons) },
- )
+ it "has a specific set of optional parameters" do
+ expect(described_class.method(:add_uid_to_key).parameters).to include %i[key email]
end
context "with missing :userid" do
@@ -480,13 +496,7 @@
context "with a random email" do
it "raises TypeError" do
- pending "It currently doesn't check for email address syntax"
- expect do
- described_class.add_uid_to_key(
- userid: "Example Addition #{rand} ",
- target_email: "random",
- )
- end.to raise_error TypeError
+ expect { described_class.add_uid_to_key(email: "random") }.to raise_error TypeError
end
end
@@ -494,28 +504,35 @@
it "succeeds without errors" do
expect do
described_class.add_uid_to_key(
- userid: "Example Addition #{rand} ",
+ email: described_class::UID_KEY_EMAIL_FIRST,
)
end.to_not raise_error
end
it "appears in .userids" do
- generated_keys = described_class.generate_new_key(
- email: described_class::UID_KEY_EMAIL_FIRST,
- )
- key_grip = generated_keys[:primary].json["grip"]
+ # generated_keys = described_class.generate_new_key(
+ # email: described_class::UID_KEY_EMAIL_FIRST,
+ # )
measurement = lambda {
- Set.new(described_class.send(:rnp).find_key(
- grip: key_grip,
- ).userids)
+ Set.new(described_class.all.to_a.select do |key|
+ key.userids.include?(described_class::UID_KEY_EMAIL_FIRST)
+ end)
}
- additional_userid = "Example Addition #{rand} "
+ additional_name = "Example Addition #{rand}"
+ additional_email = "valid@example.com"
+ additional_comment = "some comment"
+ additional_userid = "#{additional_name} (#{additional_comment}) <#{additional_email}>"
+ allow(described_class).to receive(:add_uid_params).and_return(
+ "keyedit.prompt" => "adduid",
+ "keygen.name" => additional_name,
+ "keygen.email" => additional_email,
+ "keygen.comment" => additional_comment,
+ )
set1 = measurement.call
described_class.add_uid_to_key(
- target_email: described_class::UID_KEY_EMAIL_FIRST,
- userid: additional_userid,
+ email: described_class::UID_KEY_EMAIL_FIRST,
)
set2 = measurement.call
@@ -524,6 +541,107 @@
expect(diff.first).to eq additional_userid
end
end
+
+ xit "calls :add_uid_editfunc" do
+ pending "example runs forever until interrupted"
+ expect(described_class).to receive(:add_uid_editfunc).once
+ described_class.add_uid_to_key
+ end
+
+ xit "sets Thread.current['rk-gpg-editkey-working'] to 'true'" do
+ pending "Thread local variables cannot be tested"
+ expect { described_class.add_uid_to_key }.to change {
+ Thread.current["rk-gpg-editkey-working"]
+ }.to true
+ end
+
+ it "sets Thread.current['rk-gpg-editkey-working'] to 'true'" do
+ expect(Thread.current).to receive(:[]=).with("rk-gpg-editkey-working", true)
+ described_class.add_uid_to_key
+ end
+ end
+
+ describe ".add_uid_editfunc" do
+
+ it "has an arity of 4" do
+ expect(described_class.method(:add_uid_editfunc).arity).to eq 4
+ end
+
+ # when GPGME::GPGME_STATUS_GET_BOOL
+ # debug_log.puts("# GPGME_STATUS_GET_BOOL")
+ # io = IO.for_fd(fd)
+ # # we always answer yes here
+ # io.puts("Y")
+ # io.flush
+ # when GPGME::GPGME_STATUS_GET_LINE,
+ # GPGME::GPGME_STATUS_GET_HIDDEN
+ #
+ # debug_log.puts("# GPGME_STATUS_GET_(LINE/HIDDEN)")
+ # debug_log.flush
+ #
+ # input = add_uid_params[args]
+ #
+ # if args == "keyedit.prompt"
+ # if Thread.current['rk-gpg-editkey-working']
+ # Thread.current['rk-gpg-editkey-working'] = nil
+ # else
+ # input = "quit"
+ # end
+ # end
+ #
+ # debug_log.puts(" $ #{args} => typing '#{input}'")
+ # io = IO.for_fd(fd)
+ # io.puts(input)
+ # io.flush
+
+ describe "when :status" do
+ {
+ GPGME::GPGME_STATUS_GOT_IT => "# GPGME_STATUS_GOT_IT",
+ GPGME::GPGME_STATUS_GOOD_PASSPHRASE => "# GPGME_STATUS_GOOD_PASSPHRASE, command complete",
+ GPGME::GPGME_STATUS_EOF => "# GPGME_STATUS_EOF, exiting now",
+ "unknown" => "# error: unknown status from GPGME editkey. status(unknown) args(2)",
+ }.each do |key, val|
+ context "is #{key}" do
+ let(:status) { key }
+ let(:action) { described_class.add_uid_editfunc(nil, status, 2, 4) }
+ it "debug_log.puts(#{val})" do
+ expect(described_class.debug_log).to receive(:puts).with(val)
+ action
+ end
+
+ end
+ end
+ end
+
+ it "reads from the key" do
+ pending "TBI mock IO"
+ # fd = IO.sysopen("/dev/tty", "w")
+ io = IO.new 2
+ # io = IO.for_fd fd
+
+ def io.write stuff
+ @buffer ||= ""
+ @buffer << stuff
+ end
+
+ def io.gets
+ @buffer
+ end
+
+ described_class.add_uid_editfunc(nil, GPGME::GPGME_STATUS_GET_BOOL, 2, 4)
+ expect(io.gets).to eq "Y\n"
+ end
+ end
+
+ describe ".add_uid_params" do
+ it "returns a specific Hash" do
+ expect(described_class.add_uid_params).to eq ({
+ "keyedit.prompt" => "adduid",
+ "keygen.name" => RK::Key::PGP::UID_KEY_NAME_SECOND,
+ "keygen.email" => RK::Key::PGP::UID_KEY_EMAIL_SECOND,
+ "keygen.comment" => RK::Key::PGP::UID_KEY_COMMENT_SECOND,
+ })
+ end
end
describe "#expires?" do
diff --git a/spec/models/rails/key_spec.rb b/spec/models/rails/key_spec.rb
index 0fb5561..708f0ca 100644
--- a/spec/models/rails/key_spec.rb
+++ b/spec/models/rails/key_spec.rb
@@ -57,7 +57,7 @@ module Rails::Keyserver
end
end
- describe ".grip" do
+ xdescribe ".grip" do
before do
5.times do
FactoryBot.create :rails_keyserver_key_pgp
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 7489da4..9667910 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -16,14 +16,13 @@
# users commonly want.
#
-# URL:
+# URL:
# http://stackoverflow.com/questions/22860025/rails-4-mounted-engine-with-rspec-and-factory-girl-rails
-ENGINE_RAILS_ROOT=File.join(__dir__, '../')
-Dir[File.join(ENGINE_RAILS_ROOT, "spec/factories/**/*.rb")].each {|f| require f }
+ENGINE_RAILS_ROOT = File.join(__dir__, "../")
+Dir[File.join(ENGINE_RAILS_ROOT, "spec/factories/**/*.rb")].each { |f| require f }
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
-
config.include FactoryBot::Syntax::Methods
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
@@ -55,53 +54,51 @@
# triggering implicit auto-inclusion in groups with matching metadata.
config.shared_context_metadata_behavior = :apply_to_host_groups
-# The settings below are suggested to provide a good initial experience
-# with RSpec, but feel free to customize to your heart's content.
-=begin
- # This allows you to limit a spec run to individual examples or groups
- # you care about by tagging them with `:focus` metadata. When nothing
- # is tagged with `:focus`, all examples get run. RSpec also provides
- # aliases for `it`, `describe`, and `context` that include `:focus`
- # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
- config.filter_run_when_matching :focus
-
- # Allows RSpec to persist some state between runs in order to support
- # the `--only-failures` and `--next-failure` CLI options. We recommend
- # you configure your source control system to ignore this file.
- config.example_status_persistence_file_path = "spec/examples.txt"
-
- # Limits the available syntax to the non-monkey patched syntax that is
- # recommended. For more details, see:
- # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
- # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
- # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
- config.disable_monkey_patching!
-
- # Many RSpec users commonly either run the entire suite or an individual
- # file, and it's useful to allow more verbose output when running an
- # individual spec file.
- if config.files_to_run.one?
- # Use the documentation formatter for detailed output,
- # unless a formatter has already been configured
- # (e.g. via a command-line flag).
- config.default_formatter = 'doc'
- end
-
- # Print the 10 slowest examples and example groups at the
- # end of the spec run, to help surface which specs are running
- # particularly slow.
- config.profile_examples = 10
-
- # Run specs in random order to surface order dependencies. If you find an
- # order dependency and want to debug it, you can fix the order by providing
- # the seed, which is printed after each run.
- # --seed 1234
- config.order = :random
-
- # Seed global randomization in this process using the `--seed` CLI option.
- # Setting this allows you to use `--seed` to deterministically reproduce
- # test failures related to randomization by passing the same `--seed` value
- # as the one that triggered the failure.
- Kernel.srand config.seed
-=end
+ # The settings below are suggested to provide a good initial experience
+ # with RSpec, but feel free to customize to your heart's content.
+ # # This allows you to limit a spec run to individual examples or groups
+ # # you care about by tagging them with `:focus` metadata. When nothing
+ # # is tagged with `:focus`, all examples get run. RSpec also provides
+ # # aliases for `it`, `describe`, and `context` that include `:focus`
+ # # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
+ # config.filter_run_when_matching :focus
+ #
+ # # Allows RSpec to persist some state between runs in order to support
+ # # the `--only-failures` and `--next-failure` CLI options. We recommend
+ # # you configure your source control system to ignore this file.
+ # config.example_status_persistence_file_path = "spec/examples.txt"
+ #
+ # # Limits the available syntax to the non-monkey patched syntax that is
+ # # recommended. For more details, see:
+ # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
+ # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
+ # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
+ # config.disable_monkey_patching!
+ #
+ # # Many RSpec users commonly either run the entire suite or an individual
+ # # file, and it's useful to allow more verbose output when running an
+ # # individual spec file.
+ # if config.files_to_run.one?
+ # # Use the documentation formatter for detailed output,
+ # # unless a formatter has already been configured
+ # # (e.g. via a command-line flag).
+ # config.default_formatter = 'doc'
+ # end
+ #
+ # # Print the 10 slowest examples and example groups at the
+ # # end of the spec run, to help surface which specs are running
+ # # particularly slow.
+ # config.profile_examples = 10
+ #
+ # # Run specs in random order to surface order dependencies. If you find an
+ # # order dependency and want to debug it, you can fix the order by providing
+ # # the seed, which is printed after each run.
+ # # --seed 1234
+ # config.order = :random
+ #
+ # # Seed global randomization in this process using the `--seed` CLI option.
+ # # Setting this allows you to use `--seed` to deterministically reproduce
+ # # test failures related to randomization by passing the same `--seed` value
+ # # as the one that triggered the failure.
+ # Kernel.srand config.seed
end