sgpg 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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