sgpg 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # Sgpg
2
+ Short gpg, tool for manage your gpg key (backup tarball, unprivileged keys, etc)
3
+
4
+ Followed my [post](https://szorfein.github.io/gpg/build-secure-gpg-key/) to
5
+ create a secure gpg key, i need to update my key all the 6 month on each PC
6
+ and each time, it's very very annoying so i build this tool to gain in time
7
+ and sanity :).
8
+
9
+ So with this tool, 'i,you,we' should use no more than 3 commands max instead of 50...
10
+
11
+ You always need to create a gpg key as well
12
+
13
+ gpg --expert --full-generate-key
14
+
15
+ ## Install sgpg locally
16
+
17
+ gem install sgpg
18
+
19
+ You also need to install dependencies: tar, cryptsetup, shred and gpg.
20
+
21
+ ## Configure
22
+ The config is located at ~/.config/sgpg/config.yml. You can use the command line with `--save`:
23
+
24
+ sgpg --disk /dev/sdc2 --disk-encrypt --key szorfein@protonmail.com --save
25
+
26
+ You can register the disk/by-id or disk/by-uuid if you prefer.
27
+
28
+ sgpg --disk /dev/disk/by-id/wmn-0xXXXX-part2 --disk-encrypt --save
29
+
30
+ ## Usage
31
+
32
+ sgpg -h
33
+
34
+ When subkeys expire:
35
+
36
+ sgpg --last-master --edit-key # update expired keys, change password, etc...
37
+ sgpg --export
38
+ sgpg --close # unmount and close disk
39
+
40
+ Import the last unpriviliged key (laptop and other)
41
+
42
+ sgpg --last-lesser --edit-key # trust (555)
43
+ sgpg --close # unmount and close disk
44
+
45
+ ## Gem push
46
+
47
+ gem login
48
+ gem build sgpg.gemspec
49
+ gem push sgpg-0.0.1.gem
50
+
data/bin/sgpg ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'sgpg'
5
+ require 'optparse'
6
+
7
+ config_file = Sgpg::YamlConfig.new
8
+ config_file.load
9
+
10
+ options = config_file.config
11
+
12
+ OptionParser.new do |opts|
13
+ opts.banner = 'Usage: sgpg.rb [options]'
14
+
15
+ opts.on('-d', '--disk DEV', 'Specify device disk to use to save your keys') do |dev|
16
+ options[:disk] = dev
17
+ end
18
+
19
+ opts.on('--disk-encrypt', 'If we need to use cryptsetup to open the disk.') do
20
+ options[:crypted] = true
21
+ end
22
+
23
+ opts.on('-k', '--key NAME', 'Use the key name, default use ENV["USER"]') do |name|
24
+ options[:keyname] = name
25
+ end
26
+
27
+ opts.on('-e', '--edit-key', 'Edit your secret key.') do
28
+ Sgpg::Main.edit_key(options)
29
+ end
30
+
31
+ opts.on('-l', '--list-key', "Display the last secret key #{Sgpg::MOUNTPOINT}.") do
32
+ Sgpg.list_keys(options[:keyname])
33
+ end
34
+
35
+ opts.on('-o', '--open', "Open and mount device disk at #{Sgpg::MOUNTPOINT}.") do
36
+ Sgpg.open(options[:disk], options[:crypted])
37
+ end
38
+
39
+ opts.on('-p', '--path-key PATH', 'use archive PATH') do |path|
40
+ raise "path #{path} didn't found" unless File.exist? path
41
+
42
+ options[:keypath] = path
43
+ end
44
+
45
+ opts.on('--last-master', 'Use the last archive master') do
46
+ options[:keypath] = Sgpg.last_key(options, 'master')
47
+ end
48
+
49
+ opts.on('--last-lesser', 'Use the last archive lesser (unpriviliged)') do
50
+ options[:keypath] = Sgpg.last_key(options, 'lesser')
51
+ end
52
+
53
+ opts.on('-c', '--close', 'Unmount and close disk device.') do
54
+ Sgpg.close(options[:disk], options[:crypted])
55
+ end
56
+
57
+ opts.on('-s', '--save', 'Save current optargs in the config file') do
58
+ config_file.save(options)
59
+ end
60
+
61
+ opts.on('--export', 'Export your current master key and create both archives (master and lesser)') do
62
+ Sgpg::Main.export_secret(options)
63
+ Sgpg::Main.lesser_keys(options)
64
+ end
65
+ end.parse!
66
+
67
+ raise 'no disk to use' if !options[:disk] || options[:disk] == ''
68
+ raise 'no key to use' if !options[:keyname] || options[:keybase] == ''
69
+
70
+ puts config_file
71
+
72
+ puts "Sgpg v.#{Sgpg::VERSION}"
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Sgpg
6
+ # Interact with program tar from unix
7
+ class Archive
8
+ # Code here
9
+ def initialize(key, name = ENV['USER'])
10
+ @key = key || ''
11
+ @name = name
12
+ @date = Time.now.strftime('%Y-%m-%d')
13
+ puts "create key #{@name}-#{@date}-master.tar"
14
+ FileUtils.mkdir_p Sgpg::WORKDIR
15
+ @gpg = Gpg.new(@name)
16
+ # make it compatabble with Tails Linux
17
+ end
18
+
19
+ def create_master_tar
20
+ Dir.chdir(Sgpg::WORKDIR)
21
+ @gpg.export_secret_keys(Sgpg::WORKDIR)
22
+ create_tar('master')
23
+ end
24
+
25
+ def create_lesser_tar
26
+ Dir.chdir(Sgpg::WORKDIR)
27
+ @gpg.export_subkey(Sgpg::WORKDIR)
28
+ @gpg.delete_keys
29
+ @gpg.import_lesser_keys
30
+ @gpg.export_secret_keys(Sgpg::WORKDIR)
31
+ create_tar('lesser')
32
+ end
33
+
34
+ def extract
35
+ raise 'No key found, try with --path-key PATH.' unless File.exist?(@key)
36
+
37
+ Dir.chdir(Sgpg::WORKDIR)
38
+ puts "Unpacking archive #{@key}..."
39
+ system('tar', 'xvf', @key)
40
+ end
41
+
42
+ def import
43
+ @gpg.delete_keys
44
+ puts 'Importing gpg keys...'
45
+ keys = Dir.glob("#{Sgpg::WORKDIR}/*.key")
46
+ import_secret(keys)
47
+ import_public(keys)
48
+ @gpg.edit_key
49
+ end
50
+
51
+ def move(pathdir)
52
+ raise "Dir #{pathdir} no found." unless Dir.exist?(pathdir)
53
+
54
+ tar = Dir.glob("#{Sgpg::WORKDIR}/*.tar")
55
+ raise 'No archive found.' unless tar.length >= 1
56
+
57
+ mv(tar, pathdir)
58
+ end
59
+
60
+ private
61
+
62
+ def import_secret(keys)
63
+ keys.each { |k| system('gpg', '-a', '--import', k) if k.match(/secret/) }
64
+ end
65
+
66
+ def import_public(keys)
67
+ keys.each { |k| system('gpg', '-a', '--import', k) if k.match(/public/) }
68
+ end
69
+
70
+ # suffix should be 'master' or 'lesser' (keys without privilege)
71
+ def create_tar(suffix = 'master')
72
+ if suffix == 'master'
73
+ system("tar -cf #{@name}-#{@date}-#{suffix}-keys.tar *.key revocation.cert")
74
+ else
75
+ system("tar -cf #{@name}-#{@date}-#{suffix}-keys.tar *.key")
76
+ end
77
+ end
78
+
79
+ def mv(tar, pathdir)
80
+ puts "Moving archive at #{pathdir}..."
81
+ case Helper.auth?
82
+ when :root
83
+ tar.each { |f| FileUtils.mv(f, "#{pathdir}/#{f}") }
84
+ when :sudo
85
+ tar.each { |f| system('sudo', 'mv', f, "#{pathdir}/") }
86
+ when :doas
87
+ tar.each { |f| system('doas', 'mv', f, "#{pathdir}/") }
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/cryptsetup.rb
4
+
5
+ module Sgpg
6
+ # Manipule program cryptsetup
7
+ class Cryptsetup
8
+ class InvalidDisk < StandardError; end
9
+
10
+ def initialize(disk)
11
+ raise "no disk #{disk} specified..." unless disk
12
+
13
+ @disk = disk
14
+ @mapname = 'sgpg'
15
+ end
16
+
17
+ # tails linux make persistent volume on second partiton 'disk_name'2
18
+ def open
19
+ check_disk
20
+
21
+ case Helper.auth?
22
+ when :root
23
+ open_with
24
+ when :doas
25
+ open_with 'doas'
26
+ when :sudo
27
+ open_with 'sudo'
28
+ end
29
+ end
30
+
31
+ def close
32
+ case Helper.auth?
33
+ when :root
34
+ close_with
35
+ when :doas
36
+ close_with 'doas'
37
+ when :sudo
38
+ close_with 'sudo'
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ def check_disk
45
+ raise InvalidDisk, "Disk #{@disk} not exist or not plugged." unless
46
+ File.exist?(@disk)
47
+ end
48
+
49
+ private
50
+
51
+ def open_with(prefix = '')
52
+ if prefix
53
+ puts "openning disk #{@disk} with #{prefix}..."
54
+ else
55
+ puts "openning disk #{@disk}..."
56
+ end
57
+
58
+ raise "unable to open #{@disk} #{prefix}" unless
59
+ system(prefix, 'cryptsetup', 'open', '--type', 'luks', @disk, @mapname)
60
+ end
61
+
62
+ def close_with(prefix = '')
63
+ puts "closing disk #{@disk}..."
64
+
65
+ raise "closing disk #{@disk} failed" unless
66
+ system(prefix, 'cryptsetup', 'close', @mapname)
67
+ end
68
+ end
69
+ end
data/lib/sgpg/gpg.rb ADDED
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sgpg
4
+ # Interact with GnuPG from unix
5
+ class Gpg
6
+ # Code here
7
+ def initialize(keyname)
8
+ @keyname = keyname
9
+ puts "using key #{@keyname}"
10
+ end
11
+
12
+ def delete_keys
13
+ return unless system('gpg', '-k', @keyname)
14
+
15
+ puts "Found older key name #{@keyname}, need to be deleted to import new..."
16
+ system('gpg', '--delete-secret-keys', @keyname)
17
+ system('gpg', '--delete-keys', @keyname)
18
+ end
19
+
20
+ def edit_key
21
+ raise "No key #{@keyname} found." unless system('gpg', '-k', @keyname)
22
+
23
+ system('gpg', '--edit-key', @keyname)
24
+ end
25
+
26
+ def export_secret_keys(pathdir)
27
+ raise "No dir #{pathdir} exist" unless File.exist?(pathdir)
28
+
29
+ output = "#{pathdir}/#{@keyname}"
30
+ system("gpg --armor --export-secret-keys #{@keyname} > #{output}-secret.key")
31
+ system("gpg --armor --export #{@keyname} > #{output}-public.key")
32
+ end
33
+
34
+ def export_subkey(pathdir)
35
+ raise "No dir #{pathdir} exist" unless File.exist?(pathdir)
36
+
37
+ system("gpg --export-secret-subkeys #{@keyname} > #{pathdir}/subkeys")
38
+ end
39
+
40
+ def import_lesser_keys
41
+ keypath = "#{Sgpg::WORKDIR}/subkeys"
42
+ raise "No key #{keypath} exist" unless File.exist?(keypath)
43
+
44
+ system('gpg', '--import', keypath)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/helper.rb
4
+
5
+ module Sgpg
6
+ # Reusable functions for the program
7
+ module Helper
8
+ def self.auth?
9
+ return :root if Process.uid == '0'
10
+ return :doas if File.exist?('/bin/doas') || File.exist?('/sbin/doas')
11
+ return :sudo if File.exist?('/bin/sudo') || File.exist?('/sbin/sudo')
12
+ end
13
+ end
14
+ end
data/lib/sgpg/mount.rb ADDED
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/mount.rb
4
+
5
+ require 'fileutils'
6
+
7
+ module Sgpg
8
+ # (un)mount a device to a mountpoint
9
+ class Mount
10
+ # Argument 'disk' = full path of the disk.
11
+ # e.g: /dev/mapper/sgpg for cryptsetup (See Sgpg::Cryptsetup) of just /dev/sdX
12
+ def initialize(disk)
13
+ @user = ENV['USER'] || 'root'
14
+ @disk = disk
15
+ end
16
+
17
+ def open
18
+ case Helper.auth?
19
+ when :root
20
+ open_with
21
+ when :doas
22
+ open_with 'doas'
23
+ when :sudo
24
+ open_with 'sudo'
25
+ end
26
+ end
27
+
28
+ def close
29
+ case Helper.auth?
30
+ when :root
31
+ close_with
32
+ when :doas
33
+ close_with 'doas'
34
+ when :sudo
35
+ close_with 'sudo'
36
+ end
37
+ end
38
+
39
+ protected
40
+
41
+ def open_with(prefix = '')
42
+ prefix == '' ? create_with_ruby : create_with_system(prefix)
43
+
44
+ raise "Unable to mount #{@disk}" unless
45
+ system(prefix, 'mount', '-t', 'ext4', @disk, Sgpg::MOUNTPOINT)
46
+ end
47
+
48
+ def close_with(prefix = '')
49
+ raise "Unable to umount #{@disk}" unless
50
+ system(prefix, 'umount', Sgpg::MOUNTPOINT)
51
+ end
52
+
53
+ private
54
+
55
+ def create_wiht_ruby
56
+ FileUtils.mkdir_p Sgpg::MOUNTPOINT
57
+ FileUtils.chown @user, @user, Sgpg::MOUNTPOINT, verbose: true
58
+ FileUtils.chmod 0644, Sgpg::MOUNTPOINT, verbose: true
59
+ end
60
+
61
+ def create_with_system(prefix)
62
+ system(prefix, 'mkdir', '-p', Sgpg::MOUNTPOINT)
63
+ system(prefix, 'chown', "#{@user}:#{@user}", Sgpg::MOUNTPOINT)
64
+ system(prefix, 'chmod', '0644', Sgpg::MOUNTPOINT)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/option.rb
4
+
5
+ module Sgpg
6
+ MOUNTPOINT = '/mnt/sgpg' # Better permission than /media/sgpg
7
+ KEYDIR = "#{MOUNTPOINT}/Persistent" # Tails Linux Comptatible
8
+ WORKDIR = '/tmp/sgpg'
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sgpg
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Sgpg
6
+ # Manage the configuration file
7
+ class YamlConfig
8
+ attr_reader :config
9
+
10
+ def initialize
11
+ @file_dir = "#{ENV['HOME']}/.config/sgpg"
12
+ @filename = 'config.yml'
13
+ @full_path = "#{@file_dir}/#{@filename}"
14
+ @config = {
15
+ disk: '',
16
+ keyname: '',
17
+ crypted: false
18
+ }
19
+ end
20
+
21
+ def load
22
+ if !Dir.exist?(@file_dir) || !File.exist?(@full_path)
23
+ create_dir
24
+ write_config unless File.exist?(@full_path)
25
+ else
26
+ loading
27
+ end
28
+ end
29
+
30
+ def to_s
31
+ "Using disk >> #{@config[:disk]}, key >> #{@config[:keyname]}"
32
+ end
33
+
34
+ def save(opts)
35
+ puts "saving options #{opts}"
36
+ @config[:disk] = opts[:disk] if opts[:disk]
37
+ @config[:keyname] = opts[:keyname] if opts[:keyname]
38
+ @config[:crypted] = opts[:crypted] if opts[:crypted]
39
+ puts "current #{config}"
40
+ write_config
41
+ end
42
+
43
+ protected
44
+
45
+ def loading
46
+ puts "Loading #{@full_path}..."
47
+ @config = YAML.load_file(@full_path)
48
+ rescue Psych::SyntaxError => e
49
+ puts "YAML syntax error: #{e.message}"
50
+ rescue Errno::ENOENT
51
+ puts "File not found: #{@full_path}"
52
+ end
53
+
54
+ private
55
+
56
+ def create_dir
57
+ return if Dir.exist? @file_dir
58
+
59
+ puts "Creating #{@file_dir}..."
60
+ Dir.mkdir @file_dir
61
+ end
62
+
63
+ def write_config
64
+ in_yaml = YAML.dump(@config)
65
+ File.write(@full_path, in_yaml)
66
+ end
67
+ end
68
+ end
data/lib/sgpg.rb ADDED
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'sgpg/version'
4
+ require_relative 'sgpg/option'
5
+ require_relative 'sgpg/yaml_config'
6
+ require_relative 'sgpg/helper'
7
+ require_relative 'sgpg/cryptsetup'
8
+ require_relative 'sgpg/mount'
9
+ require_relative 'sgpg/gpg'
10
+ require_relative 'sgpg/archive'
11
+
12
+ # Manage your gpg key easyly
13
+ module Sgpg
14
+ def self.open(disk, is_crypted = false)
15
+ return if Dir.glob("#{Sgpg::MOUNTPOINT}/*").length >= 1
16
+
17
+ puts "Open device #{disk}..."
18
+ if is_crypted
19
+ Cryptsetup.new(disk).open
20
+ Mount.new('/dev/mapper/sgpg').open
21
+ else
22
+ Mount.new(disk).open
23
+ end
24
+ end
25
+
26
+ def self.close(disk, is_crypted = false)
27
+ if is_crypted
28
+ Mount.new('/dev/mapper/sgpg').close
29
+ Cryptsetup.new(disk).close
30
+ else
31
+ Mount.new(disk).close
32
+ end
33
+ end
34
+
35
+ def self.list_keys(keyname)
36
+ keys = Dir.glob("#{Sgpg::MOUNTPOINT}/Persistent/#{keyname}*.tar")
37
+ puts "Listing keys for #{keyname}..."
38
+ keys = Dir.glob("#{Sgpg::MOUNTPOINT}/Persistent/*.tar") if keys == [] || keys == ''
39
+ puts keys
40
+ end
41
+
42
+ # argument 'suffix' = master or lesser
43
+ def self.last_key(opts, suffix = 'master')
44
+ Sgpg.open(opts[:disk], opts[:crypted])
45
+
46
+ keys = Dir.glob("#{Sgpg::KEYDIR}/#{opts[:keyname]}*#{suffix}*.tar").sort
47
+ raise 'No keys found' unless keys.length >= 1
48
+
49
+ keys[0]
50
+ end
51
+
52
+ def self.clear_keys
53
+ return unless Dir.glob("#{Sgpg::WORKDIR}/*.key").length >= 1
54
+
55
+ print "Clearing keys located at #{Sgpg::WORKDIR}? (y/n) "
56
+ choice = gets.chomp
57
+ return unless choice.match(/^y|^Y/)
58
+
59
+ puts "Clearing #{Sgpg::WORKDIR}..."
60
+ system("shred -u #{Sgpg::WORKDIR}/*.key")
61
+ end
62
+
63
+ # Main logic here
64
+ module Main
65
+ # import and edit a gpg key from an archive (opts[:keypath])
66
+ def self.edit_key(opts)
67
+ Sgpg.open(opts[:disk], opts[:crypted])
68
+ archive = Archive.new(opts[:keypath], opts[:keyname])
69
+ archive.extract
70
+ archive.import
71
+ Sgpg.clear_keys
72
+ end
73
+
74
+ # export your real keys and create an archive (tar)
75
+ def self.export_secret(opts)
76
+ archive = Archive.new(opts[:keypath], opts[:keyname])
77
+ archive.create_master_tar
78
+ archive.move(Sgpg::KEYDIR)
79
+ Sgpg.clear_keys
80
+ end
81
+
82
+ # create an unpriviliged gpg key (no change can be made)
83
+ def self.lesser_keys(opts)
84
+ archive = Archive.new(opts[:keypath], opts[:keyname])
85
+ archive.create_lesser_tar
86
+ archive.move(Sgpg::KEYDIR)
87
+ Sgpg.clear_keys
88
+ end
89
+ end
90
+ end
data/sgpg.gemspec ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/sgpg/version'
4
+
5
+ # https://guides.rubygems.org/specification-reference/
6
+ Gem::Specification.new do |s|
7
+ s.name = 'sgpg'
8
+ s.summary = 'Short gpg, tool for manage your gpg key (backup tarball, unprivileged keys, etc...)'
9
+ s.version = Sgpg::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+
12
+ s.description = <<-DESCRIPTION
13
+ Short gpg, tool for manage your gpg key (backup tarball, unprivileged keys, etc...)
14
+ DESCRIPTION
15
+
16
+ s.email = 'szorfein@protonmail.com'
17
+ s.homepage = 'https://github.com/szorfein/sgpg'
18
+ s.license = 'GPL-3'
19
+ s.author = 'szorfein'
20
+
21
+ s.metadata = {
22
+ 'bug_tracker_uri' => 'https://github.com/szorfein/sgpg/issues',
23
+ 'changelog_uri' => 'https://github.com/szorfein/sgpg/blob/main/CHANGELOG.md',
24
+ 'source_code_uri' => 'https://github.com/szorfein/sgpg',
25
+ 'wiki_uri' => 'https://github.com/szorfein/sgpg/wiki',
26
+ 'funding_uri' => 'https://patreon.com/szorfein'
27
+ }
28
+
29
+ s.files = Dir.glob('{lib,bin}/**/*', File::FNM_DOTMATCH).reject { |f| File.directory?(f) }
30
+ s.files += %w[CHANGELOG.md LICENSE README.md]
31
+ s.files += %w[sgpg.gemspec]
32
+
33
+ s.bindir = 'bin'
34
+ s.executables << 'sgpg'
35
+ s.extra_rdoc_files = %w[README.md]
36
+
37
+ # s.cert_chain = %w[certs/szorfein.pem]
38
+ # s.signing_key = File.expand_path('~/.ssh/gem-private_key.pem')
39
+
40
+ s.required_ruby_version = '>=2.6'
41
+ s.requirements << 'gpg'
42
+ s.requirements << 'cryptsetup'
43
+ s.requirements << 'shred'
44
+ s.requirements << 'tar'
45
+ end
data.tar.gz.sig ADDED
Binary file