ubicloud/rhizome/host/spec/storage_key_encryption_spec.rb
Hadi Moshayedi 4c6f74738b Make StorageKeyEncryption.read_encrypted_dek backward compatible.
Previously, wrapped DEKs written to `data_encryption_key.json` were
encoded using `Base64.encode64`, which inserted a newline every 60
characters and always appended a trailing newline. Later, this was
changed to `Base64.strict_encode64`, which produces a single
uninterrupted string.

While `Base64.decode64` can handle both formats,
`Base64.strict_decode64` fails if newlines are present.

Since `StorageKeyEncryption.read_encrypted_dek` is called when starting
old VMs after a server reboot, it must remain backward compatible.

This change updates `StorageKeyEncryption.read_encrypted_dek` to use
`Base64.decode64`.
2025-08-21 16:02:20 -07:00

113 lines
3.1 KiB
Ruby

# frozen_string_literal: true
require_relative "../lib/storage_key_encryption"
require "openssl"
require "base64"
RSpec.describe StorageKeyEncryption do
subject(:sek) {
algorithm = "aes-256-gcm"
cipher = OpenSSL::Cipher.new(algorithm)
described_class.new({
"algorithm" => algorithm,
"key" => Base64.encode64(cipher.random_key),
"init_vector" => Base64.encode64(cipher.random_iv),
"auth_data" => "Ubicloud-Test-Auth"
})
}
it "can unwrap a wrapped key" do
key = "abcdefgh01234567abcdefgh01234567"
expect(sek.unwrap_key(sek.wrap_key(key))).to eq(key)
end
it "can wrap a key" do
dek = OpenSSL::Cipher.new("aes-256-xts").random_key.unpack1("H*")
r1 = sek.wrap_key(dek[..63])
expect(r1[0].length).to eq(64)
expect(r1[1].length).to eq(16)
r2 = sek.wrap_key(dek[64..])
expect(r2[0].length).to eq(64)
expect(r2[1].length).to eq(16)
end
it "fails if algorithm is not aes-256-gcm" do
sek2 = described_class.new({
"algorithm" => "aes256-wrap",
:key => "123",
:init_vector => "456"
})
expect {
sek2.unwrap_key("some key")
}.to raise_error RuntimeError, "currently only aes-256-gcm is supported"
expect {
sek2.wrap_key("some key")
}.to raise_error RuntimeError, "currently only aes-256-gcm is supported"
end
it "fails if auth_tag is not 16" do
key = "abcdefgh01234567abcdefgh01234567"
wrapped = sek.wrap_key(key)
wrapped[1] = wrapped[1][0]
expect {
sek.unwrap_key(wrapped)
}.to raise_error RuntimeError, "Invalid auth_tag size: 1"
end
describe "#read_encrypted_dek" do
let(:dek) {
k = OpenSSL::Cipher.new("aes-256-xts").random_key.unpack1("H*")
{key: k[..63], key2: k[64..]}
}
let(:wrapped_dek) {
{
key: sek.wrap_key(dek[:key]),
key2: sek.wrap_key(dek[:key2])
}
}
it "can read a file generated using strict_encode64" do
expect(File).to receive(:read).with("key-file").and_return(
JSON.pretty_generate({
cipher: "AES_XTS",
key: [
Base64.strict_encode64(wrapped_dek[:key][0]),
Base64.strict_encode64(wrapped_dek[:key][1])
],
key2: [
Base64.strict_encode64(wrapped_dek[:key2][0]),
Base64.strict_encode64(wrapped_dek[:key2][1])
]
})
)
read_key = sek.read_encrypted_dek("key-file")
expect(read_key[:key]).to eq(dek[:key])
expect(read_key[:key2]).to eq(dek[:key2])
end
it "can read a file generated using encode64" do
expect(File).to receive(:read).with("key-file").and_return(
JSON.pretty_generate({
cipher: "AES_XTS",
key: [
Base64.encode64(wrapped_dek[:key][0]),
Base64.encode64(wrapped_dek[:key][1])
],
key2: [
Base64.encode64(wrapped_dek[:key2][0]),
Base64.encode64(wrapped_dek[:key2][1])
]
})
)
read_key = sek.read_encrypted_dek("key-file")
expect(read_key[:key]).to eq(dek[:key])
expect(read_key[:key2]).to eq(dek[:key2])
end
end
end