Closed Luc45 closed 1 year ago
This package is considered feature-complete, and is now in security-only maintenance mode, following a decision by the Technical Steering Committee. If you have a security issue, please follow our security reporting guidelines. If you wish to take on the role of maintainer, please nominate yourself
I have come up with a workaround for this in the meantime. It's a sort of PHP-based ssh-agent.
Request A:
shmop
extension enabled, we persist the password in Shared MemoryRequest B:
This is very similar to how ssh-agent
works in Unix, as it asks for the decryption password once and store the unencrypted key in memory for the duration of that boot.
Code:
/**
* @return string|null The password stored in the shared memory, if any.
*/
protected function get_encryption_password_from_shared_memory() {
// Early bail: If "shmop" is not available, simply do not persist the password in memory.
if ( ! extension_loaded( 'shmop' ) ) {
return null;
}
// Create shared memory space if it doesn't exist.
$shared_memory = @shmop_open( ftok( __FILE__, 't' ), "c", 0600, 1000 );
if ( $shared_memory === false ) {
// Open it with "read-only" if it exists.
$shared_memory = shmop_open( ftok( __FILE__, 't' ), "a", 0600, 1000 );
}
$password = shmop_read( $shared_memory, 0, 1000 );
shmop_close( $shared_memory );
if ( empty( trim( $password ) ) ) {
return null;
}
return trim( $password );
}
/**
* @param string $password The password to store in the shared memory.
*
* @return void
*/
protected function save_encryption_password_to_shared_memory( string $password ): void {
// Early bail: If "shmop" is not available, simply do not persist the password in memory.
if ( ! extension_loaded( 'shmop' ) ) {
return;
}
// Create shared memory space if it doesn't exist.
$shared_memory = @shmop_open( ftok( __FILE__, 't' ), "c", 0600, 1000 );
if ( $shared_memory === false ) {
// Open it with "write-only" if it exists.
$shared_memory = shmop_open( ftok( __FILE__, 't' ), "w", 0600, 1000 );
}
$written = shmop_write( $shared_memory, str_pad( $password, 1000 ), 0 );
if ( $written === false ) {
throw EncryptionException::password_persist_exception();
}
}
The code example sets Unix permissions 0600
for reading/writing the password used to unencrypt the private key. This ensures only the owner has access.
However, it is important to note that the shared memory used to store the password could be read by another program running under the same user.
For example, a malware could read the password from the shared memory and use it to decrypt the private key. If this risk is unacceptable, you may want to consider alternative solutions using native ssh-agent/Keychain from the OS.
By the way, if someone can explain to me how the SSH Agent protocol stores the unencrypted private key in memory without the risk above, I'd love to know more.
if someone can explain to me how the SSH Agent protocol stores the unencrypted private key in memory without the risk above, I'd love to know more
It seems the SSH Agent protocol stores unencrypted private keys in memory, making them vulnerable to being read. (Google "dump memory ssh agent")
This has been demonstrated through examples such as using memory dumps to extract private SSH keys from OpenSSH (see: https://research.nccgroup.com/2020/11/11/decrypting-openssh-sessions-for-fun-and-profit/) and through the use of machine learning to extract keys from memory dumps (see: https://arxiv.org/pdf/2209.05243.pdf).
This exploit requires root
access, though, at which point the attacker could insert a keylogger and steal your SSH key password anyway.
That makes me feel better about the shmop
approach.
And lastly, for those that land on this thread later, we can use a bash script to encrypt/decrypt using the local ssh-agent
, leveraging the stored private keys: https://gist.github.com/wmertens/c4f2c4186c04dc5442bbe3396f2c12f6
Usage:
echo foo > foo
ssh-add -L
)./ssh-crypt.bash -e 123 ssh-rsa {PUBLIC_KEY_HERE} < foo > bar
./ssh-crypt.bash -d 123 ssh-rsa {PUBLIC_KEY_HERE} < bar > baz
Result:
➜ cat foo
foo
➜ cat bar
Salted__Æ+Ö5^_[4i<9c><85>^FR^K´lÙ^M^BK^Z>èH<9c>ìw^Ymµÿ&V<88><8e>ÚO½^@M¿
➜ cat baz
foo
Feature Request
Summary
The feature request is to leverage the existing ssh-agent from the operating system to provide a password-protected SSH key for laminas-crypt encryption. This will reduce the need for the user to enter the password multiple times, as the ssh-agent will only prompt for the password once and hold the unencrypted key in memory for the duration of the boot.
Background
My understanding is that if
laminas-crypt
is using a password-protected SSH key, we need to pass the password to\Laminas\Crypt\PublicKey\Rsa\PrivateKey
. So on every interaction with my CLI tool that uses encryption, the password is requested and then used to decrypt the config. This can be inconvenient and increase the risk of human error.Example: On every interaction with the CLI tool, the password is requested in STDIN and used to decrypt:
Proposed Solution
By leveraging the ssh-agent, the user can add the password-protected key to the local ssh-agent and use the private key provided by the ssh-agent to decrypt the data in the CLI. The ssh-agent holds the unencrypted key in memory, eliminating the need to enter the password multiple times.
Example
Here's an example of how the process would work:
Add the password-protected key to the local ssh-agent: