1.13. Security Module#

Starting with LinOTP 2.5 it is supported to store the encryption key in Hardware Security Modules. Encryption and decryption can be performed in such a hardware module. LinOTP implements a concept of a security module abstraction layer i.e. even the old encryption key stored at /etc/linotp2/encKey now is handled via a security module. New modules can be added easily. To define a module you need to configure this in the linotp.ini file. If you do not add anything, the old encryption key functionality is used.:

linotpActiveSecurityModule = lunasa
linotpSecurity.lunasa.module = linotp.lib.security.pkcs11.Pkcs11SecurityModule

You can define several modules. LinOTP at the moment comes with a PKCS11 security module. To define, which module should be used the key linotpActiveSecurityModule is used which takes the identifier of the module. To define a new module, you use the key linotpSecurity.lunasa.module, where “lunasa” is the identifier or the name of the module and the key takes the Python module.

The following keys are configuration that depend on the chosen module. In this case the PKCS11 module.:

#Config depending on module
linotpSecurity.lunasa.library = libCryptoki2_64.so
linotpSecurity.lunasa.configHandle = 21
linotpSecurity.lunasa.valueHandle = 22
linotpSecurity.lunasa.tokenHandle = 23
linotpSecurity.lunasa.defaultHandle = 22
linotpSecurity.lunasa.slotid = 1

1.13.1. PKCS11 module and SafeNet LunaSA#

library

defines the PKCS11 so library in your filesystem.

slotid

is the slot of the PKCS11 module to use.

The configHandle, valueHandle and tokenHandle

are the handles within the slot of the corresponding AES keys to do the encryption and decryption of the OTP PIN, of configuration values, OTP keys and passwords. The defaultHandle is used, If one of the other Handles is not defined.

1.13.2. Password handling#

Usually the PKCS11 device needs a password to access the slot. This password can either be defined in the linotp.ini file or needs to be passed to the LinOTP server after it has started. To define it in linotp.ini do it like this:

linotpSecurity.lunasa.password = YourPassword

To pass the password later to the LinOTP server you can use the linotpadm.py command line client:

% linotpadm.py --admin=admin --url=https://localhost -C securitymodule --module=default
      python yubikey module not available.
      please get it from https://github.com/Yubico/python-yubico if you want to enroll yubikeys
      No module named yubico
      Please enter password for 'admin':
      Please enter password for security module 'default':
      { u'status': True,
      u'value': { u'setupSecurityModule': { u'activeSecurityModule': u'default',
      u'connected': True}}}

To check the status of the security module you can do this:

% linotpadm.py --admin=admin --url=https://localhost -C securitymodule
      python yubikey module not available.
      please get it from https://github.com/Yubico/python-yubico if you want to enroll yubikeys
      No module named yubico
      Please enter password for 'admin':
      { u'status': True,
      u'value': { u'setupSecurityModule': { u'activeSecurityModule': u'default',
      u'connected': True}}}

1.13.3. PKCS #11 and YubiHSM Padding bug#

Note

If you didn’t upgrade your LinOTP from a version before 2.7.1 or don’t use either the YubiHSM or a PKCS #11 HSM this does not affect you in any way!

Both our YubiHSM (linotp.lib.security.yubihsm.YubiSecurityModule) and our PKCS #11 (linotp.lib.security.pkcs11.Pkcs11SecurityModule) security modules had a bug in the pad() method. This bug was fixed in LinOTP 2.7.1.

If you have been using LinOTP before 2.7.1 and are now upgrading to a version greater or equal to 2.7.1 and use either a YubiHSM or a PKCS #11 HSM then you should set one of the following LinOTP config options:

pkcs11.accept_invalid_padding = True
yubihsm.accept_invalid_padding = True

Otherwise a lot of your encrypted data (mostly token seeds) could be unreadable and you would get an error when trying to authenticate or when performing other operations that use this data.

Beware that a small percentage of data (statistically about 0.4% of all values whose length is a multiple of the block length) will not be recoverable even when settings this option. This was also the case before fixing the bug. For example if you had about 2000 SHA256 OCRA tokens in your database, then about 8 of those would fail to authenticate (both before and after upgrading to LinOTP >= 2.7.1). For a detailed explanation of the why and when continue reading.

Technical details#

  • Both security modules pad data so it is a multiple of the block size before encrypting it

  • After decrypting the padding is removed

  • The default block size is 16 bytes

  • The padding algorithm is the one defined in PKCS #7 (i.e. if N bytes are missing to complete the block then append N bytes containing the value N). See: http://en.wikipedia.org/wiki/Padding_%28cryptography%29#PKCS7

For example assuming a block length of 16 bytes following is correct padding:

data:           0 x 01 01 01 01 01 01 01 01 01 01 01 01
padded data:    0 x 01 01 01 01 01 01 01 01 01 01 01 01 04 04 04 04

The bug appears when the data-length is a multiple of the block length. The correct padding in this case would be adding a complete block with bytes 0xFF. In our case nothing was added. When unpadding, the unpad() method tries to remove the (non-existing) padding and two things can happen:

  • The bytes at the end of the data happen to be ‘valid padding’ and are removed. This means data loss. Fortunately this happens rarely. The most likely case would be last byte equalling 0x01 which has probability of 1/256 (0.3%).

  • The bytes at the end of the data are invalid padding. Our default in this case is to raise a ValueError exception. To prevent the exception from being raised and to continue using the data the ‘accept_invalid_padding’ config options mentioned above can be set. If the option is set to True, then the data is returned as-it-is (without removing anything).

Example of the first case:

original data:              0 x 01 01 01 01 01 01 01 01 01 01 01 01
(incorrectly) padded data:  0 x 01 01 01 01 01 01 01 01 01 01 01 01
unpadded data:              0 x 01 01 01 01 01 01 01 01 01 01 01

Example of the second case:

original data:              0 x 01 01 01 01 01 01 01 01 01 01 01 03
(incorrectly) padded data:  0 x 01 01 01 01 01 01 01 01 01 01 01 03
if accept_invalid_padding:
    unpadded data:          0 x 01 01 01 01 01 01 01 01 01 01 01 03
else:
                            ValueError()

The correct padding (fixed in LinOTP 2.7.1):

original data:              0 x 01 01 01 01 01 01 01 01 01 01 01 01
padded data:                0 x 01 01 01 01 01 01 01 01 01 01 01 01 FF FF \
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF
unpadded data:              0 x 01 01 01 01 01 01 01 01 01 01 01 01