Class: Paperback::Preparer
- Inherits:
-
Object
- Object
- Paperback::Preparer
- Extended by:
- T::Sig
- Defined in:
- lib/paperback/preparer.rb
Overview
Class wrapping functions to prepare data for paperback storage, including QR code and sixword encoding.
Constant Summary collapse
- PassChars =
T.let( [*'a'..'z', *'A'..'Z', *'0'..'9'].freeze, T::Array[String] )
Instance Attribute Summary collapse
-
#data ⇒ Object
readonly
Returns the value of attribute data.
-
#encrypt ⇒ Object
readonly
Returns the value of attribute encrypt.
-
#labels ⇒ Object
readonly
Returns the value of attribute labels.
-
#passphrase_file ⇒ Object
readonly
Returns the value of attribute passphrase_file.
-
#qr_base64 ⇒ Object
readonly
Returns the value of attribute qr_base64.
Class Method Summary collapse
- .gpg_ascii_dearmor(data) ⇒ Object
- .gpg_ascii_enarmor(data, strip_comments: true) ⇒ Object
- .gpg_encrypt(filename:, password:) ⇒ Object
- .log ⇒ Object
- .random_passphrase(entropy_bits: 256, char_set: PassChars) ⇒ Object
- .truncated_sha256(content) ⇒ Object
Instance Method Summary collapse
- #include_base64? ⇒ Boolean
-
#initialize(filename:, encrypt: true, qr_base64: false, qr_level: nil, comment: nil, passphrase_file: nil, include_base64: true) ⇒ Preparer
constructor
A new instance of Preparer.
- #log ⇒ Object
- #passphrase ⇒ Object
- #render(output_filename:, extra_draw_opts: {}) ⇒ Object
Constructor Details
#initialize(filename:, encrypt: true, qr_base64: false, qr_level: nil, comment: nil, passphrase_file: nil, include_base64: true) ⇒ Preparer
Returns a new instance of Preparer.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/paperback/preparer.rb', line 44 def initialize(filename:, encrypt: true, qr_base64: false, qr_level: nil, comment: nil, passphrase_file: nil, include_base64: true) log.debug('Preparer#initialize') # lazy initializers, all explicitly set to nil @log = T.let(nil, T.nilable(Logger)) @qr_code = T.let(nil, T.nilable(RQRCode::QRCode)) @sixword_lines = T.let(nil, T.nilable(T::Array[String])) @passphrase = T.let(nil, T.nilable(String)) log.info("Reading #{filename.inspect}") plain_data = File.read(filename) log.debug("Read #{plain_data.bytesize} bytes") @encrypt = T.let(encrypt, T::Boolean) if encrypt @data = self.class.gpg_encrypt(filename: filename, password: passphrase) else @data = T.let(plain_data, String) end @sha256 = T.let(Digest::SHA256.hexdigest(plain_data), String) @qr_base64 = T.let(qr_base64, T::Boolean) @qr_level = T.let(qr_level, T.nilable(Symbol)) @passphrase_file = T.let(passphrase_file, T.nilable(String)) @include_base64 = T.let(!!include_base64, T::Boolean) @labels = T.let({}, T::Hash[String, T.untyped]) @labels['Filename'] = filename @labels['Backed up'] = Time.now.to_s stat = File.stat(filename) @labels['Mtime'] = stat.mtime @labels['Bytes'] = plain_data.bytesize @labels['Comment'] = comment if comment @labels['SHA256'] = Digest::SHA256.hexdigest(plain_data) @document = T.let(Paperback::Document.new, Paperback::Document) end |
Instance Attribute Details
#data ⇒ Object (readonly)
Returns the value of attribute data.
19 20 21 |
# File 'lib/paperback/preparer.rb', line 19 def data @data end |
#encrypt ⇒ Object (readonly)
Returns the value of attribute encrypt.
28 29 30 |
# File 'lib/paperback/preparer.rb', line 28 def encrypt @encrypt end |
#labels ⇒ Object (readonly)
Returns the value of attribute labels.
22 23 24 |
# File 'lib/paperback/preparer.rb', line 22 def labels @labels end |
#passphrase_file ⇒ Object (readonly)
Returns the value of attribute passphrase_file.
31 32 33 |
# File 'lib/paperback/preparer.rb', line 31 def passphrase_file @passphrase_file end |
#qr_base64 ⇒ Object (readonly)
Returns the value of attribute qr_base64.
25 26 27 |
# File 'lib/paperback/preparer.rb', line 25 def qr_base64 @qr_base64 end |
Class Method Details
.gpg_ascii_dearmor(data) ⇒ Object
212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/paperback/preparer.rb', line 212 def self.gpg_ascii_dearmor(data) cmd = %w[gpg --batch --dearmor] out = T.let(nil, T.nilable(String)) log.debug('+ ' + cmd.join(' ')) Subprocess.check_call(cmd, stdin: Subprocess::PIPE, stdout: Subprocess::PIPE) do |p| out, _err = p.communicate(data) end T.must(out) end |
.gpg_ascii_enarmor(data, strip_comments: true) ⇒ Object
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/paperback/preparer.rb', line 192 def self.gpg_ascii_enarmor(data, strip_comments: true) cmd = %w[gpg --batch --enarmor] out = T.let(nil, T.nilable(String)) log.debug('+ ' + cmd.join(' ')) Subprocess.check_call(cmd, stdin: Subprocess::PIPE, stdout: Subprocess::PIPE) do |p| out, _err = p.communicate(data) end out = T.must(out) if strip_comments out = out.each_line.select { |l| !l.start_with?('Comment: ') }.join end out end |
.gpg_encrypt(filename:, password:) ⇒ Object
176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/paperback/preparer.rb', line 176 def self.gpg_encrypt(filename:, password:) cmd = %w[ gpg -c -o - --batch --cipher-algo aes256 --passphrase-fd 0 -- ] + [filename] out = T.let(nil, T.nilable(String)) log.debug('+ ' + cmd.join(' ')) Subprocess.check_call(cmd, stdin: Subprocess::PIPE, stdout: Subprocess::PIPE) do |p| out, _err = p.communicate(password) end T.must(out) end |
.log ⇒ Object
97 98 99 |
# File 'lib/paperback/preparer.rb', line 97 def self.log @log ||= Paperback.class_log(self) end |
.random_passphrase(entropy_bits: 256, char_set: PassChars) ⇒ Object
162 163 164 165 166 167 |
# File 'lib/paperback/preparer.rb', line 162 def self.random_passphrase(entropy_bits: 256, char_set: PassChars) chars_needed = (entropy_bits / Math.log2(char_set.length)).ceil (0...chars_needed).map { PassChars.fetch(SecureRandom.random_number(char_set.length)) }.join end |
.truncated_sha256(content) ⇒ Object
171 172 173 |
# File 'lib/paperback/preparer.rb', line 171 def self.truncated_sha256(content) Digest::SHA256.hexdigest(content)[0...16] end |
Instance Method Details
#include_base64? ⇒ Boolean
229 230 231 |
# File 'lib/paperback/preparer.rb', line 229 def include_base64? !!@include_base64 end |
#log ⇒ Object
93 94 95 |
# File 'lib/paperback/preparer.rb', line 93 def log @log ||= Paperback.class_log(self.class) end |
#passphrase ⇒ Object
149 150 151 152 |
# File 'lib/paperback/preparer.rb', line 149 def passphrase raise "Can't have passphrase without encrypt" unless encrypt @passphrase ||= self.class.random_passphrase end |
#render(output_filename:, extra_draw_opts: {}) ⇒ Object
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/paperback/preparer.rb', line 102 def render(output_filename:, extra_draw_opts: {}) log.debug('Preparer#render') opts = { labels: labels, qr_code: qr_code, sixword_lines: sixword_lines, sixword_bytes: data.bytesize, } if include_base64? opts[:base64_content] = base64_content opts[:base64_bytes] = data.bytesize end if encrypt opts[:passphrase_sha] = self.class.truncated_sha256(passphrase) opts[:passphrase_len] = passphrase.length if passphrase_file File.open( T.must(passphrase_file), File::CREAT | File::EXCL | File::WRONLY, 0o400 ) do |f| f.write(passphrase) end log.info("Wrote passphrase to #{passphrase_file.inspect}") end end opts.merge!(extra_draw_opts) @document.render(output_file: output_filename, draw_opts: opts) log.info('Render complete') if encrypt puts 'SHA256(passphrase)[0...16]: ' + opts.fetch(:passphrase_sha) if !passphrase_file puts "Passphrase: #{passphrase}" end else log.info('Content was not encrypted') end end |