Module: SshTresor::Crypto Private

Defined in:
lib/ssh_tresor/crypto.rb

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Cryptographic primitives used by the envelope encryption construction.

This module intentionally exposes small byte-oriented helpers. Higher-level callers should normally use Vault.

Constant Summary collapse

CHALLENGE_SIZE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

32
MASTER_KEY_SIZE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

32
NONCE_SIZE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

12
AUTH_TAG_SIZE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

16

Class Method Summary collapse

Class Method Details

.decrypt(key, nonce, ciphertext_with_tag) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Decrypts ciphertext produced by encrypt.

Parameters:

  • key (String)

    32-byte AES key.

  • nonce (String)

    12-byte AES-GCM nonce.

  • ciphertext_with_tag (String)

    ciphertext followed by the GCM tag.

Returns:

  • (String)

    plaintext bytes.

Raises:



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/ssh_tresor/crypto.rb', line 85

def decrypt(key, nonce, ciphertext_with_tag)
  raise DecryptionError, "ciphertext too short" if ciphertext_with_tag.bytesize < AUTH_TAG_SIZE

  ciphertext = ciphertext_with_tag.byteslice(0, ciphertext_with_tag.bytesize - AUTH_TAG_SIZE)
  tag = ciphertext_with_tag.byteslice(-AUTH_TAG_SIZE, AUTH_TAG_SIZE)

  cipher = OpenSSL::Cipher.new("aes-256-gcm")
  cipher.decrypt
  cipher.key = key
  cipher.iv = nonce
  cipher.auth_tag = tag
  cipher.auth_data = "".b

  cipher.update(ciphertext) + cipher.final
rescue OpenSSL::Cipher::CipherError
  raise DecryptionError, "authentication failed - wrong key or corrupted data"
end

.derive_key(signature) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Derives a slot-wrapping key from SSH-agent signature bytes.

Parameters:

  • signature (String)

    raw signature bytes returned by the SSH agent.

Returns:

  • (String)

    32-byte AES key derived with HKDF-SHA256.



48
49
50
51
52
53
54
55
56
# File 'lib/ssh_tresor/crypto.rb', line 48

def derive_key(signature)
  OpenSSL::KDF.hkdf(
    signature,
    salt: "ssh-tresor-v3",
    info: "slot-key-derivation",
    length: 32,
    hash: "SHA256"
  )
end

.encrypt(key, nonce, plaintext) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Encrypts plaintext with AES-256-GCM.

Parameters:

  • key (String)

    32-byte AES key.

  • nonce (String)

    12-byte AES-GCM nonce.

  • plaintext (String)

    plaintext bytes.

Returns:

  • (String)

    ciphertext followed by the 16-byte GCM authentication tag.

Raises:



65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/ssh_tresor/crypto.rb', line 65

def encrypt(key, nonce, plaintext)
  cipher = OpenSSL::Cipher.new("aes-256-gcm")
  cipher.encrypt
  cipher.key = key
  cipher.iv = nonce
  cipher.auth_data = "".b

  ciphertext = cipher.update(plaintext.b) + cipher.final
  ciphertext + cipher.auth_tag
rescue OpenSSL::Cipher::CipherError => e
  raise Error, "Encryption failed: AES-GCM encryption failed: #{e.message}"
end

.random_challengeString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Generates a random SSH-agent signing challenge.

Returns:

  • (String)

    32 random bytes.



26
27
28
# File 'lib/ssh_tresor/crypto.rb', line 26

def random_challenge
  SecureRandom.random_bytes(CHALLENGE_SIZE)
end

.random_master_keyString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Generates a fresh data master key.

Returns:

  • (String)

    32 random bytes suitable for AES-256.



33
34
35
# File 'lib/ssh_tresor/crypto.rb', line 33

def random_master_key
  SecureRandom.random_bytes(MASTER_KEY_SIZE)
end

.random_nonceString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Generates a fresh AES-GCM nonce.

Returns:

  • (String)

    12 random bytes.



40
41
42
# File 'lib/ssh_tresor/crypto.rb', line 40

def random_nonce
  SecureRandom.random_bytes(NONCE_SIZE)
end