parse.rb 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. module SecureMailing::PGP::Tool::Parse
  3. extend ActiveSupport::Concern
  4. include SecureMailing::PGP::Tool::Exec
  5. PGP_KEY_INFO = Struct.new(:fingerprint, :uids, :created_at, :expires_at, :secret)
  6. PGP_KEY_INFO_EXPIRES_AT_TIMESTAMP = 6
  7. PGP_KEY_INFO_CREATED_AT_TIMESTAMP = 5
  8. PGP_KEY_INFO_UID = 9
  9. PGP_KEY_INFO_UID_VALIDITY = 1
  10. PGP_KEY_INFO_UID_INVALID_STATE = %w[i d r n].freeze
  11. included do # rubocop:disable Metrics/BlockLength
  12. def info(key)
  13. result = gpg('show-key', options: %w[--with-colons], stdin: key)
  14. parse_info(result.stdout)
  15. end
  16. private
  17. def parse_info(data)
  18. # https://github.com/gpg/gnupg/blob/master/doc/DETAILS
  19. info = {
  20. fingerprint: nil,
  21. uids: [],
  22. created_at: nil,
  23. expires_at: nil,
  24. secret: false
  25. }
  26. data.split("\n").tap do |chunks|
  27. # We assume all relevant subkeys [SCE] have the same expiration date.
  28. dates = chunks.find { |chunk| chunk.start_with?(%r{pub|sec}) }
  29. info[:expires_at] = expires_at(dates)
  30. info[:created_at] = created_at(dates)
  31. info[:secret] = secret?(chunks)
  32. fpr = chunks.find { |chunk| chunk.start_with?('fpr') }
  33. info[:fingerprint] = fingerprint(fpr)
  34. uids = chunks.select { |chunk| chunk.start_with?('uid') }
  35. uids = uids.map { |uid| uid(uid) }
  36. info[:uids] = uids.compact
  37. end
  38. PGP_KEY_INFO.new(*info.values)
  39. end
  40. def created_at(chunk)
  41. timestamp = chunk.split(':').fetch(PGP_KEY_INFO_CREATED_AT_TIMESTAMP)
  42. return nil if timestamp == '0'
  43. Time.zone.at(timestamp.to_i)
  44. end
  45. def expires_at(chunk)
  46. timestamp = chunk.split(':').fetch(PGP_KEY_INFO_EXPIRES_AT_TIMESTAMP)
  47. return nil if timestamp.blank? || timestamp == '0'
  48. Time.zone.at(timestamp.to_i)
  49. end
  50. def fingerprint(chunk)
  51. chunk.split(':').last
  52. end
  53. def uid(chunk)
  54. hunks = chunk.split(':')
  55. return nil if PGP_KEY_INFO_UID_INVALID_STATE.include?(hunks.fetch(PGP_KEY_INFO_UID_VALIDITY))
  56. hunks.fetch(PGP_KEY_INFO_UID)
  57. end
  58. def secret?(chunks)
  59. chunks.any? { |chunk| chunk.start_with?('sec') }
  60. end
  61. end
  62. end