Class: SshTresor::TresorBlob
- Inherits:
-
Object
- Object
- SshTresor::TresorBlob
- Defined in:
- lib/ssh_tresor/format.rb
Overview
Parsed SSHTRESR v3 encrypted file.
A blob contains one or more key slots and one AES-256-GCM encrypted payload. It can be read from or written to the binary wire format, and it can also be represented as base64 armor for terminal-friendly transport.
Constant Summary collapse
- MAGIC =
"SSHTRESR".b
- VERSION =
0x03- FINGERPRINT_SIZE =
32- CHALLENGE_SIZE =
32- NONCE_SIZE =
12- AUTH_TAG_SIZE =
16- MASTER_KEY_SIZE =
32- ENCRYPTED_KEY_SIZE =
MASTER_KEY_SIZE + AUTH_TAG_SIZE
- SLOT_SIZE =
FINGERPRINT_SIZE + CHALLENGE_SIZE + NONCE_SIZE + ENCRYPTED_KEY_SIZE
- HEADER_SIZE =
10- MAX_TRESOR_SIZE =
100 * 1024 * 1024
- ARMOR_BEGIN =
"-----BEGIN SSH TRESOR-----"- ARMOR_END =
"-----END SSH TRESOR-----"
Instance Attribute Summary collapse
-
#ciphertext ⇒ Object
readonly
Returns the value of attribute ciphertext.
-
#data_nonce ⇒ Object
readonly
Returns the value of attribute data_nonce.
-
#slots ⇒ Object
readonly
Returns the value of attribute slots.
Class Method Summary collapse
-
.from_armored(text) ⇒ SshTresor::TresorBlob
Parses armored tresor text.
-
.from_binary(data) ⇒ SshTresor::TresorBlob
Parses binary
SSHTRESRv3 bytes. -
.from_bytes(data) ⇒ SshTresor::TresorBlob
Parses binary or armored tresor content.
-
.parse_slot(bytes) ⇒ SshTresor::Slot
Parses a fixed-width key slot from binary data.
Instance Method Summary collapse
-
#find_slot(fingerprint) ⇒ SshTresor::Slot?
Finds a slot by raw SHA-256 fingerprint bytes.
-
#initialize(slots:, data_nonce:, ciphertext:) ⇒ TresorBlob
constructor
Creates an in-memory tresor blob.
-
#slot_fingerprints ⇒ Array<String>
Lists raw slot fingerprints.
-
#to_armored ⇒ String
Serializes the blob as PEM-like base64 armor.
-
#to_bytes ⇒ String
Serializes the blob as binary
SSHTRESRv3 bytes.
Constructor Details
#initialize(slots:, data_nonce:, ciphertext:) ⇒ TresorBlob
Creates an in-memory tresor blob.
142 143 144 145 146 |
# File 'lib/ssh_tresor/format.rb', line 142 def initialize(slots:, data_nonce:, ciphertext:) @slots = slots @data_nonce = data_nonce @ciphertext = ciphertext end |
Instance Attribute Details
#ciphertext ⇒ Object (readonly)
Returns the value of attribute ciphertext.
48 49 50 |
# File 'lib/ssh_tresor/format.rb', line 48 def ciphertext @ciphertext end |
#data_nonce ⇒ Object (readonly)
Returns the value of attribute data_nonce.
48 49 50 |
# File 'lib/ssh_tresor/format.rb', line 48 def data_nonce @data_nonce end |
#slots ⇒ Object (readonly)
Returns the value of attribute slots.
48 49 50 |
# File 'lib/ssh_tresor/format.rb', line 48 def slots @slots end |
Class Method Details
.from_armored(text) ⇒ SshTresor::TresorBlob
Parses armored tresor text.
69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/ssh_tresor/format.rb', line 69 def self.from_armored(text) start = text.index(ARMOR_BEGIN) finish = text.index(ARMOR_END) raise Error, "Invalid tresor format: missing BEGIN header" if start.nil? raise Error, "Invalid tresor format: missing END footer" if finish.nil? raise Error, "Invalid tresor format: invalid armor structure" if start >= finish base64 = text[(start + ARMOR_BEGIN.length)...finish].chars.reject { |char| char =~ /\s/ }.join from_binary(Base64.strict_decode64(base64)) rescue ArgumentError => e raise Error, "Invalid tresor format: base64 decoding failed: #{e.}" end |
.from_binary(data) ⇒ SshTresor::TresorBlob
Parses binary SSHTRESR v3 bytes.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/ssh_tresor/format.rb', line 87 def self.from_binary(data) min_size = HEADER_SIZE + SLOT_SIZE + NONCE_SIZE + AUTH_TAG_SIZE raise Error, "Invalid tresor format: data too short: #{data.bytesize} bytes, minimum #{min_size} required" if data.bytesize < min_size raise Error, "Invalid tresor format: invalid magic header" unless data.byteslice(0, 8) == MAGIC version = data.getbyte(8) raise Error, "Invalid tresor format: unsupported version: #{version}, expected #{VERSION}" unless version == VERSION slot_count = data.getbyte(9) raise Error, "Invalid tresor format: tresor has no key slots" if slot_count.zero? slots_end = HEADER_SIZE + (slot_count * SLOT_SIZE) raise Error, "Invalid tresor format: data too short for #{slot_count} slots" if data.bytesize < slots_end + NONCE_SIZE + AUTH_TAG_SIZE slots = Array.new(slot_count) do |index| offset = HEADER_SIZE + (index * SLOT_SIZE) parse_slot(data.byteslice(offset, SLOT_SIZE)) end data_nonce = data.byteslice(slots_end, NONCE_SIZE) ciphertext = data.byteslice(slots_end + NONCE_SIZE, data.bytesize - slots_end - NONCE_SIZE) new(slots: slots, data_nonce: data_nonce, ciphertext: ciphertext) end |
.from_bytes(data) ⇒ SshTresor::TresorBlob
Parses binary or armored tresor content.
55 56 57 58 59 60 61 62 |
# File 'lib/ssh_tresor/format.rb', line 55 def self.from_bytes(data) bytes = data.b if bytes.valid_encoding? && bytes.strip.start_with?(ARMOR_BEGIN) from_armored(bytes) else from_binary(bytes) end end |
.parse_slot(bytes) ⇒ SshTresor::Slot
Parses a fixed-width key slot from binary data.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/ssh_tresor/format.rb', line 117 def self.parse_slot(bytes) raise Error, "Invalid tresor format: slot data too short" if bytes.bytesize < SLOT_SIZE offset = 0 fingerprint = bytes.byteslice(offset, FINGERPRINT_SIZE) offset += FINGERPRINT_SIZE challenge = bytes.byteslice(offset, CHALLENGE_SIZE) offset += CHALLENGE_SIZE nonce = bytes.byteslice(offset, NONCE_SIZE) offset += NONCE_SIZE encrypted_key = bytes.byteslice(offset, ENCRYPTED_KEY_SIZE) Slot.new( fingerprint: fingerprint, challenge: challenge, nonce: nonce, encrypted_key: encrypted_key ) end |
Instance Method Details
#find_slot(fingerprint) ⇒ SshTresor::Slot?
Finds a slot by raw SHA-256 fingerprint bytes.
172 173 174 |
# File 'lib/ssh_tresor/format.rb', line 172 def find_slot(fingerprint) slots.find { |slot| slot.fingerprint == fingerprint } end |
#slot_fingerprints ⇒ Array<String>
Lists raw slot fingerprints.
179 180 181 |
# File 'lib/ssh_tresor/format.rb', line 179 def slot_fingerprints slots.map(&:fingerprint) end |
#to_armored ⇒ String
Serializes the blob as PEM-like base64 armor.
162 163 164 165 166 |
# File 'lib/ssh_tresor/format.rb', line 162 def to_armored encoded = Base64.strict_encode64(to_bytes) wrapped = encoded.scan(/.{1,64}/).join("\n") "#{ARMOR_BEGIN}\n#{wrapped}\n#{ARMOR_END}\n" end |
#to_bytes ⇒ String
Serializes the blob as binary SSHTRESR v3 bytes.
152 153 154 155 156 157 |
# File 'lib/ssh_tresor/format.rb', line 152 def to_bytes raise Error, "Invalid tresor format: tresor has no key slots" if slots.empty? raise Error, "Invalid tresor format: tresor has too many slots (max 255)" if slots.length > 255 MAGIC + [VERSION, slots.length].pack("CC") + slots.map(&:to_bytes).join.b + data_nonce + ciphertext end |