Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decryption using symmetric key in another function gives CKR_GENERAL_ERROR #53

Closed
Magentron opened this issue Feb 21, 2021 · 14 comments
Closed

Comments

@Magentron
Copy link
Contributor

Magentron commented Feb 21, 2021

On current master 941f846 using the following logic (pseduo code):

    $module = new Pkcs11\Module($path);
    
    // function openSession($path, $slot, $pin): Pkcs11\Session
    global $module;
    $session = $module->openSession($slot, Pkcs11\CKF_RW_SESSION);
    $session->login(Pkcs11\CKU_USER, $pin);
    return $session;
    
    // function getKey(Pkcs11\Session $session)
    $objects = $session->findObjects([
        Pkcs11\CKA_CLASS => Pkcs11\CKO_SECRET_KEY,
        Pkcs11\CKA_LABEL => 'My name',
    ]);
    return reset($objects);
    
    // function getMechanism(string $iv): Pkcs11\Mechanism
    $gcmParams = new Pkcs11\GcmParams($iv, '', 128);
    return new Pkcs11\Mechanism(Pkcs11\CKM_AES_GCM, $gcmParams);
    
    // function encrypt($iv, $data): string
    $mechanism = $this->getMechanism($iv);
    $session = openSession($path, $slot, $pin);
    $key = getKey($session);
    return $key->encrypt($mechanism, $data);
    
    // function decrypt($iv, $data): string
    $mechanism = $this->getMechanism($iv);
    $session = openSession($path, $slot, $pin);
    $key = getKey($session);
    return $key->decrypt($mechanism, $data);
    
    // main
    $iv = openssl_random_pseudo_bytes(16);
    $data = 'This is a test';
    $encrypted = encrypt($iv, $data);
    $decrypted = decrypt($iv, $data);

Results in:

    Exception: (0x00000005/CKR_GENERAL_ERROR) PKCS#11 module error: Unable to decrypt

PS: I'm using SoftHSMv2

@gamringer
Copy link
Owner

Are you able to submit a script that actually triggers the error ?

When trying it myself, I get a SegFault that seems related to issue #51

@Magentron
Copy link
Contributor Author

Magentron commented Feb 22, 2021

Sure, it took a while before getting it figured out, but here it is. HTH

FYI: While creating this script I have also come across malloc() crashes like so (unable to reproduce now):

php(34332,0x10c0375c0) malloc: can't allocate region
*** mach_vm_map(size=8665304126897934336) failed (error code=3)
php(34332,0x10c0375c0) malloc: *** set a breakpoint in malloc_error_break to debug

NB: If you replace the $key->encrypt($mechanism, and $key->decrypt($mechanism, for str_rot13( it works for all cases.

use Pkcs11\Key;
use Pkcs11\Module;
use Pkcs11\Session;

class Pkcs11Encryption
{
    /** @var Module */
    protected static $module;

    public function __construct()
    {
        $this->slot     = getenv('PHP11_SLOT');
        $this->pin      = getenv('PHP11_PIN');
        $this->keyLabel = getenv('PHP11_KEY_LABEL');
    }

    public function encrypt(string $iv, string $data, bool $serialize = true, bool $wrap = true)
    {
        $mechanism = $this->getMechanism($iv);
        $session = $this->openSession();

        try {
            $key = $this->getKeyFromSession($session);

            $data      = $serialize ? serialize($data) : $data;
            $encrypted = $key->encrypt($mechanism, $data);

            if ($wrap) {
                $encrypted = base64_encode($encrypted);
            }

            return $encrypted;

        } finally {
            $session->logout();
            unset($session);
        }
    }

    public function decrypt(string $iv, string $data, bool $serialize = true, bool $unwrap = true)
    {
        $mechanism = $this->getMechanism($iv);
        $session   = $this->openSession();

        try {
            $key = $this->getKeyFromSession($session);

            if ($unwrap) {
                $data = base64_decode($data);
            }
            $value = $key->decrypt($mechanism, $data);
            $value = $serialize ? unserialize($value) : $value;

            return $value;

        } finally {
            $session->logout();
            unset($session);
        }
    }

    protected function getKeyFromSession(Session $session): Key
    {
        $objects = $session->findObjects([
            Pkcs11\CKA_LABEL => $this->keyLabel,
            Pkcs11\CKA_CLASS => Pkcs11\CKO_SECRET_KEY,
        ]);

        if ($objects) {
            return reset($objects);
        }

        throw new Exception('No key found');
    }

    protected function getMechanism(string $iv): Pkcs11\Mechanism
    {
        $gcmParams = new Pkcs11\GcmParams($iv, '', 128);

        return new Pkcs11\Mechanism(Pkcs11\CKM_AES_GCM, $gcmParams);
    }

    protected function openSession(): Session
    {
        $module  = $this->getModule();
        $session = $module->openSession($this->slot, Pkcs11\CKF_RW_SESSION);
        $session->login(Pkcs11\CKU_USER, $this->pin);

        return $session;
    }

    protected static function getModule(): Module
    {
        if (null === self::$module) {
            self::$module = new Module(getenv('PHP11_MODULE'));
        }
        return self::$module;
    }

}

function test(Pkcs11Encryption $encryption, $serialize, $wrap)
{
    static $run = 0;

    $iv   = openssl_random_pseudo_bytes(16);
    $data = 'This is a test';

    $encrypted  = $encryption->encrypt($iv, $data,      $serialize, $wrap);
    $decrypted  = $encryption->decrypt($iv, $encrypted, $serialize, $wrap);

    ++$run;
    echo "ORIGINAL #{$run}: '{$data}'\n";
    echo "DECRYPTED#{$run}: '{$decrypted}'\n";
    assert($decrypted === $data, 'Decrypted data does not match original data');
}

// main
$encryption = new Pkcs11Encryption();

/** DOES     WORK */ $serialize  = false; $wrap  = false; test($encryption, $serialize, $wrap);
/** DOES     WORK */ $serialize  = false; $wrap  = true;  test($encryption, $serialize, $wrap);
/** DOES     WORK */ $serialize  = true;  $wrap  = false; test($encryption, $serialize, $wrap);
/** DOES NOT WORK */ $serialize  = true;  $wrap  = true;  test($encryption, $serialize, $wrap);

Output:

$ PHP11_MODULE=/usr/lib/softhsm/libsofthsm2.so PHP11_SLOT=1 PHP11_PIN=123456 PHP11_KEY_LABEL='My key' php test-pkcs11-53.php
ORIGINAL #1: 'This is a test'
DECRYPTED#1: 'This is a test'
ORIGINAL #2: 'This is a test'
DECRYPTED#2: 'This is a test'
ORIGINAL #3: 'This is a test'
DECRYPTED#3: 'This is a test'
PHP Fatal error:  Uncaught Exception: (0x00000005/CKR_GENERAL_ERROR) PKCS#11 module error: Unable to decrypt in test-pkcs11-53.php:58
Stack trace:
#0 test-pkcs11-53.php(58): Pkcs11\Key->decrypt(Object(Pkcs11\Mechanism), '\x00\x07\x1AuL\xC7\xCFv5\xAF\x8F\x01\xEDy:...')
#1 test-pkcs11-53.php(117): Pkcs11Encryption->decrypt('\xE0OQ\x10\xCF\x0F\x9B\x82Xm\n\xF1\x1E\x96>...', '\x00\x07\x1AuL\xC7\xCFv5\xAF\x8F\x01\xEDy:...', true, true)
#2 test-pkcs11-53.php(131): test(Object(Pkcs11Encryption), true, true)
#3 {main}
  thrown in test-pkcs11-53.php on line 58

@Magentron
Copy link
Contributor Author

Hi @gamringer, any news on this? I've just received my Nitrokey HSM 2 and would like to start using it. 😀

@gamringer
Copy link
Owner

Hi @Magentron, I am still working on #51, fixing a bad design decision that I made early on.

Since you now mention the Nitrokey HSM 2, that should make debugging this easier. I did also just received one myself a 2 weeks ago and saw some very interesting things with respect to the returned size for some operations, which is why I added some checks on buffer creations like in a85bd3f. In that case, attempting to wrap something on a Nitrokey HSM 2 returns a ridiculous ciphertextLen (I didn't check if it could just be a signed vs unsigned issue, since it's not supposed to be). But I did not add that check everywhere yet.

@gamringer
Copy link
Owner

Not related to #51. I tried a few modifications to your test script. Somehow, merely calling base64_encode() on the encrypted data prevents it from decrypting the data, even when using the variable directly, not going through base64. I'm getting very confused.

@gamringer
Copy link
Owner

So it looks like it is in fact related to #51 after all. I will be putting up a pull request for you to try soon.

@gamringer
Copy link
Owner

@Magentron Here you go. Try PR #57

@Magentron
Copy link
Contributor Author

@gamringer It's probably going to be Monday or Tuesday, so far it looks good with the latest changes.

@gamringer
Copy link
Owner

NP The PR is already merged.

@Magentron
Copy link
Contributor Author

Magentron commented Mar 23, 2021

The test I wrote for my application now works in PHP 7.3 and PHP 8.0. In order to make the extension compile for 7.3 I had to add a define for ZEND_THIS since this appeared in 8.0, do you @gamringer want me to create an issue/PR for it?

@gamringer
Copy link
Owner

I was not planning on adding support for 7.3. If it's a relatively small change, sure, make a PR and we'll discuss further on it.

@vjardin
Copy link
Contributor

vjardin commented Mar 23, 2021

+1 small patches that can extend the scope of supported versions are welcomed as long as they remain maintainable. The more people/PHP versions will be using this extension, the better.

@Magentron
Copy link
Contributor Author

Unfortunately, I got it running with PHP 7.3 with no compiler errors, but with warnings, so I've been checking the warnings and there are some warnings that should be resolved properly (like implicit declaration of function 'ZEND_TRY_ASSIGN_REF_VALUE' is invalid) that require data structures not available until PHP 7.4 as far as I can see.
So unless somebody wants to put in the work PHP 7.4 is the lowest version the extension will work with, which probably should be documented.

@gamringer
Copy link
Owner

It already documented on the pecl page, but I'll put it up on the README as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants