Class: Capsium::Package::Cipher

Inherits:
Object
  • Object
show all
Defined in:
lib/capsium/package/cipher.rb,
sig/capsium/package/cipher.rbs

Overview

Encrypts and decrypts whole Capsium packages (05x-packaging "Encryption", 05x-security "Encrypted information"). An encrypted .cap is a zip containing metadata.json (cleartext), signature.json (the encryption envelope) and package.enc (AES-256-GCM of the inner plaintext .cap zip). The random DEK is wrapped with the recipient's RSA public key using OAEP with SHA-256. OCB/OpenPGP are out of scope.

Direct Known Subclasses

OpenPgpCipher

Defined Under Namespace

Classes: CipherError, DecryptionError, KeyRequiredError

Constant Summary collapse

ALGORITHM =

Returns:

  • (String)
"AES-256-GCM"
KEY_MANAGEMENT =

Returns:

  • (String)
"RSA-OAEP-SHA256"
ENCRYPTED_FILE =

Returns:

  • (String)
"package.enc"
ENVELOPE_FILE =

Returns:

  • (String)
"signature.json"
RSA_OPTIONS =

Returns:

  • (Hash[String, String])
{ "rsa_padding_mode" => "oaep",
"rsa_oaep_md" => "SHA256", "rsa_mgf1_md" => "SHA256" }.freeze
CIPHER_NAME =

Returns:

  • (String)

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.decrypt_to_directory(source_path, private_key_path) ⇒ String

Decrypts an encrypted package into a fresh temporary directory and returns the directory path. The cipher is selected from the envelope's keyManagement: OpenPGP envelopes decrypt through OpenPgpCipher, everything else through the RSA cipher.

Parameters:

  • source_path (String, Pathname)
  • private_key_path (String)

Returns:

  • (String)


123
124
125
126
127
128
129
130
131
132
133
# File 'lib/capsium/package/cipher.rb', line 123

def self.decrypt_to_directory(source_path, private_key_path)
  Dir.mktmpdir.tap do |tmp|
    inner_cap = File.join(tmp, "inner.cap")
    for_encrypted(source_path).decrypt(source_path, private_key_path, inner_cap)
    package_path = File.join(tmp, File.basename(source_path.to_s, ".cap"))
    FileUtils.mkdir_p(package_path)
    Packager.new.unpack(inner_cap, package_path)
    FileUtils.rm_f(inner_cap)
    return package_path
  end
end

.encrypted?(path) ⇒ Boolean

Whether the path (.cap file or uncompressed directory) is an encrypted package, i.e. contains package.enc.

Parameters:

  • path (String, Pathname)

Returns:

  • (Boolean)


57
58
59
60
61
62
63
64
# File 'lib/capsium/package/cipher.rb', line 57

def self.encrypted?(path)
  return File.file?(File.join(path, ENCRYPTED_FILE)) if File.directory?(path)
  return false unless File.file?(path)

  Zip::File.open(path) { |zip| !zip.find_entry(ENCRYPTED_FILE).nil? }
rescue Zip::Error
  false
end

.for_encrypted(path) ⇒ Cipher

The cipher instance matching the envelope's keyManagement: OpenPgpCipher for OpenPGP envelopes, the RSA cipher otherwise.

Parameters:

  • path (String, Pathname)

Returns:



78
79
80
# File 'lib/capsium/package/cipher.rb', line 78

def self.for_encrypted(path)
  key_management(path) == OpenPgpCipher::KEY_MANAGEMENT ? OpenPgpCipher.new : new
end

.key_management(path) ⇒ String?

The keyManagement declared by the encryption envelope of the path (.cap file or uncompressed directory), or nil when the envelope is absent or unreadable.

Parameters:

  • path (String, Pathname)

Returns:

  • (String, nil)


69
70
71
72
73
74
# File 'lib/capsium/package/cipher.rb', line 69

def self.key_management(path)
  source = envelope_source(path)
  source && EncryptionConfig.from_json(source).encryption&.key_management
rescue Lutaml::Model::Error, JSON::ParserError
  nil
end

Instance Method Details

#decrypt(encrypted_path, private_key_path, output_path) ⇒ String

Decrypts the encrypted package at encrypted_path (.cap file or uncompressed directory) with the recipient's RSA private key and writes the plaintext .cap to output_path. Returns output_path.

Parameters:

  • encrypted_path (String, Pathname)
  • private_key_path (String)
  • output_path (String)

Returns:

  • (String)


110
111
112
113
114
115
116
# File 'lib/capsium/package/cipher.rb', line 110

def decrypt(encrypted_path, private_key_path, output_path)
  private_key = load_private_key(private_key_path)
  envelope = load_envelope(encrypted_path)
  ciphertext = read_source(encrypted_path, ENCRYPTED_FILE)
  File.binwrite(output_path, decrypt_bytes(ciphertext, envelope, private_key))
  output_path
end

#encrypt(source_path, public_key_path, output_path) ⇒ String

Encrypts the package at source_path (a .cap file, or a package directory which is packed first) for the recipient's RSA public key (or X.509 certificate) and writes the encrypted .cap to output_path. Returns output_path.

Parameters:

  • source_path (String)
  • public_key_path (String)
  • output_path (String)

Returns:

  • (String)


97
98
99
100
101
102
103
104
105
# File 'lib/capsium/package/cipher.rb', line 97

def encrypt(source_path, public_key_path, output_path)
  public_key = load_public_key(public_key_path)
  with_cap_file(source_path) do |cap_path|
    envelope, ciphertext = encrypt_bytes(File.binread(cap_path), public_key)
    write_encrypted_cap(output_path, read_source(cap_path, Package::METADATA_FILE),
                        envelope, ciphertext)
  end
  output_path
end