scl 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,84 @@
1
+ require 'json'
2
+ module Scl
3
+ module Control
4
+ class DH < ControllerModule
5
+
6
+ help <<-HELP,
7
+ syn. Step 1 of a diffie hellman exchange.
8
+ Generates a private and public key and der to be used to generate a symmetric key with a second party.
9
+
10
+ e.g
11
+ >> Using public key (Saves to [filename].enc by default, unless -o is used)
12
+ scl dh syn
13
+ scl dh syn -fbase64
14
+ scl dh syn -o ./dhell
15
+ HELP
16
+ def syn
17
+ syn = dh.syn
18
+ args.output_file ||= "dh-key-exchange"
19
+ controller.output(
20
+ Output.new(syn[:private].to_json, ".dh1.priv"),
21
+ Output.new(syn[:public].to_json, ".dh1.pub")
22
+ )
23
+ end
24
+
25
+ help <<-HELP,
26
+ ack. Step 2 of a diffie hellman exchange.
27
+ Requires the public output of step 1 (The der and public key)
28
+ Generates a private and public key and der to be used to generate a symmetric key with a second party.
29
+
30
+ The output [output_file].key contains the secret key generated by the DH exchange.
31
+ The public output [output_file].dh2.pub must be passed back to the first party to complete the exchange
32
+ e.g
33
+ >> Using public key (Saves to [filename].enc by default, unless -o is used)
34
+ scl dh ack ./dh-key-exchange.dh1.pub
35
+ scl dh ack ./dhell.dh1.pub -o ./dhell
36
+ HELP
37
+ def ack(file)
38
+ unless File.exists?(file)
39
+ raise ControlError.new("Couldn't find file containing part 1 of diffie hellman exchange: #{file}. File doesnt exist")
40
+ end
41
+ input = JSON.parse(input_decoder.decode(IO.read(file)))
42
+ ack = dh.ack(der: input['der'], public_key: input['public_key'])
43
+ args.output_file ||= "dh-key-exchange"
44
+ controller.output(
45
+ Output.new(ack[:private].to_json, ".dh2.key"),
46
+ Output.new(ack[:public].to_json, ".dh2.pub")
47
+ )
48
+ end
49
+
50
+ help <<-HELP,
51
+ fin. Step 3 and final step of a diffie hellman exchange.
52
+ Requires the private output of step 1 (The der and private key) and the public key from step 2
53
+ Generates a secret key shared with the second party
54
+
55
+ The output [output_file].key contains the secret key generated by the DH exchange.
56
+
57
+ e.g
58
+ >> Using public key (Saves to [filename].enc by default, unless -o is used)
59
+ scl dh fin ./dh-key-exchange.dh1.priv ./dh-key-exchange.dh2.pub
60
+ scl dh fin ./dhell.dh1.priv ./dhell.dh2.pub -o ./dhell
61
+ HELP
62
+ def fin(dh1_priv, dh2_pub)
63
+ unless File.exists?(dh1_priv)
64
+ raise ControlError.new("Couldn't find file private portion of part 1 of diffie hellman exchange: #{dh1_priv}. File doesnt exist")
65
+ end
66
+ unless File.exists?(dh2_pub)
67
+ raise ControlError.new("Couldn't find file containing public portion of part 2 of diffie hellman exchange: #{dh2_pub}. File doesnt exist")
68
+ end
69
+ dh1 = JSON.parse(input_decoder.decode(IO.read(dh1_priv)))
70
+ dh2 = JSON.parse(input_decoder.decode(IO.read(dh2_pub)))
71
+ result = dh.fin(der: dh1['der'], private_key: dh1['private_key'], public_key: dh2['public_key'])
72
+ args.output_file ||= "dh-key-exchange"
73
+ controller.output(
74
+ Output.new(result[:private].to_json, ".dh1.key")
75
+ )
76
+ end
77
+
78
+ private
79
+ def dh
80
+ @dh ||= Scl::DH.new
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,90 @@
1
+ module Scl
2
+ module Control
3
+ class Digest < ControllerModule
4
+
5
+ help <<-HELP,
6
+ sign. Signs a file using a support hash algorithm. Defaults to sha256
7
+ e.g
8
+ scl digest sign /path/to/file
9
+ scl digest sign /path/to/file -o stdout
10
+ scl digest sign /path/to/file -d sha512
11
+ HELP
12
+ def sign(input_file)
13
+ signature = Scl::Digest.digest(args.digest || 'sha256', read_file(input_file, "File to sign"))
14
+ args.output_file ||= input_file
15
+ args.output_format ||= 'binary'
16
+ controller.output(
17
+ Output.new(signature, ".sig")
18
+ )
19
+ end
20
+
21
+ help <<-HELP,
22
+ verify
23
+ e.g.
24
+ scl digest verify file signature
25
+ scl digest verify file signature -d sha512
26
+ HELP
27
+ def verify(input_file, signature_file)
28
+ signature = Scl::Digest.digest(args.digest || 'sha256', read_file(input_file, "File to verify"))
29
+ args.input_format ||= 'binary'
30
+ if signature == input_decoder.decode(read_file(signature_file, "Signature"))
31
+ exit(0)
32
+ else
33
+ exit(1)
34
+ end
35
+ end
36
+
37
+ help <<-HELP,
38
+ hmac. Generates an HMAC for a file, can be given an optional key or alternately one will be
39
+ generated for you. Keep hold of this key for verifying signatures in the future
40
+ e.g.
41
+ scl hmac file # Generates both a digest and a secure random key
42
+ scl hmac file -k keyfile # Generates a digest using an existing key
43
+ scl hmac file -d sha512 # Provide alternate digest algorithm
44
+ HELP
45
+ def hmac(input_file)
46
+ input_key = args.key_path ?
47
+ read_file(args.key_path) :
48
+ nil
49
+ signature, key = Scl::Digest.hmac(args.digest || 'sha256', read_file(input_file, "File to sign"), input_key)
50
+ args.output_file ||= input_file
51
+ args.output_format ||= 'binary'
52
+ controller.output(
53
+ Output.new(signature, ".sig"),
54
+ input_key ? nil : Output.new(key, '.key')
55
+ )
56
+ end
57
+
58
+ help <<-HELP,
59
+ hmac. Verifies the contents of a file match an HMAC signature (using a given key)
60
+ e.g.
61
+ scl hmac_verify file signature -k keyfile
62
+ HELP
63
+ def hmac_verify(input_file, signature_file)
64
+ args.input_format ||= 'binary'
65
+ key = input_decoder.decode(read_file(args.key_path, 'HMAC Key file', 'Use -k'))
66
+ data = read_file(input_file, "File to verify")
67
+ signature, key = Scl::Digest.hmac(args.digest || 'sha256', data, key)
68
+ if signature == input_decoder.decode(read_file(signature_file, "Signature"))
69
+ exit(0)
70
+ else
71
+ exit(1)
72
+ end
73
+ end
74
+
75
+ help <<-HELP,
76
+ list. List supported hash algorithms
77
+ e.g.
78
+ scl digest list
79
+ HELP
80
+ def list
81
+ puts OpenSSL::Digest.constants.reject{|x| x.to_s =~ /Error/ }
82
+ end
83
+
84
+ private
85
+ def ss
86
+ @ss ||= Scl::SecretShare.new(args.min_shares.to_i, args.num_shares.to_i)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,14 @@
1
+ module Scl
2
+ module Control
3
+ class Output
4
+ attr_reader :content
5
+ def initialize(content, suffix)
6
+ @content, @suffix = content, suffix
7
+ end
8
+
9
+ def file(path)
10
+ File.join("#{path.gsub(%r(#{@suffix.gsub('.','\.')}$),'')}#{@suffix}")
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,161 @@
1
+ module Scl
2
+ module Control
3
+ class RSA < ControllerModule
4
+
5
+ help <<-HELP,
6
+ Generates an RSA keypair
7
+ e.g
8
+ >> Generates a key-pair and prints to stdout
9
+ $ scl rsa generate
10
+
11
+ >> Generates a key-pair and saves to the filesystem
12
+ $ scl rsa generate -o /path/to/file
13
+ HELP
14
+ def generate
15
+ begin
16
+ result = Scl::RSA.generate(args.key_size)
17
+ controller.output(
18
+ Output.new(result.public.export, '.pub'),
19
+ Output.new(result.private.export, '.priv')
20
+ )
21
+ rescue StandardError => e
22
+ raise ControlError.new("Couldn't generate key of size #{args.key_size}")
23
+ end
24
+ end
25
+
26
+ help <<-HELP,
27
+ Signs a file using an RSA private key.
28
+ This file can then be verified using the corresponding public-key or the same private-key
29
+ e.g
30
+ >> Sign [file] using private key
31
+ $ scl rsa sign file -Z /path/to/private/key
32
+ HELP
33
+ def sign(file)
34
+ unless args.private_key_file
35
+ raise ControlError.new("Please provide a private key file (-Z or --priv) use --help to find out more")
36
+ end
37
+ unless File.exists?(args.private_key_file)
38
+ raise ControlError.new("Private key file #{args.private_key_file} doesnt exist")
39
+ end
40
+ unless File.exists?(file)
41
+ raise ControlError.new("Couldn't find file to sign: #{file}. File doesnt exist")
42
+ end
43
+
44
+ private_key = load_key(args.private_key_file)
45
+ signature = private_key.sign(IO.read(file))
46
+ args.output_file ||= file
47
+ controller.output(
48
+ Output.new(signature, ".sig")
49
+ )
50
+ end
51
+
52
+ help <<-HELP,
53
+ Verifies a file matches a signature for an RSA private key
54
+ Verification can be performed using the corresponding public-key or the same private-key that generated the signature
55
+ e.g
56
+
57
+ >> Verify [file] using public key
58
+ $ scl rsa verify -p /path/to/private/key file file.sig
59
+ $ scl rsa verify --pub-key /path/to/private/key file file.sig
60
+
61
+ >> Verify [file] using private key
62
+ $ scl rsa verify -Z /path/to/private/key file file.sig
63
+ HELP
64
+ def verify(file, signature="#{file}.sig")
65
+ key_file = args.public_key_file || args.private_key_file
66
+ unless key_file
67
+ raise ControlError.new("Please provide a private or public key file (-p --pub-key, -Z or --priv-key) use --help to find out more")
68
+ end
69
+ unless File.exists?(key_file)
70
+ raise ControlError.new("Key file #{key_file} doesnt exist")
71
+ end
72
+ unless File.exists?(file)
73
+ raise ControlError.new("Couldn't find file to verify: #{file}. File doesnt exist")
74
+ end
75
+ unless File.exists?(signature)
76
+ raise ControlError.new("Couldn't find signature to verify: #{signature}. File doesnt exist")
77
+ end
78
+ key = load_key(key_file)
79
+ if key.verify(input_decoder.decode(IO.read(signature)), IO.read(file))
80
+ exit(0)
81
+ else
82
+ exit(1)
83
+ end
84
+ end
85
+
86
+ help <<-HELP,
87
+ Encrypts a file.
88
+ e.g
89
+ >> Using public key (Saves to [filename].enc by default, unless -o is used)
90
+ scl rsa encrypt -p /path/to/public_key /path/to/file
91
+ scl rsa encrypt --pub-key /path/to/public_key /path/to/file
92
+
93
+ >> Using public key (Saves to [filename].enc by default, unless -o is used)
94
+ scl rsa encrypt -Z /path/to/private_key /path/to/file
95
+ scl rsa encrypt --priv-key /path/to/private_key /path/to/file
96
+ HELP
97
+ def encrypt(file)
98
+ key_file = args.public_key_file || args.private_key_file
99
+ unless key_file
100
+ raise ControlError.new("Please provide a private or public key file (-p --pub-key, -Z or --priv-key) use --help to find out more")
101
+ end
102
+ unless File.exists?(key_file)
103
+ raise ControlError.new("Key file #{key_file} doesnt exist")
104
+ end
105
+ unless File.exists?(file)
106
+ raise ControlError.new("Couldn't find file to verify: #{file}. File doesnt exist")
107
+ end
108
+ key = load_key(key_file)
109
+ encrypted = key.encrypt(IO.read(file))
110
+ args.output_file ||= file
111
+ controller.output(
112
+ Output.new(encrypted, '.enc')
113
+ )
114
+ end
115
+
116
+ help <<-HELP,
117
+ Decrypts a file.
118
+ e.g
119
+ >> Using public key (Writes to stdout unless -o is used)
120
+ scl rsa decrypt -p /path/to/public_key /path/to/file.enc
121
+ scl rsa decrypt --pub-key /path/to/public_key /path/to/file.enc
122
+
123
+ >> Using public key (Writes to stdout unless -o is used)
124
+ scl rsa decrypt -Z /path/to/private_key /path/to/file.enc
125
+ scl rsa decrypt --priv-key /path/to/private_key /path/to/file.enc
126
+ HELP
127
+ def decrypt(file)
128
+ key_file = args.public_key_file || args.private_key_file
129
+ unless key_file
130
+ raise ControlError.new("Please provide a private or public key file (-p --pub-key, -Z or --priv-key) use --help to find out more")
131
+ end
132
+ unless File.exists?(key_file)
133
+ raise ControlError.new("Key file #{key_file} doesnt exist")
134
+ end
135
+ unless File.exists?(file)
136
+ raise ControlError.new("Couldn't find file to verify: #{file}. File doesnt exist")
137
+ end
138
+ key = load_key(key_file)
139
+ decrypted = key.decrypt(input_decoder.decode(IO.read(file)))
140
+ args.output_format = 'binary'
141
+ controller.output(
142
+ Output.new(decrypted, '')
143
+ )
144
+ end
145
+
146
+ private
147
+ def load_key(key_file)
148
+ raw_input = IO.read(key_file)
149
+ puts "Decoding key using #{input_decoder.name}" if args.verbose
150
+ decoded_input = key_coder.decode(raw_input)
151
+ puts "Constructing new RSA key using decoded input" if args.verbose
152
+ begin
153
+ pkey = OpenSSL::PKey::RSA.new(decoded_input)
154
+ Scl::RSA::Key.new(pkey)
155
+ rescue StandardError => e
156
+ raise ControlError.new("Unable to construct key from decoded input #{e.message}")
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,48 @@
1
+ module Scl
2
+ module Control
3
+ class SSS < ControllerModule
4
+
5
+ help <<-HELP,
6
+ generate. Generate a set of shares for a shared secret
7
+ Arguments are the total number of shares to generate, and the number of shares required to unlock
8
+ the secret.
9
+ e.g
10
+ scl sss generate -m 3 -n 5
11
+ scl sss generate --min-shares=11 --num-shares=14 -o output_file
12
+
13
+ Large secrets are encoded using multiple blocks, which can create large shares.
14
+ An alternative, more space efficient approach to this is to encode a shorter key using secret sharing,
15
+ and to then encrypt the large secret using this key and a block-cipher
16
+
17
+ HELP
18
+ def generate(input_file)
19
+ raise ControlError.new("Min-shares must be a positive integer") unless args.min_shares.to_i > 0
20
+ raise ControlError.new("Num-shares must be a positive integer") unless args.num_shares.to_i > 0
21
+ raise ControlError.new('Num shares must be larger than or equal to min shares') unless args.num_shares.to_i >= args.min_shares.to_i
22
+ input = read_file(input_file)
23
+ args.output_file ||= input_file
24
+ controller.output(
25
+ Output.new(ss.generate(input).join("\n"), ".shares")
26
+ )
27
+ end
28
+
29
+ help <<-HELP,
30
+ combine. Combines a set of shares for a shared secret
31
+ Expects as an argument a file of "\n" separate secret shares
32
+ e.g
33
+ scl sss combine shares.txt
34
+ HELP
35
+ def combine(input_file)
36
+ shares = read_file(input_file).split("\n")
37
+ controller.output(
38
+ Output.new(Scl::SecretShare.combine(shares), ".sec")
39
+ )
40
+ end
41
+
42
+ private
43
+ def ss
44
+ @ss ||= Scl::SecretShare.new(args.min_shares.to_i, args.num_shares.to_i)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,14 @@
1
+ require 'scl/control/output'
2
+ require 'scl/control/controller'
3
+ require 'scl/control/controller_module'
4
+ require 'scl/control/aes'
5
+ require 'scl/control/rsa'
6
+ require 'scl/control/sss'
7
+ require 'scl/control/dh'
8
+ require 'scl/control/digest'
9
+
10
+ module Scl
11
+ module Control
12
+ class ControlError < StandardError; end
13
+ end
14
+ end
data/lib/scl/dh.rb ADDED
@@ -0,0 +1,52 @@
1
+ module Scl
2
+ require 'base64'
3
+ class DH
4
+ attr_reader :encoder
5
+
6
+ def initialize(encoder: Format::BASE64)
7
+ @encoder = encoder
8
+ end
9
+ # :0> syn = Scl::DH.new.syn
10
+ # :1> ack = Scl::DH.new.ack(syn[:public])
11
+ # :2> shared_key1 = Scl::DH.new.fin(syn[:private].merge(ack[:public]))[:private][:shared_key]
12
+ # :3> shared_key2 = ack[:private][:shared_key]
13
+ def syn(length: 512)
14
+ dh = OpenSSL::PKey::DH.new(length)
15
+ {
16
+ private: {
17
+ der: encoder.encode(dh.public_key.to_der),
18
+ private_key: encoder.encode(dh.priv_key.to_s(16))
19
+ },
20
+ public: {
21
+ der: encoder.encode(dh.public_key.to_der),
22
+ public_key: encoder.encode(dh.pub_key.to_s(16))
23
+ }
24
+ }
25
+ end
26
+
27
+ def ack(der:, public_key:)
28
+ dh = OpenSSL::PKey::DH.new(encoder.decode(der))
29
+ dh.generate_key!
30
+ shared_key = dh.compute_key(OpenSSL::BN.new(encoder.decode(public_key), 16))
31
+ {
32
+ private: {
33
+ shared_key: encoder.encode(shared_key)
34
+ },
35
+ public: {
36
+ public_key: encoder.encode(dh.pub_key.to_s(16))
37
+ }
38
+ }
39
+ end
40
+
41
+ def fin(private_key:, der:, public_key:)
42
+ dh = OpenSSL::PKey::DH.new(encoder.decode(der))
43
+ dh.priv_key = OpenSSL::BN.new(encoder.decode(private_key), 16)
44
+ shared_key = dh.compute_key(OpenSSL::BN.new(encoder.decode(public_key), 16))
45
+ {
46
+ private: {
47
+ shared_key: encoder.encode(shared_key)
48
+ }
49
+ }
50
+ end
51
+ end
52
+ end
data/lib/scl/digest.rb ADDED
@@ -0,0 +1,29 @@
1
+ module Scl
2
+ class Digest
3
+ def self.digest(digest, data)
4
+ if digest_exists?(digest)
5
+ OpenSSL::Digest.const_get(digest.upcase).new.hexdigest(data)
6
+ end
7
+ end
8
+
9
+ def self.hmac(digest, data, key=nil)
10
+ if digest_exists?(digest)
11
+ require 'securerandom'
12
+ key = key || SecureRandom.hex
13
+ [OpenSSL::HMAC.hexdigest(digest, key, data), key]
14
+ end
15
+ end
16
+
17
+ private
18
+ def self.digest_exists?(digest)
19
+ begin
20
+ OpenSSL::Digest.const_get(digest.upcase)
21
+ rescue NameError => e
22
+ puts "Couldn't get digest type. #{digest}"
23
+ puts "Try $ scl digest list – for a list of supported digests"
24
+ return false
25
+ end
26
+ true
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ module Scl
2
+ class Scl::Auto < Scl::Format
3
+ def encode(data)
4
+ sorted = (data.chars.map(&:ord).uniq.sort) - [10]
5
+ if sorted.first < 32 || sorted.max > 126
6
+ Scl::Format::BASE64.encode(data)
7
+ else
8
+ Scl::Format::BINARY.encode(data)
9
+ end
10
+ end
11
+
12
+ def decode(data)
13
+ png = Regexp.new("\x89PNG".force_encoding("binary"))
14
+ if /^#{png}/ === data
15
+ Scl::Format::QRCODE.decode(data)
16
+ elsif data[/[^A-Za-z0-9\+\/\n=]/]
17
+ Scl::Format::BINARY.decode(data)
18
+ else
19
+ Scl::Format::BASE64.decode(data)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ module Scl
2
+ class Scl::Base64 < Scl::Format
3
+ require 'base64'
4
+ def encode(data)
5
+ ::Base64.encode64(data)
6
+ end
7
+
8
+ def decode(data)
9
+ ::Base64.decode64(data)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ module Scl
2
+ class Scl::Binary < Scl::Format
3
+ def encode(data)
4
+ data
5
+ end
6
+
7
+ def decode(data)
8
+ data
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,44 @@
1
+ module Scl
2
+ class Format
3
+ def output(filename, data)
4
+ IO.write(filename, encode(data))
5
+ end
6
+
7
+ def read(filename)
8
+ decode(IO.read(filename))
9
+ end
10
+
11
+ def encode(data)
12
+ raise "Must be implemented by subclass"
13
+ end
14
+
15
+ def decode(data)
16
+ raise "Must be implemented by subclass"
17
+ end
18
+
19
+ def name
20
+ self.class.name
21
+ end
22
+ end
23
+ end
24
+
25
+ require 'scl/formats/base64'
26
+ require 'scl/formats/binary'
27
+ require 'scl/formats/words'
28
+ require 'scl/formats/auto'
29
+ require 'scl/formats/hex'
30
+ require 'scl/formats/qrcode'
31
+ require 'scl/formats/stdout'
32
+
33
+ module Scl
34
+ class Format
35
+
36
+ BASE64 = Scl::Base64.new
37
+ BINARY = Scl::Binary.new
38
+ WORDS = Scl::Words.new
39
+ QRCODE = Scl::QRCode.new
40
+ HEX = Scl::Hex.new
41
+ AUTO = Scl::Auto.new
42
+ STDOUT = Scl::Stdout.new
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ module Scl
2
+ class Scl::Hex < Scl::Format
3
+ def encode(data)
4
+ data.bytes.map{|x| x.to_s(16).rjust(2, ?0) }.join
5
+ end
6
+
7
+ def decode(data)
8
+ data.scan(/../).map{|s| s.to_i(16) }.map(&:chr).join
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Scl
2
+ class Scl::QRCode < Scl::Format
3
+ def encode(data)
4
+ raise "Not implemented yet"
5
+ end
6
+
7
+ def decode(data)
8
+ raise "Not implemented yet"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Scl
2
+ class Scl::Stdout < Scl::Format
3
+ def encode(data)
4
+ puts data
5
+ end
6
+
7
+ def decode(data)
8
+ data
9
+ end
10
+ end
11
+ end