Class: GPGMeh

Inherits:
Object
  • Object
show all
Defined in:
lib/gpgmeh.rb,
lib/gpgmeh.rb,
lib/gpgmeh/key.rb,
lib/gpgmeh/version.rb

Defined Under Namespace

Classes: Error, Key, NoPassphraseError, TimeoutError

Constant Summary collapse

VERSION =
"0.1.6"

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cmd: self.class.default_cmd, args: self.class.default_args, homedir: self.class.default_homedir, timeout_sec: self.class.timeout_sec) ⇒ GPGMeh

Returns a new instance of GPGMeh.



138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/gpgmeh.rb', line 138

def initialize(
  cmd: self.class.default_cmd,
  args: self.class.default_args,
  homedir: self.class.default_homedir,
  timeout_sec: self.class.timeout_sec
)
  @gpg_cmd = cmd
  @gpg_args = args
  @gpg_args += ["--homedir", homedir] if homedir
  @deadline = Time.now + timeout_sec
  @stdout_buffer = +""
  @stderr_buffer = +""
  @status_r_buffer = +""
end

Class Attribute Details

.default_argsObject

Returns the value of attribute default_args.



125
126
127
# File 'lib/gpgmeh.rb', line 125

def default_args
  @default_args
end

.default_cmdObject

Returns the value of attribute default_cmd.



125
126
127
# File 'lib/gpgmeh.rb', line 125

def default_cmd
  @default_cmd
end

.default_homedirObject

Returns the value of attribute default_homedir.



125
126
127
# File 'lib/gpgmeh.rb', line 125

def default_homedir
  @default_homedir
end

.loggerObject



132
133
134
135
136
# File 'lib/gpgmeh.rb', line 132

def self.logger
  return @logger if defined?(@logger)
  require "logger"
  @logger = Logger.new(STDERR)
end

.timeout_secObject

Returns the value of attribute timeout_sec.



125
126
127
# File 'lib/gpgmeh.rb', line 125

def timeout_sec
  @timeout_sec
end

Class Method Details

.decrypt(encrypted_blob, gpg_options: {}, passphrase_callback: nil, &block) ⇒ String

Decrypt public key encrypted message using secret keyring

Parameters:

  • encrypted_blob (String)

    encrypted blob to decrypt

  • gpg_options (@see #GPGMeh.encrypt) (defaults to: {})
  • passphrase_callback (@see #GPGMeh.encrypt) (defaults to: nil)

Returns:

  • (String)

    encrypted message



64
65
66
67
68
69
70
# File 'lib/gpgmeh.rb', line 64

def self.decrypt(encrypted_blob, gpg_options: {}, passphrase_callback: nil, &block)
  raise ArgumentError, "passphrase callback required" if (passphrase_callback || block).nil?
  t = Time.now
  new(gpg_options).decrypt(encrypted_blob, passphrase_callback || block)
ensure
  logger.debug(format("GPGMeh: decryption time=%.3fs", Time.now - t)) if t
end

.encrypt(plaintext, recipients, gpg_options: {}, sign: true, passphrase_callback: nil, &block) ⇒ String

Encrypt message using public key encryption for the ‘recipients`

Example:

GPGMeh.encrypt("boom", "ABC123DE") do |secret_key_id|
  if secret_key_id == "123ABC45"
    "secret_keyring1_passphrase"
  else
    "secret_keyring2_passphrase"
  end
end

Parameters:

  • plaintext (String)

    bytes to be encrypted with the recipient(s)‘ public key; each recipient’s secret key must be used to decrypt the message

  • recipients (String)

    or [Array<String>] list of public key id’s

  • gpg_options (Hash<Symbol, String>) (defaults to: {})

    gpg options, valid keys: cmd, args, homedir, timeout_sec cmd: gpg command to execute, default=gpg args: command line arguments for gpg, default=%w(–armor –trust-model always)

    (note: --no-tty and --quiet are always added)
    

    homedir: custom homedir for gpg (passes –homedir argument to gpg) timeout_sec: timeout for gpg command, default=0.2

  • sign (bool) (defaults to: true)

    should the encrypted message be signed? Requires ‘passphrase_callback`. [default=true]

  • passphrase_callback (callable) (defaults to: nil)

    or [block] callable that returns the secret keyring passphrase, only required when signing; the callable takes an 8 character string argument (short format key id)

Returns:

  • (String)

    encrypted message



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/gpgmeh.rb', line 45

def self.encrypt(plaintext, recipients, gpg_options: {}, sign: true, passphrase_callback: nil, &block)
  raise ArgumentError, "passphrase callback required to sign" if sign && (passphrase_callback || block).nil?
  raise ArgumentError, "recipient(s) required" if recipients.empty?
  unless recipients.all? { |key_id| /^[A-Za-z0-9]+$/ =~ key_id }
    raise ArgumentError, "recipient key ids must all be alphanumeric strings"
  end
  t = Time.now
  new(gpg_options).encrypt(plaintext, recipients, sign: sign, passphrase_callback: passphrase_callback || block)
ensure
  logger.debug(format("GPGMeh: encryption time=%.3fs", Time.now - t)) if t
end

.encrypt_symmetric(plaintext, gpg_options: {}, sign: true, passphrase_callback: nil, &block) ⇒ String

Encrypt message using a symmetric passphrase

Example:

GPGMeh.encrypt_symmetric("boom") do |secret_key_id|
  if secret_key_id == :symmetric
    "my-symmetric-secret"
  elsif secret_key_id == "123ABC45"
    "secret_keyring1_passphrase"
  else
    "secret_keyring2_passphrase"
  end
end

Parameters:

  • plaintext (@see #GPGMeh.encrypt)
  • gpg_options (@see #GPGMeh.encrypt) (defaults to: {})
  • sign (@see #GPGMeh.encrypt) (defaults to: true)
  • passphrase_callback (callable) (defaults to: nil)

    or [block] callable that returns passphrases: ‘callable.call(:symmetric)` # => the symmetric passphrase (required) `callable.call(<short format secret key id>)` # => the secret keyring passphrase

    (optional, only used when signing)
    

Returns:

  • (String)

    encrypted message



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/gpgmeh.rb', line 95

def self.encrypt_symmetric(
  plaintext,
  gpg_options: {},
  sign: true,
  passphrase_callback: nil,
  &block
)
  t = Time.now
  new(gpg_options).encrypt_symmetric(
    plaintext,
    sign: sign,
    passphrase_callback: passphrase_callback || block
  )
ensure
  logger.debug(format("GPGMeh: symmetric encryption time=%.3fs", Time.now - t)) if t
end

.public_keys(gpg_options: {}) ⇒ Object



112
113
114
# File 'lib/gpgmeh.rb', line 112

def self.public_keys(gpg_options: {})
  new(gpg_options).public_keys
end

.secret_keys(gpg_options: {}) ⇒ Object



116
117
118
# File 'lib/gpgmeh.rb', line 116

def self.secret_keys(gpg_options: {})
  new(gpg_options).secret_keys
end

.version(gpg_options: {}) ⇒ Object



120
121
122
# File 'lib/gpgmeh.rb', line 120

def self.version(gpg_options: {})
  new(gpg_options).version
end

Instance Method Details

#decrypt(encrypted_blob, passphrase_callback) ⇒ Object



340
341
342
# File 'lib/gpgmeh.rb', line 340

def decrypt(encrypted_blob, passphrase_callback)
  start(["--decrypt"], encrypted_blob, passphrase_callback)
end

#encrypt(plaintext, recipients, sign:, passphrase_callback:) ⇒ Object



333
334
335
336
337
# File 'lib/gpgmeh.rb', line 333

def encrypt(plaintext, recipients, sign:, passphrase_callback:)
  extra_args = %w[--encrypt] + recipients.flat_map { |recipient| ["--recipient", recipient] }
  extra_args << "--sign" if sign
  start(extra_args, plaintext, passphrase_callback)
end

#encrypt_symmetric(plaintext, sign:, passphrase_callback:) ⇒ Object



345
346
347
348
349
350
# File 'lib/gpgmeh.rb', line 345

def encrypt_symmetric(plaintext, sign:, passphrase_callback:)
  extra_args = ["--symmetric"]
  extra_args << "--sign" if sign

  start(extra_args, plaintext, passphrase_callback)
end

#public_keysObject



353
354
355
# File 'lib/gpgmeh.rb', line 353

def public_keys
  Key.parse(start(%w[--with-colons --list-public-keys]))
end

#secret_keysObject



358
359
360
# File 'lib/gpgmeh.rb', line 358

def secret_keys
  Key.parse(start(%w[--with-colons --list-secret-keys]))
end

#versionObject



363
364
365
# File 'lib/gpgmeh.rb', line 363

def version
  start(%w[--version])
end