pyhsm-1.2.0/0000775000175000017500000000000013006370267012530 5ustar daindain00000000000000pyhsm-1.2.0/doc/0000775000175000017500000000000013006370267013275 5ustar daindain00000000000000pyhsm-1.2.0/doc/Running_Hardware_Tests.adoc0000664000175000017500000000325113002424703020534 0ustar daindain00000000000000== Tests Running these tests requires a YubiHSM. The user running the tests also needs to have permission to use the device. On Ubuntu this can be achieved by adding the user to the `dialout` group. === Setup the device Initialize the YubiHSM by holding down the button on it while it is inserted into a USB port. The YubiHSM should blink slowly to indicate configuration mode. Connect to the serial interface of the device and enter the following commands: HSM (keys not decrypted)> zap Confirm current config being erased (type yes) yes - wait - done NO_CFG> hsm ffffffff Enabled flags ffffffff = YSM_AEAD_GENERATE,YSM_BUFFER_AEAD_GENERATE,YSM_RANDOM_AEAD_GENERATE,YSM_AEAD_DECRYPT_CMP,YSM_DG Enter cfg password (g to generate) Enter admin Yubikey public id 001/008 (enter when done) Enter master key (g to generate) Confirm current config being erased (type yes) yes - wait - done HSM (keys changed)> keycommit - Done HSM> exit You should now be ready to run the tests. === Running the tests To run the tests, have a device configured as described above, then run: $ YHSM_ZAP=1 python setup.py test Note that subsequent testruns can omit `YHSM_ZAP=1` to skip the initial HSM device configuration, however each testrun that doesn't reset the device configuration will take longer and longer to complete, until the next configuration reset. This is due to the admin YubiKey OTP counters being incremented on each run, and not stored anywhere, requiring looping to find the correct value. It is also possible to just reset the configuration, without running the tests: $ python -m test.configure_hsm pyhsm-1.2.0/doc/Intro.adoc0000664000175000017500000000313613002140277015213 0ustar daindain00000000000000== Installation === API To learn about the API, either download an official release tar.gz file and look in doc/html/ (more details in doc/README file), or install pyhsm and start reading the pydoc documentation. === Installation ==== Quick Ubuntu guide [source, sh] ---- $ sudo add-apt-repository ppa:yubico/stable $ sudo apt-get update $ sudo apt-get install python-pyhsm $ pydoc pyhsm $ pydoc pyhsm.base ---- ==== Windows python-pyhsm has been tested on Windows with Python 2.6 (2.7 will probably also work) from http://www.python.org/. You need to install pySerial (tested with pyserial-2.5.win32.exe) from http://pypi.python.org/pypi/pyserial. When you first insert the YubiHSM in your computer, Windows will indicate it cannot install the hardware. Download the .INF file from http://www.yubico.com/yubihsm/ and select that in the Windows dialog. Now, you should be able to communicate with the YubiHSM from Python like this (for this simple test, I put a copy of the pyhsm source tree in Z:\tmp\pyhsm): [source, py] ---- Z:\tmp\pyhsm>c:\Python26\python.exe Python 2.6.6 (r266:84297, Aug 24 2010, 18:46:32) [MSC v.1500 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys.path.append("Z:\\tmp\\pyhsm") >>> import pyhsm >>> hsm = pyhsm.YHSM(device = "COM3") >>> hsm.info() >>> ---- Here is how to for example get five bytes of random binary data from the YubiHSM: [source, py] ---- >>> hsm.random(5) '\x9c\x0fu\xcfP' >>> hsm.random(5).encode("hex") 'b562456719' >>> ---- pyhsm-1.2.0/doc/db_schema0000664000175000017500000000026213002140277015115 0ustar daindain00000000000000CREATE TABLE aead_table ( public_id varchar(16) NOT NULL, keyhandle INT NOT NULL, nonce BLOB(6) NOT NULL, aead BLOB(32) NOT NULL, PRIMARY KEY (public_id, keyhandle) ); pyhsm-1.2.0/doc/Database_Usage.adoc0000664000175000017500000000260013002140277016743 0ustar daindain00000000000000== Database Usage IMPORT / EXPORT AEADs, to/from a database using yhsm-db-import and yhsm-db-export === INSTALLATION On Debian/Ubuntu install [source, sh] ~$ sudo apt-get install python-sqlalchemy (or any other method illustrated at: http://docs.sqlalchemy.org/en/rel_0_8/intro.html#installation) On other systems: Install SQLAlchemy from http://docs.sqlalchemy.org/en/rel_0_8/intro.html#installation A database schema is provided to configure the database table for the import/export tools. Create your favourite database and use te schema db_schema provided. [source, sh] ---- ~$ cat doc/db_schema CREATE TABLE aead_table ( public_id varchar(16) NOT NULL, keyhandle INT NOT NULL, aead BLOB NOT NULL, PRIMARY KEY (public_id, keyhandle) ); ---- === USAGE IMPORT: yhsm-db-import aeads_source_folder database_url EXPORT: yhsm-db-export aeads_destination_folder database_url [CAUTION] It is not safe to execute the command with the database url containing username and password. ==== IMPORT [source, sh] ~$ python yhsm-db-import /root/aeads/ mysql://localhost/database_name OR [source, sh] ~$ python yhsm-db-import /root/aeads/ mysql://root:password@localhost:3306/database_name ==== EXPORT [source, sh] ~$ python yhsm-db-export /root/aeads/ mysql://localhost/database_name OR [source, sh] ~$ python yhsm-db-export /root/aeads/ mysql://root:password@localhost:3306/database_name pyhsm-1.2.0/doc/YubiHSM_if.h0000664000175000017500000004663613002140277015413 0ustar daindain00000000000000/************************************************************************* ** ** ** YubiHSM_if - HSM mode interface declarations ** ** ** ** Copyright 2011 - Yubico AB ** ** ** ** Date / Sig / Rev / History ** ** 110205 / J E / 0.00 / Main ** ** 110412 / J E / 0.98 / Release changes ** ** 110809 / J E / 1.01 / Release changes ** ** ** *************************************************************************/ #ifndef __YUBIHSM_H_INCLUDED #define __YUBIHSM_H_INCLUDED #include #ifdef _WIN32 typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned long uint32_t; #endif #define YSM_PUBLIC_ID_SIZE 6 // Size of public id for std OTP validation #define YSM_OTP_SIZE 16 // Size of OTP #define YSM_BLOCK_SIZE 16 // Size of block operations #define YSM_MAX_KEY_SIZE 32 // Max size of CCMkey #define YSM_DATA_BUF_SIZE 64 // Size of internal data buffer #define YSM_AEAD_NONCE_SIZE 6 // Size of AEAD nonce (excluding size of key handle) #define YSM_AEAD_MAC_SIZE 8 // Size of AEAD MAC field #define YSM_CCM_CTR_SIZE 2 // Sizeof of AES CCM counter field #define YSM_AEAD_MAX_SIZE (YSM_DATA_BUF_SIZE + YSM_AEAD_MAC_SIZE) // Max size of an AEAD block #define YSM_YUBIKEY_AEAD_SIZE (KEY_SIZE + UID_SIZE + YSM_AEAD_MAC_SIZE) #define YSM_SHA1_HASH_SIZE 20 // 160-bit SHA1 hash size #define YSM_CTR_DRBG_SEED_SIZE 32 // Size of CTR-DRBG entropy #define YSM_MAX_PKT_SIZE 0x60 // Max size of a packet (excluding command byte) #define YSM_PROTOCOL_VERSION 1 // Protocol version for this file #define YSM_TEMP_KEY_HANDLE 0xffffffff // Phantom temporary key handle // 22-bytes Yubikey secrets block typedef struct { uint8_t key[KEY_SIZE]; // AES key uint8_t uid[UID_SIZE]; // Unique (secret) ID } YSM_YUBIKEY_SECRETS; // AES CCM nonce typedef struct { uint32_t keyHandle; // Key handle uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce / public id } YSM_CCM_NONCE; // Up- and downlink packet typedef struct { uint8_t bcnt; // Number of bytes (cmd + payload) uint8_t cmd; // YSM_xxx command uint8_t payload[YSM_MAX_PKT_SIZE]; // Payload } YSM_PKT; // HSM commands #define YSM_RESPONSE 0x80 // Response bit // Status codes typedef uint8_t YSM_STATUS; #define YSM_STATUS_OK 0x80 // Executed successfully #define YSM_KEY_HANDLE_INVALID 0x81 // Key handle is invalid #define YSM_AEAD_INVALID 0x82 // Supplied AEAD block is invalid #define YSM_OTP_INVALID 0x83 // Supplied OTP is invalid (CRC or UID) #define YSM_OTP_REPLAY 0x84 // Supplied OTP is replayed #define YSM_ID_DUPLICATE 0x85 // The supplied public ID is already in the database #define YSM_ID_NOT_FOUND 0x86 // The supplied public ID was not found in the database #define YSM_DB_FULL 0x87 // The database storage is full #define YSM_MEMORY_ERROR 0x88 // Memory read/write error #define YSM_FUNCTION_DISABLED 0x89 // Function disabled via attribute(s) #define YSM_KEY_STORAGE_LOCKED 0x8a // Key storage locked #define YSM_MISMATCH 0x8b // Verification mismatch #define YSM_INVALID_PARAMETER 0x8c // Invalid parameter #define YSM_CUSTOM_STATUS_FIRST 0xf0 // Start custom status codes #define YSM_CUSTOM_STATUS_LAST 0xff // Start custom status codes //////////////////////////////////// // NULL / resync command //////////////////////////////////// #define YSM_NULL 0x00 typedef struct { uint8_t allNull[YSM_MAX_PKT_SIZE - 1]; // Set all to NULL when sending resync } YSM_RESYNC_REQ; //////////////////////////////////// // Generate AEAD block from data for a specific key //////////////////////////////////// #define YSM_AEAD_GENERATE 0x01 typedef struct { uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce (publicId for Yubikey AEADs) uint32_t keyHandle; // Key handle uint8_t numBytes; // Number of data bytes uint8_t data[YSM_DATA_BUF_SIZE]; // Data } YSM_AEAD_GENERATE_REQ; #define YSM_AEAD_GENERATED (YSM_AEAD_GENERATE | YSM_RESPONSE) typedef struct { uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce (publicId for Yubikey AEADs) uint32_t keyHandle; // Key handle YSM_STATUS status; // Status uint8_t numBytes; // Number of bytes in AEAD block uint8_t aead[YSM_AEAD_MAX_SIZE]; // AEAD block } YSM_AEAD_GENERATE_RESP; //////////////////////////////////// // Generate AEAD block of data buffer for a specific key //////////////////////////////////// #define YSM_BUFFER_AEAD_GENERATE 0x02 typedef struct { uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce (publicId for Yubikey AEADs) uint32_t keyHandle; // Key handle } YSM_BUFFER_AEAD_GENERATE_REQ; #define YSM_BUFFER_AEAD_GENERATED (YSM_BUFFER_AEAD_GENERATE | YSM_RESPONSE) typedef YSM_AEAD_GENERATE_RESP YSM_BUFFER_AEAD_GENERATE_RESP; //////////////////////////////////// // Generate random AEAD block in one stroke //////////////////////////////////// #define YSM_RANDOM_AEAD_GENERATE 0x03 typedef struct { uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce (publicId for Yubikey AEADs) uint32_t keyHandle; // Key handle uint8_t numBytes; // Number of bytes to randomize } YSM_RANDOM_AEAD_GENERATE_REQ; #define YSM_RANDOM_AEAD_GENERATED (YSM_RANDOM_AEAD_GENERATE | YSM_RESPONSE) typedef YSM_AEAD_GENERATE_RESP YSM_RANDOM_AEAD_GENERATE_RESP; //////////////////////////////////// // Decrypt AEAD block and compare with plaintext //////////////////////////////////// #define YSM_AEAD_DECRYPT_CMP 0x04 typedef struct { uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce (publicId for Yubikey AEADs) uint32_t keyHandle; // Key handle uint8_t numBytes; // Number of data bytes (cleartext + aead) uint8_t data[YSM_MAX_PKT_SIZE - 0x10]; // Data (cleartext + aead). Empty cleartext validates aead only } YSM_AEAD_DECRYPT_CMP_REQ; #define YSM_AEAD_DECRYPT_CMPD (YSM_AEAD_DECRYPT_CMP | YSM_RESPONSE) typedef struct { uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce (publicId for Yubikey AEADs) uint32_t keyHandle; // Key handle YSM_STATUS status; // Status } YSM_AEAD_DECRYPT_CMP_RESP; //////////////////////////////////// // Store Yubikey specific AEAD block in internal store (nonce == public id) //////////////////////////////////// #define YSM_DB_YUBIKEY_AEAD_STORE 0x05 typedef struct { uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id (and nonce in this case) uint32_t keyHandle; // Key handle uint8_t aead[YSM_YUBIKEY_AEAD_SIZE]; // AEAD block } YSM_DB_YUBIKEY_AEAD_STORE_REQ; #define YSM_DB_YUBIKEY_AEAD_STORED (YSM_DB_YUBIKEY_AEAD_STORE | YSM_RESPONSE) typedef struct { uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id (nonce) uint32_t keyHandle; // Key handle YSM_STATUS status; // Validation status } YSM_DB_YUBIKEY_AEAD_STORE_RESP; //////////////////////////////////// // Decode Yubico OTP using supplied AEAD block //////////////////////////////////// #define YSM_AEAD_YUBIKEY_OTP_DECODE 0x06 typedef struct { uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id (nonce) uint32_t keyHandle; // Key handle uint8_t otp[YSM_OTP_SIZE]; // OTP uint8_t aead[YSM_YUBIKEY_AEAD_SIZE]; // AEAD block } YSM_AEAD_YUBIKEY_OTP_DECODE_REQ; #define YSM_AEAD_YUBIKEY_OTP_DECODED (YSM_AEAD_YUBIKEY_OTP_DECODE | YSM_RESPONSE) typedef struct { uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id (nonce) uint32_t keyHandle; // Key handle uint16_t useCtr; // Use counter uint8_t sessionCtr; // Session counter uint8_t tstph; // Timestamp (high part) uint16_t tstpl; // Timestamp (low part) YSM_STATUS status; // Validation status } YSM_AEAD_YUBIKEY_OTP_DECODE_RESP; //////////////////////////////////// // Validate OTP using interal store //////////////////////////////////// #define YSM_DB_YUBIKEY_OTP_VALIDATE 0x07 typedef struct { uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id uint8_t otp[YSM_OTP_SIZE]; // OTP } YSM_DB_YUBIKEY_OTP_VALIDATE_REQ; #define YSM_DB_YUBIKEY_OTP_VALIDATED (YSM_DB_YUBIKEY_OTP_VALIDATE | YSM_RESPONSE) typedef struct { uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id uint16_t useCtr; // Use counter uint8_t sessionCtr; // Session counter uint8_t tstph; // Timestamp (high part) uint16_t tstpl; // Timestamp (low part) YSM_STATUS status; // Validation status } YSM_DB_YUBIKEY_OTP_VALIDATE_RESP; //////////////////////////////////// // Store Yubikey specific AEAD block in internal store (nonce != public id) //////////////////////////////////// #define YSM_DB_YUBIKEY_AEAD_STORE2 0x08 typedef struct { uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id uint32_t keyHandle; // Key handle uint8_t aead[YSM_YUBIKEY_AEAD_SIZE]; // AEAD block uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce } YSM_DB_YUBIKEY_AEAD_STORE2_REQ; #define YSM_DB_YUBIKEY_AEAD_STORED2 (YSM_DB_YUBIKEY_AEAD_STORE2 | YSM_RESPONSE) typedef struct { uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id uint32_t keyHandle; // Key handle YSM_STATUS status; // Validation status } YSM_DB_YUBIKEY_AEAD_STORE2_RESP; //////////////////////////////////// // AES ECB block encrypt request //////////////////////////////////// #define YSM_AES_ECB_BLOCK_ENCRYPT 0x0d typedef struct { uint32_t keyHandle; // Key handle uint8_t plaintext[YSM_BLOCK_SIZE]; // Plaintext block } YSM_AES_ECB_BLOCK_ENCRYPT_REQ; #define YSM_AES_ECB_BLOCK_ENCRYPTED (YSM_AES_ECB_BLOCK_ENCRYPT | YSM_RESPONSE) typedef struct { uint32_t keyHandle; // Key handle uint8_t ciphertext[YSM_BLOCK_SIZE]; // Ciphertext block YSM_STATUS status; // Encryption status } YSM_AES_ECB_BLOCK_ENCRYPT_RESP; //////////////////////////////////// // ECB block decrypt request //////////////////////////////////// #define YSM_AES_ECB_BLOCK_DECRYPT 0x0e typedef struct { uint32_t keyHandle; // Key handle uint8_t ciphertext[YSM_BLOCK_SIZE]; // Ciphertext block } YSM_AES_ECB_BLOCK_DECRYPT_REQ; #define YSM_AES_ECB_BLOCK_DECRYPTED (YSM_AES_ECB_BLOCK_DECRYPT | YSM_RESPONSE) typedef struct { uint32_t keyHandle; // Key handle uint8_t plaintext[YSM_BLOCK_SIZE]; // Plaintext block YSM_STATUS status; // Decryption status } YSM_AES_ECB_BLOCK_DECRYPT_RESP; //////////////////////////////////// // ECB block decrypt and verify request //////////////////////////////////// #define YSM_AES_ECB_BLOCK_DECRYPT_CMP 0x0f typedef struct { uint32_t keyHandle; // Key handle uint8_t ciphertext[YSM_BLOCK_SIZE]; // Ciphertext block uint8_t plaintext[YSM_BLOCK_SIZE]; // Plaintext block } YSM_AES_ECB_BLOCK_DECRYPT_CMP_REQ; #define YSM_AES_ECB_BLOCK_DECRYPT_CMPD (YSM_AES_ECB_BLOCK_DECRYPT_CMP | YSM_RESPONSE) typedef struct { uint32_t keyHandle; // Key handle YSM_STATUS status; // Decryption + verification status } YSM_AES_ECB_BLOCK_DECRYPT_CMP_RESP; //////////////////////////////////// // HMAC-SHA1 data input //////////////////////////////////// #define YSM_HMAC_SHA1_GENERATE 0x10 #define YSM_HMAC_SHA1_RESET 0x01 // Flag to indicate reset at first packet #define YSM_HMAC_SHA1_FINAL 0x02 // Flag to indicate that the hash shall be calculated #define YSM_HMAC_SHA1_TO_BUFFER 0x04 // Flag to transfer HMAC to buffer typedef struct { uint32_t keyHandle; // Key handle uint8_t flags; // Flags uint8_t numBytes; // Number of bytes in data packet uint8_t data[YSM_MAX_PKT_SIZE - 6]; // Data to be written } YSM_HMAC_SHA1_GENERATE_REQ; #define YSM_HMAC_SHA1_GENERATED (YSM_HMAC_SHA1_GENERATE | YSM_RESPONSE) typedef struct { uint32_t keyHandle; // Key handle YSM_STATUS status; // Status uint8_t numBytes; // Number of bytes in hash output uint8_t hash[YSM_SHA1_HASH_SIZE]; // Hash output (if applicable) } YSM_HMAC_SHA1_GENERATE_RESP; //////////////////////////////////// // Load temporary key from AEAD input //////////////////////////////////// #define YSM_TEMP_KEY_LOAD 0x11 typedef struct { uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce uint32_t keyHandle; // Key handle to unlock AEAD uint8_t numBytes; // Number of bytes (explicit key size 16, 20, 24 or 32 bytes + flags + MAC) uint8_t aead[YSM_MAX_KEY_SIZE + sizeof(uint32_t) + YSM_AEAD_MAC_SIZE]; // AEAD block (including flags) } YSM_TEMP_KEY_LOAD_REQ; #define YSM_TEMP_KEY_LOADED (YSM_TEMP_KEY_LOAD | YSM_RESPONSE) typedef struct { uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce uint32_t keyHandle; // Key handle YSM_STATUS status; // Status } YSM_TEMP_KEY_LOAD_RESP; //////////////////////////////////// // Load data buffer with data //////////////////////////////////// #define YSM_BUFFER_LOAD 0x20 typedef struct { uint8_t offs; // Offset in buffer. Zero flushes/resets buffer first uint8_t numBytes; // Number of bytes to load uint8_t data[YSM_DATA_BUF_SIZE]; // Data to load } YSM_BUFFER_LOAD_REQ; #define YSM_BUFFER_LOADED (YSM_BUFFER_LOAD | YSM_RESPONSE) typedef struct { uint8_t numBytes; // Number of bytes in buffer now } YSM_BUFFER_LOAD_RESP; //////////////////////////////////// // Load data buffer with random data //////////////////////////////////// #define YSM_BUFFER_RANDOM_LOAD 0x21 typedef struct { uint8_t offs; // Offset in buffer. Zero flushes/resets buffer first uint8_t numBytes; // Number of bytes to randomize } YSM_BUFFER_RANDOM_LOAD_REQ; #define YSM_BUFFER_RANDOM_LOADED (YSM_BUFFER_RANDOM_LOAD | YSM_RESPONSE) typedef struct { uint8_t numBytes; // Number of bytes in buffer now } YSM_BUFFER_RANDOM_LOAD_RESP; //////////////////////////////////// // Get new unique nonce //////////////////////////////////// #define YSM_NONCE_GET 0x22 typedef struct { uint16_t postIncrement; // Step in post increment } YSM_NONCE_GET_REQ; #define YSM_NONCE_GOT (YSM_NONCE_GET | YSM_RESPONSE) typedef struct { YSM_STATUS status; // Status uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce } YSM_NONCE_GET_RESP; //////////////////////////////////// // Echo (loopback test) //////////////////////////////////// #define YSM_ECHO 0x23 typedef struct { uint8_t numBytes; // Number of bytes in data field uint8_t data[YSM_MAX_PKT_SIZE - 1]; // Data } YSM_ECHO_REQ; #define YSM_ECHOED (YSM_ECHO | YSM_RESPONSE) typedef struct { uint8_t numBytes; // Number of bytes in data field uint8_t data[YSM_MAX_PKT_SIZE - 1]; // Data } YSM_ECHO_RESP; //////////////////////////////////// // Generate random number block(s) //////////////////////////////////// #define YSM_RANDOM_GENERATE 0x24 typedef struct { uint8_t numBytes; // Number of bytes to generate } YSM_RANDOM_GENERATE_REQ; #define YSM_RANDOM_GENERATED (YSM_RANDOM_GENERATE | YSM_RESPONSE) typedef struct { uint8_t numBytes; // Number of bytes generated uint8_t rnd[YSM_MAX_PKT_SIZE - 1]; // Random data } YSM_RANDOM_GENERATE_RESP; //////////////////////////////////// // Re-seed CTR-DRBG //////////////////////////////////// #define YSM_RANDOM_RESEED 0x25 typedef struct { uint8_t seed[YSM_CTR_DRBG_SEED_SIZE]; // New seed } YSM_RANDOM_RESEED_REQ; #define YSM_RANDOM_RESEEDED (YSM_RANDOM_RESEED | YSM_RESPONSE) typedef struct { YSM_STATUS status; // Status } YSM_RANDOM_RESEED_RESP; //////////////////////////////////// // System information query //////////////////////////////////// #define YSM_SYSTEM_INFO_QUERY 0x26 #define YSM_SYSTEM_INFO (YSM_SYSTEM_INFO_QUERY | YSM_RESPONSE) #define YSM_SYSTEM_UID_SIZE 12 // Sizeof unique identifier typedef struct { uint8_t versionMajor; // Major version # uint8_t versionMinor; // Minor version # uint8_t versionBuild; // Build version # uint8_t protocolVersion; // Protocol version # uint8_t systemUid[YSM_SYSTEM_UID_SIZE]; // System unique identifier } YSM_SYSTEM_INFO_RESP; //////////////////////////////////// // Unlock key handle based operations (version 0.x) //////////////////////////////////// #define YSM_KEY_STORAGE_UNLOCK 0x27 typedef struct { uint8_t password[YSM_BLOCK_SIZE]; // Unlock password } YSM_KEY_STORAGE_UNLOCK_REQ; #define YSM_KEY_STORAGE_UNLOCKED (YSM_KEY_STORAGE_UNLOCK | YSM_RESPONSE) typedef struct { YSM_STATUS status; // Unlock status } YSM_KEY_STORAGE_UNLOCK_RESP; //////////////////////////////////// // Unlock HSM mode of operation (version 1.x) //////////////////////////////////// #define YSM_HSM_UNLOCK 0x28 typedef struct { uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id uint8_t otp[YSM_OTP_SIZE]; // OTP } YSM_HSM_UNLOCK_REQ; #define YSM_HSM_UNLOCKED (YSM_HSM_UNLOCK | YSM_RESPONSE) typedef struct { YSM_STATUS status; // Unlock status } YSM_HSM_UNLOCK_RESP; //////////////////////////////////// // Decrypt key store //////////////////////////////////// #define YSM_KEY_STORE_DECRYPT 0x29 typedef struct { uint8_t key[YSM_MAX_KEY_SIZE]; // Key store decryption key } YSM_KEY_STORE_DECRYPT_REQ; #define YSM_KEY_STORE_DECRYPTED (YSM_KEY_STORE_DECRYPT | YSM_RESPONSE) typedef struct { YSM_STATUS status; // Unlock status } YSM_KEY_STORE_DECRYPT_RESP; //////////////////////////////////// // Exit HSM mode (debug mode only) //////////////////////////////////// #define YSM_MONITOR_EXIT 0x7f // Exit to monitor (no response sent) #define YSM_MONITOR_EXIT_MAGIC 0xbaadbeef typedef struct { uint32_t magic; // Magic number for trigger uint32_t magicInv; // 1st complement of magic } YSM_MONITOR_EXIT_REQ; #define YSM_EXTENDED_CMD_FIRST 0x60 #define YSM_EXTENDED_CMD_LAST 0x6f #endif // __YUBIHSM_H_INCLUDED pyhsm-1.2.0/doc/README0000664000175000017500000000064213002424553014151 0ustar daindain00000000000000This is the documentation directory of pyhsm. ---- API documentation ---- You can generate HTML API documentation from the source code by using epydoc. See the epydoc documentation for instructions on doing so. ---- Low level documentation ---- The reference manual for the YubiHSM is available from https://www.yubico.com/yubihsm/ The file doc/YubiHSM_if.h details all the available commands of the YubiHSM. pyhsm-1.2.0/doc/YubiKey_KSM.adoc0000664000175000017500000001207413002140277016214 0ustar daindain00000000000000== YubiKey Key Storage Module using the YubiHSM === Introduction The YubiCloud architecture separates the online validation servers where queries are sent from the place where the actual secret YubiKey AES keys are stored. The KSM decrypts the YubiKey OTP using the AES key identified by the "public id" part of the OTP, and return the counter values of the OTP to the querying validation server, which decides if the OTP is valid or not. The application "yhsm-yubikey-ksm" bundled with pyhsm is a KSM backend using the YubiHSM to further protect the AES keys. The interface exposed to the validation servers is a simple REST web interface, currently implemented using the BaseHTTPServer. It is inter- changeable with the original PHP based KSM found at https://developers.yubico.com/yubikey-ksm/ [WARNING] The server is currently single threaded, and uses a short timeout (5 seconds by default) to prevent blocking on bad requests (typically network problems between a validation server and a KSM). [NOTE] .Key handle You need to configure the YubiHSM connected to the KSM server with at least one key handle that has the YSM_AEAD_OTP_DECODE permission flag set. It is preferable to use a separate server (and YubiHSM) to create AEAD:s, in which case the other YubiHSM will have the same key handle with the same secret key, but with different permission flags (the appropriate YSM AEAD generate flags for the type of keys you use). === Installation on Debian/Ubuntu You can install the yhsm-yubikey-ksm from the package repositories : [source, sh] ---- $ sudo apt-get install yhsm-yubikey-ksm $ sudo $EDITOR /etc/default/yhsm-yubikey-ksm ---- === Non-Debian/Ubuntu installation instructions . Install pyhsm (see README in top level of pyhsm) . Install yhsm-yubikey-ksm into a suitable directory such as /usr/local/sbin/ . Run yhsm-yubikey-ksm --help to see what options are available. You will need to supply *--key-handles*, and possibly other parameters (verify --output-dir for example). === The AEAD files yhsm-yubikey-ksm is supposed to work with at least a couple of million YubiKeys (although the use of BaseHTTPServer might prove to be a bit too simplistic for a validation service of that magnitude, since it is not threaded). To keep things (such as adding new keys to a non-stop service) simple, and thereby hopefully robust, the initial scheme is to use the filesystem as database. We store the secrets of every YubiKey (22 bytes) in a separate file. The secrets are randomized and encrypted by a YubiHSM (preferably a separate one at a key provisioning site) so that they are protected from attackers even if they were to gain administrative access to the server/servers with YubiHSM's attached to them. The YubiHSM adds a MAC of 8 bytes to the 22 bytes secret data of the YubiKey, resulting in an AEAD of 30 bytes. This data is opaque by nature to everyone but the YubiHSM, since it is the only one with the key to decrypt the AEAD. Most filesystems start under-performing if you put a million files in a single directory. To avoid that, the AEAD file is hashed into a directory for each octet but the last one in the public ID. This ensures there will never be more than 256 AEAD files in each directory : .... public id key handle AEAD file ccbbddeeffcc 0x20 /var/cache/yubikey-ksm/aeads/0x20/cc/bb/dd/ee/ff/ccbbddeeffcc .... An important note for storing large numbers of AEAD files in a filesystem is that it will use up large numbers of inodes. Consideration for this should be taken into account when setting up the filesystem. === Importing YubiKey secrets If you had a YK-KSM server before getting a YubiHSM, use the export utility to export all the secrets into a text file of this format : .... # ykksm 1 123456,ftftftcccccc,534543524554,fcacd309a20ce1809c2db257f0e8d6ea,000000000000,,, ... .... and then use the import utility /usr/sbin/yhsm-import-keys to create AEAD's for all YubiKey's listed in the text file. === Running without a hardware YubiHSM _Available in pyhsm 1.1.0 and later._ It's also possible to run the yubikey-ksm server without a physical YubiHSM. This can be useful for testing or staging purposes, or other situations where is it inpractical or infeasable to use a real YubiHSM. NOTE: When running completely in software the AES keys will be stored in a file on disk, and can be compromised in the event of an attack. To run without a physical YubiHSM, specify a properly formatted JSON file as the device when invoking the yhsm-yubikey-ksm executable (this also works for yhsm-import-keys as well as yhsm-generate-keys). The file should contain the numeric key-handles associated with the corresponding AES keys, as follows: .... { "1": "0000000100020003000400050006000700080009000a000b000c000d000e000f", "2": "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff" } .... Keys must be strings that parse into integers corresponding with the numeric key handles used in the YubiHSM, and values must be the hex representation of the AES key for each key handle. For example: [source, sh] ---- $ yhsm-yubikey-ksm -D /path/to/my/secrets.json [other args here...] ---- pyhsm-1.2.0/pyhsm/0000775000175000017500000000000013006370267013670 5ustar daindain00000000000000pyhsm-1.2.0/pyhsm/debug_cmd.py0000664000175000017500000000175113002140277016147 0ustar daindain00000000000000""" implementations of debugging commands to execute on a YubiHSM """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import struct __all__ = [ # constants # functions # classes 'YHSM_Cmd_Monitor_Exit', ] import pyhsm.defines from pyhsm.cmd import YHSM_Cmd class YHSM_Cmd_Monitor_Exit(YHSM_Cmd): """ Send magics to YubiHSM in debug mode, and get it to exit to configuration mode again. """ def __init__(self, stick, payload=''): #define YHSM_MONITOR_EXIT 0x7f // Exit to monitor (no response sent) #define YHSM_MONITOR_EXIT_MAGIC 0xbaadbeef # typedef struct { # uint32_t magic; // Magic number for trigger # uint32_t magicInv; // 1st complement of magic # } YHSM_MONITOR_EXIT_REQ; packed = struct.pack(' .*', this): raise pyhsm.exception.YHSM_Error('YubiHSM is in configuration mode') raise pyhsm.exception.YHSM_Error('Unknown response from serial device %s : "%s"' \ % (self.stick.device, res.encode('hex'))) def parse_result(self, data): """ This function is intended to be overridden by sub-classes that implements commands that should not just return the data read from the YubiHSM. """ return data def reset(stick): """ Send a bunch of zero-bytes to the YubiHSM, and flush the input buffer. """ nulls = (pyhsm.defines.YSM_MAX_PKT_SIZE - 1) * '\x00' res = YHSM_Cmd(stick, pyhsm.defines.YSM_NULL, payload = nulls).execute(read_response = False) unlock = stick.acquire() try: stick.drain() stick.flush() finally: unlock() return res == 0 pyhsm-1.2.0/pyhsm/ksm/0000775000175000017500000000000013006370267014462 5ustar daindain00000000000000pyhsm-1.2.0/pyhsm/ksm/db_import.py0000664000175000017500000000602013005606114017001 0ustar daindain00000000000000# # Copyright (c) 2013-2014 Yubico AB # See the file COPYING for licence statement. # """ Import AEADs to database. """ import os import re import sys import argparse import sqlalchemy from pyhsm.util import key_handle_to_int import pyhsm.aead_cmd def extract_keyhandle(path, filepath): """extract keyhandle value from the path""" keyhandle = filepath.lstrip(path) keyhandle = keyhandle.split("/") return keyhandle[0] def insert_query(connection, publicId, aead, keyhandle, aeadobj): """this functions read the response fields and creates sql query. then inserts everything inside the database""" # turn the keyhandle into an integer keyhandle = key_handle_to_int(keyhandle) if not keyhandle == aead.key_handle: print("WARNING: keyhandle does not match aead.key_handle") return None # creates the query object try: sql = aeadobj.insert().values(public_id=publicId, keyhandle=aead.key_handle, nonce=aead.nonce, aead=aead.data) # insert the query result = connection.execute(sql) return result except sqlalchemy.exc.IntegrityError: pass return None def main(): parser = argparse.ArgumentParser(description='Import AEADs into the database') parser.add_argument('path', help='filesystem path of where to find AEADs') parser.add_argument('dburl', help='connection URL for the database') args = parser.parse_args() path = args.path databaseUrl = args.dburl if not os.path.isdir(path): print("\nInvalid path, check your spelling.\n") return 2 try: engine = sqlalchemy.create_engine(databaseUrl) #SQLAlchemy voodoo metadata = sqlalchemy.MetaData() aeadobj = sqlalchemy.Table('aead_table', metadata, autoload=True, autoload_with=engine) connection = engine.connect() except: print("FATAL: Database connect failure") return 1 for root, subFolders, files in os.walk(path): if files: if not re.match(r'^[cbdefghijklnrtuv]+$', files[0]): continue #build file path filepath = os.path.join(root, files[0]) #extract the key handle from the path keyhandle = extract_keyhandle(path, filepath) kh_int = pyhsm.util.key_handle_to_int(keyhandle) #instantiate a new aead object aead = pyhsm.aead_cmd.YHSM_GeneratedAEAD(None, kh_int, '') aead.load(filepath) #set the public_id public_id = str(files[0]) #check it is old format aead if not aead.nonce: #configure values for oldformat aead.nonce = pyhsm.yubikey.modhex_decode(public_id).decode('hex') aead.key_handle = key_handle_to_int(keyhandle) if not insert_query(connection, public_id, aead, keyhandle, aeadobj): print("WARNING: could not insert %s" % public_id) #close sqlalchemy connection.close() if __name__ == '__main__': sys.exit(main()) pyhsm-1.2.0/pyhsm/ksm/__init__.py0000664000175000017500000000000013005606114016551 0ustar daindain00000000000000pyhsm-1.2.0/pyhsm/ksm/import_keys.py0000664000175000017500000002064513006106152017376 0ustar daindain00000000000000# """ Tool to import YubiKey secrets to YubiHSM. The default mode is to turn each YubiKey secret into an AEAD (Authenticated Encryption with Associated Data) block that is stored in a file on the host computer (one file per YubiKey). This enables validation of virtually unlimited numbers of YubiKey's OTPs. If --internal-db is used, the YubiKey secret will be stored inside the YubiHSM, and complete validation (including counter management) will be done inside the YubiHSM. The internal database is currently limited to 1024 entries. The input is supposed to be a comma-separated list of entries like this # ykksm 1 123456,ftftftcccc,534543524554,fcacd309a20ce1809c2db257f0e8d6ea,000000000000,,, (seqno, public id, private uid, AES key, dunno,,,) This is also the format of a database export from a traditional YK-KSM. """ # # Copyright (c) 2011, 2012 Yubico AB # See the file COPYING for licence statement. # import os import sys import argparse import pyhsm import pyhsm.yubikey from pyhsm.soft_hsm import SoftYHSM default_device = "/dev/ttyACM0" default_dir = "/var/cache/yubikey-ksm/aeads" default_public_id_chars = 12 def parse_args(): """ Parse the command line arguments """ parser = argparse.ArgumentParser( description="Import existing secrets to YubiHSM eco system", add_help=True, formatter_class=argparse.ArgumentDefaultsHelpFormatter,) parser.add_argument('-D', '--device', dest='device', default=default_device, required=False, help='YubiHSM device', ) parser.add_argument('-O', '--output-dir', '--aead-dir', dest='output_dir', default=default_dir, required=False, help='Output directory (AEAD base dir)', metavar='DIR', ) parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Enable verbose operation', ) parser.add_argument('--debug', dest='debug', action='store_true', default=False, help='Enable debug operation', ) parser.add_argument('--public-id-chars', dest='public_id_chars', type=int, default=default_public_id_chars, required=False, help='Number of chars in generated public ids', metavar='NUM', ) parser.add_argument( '--key-handles', dest='key_handles', nargs='+', required=True, help='Key handle(s) to encrypt the imported secrets with', metavar='HANDLE', ) parser.add_argument('--internal-db', dest='internal_db', action='store_true', default=False, help='Store key in YubiHSM internal database', ) parser.add_argument( '--aes-key', dest='aes_key', required=False, default=None, help='AES key to use when generating AEADs (no YubiHSM)', metavar='HEXSTR', ) parser.add_argument('--random-nonce', dest='random_nonce', action='store_true', default=False, help='Let the HSM generate random nonce', ) args = parser.parse_args() if args.internal_db: if len(args.key_handles) != 1: sys.stderr.write("--internal-db requires exactly one key handle.\n") sys.exit(1) if args.aes_key: sys.stderr.write("--internal-db incompatible with --aes-key.\n") sys.exit(1) return args def args_fixup(args): if not args.internal_db and not os.path.isdir(args.output_dir): sys.stderr.write( "Output directory '%s' does not exist.\n" % (args.output_dir)) sys.exit(1) if args.aes_key: args.aes_key = args.aes_key.decode('hex') keyhandles_fixup(args) def keyhandles_fixup(args): """ Walk through the supplied key handles and normalize them, while keeping the input format too (as value in a dictionary). The input format is used in AEAD filename paths. """ new_handles = {} for val in args.key_handles: n = pyhsm.util.key_handle_to_int(val) new_handles[n] = val args.key_handles = new_handles def import_keys(hsm, args): """ The main stdin iteration loop. """ res = True # ykksm 1 #123456,ftftftcccc,534543524554,fcacd309a20ce1809c2db257f0e8d6ea,000000000000,,, for line in sys.stdin: if line[0] == '#': continue l = line.split(',') modhex_id = l[1] uid = l[2].decode('hex') key = l[3].decode('hex') if modhex_id and uid and key: public_id = pyhsm.yubikey.modhex_decode(modhex_id) padded_id = modhex_id.rjust(args.public_id_chars, 'c') if int(public_id, 16) == 0: print "WARNING: Skipping import of key with public ID: %s" % (padded_id) print "This public ID is unsupported by the YubiHSM.\n" continue if args.verbose: print " %s" % (padded_id) secret = pyhsm.aead_cmd.YHSM_YubiKeySecret(key, uid) hsm.load_secret(secret) for kh in args.key_handles.keys(): if(args.random_nonce): nonce = "" else: nonce = public_id.decode('hex') aead = hsm.generate_aead(nonce, kh) if args.internal_db: if not store_in_internal_db( args, hsm, modhex_id, public_id, kh, aead): res = False continue filename = output_filename( args.output_dir, args.key_handles[kh], padded_id) if args.verbose: print " %4s, %i bytes (%s) -> %s" % \ (args.key_handles[kh], len(aead.data), shorten_aead(aead), filename) aead.save(filename) if args.verbose: print "" if res: print "\nDone\n" else: print "\nDone (one or more entries rejected)" return res def store_in_internal_db(args, hsm, modhex_id, public_id, kh, aead): """ Store record (AEAD) in YubiHSM internal DB """ if args.verbose: print " %i bytes (%s) -> internal db..." % \ (len(aead.data), shorten_aead(aead)), try: hsm.db_store_yubikey(public_id.decode('hex'), kh, aead) if args.verbose: print "OK" except pyhsm.exception.YHSM_CommandFailed as e: if args.verbose: print "%s" % (pyhsm.defines.status2str(e.status)) else: print "Storing ID %s FAILED: %s" % (modhex_id, pyhsm.defines.status2str(e.status)) return False return True def shorten_aead(aead): """ Produce pretty-printable version of long AEAD. """ head = aead.data[:4].encode('hex') tail = aead.data[-4:].encode('hex') return "%s...%s" % (head, tail) def output_filename(output_dir, key_handle, public_id): """ Return an output filename for a generated AEAD. Creates a hashed directory structure using the last three bytes of the public id to get equal usage. """ parts = [output_dir, key_handle] + pyhsm.util.group(public_id, 2) path = os.path.join(*parts) if not os.path.isdir(path): os.makedirs(path) return os.path.join(path, public_id) def main(): args = parse_args() args_fixup(args) if sys.stdin.readline() != "# ykksm 1\n": sys.stderr.write( "Did not get '# ykksm 1' header as first line of input.\n") sys.exit(1) print "output dir : %s" % (args.output_dir) print "key handles : %s" % (args.key_handles) print "YHSM device : %s" % (args.device) print "" if args.aes_key: keys = {kh: args.aes_key for kh in args.key_handles} hsm = SoftYHSM(keys, args.debug) elif os.path.isfile(args.device): hsm = SoftYHSM.from_file(args.device, debug=args.debug) else: hsm = pyhsm.YHSM(device=args.device, debug=args.debug) return not import_keys(hsm, args) if __name__ == '__main__': sys.exit(main()) pyhsm-1.2.0/pyhsm/ksm/yubikey_ksm.py0000664000175000017500000004102313006337536017371 0ustar daindain00000000000000# # Copyright (c) 2011-2014 Yubico AB # See the file COPYING for licence statement. # """ Small network server decrypting YubiKey OTPs using an attached YubiHSM. To support unlimited numbers of YubiKeys, the YubiKey AES keys are stored in AEAD's (Authenticated Encryption with Associated Data) on the host computer. When an OTP is received, we find the right AEAD for this key (based on the public ID of the YubiKey), and then send the AEAD together with the OTP to the YubiHSM. The YubiHSM is able to decrypt the AEAD (if it has the appropriate key handle configured), and then able to decrypt the YubiKey OTP using the AES key stored in the AEAD. The information the YubiKey encrypted using it's AES key is then returned in clear text from the YubiHSM. This includes the counter information and also (relative) timestamps. It is not the job of the KSM (or YubiHSM) to ensure that the OTP has not been seen before - that is done by the validation server (using the database) : O +----------+ /|\ |Validation| +-----+ +---------+ | -- OTP--> | server | --> | KSM +---| YubiHSM | / \ +----------+ +-----+ +---------+ | user +--+--+ | DB | +-----+ """ import os import sys import BaseHTTPServer import socket import argparse import syslog import re import pyhsm import pyhsm.yubikey import serial import daemon import sqlalchemy from functools import partial from pyhsm.soft_hsm import SoftYHSM default_device = "/dev/ttyACM0" default_dir = "/var/cache/yubikey-ksm/aeads" default_serve_url = "/wsapi/decrypt?otp=" default_listen_addr = "127.0.0.1" default_port = 8002 default_reqtimeout = 5 default_pid_file = None default_db_url = None valid_input_from_key = re.compile('^[cbdefghijklnrtuv]{32,48}$') stats = { 'ok': 0, 'invalid': 0, 'no_aead': 0, 'err': 0 } context = daemon.DaemonContext() class YHSM_KSMRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): """ Handle a HTTP request. Try to be careful and validate the input, and then look for an AEAD file matching the public id of the OTP. If an AEAD for one of our key handles is found, we ask the YubiHSM to decrypt the OTP using the AEAD and return the result (counter and timestamp information). """ def __init__(self, hsm, aead_backend, args, *other_args, **kwargs): self.hsm = hsm self.verbose = args.debug or args.verbose self.serve_url = args.serve_url self.stats_url = args.stats_url self.key_handles = args.key_handles self.timeout = args.reqtimeout self.aead_backend = aead_backend BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *other_args, **kwargs) def do_GET(self): """ Handle a HTTP GET request. """ # Example session: # in : GET /wsapi/decrypt?otp=ftftftccccdvvbfcfduvvcubikngtchlubtutucrld HTTP/1.0 # out : OK counter=0004 low=f585 high=3e use=03 if self.path.startswith(self.serve_url): from_key = self.path[len(self.serve_url):] val_res = self.decrypt_yubikey_otp(from_key) self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(val_res) self.wfile.write("\n") elif self.stats_url and self.path == self.stats_url: self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() for key in stats: self.wfile.write("%s %d\n" % (key, stats[key])) else: self.log_error("Bad URL '%s' - I'm serving '%s' (responding 403)" % (self.path, self.serve_url)) self.send_response(403, 'Forbidden') self.end_headers() def decrypt_yubikey_otp(self, from_key): """ Try to decrypt a YubiKey OTP. Returns a string starting with either 'OK' or 'ERR' : 'OK counter=ab12 low=dd34 high=2a use=0a' 'ERR Unknown public_id' on YubiHSM errors (or bad OTP), only 'ERR' is returned. """ if not re.match(valid_input_from_key, from_key): self.log_error("IN: %s, Invalid OTP" % (from_key)) if self.stats_url: stats['invalid'] += 1 return "ERR Invalid OTP" public_id, _otp = pyhsm.yubikey.split_id_otp(from_key) try: aead = self.aead_backend.load_aead(public_id) except Exception as e: self.log_error(str(e)) if self.stats_url: stats['no_aead'] += 1 return "ERR Unknown public_id" try: res = pyhsm.yubikey.validate_yubikey_with_aead( self.hsm, from_key, aead, aead.key_handle) # XXX double-check public_id in res, in case BaseHTTPServer suddenly becomes multi-threaded # XXX fix use vs session counter confusion val_res = "OK counter=%04x low=%04x high=%02x use=%02x" % \ (res.use_ctr, res.ts_low, res.ts_high, res.session_ctr) if self.stats_url: stats['ok'] += 1 except pyhsm.exception.YHSM_Error as e: self.log_error ("IN: %s, Validate FAILED: %s" % (from_key, str(e))) val_res = "ERR" if self.stats_url: stats['err'] += 1 self.log_message("SUCCESS OTP %s PT hsm %s", from_key, val_res) return val_res def log_error(self, fmt, *fmt_args): """ Log to syslog. """ msg = self.my_address_string() + " - - " + fmt % fmt_args my_log_message(self.verbose, syslog.LOG_ERR, msg) def log_message(self, fmt, *fmt_args): """ Log to syslog. """ msg = self.my_address_string() + " - - " + fmt % fmt_args my_log_message(self.verbose, syslog.LOG_INFO, msg) def my_address_string(self): """ For logging client host without resolving. """ return getattr(self, 'client_address', ('', None))[0] class FSBackend(object): def __init__(self, aead_dir, key_handles): self.aead_dir = aead_dir self.key_handles = key_handles if not os.path.isdir(aead_dir): raise ValueError("AEAD directory '%s' does not exist." % aead_dir) def load_aead(self, public_id): fn_list = [] for kh, kh_int in self.key_handles: aead = pyhsm.aead_cmd.YHSM_GeneratedAEAD(None, kh_int, '') filename = aead_filename(self.aead_dir, kh, public_id) fn_list.append(filename) try: aead.load(filename) if not aead.nonce: aead.nonce = pyhsm.yubikey.modhex_decode(public_id).decode('hex') return aead except IOError: continue raise Exception("Attempted to load AEAD from : %s" % (fn_list)) class SQLBackend(object): def __init__(self, db_url): self.engine = sqlalchemy.create_engine(db_url) metadata = sqlalchemy.MetaData() self.aead_table = sqlalchemy.Table('aead_table', metadata, autoload=True, autoload_with=self.engine) def load_aead(self, public_id): """ Loads AEAD from the specified database. """ connection = self.engine.connect() trans = connection.begin() try: s = sqlalchemy.select([self.aead_table]).where(self.aead_table.c.public_id == public_id) result = connection.execute(s) for row in result: kh_int = row['keyhandle'] aead = pyhsm.aead_cmd.YHSM_GeneratedAEAD(None, kh_int, '') aead.data = row['aead'] aead.nonce = row['nonce'] return aead except Exception: trans.rollback() raise Exception("No AEAD in DB for public_id %s (%s)" % (public_id, str(e))) finally: connection.close() class YHSM_KSMServer(BaseHTTPServer.HTTPServer): """ Wrapper class to properly initialize address_family for IPv6 addresses. """ def __init__(self, server_address, req_handler): if ":" in server_address[0]: self.address_family = socket.AF_INET6 BaseHTTPServer.HTTPServer.__init__(self, server_address, req_handler) def aead_filename(aead_dir, key_handle, public_id): """ Return the filename of the AEAD for this public_id. """ parts = [aead_dir, key_handle] + pyhsm.util.group(public_id, 2) + [public_id] return os.path.join(*parts) def parse_args(): """ Parse the command line arguments """ parser = argparse.ArgumentParser(description="Decrypt YubiKey OTPs using YubiHSM", add_help=True, formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser.add_argument('-D', '--device', dest='device', default=default_device, required=False, help='YubiHSM device' ) parser.add_argument('-B', '--aead-dir', dest='aead_dir', default=default_dir, required=False, help='AEAD directory - base directory of your AEADs', metavar='DIR', ) parser.add_argument('-U', '--serve-url', dest='serve_url', default=default_serve_url, required=False, help='Base URL for decrypt web service', metavar='URL', ) parser.add_argument('-S', '--stats-url', dest='stats_url', required=False, help='URL where statistics can be retrieved', metavar='URL', ) parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Enable verbose operation' ) parser.add_argument('-d', '--daemon', dest='daemon', action='store_true', default=False, help='Run as daemon' ) parser.add_argument('--debug', dest='debug', action='store_true', default=False, help='Enable debug operation' ) parser.add_argument('--port', dest='listen_port', type=int, default=default_port, required=False, help='Port to listen on', metavar='PORT', ) parser.add_argument('--addr', dest='listen_addr', default=default_listen_addr, required=False, help='Address to bind to', metavar='ADDR', ) parser.add_argument('--reqtimeout', dest='reqtimeout', type=int, default=default_reqtimeout, required=False, help='Request timeout in seconds', metavar='SECONDS', ) parser.add_argument('--key-handle', '--key-handles', dest='key_handles', nargs='+', required=True, help='Key handle(s) to use to decrypt AEADs on the YHSM.', metavar='HANDLE', ) parser.add_argument('--pid-file', dest='pid_file', default=default_pid_file, required=False, help='PID file', metavar='FILENAME', ) parser.add_argument('--db-url', dest='db_url', default=default_db_url, required=False, help='The database url to read the AEADs from, you can ' 'also use env:YOURENVVAR to instead read the URL from ' 'the YOURENVVAR environment variable.', metavar='DBURL', ) return parser.parse_args() def args_fixup(args): """ Additional checks/cleanups of parsed arguments. """ # cache key_handle_to_int of all key handles, turning args.key_handles into # a list of tuples with both original value and integer res = [] for kh in args.key_handles: kh_int = pyhsm.util.key_handle_to_int(kh) res.append((kh, kh_int,)) args.key_handles = res # Check if the DB url should be read from an environment variable if args.db_url and args.db_url.startswith('env:'): env_var = args.db_url[4:] if env_var in os.environ: args.db_url = os.environ[env_var] def write_pid_file(fn): """ Create a file with our PID. """ if not fn: return None if fn == '' or fn == "''": # work around argument passings in init-scripts return None f = open(fn, "w") f.write("%s\n" % (os.getpid())) f.close() def run(hsm, aead_backend, args): """ Start a BaseHTTPServer.HTTPServer and serve requests forever. """ write_pid_file(args.pid_file) server_address = (args.listen_addr, args.listen_port) httpd = YHSM_KSMServer(server_address, partial(YHSM_KSMRequestHandler, hsm, aead_backend, args)) my_log_message(args.debug or args.verbose, syslog.LOG_INFO, "Serving requests to 'http://%s:%s%s' with key handle(s) %s (YubiHSM: '%s', AEADs in '%s', DB in '%s')" % (args.listen_addr, args.listen_port, args.serve_url, args.key_handles, args.device, args.aead_dir, args.db_url)) httpd.serve_forever() def my_log_message(verbose, prio, msg): """ Log to syslog, and possibly also to stderr. """ syslog.syslog(prio, msg) if verbose or prio == syslog.LOG_ERR: sys.stderr.write("%s\n" % (msg)) def main(): """ Main program. """ my_name = os.path.basename(sys.argv[0]) if not my_name: my_name = "yhsm-yubikey-ksm" syslog.openlog(my_name, syslog.LOG_PID, syslog.LOG_LOCAL0) args = parse_args() args_fixup(args) aead_backend = None if args.db_url: # Using an SQL database for AEADs try: aead_backend = SQLBackend(args.db_url) except Exception as e: my_log_message(args.debug or args.verbose, syslog.LOG_ERR, 'Could not connect to database "%s" : %s' % (args.db_url, e)) return 1 else: # Using the filesystem for AEADs try: aead_backend = FSBackend(args.aead_dir, args.key_handles) except Exception as e: my_log_message(args.debug or args.verbose, syslog.LOG_ERR, 'Could not create AEAD FSBackend: %s' % e) return 1 if args.device == '-': # Using a soft-HSM with keys from stdin try: hsm = SoftYHSM.from_json(sys.stdin.read(), debug=args.debug) except ValueError as e: my_log_message(args.debug or args.verbose, syslog.LOG_ERR, 'Failed opening soft YHSM from stdin : %s' % (e)) return 1 elif os.path.isfile(args.device): # Using a soft-HSM from file try: hsm = SoftYHSM.from_file(args.device, debug=args.debug) except ValueError as e: my_log_message(args.debug or args.verbose, syslog.LOG_ERR, 'Failed opening soft YHSM "%s" : %s' % (args.device, e)) return 1 else: # Using a real HSM try: hsm = pyhsm.YHSM(device=args.device, debug=args.debug) context.files_preserve = [hsm.get_raw_device()] except serial.SerialException as e: my_log_message(args.debug or args.verbose, syslog.LOG_ERR, 'Failed opening YubiHSM device "%s" : %s' % (args.device, e)) return 1 if args.daemon: with context: run(hsm, aead_backend, args) else: try: run(hsm, aead_backend, args) except KeyboardInterrupt: print "" print "Shutting down" print "" if __name__ == '__main__': sys.exit(main()) pyhsm-1.2.0/pyhsm/ksm/db_export.py0000664000175000017500000000445213005606114017017 0ustar daindain00000000000000# # Copyright (c) 2013-2014 Yubico AB # See the file COPYING for licence statement. # """ Export AEAD from database. """ import os import sys import errno import argparse import sqlalchemy import pyhsm.aead_cmd def insert_slash(string, every=2): """insert_slash insert / every 2 char""" return os.path.join(string[i:i+every] for i in xrange(0, len(string), every)) def mkdir_p(path): """mkdir -p: creates path like mkdir -p""" try: os.makedirs(path) except OSError as exc: if exc.errno == errno.EEXIST and os.path.isdir(path): pass else: raise def main(): parser = argparse.ArgumentParser(description='Import AEADs into the database') parser.add_argument('path', help='filesystem path of where to put AEADs') parser.add_argument('dburl', help='connection URL for the database') args = parser.parse_args() #set the path path = args.path if not os.path.isdir(path): print("\nInvalid path, make sure it exists.\n") return 2 #mysql url databaseUrl = args.dburl try: #check database connection engine = sqlalchemy.create_engine(databaseUrl) #SQLAlchemy voodoo metadata = sqlalchemy.MetaData() aeadobj = sqlalchemy.Table('aead_table', metadata, autoload=True, autoload_with=engine) connection = engine.connect() except: print("FATAL: Database connect failure") return 1 aead = None nonce = None key_handle = None aead = pyhsm.aead_cmd.YHSM_GeneratedAEAD(nonce, key_handle, aead) #get data from the database result = connection.execute("SELECT * from aead_table") #cycle through resutls for row in result: #read values row by row aead.data = row['aead'] publicId = row['public_id'] aead.key_handle = row['keyhandle'] aead.nonce = row['nonce'] aead_dir = os.path.join(path, str(hex(aead.key_handle)).rstrip('L'), insert_slash(publicId)) #sanitize path aead_dir = os.path.normpath(aead_dir) #create path mkdir_p(aead_dir) #write the file in the path pyhsm.aead_cmd.YHSM_GeneratedAEAD.save(aead, os.path.join(aead_dir, publicId)) #close connection connection.close() if __name__ == '__main__': sys.exit(main()) pyhsm-1.2.0/pyhsm/db_cmd.py0000664000175000017500000001350213002140277015443 0ustar daindain00000000000000""" implementations of internal DB commands for YubiHSM """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import struct __all__ = [ # constants # functions # classes 'YHSM_Cmd_DB_YubiKey_Store', 'YHSM_Cmd_DB_Validate_OTP', ] import pyhsm.defines import pyhsm.exception import pyhsm.aead_cmd import pyhsm.validate_cmd from pyhsm.cmd import YHSM_Cmd class YHSM_Cmd_DB_YubiKey_Store(YHSM_Cmd): """ Ask YubiHSM to store data about a YubiKey in the internal database (not buffer). The input is an AEAD, perhaps previously created using generate_aead(). If the nonce for the AEAD is not the same as the public_id, specify it with the nonce keyword argument. This requires a YubiHSM >= 1.0.4. """ status = None def __init__(self, stick, public_id, key_handle, aead, nonce = None): self.key_handle = pyhsm.util.input_validate_key_handle(key_handle) self.public_id = pyhsm.util.input_validate_nonce(public_id, pad = True) aead = pyhsm.util.input_validate_aead(aead, expected_len = pyhsm.defines.YSM_YUBIKEY_AEAD_SIZE) if nonce is None: # typedef struct { # uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id (nonce) # uint32_t keyHandle; // Key handle # uint8_t aead[YSM_YUBIKEY_AEAD_SIZE]; // AEAD block # } YSM_DB_YUBIKEY_AEAD_STORE_REQ; fmt = "< %is I %is" % (pyhsm.defines.YSM_PUBLIC_ID_SIZE, \ pyhsm.defines.YSM_YUBIKEY_AEAD_SIZE) packed = struct.pack(fmt, self.public_id, self.key_handle, aead) YHSM_Cmd.__init__(self, stick, pyhsm.defines.YSM_DB_YUBIKEY_AEAD_STORE, packed) else: nonce = pyhsm.util.input_validate_nonce(nonce) # typedef struct { # uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id # uint32_t keyHandle; // Key handle # uint8_t aead[YSM_YUBIKEY_AEAD_SIZE]; // AEAD block # uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce # } YSM_DB_YUBIKEY_AEAD_STORE2_REQ; fmt = "< %is I %is %is" % (pyhsm.defines.YSM_PUBLIC_ID_SIZE, \ pyhsm.defines.YSM_YUBIKEY_AEAD_SIZE, \ pyhsm.defines.YSM_AEAD_NONCE_SIZE) packed = struct.pack(fmt, self.public_id, self.key_handle, aead, nonce) YHSM_Cmd.__init__(self, stick, pyhsm.defines.YSM_DB_YUBIKEY_AEAD_STORE2, packed) def parse_result(self, data): """ Return True if the AEAD was stored sucessfully. """ # typedef struct { # uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id (nonce) # uint32_t keyHandle; // Key handle # YSM_STATUS status; // Validation status # } YSM_DB_YUBIKEY_AEAD_STORE_RESP; public_id, \ key_handle, \ self.status = struct.unpack("< %is I B" % (pyhsm.defines.YSM_AEAD_NONCE_SIZE), data) pyhsm.util.validate_cmd_response_str('public_id', public_id, self.public_id) pyhsm.util.validate_cmd_response_hex('key_handle', key_handle, self.key_handle) if self.status == pyhsm.defines.YSM_STATUS_OK: return True else: raise pyhsm.exception.YHSM_CommandFailed(pyhsm.defines.cmd2str(self.command), self.status) class YHSM_Cmd_DB_Validate_OTP(YHSM_Cmd): """ Request the YubiHSM to validate an OTP for a YubiKey stored in the internal database. """ response = None status = None def __init__(self, stick, public_id, otp): self.public_id = pyhsm.util.input_validate_nonce(public_id, pad = True) self.otp = pyhsm.util.input_validate_str(otp, 'otp', exact_len = pyhsm.defines.YSM_OTP_SIZE) # typedef struct { # uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id # uint8_t otp[YSM_OTP_SIZE]; // OTP # } YSM_DB_OTP_VALIDATE_REQ; fmt = "%is %is" % (pyhsm.defines.YSM_AEAD_NONCE_SIZE, pyhsm.defines.YSM_OTP_SIZE) packed = struct.pack(fmt, self.public_id, self.otp) YHSM_Cmd.__init__(self, stick, pyhsm.defines.YSM_DB_OTP_VALIDATE, packed) def __repr__(self): if self.executed: return '<%s instance at %s: public_id=%s, status=0x%x>' % ( self.__class__.__name__, hex(id(self)), self.public_id.encode('hex'), self.status ) else: return '<%s instance at %s (not executed)>' % ( self.__class__.__name__, hex(id(self)) ) def parse_result(self, data): # typedef struct { # uint8_t public_id[YSM_PUBLIC_ID_SIZE]; // Public id # uint16_t use_ctr; // Use counter # uint8_t session_ctr; // Session counter # uint8_t tstph; // Timestamp (high part) # uint16_t tstpl; // Timestamp (low part) # YHSM_STATUS status; // Validation status # } YHSM_AEAD_OTP_DECODED_RESP; fmt = "%is H B B H B" % (pyhsm.defines.YSM_PUBLIC_ID_SIZE) public_id, \ use_ctr, \ session_ctr, \ ts_high, \ ts_low, \ self.status = struct.unpack(fmt, data) pyhsm.util.validate_cmd_response_str('public_id', public_id, self.public_id) if self.status == pyhsm.defines.YSM_STATUS_OK: self.response = pyhsm.validate_cmd.YHSM_ValidationResult( \ public_id, use_ctr, session_ctr, ts_high, ts_low) return self.response else: raise pyhsm.exception.YHSM_CommandFailed(pyhsm.defines.cmd2str(self.command), self.status) pyhsm-1.2.0/pyhsm/__init__.py0000664000175000017500000000441513006351320015772 0ustar daindain00000000000000# Copyright (c) 2011-2015 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # """ the pyhsm package Basic usage :: import pyhsm try: hsm = pyhsm.base.YHSM(device="/dev/ttyACM0", debug=False) print "Version : %s" % (hsm.info()) except pyhsm.exception.YHSM_Error, e: print "ERROR: %s" % e See help(pyhsm.base) (L{pyhsm.base.YHSM}) for more information. """ __version__ = '1.2.0' __copyright__ = 'Yubico AB' __organization__ = 'Yubico' __license__ = 'BSD' __authors__ = ['Fredrik Thulin', 'Dain Nilsson'] __all__ = ["base", "cmd", "defines", "exception", "stick", "util", "version", "yubikey", "soft_hsm", # "aead_cmd", "aes_ecb_cmd", "basic_cmd", "buffer_cmd", "db_cmd", "debug_cmd", "hmac_cmd", "oath_hotp", "oath_totp", "validate_cmd", ] from pyhsm.base import YHSM pyhsm-1.2.0/pyhsm/aes_ecb_cmd.py0000664000175000017500000001312313002347641016442 0ustar daindain00000000000000""" implementations of AES ECB block cipher commands to execute on a YubiHSM """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import struct __all__ = [ # constants # functions # classes #'YHSM_Cmd_AES_ECB', 'YHSM_Cmd_AES_ECB_Encrypt', 'YHSM_Cmd_AES_ECB_Decrypt', 'YHSM_Cmd_AES_ECB_Compare', ] import pyhsm.defines import pyhsm.exception from pyhsm.cmd import YHSM_Cmd class YHSM_Cmd_AES_ECB(YHSM_Cmd): """ Common code for command classes in this module. """ status = None key_handle = 0x00 def __init__(self, stick, command, payload): YHSM_Cmd.__init__(self, stick, command, payload) def __repr__(self): return '<%s instance at %s: key_handle=0x%x>' % ( self.__class__.__name__, hex(id(self)), self.key_handle ) def parse_result(self, data): # typedef struct { # uint32_t keyHandle; // Key handle # uint8_t ciphertext[YSM_BLOCK_SIZE]; // Ciphertext block # YHSM_STATUS status; // Encryption status # } YHSM_ECB_BLOCK_ENCRYPT_RESP; # OR # typedef struct { # uint32_t keyHandle; // Key handle # uint8_t plaintext[YSM_BLOCK_SIZE]; // Plaintext block # YHSM_STATUS status; // Decryption status # } YHSM_ECB_BLOCK_DECRYPT_RESP; fmt = "< I %is B" % (pyhsm.defines.YSM_BLOCK_SIZE) key_handle, \ result, \ self.status = struct.unpack(fmt, data) # check that returned key_handle matches the one in the request pyhsm.util.validate_cmd_response_hex('key_handle', key_handle, self.key_handle) if self.status == pyhsm.defines.YSM_STATUS_OK: return result else: raise pyhsm.exception.YHSM_CommandFailed(pyhsm.defines.cmd2str(self.command), self.status) class YHSM_Cmd_AES_ECB_Encrypt(YHSM_Cmd_AES_ECB): """ Have the YubiHSM AES ECB encrypt something using the key of a key handle. """ def __init__(self, stick, key_handle, plaintext): pyhsm.util.input_validate_str(plaintext, name='plaintext', max_len = pyhsm.defines.YSM_BLOCK_SIZE) self.key_handle = pyhsm.util.input_validate_key_handle(key_handle) # typedef struct { # uint32_t keyHandle; // Key handle # uint8_t plaintext[YHSM_BLOCK_SIZE]; // Plaintext block # } YHSM_ECB_BLOCK_ENCRYPT_REQ; payload = struct.pack('' % ( self.__class__.__name__, hex(id(self)), self.reason ) class YHSM_WrongInputSize(YHSM_Error): """ Exception raised for errors in the size of an argument to some function. """ def __init__(self, name, expected, size): reason = "Bad size of argument '%s', expected %i got %i" % (name, expected, size) YHSM_Error.__init__(self, reason) class YHSM_InputTooShort(YHSM_Error): """ Exception raised for too short input to some function. """ def __init__(self, name, expected, size): reason = "Argument '%s' too short, expected min %i got %i" % (name, expected, size) YHSM_Error.__init__(self, reason) class YHSM_InputTooLong(YHSM_Error): """ Exception raised for too long input to some function. """ def __init__(self, name, expected, size): reason = "Argument '%s' too long, expected max %i got %i" % (name, expected, size) YHSM_Error.__init__(self, reason) class YHSM_WrongInputType(YHSM_Error): """ Exception raised for errors in the type of an argument to some function. """ def __init__(self, name, expected, name_type): reason = "Bad type of argument '%s', expected %s got %s" % (name, expected, name_type) YHSM_Error.__init__(self, reason) class YHSM_CommandFailed(YHSM_Error): """ Exception raised when a command sent to the YubiHSM returned an error. """ def __init__(self, name, status): self.status = status self.status_str = pyhsm.defines.status2str(status) reason = "Command %s failed: %s" % (name, self.status_str) YHSM_Error.__init__(self, reason) pyhsm-1.2.0/pyhsm/stick_client.py0000664000175000017500000001137313004651476016724 0ustar daindain00000000000000""" module for talking to the YubiHSM over a socket. """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. __all__ = [ # constants # functions # classes 'YHSM_Stick_Client', ] import sys import re import socket import json import pyhsm.util import pyhsm.exception CMD_WRITE = 0 CMD_READ = 1 CMD_FLUSH = 2 CMD_DRAIN = 3 CMD_LOCK = 4 CMD_UNLOCK = 5 DEVICE_PATTERN = re.compile(r'yhsm://(?P[^:]+)(:(?P\d+))?/?') DEFAULT_PORT = 5348 def pack_data(data): if isinstance(data, basestring): return data.encode('base64') return data def unpack_data(data): if isinstance(data, basestring): return data.decode('base64') elif isinstance(data, dict) and 'error' in data: return pyhsm.exception.YHSM_Error(data['error']) return data def read_sock(sf): line = sf.readline() return unpack_data(json.loads(line)) def write_sock(sf, cmd, *args): json.dump([cmd] + map(pack_data, args), sf) sf.write("\n") sf.flush() class YHSM_Stick_Client(): """ The current YHSM is a USB device using serial communication. This class exposes the basic functions read, write and flush (input). """ def __init__(self, device, timeout=1, debug=False): """ Open YHSM device. """ self.debug = debug self.device = device self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) match = DEVICE_PATTERN.match(device) host = match.group('host') port = match.group('port') or DEFAULT_PORT self.socket.connect((host, int(port))) self.socket_file = self.socket.makefile('wb') self.num_read_bytes = 0 self.num_write_bytes = 0 if self.debug: sys.stderr.write("%s: OPEN %s\n" % ( self.__class__.__name__, self.socket )) return None def acquire(self): write_sock(self.socket_file, CMD_LOCK) return self.release def release(self): write_sock(self.socket_file, CMD_UNLOCK) def write(self, data, debug_info=None): """ Write data to YHSM device. """ self.num_write_bytes += len(data) if self.debug: if not debug_info: debug_info = str(len(data)) sys.stderr.write("%s: WRITE %s:\n%s\n" % ( self.__class__.__name__, debug_info, pyhsm.util.hexdump(data) )) write_sock(self.socket_file, CMD_WRITE, data) return read_sock(self.socket_file) def read(self, num_bytes, debug_info=None): """ Read a number of bytes from YubiHSM device. """ if self.debug: if not debug_info: debug_info = str(num_bytes) sys.stderr.write("%s: READING %s\n" % ( self.__class__.__name__, debug_info )) write_sock(self.socket_file, CMD_READ, num_bytes) res = read_sock(self.socket_file) if isinstance(res, Exception): raise res if self.debug: sys.stderr.write("%s: READ %i:\n%s\n" % ( self.__class__.__name__, len(res), pyhsm.util.hexdump(res) )) self.num_read_bytes += len(res) return res def flush(self): """ Flush input buffers. """ write_sock(self.socket_file, CMD_FLUSH) return read_sock(self.socket_file) def drain(self): """ Drain input. """ write_sock(self.socket_file, CMD_DRAIN) return read_sock(self.socket_file) def raw_device(self): """ Get the socket address. Only intended for test code/debugging! """ return self.socket def set_debug(self, new): """ Set debug mode (boolean). Returns old setting. """ if type(new) is not bool: raise pyhsm.exception.YHSM_WrongInputType( 'new', bool, type(new)) old = self.debug self.debug = new return old def __repr__(self): return '<%s instance at %s: %s - r:%i w:%i>' % ( self.__class__.__name__, hex(id(self)), self.device, self.num_read_bytes, self.num_write_bytes ) def __del__(self): """ Close device when YHSM instance is destroyed. """ if self.debug: sys.stderr.write("%s: CLOSE %s\n" % ( self.__class__.__name__, self.device )) try: self.socket_file.close() self.socket.close() except Exception: pass pyhsm-1.2.0/pyhsm/aead_cmd.py0000664000175000017500000002605713002347641015765 0ustar daindain00000000000000""" implementations of AEAD commands for the YubiHSM """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import struct __all__ = [ # constants 'YHSM_AEAD_File_Marker', # functions # classes 'YHSM_AEAD_Cmd', 'YHSM_Cmd_AEAD_Generate', 'YHSM_Cmd_AEAD_Random_Generate', 'YHSM_Cmd_AEAD_Buffer_Generate', 'YHSM_Cmd_AEAD_Decrypt_Cmp', 'YHSM_GeneratedAEAD', 'YHSM_YubiKeySecret', ] import pyhsm.defines import pyhsm.exception from pyhsm.cmd import YHSM_Cmd YHSM_AEAD_File_Marker = 'YubiHSM AEAD\n' # AEADs generated on Windows using pyhsm <= 1.1.1 will have CRLF instead of LF. YHSM_AEAD_CRLF_File_Marker = YHSM_AEAD_File_Marker[:-1] + '\r\n' class YHSM_AEAD_Cmd(YHSM_Cmd): """ Class for common non-trivial parse_result for commands returning a YSM_AEAD_GENERATE_RESP. """ nonce = '' key_handle = 0 status = 0 response = None def __repr__(self): if self.executed: return '<%s instance at %s: nonce=%s, key_handle=0x%x, status=%s>' % ( self.__class__.__name__, hex(id(self)), self.nonce.encode('hex'), self.key_handle, pyhsm.defines.status2str(self.status) ) else: return '<%s instance at %s (not executed)>' % ( self.__class__.__name__, hex(id(self)) ) def parse_result(self, data): """ Returns a YHSM_GeneratedAEAD instance, or throws pyhsm.exception.YHSM_CommandFailed. """ # typedef struct { # uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce (publicId for Yubikey AEADs) # uint32_t keyHandle; // Key handle # YSM_STATUS status; // Status # uint8_t numBytes; // Number of bytes in AEAD block # uint8_t aead[YSM_AEAD_MAX_SIZE]; // AEAD block # } YSM_AEAD_GENERATE_RESP; nonce, \ key_handle, \ self.status, \ num_bytes = struct.unpack_from("< %is I B B" % (pyhsm.defines.YSM_AEAD_NONCE_SIZE), data, 0) pyhsm.util.validate_cmd_response_hex('key_handle', key_handle, self.key_handle) if self.status == pyhsm.defines.YSM_STATUS_OK: pyhsm.util.validate_cmd_response_nonce(nonce, self.nonce) offset = pyhsm.defines.YSM_AEAD_NONCE_SIZE + 6 aead = data[offset:offset + num_bytes] self.response = YHSM_GeneratedAEAD(nonce, key_handle, aead) return self.response else: raise pyhsm.exception.YHSM_CommandFailed(pyhsm.defines.cmd2str(self.command), self.status) class YHSM_Cmd_AEAD_Generate(YHSM_AEAD_Cmd): """ Generate AEAD block from data for a specific key. `data' is either a string, or a YHSM_YubiKeySecret. """ def __init__(self, stick, nonce, key_handle, data): self.nonce = pyhsm.util.input_validate_nonce(nonce, pad = True) self.key_handle = pyhsm.util.input_validate_key_handle(key_handle) self.data = pyhsm.util.input_validate_yubikey_secret(data) # typedef struct { # uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce (publicId for Yubikey AEADs) # uint32_t keyHandle; // Key handle # uint8_t numBytes; // Number of data bytes # uint8_t data[YSM_DATA_BUF_SIZE]; // Data # } YSM_AEAD_GENERATE_REQ; fmt = "< %is I B %is" % (pyhsm.defines.YSM_AEAD_NONCE_SIZE, len(self.data)) packed = struct.pack(fmt, nonce, key_handle, len(self.data), self.data) YHSM_AEAD_Cmd.__init__(self, stick, pyhsm.defines.YSM_AEAD_GENERATE, packed) class YHSM_Cmd_AEAD_Random_Generate(YHSM_AEAD_Cmd): """ Generate a random AEAD block using the YubiHSM internal TRNG. To generate a secret for a YubiKey, use public_id as nonce. """ def __init__(self, stick, nonce, key_handle, num_bytes): self.nonce = pyhsm.util.input_validate_nonce(nonce, pad = True) self.key_handle = pyhsm.util.input_validate_key_handle(key_handle) self.num_bytes = pyhsm.util.input_validate_int(num_bytes, 'num_bytes') # typedef struct { # uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce (publicId for Yubikey AEADs) # uint32_t keyHandle; // Key handle # uint8_t numBytes; // Number of bytes to randomize # } YSM_RANDOM_AEAD_GENERATE_REQ; fmt = "< %is I B" % (pyhsm.defines.YSM_AEAD_NONCE_SIZE) packed = struct.pack(fmt, nonce, key_handle, num_bytes) YHSM_AEAD_Cmd.__init__(self, stick, pyhsm.defines.YSM_RANDOM_AEAD_GENERATE, packed) class YHSM_Cmd_AEAD_Buffer_Generate(YHSM_AEAD_Cmd): """ Generate AEAD block of data buffer for a specific key. After a key has been loaded into the internal data buffer, this command can be used a number of times to get AEADs of the data buffer for different key handles. For example, to encrypt a YubiKey secrets to one or more Yubico KSM's that all have a YubiHSM attached to them. Key handle (and system flags) permission flags required for this operation : YSM_BUFFER_AEAD_GENERATE YSM_BUFFER_LOAD if non-random data has been loaded into the internal buffer """ def __init__(self, stick, nonce, key_handle): self.nonce = pyhsm.util.input_validate_nonce(nonce, pad = True) self.key_handle = pyhsm.util.input_validate_key_handle(key_handle) # typedef struct { # uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce (publicId for Yubikey AEADs) # uint32_t keyHandle; // Key handle # } YSM_BUFFER_AEAD_GENERATE_REQ; packed = struct.pack("< %is I" % (pyhsm.defines.YSM_AEAD_NONCE_SIZE), \ self.nonce, self.key_handle) YHSM_AEAD_Cmd.__init__(self, stick, pyhsm.defines.YSM_BUFFER_AEAD_GENERATE, packed) class YHSM_Cmd_AEAD_Decrypt_Cmp(YHSM_Cmd): """ Validate an AEAD using the YubiHSM, matching it against some known plain text. Matching is done inside the YubiHSM so the decrypted AEAD is never exposed. """ status = None def __init__(self, stick, nonce, key_handle, aead, cleartext): aead = pyhsm.util.input_validate_aead(aead) expected_ct_len = len(aead) - pyhsm.defines.YSM_AEAD_MAC_SIZE cleartext = pyhsm.util.input_validate_str(cleartext, 'cleartext', exact_len = expected_ct_len) self.nonce = pyhsm.util.input_validate_nonce(nonce, pad = True) self.key_handle = pyhsm.util.input_validate_key_handle(key_handle) data = cleartext + aead if len(data) > pyhsm.defines.YSM_MAX_PKT_SIZE - 10: raise pyhsm.exception.YHSM_InputTooLong( 'cleartext+aead', pyhsm.defines.YSM_MAX_PKT_SIZE - 10, len(data)) # typedef struct { # uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce (publicId for Yubikey AEADs) # uint32_t keyHandle; // Key handle # uint8_t numBytes; // Number of data bytes (cleartext + aead) # uint8_t data[YSM_MAX_PKT_SIZE - 0x10]; // Data (cleartext + aead). Empty cleartext validates aead only # } YSM_AEAD_DECRYPT_CMP_REQ; fmt = "< %is I B %is" % (pyhsm.defines.YSM_AEAD_NONCE_SIZE, len(data)) packed = struct.pack(fmt, self.nonce, key_handle, len(data), data) YHSM_Cmd.__init__(self, stick, pyhsm.defines.YSM_AEAD_DECRYPT_CMP, packed) def parse_result(self, data): # typedef struct { # uint8_t nonce[YSM_AEAD_NONCE_SIZE]; // Nonce (publicId for Yubikey AEADs) # uint32_t keyHandle; // Key handle # YSM_STATUS status; // Status # } YSM_AEAD_DECRYPT_CMP_RESP; fmt = "< %is I B" % (pyhsm.defines.YSM_AEAD_NONCE_SIZE) nonce, key_handle, self.status = struct.unpack(fmt, data) pyhsm.util.validate_cmd_response_str('nonce', nonce, self.nonce) pyhsm.util.validate_cmd_response_hex('key_handle', key_handle, self.key_handle) if self.status == pyhsm.defines.YSM_STATUS_OK: return True if self.status == pyhsm.defines.YSM_MISMATCH: return False else: raise pyhsm.exception.YHSM_CommandFailed(pyhsm.defines.cmd2str(self.command), self.status) class YHSM_GeneratedAEAD(): """ Small class to represent a YHSM_AEAD_GENERATE_RESP. """ def __init__(self, nonce, key_handle, aead): self.nonce = nonce self.key_handle = key_handle self.data = aead def __repr__(self): nonce_str = "None" if self.nonce is not None: nonce_str = self.nonce.encode('hex') return '<%s instance at %s: nonce=%s, key_handle=0x%x, data=%i bytes>' % ( self.__class__.__name__, hex(id(self)), nonce_str, self.key_handle, len(self.data) ) def save(self, filename): """ Store AEAD in a file. @param filename: File to create/overwrite @type filename: string """ aead_f = open(filename, "wb") fmt = "< B I %is %is" % (pyhsm.defines.YSM_AEAD_NONCE_SIZE, len(self.data)) version = 1 packed = struct.pack(fmt, version, self.key_handle, self.nonce, self.data) aead_f.write(YHSM_AEAD_File_Marker + packed) aead_f.close() def load(self, filename): """ Load AEAD from a file. @param filename: File to read AEAD from @type filename: string """ aead_f = open(filename, "rb") buf = aead_f.read(1024) if buf.startswith(YHSM_AEAD_CRLF_File_Marker): buf = YHSM_AEAD_File_Marker + buf[len(YHSM_AEAD_CRLF_File_Marker):] if buf.startswith(YHSM_AEAD_File_Marker): if buf[len(YHSM_AEAD_File_Marker)] == chr(1): # version 1 format fmt = "< I %is" % (pyhsm.defines.YSM_AEAD_NONCE_SIZE) self.key_handle, self.nonce = struct.unpack_from(fmt, buf, len(YHSM_AEAD_File_Marker) + 1) self.data = buf[len(YHSM_AEAD_File_Marker) + 1 + struct.calcsize(fmt):] else: raise pyhsm.exception.YHSM_Error('Unknown AEAD file format') else: # version 0 format, just AEAD data self.data = buf[:pyhsm.defines.YSM_MAX_KEY_SIZE + pyhsm.defines.YSM_BLOCK_SIZE] aead_f.close() class YHSM_YubiKeySecret(): """ Small class to represent a YUBIKEY_SECRETS struct. """ def __init__(self, key, uid): self.key = pyhsm.util.input_validate_str(key, 'key', exact_len = pyhsm.defines.KEY_SIZE) self.uid = pyhsm.util.input_validate_str(uid, 'uid', max_len = pyhsm.defines.UID_SIZE) def pack(self): """ Return key and uid packed for sending in a command to the YubiHSM. """ # # 22-bytes Yubikey secrets block # typedef struct { # uint8_t key[KEY_SIZE]; // AES key # uint8_t uid[UID_SIZE]; // Unique (secret) ID # } YUBIKEY_SECRETS; return self.key + self.uid.ljust(pyhsm.defines.UID_SIZE, chr(0)) pyhsm-1.2.0/pyhsm/stick_daemon.py0000664000175000017500000001222313005606114016672 0ustar daindain00000000000000# # Copyright (C) 2013 Yubico AB. All rights reserved. # """ This is a daemon to allow multiple users of a YubiHSM without requiring permission to use the device. The daemon listens on a TCP port on localhost and allows multiple connections to share a YubiHSM. Access the YubiHSM via the daemon by specifying a device string following the yhsm://: syntax: hsm = YHSM('yhsm://localhost:5348') Note that the daemon and clients need to share the same version number to be compatible. """ import sys import socket import json import threading import argparse import pyhsm.stick import daemon import os CMD_WRITE = 0 CMD_READ = 1 CMD_FLUSH = 2 CMD_DRAIN = 3 CMD_LOCK = 4 CMD_UNLOCK = 5 COMMANDS = { CMD_WRITE: 'write', CMD_READ: 'read', CMD_FLUSH: 'flush', CMD_DRAIN: 'drain' } context = daemon.DaemonContext() def pack_data(data): if isinstance(data, basestring): return data.encode('base64') return data def unpack_data(data): if isinstance(data, basestring): return data.decode('base64') return data def write_pid_file(fn): """ Create a file with our PID. """ if not fn: return None if fn == '' or fn == "''": # work around argument passings in init-scripts return None f = open(fn, "w") f.write("%s\n" % (os.getpid())) f.close() class YHSM_Stick_Server(): def __init__(self, device, addr): self.device = device self._stick = None self.pidfile = None self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self.socket.bind(addr) self.lock = threading.Lock() def serve(self): write_pid_file(self.pidfile) self.socket.listen(20) try: while True: cs, address = self.socket.accept() thread = threading.Thread(target=self.client_handler, args=(cs,)) thread.start() except Exception as e: print e sys.exit(1) def invoke(self, cmd, *args): try: if not self._stick: self._stick = pyhsm.stick.YHSM_Stick(self.device) res = getattr(self._stick, COMMANDS[cmd])(*args) except Exception as e: res = e print e self._stick = None return res def client_handler(self, socket): socket_file = socket.makefile('wb') has_lock = False try: while True: data = json.loads(socket_file.readline()) cmd = data[0] args = map(unpack_data, data[1:]) if cmd == CMD_LOCK: if not has_lock: self.lock.acquire() has_lock = True elif has_lock: if cmd == CMD_UNLOCK: self.lock.release() has_lock = False else: res = self.invoke(cmd, *args) json.dump(pack_data(res), socket_file) socket_file.write("\n") socket_file.flush() else: err = 'Command run without holding lock!' print err json.dump({'error': err}, socket_file) socket_file.write("\n") socket_file.flush() break except Exception: # Client disconnected, ignore. pass finally: if has_lock: self.lock.release() socket_file.close() socket.close() def main(): parser = argparse.ArgumentParser( description='YubiHSM server daemon', add_help=True, formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser.add_argument('-D', '--device', nargs='?', default='/dev/ttyACM0', help='device name') parser.add_argument('-d', '--daemon', default=False, action='store_true', help='run as daemon') parser.add_argument('-I', '--interface', nargs='?', default='localhost', help='network interface to bind to') parser.add_argument('-P', '--port', nargs='?', type=int, default=5348, help='TCP port to bind to') parser.add_argument('--pid-file', dest='pid_file', default=None, required=False, help='PID file', metavar='FILENAME') args = parser.parse_args() print 'Starting YubiHSM daemon for device: %s, listening on: %s:%d' % \ (args.device, args.interface, args.port) server = YHSM_Stick_Server(args.device, (args.interface, args.port)) print 'You can connect to the server using the following device string:' print 'yhsm://127.0.0.1:%d' % args.port server.pidfile = args.pid_file context.files_preserve = [server.socket] if args.daemon: with context: server.serve() else: server.serve() pyhsm-1.2.0/pyhsm/oath_hotp.py0000664000175000017500000000361313002416721016222 0ustar daindain00000000000000""" helper functions to work with OATH HOTP (RFC4226) OTP's and YubiHSM """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import struct import pyhsm.exception import pyhsm.aead_cmd __all__ = [ # constants # functions 'search_for_oath_code', # classes ] def search_for_oath_code(hsm, key_handle, nonce, aead, counter, user_code, look_ahead=1): """ Try to validate an OATH HOTP OTP generated by a token whose secret key is available to the YubiHSM through the AEAD. The parameter `aead' is either a string, or an instance of YHSM_GeneratedAEAD. Returns next counter value on successful auth, and None otherwise. """ key_handle = pyhsm.util.input_validate_key_handle(key_handle) nonce = pyhsm.util.input_validate_nonce(nonce, pad = False) aead = pyhsm.util.input_validate_aead(aead) counter = pyhsm.util.input_validate_int(counter, 'counter') user_code = pyhsm.util.input_validate_int(user_code, 'user_code') hsm.load_temp_key(nonce, key_handle, aead) # User might have produced codes never sent to us, so we support trying look_ahead # codes to see if we find the user's current code. for j in xrange(look_ahead): this_counter = counter + j secret = struct.pack("> Q", this_counter) hmac_result = hsm.hmac_sha1(pyhsm.defines.YSM_TEMP_KEY_HANDLE, secret).get_hash() this_code = truncate(hmac_result) if this_code == user_code: return this_counter + 1 return None def truncate(hmac_result, length=6): """ Perform the truncating. """ assert(len(hmac_result) == 20) offset = ord(hmac_result[19]) & 0xf bin_code = (ord(hmac_result[offset]) & 0x7f) << 24 \ | (ord(hmac_result[offset+1]) & 0xff) << 16 \ | (ord(hmac_result[offset+2]) & 0xff) << 8 \ | (ord(hmac_result[offset+3]) & 0xff) return bin_code % (10 ** length) pyhsm-1.2.0/pyhsm/hmac_cmd.py0000664000175000017500000001152413002140277015770 0ustar daindain00000000000000""" implementations of HMAC commands to execute on a YubiHSM """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import struct __all__ = [ # constants # functions # classes 'YHSM_Cmd_HMAC_SHA1_Write', 'YHSM_GeneratedHMACSHA1', ] import pyhsm.exception import pyhsm.defines from pyhsm.cmd import YHSM_Cmd class YHSM_Cmd_HMAC_SHA1_Write(YHSM_Cmd): """ Calculate HMAC SHA1 using a key_handle in the YubiHSM. Set final=False to not get a hash generated for the initial request. Set to_buffer=True to get the SHA1 stored into the internal buffer, for use in some other cryptographic operation. """ status = None result = None def __init__(self, stick, key_handle, data, flags = None, final = True, to_buffer = False): data = pyhsm.util.input_validate_str(data, 'data', max_len = pyhsm.defines.YSM_MAX_PKT_SIZE - 6) self.key_handle = pyhsm.util.input_validate_key_handle(key_handle) if flags != None: flags = pyhsm.util.input_validate_int(flags, 'flags', max_value=0xff) else: flags = pyhsm.defines.YSM_HMAC_SHA1_RESET if final: flags |= pyhsm.defines.YSM_HMAC_SHA1_FINAL if to_buffer: flags |= pyhsm.defines.YSM_HMAC_SHA1_TO_BUFFER self.final = final self.flags = flags packed = _raw_pack(self.key_handle, self.flags, data) YHSM_Cmd.__init__(self, stick, pyhsm.defines.YSM_HMAC_SHA1_GENERATE, packed) def next(self, data, final = False, to_buffer = False): """ Add more input to the HMAC SHA1. """ if final: self.flags = pyhsm.defines.YSM_HMAC_SHA1_FINAL else: self.flags = 0x0 if to_buffer: self.flags |= pyhsm.defines.YSM_HMAC_SHA1_TO_BUFFER self.payload = _raw_pack(self.key_handle, self.flags, data) self.final = final return self def get_hash(self): """ Get the HMAC-SHA1 that has been calculated this far. """ if not self.executed: raise pyhsm.exception.YHSM_Error("HMAC-SHA1 hash not available, before execute().") return self.result.hash_result def __repr__(self): if self.executed: return '<%s instance at %s: key_handle=0x%x, flags=0x%x, executed=%s>' % ( self.__class__.__name__, hex(id(self)), self.key_handle, self.flags, self.executed, ) def parse_result(self, data): # typedef struct { # uint32_t keyHandle; // Key handle # YHSM_STATUS status; // Status # uint8_t numBytes; // Number of bytes in hash output # uint8_t hash[YSM_SHA1_HASH_SIZE]; // Hash output (if applicable) # } YHSM_HMAC_SHA1_GENERATE_RESP; key_handle, \ self.status, \ num_bytes = struct.unpack_from('' % ( self.__class__.__name__, hex(id(self)), self.key_handle, self.hash_result[:4].encode('hex'), self.final, ) pyhsm-1.2.0/pyhsm/base.py0000664000175000017500000005533613004651524015164 0ustar daindain00000000000000# # Copyright (c) 2011 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # """ module for accessing a YubiHSM """ __all__ = [ # constants # functions # classes 'YHSM' ] #from pyhsm import __version__ import pyhsm.cmd import pyhsm.stick import pyhsm.stick_client import pyhsm.exception import pyhsm.version import pyhsm.aead_cmd import pyhsm.aes_ecb_cmd import pyhsm.basic_cmd import pyhsm.buffer_cmd import pyhsm.db_cmd import pyhsm.debug_cmd import pyhsm.hmac_cmd import pyhsm.validate_cmd import pyhsm.yubikey import pyhsm.soft_hsm class YHSM(): """ Base class for accessing a YubiHSM. """ def __init__(self, device, debug=False, timeout=1, test_comm=True): self.debug = debug if device.startswith('yhsm://'): self.stick = pyhsm.stick_client.YHSM_Stick_Client(device) else: self.stick = pyhsm.stick.YHSM_Stick(device, debug = self.debug, timeout = timeout) if not self.reset(test_sync = False): raise pyhsm.exception.YHSM_Error("Initialization of YubiHSM failed") self.version = pyhsm.version.YHSM_Version(self.info()) # Check that this is a device we know how to talk to if self.version.sysinfo.protocol_ver != pyhsm.defines.YSM_PROTOCOL_VERSION: raise pyhsm.exception.YHSM_Error("Unknown YubiHSM protocol version (%i, I speak %i)" % \ (self.version.sysinfo.protocol_ver, \ pyhsm.defines.YSM_PROTOCOL_VERSION)) # Check that communication isn't mangled (by something like 'stty onlcr') if test_comm: self.test_comm() return None def __repr__(self): return '<%s instance at %s: %s>' % ( self.__class__.__name__, hex(id(self)), self.stick.device ) def reset(self, test_sync = True): """ Perform stream resynchronization. @param test_sync: Verify sync with YubiHSM after reset @type test_sync: bool @return: True if successful @rtype: bool """ pyhsm.cmd.reset(self.stick) if test_sync: # Now verify we are in sync data = 'ekoeko' echo = self.echo(data) # XXX analyze 'echo' to see if we are in config mode, and produce a # nice exception if we are. return data == echo else: return True def set_debug(self, new): """ Set debug mode. @param new: new value @type new: bool @return: old value @rtype: bool """ if type(new) is not bool: raise pyhsm.exception.YHSM_WrongInputType( 'new', bool, type(new)) old = self.debug self.debug = new self.stick.set_debug(new) return old def test_comm(self): """ Verify that data we send to and receive from the YubiHSM isn't mangled. In some scenarios, communications with the YubiHSM might be affected by terminal line settings turning CR into LF for example. """ data = ''.join([chr(x) for x in range(256)]) data = data + '0d0a0d0a'.decode('hex') chunk_size = pyhsm.defines.YSM_MAX_PKT_SIZE - 10 # max size of echo count = 0 while data: this = data[:chunk_size] data = data[chunk_size:] res = self.echo(this) for i in xrange(len(this)): if res[i] != this[i]: msg = "Echo test failed at position %i (0x%x != 0x%x)" \ % (count + i, ord(res[i]), ord(this[i])) raise pyhsm.exception.YHSM_Error(msg) count += len(this) # # Basic commands # def echo(self, data): """ Echo test. @type data: string @return: data read from YubiHSM -- should equal `data' @rtype: string @see: L{pyhsm.basic_cmd.YHSM_Cmd_Echo} """ return pyhsm.basic_cmd.YHSM_Cmd_Echo(self.stick, data).execute() def info(self): """ Get firmware version and unique ID from YubiHSM. @return: System information @rtype: L{YHSM_Cmd_System_Info} @see: L{pyhsm.basic_cmd.YHSM_Cmd_System_Info} """ return pyhsm.basic_cmd.YHSM_Cmd_System_Info(self.stick).execute() def random(self, num_bytes): """ Get random bytes from YubiHSM. The random data is DRBG_CTR seeded on each startup by a hardware TRNG, so it should be of very good quality. @type num_bytes: integer @return: Bytes with random data @rtype: string @see: L{pyhsm.basic_cmd.YHSM_Cmd_Random} """ return pyhsm.basic_cmd.YHSM_Cmd_Random(self.stick, num_bytes).execute() def random_reseed(self, seed): """ Provide YubiHSM DRBG_CTR with a new seed. @param seed: new seed -- must be exactly 32 bytes @type seed: string @returns: True on success @rtype: bool @see: L{pyhsm.basic_cmd.YHSM_Cmd_Random_Reseed} """ return pyhsm.basic_cmd.YHSM_Cmd_Random_Reseed(self.stick, seed).execute() def get_nonce(self, increment=1): """ Get current nonce from YubiHSM. Use increment 0 to just fetch the value without incrementing it. @keyword increment: requested increment (optional) @return: nonce value _before_ increment @rtype: L{YHSM_NonceResponse} @see: L{pyhsm.basic_cmd.YHSM_Cmd_Nonce_Get} """ return pyhsm.basic_cmd.YHSM_Cmd_Nonce_Get(self.stick, increment).execute() def load_temp_key(self, nonce, key_handle, aead): """ Load the contents of an AEAD into the phantom key handle 0xffffffff. @param nonce: The nonce used when creating the AEAD @param key_handle: The key handle that can decrypt the AEAD @param aead: AEAD containing the cryptographic key and permission flags @type nonce: string @type key_handle: integer or string @type aead: L{YHSM_GeneratedAEAD} or string @returns: True on success @rtype: bool @see: L{pyhsm.basic_cmd.YHSM_Cmd_Temp_Key_Load} """ return pyhsm.basic_cmd.YHSM_Cmd_Temp_Key_Load(self.stick, nonce, key_handle, aead).execute() def unlock(self, password = None, otp = None): """ Unlock the YubiHSM using the master key and/or a YubiKey OTP. If the master key is given during configuration, all key handles will be encrypted (with AES-256) using that passphrase. If one or more admin Yubikey public id's are given during configuration, an OTP from one of these must be provided to the YubiHSM for it to start responding to cryptographic requests. The admin YubiKeys must be present in the internal database for this validation to work. @param password: The 'master key' set during YubiHSM configuration @type password: NoneType or string @param otp: A YubiKey OTP from an 'admin' YubiKey (modhex), to unlock YubiHSM. @type otp: NoneType or string @returns: Only returns (True) on success @rtype: bool @see: L{pyhsm.basic_cmd.YHSM_Cmd_Key_Storage_Unlock} @see: L{pyhsm.basic_cmd.YHSM_Cmd_HSM_Unlock} """ if otp is not None and not self.version.have_unlock(): # only in 1.0 raise pyhsm.exception.YHSM_Error("Your YubiHSM does not support OTP unlocking.") if password is not None: if self.version.have_key_storage_unlock(): # 0.9.x res = pyhsm.basic_cmd.YHSM_Cmd_Key_Storage_Unlock(self.stick, password).execute() elif self.version.have_key_store_decrypt(): # 1.0 res = pyhsm.basic_cmd.YHSM_Cmd_Key_Store_Decrypt(self.stick, password).execute() else: raise pyhsm.exception.YHSM_Error("Don't know how to unlock your YubiHSM.") else: res = True if res and otp is not None: (public_id, otp,) = pyhsm.yubikey.split_id_otp(otp) public_id = pyhsm.yubikey.modhex_decode(public_id).decode('hex') otp = pyhsm.yubikey.modhex_decode(otp).decode('hex') return pyhsm.basic_cmd.YHSM_Cmd_HSM_Unlock(self.stick, public_id, otp).execute() return res def key_storage_unlock(self, password): """ @deprecated: Too specific (and hard to remember) name. @see: L{unlock} """ return self.unlock(password = password) # # AEAD related commands # def load_secret(self, secret): """ Ask YubiHSM to load a pre-existing YubiKey secret. The data is stored internally in the YubiHSM in temporary memory - this operation would typically be followed by one or more L{generate_aead} commands to actually retreive the generated secret (in encrypted form). @param secret: YubiKey secret to load @type secret: L{pyhsm.aead_cmd.YHSM_YubiKeySecret} or string @returns: Number of bytes in YubiHSM internal buffer after load @rtype: integer @see: L{pyhsm.buffer_cmd.YHSM_Cmd_Buffer_Load} """ if isinstance(secret, pyhsm.aead_cmd.YHSM_YubiKeySecret): secret = secret.pack() return pyhsm.buffer_cmd.YHSM_Cmd_Buffer_Load(self.stick, secret).execute() def load_data(self, data, offset): """ Ask YubiHSM to load arbitrary data into it's internal buffer, at any offset. The data is stored internally in the YubiHSM in temporary memory - this operation would typically be followed by one or more L{generate_aead} commands to actually retreive the generated secret (in encrypted form). Load data to offset 0 to reset the buffer. @param data: arbitrary data to load @type data: string @returns: Number of bytes in YubiHSM internal buffer after load @rtype: integer @see: L{pyhsm.buffer_cmd.YHSM_Cmd_Buffer_Load} """ return pyhsm.buffer_cmd.YHSM_Cmd_Buffer_Load(self.stick, data, offset).execute() def load_random(self, num_bytes, offset = 0): """ Ask YubiHSM to generate a number of random bytes to any offset of it's internal buffer. The data is stored internally in the YubiHSM in temporary memory - this operation would typically be followed by one or more L{generate_aead} commands to actually retreive the generated secret (in encrypted form). @param num_bytes: Number of bytes to generate @type num_bytes: integer @returns: Number of bytes in YubiHSM internal buffer after load @rtype: integer @see: L{pyhsm.buffer_cmd.YHSM_Cmd_Buffer_Random_Load} """ return pyhsm.buffer_cmd.YHSM_Cmd_Buffer_Random_Load(self.stick, num_bytes, offset).execute() def generate_aead_simple(self, nonce, key_handle, data): """ Generate AEAD block from data for a specific key in a single step (without using the YubiHSM internal buffer). @param nonce: The nonce to use when creating the AEAD @param key_handle: The key handle that can encrypt data into an AEAD @param data: Data to put inside the AEAD @type nonce: string @type key_handle: integer or string @type data: string @returns: The generated AEAD on success. @rtype: L{YHSM_GeneratedAEAD} @see: L{pyhsm.aead_cmd.YHSM_Cmd_AEAD_Generate} """ return pyhsm.aead_cmd.YHSM_Cmd_AEAD_Generate(self.stick, nonce, key_handle, data).execute() def generate_aead_random(self, nonce, key_handle, num_bytes): """ Generate a random AEAD block using the YubiHSM internal DRBG_CTR random generator. To generate a secret for a YubiKey, use public_id as nonce. @param nonce: The nonce to use when creating the AEAD @param key_handle: The key handle that can encrypt the random data into an AEAD @param num_bytes: Number of random data bytes to put inside the AEAD @type nonce: string @type key_handle: integer or string @type num_bytes: integer @returns: The generated AEAD on success. @rtype: L{YHSM_GeneratedAEAD} @see: L{pyhsm.aead_cmd.YHSM_Cmd_AEAD_Random_Generate} """ return pyhsm.aead_cmd.YHSM_Cmd_AEAD_Random_Generate(self.stick, nonce, key_handle, num_bytes).execute() def generate_aead(self, nonce, key_handle): """ Ask YubiHSM to return an AEAD made of the contents of it's internal buffer (see L{load_secret}, L{load_data} and L{load_random}) encrypted with the specified key_handle. For a YubiKey secret, the nonce should be the public_id. @param nonce: The nonce to use when creating the AEAD @param key_handle: The key handle that can create an AEAD @type nonce: string @type key_handle: integer or string @returns: The generated AEAD on success. @rtype: L{YHSM_GeneratedAEAD} @see: L{pyhsm.aead_cmd.YHSM_Cmd_AEAD_Buffer_Generate} """ return pyhsm.aead_cmd.YHSM_Cmd_AEAD_Buffer_Generate(self.stick, nonce, key_handle).execute() def validate_aead(self, nonce, key_handle, aead, cleartext): """ Validate the contents of an AEAD using the YubiHSM. The matching is done inside the YubiHSM so the contents of the AEAD is never exposed (well, except indirectionally when the cleartext does match). The cleartext should naturally be of the same length as the AEAD minus the size of the MAC (8 bytes). @param nonce: The nonce used when creating the AEAD @param key_handle: The key handle that can decrypt the AEAD @param aead: AEAD containing the cryptographic key and permission flags @param cleartext: The presumed cleartext of the AEAD @type nonce: string @type key_handle: integer or string @type aead: L{YHSM_GeneratedAEAD} or string @type cleartext: string @returns: Whether or not the cleartext matches the contents of the AEAD. @rtype: bool @see: L{pyhsm.aead_cmd.YHSM_Cmd_AEAD_Decrypt_Cmp} """ return pyhsm.aead_cmd.YHSM_Cmd_AEAD_Decrypt_Cmp(self.stick, nonce, key_handle, aead, cleartext).execute() def validate_aead_otp(self, public_id, otp, key_handle, aead): """ Ask YubiHSM to validate a YubiKey OTP using an AEAD and a key_handle to decrypt the AEAD. @param public_id: The six bytes public id of the YubiKey @param otp: The one time password (OTP) to validate @param key_handle: The key handle that can decrypt the AEAD @param aead: AEAD containing the cryptographic key and permission flags @type public_id: string @type otp: string @type key_handle: integer or string @type aead: L{YHSM_GeneratedAEAD} or string @returns: validation response @rtype: L{YHSM_ValidationResult} @see: L{pyhsm.validate_cmd.YHSM_Cmd_AEAD_Validate_OTP} """ if type(public_id) is not str: assert() if type(otp) is not str: assert() if type(key_handle) is not int: assert() if type(aead) is not str: assert() return pyhsm.validate_cmd.YHSM_Cmd_AEAD_Validate_OTP( \ self.stick, public_id, otp, key_handle, aead).execute() # # Debug/testing commands. # def monitor_exit(self): """ Ask YubiHSM to exit to configuration mode (requires 'debug' mode enabled). @returns: None @rtype: NoneType @see: L{pyhsm.debug_cmd.YHSM_Cmd_Monitor_Exit} """ return pyhsm.debug_cmd.YHSM_Cmd_Monitor_Exit(self.stick).execute(read_response=False) def get_raw_device(self): """ Get the raw device. Only intended for test code/debugging! @returns: serial device @rtype: Serial """ return self.stick.raw_device() def drain(self): """ Read until there is nothing more to be read. Only intended for test code/debugging! @returns: True on success @rtype: bool """ try: unlock = self.stick.acquire() return self.stick.drain() finally: unlock() # # AES ECB commands # def aes_ecb_encrypt(self, key_handle, plaintext): """ AES ECB encrypt using a key handle. @warning: Please be aware of the known limitations of AES ECB mode before using it! @param key_handle: Key handle to use for AES ECB encryption @param plaintext: Data to encrypt @type key_handle: integer or string @type plaintext: string @returns: Ciphertext @rtype: string @see: L{pyhsm.aes_ecb_cmd.YHSM_Cmd_AES_ECB_Encrypt} """ return pyhsm.aes_ecb_cmd.YHSM_Cmd_AES_ECB_Encrypt( \ self.stick, key_handle, plaintext).execute() def aes_ecb_decrypt(self, key_handle, ciphertext): """ AES ECB decrypt using a key handle. @warning: Please be aware of the known limitations of AES ECB mode before using it! @param key_handle: Key handle to use for AES ECB decryption @param ciphertext: Data to decrypt @type key_handle: integer or string @type ciphertext: string @returns: Plaintext @rtype: string @see: L{pyhsm.aes_ecb_cmd.YHSM_Cmd_AES_ECB_Decrypt} """ return pyhsm.aes_ecb_cmd.YHSM_Cmd_AES_ECB_Decrypt( \ self.stick, key_handle, ciphertext).execute() def aes_ecb_compare(self, key_handle, ciphertext, plaintext): """ AES ECB decrypt and then compare using a key handle. The comparison is done inside the YubiHSM so the plaintext is never exposed (well, except indirectionally when the provided plaintext does match). @warning: Please be aware of the known limitations of AES ECB mode before using it! @param key_handle: Key handle to use for AES ECB decryption @param plaintext: Data to decrypt @type key_handle: integer or string @type plaintext: string @returns: Match result @rtype: bool @see: L{pyhsm.aes_ecb_cmd.YHSM_Cmd_AES_ECB_Compare} """ return pyhsm.aes_ecb_cmd.YHSM_Cmd_AES_ECB_Compare( \ self.stick, key_handle, ciphertext, plaintext).execute() # # HMAC commands # def hmac_sha1(self, key_handle, data, flags = None, final = True, to_buffer = False): """ Have the YubiHSM generate a HMAC SHA1 of 'data' using a key handle. Use the L{pyhsm.hmac_cmd.YHSM_Cmd_HMAC_SHA1_Write.next} to add more input (until 'final' has been set to True). Use the L{pyhsm.hmac_cmd.YHSM_Cmd_HMAC_SHA1_Write.get_hash} to get the hash result this far. @param key_handle: Key handle to use when generating HMAC SHA1 @param data: what to calculate the HMAC SHA1 checksum of @keyword flags: bit-flags, overrides 'final' and 'to_buffer' @keyword final: True when there is no more data, False if there is more @keyword to_buffer: Should the final result be stored in the YubiHSM internal buffer or not @type key_handle: integer or string @type data: string @type flags: None or integer @returns: HMAC-SHA1 instance @rtype: L{YHSM_Cmd_HMAC_SHA1_Write} @see: L{pyhsm.hmac_cmd.YHSM_Cmd_HMAC_SHA1_Write} """ return pyhsm.hmac_cmd.YHSM_Cmd_HMAC_SHA1_Write( \ self.stick, key_handle, data, flags = flags, final = final, to_buffer = to_buffer).execute() # # Internal YubiKey database related commands # def db_store_yubikey(self, public_id, key_handle, aead, nonce = None): """ Ask YubiHSM to store data about a YubiKey in the internal database (not buffer). The input is an AEAD with the secrets of a YubiKey, perhaps previously created using L{load_secret}. @param public_id: The six bytes public id of the YubiKey @param key_handle: Key handle that can decrypt the YubiKey AEAD @param aead: AEAD of an L{pyhsm.aead_cmd.YHSM_YubiKeySecret} @param nonce: Nonce, if different from public_id. @type public_id: string @type key_handle: integer or string @type aead: L{YHSM_GeneratedAEAD} or string @type nonce: None or string @return: True on success @rtype: bool @see: L{pyhsm.db_cmd.YHSM_Cmd_DB_YubiKey_Store} """ if nonce is not None and not self.version.have_YSM_DB_YUBIKEY_AEAD_STORE2(): # introduced in 1.0.4 raise pyhsm.exception.YHSM_Error("YubiHSM does not support nonce != public_id.") return pyhsm.db_cmd.YHSM_Cmd_DB_YubiKey_Store( \ self.stick, public_id, key_handle, aead, nonce = nonce).execute() def db_validate_yubikey_otp(self, public_id, otp): """ Request the YubiHSM to validate an OTP for a YubiKey stored in the internal database. @param public_id: The six bytes public id of the YubiKey @param otp: The OTP from a YubiKey in binary form (16 bytes) @type public_id: string @type otp: string @returns: validation response @rtype: L{YHSM_ValidationResult} @see: L{pyhsm.db_cmd.YHSM_Cmd_DB_Validate_OTP} """ return pyhsm.db_cmd.YHSM_Cmd_DB_Validate_OTP( \ self.stick, public_id, otp).execute() pyhsm-1.2.0/pyhsm/yubikey.py0000664000175000017500000000744613002140277015726 0ustar daindain00000000000000""" helper functions to work with Yubikeys and YubiHSM """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import string __all__ = [ # constants # functions 'validate_otp', 'validate_yubikey_with_aead', 'modhex_encode', 'modhex_decode', 'split_id_otp', # classes ] import pyhsm.exception import pyhsm.aead_cmd def validate_otp(hsm, from_key): """ Try to validate an OTP from a YubiKey using the internal database on the YubiHSM. `from_key' is the modhex encoded string emitted when you press the button on your YubiKey. Will only return on succesfull validation. All failures will result in an L{pyhsm.exception.YHSM_CommandFailed}. @param hsm: The YHSM instance @param from_key: The OTP from a YubiKey (in modhex) @type hsm: L{pyhsm.YHSM} @type from_key: string @returns: validation response, if successful @rtype: L{YHSM_ValidationResult} @see: L{pyhsm.db_cmd.YHSM_Cmd_DB_Validate_OTP.parse_result} """ public_id, otp = split_id_otp(from_key) return hsm.db_validate_yubikey_otp(modhex_decode(public_id).decode('hex'), modhex_decode(otp).decode('hex') ) def validate_yubikey_with_aead(hsm, from_key, aead, key_handle): """ Try to validate an OTP from a YubiKey using the AEAD that can decrypt this YubiKey's internal secret, using the key_handle for the AEAD. `from_key' is the modhex encoded string emitted when you press the button on your YubiKey. Will only return on succesfull validation. All failures will result in an L{pyhsm.exception.YHSM_CommandFailed}. @param hsm: The YHSM instance @param from_key: The OTP from a YubiKey (in modhex) @param aead: AEAD containing the cryptographic key and permission flags @param key_handle: The key handle that can decrypt the AEAD @type hsm: L{pyhsm.YHSM} @type from_key: string @type aead: L{YHSM_GeneratedAEAD} or string @type key_handle: integer or string @returns: validation response @rtype: L{YHSM_ValidationResult} @see: L{pyhsm.validate_cmd.YHSM_Cmd_AEAD_Validate_OTP.parse_result} """ from_key = pyhsm.util.input_validate_str(from_key, 'from_key', max_len = 48) nonce = aead.nonce aead = pyhsm.util.input_validate_aead(aead) key_handle = pyhsm.util.input_validate_key_handle(key_handle) public_id, otp = split_id_otp(from_key) public_id = modhex_decode(public_id) otp = modhex_decode(otp) if not nonce: nonce = public_id.decode('hex') return hsm.validate_aead_otp(nonce, otp.decode('hex'), key_handle, aead) def modhex_decode(data): """ Convert a modhex string to ordinary hex. @param data: Modhex input @type data: string @returns: Hex @rtype: string """ t_map = string.maketrans("cbdefghijklnrtuv", "0123456789abcdef") return data.translate(t_map) def modhex_encode(data): """ Convert an ordinary hex string to modhex. @param data: Hex input @type data: string @returns: Modhex @rtype: string """ t_map = string.maketrans("0123456789abcdef", "cbdefghijklnrtuv") return data.translate(t_map) def split_id_otp(from_key): """ Separate public id from OTP given a YubiKey OTP as input. @param from_key: The OTP from a YubiKey (in modhex) @type from_key: string @returns: public_id and OTP @rtype: tuple of string """ if len(from_key) > 32: public_id, otp = from_key[:-32], from_key[-32:] elif len(from_key) == 32: public_id = '' otp = from_key else: raise pyhsm.exception.YHSM_Error("Bad from_key length %i < 32 : %s" \ % (len(from_key), from_key)) return public_id, otp pyhsm-1.2.0/pyhsm/tools/0000775000175000017500000000000013006370267015030 5ustar daindain00000000000000pyhsm-1.2.0/pyhsm/tools/__init__.py0000664000175000017500000000000013005627652017131 0ustar daindain00000000000000pyhsm-1.2.0/pyhsm/tools/generate_keys.py0000664000175000017500000001553413005627652020241 0ustar daindain00000000000000# """ Tool to generate YubiKey secret keys using YubiHSM. After generation with this tool, you can (given that you know the AES key for the key handle used in the HSM) generate a CSV file of the unencrypted AEADs formatted for YubiKey personalization using the YubiKey multi configuration utility (Windows) using the command yhsm-decrypt-aead. Example : 1) Configure HSM with key handle 99 having key 2000200020002000200020002000200020002000200020002000200020002000 2) Generate 1000 AEADs for YubiKeys using something like this (XXXX can be a customer specific public_id prefix allocated by Yubico - 0000-0009 (in modhex) are for tests) $ yhsm-generate-keys --key-handles 99 --start-public-id djXXXXcccccc \ -O /var/cache/yubikey-ksm/aeads --count 1000 3) Create CSV-file with $ yhsm-decrypt-aead --aes-key 2000...2000 --format yubikey-csv \ /var/cache/yubikey-ksm/aeads 4) Program YubiKeys using CSV file contents 5) Start a KSM to decrypt OTPs from the YubiKeys $ yhsm-yubikey-ksm --key-handle 99 """ # # Copyright (c) 2011, 2012 Yubico AB # See the file COPYING for licence statement. # import os import sys import argparse import pyhsm import pyhsm.yubikey default_device = "/dev/ttyACM0" default_dir = "/var/cache/yubikey-ksm/aeads" def parse_args(): """ Parse the command line arguments """ parser = argparse.ArgumentParser(description = "Generate secrets for YubiKeys using YubiHSM", add_help=True, formatter_class = argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument('-D', '--device', dest='device', default=default_device, required=False, help='YubiHSM device', ) parser.add_argument('-O', '--output-dir', '--aead-dir', dest='output_dir', default=default_dir, required=False, help='Output directory (AEAD base dir)', ) parser.add_argument('-c', '--count', dest='count', type=int, default=1, required=False, help='Number of secrets to generate', ) parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Enable verbose operation', ) parser.add_argument('--public-id-chars', dest='public_id_chars', type=int, default=12, required=False, help='Number of chars in generated public ids', ) parser.add_argument('--key-handles', dest='key_handles', nargs='+', required=True, help='Key handles to encrypt the generated secrets with', ) parser.add_argument('--start-public-id', dest='start_id', required=True, help='The first public id to generate AEAD for', ) parser.add_argument('--random-nonce', dest='random_nonce', required=False, action='store_true', default=False, help='Let the HSM generate nonce', ) return parser.parse_args() def args_fixup(args): if not os.path.isdir(args.output_dir): sys.stderr.write("Output directory '%s' does not exist.\n" % (args.output_dir)) sys.exit(1) keyhandles_fixup(args) try: n = int(args.start_id) except ValueError: hexstr = pyhsm.yubikey.modhex_decode(args.start_id) n = int(hexstr, 16) if(n <= 0): sys.stderr.write("Start ID must be greater than 0, was %d\n" % (n)) sys.exit(1) args.start_id = n def keyhandles_fixup(args): """ Walk through the supplied key handles and normalize them, while keeping the input format too (as value in a dictionary). The input format is used in AEAD filename paths. """ new_handles = {} for val in args.key_handles: for this in val.split(','): n = pyhsm.util.key_handle_to_int(this) new_handles[n] = this args.key_handles = new_handles def gen_keys(hsm, args): """ The main key generating loop. """ if args.verbose: print "Generating %i keys :\n" % (args.count) else: print "Generating %i keys" % (args.count) for int_id in range(args.start_id, args.start_id + args.count): public_id = ("%x" % int_id).rjust(args.public_id_chars, '0') padded_id = pyhsm.yubikey.modhex_encode(public_id) if args.verbose: print " %s" % (padded_id) num_bytes = len(pyhsm.aead_cmd.YHSM_YubiKeySecret('a' * 16, 'b' * 6).pack()) hsm.load_random(num_bytes) for kh in args.key_handles.keys(): if args.random_nonce: nonce = "" else: nonce = public_id.decode('hex') aead = hsm.generate_aead(nonce, kh) filename = output_filename(args.output_dir, args.key_handles[kh], padded_id) if args.verbose: print " %4s, %i bytes (%s) -> %s" % \ (args.key_handles[kh], len(aead.data), shorten_aead(aead), filename) aead.save(filename) if args.verbose: print "" print "\nDone\n" def shorten_aead(aead): """ Produce pretty-printable version of long AEAD. """ head = aead.data[:4].encode('hex') tail = aead.data[-4:].encode('hex') return "%s...%s" % (head, tail) def output_filename(output_dir, key_handle, public_id): """ Return an output filename for a generated AEAD. Creates a hashed directory structure using the last three bytes of the public id to get equal usage. """ parts = [output_dir, key_handle] + pyhsm.util.group(public_id, 2) path = os.path.join(*parts) if not os.path.isdir(path): os.makedirs(path) return os.path.join(path, public_id) def main(): args = parse_args() args_fixup(args) print "output dir : %s" % (args.output_dir) print "keys to generate : %s" % (args.count) print "key handles : %s" % (args.key_handles) print "start public_id : %s (0x%x)" % (args.start_id, args.start_id) print "YHSM device : %s" % (args.device) print "" if os.path.isfile(args.device): hsm = pyhsm.soft_hsm.SoftYHSM.from_file(args.device) else: hsm = pyhsm.YHSM(device=args.device) gen_keys(hsm, args) pyhsm-1.2.0/pyhsm/tools/keystore_unlock.py0000664000175000017500000001016513005627652020627 0ustar daindain00000000000000# # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. # """ Utility to unlock the key store of a YubiHSM, using the 'HSM password'/'master key'. """ import sys import pyhsm import argparse import getpass default_device = "/dev/ttyACM0" def parse_args(): """ Parse the command line arguments """ parser = argparse.ArgumentParser(description = "Unlock key store of YubiHSM", add_help = True, formatter_class = argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument('-D', '--device', dest='device', default=default_device, required=False, help='YubiHSM device', ) parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Enable verbose operation' ) parser.add_argument('--debug', dest='debug', action='store_true', default=False, help='Enable debug operation' ) parser.add_argument('--no-otp', dest='no_otp', action='store_true', default=False, help='Don\'t ask for OTP, even if YubiHSM supports it', ) parser.add_argument('--stdin', dest='stdin', action='store_true', default=False, help='Read data from stdin instead of prompting', ) args = parser.parse_args() return args def get_password(hsm, args): """ Get password of correct length for this YubiHSM version. """ expected_len = 32 name = 'HSM password' if hsm.version.have_key_store_decrypt(): expected_len = 64 name = 'master key' if args.stdin: password = sys.stdin.readline() while password and password[-1] == '\n': password = password[:-1] else: if args.debug: password = raw_input('Enter %s (press enter to skip) (will be echoed) : ' % (name)) else: password = getpass.getpass('Enter %s (press enter to skip) : ' % (name)) if len(password) <= expected_len: password = password.decode('hex') if not password: return None return password else: sys.stderr.write("ERROR: Invalid HSM password (expected max %i chars, got %i)\n" % \ (expected_len, len(password))) return 1 def get_otp(hsm, args): """ Get OTP from YubiKey. """ if args.no_otp: return None if hsm.version.have_unlock(): if args.stdin: otp = sys.stdin.readline() while otp and otp[-1] == '\n': otp = otp[:-1] else: otp = raw_input('Enter admin YubiKey OTP (press enter to skip) : ') if len(otp) == 44: # YubiHSM admin OTP's always have a public_id length of 6 bytes return otp if otp: sys.stderr.write("ERROR: Invalid YubiKey OTP\n") return None def main(): """ What will be executed when running as a stand alone program. """ args = parse_args() try: hsm = pyhsm.base.YHSM(device=args.device, debug=args.debug) if args.debug or args.verbose: print "Device : %s" % (args.device) print "Version : %s" % (hsm.info()) print "" password = get_password(hsm, args) otp = get_otp(hsm, args) if not password and not otp: print "\nAborted\n" return 1 else: if args.debug or args.verbose: print "" if hsm.unlock(password = password, otp = otp): if args.debug or args.verbose: print "OK\n" except pyhsm.exception.YHSM_Error, e: sys.stderr.write("ERROR: %s\n" % (e.reason)) return 1 return 0 pyhsm-1.2.0/pyhsm/tools/decrypt_aead.py0000664000175000017500000002540513005627652020036 0ustar daindain00000000000000# # Copyright (C) 2012-2013 Yubico AB. All rights reserved. # """ This is a tool to decrypt AEADs generated using a YubiHSM, provided that you know the key_handle used as well as the AES key used. This can be used together with yhsm-generate-keys to generate a number of AEADs, and then decrypt them to program YubiKeys accordingly. """ import os import re import sys import fcntl import argparse import traceback import pyhsm args = None yknum = 0 def parse_args(): """ Parse the command line arguments """ parser = argparse.ArgumentParser(description = 'Decrypt AEADs', add_help = True, formatter_class = argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Enable verbose operation', ) parser.add_argument('--debug', dest='debug', action='store_true', default=False, help='Enable debug operation', ) parser.add_argument('--format', dest='format', default='raw', help='Select output format (aead, raw or yubikey-csv)', ) parser.add_argument('--output-dir', dest='output_dir', help='Output dir basename (for --format aead)', metavar='DIR', ) parser.add_argument('--print-filename', dest='print_filename', action='store_true', default=False, help='Prefix each row with the AEAD filename', ) parser.add_argument('--key-handle', dest='key_handle', help='Key handle used when generating the AEADs.', metavar='HANDLE', ) parser.add_argument('--key-handle-out', dest='key_handle_out', help='Key handle used when generating *new* AEADs (with --format aead).', metavar='HANDLE', ) parser.add_argument('--aes-key', dest='aes_key', required=True, help='AES key used when generating the AEADs.', metavar='HEXSTR', ) parser.add_argument('--aes-key-out', dest='aes_key_out', required=False, help='AES key used when generating *new* AEADs (with --format aead).', metavar='HEXSTR', ) parser.add_argument('--start-public-id', dest='start_id', required=False, default=None, help='The first public id to decrypt', metavar='INT-OR-MODHEX', ) parser.add_argument('--stop-public-id', dest='stop_id', required=False, default=None, help='The last public id to decrypt', metavar='INT-OR-MODHEX', ) parser.add_argument('--fail-fast', dest='fail_fast', action='store_true', default=False, help='Terminate on the first AEAD failure, rather than keep going.', ) parser.add_argument('paths', nargs='+', help='Files and/or directories to process.', metavar='FILE-OR-DIR' ) args = parser.parse_args() # argument fixups args.format = args.format.lower() args.aes_key = args.aes_key.decode('hex') if args.key_handle: args.key_handle = pyhsm.util.key_handle_to_int(args.key_handle) if args.start_id is not None: try: n = int(args.start_id) except ValueError: hexstr = pyhsm.yubikey.modhex_decode(args.start_id) n = int(hexstr, 16) args.start_id = n if args.stop_id is not None: try: n = int(args.stop_id) except ValueError: hexstr = pyhsm.yubikey.modhex_decode(args.stop_id) n = int(hexstr, 16) args.stop_id = n # some checks if args.format == 'aead': if not args.output_dir: sys.stderr.write("error: --output-dir is required when using --format aead.\n") return False if not os.path.isdir(args.output_dir): sys.stderr.write("error: Output directory '%s' not found\n" % (args.output_dir)) return False if not args.aes_key_out: sys.stderr.write("error: --aes-key-out is required when using --format aead.\n") return False if not args.key_handle_out: sys.stderr.write("error: --key-handle-out is required when using --format aead.\n") return False # argument fixups args.aes_key_out = args.aes_key_out.decode('hex') args.key_handle_out_orig = args.key_handle_out # save to use in AEAD output paths args.key_handle_out = pyhsm.util.key_handle_to_int(args.key_handle_out) return args class MyState(): """ Class to keep track of failed files. """ def __init__(self, args): self.args = args self.failed_files = [] self.file_count = 0 def log_failed(self, fn): self.failed_files.append(fn) self.file_count += 1 def log_success(self, fn): self.file_count += 1 def should_quit(self): return (self.failed_files and self.args.fail_fast) def process_file(path, fn, args, state): """ The main function for reading a file and decrypting it. """ full_fn = os.path.join(path, fn) if not re.match("^[cbdefghijklnrtuv]+$", fn): if args.debug: sys.stderr.write("warning: Ignoring non-modhex file '%s'\n" % (full_fn)) return True if (args.start_id is not None) or (args.stop_id is not None): this = int(pyhsm.yubikey.modhex_decode(fn), 16) if (args.start_id is not None) and this < args.start_id: if args.debug: sys.stderr.write("warning: Skipping public id %s (%i) < %i\n" % (fn, this, args.start_id)) return True if (args.stop_id is not None) and this > args.stop_id: if args.debug: sys.stderr.write("warning: Skipping public id %s (%i) > %i\n" % (fn, this, args.stop_id)) return True if args.debug: sys.stderr.write("Loading AEAD : %s\n" % full_fn) aead = pyhsm.aead_cmd.YHSM_GeneratedAEAD(None, None, '') aead.load(full_fn) if not aead.nonce: # AEAD file version 0, need to fill in nonce etc. if args.key_handle is None: sys.stderr.write("error: AEAD in file %s does not include key_handle, and none provided.\n" % (full_fn)) state.log_failed(full_fn) return False aead.key_handle = args.key_handle aead.nonce = pyhsm.yubikey.modhex_decode(fn).decode('hex') if args.debug: sys.stderr.write("%s\n" % aead) sys.stderr.write("AEAD len %i : %s\n" % (len(aead.data), aead.data.encode('hex'))) pt = pyhsm.soft_hsm.aesCCM(args.aes_key, aead.key_handle, aead.nonce, aead.data, decrypt = True) if args.print_filename: print("%s " % (full_fn)), if args.format == 'raw': print(pt.encode('hex')) elif args.format == 'aead': # encrypt secrets with new key ct = pyhsm.soft_hsm.aesCCM(args.aes_key_out, args.key_handle_out, aead.nonce, pt, decrypt = False) aead_out = pyhsm.aead_cmd.YHSM_GeneratedAEAD(aead.nonce, args.key_handle_out, ct) filename = aead_filename(args.output_dir, args.key_handle_out_orig, fn) aead_out.save(filename) if args.print_filename: print "" elif args.format == 'yubikey-csv': key = pt[:pyhsm.defines.KEY_SIZE] uid = pt[pyhsm.defines.KEY_SIZE:] access_code = '00' * 6 timestamp = '' global yknum yknum += 1 print("%i,%s,%s,%s,%s,%s,,,,," % (yknum, fn, uid.encode('hex'), key.encode('hex'), access_code, timestamp, )) state.log_success(full_fn) return True def aead_filename(aead_dir, key_handle, public_id): """ Return the filename of the AEAD for this public_id, and create any missing directorys. """ parts = [aead_dir, key_handle] + pyhsm.util.group(public_id, 2) path = os.path.join(*parts) if not os.path.isdir(path): os.makedirs(path) return os.path.join(path, public_id) def safe_process_files(path, files, args, state): """ Process a number of files in a directory. Catches any exception from the processing and checks if we should fail directly or keep going. """ for fn in files: full_fn = os.path.join(path, fn) try: if not process_file(path, fn, args, state): return False except Exception, e: sys.stderr.write("error: %s\n%s\n" % (os.path.join(path, fn), traceback.format_exc())) state.log_failed(full_fn) if state.should_quit(): return False return True def walk_dir(path, args, state): """ Check all files in `path' to see if there is any requests that we should send out on the bus. """ if args.debug: sys.stderr.write("Walking %s\n" % path) for root, _dirs, files in os.walk(path): if not safe_process_files(root, files, args, state): return False if state.should_quit(): return False return True def main(): """ Main function when running as a program. """ global args args = parse_args() if not args: return 1 state = MyState(args) for path in args.paths: if os.path.isdir(path): walk_dir(path, args, state) else: safe_process_files(os.path.dirname(path), [os.path.basename(path)], args, state) if state.should_quit(): break if state.failed_files: sys.stderr.write("error: %i/%i AEADs failed\n" % (len(state.failed_files), state.file_count)) return 1 if args.debug: sys.stderr.write("Successfully processed %i AEADs\n" % (state.file_count)) pyhsm-1.2.0/pyhsm/tools/linux_add_entropy.py0000664000175000017500000000601713005627652021137 0ustar daindain00000000000000# # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. # """ Get random data from TRNG on YubiHSM and insert it into host entropy pool. Probably only works on Linux since the ioctl() request value RNDADDENTROPY seems Linux specific. """ import os import sys import fcntl import struct import argparse import pyhsm default_device = "/dev/ttyACM0" default_iterations = 100 default_entropy_ratio = 2 # number of bits of entropy per byte of random data RNDADDENTROPY = 1074287107 # from /usr/include/linux/random.h def parse_args(): """ Parse the command line arguments """ parser = argparse.ArgumentParser(description = "Add random data from YubiHSM to Linux entropy", add_help = True, formatter_class = argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument('-D', '--device', dest='device', default=default_device, required=False, help='YubiHSM device', ) parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Enable verbose operation' ) parser.add_argument('--debug', dest='debug', action='store_true', default=False, help='Enable debug operation' ) parser.add_argument('-r', '--ratio', dest='ratio', type=int, default=default_entropy_ratio, help='Bits per byte of data read to use as entropy', ) parser.add_argument('-c', '--count', dest='iterations', type=int, default=default_iterations, help='Number of iterations to run', ) args = parser.parse_args() return args def get_entropy(hsm, iterations, entropy_ratio): """ Read entropy from YubiHSM and feed it to Linux as entropy using ioctl() syscall. """ fd = os.open("/dev/random", os.O_WRONLY) # struct rand_pool_info { # int entropy_count; # int buf_size; # __u32 buf[0]; # }; fmt = 'ii%is' % (pyhsm.defines.YSM_MAX_PKT_SIZE - 1) for _ in xrange(iterations): rnd = hsm.random(pyhsm.defines.YSM_MAX_PKT_SIZE - 1) this = struct.pack(fmt, entropy_ratio * len(rnd), len(rnd), rnd) fcntl.ioctl(fd, RNDADDENTROPY, this) os.close(fd) def main(): """ What will be executed when running as a stand alone program. """ args = parse_args() try: s = pyhsm.base.YHSM(device=args.device, debug=args.debug) get_entropy(s, args.iterations, args.ratio) return 0 except pyhsm.exception.YHSM_Error as e: sys.stderr.write("ERROR: %s" % (e.reason)) return 1 pyhsm-1.2.0/pyhsm/util.py0000664000175000017500000001315513002347641015220 0ustar daindain00000000000000""" collection of utility functions """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import struct __all__ = [ # constants # functions 'hexdump', 'group', 'key_handle_to_int', # classes ] import pyhsm.exception def hexdump(src, length=8): """ Produce a string hexdump of src, for debug output.""" if not src: return str(src) src = input_validate_str(src, 'src') offset = 0 result = '' for this in group(src, length): hex_s = ' '.join(["%02x" % ord(x) for x in this]) result += "%04X %s\n" % (offset, hex_s) offset += length return result def group(data, num): """ Split data into chunks of num chars each """ return [data[i:i+num] for i in xrange(0, len(data), num)] def key_handle_to_int(this): """ Turn "123" into 123 and "KSM1" into 827151179 (0x314d534b, 'K' = 0x4b, S = '0x53', M = 0x4d). YHSM is little endian, so this makes the bytes KSM1 appear in the most human readable form in packet traces. """ try: num = int(this) return num except ValueError: if this[:2] == "0x": return int(this, 16) if (len(this) == 4): num = struct.unpack(' max_len: raise pyhsm.exception.YHSM_InputTooLong(name, max_len, len(string)) if exact_len != None and len(string) != exact_len: raise pyhsm.exception.YHSM_WrongInputSize(name, exact_len, len(string)) return string def input_validate_int(value, name, max_value=None): """ Input validation for integers. """ if type(value) is not int: raise pyhsm.exception.YHSM_WrongInputType(name, int, type(value)) if max_value != None and value > max_value: raise pyhsm.exception.YHSM_WrongInputSize(name, max_value, value) return value def input_validate_nonce(nonce, name='nonce', pad = False): """ Input validation for nonces. """ if type(nonce) is not str: raise pyhsm.exception.YHSM_WrongInputType( \ name, str, type(nonce)) if len(nonce) > pyhsm.defines.YSM_AEAD_NONCE_SIZE: raise pyhsm.exception.YHSM_InputTooLong( name, pyhsm.defines.YSM_AEAD_NONCE_SIZE, len(nonce)) if pad: return nonce.ljust(pyhsm.defines.YSM_AEAD_NONCE_SIZE, chr(0x0)) else: return nonce def input_validate_key_handle(key_handle, name='key_handle'): """ Input validation for key_handles. """ if type(key_handle) is not int: try: return key_handle_to_int(key_handle) except pyhsm.exception.YHSM_Error: raise pyhsm.exception.YHSM_WrongInputType(name, int, type(key_handle)) return key_handle def input_validate_yubikey_secret(data, name='data'): """ Input validation for YHSM_YubiKeySecret or string. """ if isinstance(data, pyhsm.aead_cmd.YHSM_YubiKeySecret): data = data.pack() return input_validate_str(data, name) def input_validate_aead(aead, name='aead', expected_len=None, max_aead_len = pyhsm.defines.YSM_AEAD_MAX_SIZE): """ Input validation for YHSM_GeneratedAEAD or string. """ if isinstance(aead, pyhsm.aead_cmd.YHSM_GeneratedAEAD): aead = aead.data if expected_len != None: return input_validate_str(aead, name, exact_len = expected_len) else: return input_validate_str(aead, name, max_len=max_aead_len) def validate_cmd_response_int(name, got, expected): """ Check that some value returned in the response to a command matches what we put in the request (the command). """ if got != expected: raise(pyhsm.exception.YHSM_Error("Bad %s in response (got %i, expected %i)" \ % (name, got, expected))) return got def validate_cmd_response_hex(name, got, expected): """ Check that some value returned in the response to a command matches what we put in the request (the command). """ if got != expected: raise(pyhsm.exception.YHSM_Error("Bad %s in response (got 0x%x, expected 0x%x)" \ % (name, got, expected))) return got def validate_cmd_response_str(name, got, expected, hex_encode=True): """ Check that some value returned in the response to a command matches what we put in the request (the command). """ if got != expected: if hex_encode: got_s = got.encode('hex') exp_s = expected.encode('hex') else: got_s = got exp_s = expected raise(pyhsm.exception.YHSM_Error("Bad %s in response (got %s, expected %s)" \ % (name, got_s, exp_s))) return got def validate_cmd_response_nonce(got, used): """ Check that the returned nonce matches nonce used in request. A request nonce of 000000000000 means the HSM should generate a nonce internally though, so if 'used' is all zeros we actually check that 'got' does NOT match 'used'. """ if used == '000000000000'.decode('hex'): if got == used: raise(pyhsm.exception.YHSM_Error("Bad nonce in response (got %s, expected HSM generated nonce)" \ % (got.encode('hex')))) return got return validate_cmd_response_str('nonce', got, used) pyhsm-1.2.0/pyhsm/oath_totp.py0000664000175000017500000000226713002421756016246 0ustar daindain00000000000000""" helper functions to work with OATH TOTP (RFC6238) OTP's and YubiHSM """ # Copyright (c) 2016 Storedsafe AB # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import datetime import time from pyhsm.oath_hotp import search_for_oath_code as search_hotp __all__ = [ # constants # functions 'search_for_oath_code', # classes ] def search_for_oath_code(hsm, key_handle, nonce, aead, user_code, interval=30, tolerance=0): """ Try to validate an OATH TOTP OTP generated by a token whose secret key is available to the YubiHSM through the AEAD. The parameter `aead' is either a string, or an instance of YHSM_GeneratedAEAD. Returns timecounter value on successful auth, and None otherwise. """ # timecounter is the lowest acceptable value based on tolerance timecounter = timecode(datetime.datetime.now(), interval) - tolerance return search_hotp( hsm, key_handle, nonce, aead, timecounter, user_code, 1 + 2*tolerance) def timecode(time_now, interval): """ make integer and divide by time interval of valid OTP """ i = time.mktime(time_now.timetuple()) return int(i / interval) pyhsm-1.2.0/pyhsm/val/0000775000175000017500000000000013006370267014452 5ustar daindain00000000000000pyhsm-1.2.0/pyhsm/val/__init__.py0000664000175000017500000000000013005606114016541 0ustar daindain00000000000000pyhsm-1.2.0/pyhsm/val/validate_otp.py0000664000175000017500000000635713005606114017502 0ustar daindain00000000000000# # Tool to validate a YubiKey OTP using the YubiHSM internal database. # # This requires that you have imported the secret AES key of the YubiKey # into the database with `../yubikey-ksm/yhsm-import-keys --internal-db' # or otherwise. # # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. # import os import re import sys import struct import argparse import pyhsm import pyhsm.yubikey default_device = "/dev/ttyACM0" def parse_args(): """ Parse the command line arguments """ global default_device parser = argparse.ArgumentParser(description = "Validate YubiKey OTP's using YubiHSM", add_help=True ) parser.add_argument('-D', '--device', dest='device', default=default_device, required=False, help='YubiHSM device (default : %s).' % default_device ) parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Enable verbose operation.' ) parser.add_argument('--debug', dest='debug', action='store_true', default=False, help='Enable debug operation.' ) group = parser.add_argument_group('Modes', 'What you want to validate') mode_group = group.add_mutually_exclusive_group(required = True) mode_group.add_argument('--otp', dest='otp', help='The output from your YubiKey.' ) mode_group.add_argument('--oath', dest='oath', help='The output from your OATH-HOTP token.' ) args = parser.parse_args() return args def validate_otp(hsm, args): """ Validate an OTP. """ try: res = pyhsm.yubikey.validate_otp(hsm, args.otp) if args.verbose: print "OK counter=%04x low=%04x high=%02x use=%02x" % \ (res.use_ctr, res.ts_low, res.ts_high, res.session_ctr) return 0 except pyhsm.exception.YHSM_CommandFailed, e: if args.verbose: print "%s" % (pyhsm.defines.status2str(e.status)) # figure out numerical response code for r in [pyhsm.defines.YSM_OTP_INVALID, \ pyhsm.defines.YSM_OTP_REPLAY, \ pyhsm.defines.YSM_ID_NOT_FOUND]: if e.status == r: return r - pyhsm.defines.YSM_RESPONSE # not found return 0xff def validate_oath(hsm, args): """ Validate an OATH OTP. """ print "ERROR: Not implemented, try 'yhsm-validation-server'." return 0 def main(): args = parse_args() if args.debug: print "YHSM device : %s" % (args.device) print "" hsm = pyhsm.YHSM(device = args.device, debug=args.debug) status = 1 if args.otp: status = validate_otp(hsm, args) elif args.oath: status = validate_oath(hsm, args) return status if __name__ == '__main__': sys.exit(main()) pyhsm-1.2.0/pyhsm/val/init_oath_token.py0000664000175000017500000001730413005606114020177 0ustar daindain00000000000000# # Tool to add an OATH token to the yhsm-validation-server database. # # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. # import sys import struct import sqlite3 import argparse import pyhsm import pyhsm.oath_hotp from hashlib import sha1 default_device = "/dev/ttyACM0" default_db_file = "/var/yubico/yhsm-validation-server.db" def parse_args(): """ Parse the command line arguments """ global default_device parser = argparse.ArgumentParser(description = 'Initialize OATH token for use with yhsm-validation-server', add_help=True, formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument('-D', '--device', dest='device', default=default_device, required=False, help='YubiHSM device', ) parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Enable verbose operation', ) parser.add_argument('--debug', dest='debug', action='store_true', default=False, help='Enable debug operation', ) parser.add_argument('--force', dest='force', action='store_true', default=False, help='Overwrite any present entry', ) parser.add_argument('--key-handle', dest='key_handle', required=True, help='Key handle to create AEAD', metavar='HANDLE', ) parser.add_argument('--uid', dest='uid', required=True, help='User ID', metavar='STR', ) parser.add_argument('--oath-c', dest='oath_c', required=False, default = 0, help='Initial OATH counter value', metavar='INT', ) parser.add_argument('--test-oath-window', dest='look_ahead', required=False, default = 10, help='Number of codes to search with --test-code', metavar='INT', ) parser.add_argument('--test-code', dest='test_code', type=int, required=False, help='Optional OTP from token for verification', metavar='INT', ) parser.add_argument('--oath-k', dest='oath_k', required=False, help='The secret key of the token, hex encoded', metavar='HEXSTR', ) parser.add_argument('--db-file', dest='db_file', default=default_db_file, required=False, help='DB file for storing AEAD\'s for --pwhash and --oath in the yhsm-validation-server', metavar='FN', ) args = parser.parse_args() return args def args_fixup(args): keyhandles_fixup(args) def keyhandles_fixup(args): args.key_handle = pyhsm.util.key_handle_to_int(args.key_handle) def generate_aead(hsm, args): """ Protect the oath-k in an AEAD. """ key = get_oath_k(args) # Enabled flags 00010000 = YSM_HMAC_SHA1_GENERATE flags = struct.pack("< I", 0x10000) hsm.load_secret(key + flags) nonce = hsm.get_nonce().nonce aead = hsm.generate_aead(nonce, args.key_handle) if args.debug: print "AEAD: %s (%s)" % (aead.data.encode('hex'), aead) return nonce, aead def validate_oath_c(hsm, args, nonce, aead): if args.test_code: if args.verbose: print "Trying to validate the OATH counter value in the range %i..%i." \ % (args.oath_c, args.oath_c + args.look_ahead) counter = pyhsm.oath_hotp.search_for_oath_code(hsm, args.key_handle, nonce, aead, \ args.oath_c, args.test_code, args.look_ahead \ ) if type(counter) != int: sys.stderr.write("Failed validating OTP %s (in range %i..%i) using supplied key.\n" \ % (args.test_code, args.oath_c, args.oath_c + args.look_ahead)) sys.exit(1) if args.verbose: print "OATH C==%i validated with code %s" % (counter - 1, args.test_code) return counter return args.oath_c def get_oath_k(args): """ Get the OATH K value (secret key), either from args or by prompting. """ if args.oath_k: decoded = args.oath_k.decode('hex') else: t = raw_input("Enter OATH key (hex encoded) : ") decoded = t.decode('hex') if len(decoded) > 20: decoded = sha1(decoded).digest() decoded = decoded.ljust(20, '\0') return decoded class ValOathDb(): """ Provides access to database with AEAD's and other information. """ def __init__(self, filename): self.filename = filename self.conn = sqlite3.connect(self.filename) self.create_table() def create_table(self): c = self.conn.cursor() c.execute("CREATE TABLE IF NOT EXISTS oath " \ "(key TEXT PRIMARY KEY, nonce TEXT, key_handle INTEGER, aead TEXT, oath_C INTEGER, oath_T INTEGER)") def add(self, entry): """ Add entry to database. """ c = self.conn.cursor() c.execute("INSERT INTO oath (key, aead, nonce, key_handle, oath_C, oath_T) VALUES (?, ?, ?, ?, ?, ?)", (entry.data["key"], \ entry.data["aead"], \ entry.data["nonce"], \ entry.data["key_handle"], \ entry.data["oath_C"], \ entry.data["oath_T"],)) self.conn.commit() return c.rowcount == 1 def delete(self, entry): """ Delete entry from database. """ c = self.conn.cursor() c.execute("DELETE FROM oath WHERE key = ?", (entry.data["key"],)) class ValOathEntry(): """ Class to hold a row of ValOathDb. """ def __init__(self, row): if row: self.data = row def store_oath_entry(args, nonce, aead, oath_c): """ Store the AEAD in the database. """ data = {"key": args.uid, "aead": aead.data.encode('hex'), "nonce": nonce.encode('hex'), "key_handle": args.key_handle, "oath_C": oath_c, "oath_T": None, } entry = ValOathEntry(data) db = ValOathDb(args.db_file) try: if args.force: db.delete(entry) db.add(entry) except sqlite3.IntegrityError, e: sys.stderr.write("ERROR: %s\n" % (e)) return False return True def main(): args = parse_args() args_fixup(args) print "Key handle : %s" % (args.key_handle) print "YHSM device : %s" % (args.device) print "" hsm = pyhsm.YHSM(device = args.device, debug=args.debug) nonce, aead = generate_aead(hsm, args) oath_c = validate_oath_c(hsm, args, nonce, aead) if not store_oath_entry(args, nonce, aead, oath_c): return 1 if __name__ == '__main__': sys.exit(main()) pyhsm-1.2.0/pyhsm/val/validation_server.py0000664000175000017500000006744213005606114020551 0ustar daindain00000000000000# # Copyright (c) 2011, 2012 Yubico AB # See the file COPYING for licence statement. # """ Credential validation server utilizing YubiHSM. Modes of operation : OTP - YubiKey validation using internal DB in YubiHSM. The YubiHSM can take care of complete Yubico OTP validation - including storing seen counter values in an internal database. There is an --otp mode available that tries to be compatible with YK-VAL, and there is a --short-otp mode that gives responses looking like YK-KSM. HOTP - OATH-HOTP validation using secrets stored on host computer (in secure AEADs only decryptable inside YubiHSM). The HMAC-SHA1 of the OATH counter value is done inside the YubiHSM, so the OATH Key is never exposed outside the YubiHSM. TOTP - OATH-TOTP validation using secrets stored on host computer (in secure AEADs only decryptable inside YubiHSM). The HMAC-SHA1 of the OATH timecounter value is compared, so the OATH Key is never exposed outside the YubiHSM. PWHASH - Uses AEAD plaintext compare in the YubiHSM to see if a supplied password hash matches the password hash used in an earlier 'set' operation. These AEADs can be generated using `yhsm-password-auth.py --set ...'. All these modes must be explicitly enabled on the command line to be allowed (--otp, --hotp, --totp and --pwhash). Examples using OATH-HOTP : > GET /yhsm/validate?hotp=ubftcdcdckcf359152 HTTP/1.1 ... < HTTP/1.0 200 OK < OK counter=0003 same again (replay), differently formatted : > GET /yhsm/validate?uid=ubftcdcdckcf&hotp=359152 HTTP/1.1 ... < HTTP/1.0 200 OK < ERR Could not validate OATH-HOTP OTP Examples using OATH-TOTP : > GET /yhsm/validate?totp=ubftcdcdckcf216781 HTTP/1.1 ... < HTTP/1.0 200 OK < OK timecounter=2ed5376 same again (but outside of time tolerance), differently formatted : > GET /yhsm/validate?uid=ubftcdcdckcf&totp=359152 HTTP/1.1 ... < HTTP/1.0 200 OK < ERR Could not validate OATH-TOTP OTP Example PWHASH (AEAD and NONCE as returned by `yhsm-password-auth.py --set ...') : > GET /yhsm/validate?pwhash=pbkdf2-of-password-here&aead=2b70...2257&nonce=010203040506&kh=8192 HTTP/1.1 ... < HTTP/1.0 200 OK < OK pwhash validated """ import re import os import sys import time import hmac import syslog import serial import socket import base64 import hashlib import sqlite3 import argparse import urlparse import BaseHTTPServer import pyhsm import pyhsm.yubikey import pyhsm.oath_hotp import pyhsm.oath_totp default_device = "/dev/ttyACM0" default_serve_url = "/yhsm/validate?" default_db_file = "/var/yubico/yhsm-validation-server.db" default_clients_file = "/var/yubico/yhsm-validation-server_client-id.conf" default_hotp_window = 5 default_totp_interval = 30 default_totp_tolerance = 1 default_pid_file = None ykotp_valid_input = re.compile('^[cbdefghijklnrtuv]{32,48}$') hotp_valid_input = re.compile('^[cbdefghijklnrtuv0-9]{6,20}$') totp_valid_input = re.compile('^[cbdefghijklnrtuv0-9]{6,20}$') hsm = None args = None saved_key_handle = None client_ids = {} class YHSM_VALRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): """ Handle HTTP GET requests according to configuration in global variable `args'. """ def do_GET(self): """ Process validation GET requests. All modes of validation (OTP, OATH and PWHASH) must be explicitly enabled in `args' to be allowed. """ if self.path.startswith(args.serve_url): res = None log_res = None mode = None params = urlparse.parse_qs(self.path[len(args.serve_url):]) if "otp" in params: if args.mode_short_otp: # YubiKey internal db OTP in KSM mode mode = 'YubiKey OTP (short)' res = validate_yubikey_otp_short(self, params) elif args.mode_otp: # YubiKey internal db OTP validation 2.0 mode = 'YubiKey OTP' res = validate_yubikey_otp(self, params) #status = [x for x in res.split('\n') if x.startswith("status=")] #if len(status) == 1: # res = status[0][7:] log_res = '&'.join(res.split('\n')) else: res = "ERR 'otp/otp2' disabled" elif "hotp" in params: if args.mode_hotp: mode = 'OATH-HOTP' res = validate_oath_hotp(self, params) else: res = "ERR 'hotp' disabled" elif "totp" in params: if args.mode_totp: mode = 'OATH-TOTP' res = validate_oath_totp(self, params) else: res = "ERR 'totp' disabled" elif "pwhash" in params: if args.mode_pwhash: mode = 'Password hash' res = validate_pwhash(self, params) else: res = "ERR 'pwhash' disabled" if not log_res: log_res = res self.log_message("%s validation result: %s -> %s", mode, self.path, log_res) if res != None: self.send_response(200) self.send_header('Content-type', 'text/plain') self.end_headers() self.wfile.write(res) self.wfile.write("\n") else: self.log_error ("No validation result to '%s' (responding 403)" % (self.path)) self.send_response(403, 'Forbidden') self.end_headers() else: self.log_error ("Bad URL '%s' - I'm serving '%s' (responding 403)" % (self.path, args.serve_url)) self.send_response(403, 'Forbidden') self.end_headers() def log_error(self, fmt, *fmt_args): """ Log to syslog. """ msg = self.my_address_string() + " - - " + fmt % fmt_args my_log_message(args, syslog.LOG_ERR, msg) def log_message(self, fmt, *fmt_args): """ Log to syslog. """ msg = self.my_address_string() + " - - " + fmt % fmt_args my_log_message(args, syslog.LOG_INFO, msg) def my_address_string(self): """ For logging client host without resolving. """ return self.client_address[0] class YHSM_VALServer(BaseHTTPServer.HTTPServer): """ Wrapper class to properly initialize address_family for IPv6 addresses. """ def __init__(self, server_address, req_handler): if ":" in server_address[0]: self.address_family = socket.AF_INET6 BaseHTTPServer.HTTPServer.__init__(self, server_address, req_handler) def validate_yubikey_otp_short(self, params): """ Validate YubiKey OTP using YubiHSM internal database. """ from_key = params["otp"][0] if not re.match(ykotp_valid_input, from_key): self.log_error("IN: %s, Invalid OTP" % (from_key)) return "ERR Invalid OTP" try: res = pyhsm.yubikey.validate_otp(hsm, from_key) return "OK counter=%04x low=%04x high=%02x use=%02x" % \ (res.use_ctr, res.ts_low, res.ts_high, res.session_ctr) except pyhsm.exception.YHSM_CommandFailed, e: return "ERR %s" % (pyhsm.defines.status2str(e.status)) def validate_yubikey_otp(self, params): """ Validate YubiKey OTP using YubiHSM internal database. """ vres = {} from_key = params["otp"][0] if not re.match(ykotp_valid_input, from_key): self.log_error("IN: %s, Invalid OTP" % (from_key)) vres["status"] = "BAD_OTP" else: vres["otp"] = from_key if not "nonce" in params: self.log_error("IN: %s, no nonce" % (from_key)) vres["status"] = "MISSING_PARAMETER" else: nonce = params["nonce"][0] if len(nonce) < 16 or len(nonce) > 40: self.log_error("IN: %s, bad nonce : %s" % (from_key, nonce)) vres["status"] = "MISSING_PARAMETER" else: vres["nonce"] = nonce if "sl" in params and not (params["sl"] == "100" or params["sl"] == "secure"): self.log_error("IN: %s, sync level unsupported" % (from_key)) vres["status"] = "BACKEND_ERROR" sig, client_key, = check_signature(params) if sig != True: self.log_error("IN: %s, signature validation error" % (from_key)) if client_key == None: vres["status"] = "NO_SUCH_CLIENT" # To be compatible with YK-VAL, we will sign this response using a null key client_key = chr(0) else: vres["status"] = "BAD_SIGNATURE" if "status" not in vres: try: res = pyhsm.yubikey.validate_otp(hsm, from_key) vres.update({"status": "OK", "sessioncounter": str(res.use_ctr), # known confusion "sessionuse": str(res.session_ctr), # known confusion "timestamp": str((res.ts_high << 16 | res.ts_low) / 8) } ) if "sl" in params: vres["sl"] = "100" if "timestamp" in params: vres["t"] = time.strftime("%FT%TZ0000", time.gmtime()) except pyhsm.exception.YHSM_CommandFailed, e: if e.status == pyhsm.defines.YSM_ID_NOT_FOUND: vres["status"] = "BAD_OTP" elif e.status == pyhsm.defines.YSM_OTP_REPLAY: vres["status"] = "REPLAYED_OTP" elif e.status == pyhsm.defines.YSM_OTP_INVALID: vres["status"] = "BAD_OTP" else: vres["status"] = "BACKEND_ERROR" self.log_error("IN: %s, validation result %s (replying %s)" % (from_key, pyhsm.defines.status2str(e.status), vres["status"])) return make_otp_response(vres, client_key) def make_otp_response(vres, client_key): """ Create validation response (signed, if a client key is supplied). """ if client_key is not None: sig = make_signature(vres, client_key) vres['h'] = sig # produce "key=value" pairs from vres pairs = [x + "=" + ''.join(vres[x]) for x in sorted(vres.keys())] return '\n'.join(pairs) def check_signature(params): """ Verify the signature of the parameters in an OTP v2.0 verify request. Returns ValResultBool, Key """ if 'id' in params: try: id_int = int(params['id'][0]) except: my_log_message(args, syslog.LOG_INFO, "Non-numerical client id (%s) in request." % (params['id'][0])) return False, None key = client_ids.get(id_int) if key: if 'h' in params: sig = params['h'][0] good_sig = make_signature(params, key) if sig == good_sig: #my_log_message(args, syslog.LOG_DEBUG, "Good signature (client id '%i')" % id_int) return True, key else: my_log_message(args, syslog.LOG_INFO, "Bad signature from client id '%i' (%s, expected %s)." \ % (id_int, sig, good_sig)) else: my_log_message(args, syslog.LOG_INFO, "Client id (%i) but no HMAC in request." % (id_int)) return False, key else: my_log_message(args, syslog.LOG_INFO, "Unknown client id '%i'" % (id_int)) return False, None return True, None def make_signature(params, hmac_key): """ Calculate a HMAC-SHA-1 (using hmac_key) of all the params except "h=". Returns base64 encoded signature as string. """ # produce a list of "key=value" for all entries in params except `h' pairs = [x + "=" + ''.join(params[x]) for x in sorted(params.keys()) if x != "h"] sha = hmac.new(hmac_key, '&'.join(pairs), hashlib.sha1) return base64.b64encode(sha.digest()) def validate_oath_hotp(self, params): """ Validate OATH-HOTP code using YubiHSM HMAC-SHA1 hashing with token keys secured in AEAD's that we have stored in an SQLite3 database. """ from_key = params["hotp"][0] if not re.match(hotp_valid_input, from_key): self.log_error("IN: %s, Invalid OATH-HOTP OTP" % (params)) return "ERR Invalid OATH-HOTP OTP" uid, otp, = get_oath_hotp_bits(params) if not uid or not otp: self.log_error("IN: %s, could not get UID/OTP ('%s'/'%s')" % (params, uid, otp)) return "ERR Invalid OATH-HOTP input" if args.debug: print "OATH-HOTP uid %s, OTP %s" % (uid, otp) # Fetch counter value for `uid' from database try: db = ValOathDb(args.db_file) entry = db.get(uid) except Exception, e: self.log_error("IN: %s, database error : '%s'" % (params, e)) return "ERR Internal error" # Check for correct OATH-HOTP OTP nonce = entry.data["nonce"].decode('hex') aead = entry.data["aead"].decode('hex') new_counter = pyhsm.oath_hotp.search_for_oath_code(hsm, entry.data["key_handle"], nonce, aead, \ entry.data["oath_c"], otp, args.look_ahead) if args.debug: print "OATH-HOTP %i..%i -> new C == %s" \ % (entry.data["oath_c"], entry.data["oath_c"] + args.look_ahead, new_counter) if type(new_counter) != int: # XXX increase 'throttling parameter' to make brute forcing harder/impossible return "ERR Could not validate OATH-HOTP OTP" try: # Must successfully store new_counter before we return OK if db.update_oath_hotp_c(entry, new_counter): return "OK counter=%04x" % (new_counter) else: return "ERR replayed OATH-HOTP" except Exception, e: self.log_error("IN: %s, database error updating counter : %s" % (params, e)) return "ERR Internal error" def validate_oath_totp(self, params): """ Validate OATH-TOTP code using YubiHSM HMAC-SHA1 hashing with token keys secured in AEAD's that we have stored in an SQLite3 database. """ from_key = params["totp"][0] if not re.match(totp_valid_input, from_key): self.log_error("IN: %s, Invalid OATH-TOTP OTP" % (params)) return "ERR Invalid OATH-TOTP OTP" uid, otp, = get_oath_totp_bits(params) if not uid or not otp: self.log_error("IN: %s, could not get UID/OTP ('%s'/'%s')" % (params, uid, otp)) return "ERR Invalid OATH-TOTP input" if args.debug: print "OATH-TOTP uid %s, OTP %s" % (uid, otp) # Fetch counter value for `uid' from database try: db = ValOathDb(args.db_file) entry = db.get(uid) except Exception, e: self.log_error("IN: %s, database error : '%s'" % (params, e)) return "ERR Internal error" # Check for correct OATH-TOTP OTP nonce = entry.data["nonce"].decode('hex') aead = entry.data["aead"].decode('hex') new_timecounter = pyhsm.oath_totp.search_for_oath_code( hsm, entry.data["key_handle"], nonce, aead, otp, args.interval, args.tolerance) if args.debug: print "OATH-TOTP counter: %i, interval: %i -> new timecounter == %s" \ % (entry.data["oath_c"], args.interval, new_timecounter) if type(new_timecounter) != int: return "ERR Could not validate OATH-TOTP OTP" try: # Must successfully store new_timecounter before we return OK # Can use existing hotp function since it would be identical if db.update_oath_hotp_c(entry, new_timecounter): return "OK timecounter=%04x" % (new_timecounter) else: return "ERR replayed OATH-TOTP" except Exception, e: self.log_error("IN: %s, database error updating counter : %s" % (params, e)) return "ERR Internal error" def validate_pwhash(_self, params): """ Validate password hash using YubiHSM. """ pwhash, nonce, aead, key_handle = get_pwhash_bits(params) d_aead = aead.decode('hex') plaintext_len = len(d_aead) - pyhsm.defines.YSM_AEAD_MAC_SIZE pw = pwhash.ljust(plaintext_len, chr(0x0)) if hsm.validate_aead(nonce.decode('hex'), key_handle, d_aead, pw): return "OK pwhash validated" return "ERR Could not validate pwhash" def get_pwhash_bits(params): """ Extract bits for password hash validation from params. """ if not "pwhash" in params or \ not "nonce" in params or \ not "aead" in params or \ not "kh" in params: raise Exception("Missing required parameter in request (pwhash, nonce, aead or kh)") pwhash = params["pwhash"][0] nonce = params["nonce"][0] aead = params["aead"][0] key_handle = pyhsm.util.key_handle_to_int(params["kh"][0]) return pwhash, nonce, aead, key_handle def get_oath_hotp_bits(params): """ Extract the OATH-HOTP uid and OTP from params. """ if "uid" in params: return params["uid"][0], int(params["hotp"][0]) m = re.match("^([cbdefghijklnrtuv]*)([0-9]{6,8})", params["hotp"][0]) uid, otp, = m.groups() return uid, int(otp), def get_oath_totp_bits(params): """ Extract the OATH-TOTP uid and OTP from params. """ if "uid" in params: return params["uid"][0], int(params["totp"][0]) m = re.match("^([cbdefghijklnrtuv]*)([0-9]{6,8})", params["totp"][0]) uid, otp, = m.groups() return uid, int(otp), class ValOathDb(): """ Provides access to database with AEAD's and other information for OATH tokens. """ def __init__(self, filename): self.filename = filename self.conn = sqlite3.connect(self.filename) self.conn.row_factory = sqlite3.Row def get(self, key): """ Fetch entry from database. """ c = self.conn.cursor() for row in c.execute("SELECT key, nonce, key_handle, aead, oath_C, oath_T FROM oath WHERE key = ?", (key,)): return ValOathEntry(row) raise Exception("OATH token for '%s' not found in database (%s)" % (key, self.filename)) def update_oath_hotp_c(self, entry, new_c): """ Update the OATH-HOTP counter value for `entry' in the database. Use SQL statement to ensure we only ever increase the counter. """ key = entry.data["key"] c = self.conn.cursor() c.execute("UPDATE oath SET oath_c = ? WHERE key = ? AND ? > oath_c", (new_c, key, new_c,)) self.conn.commit() return c.rowcount == 1 class ValOathEntry(): """ Class to hold a row of ValOathDb. """ def __init__(self, row): if row: self.data = row def parse_args(): """ Parse the command line arguments """ parser = argparse.ArgumentParser(description = "Validate secrets using YubiHSM", add_help=True, formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument('-D', '--device', dest='device', default=default_device, required=False, help='YubiHSM device', ) parser.add_argument('-U', '--serve-url', dest='serve_url', default=default_serve_url, required=False, help='Base URL for validation web service', ) parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Enable verbose operation', ) parser.add_argument('--debug', dest='debug', action='store_true', default=False, help='Enable debug operation', ) parser.add_argument('--port', dest='listen_port', type=int, default=8003, required=False, help='Port to listen on', metavar='PORT', ) parser.add_argument('--addr', dest='listen_addr', default="127.0.0.1", required=False, help='Address to bind to', metavar='ADDR', ) parser.add_argument('--hmac-kh', dest='hmac_kh', required=False, default=0, help='Key handle to use for creating HMAC-SHA1 hashes', metavar='KEY_HANDLE', ) parser.add_argument('--short-otp', dest='mode_short_otp', action='store_true', default=False, help='Enable YubiKey OTP validation (KSM style response)', ) parser.add_argument('--otp', dest='mode_otp', action='store_true', default=False, help='Enable YubiKey OTP validation 2.0', ) parser.add_argument('--hotp', dest='mode_hotp', action='store_true', default=False, help='Enable OATH-HOTP validation', ) parser.add_argument('--totp', dest='mode_totp', action='store_true', default=False, help='Enable OATH-TOTP validation', ) parser.add_argument('--pwhash', dest='mode_pwhash', action='store_true', default=False, help='Enable password hash validation', ) parser.add_argument('--db-file', dest='db_file', default=default_db_file, required=False, help='DB file for storing AEAD\'s etc. for --pwhash and --hotp', metavar='FILENAME', ) # XXX bad interaction with argparse.ArgumentDefaultsHelpFormatter here - we don't want to # use default=default_clients_file since we need to know if this option was specified explicitly # or not. parser.add_argument('--clients-file', dest='clients_file', default=None, required=False, help='File with OTP validation clients shared secrets. for --otp. Default : %s' % (default_clients_file), metavar='FILENAME', ) parser.add_argument('--hotp-window', dest='look_ahead', type=int, required=False, default = default_hotp_window, help='Number of OATH-HOTP codes to search', metavar='NUM', ) parser.add_argument('--totp-interval', dest='interval', type=int, required=False, default = default_totp_interval, help='Timeframe in seconds for a valid OATH-TOTP code', metavar='NUM', ) parser.add_argument('--totp-tolerance', dest='tolerance', type=int, required=False, default = default_totp_tolerance, help='Tolerance in time-steps for a valid OATH-TOTP code', metavar='NUM', ) parser.add_argument('--pid-file', dest='pid_file', default=default_pid_file, required=False, help='PID file', metavar='FILENAME', ) return parser.parse_args() def args_fixup(): """ Various cleanups/initializations based on result of parse_args(). """ global saved_key_handle saved_key_handle = args.hmac_kh args.key_handle = pyhsm.util.key_handle_to_int(args.hmac_kh) if not (args.mode_otp or args.mode_short_otp or args.mode_totp or args.mode_hotp or args.mode_pwhash): my_log_message(args, syslog.LOG_ERR, 'No validation mode enabled') sys.exit(1) global client_ids if args.clients_file != None: if not args.mode_otp: my_log_message(args, syslog.LOG_ERR, 'Clients file should only be used with --otp.') sys.exit(1) client_ids = load_clients_file(args.clients_file) if not client_ids: my_log_message(args, syslog.LOG_ERR, 'Failed loading clients file "%s"' % (args.clients_file)) sys.exit(1) else: # we accept failure to load this file when the default is used loaded_client_ids = load_clients_file(default_clients_file) if loaded_client_ids: args.clients_file = default_clients_file client_ids = loaded_client_ids def load_clients_file(filename): """ Load a list of base64 encoded shared secrets for numerical client ids. Returns a dict. Format of file is expected to be # This is a comment. Blank lines are OK. 123,c2hhcmVkIHNlY3JldA== 456,MTIzNDU2Nzg5MDEyMw== """ res = {} content = [] try: fhandle = file(filename) content = fhandle.readlines() fhandle.close() except IOError: return None linenum = 0 for line in content: linenum += 1 while line.endswith("\r") or line.endswith("\n"): line = line[:-1] if re.match("(^\s*#|^\s*$)", line): # skip comments and empty lines continue parts = [x.strip() for x in line.split(',')] try: if len(parts) != 2: raise Exception() id_num = int(parts[0]) key = base64.b64decode(parts[1]) res[id_num] = key except: my_log_message(args, syslog.LOG_ERR, 'Bad data on line %i of clients file "%s" : "%s"' % (linenum, filename, line)) return None return res def write_pid_file(fn): """ Create a file with our PID. """ if not fn: return None if fn == '' or fn == "''": # work around argument passings in init-scripts return None f = open(fn, "w") f.write("%s\n" % (os.getpid())) f.close() def run(): """ Start the BaseHTTPServer and serve requests forever. """ server_address = (args.listen_addr, args.listen_port) httpd = YHSM_VALServer(server_address, YHSM_VALRequestHandler) my_log_message(args, syslog.LOG_INFO, "Serving requests to 'http://%s:%s%s' (YubiHSM: '%s')" \ % (args.listen_addr, args.listen_port, args.serve_url, args.device)) httpd.serve_forever() def my_log_message(my_args, prio, msg): """ Log msg to syslog, and possibly also output to stderr. """ syslog.syslog(prio, msg) if my_args.debug or my_args.verbose or prio == syslog.LOG_ERR: sys.stderr.write("%s\n" % (msg)) def main(): """ The main function that will be executed when running this as a stand alone script. """ my_name = os.path.basename(sys.argv[0]) if not my_name: my_name = "yhsm-validation-server" syslog.openlog(my_name, syslog.LOG_PID, syslog.LOG_LOCAL0) global args args = parse_args() args_fixup() global hsm try: hsm = pyhsm.YHSM(device = args.device, debug = args.debug) except serial.SerialException, e: my_log_message(args, syslog.LOG_ERR, 'Failed opening YubiHSM device "%s" : %s' %(args.device, e)) return 1 write_pid_file(args.pid_file) try: run() except KeyboardInterrupt: print "" print "Shutting down" print "" if __name__ == '__main__': sys.exit(main()) pyhsm-1.2.0/pyhsm/basic_cmd.py0000664000175000017500000003463213002347641016152 0ustar daindain00000000000000""" implementations of basic commands to execute on a YubiHSM """ # Copyright (c) 2011-2014 Yubico AB # See the file COPYING for licence statement. import struct __all__ = [ # constants # functions # classes 'YHSM_Cmd_Echo', 'YHSM_Cmd_System_Info', 'YHSM_Cmd_Random', 'YHSM_Cmd_Random_Reseed', 'YHSM_Cmd_Temp_Key_Load', 'YHSM_Cmd_Nonce_Get', 'YHSM_Cmd_Key_Storage_Unlock', 'YHSM_Cmd_Key_Store_Decrypt', 'YHSM_Cmd_HSM_Unlock', 'YHSM_NonceResponse', ] import pyhsm.defines import pyhsm.exception import pyhsm.aead_cmd from pyhsm.cmd import YHSM_Cmd class YHSM_Cmd_Echo(YHSM_Cmd): """ Send something to the stick, and expect to get it echoed back. """ def __init__(self, stick, payload=''): payload = pyhsm.util.input_validate_str(payload, 'payload', max_len = pyhsm.defines.YSM_MAX_PKT_SIZE - 1) # typedef struct { # uint8_t numBytes; // Number of bytes in data field # uint8_t data[YSM_MAX_PKT_SIZE - 1]; // Data # } YSM_ECHO_REQ; packed = chr(len(payload)) + payload YHSM_Cmd.__init__(self, stick, pyhsm.defines.YSM_ECHO, packed) def parse_result(self, data): # typedef struct { # uint8_t numBytes; // Number of bytes in data field # uint8_t data[YSM_MAX_PKT_SIZE - 1]; // Data # } YSM_ECHO_RESP; return data[1:] class YHSM_Cmd_System_Info(YHSM_Cmd): """ Request system information from the YubiHSM. @ivar version_major: Major firmware version @ivar version_minor: Minor firmware version @ivar version_build: Firmware build version @ivar protocol_ver: Communication protocol version @ivar system_uid: Unique identifier for YubiHSM @type system_uid: string """ version_major = 0 version_minor = 0 version_build = 0 protocol_ver = 0 system_uid = None def __init__(self, stick): YHSM_Cmd.__init__(self, stick, pyhsm.defines.YSM_SYSTEM_INFO_QUERY) def __repr__(self): if self.executed: return '<%s instance at %s: ver=%s, proto=%s, sysid=0x%s>' % ( self.__class__.__name__, hex(id(self)), (self.version_major, self.version_minor, self.version_build), self.protocol_ver, self.system_uid.encode('hex') ) else: return '<%s instance at %s (not executed)>' % ( self.__class__.__name__, hex(id(self)) ) def parse_result(self, data): # #define SYSTEM_ID_SIZE 12 # typedef struct { # uint8_t version_major; // Major version # # uint8_t version_minor; // Minor version # # uint8_t version_build; // Build version # # uint8_t protocolVersion; // Protocol version # # uint8_t systemUid[SYSTEM_ID_SIZE]; // System unique identifier # } YHSM_SYSTEM_INFO_RESP; self.version_major, \ self.version_minor, \ self.version_build, \ self.protocol_ver, \ self.system_uid = struct.unpack('BBBB12s', data) return self class YHSM_Cmd_Random(YHSM_Cmd): """ Ask stick to generate a number of random bytes. """ def __init__(self, stick, num_bytes): self.num_bytes = pyhsm.util.input_validate_int(num_bytes, 'num_bytes', pyhsm.defines.YSM_MAX_PKT_SIZE - 1) # typedef struct { # uint8_t numBytes; // Number of bytes to generate # } YSM_RANDOM_GENERATE_REQ; packed = chr(self.num_bytes) YHSM_Cmd.__init__(self, stick, pyhsm.defines.YSM_RANDOM_GENERATE, packed) def parse_result(self, data): # typedef struct { # uint8_t numBytes; // Number of bytes generated # uint8_t rnd[YSM_MAX_PKT_SIZE - 1]; // Random data # } YHSM_RANDOM_GENERATE_RESP; num_bytes = pyhsm.util.validate_cmd_response_int('num_bytes', ord(data[0]), self.num_bytes) return data[1:1 + num_bytes] class YHSM_Cmd_Random_Reseed(YHSM_Cmd): """ Provide YubiHSM DRBG_CTR with a new seed. """ status = None def __init__(self, stick, seed): seed = pyhsm.util.input_validate_str(seed, 'seed', exact_len = pyhsm.defines.YSM_CTR_DRBG_SEED_SIZE) # #define YSM_CTR_DRBG_SEED_SIZE 32 # typedef struct { # uint8_t seed[YSM_CTR_DRBG_SEED_SIZE]; // New seed # } YSM_RANDOM_RESEED_REQ; fmt = "%is" % (pyhsm.defines.YSM_CTR_DRBG_SEED_SIZE) packed = struct.pack(fmt, seed) YHSM_Cmd.__init__(self, stick, pyhsm.defines.YSM_RANDOM_RESEED, packed) def parse_result(self, data): # typedef struct { # YSM_STATUS status; // Status # } YSM_RANDOM_RESEED_RESP; fmt = "B" self.status, = struct.unpack(fmt, data) if self.status == pyhsm.defines.YSM_STATUS_OK: return True else: raise pyhsm.exception.YHSM_CommandFailed(pyhsm.defines.cmd2str(self.command), self.status) class YHSM_Cmd_Temp_Key_Load(YHSM_Cmd): """ Load an AEAD into the phantom key handle 0xffffffff. The `aead' is either a YHSM_GeneratedAEAD, or a string. """ status = None def __init__(self, stick, nonce, key_handle, aead): self.nonce = pyhsm.util.input_validate_nonce(nonce, pad = True) self.key_handle = pyhsm.util.input_validate_key_handle(key_handle) flags_size = struct.calcsize("' % ( self.__class__.__name__, hex(id(self)), self.nonce.encode('hex'), self.pu_count, self.volatile ) pyhsm-1.2.0/pyhsm/defines.py0000664000175000017500000001424713002140277015657 0ustar daindain00000000000000""" Various defines from pyhsm_if.h. """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. __all__ = [ # constants 'YSM_PUBLIC_ID_SIZE', 'YSM_OTP_SIZE', 'YSM_BLOCK_SIZE', 'YSM_MAX_KEY_SIZE', 'YSM_DATA_BUF_SIZE', 'YSM_AEAD_NONCE_SIZE', 'YSM_AEAD_MAC_SIZE', 'YSM_CCM_CTR_SIZE', 'YSM_AEAD_MAX_SIZE', 'YSM_SHA1_HASH_SIZE', 'YSM_CTR_DRBG_SEED_SIZE', 'YSM_MAX_PKT_SIZE', 'YSM_PROTOCOL_VERSION', 'YSM_TEMP_KEY_HANDLE', 'UID_SIZE', 'KEY_SIZE', ## statuses 'YSM_STATUS_OK', 'YSM_KEY_HANDLE_INVALID', 'YSM_AEAD_INVALID', 'YSM_OTP_INVALID', 'YSM_OTP_REPLAY', 'YSM_ID_DUPLICATE', 'YSM_ID_NOT_FOUND', 'YSM_DB_FULL', 'YSM_MEMORY_ERROR', 'YSM_FUNCTION_DISABLED', 'YSM_KEY_STORAGE_LOCKED', 'YSM_MISMATCH', 'YSM_INVALID_PARAMETER', ## commands 'YSM_NULL', 'YSM_AEAD_GENERATE', 'YSM_BUFFER_AEAD_GENERATE', 'YSM_RANDOM_AEAD_GENERATE', 'YSM_AEAD_DECRYPT_CMP', 'YSM_DB_YUBIKEY_AEAD_STORE', 'YSM_DB_YUBIKEY_AEAD_STORE2', 'YSM_AEAD_YUBIKEY_OTP_DECODE', 'YSM_DB_OTP_VALIDATE', 'YSM_AES_ECB_BLOCK_ENCRYPT', 'YSM_AES_ECB_BLOCK_DECRYPT', 'YSM_AES_ECB_BLOCK_DECRYPT_CMP', 'YSM_HMAC_SHA1_GENERATE', 'YSM_TEMP_KEY_LOAD', 'YSM_BUFFER_LOAD', 'YSM_BUFFER_RANDOM_LOAD', 'YSM_NONCE_GET', 'YSM_ECHO', 'YSM_RANDOM_GENERATE', 'YSM_RANDOM_RESEED', 'YSM_SYSTEM_INFO_QUERY', 'YSM_MONITOR_EXIT', ## # functions 'cmd2str', 'status2str' # classes ] YSM_PUBLIC_ID_SIZE = 6 # Size of public id for std OTP validation YSM_OTP_SIZE = 16 # Size of OTP YSM_BLOCK_SIZE = 16 # Size of block operations YSM_MAX_KEY_SIZE = 32 # Max size of CCMkey YSM_DATA_BUF_SIZE = 64 # Size of internal data buffer YSM_AEAD_NONCE_SIZE = 6 # Size of AEAD nonce (excluding size of key handle) YSM_AEAD_MAC_SIZE = 8 # Size of AEAD MAC field YSM_CCM_CTR_SIZE = 2 # Sizeof of AES CCM counter field YSM_AEAD_MAX_SIZE = (YSM_DATA_BUF_SIZE + YSM_AEAD_MAC_SIZE) # Max size of an AEAD block YSM_SHA1_HASH_SIZE = 20 # 160-bit SHA1 hash size YSM_CTR_DRBG_SEED_SIZE = 32 # Size of CTR-DRBG entropy YSM_MAX_PKT_SIZE = 0x60 # Max size of a packet (excluding command byte) YSM_PROTOCOL_VERSION = 1 # Protocol version for this file YSM_TEMP_KEY_HANDLE = 0xffffffff # Phantom temporary key handle # these two are in ykdef.h UID_SIZE = 6 KEY_SIZE = 16 YSM_RESPONSE = 0x80 # Response bit YSM_YUBIKEY_AEAD_SIZE = (KEY_SIZE + UID_SIZE + YSM_AEAD_MAC_SIZE) # Response codes YSM_STATUS_OK = 0x80 # Executed successfully YSM_KEY_HANDLE_INVALID = 0x81 # Key handle is invalid YSM_AEAD_INVALID = 0x82 # Supplied AEAD block is invalid YSM_OTP_INVALID = 0x83 # Supplied OTP is invalid (CRC or UID) YSM_OTP_REPLAY = 0x84 # Supplied OTP is replayed YSM_ID_DUPLICATE = 0x85 # The supplied public ID is already in the database YSM_ID_NOT_FOUND = 0x86 # The supplied public ID was not found in the database YSM_DB_FULL = 0x87 # The database storage is full YSM_MEMORY_ERROR = 0x88 # Memory read/write error YSM_FUNCTION_DISABLED = 0x89 # Funciton disabled via attribute(s) YSM_KEY_STORAGE_LOCKED = 0x8a # Key storage locked YSM_MISMATCH = 0x8b # Verification mismatch YSM_INVALID_PARAMETER = 0x8c # Invalid parameter def status2str(num): """ Return YubiHSM response status code as string. """ known = {0x80: 'YSM_STATUS_OK', 0x81: 'YSM_KEY_HANDLE_INVALID', 0x82: 'YSM_AEAD_INVALID', 0x83: 'YSM_OTP_INVALID', 0x84: 'YSM_OTP_REPLAY', 0x85: 'YSM_ID_DUPLICATE', 0x86: 'YSM_ID_NOT_FOUND', 0x87: 'YSM_DB_FULL', 0x88: 'YSM_MEMORY_ERROR', 0x89: 'YSM_FUNCTION_DISABLED', 0x8a: 'YSM_KEY_STORAGE_LOCKED', 0x8b: 'YSM_MISMATCH', 0x8c: 'YSM_INVALID_PARAMETER', } if num in known: return known[num] return "0x%02x" % (num) # HMAC flags YSM_HMAC_SHA1_RESET = 0x01 # Flag to indicate reset at first packet YSM_HMAC_SHA1_FINAL = 0x02 # Flag to indicate that the hash shall be calculated YSM_HMAC_SHA1_TO_BUFFER = 0x04 # Flag to transfer HMAC to buffer # Commands YSM_NULL = 0x00 YSM_AEAD_GENERATE = 0x01 YSM_BUFFER_AEAD_GENERATE = 0x02 YSM_RANDOM_AEAD_GENERATE = 0x03 YSM_AEAD_DECRYPT_CMP = 0x04 YSM_DB_YUBIKEY_AEAD_STORE = 0x05 YSM_AEAD_YUBIKEY_OTP_DECODE = 0x06 YSM_DB_OTP_VALIDATE = 0x07 YSM_DB_YUBIKEY_AEAD_STORE2 = 0x08 YSM_AES_ECB_BLOCK_ENCRYPT = 0x0d YSM_AES_ECB_BLOCK_DECRYPT = 0x0e YSM_AES_ECB_BLOCK_DECRYPT_CMP = 0x0f YSM_HMAC_SHA1_GENERATE = 0x10 YSM_TEMP_KEY_LOAD = 0x11 YSM_BUFFER_LOAD = 0x20 YSM_BUFFER_RANDOM_LOAD = 0x21 YSM_NONCE_GET = 0x22 YSM_ECHO = 0x23 YSM_RANDOM_GENERATE = 0x24 YSM_RANDOM_RESEED = 0x25 YSM_SYSTEM_INFO_QUERY = 0x26 YSM_KEY_STORAGE_UNLOCK = 0x27 # Deprecated in 1.0 YSM_HSM_UNLOCK = 0x28 YSM_KEY_STORE_DECRYPT = 0x29 YSM_MONITOR_EXIT = 0x7f def cmd2str(cmd): """ Return command as string. """ known = {0x00: 'YSM_NULL', 0x01: 'YSM_AEAD_GENERATE', 0x02: 'YSM_BUFFER_AEAD_GENERATE', 0x03: 'YSM_RANDOM_AEAD_GENERATE', 0x04: 'YSM_AEAD_DECRYPT_CMP', 0x05: 'YSM_DB_YUBIKEY_AEAD_STORE', 0x06: 'YSM_AEAD_YUBIKEY_OTP_DECODE', 0x07: 'YSM_DB_OTP_VALIDATE', 0x08: 'YSM_DB_YUBIKEY_AEAD_STORE2', 0x0d: 'YSM_AES_ECB_BLOCK_ENCRYPT', 0x0e: 'YSM_AES_ECB_BLOCK_DECRYPT', 0x0f: 'YSM_AES_ECB_BLOCK_DECRYPT_CMP', 0x10: 'YSM_HMAC_SHA1_GENERATE', 0x11: 'YSM_TEMP_KEY_LOAD', 0x20: 'YSM_BUFFER_LOAD', 0x21: 'YSM_BUFFER_RANDOM_LOAD', 0x22: 'YSM_NONCE_GET', 0x23: 'YSM_ECHO', 0x24: 'YSM_RANDOM_GENERATE', 0x25: 'YSM_RANDOM_RESEED', 0x26: 'YSM_SYSTEM_INFO_QUERY', 0x27: 'YSM_KEY_STORAGE_UNLOCK', 0x28: 'YSM_HSM_UNLOCK', 0x29: 'YSM_KEY_STORE_DECRYPT', 0x7f: 'YSM_MONITOR_EXIT', } if cmd in known: return known[cmd] return "0x%02x" % (cmd) pyhsm-1.2.0/pyhsm/version.py0000664000175000017500000000414413002140277015722 0ustar daindain00000000000000""" module for keeping track of different capabilities in different versions """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. __all__ = [ # constants # functions # classes 'YHSM_Version' ] class YHSM_Version(): """ Keeps the YubiHSM's version number and can tell what capabilities it has. @ivar sysinfo: Sysinfo when YubiHSM was initialized. @type sysinfo: L{YHSM_Cmd_System_Info} """ def __init__(self, sysinfo): """ @param sysinfo: YubiHSM sysinfo. @type sysinfo: L{YHSM_Cmd_System_Info} """ self.sysinfo = sysinfo self.ver = (sysinfo.version_major, sysinfo.version_minor, sysinfo.version_build,) def have_key_storage_unlock(self): """ YSM_KEY_STORAGE_UNLOCK was removed in 1.0. The basic concept of a passphrase to unlock the YubiHSM is now provided with the more secure YSM_KEY_STORE_DECRYPT. """ return self.ver < (1, 0,) def have_key_store_decrypt(self): """ YSM_KEY_STORE_DECRYPT was introduced in 1.0, replacing YSM_KEY_STORAGE_UNLOCK. """ return self.ver >= (1, 0, 0) def have_unlock(self): """ YSM_HSM_UNLOCK, featuring YubiKey OTP unlocking of operations, was introduced in 1.0. """ return self.ver >= (1, 0, 0) def have_keycommit(self): """ YubiHSM have the 'keycommit' command in configuration mode. 'keycommit' was introduced in 1.0. """ return self.ver >= (1, 0, 0) def have_keydisable(self): """ YubiHSM have the 'keydis'(able) command in configuration mode. 'keydis' was introduced in 1.0. """ return self.ver >= (1, 0, 1) def have_YSM_BUFFER_LOAD(self): """ This is a key handle permission flag that was introduced in 0.9.9. """ return self.ver >= (0, 9, 9,) def have_YSM_DB_YUBIKEY_AEAD_STORE2(self): """ The 2nd generation store command (with public id != nonce) was introduced in 1.0.4. """ return self.ver >= (1, 0, 4) pyhsm-1.2.0/pyhsm/stick.py0000664000175000017500000001030413002140277015345 0ustar daindain00000000000000""" module for actually talking to the YubiHSM """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. __all__ = [ # constants # functions 'read', 'write', 'flush', # classes 'YHSM_Stick', ] import sys import serial import pyhsm.util import pyhsm.exception class YHSM_Stick(): """ The current YHSM is a USB device using serial communication. This class exposes the basic functions read, write and flush (input). """ def __init__(self, device, timeout=1, debug=False): """ Open YHSM device. """ self.debug = debug self.device = device self.num_read_bytes = 0 self.num_write_bytes = 0 self.ser = None # to not bomb in destructor on open fail self.ser = serial.serial_for_url(device) self.ser.baudrate = 115200 self.ser.timeout = timeout if self.debug: sys.stderr.write("%s: OPEN %s\n" %( self.__class__.__name__, self.ser )) return None def acquire(self): """ Do nothing """ return self.acquire def write(self, data, debug_info=None): """ Write data to YHSM device. """ self.num_write_bytes += len(data) if self.debug: if not debug_info: debug_info = str(len(data)) sys.stderr.write("%s: WRITE %s:\n%s\n" %( self.__class__.__name__, debug_info, pyhsm.util.hexdump(data) )) return self.ser.write(data) def read(self, num_bytes, debug_info=None): """ Read a number of bytes from YubiHSM device. """ if self.debug: if not debug_info: debug_info = str(num_bytes) sys.stderr.write("%s: READING %s\n" %( self.__class__.__name__, debug_info )) res = self.ser.read(num_bytes) if self.debug: sys.stderr.write("%s: READ %i:\n%s\n" %( self.__class__.__name__, len(res), pyhsm.util.hexdump(res) )) self.num_read_bytes += len(res) return res def flush(self): """ Flush input buffers. """ if self.debug: sys.stderr.write("%s: FLUSH INPUT (%i bytes waiting)\n" %( self.__class__.__name__, self.ser.inWaiting() )) self.ser.flushInput() def drain(self): """ Drain input. """ if self.debug: sys.stderr.write("%s: DRAIN INPUT (%i bytes waiting)\n" %( self.__class__.__name__, self.ser.inWaiting() )) old_timeout = self.ser.timeout self.ser.timeout = 0.1 data = self.ser.read(1) while len(data): if self.debug: sys.stderr.write("%s: DRAINED 0x%x (%c)\n" %(self.__class__.__name__, ord(data[0]), data[0])) data = self.ser.read(1) self.ser.timeout = old_timeout return True def raw_device(self): """ Get raw serial device. Only intended for test code/debugging! """ return self.ser def set_debug(self, new): """ Set debug mode (boolean). Returns old setting. """ if type(new) is not bool: raise pyhsm.exception.YHSM_WrongInputType( 'new', bool, type(new)) old = self.debug self.debug = new return old def __repr__(self): return '<%s instance at %s: %s - r:%i w:%i>' % ( self.__class__.__name__, hex(id(self)), self.device, self.num_read_bytes, self.num_write_bytes ) def __del__(self): """ Close device when YHSM instance is destroyed. """ if self.debug: sys.stderr.write("%s: CLOSE %s\n" %( self.__class__.__name__, self.ser )) if self.ser: self.ser.close() pyhsm-1.2.0/pyhsm/validate_cmd.py0000664000175000017500000001060713002140277016652 0ustar daindain00000000000000""" implementations of validation commands for YubiHSM """ # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import struct __all__ = [ # constants # functions # classes 'YHSM_Cmd_AEAD_Validate_OTP', 'YHSM_ValidationResult', ] import pyhsm.defines import pyhsm.exception from pyhsm.aead_cmd import YHSM_AEAD_Cmd class YHSM_Cmd_AEAD_Validate_OTP(YHSM_AEAD_Cmd): """ Request the YubiHSM to validate an OTP using an externally stored AEAD. """ response = None status = None def __init__(self, stick, public_id, otp, key_handle, aead): self.public_id = pyhsm.util.input_validate_nonce(public_id, pad = True) self.otp = pyhsm.util.input_validate_str(otp, 'otp', exact_len = pyhsm.defines.YSM_OTP_SIZE) self.key_handle = pyhsm.util.input_validate_key_handle(key_handle) aead = pyhsm.util.input_validate_aead(aead, expected_len = pyhsm.defines.YSM_YUBIKEY_AEAD_SIZE) # typedef struct { # uint8_t publicId[YSM_PUBLIC_ID_SIZE]; // Public id (nonce) # uint32_t keyHandle; // Key handle # uint8_t otp[YSM_OTP_SIZE]; // OTP # uint8_t aead[YSM_YUBIKEY_AEAD_SIZE]; // AEAD block # } YSM_AEAD_YUBIKEY_OTP_DECODE_REQ; fmt = "< %is I %is %is" % (pyhsm.defines.YSM_AEAD_NONCE_SIZE, \ pyhsm.defines.YSM_OTP_SIZE, \ pyhsm.defines.YSM_YUBIKEY_AEAD_SIZE) packed = struct.pack(fmt, self.public_id, \ self.key_handle, \ self.otp, \ aead) YHSM_AEAD_Cmd.__init__(self, stick, pyhsm.defines.YSM_AEAD_YUBIKEY_OTP_DECODE, packed) def parse_result(self, data): # typedef struct { # uint8_t public_id[YSM_PUBLIC_ID_SIZE]; // Public id # uint32_t keyHandle; // Key handle # uint16_t use_ctr; // Use counter # uint8_t session_ctr; // Session counter # uint8_t tstph; // Timestamp (high part) # uint16_t tstpl; // Timestamp (low part) # YHSM_STATUS status; // Validation status # } YHSM_AEAD_OTP_DECODED_RESP; fmt = "< %is I H B B H B" % (pyhsm.defines.YSM_PUBLIC_ID_SIZE) public_id, \ key_handle, \ use_ctr, \ session_ctr, \ ts_high, \ ts_low, \ self.status = struct.unpack(fmt, data) pyhsm.util.validate_cmd_response_str('public_id', public_id, self.public_id) pyhsm.util.validate_cmd_response_hex('key_handle', key_handle, self.key_handle) if self.status == pyhsm.defines.YSM_STATUS_OK: self.response = YHSM_ValidationResult(self.public_id, use_ctr, session_ctr, ts_high, ts_low) return self.response else: raise pyhsm.exception.YHSM_CommandFailed(pyhsm.defines.cmd2str(self.command), self.status) class YHSM_ValidationResult(): """ The result of a Validate operation. Contains the counters and timestamps decrypted from the OTP. @ivar public_id: The six bytes public ID of the YubiKey that produced the OTP @ivar use_ctr: The 16-bit power-on non-volatile counter of the YubiKey @ivar session_ctr: The 8-bit volatile session counter of the YubiKey @ivar ts_high: The high 8 bits of the 24-bit 8 hz timer since power-on of the YubiKey @ivar ts_low: The low 16 bits of the 24-bit 8 hz timer since power-on of the YubiKey @type public_id: string @type use_ctr: integer @type session_ctr: integer @type ts_high: integer @type ts_low: integer """ public_id = use_ctr = session_ctr = ts_high = ts_low = None def __init__(self, public_id, use_ctr, session_ctr, ts_high, ts_low): self.public_id = public_id self.use_ctr = use_ctr self.session_ctr = session_ctr self.ts_high = ts_high self.ts_low = ts_low def __repr__(self): return '<%s instance at %s: public_id=%s, use_ctr=%i, session_ctr=%i, ts=%i/%i>' % ( self.__class__.__name__, hex(id(self)), self.public_id.encode('hex'), self.use_ctr, self.session_ctr, self.ts_high, self.ts_low ) pyhsm-1.2.0/pyhsm/soft_hsm.py0000664000175000017500000001455313006343243016066 0ustar daindain00000000000000""" functions for implementing parts of the HSMs machinery in software """ # Copyright (c) 2012 Yubico AB # See the file COPYING for licence statement. import struct import json import os __all__ = [ # constants # functions 'aesCCM', 'crc16', # classes 'SoftYHSM' ] import pyhsm import pyhsm.exception from Crypto.Cipher import AES def _xor_block(a, b): """ XOR two blocks of equal length. """ return ''.join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)]) class _ctr_counter(): """ An object implementation of the struct aesCtr. """ def __init__(self, key_handle, nonce, flags = None, value = 0): self.flags = pyhsm.defines.YSM_CCM_CTR_SIZE - 1 if flags is None else flags self.key_handle = key_handle self.nonce = nonce self.value = value def next(self): """ Return next counter value, encoded into YSM_BLOCK_SIZE. """ self.value += 1 return self.pack() def pack(self): fmt = b'< B I %is BBB 2s' % (pyhsm.defines.YSM_AEAD_NONCE_SIZE) val = struct.pack('> H', self.value) return struct.pack(fmt, self.flags, self.key_handle, self.nonce, 0, 0, 0, # rfu val ) class _cbc_mac(): def __init__(self, key, key_handle, nonce, data_len): """ Initialize CBC-MAC like the YubiHSM does. """ flags = (((pyhsm.defines.YSM_AEAD_MAC_SIZE - 2) / 2) << 3) | (pyhsm.defines.YSM_CCM_CTR_SIZE - 1) t = _ctr_counter(key_handle, nonce, flags = flags, value = data_len) t_mac = t.pack() self.mac_aes = AES.new(key, AES.MODE_ECB) self.mac = self.mac_aes.encrypt(t_mac) def update(self, block): block = block.ljust(pyhsm.defines.YSM_BLOCK_SIZE, chr(0x0)) t1 = _xor_block(self.mac, block) t2 = self.mac_aes.encrypt(t1) self.mac = t2 def finalize(self, block): """ The final step of CBC-MAC encrypts before xor. """ t1 = self.mac_aes.encrypt(block) t2 = _xor_block(self.mac, t1) self.mac = t2 def get(self): return self.mac[: pyhsm.defines.YSM_AEAD_MAC_SIZE] def _split_data(data, pos): a = data[:pos] b = data[pos:] return (a, b,) def aesCCM(key, key_handle, nonce, data, decrypt=False): """ Function implementing YubiHSM AEAD encrypt/decrypt in software. """ if decrypt: (data, saved_mac) = _split_data(data, len(data) - pyhsm.defines.YSM_AEAD_MAC_SIZE) nonce = pyhsm.util.input_validate_nonce(nonce, pad = True) mac = _cbc_mac(key, key_handle, nonce, len(data)) counter = _ctr_counter(key_handle, nonce, value = 0) ctr_aes = AES.new(key, AES.MODE_CTR, counter = counter.next) out = [] while data: (thisblock, data) = _split_data(data, pyhsm.defines.YSM_BLOCK_SIZE) # encrypt/decrypt and CBC MAC if decrypt: aes_out = ctr_aes.decrypt(thisblock) mac.update(aes_out) else: mac.update(thisblock) aes_out = ctr_aes.encrypt(thisblock) out.append(aes_out) # Finalize MAC counter.value = 0 mac.finalize(counter.pack()) if decrypt: if mac.get() != saved_mac: raise pyhsm.exception.YHSM_Error('AEAD integrity check failed') else: out.append(mac.get()) return ''.join(out) def crc16(data): """ Calculate an ISO13239 CRC checksum of the input buffer. """ m_crc = 0xffff for this in data: m_crc ^= ord(this) for _ in range(8): j = m_crc & 1 m_crc >>= 1 if j: m_crc ^= 0x8408 return m_crc class SoftYHSM(object): def __init__(self, keys, debug=False): self._buffer = '' self.debug = debug if not keys: raise ValueError('Data contains no key handles!') for k, v in keys.items(): if len(v) not in AES.key_size: raise ValueError('Keyhandle of unsupported length: %d (was %d bytes)' % (k, len(v))) self.keys = keys @classmethod def from_file(cls, filename, debug=False): with open(filename, 'r') as f: return cls.from_json(f.read(), debug) @classmethod def from_json(cls, data, debug=False): data = json.loads(data) if not isinstance(data, dict): raise ValueError('Data does not contain object as root element.') keys = {} for kh, aes_key_hex in data.items(): keys[int(kh)] = aes_key_hex.decode('hex') return cls(keys, debug) def _get_key(self, kh, cmd): try: return self.keys[kh] except KeyError: raise pyhsm.exception.YHSM_CommandFailed( pyhsm.defines.cmd2str(cmd), pyhsm.defines.YSM_KEY_HANDLE_INVALID) def validate_aead_otp(self, public_id, otp, key_handle, aead): aes_key = self._get_key(key_handle, pyhsm.defines.YSM_AEAD_YUBIKEY_OTP_DECODE) cmd = pyhsm.validate_cmd.YHSM_Cmd_AEAD_Validate_OTP( None, public_id, otp, key_handle, aead) aead_pt = aesCCM(aes_key, cmd.key_handle, cmd.public_id, aead, True) yk_key, yk_uid = aead_pt[:16], aead_pt[16:] ecb_aes = AES.new(yk_key, AES.MODE_ECB) otp_plain = ecb_aes.decrypt(otp) uid = otp_plain[:6] use_ctr, ts_low, ts_high, session_ctr, rnd, crc = struct.unpack( '= 2.3 pycrypto >= 2.1 [daemon] python-daemon [db] sqlalchemy>=0.9.7 pyhsm-1.2.0/pyhsm.egg-info/entry_points.txt0000664000175000017500000000120213006370266020652 0ustar daindain00000000000000[console_scripts] yhsm-daemon = pyhsm.stick_daemon:main [daemon] yhsm-db-export = pyhsm.ksm.db_export:main [db] yhsm-db-import = pyhsm.ksm.db_import:main [db] yhsm-decrypt-aead = pyhsm.tools.decrypt_aead:main yhsm-generate-keys = pyhsm.tools.generate_keys:main yhsm-import-keys = pyhsm.ksm.import_keys:main yhsm-init-oath-token = pyhsm.val.init_oath_token:main yhsm-keystore-unlock = pyhsm.tools.keystore_unlock:main yhsm-linux-add-entropy = pyhsm.tools.linux_add_entropy:main yhsm-validate-otp = pyhsm.val.validate_otp:main yhsm-validation-server = pyhsm.val.validation_server:main yhsm-yubikey-ksm = pyhsm.ksm.yubikey_ksm:main [daemon,db] pyhsm-1.2.0/pyhsm.egg-info/PKG-INFO0000664000175000017500000000126213006370266016457 0ustar daindain00000000000000Metadata-Version: 1.1 Name: pyhsm Version: 1.2.0 Summary: Python code for talking to a YubiHSM Home-page: https://github.com/Yubico/python-pyhsm Author: Dain Nilsson Author-email: dain@yubico.com License: BSD 2 clause Description: UNKNOWN Platform: UNKNOWN Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application pyhsm-1.2.0/pyhsm.egg-info/top_level.txt0000664000175000017500000000000613006370266020107 0ustar daindain00000000000000pyhsm pyhsm-1.2.0/pyhsm.egg-info/SOURCES.txt0000664000175000017500000000350713006370266017252 0ustar daindain00000000000000COPYING ChangeLog MANIFEST.in NEWS README setup.py doc/Database_Usage.adoc doc/Intro.adoc doc/README doc/Running_Hardware_Tests.adoc doc/YubiHSM_if.h doc/YubiKey_KSM.adoc doc/db_schema examples/yhsm-monitor-exit.py examples/yhsm-password-auth.py examples/yhsm-sysinfo.py man/yhsm-daemon.1 man/yhsm-db-export.1 man/yhsm-db-import.1 man/yhsm-decrypt-aead.1 man/yhsm-generate-keys.1 man/yhsm-import-keys.1 man/yhsm-init-oath-token.1 man/yhsm-keystore-unlock.1 man/yhsm-linux-add-entropy.1 man/yhsm-validate-otp.1 man/yhsm-validation-server.1 man/yhsm-yubikey-ksm.1 pyhsm/__init__.py pyhsm/aead_cmd.py pyhsm/aes_ecb_cmd.py pyhsm/base.py pyhsm/basic_cmd.py pyhsm/buffer_cmd.py pyhsm/cmd.py pyhsm/db_cmd.py pyhsm/debug_cmd.py pyhsm/defines.py pyhsm/exception.py pyhsm/hmac_cmd.py pyhsm/oath_hotp.py pyhsm/oath_totp.py pyhsm/soft_hsm.py pyhsm/stick.py pyhsm/stick_client.py pyhsm/stick_daemon.py pyhsm/util.py pyhsm/validate_cmd.py pyhsm/version.py pyhsm/yubikey.py pyhsm.egg-info/PKG-INFO pyhsm.egg-info/SOURCES.txt pyhsm.egg-info/dependency_links.txt pyhsm.egg-info/entry_points.txt pyhsm.egg-info/requires.txt pyhsm.egg-info/top_level.txt pyhsm/ksm/__init__.py pyhsm/ksm/db_export.py pyhsm/ksm/db_import.py pyhsm/ksm/import_keys.py pyhsm/ksm/yubikey_ksm.py pyhsm/tools/__init__.py pyhsm/tools/decrypt_aead.py pyhsm/tools/generate_keys.py pyhsm/tools/keystore_unlock.py pyhsm/tools/linux_add_entropy.py pyhsm/val/__init__.py pyhsm/val/init_oath_token.py pyhsm/val/validate_otp.py pyhsm/val/validation_server.py test/README.adoc test/__init__.py test/configure_hsm.py test/test_aead.py test/test_aes_ecb.py test/test_basics.py test/test_buffer.py test/test_common.py test/test_db.py test/test_hmac.py test/test_init.py test/test_misc.py test/test_oath.py test/test_otp_validate.py test/test_soft_hsm.py test/test_stick.py test/test_util.py test/test_yubikey_validate.pypyhsm-1.2.0/NEWS0000664000175000017500000001067113006351320013221 0ustar daindain00000000000000* Version 1.2.0 (released 2016-11-02) ** yhsm-validation-server: Support OATH TOTP. ** yhsm-init-oath-token: Handle keys with length != 20. ** yhsm-yubikey-ksm: Allow passing soft-HSM keys via stdin by passing "-" as device argument. ** yhsm-yubikey-ksm: Allow passing --db-url via environment variable. ** Moved utils, yubikey-ksm and validation-server to be included when installing using pip. ** Use entry_point scripts generated by setuptools. ** Moved man pages to man/ directory. ** Bugfix: Fix AEAD generation on Windows by writing in binary mode. ** Bugfix: Support AEADs generated on Windows using pyhsm <= 1.1.1. ** Bugfix: Avoid installing unit test package. ** Bugfix: yhsm-import-keys: Fix --aes-key argument used when importing without a YubiHSM. * Version 1.1.1 (released 2016-01-08) ** Fixup release. * Version 1.1.0 (released 2016-01-08) ** Restructured the repository and build process. ** Use Semantic Versioning (semver.org). ** Added support for a "soft" HSM in yhsm-yubikey-ksm, yhsm-import-keys and yhsm-generate-keys. * Version 1.0.4l (released 2015-08-24) ** Documentation is now in asciidoc format. ** yhsm-yubikey-ksm: Fix bug when the same public ID occured for multiple keyhandles. * Version 1.0.4k (released 2014-09-18) ** yhsm-db-import, yhsm-db-export: Fix syntax error. * Version 1.0.4j (released 2014-09-16) ** yhsm-yubikey-ksm: Fix syntax error. * Version 1.0.4i (released 2014-09-16) ** yhsm-yubikey-ksm: Add --daemon. ** yhsm-yubikey-ksm: Add --db-url to specify SQL database path to AEAD store. ** yhsm-db-import, yhsm-db-export: New tools to do database import/export. ** Documentation cleanup. * Version 1.0.4h (released 2014-01-09) ** yhsm-daemon: Use JSON messages instead of Python pickling. (as suggested by Rogdham) * Version 1.0.4g (released 2013-05-06) ** yhsm-daemon: Support listening to non-loopback interfaces. ** yhsm-daemon: Forward exceptions to the client. ** yhsm-daemon: Handle device unavailable, and attempt to recover. * Version 1.0.4f (released 2013-04-12) ** Fix failing test. ** Support URLs in device field, for more info see: http://pyserial.sourceforge.net/pyserial_api.html#serial.serial_for_url ** Added yhsm-daemon. * Version 1.0.4e (released 2013-04-06) ** yhsm-decrypt-aead: For yubikey-csv output, fix prefix field. Before the prefix was set to the AEAD nonce which is only correct for old style AEAD files. Now it uses the AEAD filename for the prefix field. ** yhsm-decrypt-aead: Improve diagnostic messages. Errors and warnings are now printed to standard error. The count of number of failed and processsed AEADs should now be accurate. * Version 1.0.4d (released 2013-03-18) ** Fix so that yhsm-yubikey-ksm can work with the older format. * Version 1.0.4c (released 2013-03-18) ** When doing OTP verification, if a nonce is written to the AEAD file use that for decryption, not public id. ** yhsm-import-keys: add --random-nonce for using hsm generated nonce. ** yhsm-generate-keys: add --random-nonce for using hsm generated nonce. * Version 1.0.4b (released 2013-02-11) ** yhsm-import-keys: Support soft HSM AEAD generation. ** yhsm-import-keys: Ignore lines starting with #. ** yhsm-import-keys: Block all-zero (ccc...c) keys. ** yhsm-decrypt-keys: Support generating AEADs. ** yhsm-decrypt-keys: Ignores non-modhex files in AEAD directory trees. ** yhsm-generate-keys: Bugfix that caused AEAD generation to fail. ** yhsm-generate-keys: Bugfix that caused wrong nonce to be used. ** yhsm-generate-keys: Prevent generating all-zero (ccc...c) keys. ** Added this NEWS file, based on debian/changelog in the Debian package. * Version 1.0.4a (released 2012-06-26) ** Enable IPv6 --addr for network servers. ** Verifies communication with YubiHSM on initialization. * Version 1.0.4 (released 2012-06-21) ** Match firmware 1.0.4. Firmware adds flag YSM_USER_NONCE to address security problem for some usages where AEADs could be decrypted by an attacker capable of generating new AEADs. ** New file format for stored AEADs (code loading AEADs is backwards ** compatible), including key handle and nonce. ** AES CCM implementation compatible with YubiHSM in software, for ** transparency and to enable willfull decryption of AEADs. ** Tools to generate YubiKey secrets into AEADs as well as decrypt ** them to enable provisioning YubiKeys with the secrets. * Version 1.0.3c (released 2012-01-05) ** First public release. pyhsm-1.2.0/examples/0000775000175000017500000000000013006370267014346 5ustar daindain00000000000000pyhsm-1.2.0/examples/yhsm-monitor-exit.py0000775000175000017500000000270613002140277020334 0ustar daindain00000000000000#!/usr/bin/env python # # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. # # Utility to send a MONITOR_EXIT command to a YubiHSM. # # MONITOR_EXIT only works if the YubiHSM is in debug mode. It would # be a security problem to allow remote reconfiguration of a production # YubiHSM. # # If your YubiHSM is not in debug mode, enter configuration mode by # pressing the small button while inserting the YubiHSM in the USB port. # import sys import pyhsm device = "/dev/ttyACM0" # simplified arguments parsing d_argv = dict.fromkeys(sys.argv) debug = d_argv.has_key('-v') raw = d_argv.has_key('-v') if d_argv.has_key('-h'): sys.stderr.write("Syntax: %s [-v] [-R]\n" % (sys.argv[0])) sys.stderr.write("\nOptions :\n") sys.stderr.write(" -v verbose\n") sys.stderr.write(" -R raw MONITOR_EXIT command\n") sys.exit(0) res = 0 try: s = pyhsm.base.YHSM(device=device, debug = debug) if raw: # No initialization s.write('\x7f\xef\xbe\xad\xba\x10\x41\x52\x45') else: print "Version: %s" % s.info() s.monitor_exit() print "Exited monitor-mode (maybe)" if raw: print "s.stick == %s" % s.stick print "s.stick.ser == %s" % s.stick.ser for _ in xrange(3): s.stick.ser.write("\n") line = s.stick.ser.readline() print "%s" % (line) except pyhsm.exception.YHSM_Error, e: print "ERROR: %s" % e res = 1 sys.exit(res) pyhsm-1.2.0/examples/yhsm-password-auth.py0000775000175000017500000001413113002140277020472 0ustar daindain00000000000000#!/usr/bin/env python # # Utility to generate an AEAD (encrypted block) from a password, # that can later on be validated securely. # # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. # # If you only have a single YubiHSM, you have to have one key handle # that can both ENCRYPT and COMPARE AES ECB blocks. # # If you have two (or more) YubiHSM's, you can have a key handle in # YubiHSM 1 that can only ENCRYPT, and the same key (!) with the same # key handle (!) in YubiHSM 2 that can only COMPARE. This might add # to the overall security in certain applications. # # Hashing the password before sending it to the YubiHSM (e.g. using PBKDF2) # in order to further enhance security is left as an exercise to the reader. # # Example usage : # # First, set password (create AEAD) : # # $ yhsm-password-auth.py --set --key-handle 8192 --nonce 010203040506 --verbose # Enter password to encrypt : # Success! Remember the nonce and use this AEAD to validate the password later : # # AEAD: 2b70c81e3f84db190f772d8e8dbfe05ebded5db881e9574939a52257 NONCE: 010203040506 # $ # # Then, later on, validate the password using the AEAD and NONCE from above : # # $ yhsm-password-auth.py --key-handle 8192 --nonce 010203040506 --verbose \ # --validate 2b70c81e3f84db190f772d8e8dbfe05ebded5db881e9574939a52257 # Enter password to validate : # OK! Password validated. # $ # import sys import pyhsm import argparse import getpass default_device = "/dev/ttyACM0" def parse_args(): """ Parse the command line arguments """ global default_device parser = argparse.ArgumentParser(description = "Generate password AEAD using YubiHSM", add_help=True ) parser.add_argument('-D', '--device', dest='device', default=default_device, required=False, help='YubiHSM device (default : "%s").' % default_device ) parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Enable verbose operation.' ) parser.add_argument('--debug', dest='debug', action='store_true', default=False, help='Enable debug operation.' ) parser.add_argument('--key-handle', type=int, dest='key_handle', required=True, help='Key handle to use. Must have YHSM_ECB_BLOCK_ENCRYPT and/or YHSM_ECB_BLOCK_DECRYPT_CMP flag set.' ) parser.add_argument('-N', '--nonce', dest='nonce', required=True, help='Nonce to use. 6 bytes encoded as 12 chars hex.' ) parser.add_argument('--set', dest='set', action='store_true', default=False, help='Set password mode.' ) parser.add_argument('--validate', dest='validate', help='AEAD to validate.' ) parser.add_argument('--min_length', type=int, dest='min_len', required=False, default=20, help='Minimum length to pad passwords to (default: 20).' ) args = parser.parse_args() if args.set and args.validate: sys.stderr.write("Arguments --set and --validate are mutually exclusive.\n") sys.exit(1) if not args.set and not args.validate: sys.stderr.write("Either --set or --validate must be specified.\n") sys.exit(1) return args def generate_aead(hsm, args, password): """ Generate an AEAD using the YubiHSM. """ try: pw = password.ljust(args.min_len, chr(0x0)) return hsm.generate_aead_simple(args.nonce.decode('hex'), args.key_handle, pw) except pyhsm.exception.YHSM_CommandFailed, e: if e.status_str == 'YHSM_FUNCTION_DISABLED': print "ERROR: The key handle %s is not permitted to YSM_AEAD_GENERATE." % (args.key_handle) return None else: print "ERROR: %s" % (e.reason) def validate_aead(hsm, args, password): """ Validate a previously generated AEAD using the YubiHSM. """ try: pw = password.ljust(args.min_len, chr(0x0)) return hsm.validate_aead(args.nonce.decode('hex'), args.key_handle, args.validate.decode('hex'), pw) except pyhsm.exception.YHSM_CommandFailed, e: if e.status_str == 'YHSM_FUNCTION_DISABLED': print "ERROR: The key handle %s is not permitted to do AES ECB compare." % (args.key_handle) return None else: print "ERROR: %s" % (e.reason) def main(): args = parse_args() what="encrypt" if args.validate: what="validate" user_input = getpass.getpass('Enter password to %s : ' % (what)) if not user_input: print "\nAborted.\n" return 0 try: hsm = pyhsm.base.YHSM(device=args.device, debug=args.debug) except pyhsm.exception.YHSM_Error, e: print "ERROR: %s" % e return 1 if args.set: # # SET password # aead = generate_aead(hsm, args, user_input) if not aead: return 1 if args.verbose: print "Success! Remember the nonce and use this AEAD to validate the password later :\n" print "AEAD: %s NONCE: %s" % (aead.data.encode('hex'), args.nonce) else: # # VALIDATE password # if not validate_aead(hsm, args, user_input): if args.verbose: print "FAIL! Password does not match the generated AEAD." return 1 if args.verbose: print "OK! Password validated." return 0 if __name__ == '__main__': res = main() sys.exit(res) pyhsm-1.2.0/examples/yhsm-sysinfo.py0000775000175000017500000000125213002140277017363 0ustar daindain00000000000000#!/usr/bin/env python # # Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. # # Utility to show system information of a YubiHSM. # import sys import pyhsm device = "/dev/ttyACM0" # simplified arguments parsing d_argv = dict.fromkeys(sys.argv) debug = d_argv.has_key('-v') if d_argv.has_key('-h'): sys.stderr.write("Syntax: %s [-v]\n" % (sys.argv[0])) sys.exit(0) res = 0 try: s = pyhsm.base.YHSM(device=device, debug=debug) print "Version : %s" % (s.info()) nonce = s.get_nonce() print "Power-up count : %i" % (nonce.pu_count) except pyhsm.exception.YHSM_Error, e: print "ERROR: %s" % e res = 1 sys.exit(res) pyhsm-1.2.0/test/0000775000175000017500000000000013006370267013507 5ustar daindain00000000000000pyhsm-1.2.0/test/test_common.py0000664000175000017500000001411613002355140016400 0ustar daindain00000000000000# Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import os import sys import unittest import pyhsm import struct from pyhsm.soft_hsm import crc16 # configuration parameters CfgPassphrase = "" HsmPassphrase = "bada" * 2 PrimaryAdminYubiKey = ('ftftftfteeee', 'f0f1f2f3f4f5', '4d' * 16,) AdminYubiKeys = [PrimaryAdminYubiKey[0]] class YHSM_TestCase(unittest.TestCase): hsm = None def setUp(self, device = os.getenv('YHSM_DEVICE', '/dev/ttyACM0'), debug = False): """ Common initialization class for our tests. Initializes a YubiHSM in self.hsm. """ self.hsm = pyhsm.base.YHSM(device = device, debug = debug) # unlock keystore if our test configuration contains a passphrase if HsmPassphrase is not None and HsmPassphrase != "": try: self.hsm.unlock(password = HsmPassphrase.decode("hex")) self.otp_unlock() except pyhsm.exception.YHSM_CommandFailed, e: # ignore errors from the unlock function, in case our test configuration # hasn't been loaded into the YubiHSM yet pass def tearDown(self): # get destructor called properly self.hsm = None def who_can(self, what, expected = [], extra_khs = []): """ Try the lambda what() with all key handles between 1 and 32, except the expected one. Fail on anything but YSM_FUNCTION_DISABLED. """ for kh in list(xrange(1, 32)) + extra_khs: if kh in expected: continue res = None try: res = what(kh) self.fail("Expected YSM_FUNCTION_DISABLED for key handle 0x%0x, got '%s'" % (kh, res)) except pyhsm.exception.YHSM_CommandFailed, e: if e.status != pyhsm.defines.YSM_FUNCTION_DISABLED: self.fail("Expected YSM_FUNCTION_DISABLED for key handle 0x%0x, got %s" \ % (kh, e.status_str)) def otp_unlock(self): """ Do OTP unlock of the YubiHSM keystore. Since we don't always reprogram the YubiHSM, we might need to hunt for an unused OTP. """ if not self.hsm.version.have_unlock(): return None Params = PrimaryAdminYubiKey YK = FakeYubiKey(pyhsm.yubikey.modhex_decode(Params[0]).decode('hex'), Params[1].decode('hex'), Params[2].decode('hex') ) YK.session_ctr = 0 use_ctr = 1 # the 16 bit power-up counter of the YubiKey while use_ctr < 0xffff: YK.use_ctr = use_ctr otp = YK.from_key() try: res = self.hsm.unlock(otp = otp) self.assertTrue(res) # OK - if we got here we've got a successful response for this OTP break except pyhsm.exception.YHSM_CommandFailed, e: if e.status != pyhsm.defines.YSM_OTP_REPLAY: raise # don't bother with the session_ctr - test run 5 would mean we first have to # exhaust 4 * 256 session_ctr increases before the YubiHSM would pass our OTP use_ctr += 1 class YubiKeyEmu(): """ Emulate the internal memory of a YubiKey. """ def __init__(self, user_id, use_ctr, timestamp, session_ctr): if len(user_id) != pyhsm.defines.UID_SIZE: raise pyhsm.exception.YHSM_WrongInputSize( 'user_id', pyhsm.defines.UID_SIZE, len(user_id)) self.user_id = user_id self.use_ctr = use_ctr self.timestamp = timestamp self.session_ctr = session_ctr self.rnd = struct.unpack('H', os.urandom(2))[0] def pack(self): """ Return contents packed. Only add AES ECB encryption and modhex to get your own YubiKey OTP (see function 'from_key'). """ #define UID_SIZE 6 #typedef struct { # uint8_t userId[UID_SIZE]; # uint16_t sessionCtr; # NOTE: this is use_ctr # uint24_t timestamp; # uint8_t sessionUse; # NOTE: this is session_ctr # uint16_t rnd; # uint16_t crc; #} TICKET; fmt = "< %is H HB B H" % (pyhsm.defines.UID_SIZE) ts_high = (self.timestamp & 0x00ff0000) >> 16 ts_low = self.timestamp & 0x0000ffff res = struct.pack(fmt, self.user_id, \ self.use_ctr, \ ts_low, ts_high, \ self.session_ctr, \ self.rnd) crc = 0xffff - crc16(res) return res + struct.pack(' zap Confirm current config being erased (type yes) yes - wait - done NO_CFG> hsm ffffffff Enabled flags ffffffff = YSM_AEAD_GENERATE,YSM_BUFFER_AEAD_GENERATE,YSM_RANDOM_AEAD_GENERATE,YSM_AEAD_DECRYPT_CMP,YSM_DG Enter cfg password (g to generate) Enter admin Yubikey public id 001/008 (enter when done) Enter master key (g to generate) Confirm current config being erased (type yes) yes - wait - done HSM (keys changed)> keycommit - Done HSM> exit You should now be ready to run the tests. === Running the tests To run the tests, have a device configured as described above, then run: $ YHSM_ZAP=1 python setup.py test Note that subsequent testruns can omit `YHSM_ZAP=1` to skip the initial HSM device configuration, however each testrun that doesn't reset the device configuration will take longer and longer to complete, until the next configuration reset. This is due to the admin YubiKey OTP counters being incremented on each run, and not stored anywhere, requiring looping to find the correct value. It is also possible to just reset the configuration, without running the tests: $ python -m test.configure_hsm pyhsm-1.2.0/test/test_otp_validate.py0000664000175000017500000000464013002355140017564 0ustar daindain00000000000000# Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import sys import unittest import pyhsm import test_common class TestOtpValidate(test_common.YHSM_TestCase): def setUp(self): test_common.YHSM_TestCase.setUp(self) def test_load_secret_wrong_key(self): """ Test load_secret with key that should not be allowed to. """ key = "A" * 16 uid = '\x4d\x4d\x4d\x4d\x4d\x4d' public_id = 'f0f1f2f3f4f5'.decode('hex') # Enabled flags 00000100 = YHSM_AEAD_STORE # HSM> < keyload - Load key data now using flags 00000100. Press ESC to quit # 00000009 - stored ok key_handle = 9 # Enabled flags 00000020 = YHSM_AEAD_GENERATE secret = pyhsm.aead_cmd.YHSM_YubiKeySecret(key, uid) self.hsm.load_secret(secret) try: res = self.hsm.generate_aead(public_id, key_handle) self.fail("Expected YSM_FUNCTION_DISABLED, got %s" % (res)) except pyhsm.exception.YHSM_CommandFailed, e: self.assertEquals(e.status, pyhsm.defines.YSM_FUNCTION_DISABLED) def test_load_secret(self): """ Test load_secret. """ key = "A" * 16 uid = '\x4d\x01\x4d\x02' public_id = 'f1f2f3f4f5f6'.decode('hex') if self.hsm.version.have_YSM_BUFFER_LOAD(): # Enabled flags 60000004 = YSM_BUFFER_AEAD_GENERATE,YSM_USER_NONCE,YSM_BUFFER_LOAD # HSM (keys changed)> < keyload - Load key data now using flags 60000004. Press ESC to quit # 00001002 - stored ok key_handle = 0x1002 else: # Enabled flags 00000004 = YSM_BUFFER_AEAD_GENERATE # HSM> < keyload - Load key data now using flags 00000004. Press ESC to quit # 00000003 - stored ok key_handle = 3 secret = pyhsm.aead_cmd.YHSM_YubiKeySecret(key, uid) self.hsm.load_secret(secret) aead = self.hsm.generate_aead(public_id, key_handle) self.assertTrue(isinstance(aead, pyhsm.aead_cmd.YHSM_GeneratedAEAD)) self.assertEqual(aead.nonce, public_id) self.assertEqual(aead.key_handle, key_handle) def test_yubikey_secrets(self): """ Test the class representing the YUBIKEY_SECRETS struct. """ aes_128_key = 'a' * 16 first = pyhsm.aead_cmd.YHSM_YubiKeySecret(aes_128_key, 'b') self.assertEqual(len(first.pack()), pyhsm.defines.KEY_SIZE + pyhsm.defines.UID_SIZE) pyhsm-1.2.0/test/test_hmac.py0000664000175000017500000001243313002355140016020 0ustar daindain00000000000000# Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import sys import unittest import pyhsm import test_common class TestHMACSHA1(test_common.YHSM_TestCase): def setUp(self): test_common.YHSM_TestCase.setUp(self) # Enabled flags 00010000 = YSM_HMAC_SHA1_GENERATE # 00003031 - stored ok self.kh = 0x3031 def test_nist_test_vector(self): """ Test HMAC SHA1 with NIST PUB 198 A.2 test vector. """ data = 'Sample #2' this = self.hsm.hmac_sha1(self.kh, data).execute() self.assertEquals(this.get_hash().encode('hex'), '0922d3405faa3d194f82a45830737d5cc6c75d24') # test of repr method self.assertEquals(str, type(str(this))) def test_hmac_numeric_flags(self): """ Test HMAC SHA1 with numeric flags. """ data = 'Sample #2' flags = pyhsm.defines.YSM_HMAC_SHA1_RESET | pyhsm.defines.YSM_HMAC_SHA1_FINAL this = self.hsm.hmac_sha1(self.kh, data, flags = flags).execute() self.assertEquals(this.get_hash().encode('hex'), '0922d3405faa3d194f82a45830737d5cc6c75d24') def test_hmac_continuation(self): """ Test HMAC continuation. """ data = 'Sample #2' this = self.hsm.hmac_sha1(self.kh, data[:3], final = False) self.assertEquals(this.get_hash().encode('hex'), '00' * 20) this.next(data[3:], final = True).execute() self.assertEquals(this.get_hash().encode('hex'), '0922d3405faa3d194f82a45830737d5cc6c75d24') def test_hmac_continuation2(self): """ Test HMAC nasty continuation. """ data = 'Sample #2' this = self.hsm.hmac_sha1(self.kh, '', final = False) self.assertEquals(this.get_hash().encode('hex'), '00' * 20) this.next(data[:3], final = False).execute() this.next(data[3:], final = False).execute() this.next('', final = True).execute() self.assertEquals(this.get_hash().encode('hex'), '0922d3405faa3d194f82a45830737d5cc6c75d24') def test_hmac_interrupted(self): """ Test interrupted HMAC. """ data = 'Sample #2' this = self.hsm.hmac_sha1(self.kh, data[:3], final = False) self.assertEquals(this.get_hash().encode('hex'), '00' * 20) self.assertTrue(self.hsm.echo('hmac unit test')) this.next(data[3:], final = True).execute() self.assertEquals(this.get_hash().encode('hex'), '0922d3405faa3d194f82a45830737d5cc6c75d24') def test_hmac_interrupted2(self): """ Test AES-interrupted HMAC. """ data = 'Sample #2' plaintext = 'Maverick'.ljust(pyhsm.defines.YSM_BLOCK_SIZE) kh_encrypt = 0x1001 kh_decrypt = 0x1001 this = self.hsm.hmac_sha1(self.kh, data[:3], final = False) self.assertEquals(this.get_hash().encode('hex'), '00' * 20) # AES encrypt-decrypt in the middle of HMAC calculation ciphertext = self.hsm.aes_ecb_encrypt(kh_encrypt, plaintext) self.assertNotEqual(plaintext, ciphertext) decrypted = self.hsm.aes_ecb_decrypt(kh_decrypt, ciphertext) self.assertEqual(plaintext, decrypted) # continue HMAC this.next(data[3:], final = True).execute() self.assertEquals(this.get_hash().encode('hex'), '0922d3405faa3d194f82a45830737d5cc6c75d24') def test_hmac_wrong_key_handle(self): """ Test HMAC SHA1 operation with wrong key handle. """ try: res = self.hsm.hmac_sha1(0x01, 'foo').execute() self.fail("Expected YSM_FUNCTION_DISABLED, got %s" % (res)) except pyhsm.exception.YHSM_CommandFailed, e: self.assertEquals(e.status, pyhsm.defines.YSM_FUNCTION_DISABLED) def test_who_can_hash(self): """ Test what key handles can create HMAC SHA1 hashes. """ # Enabled flags 00010000 = YSM_HMAC_SHA1_GENERATE # 00000011 - stored ok data = 'Sample #2' this = lambda kh: self.hsm.hmac_sha1(kh, data).execute() self.who_can(this, expected = [0x11]) def test_generated_sha1_class(self): """ Test YHSM_GeneratedHMACSHA1 class. """ this = pyhsm.hmac_cmd.YHSM_GeneratedHMACSHA1(0x0, 'a' * 20, True) # test repr method self.assertEquals(str, type(str(this))) def test_sha1_to_buffer(self): """ Test HMAC SHA1 to internal buffer. """ self.assertEqual(0, self.hsm.load_random(0, offset = 0)) # offset = 0 clears buffer self.hsm.hmac_sha1(self.kh, 'testing is fun!', to_buffer = True) # Verify there is now 20 bytes in the buffer self.assertEqual(pyhsm.defines.YSM_SHA1_HASH_SIZE, self.hsm.load_random(0, offset = 1)) def test_hmac_continuation_with_buffer(self): """ Test HMAC continuation with buffer. """ data = 'Sample #2' self.assertEqual(0, self.hsm.load_random(0, offset = 0)) # offset = 0 clears buffer self.assertEqual(0, self.hsm.load_random(0, offset = 1)) this = self.hsm.hmac_sha1(self.kh, '', final = False) self.assertEquals(this.get_hash().encode('hex'), '00' * 20) this.next(data[:3], final = False).execute() this.next(data[3:], final = False).execute() this.next('', final = True, to_buffer = True).execute() # Verify there is now 20 bytes in the buffer self.assertEqual(pyhsm.defines.YSM_SHA1_HASH_SIZE, self.hsm.load_random(0, offset = 1)) pyhsm-1.2.0/test/test_yubikey_validate.py0000664000175000017500000000540513002355140020443 0ustar daindain00000000000000# Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import sys import string import unittest import pyhsm import test_common from test_common import YubiKeyEmu, YubiKeyRnd class TestYubikeyValidate(test_common.YHSM_TestCase): def setUp(self): test_common.YHSM_TestCase.setUp(self) self.yk_key = 'F' * 16 # 128 bit AES key self.yk_uid = '\x4d\x01\x4d\x02\x4d\x4d' self.yk_rnd = YubiKeyRnd(self.yk_uid) self.yk_public_id = '4d4d4d4d4d4d'.decode('hex') secret = pyhsm.aead_cmd.YHSM_YubiKeySecret(self.yk_key, self.yk_uid) self.hsm.load_secret(secret) # YubiHSM includes key handle id in AES-CCM of aeads, so we must use same # key to generate and validate. Key 0x2000 has all flags. self.kh_generate = 0x2000 self.kh_validate = 0x2000 self.aead = self.hsm.generate_aead(self.yk_public_id, self.kh_generate) def test_validate_aead_cmp(self): """ Test that the AEAD generated contains our secrets. """ secret = pyhsm.aead_cmd.YHSM_YubiKeySecret(self.yk_key, self.yk_uid) cleartext = secret.pack() self.assertTrue(self.hsm.validate_aead(self.yk_public_id, self.kh_validate, self.aead, cleartext)) wrong_cleartext = 'X' + cleartext[1:] self.assertFalse(self.hsm.validate_aead(self.yk_public_id, self.kh_validate, self.aead, wrong_cleartext)) def test_validate_aead_cmp_long(self): """ Test validating a long AEAD """ cleartext = 'C' * 36 key_handle = 0x2000 # key 0x2000 has all flags set nonce = '123456' aead = self.hsm.generate_aead_simple(nonce, key_handle, cleartext) self.assertTrue(self.hsm.validate_aead(nonce, key_handle, aead, cleartext)) wrong_cleartext = 'X' + cleartext[1:] self.assertFalse(self.hsm.validate_aead(nonce, key_handle, aead, wrong_cleartext)) def test_validate_yubikey(self): """ Test validate YubiKey OTP. """ from_key = self.yk_rnd.from_key(self.yk_public_id, self.yk_key) self.assertTrue(pyhsm.yubikey.validate_yubikey_with_aead( \ self.hsm, from_key, self.aead, self.kh_validate)) def test_modhex_encode_decode(self): """ Test modhex encoding/decoding. """ h = '4d014d024d4ddd5382b11195144da07d' self.assertEquals(h, pyhsm.yubikey.modhex_decode( pyhsm.yubikey.modhex_encode(h) ) ) def test_split_id_otp(self): """ Test public_id + OTP split function. """ public_id, otp, = pyhsm.yubikey.split_id_otp("ft" * 16) self.assertEqual(public_id, '') self.assertEqual(otp, "ft" * 16) public_id, otp, = pyhsm.yubikey.split_id_otp("cc" + "ft" * 16) self.assertEqual(public_id, 'cc') self.assertEqual(otp, "ft" * 16) pyhsm-1.2.0/test/test_stick.py0000664000175000017500000000200513002355140016217 0ustar daindain00000000000000# Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import sys import unittest import pyhsm import test_common class TestUtil(test_common.YHSM_TestCase): def setUp(self): self.saved_stderr = sys.stderr # Discard everything written to stderr from these tests (i.e. debug output # from YubiHSM communication routines with debugging enabled). sys.stderr = DiscardOutput() DontChange = True # we test debug output from YubiHSM communication here test_common.YHSM_TestCase.setUp(self, debug = DontChange) def test_debug_output(self): """ Test debug output of YubiHSM communication. """ self.assertTrue(self.hsm.echo('testing')) self.assertTrue(self.hsm.drain()) def tearDown(self): # Close YubiHSM interface before restoring stderr, to avoid output # when it is closed. self.hsm = None sys.stderr = self.saved_stderr class DiscardOutput(object): def write(self, text): pass pyhsm-1.2.0/test/configure_hsm.py0000664000175000017500000001516213002400025016674 0ustar daindain00000000000000# Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import re import sys import os import time import unittest import pyhsm import pyhsm.util import test_common from StringIO import StringIO from test_common import CfgPassphrase, AdminYubiKeys, HsmPassphrase, PrimaryAdminYubiKey class ConfigureYubiHSMforTest(test_common.YHSM_TestCase): def test_aaa_echo(self): """ Test echo before reconfiguration. """ self.assertTrue(self.hsm.echo('test')) def test_configure_YHSM(self): """ Reconfiguring YubiHSM for tests. """ self.ser = self.hsm.get_raw_device() # get the YubiHSM to exit to configuration mode. #self.assertTrue(self.hsm.monitor_exit()) self.hsm.monitor_exit() # get the first prompt without sending anything self.config_do("", add_cr = False) self.config_do("sysinfo") self.config_do("help") # clear memory and configure as HSM - has a few prompts we have to get past # if not self.hsm.version.have_key_store_decrypt(): self.config_do ("hsm ffffffff\r%s\r%s\ryes" % (CfgPassphrase, HsmPassphrase)) else: # HSM> < hsm ffffffff # Enabled flags ffffffff = ... # Enter cfg password (g to generate) # Enter admin Yubikey public id (enter when done) # Enter master key (g to generate) yes # Confirm current config being erased (type yes) AdminYubiKeysStr = '\r'.join(AdminYubiKeys) AdminYubiKeysStr += '\r' self.config_do ("hsm ffffffff\r%s\r%s\r%s\ryes" % (CfgPassphrase, AdminYubiKeysStr, HsmPassphrase)) self.hsm.drain() self.add_keys(xrange(31)) self.hsm.drain() self.config_do("keylist") if self.hsm.version.have_key_store_decrypt(): self.config_do("keycommit") # load a YubiKey (the first Admin YubiKey) into the internal database escape_char = chr(27) self.config_do("dbload\r00001,%s,%s,%s,\r" % (PrimaryAdminYubiKey) + escape_char, add_cr = False) self.config_do("dblist") # get back into HSM mode sys.stderr.write("exit") self.ser.write("exit\r") self.hsm.drain() self.hsm.reset() def test_zzz_unlock(self): """ Test unlock of keystore after reconfiguration. """ if self.hsm.version.have_unlock(): Params = PrimaryAdminYubiKey YK = test_common.FakeYubiKey(pyhsm.yubikey.modhex_decode(Params[0]).decode('hex'), Params[1].decode('hex'), Params[2].decode('hex') ) # After reconfigure, we know the counter values for PrimaryAdminYubiKey is zero # in the internal db. However, the test suite initialization will unlock the keystore # (in test_common.YHSM_TestCase.setUp) so a value of 0/1 should result in a replayed OTP. YK.use_ctr = 0 YK.session_ctr = 1 # first verify counters 1/0 gives the expected YSM_OTP_REPLAY try: self.hsm.unlock(otp = YK.from_key()) except pyhsm.exception.YHSM_CommandFailed, e: if e.status != pyhsm.defines.YSM_OTP_REPLAY: raise # now do real unlock with values 2/1 (there is an extra unlock done somewhere...) YK.use_ctr = 2 self.assertTrue(self.hsm.unlock(password = HsmPassphrase.decode("hex"), otp = YK.from_key())) else: self.assertTrue(self.hsm.unlock(password = HsmPassphrase.decode("hex"))) def test_zzz_echo(self): """ Test echo after reconfiguration. """ self.assertTrue(self.hsm.echo('test')) def config_do(self, cmd, add_cr = True): # Don't have to output command - it is echoed #sys.__stderr__.write("> " + cmd + "\n") if add_cr: self.ser.write(cmd + "\r") else: self.ser.write(cmd) #time.sleep(0.5) recv = '' fail_count = 0 sys.stderr.write("< ") while True: b = self.ser.read(1) if not b: fail_count += 1 if fail_count == 5: raise Exception("Did not get the next prompt", recv) sys.stderr.write(b) recv += b lines = recv.split('\n') if re.match('^(NO_CFG|WSAPI|HSM).*> .*', lines[-1]): break return recv def add_keys(self, iterator): # Set up one key for every available flag for num in iterator: flags = 1 << num key = ("%02x" % (num + 1)) * 32 self.add_key(flags, num + 1, key) # Set up some extra keys with the same key as the flag-keys, but other flags # flags YHSM_OTP_BLOB_VALIDATE (0x200) matching key 0x06 (with flags 0x20, YHSM_BLOB_GENERATE) flags = 0x200 key = "06" * 32 self.add_key(flags, 0x1000, key) # Key with full AES ECB capabilities # Enabled flags 0000e000 = YHSM_ECB_BLOCK_ENCRYPT,YHSM_ECB_BLOCK_DECRYPT,YHSM_ECB_BLOCK_DECRYPT_CMP flags = 0xe000 key = "1001" * 16 self.add_key(flags, 0x1001, key) # Key allowed to generate AEAD from known data (loaded into buffer), with user specified noncey flags = 0x4 | 0x40000000 | 0x20000000 key = "1002" * 16 self.add_key(flags, 0x1002, key) # Key with everything enabled at once flags = 0xffffffff key = "2000" * 16 self.add_key(flags, 0x2000, key) # Key with everything enabled at once, and then revoked flags = 0xffffffff key = "2001" * 16 self.add_key(flags, 0x2001, key) self.config_do("keydis 2001") # Key with NIST test vector for HMAC SHA1 # Enabled flags 00010000 = YSM_HMAC_SHA1_GENERATE flags = 0x10000 key = "303132333435363738393a3b3c3d3e3f40414243".ljust(64, '0') self.add_key(flags, 0x3031, key) # Key permitting AEAD generate with user specified nonce flags = 0x20000002 key = "20000002" * 8 self.add_key(flags, 0x20000002, key) # Key permitting random AEAD generate with user specified nonce flags = 0x20000008 key = "20000008" * 8 self.add_key(flags, 0x20000008, key) def add_key(self, flags, num, key): keyline = "%08x,%s\r" % (num, key) self.config_do("flags %04x" % (flags)) escape_char = chr(27) self.config_do("keyload\r" + keyline + escape_char, add_cr = False) if __name__ == '__main__': unittest.main() pyhsm-1.2.0/test/test_aead.py0000664000175000017500000001713113002355140016002 0ustar daindain00000000000000# Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import sys import unittest import pyhsm import test_common class TestAEAD(test_common.YHSM_TestCase): def setUp(self): test_common.YHSM_TestCase.setUp(self) self.nonce = "4d4d4d4d4d4d".decode('hex') self.key = "A" * 16 self.uid = '\x4d\x01\x4d\x02\x4d\x03' self.secret = pyhsm.aead_cmd.YHSM_YubiKeySecret(self.key, self.uid) def test_aead_cmd_class(self): """ Test YHSM_AEAD_Cmd class. """ this = pyhsm.aead_cmd.YHSM_AEAD_Cmd(None, None) # test repr method self.assertEquals(str, type(str(this))) this.executed = True self.assertEquals(str, type(str(this))) def test_generate_aead_simple(self): """ Test generate_aead_simple without specifying nonce. """ # Enabled flags 00000002 = YSM_AEAD_GENERATE # HSM> < keyload - Load key data now using flags 00000002. Press ESC to quit # 00000002 - stored ok key_handle = 2 nonce = '' aead = self.hsm.generate_aead_simple(nonce, key_handle, self.secret) self.assertNotEqual(aead.nonce, nonce) self.assertEqual(aead.key_handle, key_handle) # test repr method self.assertEquals(str, type(str(aead))) def test_generate_aead_simple_with_nonce(self): """ Test generate_aead_simple with specified nonce. """ # Enabled flags 20000002 = YSM_AEAD_GENERATE,YSM_USER_NONCE # HSM> < keyload - Load key data now using flags 20000002. Press ESC to quit # 20000002 - stored ok key_handle = 0x20000002 aead = self.hsm.generate_aead_simple(self.nonce, key_handle, self.secret) self.assertEqual(aead.nonce, self.nonce) self.assertEqual(aead.key_handle, key_handle) def test_generate_aead_simple_nonce_blocked(self): """ Test generate_aead_simple with unpermitted nonce. """ # Enabled flags 00000002 = YSM_AEAD_GENERATE # HSM> < keyload - Load key data now using flags 00000002. Press ESC to quit # 00000002 - stored ok if self.hsm.version.ver < (1,0,4): raise unittest.SkipTest("Requires 1.0.4 or greater") key_handle = 2 try: res = self.hsm.generate_aead_simple(self.nonce, key_handle, self.secret) self.fail("Expected YSM_FUNCTION_DISABLED, got %s" % (res)) except pyhsm.exception.YHSM_CommandFailed, e: self.assertEquals(e.status, pyhsm.defines.YSM_FUNCTION_DISABLED) def test_generate_aead_simple_validates(self): """ Test validate_aead of generate_aead_simple result. """ # To successfully decrypt the AEAD we have to generate and decrypt # with the same key handle. Key handle 0x2000 has all flags set. kh_gen = 0x2000 kh_val = 0x2000 aead = self.hsm.generate_aead_simple('', kh_gen, self.secret) # test that the YubiHSM validates the generated AEAD # and confirms it contains our secret self.assertTrue(self.hsm.validate_aead(aead.nonce, kh_val, \ aead, cleartext = self.secret.pack())) def test_generate_aead_simple_hsm_nonce_validates(self): """ Test validate_aead of generate_aead_simple result with HSM nonce. """ # To successfully decrypt the AEAD we have to generate and decrypt # with the same key handle. Key handle 0x2000 has all flags set. kh_gen = 0x2000 kh_val = 0x2000 nonce = '000000000000'.decode('hex') aead = self.hsm.generate_aead_simple(nonce, kh_gen, self.secret) # test that the YubiHSM validates the generated AEAD # and confirms it contains our secret self.assertTrue(self.hsm.validate_aead(aead.nonce, kh_val, \ aead, cleartext = self.secret.pack())) def test_generate_aead_random_nonce_blocked(self): """ Test generate_aead_random with unpermitted nonce. """ # Enabled flags 00000008 = YSM_RANDOM_AEAD_GENERATE # 00000004 - stored ok if self.hsm.version.ver < (1,0,4): raise unittest.SkipTest("Requires 1.0.4 or greater") key_handle = 4 try: res = self.hsm.generate_aead_random(self.nonce, key_handle, 22) self.fail("Expected YSM_FUNCTION_DISABLED, got %s" % (res)) except pyhsm.exception.YHSM_CommandFailed, e: self.assertEquals(e.status, pyhsm.defines.YSM_FUNCTION_DISABLED) def test_generate_aead_random_nonce_permitted(self): """ Test generate_aead_random with nonce. """ # Enabled flags 20000008 = YSM_RANDOM_AEAD_GENERATE,YSM_USER_NONCE # HSM> < keyload - Load key data now using flags 20000008. Press ESC to quit # 20000008 - stored ok key_handle = 0x20000008 num_bytes = 22 aead = self.hsm.generate_aead_random(self.nonce, key_handle, num_bytes) self.assertEqual(self.nonce, aead.nonce) self.assertEqual(num_bytes + pyhsm.defines.YSM_AEAD_MAC_SIZE, len(aead.data)) def test_generate_aead_random_without_nonce(self): """ Test decrypt_cmp of generate_aead_random result. """ # Enabled flags 00000008 = YSM_RANDOM_AEAD_GENERATE # 00000004 - stored ok key_handle = 4 nonce = '' # Test a number of different sizes for num_bytes in (1, \ pyhsm.defines.KEY_SIZE + pyhsm.defines.UID_SIZE, \ pyhsm.defines.YSM_AEAD_MAX_SIZE - pyhsm.defines.YSM_AEAD_MAC_SIZE): aead = self.hsm.generate_aead_random(nonce, key_handle, num_bytes) self.assertEqual(num_bytes + pyhsm.defines.YSM_AEAD_MAC_SIZE, len(aead.data)) # test num_bytes we expect to fail for num_bytes in (0, \ pyhsm.defines.YSM_AEAD_MAX_SIZE - pyhsm.defines.YSM_AEAD_MAC_SIZE + 1, \ 255): try: res = self.hsm.generate_aead_random(nonce, key_handle, num_bytes) self.fail("Expected YSM_INVALID_PARAMETER, got %s" % (res)) except pyhsm.exception.YHSM_CommandFailed, e: self.assertEquals(e.status, pyhsm.defines.YSM_INVALID_PARAMETER) def test_who_can_generate_random(self): """ Test what key handles can generate a random AEAD. """ # Enabled flags 00000008 = YSM_RANDOM_AEAD_GENERATE # 00000004 - stored ok this = lambda kh: self.hsm.generate_aead_random(self.nonce, kh, 10) self.who_can(this, expected = [0x04], extra_khs = [0x1002]) def test_who_can_generate_simple(self): """ Test what key handles can generate a simple AEAD. """ # Enabled flags 00000002 = YSM_AEAD_GENERATE # 00000002 - stored ok if self.hsm.version.ver < (1,0,4): raise unittest.SkipTest("Requires 1.0.4 or greater") this = lambda kh: self.hsm.generate_aead_simple(self.nonce, kh, self.secret) self.who_can(this, expected = [0x20000002], extra_khs = [0x1002, 0x20000002]) def test_who_can_validate(self): """ Test what key handles can validate an AEAD. """ # Enabled flags 00000002 = YSM_AEAD_GENERATE # 00000002 - stored ok gen_kh = 2 # Enabled flags 00000010 = YSM_AEAD_DECRYPT_CMP # 00000005 - stored ok aead = self.hsm.generate_aead_simple('', gen_kh, self.secret) this = lambda kh: self.hsm.validate_aead(aead.nonce, kh, \ aead, cleartext = self.secret.pack()) self.who_can(this, expected = [0x05], extra_khs = [0x1002, 0x20000002]) pyhsm-1.2.0/test/test_basics.py0000664000175000017500000001156313002355140016357 0ustar daindain00000000000000# Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import sys import unittest import pyhsm import serial import struct import test_common class TestBasics(test_common.YHSM_TestCase): def setUp(self): test_common.YHSM_TestCase.setUp(self) def test_echo(self): """ Test echo command. """ self.assertTrue(self.hsm.echo('test')) def test_random(self): """ Test random number generator . """ r1 = self.hsm.random(10) r2 = self.hsm.random(10) self.assertNotEqual(r1, r2) self.assertEqual(len(r1), 10) def test_util_key_handle_to_int(self): """ Test util.key_handle_to_int. """ self.assertEqual(1, pyhsm.util.key_handle_to_int("1")) self.assertEqual(1, pyhsm.util.key_handle_to_int("0x1")) self.assertEqual(0xffffffee, pyhsm.util.key_handle_to_int("0xffffffee")) self.assertEqual(1413895238, pyhsm.util.key_handle_to_int("FTFT")) def test_nonce(self): """ Test nonce retreival. """ n1 = self.hsm.get_nonce() n2 = self.hsm.get_nonce() self.assertEqual(n1.nonce_int + 1, n2.nonce_int) n3 = self.hsm.get_nonce(9) # YubiHSM returns nonce _before_ adding increment, so the increment # is still only 1 between n2 and n3 self.assertEqual(n2.nonce_int + 1, n3.nonce_int) n4 = self.hsm.get_nonce(1) # and now we see the 9 increment self.assertEqual(n3.nonce_int + 9, n4.nonce_int) def test_nonce_class(self): """ Test nonce class. """ # test repr method self.assertEquals(str, type(str(self.hsm.get_nonce(0)))) def test_random_reseed(self): """ Tets random reseed. """ # Unsure if we can test anything except the status returned is OK self.assertTrue(self.hsm.random_reseed('A' * 32)) # at least test we didn't disable the RNG r1 = self.hsm.random(10) r2 = self.hsm.random(10) self.assertNotEqual(r1, r2) def test_load_temp_key(self): """ Test load_temp_key. """ key = "A" * 16 uid = '\x4d\x01\x4d\x02' nonce = 'f1f2f3f4f5f6'.decode('hex') # key 0x2000 has all flags set key_handle = 0x2000 my_flags = struct.pack("< I", 0xffffffff) # full permissions when loaded into phantom key handle my_key = 'C' * pyhsm.defines.YSM_MAX_KEY_SIZE self.hsm.load_secret(my_key + my_flags) aead = self.hsm.generate_aead(nonce, key_handle) self.assertTrue(isinstance(aead, pyhsm.aead_cmd.YHSM_GeneratedAEAD)) # Load the AEAD into the phantom key handle 0xffffffff. self.assertTrue(self.hsm.load_temp_key(nonce, key_handle, aead)) # Encrypt something with the phantom key plaintext = 'Testing'.ljust(pyhsm.defines.YSM_BLOCK_SIZE) # pad for compare after decrypt ciphertext = self.hsm.aes_ecb_encrypt(pyhsm.defines.YSM_TEMP_KEY_HANDLE, plaintext) self.assertNotEqual(plaintext, ciphertext) # Now decrypt it again and verify result decrypted = self.hsm.aes_ecb_decrypt(pyhsm.defines.YSM_TEMP_KEY_HANDLE, ciphertext) self.assertEqual(plaintext, decrypted) def test_yhsm_class(self): """ Test YHSM class. """ # test repr method self.assertEquals(str, type(str(self.hsm))) def test_yhsm_stick_class(self): """ Test YHSM_Stick class. """ # test repr method self.assertEquals(str, type(str(self.hsm.stick))) def test_set_debug(self): """ Test set_debug on YHSM. """ old = self.hsm.set_debug(True) if old: self.hsm.set_debug(False) self.hsm.set_debug(old) try: self.hsm.set_debug('Test') self.fail("Expected non-bool exception.") except pyhsm.exception.YHSM_WrongInputType: pass def test_sysinfo_cmd_class(self): """ Test YHSM_Cmd_System_Info class. """ this = pyhsm.basic_cmd.YHSM_Cmd_System_Info(None) # test repr method self.assertEquals(str, type(str(this))) def test_sysinfo(self): """ Test sysinfo. """ info = self.hsm.info() self.assertTrue(info.version_major > 0 or info.version_minor > 0) self.assertEqual(12, len(info.system_uid)) self.assertEquals(str, type(str(info))) def test_drain(self): """ Test YubiHSM drain. """ self.hsm.drain() def test_raw_device(self): """ Test YubiHSM raw device fetch. """ self.assertNotEqual(False, self.hsm.get_raw_device()) def test_unknown_defines(self): """ Test command/response to string. """ self.assertEqual("YSM_NULL", pyhsm.defines.cmd2str(0)) self.assertEqual("0xff", pyhsm.defines.cmd2str(0xff)) self.assertEqual("YSM_STATUS_OK", pyhsm.defines.status2str(0x80)) self.assertEqual("0x00", pyhsm.defines.status2str(0)) pyhsm-1.2.0/test/test_misc.py0000664000175000017500000000464413002355140016050 0ustar daindain00000000000000# Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import sys import unittest import pyhsm import test_common class TestUtil(test_common.YHSM_TestCase): def setUp(self): test_common.YHSM_TestCase.setUp(self) def test_using_disabled_keyhandle(self): """ Test using a disabled key handle. """ if not self.hsm.version.have_keydisable(): return None # HSM> < keyload - Load key data now using flags ffffffff. Press ESC to quit # 00002001 - stored ok # HSM> < keydis 2001 try: res = self.hsm.aes_ecb_encrypt(0x2001, "klartext") self.fail("Expected YSM_FUNCTION_DISABLED, got %s" % (res)) except pyhsm.exception.YHSM_CommandFailed, e: self.assertEquals(e.status, pyhsm.defines.YSM_FUNCTION_DISABLED) def test_keystore_unlock(self): """ Test locking and then unlocking keystore. """ if self.hsm.version.ver <= (0, 9, 8,): print ("Test for known bug in 0.9.8 disabled.") return None cleartext = "reference" nonce = '010203040506'.decode('hex') res_before = self.hsm.generate_aead_simple(nonce, 0x2000, cleartext) # lock key store try: res = self.hsm.key_storage_unlock("A" * 8) self.fail("Expected YSM_MISMATCH/YSM_KEY_STORAGE_LOCKED, got %s" % (res)) except pyhsm.exception.YHSM_CommandFailed, e: if self.hsm.version.have_key_store_decrypt(): self.assertEquals(e.status, pyhsm.defines.YSM_MISMATCH) else: self.assertEquals(e.status, pyhsm.defines.YSM_KEY_STORAGE_LOCKED) # make sure we can't generate AEADs when keystore is locked try: res = self.hsm.generate_aead_simple(nonce, 0x2000, cleartext) self.fail("Expected YSM_KEY_STORAGE_LOCKED, got %s (before lock: %s)" \ % (res.data.encode('hex'), res_before.data.encode('hex'))) except pyhsm.exception.YHSM_CommandFailed, e: self.assertEquals(e.status, pyhsm.defines.YSM_KEY_STORAGE_LOCKED) # unlock key store with correct passphrase self.assertTrue(self.hsm.key_storage_unlock(test_common.HsmPassphrase.decode("hex"))) # make sure it is properly unlocked res_after = self.hsm.generate_aead_simple(nonce, 0x2000, cleartext) self.assertEquals(res_before.data, res_after.data) pyhsm-1.2.0/test/test_db.py0000664000175000017500000001305413002355140015475 0ustar daindain00000000000000# Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import os import sys import unittest import pyhsm import test_common from test_yubikey_validate import YubiKeyEmu class TestInternalDB(test_common.YHSM_TestCase): def setUp(self): test_common.YHSM_TestCase.setUp(self) self.key = "A" * 16 self.uid = 'f0f1f2f3f4f5'.decode('hex') self.public_id = '4d4d4d4d4d4d'.decode('hex') def test_store_yubikey(self): """ Test storing a YubiKey in the internal database. """ # Key handle 0x2000 has all flags enabled key_handle = 0x2000 secret = pyhsm.aead_cmd.YHSM_YubiKeySecret(self.key, self.uid) self.hsm.load_secret(secret) aead = self.hsm.generate_aead(self.public_id, key_handle) # Try to store a record. YSM_ID_DUPLICATE is not an error since we don't # always zap the configuration before running the test suite. try: self.assertTrue(self.hsm.db_store_yubikey(self.public_id, key_handle, aead)) except pyhsm.exception.YHSM_CommandFailed, e: self.assertEqual(e.status, pyhsm.defines.YSM_ID_DUPLICATE) # Now, try an invalid validation against that record try: res = self.hsm.db_validate_yubikey_otp(self.public_id, "x" * 16) self.fail("Expected YSM_OTP_INVALID, got %s" % (res)) except pyhsm.exception.YHSM_CommandFailed, e: self.assertEqual(e.status, pyhsm.defines.YSM_OTP_INVALID) def test_store_yubikey_with_nonce(self): """ Test storing a YubiKey generated with non-public-id nonce in the internal database. """ if not self.hsm.version.have_YSM_DB_YUBIKEY_AEAD_STORE2(): raise unittest.SkipTest("Test of command introduced in 1.0.4 disabled.") # Key handle 0x2000 has all flags enabled key_handle = 0x2000 public_id = '4d4d4d001122'.decode('hex') nonce = '010203040506'.decode('hex') key = 'T' * 16 uid = 'F' * 6 secret = pyhsm.aead_cmd.YHSM_YubiKeySecret(key, uid) self.hsm.load_secret(secret) aead = self.hsm.generate_aead(nonce, key_handle) # Try to store a record. YSM_ID_DUPLICATE is not an error since we don't # always zap the configuration before running the test suite. try: self.assertTrue(self.hsm.db_store_yubikey(public_id, key_handle, aead, nonce = nonce)) except pyhsm.exception.YHSM_CommandFailed, e: self.assertEqual(e.status, pyhsm.defines.YSM_ID_DUPLICATE) # Now, try an invalid validation against that record try: res = self.hsm.db_validate_yubikey_otp(public_id, "x" * 16) self.fail("Expected YSM_OTP_INVALID, got %s" % (res)) except pyhsm.exception.YHSM_CommandFailed, e: self.assertEqual(e.status, pyhsm.defines.YSM_OTP_INVALID) def test_real_validate(self): """ Test real validation of YubiKey OTP against internal database. """ # Key handle 0x2000 has all flags enabled key_handle = 0x2000 # randomize last byte of public_id to not have to try so hard to # find an unused OTP ;) this_public_id = self.public_id[:-1] + os.urandom(1) secret = pyhsm.aead_cmd.YHSM_YubiKeySecret(self.key, self.uid) self.hsm.load_secret(secret) aead = self.hsm.generate_aead(this_public_id, key_handle) # Try to store a record. YSM_ID_DUPLICATE is not a duplicate since we don't # always zap the configuration before running the test suite. try: self.assertTrue(self.hsm.db_store_yubikey(this_public_id, key_handle, aead)) except pyhsm.exception.YHSM_CommandFailed, e: self.assertEqual(e.status, pyhsm.defines.YSM_ID_DUPLICATE) # OK, now we know there is an entry for this_public_id in the database - use_ctr = 0 # the 16 bit power-up counter of the YubiKey session_ctr = 0 timestamp = 0xffff # dunno while use_ctr < 0xffff: YK = YubiKeyEmu(self.uid, use_ctr, timestamp, session_ctr) otp = YK.get_otp(self.key) try: res = self.hsm.db_validate_yubikey_otp(this_public_id, otp) self.assertTrue(isinstance(res, pyhsm.validate_cmd.YHSM_ValidationResult)) self.assertEqual(res.public_id, this_public_id) self.assertEqual(res.use_ctr, use_ctr) # OK - if we got here we've got a successful response for this OTP break except pyhsm.exception.YHSM_CommandFailed, e: if e.status != pyhsm.defines.YSM_OTP_REPLAY: raise # don't bother with the session_ctr - test run 5 would mean we first have to # exhaust 4 * 256 session_ctr increases before the YubiHSM would pass our OTP use_ctr += 1 # Now, check the same OTP again and make sure we get a REPLAY response YK = YubiKeyEmu(self.uid, use_ctr, timestamp, session_ctr) otp = YK.get_otp(self.key) try: res = self.hsm.db_validate_yubikey_otp(this_public_id, otp) self.fail("Expected YSM_OTP_REPLAY, got %s" % (res)) except pyhsm.exception.YHSM_CommandFailed, e: if e.status != pyhsm.defines.YSM_OTP_REPLAY: raise # increase session_ctr and test using different method session_ctr += 1 YK = YubiKeyEmu(self.uid, use_ctr, timestamp, session_ctr) mh_from_key = YK.from_key(this_public_id, self.key) pyhsm.yubikey.validate_otp(self.hsm, mh_from_key) pyhsm-1.2.0/test/test_util.py0000664000175000017500000000353113002355140016064 0ustar daindain00000000000000# Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import sys import unittest import pyhsm import test_common class TestUtil(test_common.YHSM_TestCase): def setUp(self): test_common.YHSM_TestCase.setUp(self) def test_hexdump(self): """ Test hexdump function. """ data1 = ''.join([chr(x) for x in xrange(8)]) self.assertEquals('0000 00 01 02 03 04 05 06 07\n', pyhsm.util.hexdump(data1)) data2 = ''.join([chr(x) for x in xrange(64)]) self.assertEquals(248, len(pyhsm.util.hexdump(data2))) self.assertEquals('', pyhsm.util.hexdump('')) def test_response_validation(self): """ Test response validation functions. """ self.assertRaises(pyhsm.exception.YHSM_Error, pyhsm.util.validate_cmd_response_str, \ 'test', 'abc', 'def', hex_encode=True) self.assertRaises(pyhsm.exception.YHSM_Error, pyhsm.util.validate_cmd_response_str, \ 'test', 'abc', 'def', hex_encode=False) def test_input_validate_str(self): """ Test string input validation. """ self.assertRaises(pyhsm.exception.YHSM_WrongInputType, pyhsm.util.input_validate_str, \ 0, 'foo', exact_len = 5) self.assertRaises(pyhsm.exception.YHSM_InputTooLong, pyhsm.util.input_validate_str, \ '1234', 'foo', max_len = 3) self.assertEquals('1234', pyhsm.util.input_validate_str('1234', 'foo', max_len = 4)) self.assertEquals('1234', pyhsm.util.input_validate_str('1234', 'foo', max_len = 14)) self.assertRaises(pyhsm.exception.YHSM_WrongInputSize, pyhsm.util.input_validate_str, \ '1234', 'foo', exact_len = 5) self.assertEquals('1234', pyhsm.util.input_validate_str('1234', 'foo', exact_len = 4)) pyhsm-1.2.0/test/test_oath.py0000664000175000017500000000546313002355140016050 0ustar daindain00000000000000# Copyright (c) 2011 Yubico AB # See the file COPYING for licence statement. import struct import unittest import pyhsm import pyhsm.oath_hotp import test_common class TestOath(test_common.YHSM_TestCase): def setUp(self): test_common.YHSM_TestCase.setUp(self) key = "3132333435363738393031323334353637383930".decode('hex') # Enabled flags 00010000 = YSM_HMAC_SHA1_GENERATE flags = struct.pack("< I", 0x10000) self.nonce = 'f1f2f3f4f5f6'.decode('hex') # key 0x2000 has all flags set self.key_handle = 0x2000 self.phantom = pyhsm.defines.YSM_TEMP_KEY_HANDLE self.hsm.load_secret(key + flags) self.aead = self.hsm.generate_aead(self.nonce, self.key_handle) self.assertTrue(isinstance(self.aead, pyhsm.aead_cmd.YHSM_GeneratedAEAD)) # Load the AEAD into the phantom key handle 0xffffffff. self.assertTrue(self.hsm.load_temp_key(self.nonce, self.key_handle, self.aead)) def test_OATH_HOTP_values(self): """ Test OATH HOTP known results. """ test_vectors = [(0, "cc93cf18508d94934c64b65d8ba7667fb7cde4b0", 755224,), (1, "75a48a19d4cbe100644e8ac1397eea747a2d33ab", 287082,), (2, "0bacb7fa082fef30782211938bc1c5e70416ff44", 359152,), (3, "66c28227d03a2d5529262ff016a1e6ef76557ece", 969429,), (4, "a904c900a64b35909874b33e61c5938a8e15ed1c", 338314,), (5, "a37e783d7b7233c083d4f62926c7a25f238d0316", 254676,), (6, "bc9cd28561042c83f219324d3c607256c03272ae", 287922,), (7, "a4fb960c0bc06e1eabb804e5b397cdc4b45596fa", 162583,), (8, "1b3c89f65e6c9e883012052823443f048b4332db", 399871,), (9, "1637409809a679dc698207310c8c7fc07290d9e5", 520489,), (30, "543c61f8f9aeb35f6dbc3a6847c3fe288cc0ee4c", 26920,), ] for c, expected, code in test_vectors: hmac_result = self.hsm.hmac_sha1(self.phantom, struct.pack("> Q", c)).get_hash() self.assertEqual(expected, hmac_result.encode('hex')) self.assertEqual(code, pyhsm.oath_hotp.truncate(hmac_result, length=6)) def test_OATH_HOTP_validation(self): """ Test complete OATH HOTP code validation. """ oath = lambda counter, user_code, look_ahead: \ pyhsm.oath_hotp.search_for_oath_code(self.hsm, self.key_handle, self.nonce, self.aead, \ counter, user_code, look_ahead) self.assertEqual(1, oath(0, 755224, 1)) self.assertEqual(4, oath(0, 969429, 4)) self.assertEqual(None, oath(0, 969429, 3)) self.assertEqual(10, oath(9, 520489, 3)) self.assertEqual(31, oath(30, 26920, 1)) pyhsm-1.2.0/setup.py0000664000175000017500000000707613006351316014246 0ustar daindain00000000000000# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from setuptools import setup, find_packages import re VERSION_PATTERN = re.compile(r"(?m)^__version__\s*=\s*['\"](.+)['\"]$") def get_version(): """Return the current version as defined by yubico/yubico_version.py.""" with open('pyhsm/__init__.py', 'r') as f: match = VERSION_PATTERN.search(f.read()) return match.group(1) setup( name='pyhsm', version=get_version(), description='Python code for talking to a YubiHSM', author='Dain Nilsson', author_email='dain@yubico.com', url='https://github.com/Yubico/python-pyhsm', license='BSD 2 clause', packages=find_packages(exclude=['test']), entry_points={ 'console_scripts': [ # tools 'yhsm-daemon = pyhsm.stick_daemon:main [daemon]', 'yhsm-decrypt-aead = pyhsm.tools.decrypt_aead:main', 'yhsm-generate-keys = pyhsm.tools.generate_keys:main', 'yhsm-keystore-unlock = pyhsm.tools.keystore_unlock:main', 'yhsm-linux-add-entropy = pyhsm.tools.linux_add_entropy:main', # ksm 'yhsm-yubikey-ksm = pyhsm.ksm.yubikey_ksm:main [db,daemon]', 'yhsm-import-keys = pyhsm.ksm.import_keys:main', 'yhsm-db-export = pyhsm.ksm.db_export:main [db]', 'yhsm-db-import = pyhsm.ksm.db_import:main [db]', # validation server 'yhsm-validation-server = pyhsm.val.validation_server:main', 'yhsm-validate-otp = pyhsm.val.validate_otp:main', 'yhsm-init-oath-token = pyhsm.val.init_oath_token:main' ] }, test_suite='test.test_init', tests_require=[], install_requires=[ 'pyserial >= 2.3', 'pycrypto >= 2.1' ], extras_require={ 'db': ['sqlalchemy>=0.9.7'], 'daemon': ['python-daemon'] }, classifiers=[ 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', ] ) pyhsm-1.2.0/ChangeLog0000664000175000017500000021154013006360443014300 0ustar daindain000000000000002016-11-02 Dain Nilsson * NEWS, pyhsm/__init__.py: Prepare for release. 2016-11-02 Dain Nilsson * setup.py: Fix dependencies. 2016-11-02 Dain Nilsson * pyhsm/soft_hsm.py: Fix soft HSM keys validation. 2016-11-02 Dain Nilsson * MANIFEST.in: Add all test files to sdist. 2016-11-02 Dain Nilsson * NEWS, pyhsm/ksm/yubikey_ksm.py: yhsm-yubikey-ksm: Read --db-url from env var (close #10). 2016-11-02 Dain Nilsson * NEWS, pyhsm/ksm/yubikey_ksm.py, pyhsm/soft_hsm.py: yhsm-yubikey-ksm: support "-" as device. 2016-11-01 Dain Nilsson * NEWS, pyhsm/ksm/import_keys.py: Fix double-decode. 2016-10-28 Dain Nilsson * NEWS, README: Update README. 2016-10-26 Dain Nilsson * MANIFEST.in, NEWS, {utils => man}/yhsm-daemon.1, {yubikey-ksm => man}/yhsm-db-export.1, {yubikey-ksm => man}/yhsm-db-import.1, {utils => man}/yhsm-decrypt-aead.1, {utils => man}/yhsm-generate-keys.1, {yubikey-ksm => man}/yhsm-import-keys.1, {yhsm-val => man}/yhsm-init-oath-token.1, {utils => man}/yhsm-keystore-unlock.1, {utils => man}/yhsm-linux-add-entropy.1, {yhsm-val => man}/yhsm-validate-otp.1, {yhsm-val => man}/yhsm-validation-server.1, {yubikey-ksm => man}/yhsm-yubikey-ksm.1, pyhsm/__init__.py, pyhsm/ksm/__init__.py, pyhsm/ksm/db_export.py, pyhsm/ksm/db_import.py, yubikey-ksm/yhsm-import-keys => pyhsm/ksm/import_keys.py, yubikey-ksm/yhsm-yubikey-ksm => pyhsm/ksm/yubikey_ksm.py, utils/yhsm-daemon => pyhsm/stick_daemon.py, pyhsm/tools/__init__.py, utils/yhsm-decrypt-aead => pyhsm/tools/decrypt_aead.py, utils/yhsm-generate-keys => pyhsm/tools/generate_keys.py, utils/yhsm-keystore-unlock => pyhsm/tools/keystore_unlock.py, utils/yhsm-linux-add-entropy => pyhsm/tools/linux_add_entropy.py, pyhsm/val/__init__.py, yhsm-val/yhsm-init-oath-token => pyhsm/val/init_oath_token.py, yhsm-val/yhsm-validate-otp => pyhsm/val/validate_otp.py, yhsm-val/yhsm-validation-server => pyhsm/val/validation_server.py, setup.py, yubikey-ksm/yhsm-db-export, yubikey-ksm/yhsm-db-import: Made utils, yubikey-ksm and validation-server subpackages. 2016-10-25 Dain Nilsson * {utils => yubikey-ksm}/yhsm-db-export, {utils => yubikey-ksm}/yhsm-db-export.1, {utils => yubikey-ksm}/yhsm-db-import, {utils => yubikey-ksm}/yhsm-db-import.1: Move DB import/export scripts to yubikey-ksm/. 2016-10-25 Dain Nilsson * yhsm-val/yhsm-init-oath-token.1: Fix typo in man page. 2016-10-24 Dain Nilsson * yubikey-ksm/yhsm-yubikey-ksm: Fix Exception printing. 2016-10-24 Dain Nilsson * pyhsm/__init__.py: Bumped version. 2016-10-21 Dain Nilsson * doc/README, doc/Running_Hardware_Tests.adoc, test/README.adoc: Cleanup of docs. 2016-10-21 Dain Nilsson * README: Remove submodule instructions from README. 2016-10-21 Dain Nilsson * NEWS, pyhsm/oath_totp.py, yhsm-val/yhsm-init-oath-token, yhsm-val/yhsm-validation-server: Fix tolerance parameter. 2016-10-21 Dain Nilsson * pyhsm/oath_hotp.py, pyhsm/oath_totp.py, yhsm-val/yhsm-validation-server, yhsm-val/yhsm-validation-server.1: Cleanup TOTP support in yhsm-validation-server. - Reduce code duplication. - Add tolerance parameter. 2016-10-21 Dain Nilsson * : Merge pull request #12. 2016-10-20 Dain Nilsson * setup.py: Exclude test package from installation (fixes #11). 2016-10-20 Dain Nilsson * yubikey-ksm/yhsm-yubikey-ksm: Refactor FS/DB AEAD stores. 2016-09-19 Peter Norin * pyhsm/__init__.py, pyhsm/oath_totp.py, yhsm-val/yhsm-validation-server: Added TOTP RFC 6238 functionallity to the YubiHSM similar to how the HOTP RFC 4226 works. Also fixed an import which was missing in order for HOTP to work 2016-08-10 Simon Josefsson * utils/yhsm-keystore-unlock.1: Fix typo. 2016-01-19 Dain Nilsson * NEWS, pyhsm/aead_cmd.py: Fix for Windows CRLF/LF issue with AEADs. 2016-01-08 Dain Nilsson * NEWS, pyhsm/__init__.py: Bumped version post release. 2016-01-08 Dain Nilsson * pyhsm/__init__.py: Set .post1 version to re-upload to PyPI as previous upload failed. 2016-01-08 Dain Nilsson * pyhsm/__init__.py: Bump version for release. 2016-01-08 Dain Nilsson * .gitmodules, NEWS, pyhsm/__init__.py, pyhsm/yubicommon, setup.py, vendor/yubicommon: Removed yubicommon. 2016-01-08 Dain Nilsson * NEWS: Reformatted NEWS. 2016-01-08 Dain Nilsson * NEWS, doc/Intro.adoc, doc/YubiKey_KSM.adoc, pyhsm/__init__.py: Updated NEWS and version for release. 2016-01-08 Dain Nilsson * vendor/yubicommon: Updated yubicommon. 2016-01-07 Dain Nilsson * doc/YubiKey_KSM.adoc: Add missing dot. 2016-01-07 Dain Nilsson * doc/YubiKey_KSM.adoc: Fix formatting. 2016-01-07 Dain Nilsson * doc/YubiKey_KSM.adoc: Updated documentatino regarding soft HSM. 2016-01-07 Dain Nilsson * yubikey-ksm/yhsm-yubikey-ksm: Refactored AEAD DB into separate class. 2016-01-07 Dain Nilsson * yubikey-ksm/yhsm-yubikey-ksm: Fix db_url parameter, fix logging from constructor. 2016-01-07 Klas Lindfors * pyhsm/cmd.py: add a drain after a bad response from the HSM this makes sure that we read all bad data before we try to send a new command, should prevent cascading errors. 2016-01-05 Dain Nilsson * NEWS, pyhsm/soft_hsm.py, utils/yhsm-generate-keys: Added SoftYHSM support to yhsm-generate-keys. 2016-01-05 Dain Nilsson * examples/yhsm-monitor-exit.py, examples/yhsm-password-auth.py, examples/yhsm-sysinfo.py, utils/yhsm-db-export, utils/yhsm-db-import, utils/yhsm-generate-keys, utils/yhsm-keystore-unlock, utils/yhsm-linux-add-entropy, yhsm-val/yhsm-init-oath-token, yhsm-val/yhsm-validate-otp, yhsm-val/yhsm-validation-server: Remove unneeded append to path. 2016-01-05 Dain Nilsson * NEWS: Updated NEWS. 2016-01-05 Dain Nilsson * : commit aa7a2bc15cc249b7f0a8903ca19c1c94aa5ebbbb Author: Dain Nilsson Date: Tue Jan 5 14:53:35 2016 +0100 2016-01-05 Dain Nilsson * pyhsm/soft_hsm.py, yubikey-ksm/yhsm-import-keys, yubikey-ksm/yhsm-yubikey-ksm: Added SoftYHSM support to yhsm-import-keys. 2016-01-05 Dain Nilsson * pyhsm/soft_hsm.py, test/test_common.py, yubikey-ksm/yhsm-yubikey-ksm: Added SoftYHSM and support for using it in yhsm-yubikey-hsm. SoftYHSM implements just enough functionality to be used in yhsm-yubikey-ksm. 2016-01-05 Dain Nilsson * yubikey-ksm/yhsm-yubikey-ksm: Cleanup of yhsm-yubikey-ksm. Removed global variables, restructured code. 2015-08-25 Dain Nilsson * .gitignore: Removed doc/html from .gitignore. 2015-08-25 Dain Nilsson * NEWS, README: Updated NEWS and README. 2015-08-25 Dain Nilsson * test/README.adoc, test/configure_hsm.py, test/test_configure.py: Updated test instructions. 2015-08-25 Dain Nilsson * MANIFEST.in: Updated MANIFEST. 2015-08-25 Dain Nilsson * maintainer-scripts/README, maintainer-scripts/generate_html.sh, maintainer-scripts/make-release.sh, test/run.sh, test/test_configure.py: Removed maintainer-scripts. 2015-08-25 Dain Nilsson * .gitignore, .gitmodules, {Lib/pyhsm => pyhsm}/__init__.py, {Lib/pyhsm => pyhsm}/aead_cmd.py, {Lib/pyhsm => pyhsm}/aes_ecb_cmd.py, {Lib/pyhsm => pyhsm}/base.py, {Lib/pyhsm => pyhsm}/basic_cmd.py, {Lib/pyhsm => pyhsm}/buffer_cmd.py, {Lib/pyhsm => pyhsm}/cmd.py, {Lib/pyhsm => pyhsm}/db_cmd.py, {Lib/pyhsm => pyhsm}/debug_cmd.py, {Lib/pyhsm => pyhsm}/defines.py, {Lib/pyhsm => pyhsm}/exception.py, {Lib/pyhsm => pyhsm}/hmac_cmd.py, {Lib/pyhsm => pyhsm}/oath_hotp.py, {Lib/pyhsm => pyhsm}/soft_hsm.py, {Lib/pyhsm => pyhsm}/stick.py, {Lib/pyhsm => pyhsm}/stick_client.py, {Lib/pyhsm => pyhsm}/util.py, {Lib/pyhsm => pyhsm}/validate_cmd.py, {Lib/pyhsm => pyhsm}/version.py, pyhsm/yubicommon, {Lib/pyhsm => pyhsm}/yubikey.py, setup.py, test/__init__.py, {Tests => test}/run.sh, {Tests => test}/test_aead.py, {Tests => test}/test_aes_ecb.py, {Tests => test}/test_basics.py, {Tests => test}/test_buffer.py, {Tests => test}/test_common.py, {Tests => test}/test_configure.py, {Tests => test}/test_db.py, {Tests => test}/test_hmac.py, {Tests => test}/test_init.py, {Tests => test}/test_misc.py, {Tests => test}/test_oath.py, {Tests => test}/test_otp_validate.py, {Tests => test}/test_soft_hsm.py, {Tests => test}/test_stick.py, {Tests => test}/test_util.py, {Tests => test}/test_yubikey_validate.py, vendor/yubicommon: Move stuff around and use yubicommon. 2015-08-24 Dain Nilsson * NEWS: Prepare for release. 2015-08-24 Dain Nilsson * maintainer-scripts/README: Corrected release instructions in README. 2015-08-24 Dain Nilsson * Lib/pyhsm/__init__.py, NEWS, setup.py: Corrected version and NEWS. 2015-08-21 Simon Josefsson * Lib/pyhsm/__init__.py, NEWS, setup.py: Bump version. 2015-08-17 Jean Paul Galea * doc/Database_Usage.adoc: Documentation fixes. - newline between IMPORT/EXPORT examples. - capital letters. 2015-08-17 Jean Paul Galea * yubikey-ksm/yhsm-yubikey-ksm: Changes tabs -> spaces. - adhere with project convention, avoid subtle bugs due to incorrect indentation. 2015-08-11 Klas Lindfors * yubikey-ksm/yhsm-yubikey-ksm: commit the transaction after looking at all rows and print sqlalchemy exception 2014-10-30 Dain Nilsson * doc/Database_Usage.adoc: CAUTION instead of NOTE. 2014-10-30 Dain Nilsson * doc/DatabaseUsage.txt, doc/Database_Usage.adoc, doc/Intro.adoc, doc/Intro.txt, doc/{RunningSelfTests.txt => Running_Hardware_Tests.adoc}, doc/{YubiKeyKSM.txt => YubiKey_KSM.adoc}: Made doc/* into asciidoc. 2014-10-29 Henrik Stråth * README: Update README 2014-10-29 Henrik Stråth * README: Update README 2014-10-29 Henrik Stråth * README.adoc: symlinked README 2014-09-22 Simon Josefsson * Lib/pyhsm/basic_cmd.py: Fix some cut'n'paste issues. 2014-09-18 Simon Josefsson * Lib/pyhsm/__init__.py, NEWS, setup.py: Version 1.0.4k. 2014-09-18 Simon Josefsson * NEWS, utils/yhsm-db-export, utils/yhsm-db-import: Make it work. 2014-09-16 Simon Josefsson * NEWS: Version 1.0.4j. 2014-09-16 Simon Josefsson * yubikey-ksm/yhsm-yubikey-ksm: More verbose. 2014-09-16 Simon Josefsson * Lib/pyhsm/__init__.py, NEWS, setup.py, yubikey-ksm/yhsm-yubikey-ksm: yhsm-yubikey-ksm: Fix syntax error. 2014-09-16 Simon Josefsson * Lib/pyhsm/__init__.py: Prepare for release. 2014-09-16 Simon Josefsson * NEWS: Version 1.0.4i. 2014-09-16 Simon Josefsson * setup.py: Bump setup.py. 2014-09-16 Simon Josefsson * maintainer-scripts/make-release.sh: Fix release script. 2014-09-16 Simon Josefsson * MANIFEST.in: Drop wiki/. 2014-09-16 Simon Josefsson * README: Updates. 2014-09-16 Simon Josefsson * utils/yhsm-db-export.1, utils/yhsm-db-import.1: Typo. 2014-09-16 Klas Lindfors * utils/yhsm-db-export.1, utils/yhsm-db-import.1: man pages 2014-09-16 Simon Josefsson * utils/yhsm-db-export, utils/yhsm-db-import: Don't read username/password from stdin. 2014-09-16 Simon Josefsson * NEWS: Doc fixes. 2014-09-16 Simon Josefsson * yubikey-ksm/yhsm-yubikey-ksm: Fix error handling a bit. 2014-09-16 Simon Josefsson * yubikey-ksm/yhsm-yubikey-ksm: Don't read username/password from stdin. 2014-09-16 Klas Lindfors * doc/YubiKeyKSM.txt: update URL 2014-09-16 Simon Josefsson * utils/yhsm-db-export, utils/yhsm-db-import, yubikey-ksm/yhsm-yubikey-ksm: Fix tool descriptions. Fix SQLAlchemy import statements. 2014-09-16 Simon Josefsson * : commit 16d72e989905585ac3f671f683c03faf945c1006 Author: Klas Lindfors Date: Tue Sep 16 12:12:49 2014 +0200 2014-09-16 Simon Josefsson * : commit c9c66dfcae086e2112766a22a9c9743ab4a792db Author: Simon Josefsson Date: Tue Sep 16 11:44:15 2014 +0200 2014-09-16 Simon Josefsson * AUTHORS, utils/README.db.import-export => doc/DatabaseUsage.txt, Tests/README => doc/RunningSelfTests.txt: Doc fixes. 2014-09-16 Simon Josefsson * AUTHORS, README: Cleanup README. 2014-09-16 Simon Josefsson * doc/Intro.txt, doc/README, doc/YubiKeyKSM.txt: Simplify doc/README since we dropped the wiki. 2014-09-16 Simon Josefsson * .gitmodules: Drop .gitmodules. 2014-09-16 Simon Josefsson * .gitmodules, doc/wiki: Drop git submodule. 2014-09-15 Simon Josefsson * COPYING, maintainer-scripts/make-release.sh, utils/yhsm-daemon.1, utils/yhsm-decrypt-aead.1, utils/yhsm-generate-keys.1, utils/yhsm-keystore-unlock.1, utils/yhsm-linux-add-entropy.1, yhsm-val/yhsm-init-oath-token.1, yhsm-val/yhsm-validate-otp.1, yhsm-val/yhsm-validation-server.1, yubikey-ksm/yhsm-import-keys.1, yubikey-ksm/yhsm-yubikey-ksm.1: Fix URLs a bit. 2014-09-15 Simon Josefsson * BLURB: Fix URL. 2014-08-28 Klas Lindfors * : commit e4ea12b8c2fd5910e662924f5f572ade0de16910 Merge: ffab80a 457115e Author: Dain Nilsson Date: Tue Jun 24 11:18:32 2014 +0200 2014-06-16 Alex Fisher * setup.py: Add python-daemon dependency 2014-06-12 Alex Fisher * utils/yhsm-daemon: Add option to write pid file This option is similar to the one that already exists for yhsm-yubikey-ksm 2014-06-12 Alex Fisher * yubikey-ksm/yhsm-yubikey-ksm: Add option to run yhsm-yubikey-ksm as a proper daemon Uses https://pypi.python.org/pypi/python-daemon/ library. 2014-06-12 Alex Fisher * utils/yhsm-daemon: Add option to run yhsm-daemon as a proper daemon 2014-06-12 Alex Fisher * Lib/pyhsm/stick_client.py: Make stick_client raw_device() return the socket 2014-04-25 Simon Josefsson * README: Mention pycrypto >= 2.1. 2014-04-25 Klas Lindfors * utils/yhsm-generate-keys: use nonce correctly, not public_id we set the nonce variable already, just use it for generate_aead() 2014-03-20 Klas Lindfors * yubikey-ksm/yhsm-yubikey-ksm: restructure to get a new connection from engine for each query should use sqlalchemy's pool then 2014-03-17 Klas Lindfors * utils/yhsm-db-import: allow db-import to continue when getting errors 2014-03-17 Klas Lindfors * utils/yhsm-db-import: publicId -> public_id 2014-03-17 Klas Lindfors * yubikey-ksm/yhsm-yubikey-ksm: use transactions to avoid isolation issues 2014-02-19 Klas Lindfors * BLURB: add BLURB 2014-02-19 Klas Lindfors * BLURB: add BLURB 2014-01-30 Klas Lindfors * : commit 687e80be6f62026bdb2733023b04bec49c4e789d Author: Klas Lindfors Date: Wed Jan 29 15:38:47 2014 +0100 2014-01-29 Klas Lindfors * utils/yhsm-db-export, utils/yhsm-db-import, yubikey-ksm/yhsm-yubikey-ksm: whitespace cleanup 2014-01-29 Klas Lindfors * utils/yhsm-db-import: skip files that don't look like aeads 2014-01-29 Klas Lindfors * yubikey-ksm/yhsm-yubikey-ksm: more database work 2014-01-29 Klas Lindfors * yubikey-ksm/yhsm-yubikey-ksm: only set the key_handle if we get an aead back 2014-01-29 Klas Lindfors * yubikey-ksm/yhsm-yubikey-ksm: re-work dbload, use sqlalchemy 2014-01-28 Klas Lindfors * utils/yhsm-db-import: minor refactor of yhsm-db-import 2014-01-09 Dain Nilsson * Lib/pyhsm/__init__.py, NEWS, setup.py: Bumped version and updated NEWS for release. 2014-01-09 Dain Nilsson * utils/yhsm-daemon, utils/yhsm-daemon.1: Added note about client/daemon version compatibility. 2014-01-08 Dain Nilsson * Lib/pyhsm/stick_client.py, utils/yhsm-daemon: Replaced pickle with JSON in yhsm-daemon. 2014-01-08 Dain Nilsson * .gitignore: Added .ropeproject to gitignore. 2013-11-01 Klas Lindfors * Tests/README: fixed README hsm configuration from tom@yubico.com moved from dpkg repo. 2013-09-18 tom * yubikey-ksm/yhsm-yubikey-ksm: database support now supports files system aeads also 2013-09-12 Tommaso Galassi De Orchi * yubikey-ksm/yhsm-yubikey-ksm: added AEADs loading from database with --db-url option 2013-08-14 Tommaso Galassi De Orchi * utils/yhsm-db-export, utils/yhsm-db-import: added load/save from lib 2013-08-13 Simon Josefsson * doc/db_schema: Add nonce field. Add explicit sizes. 2013-07-22 Tommaso Galassi De Orchi * utils/yhsm-db-export, utils/yhsm-db-import: minor improvements 2013-07-19 Tommaso Galassi De Orchi * utils/README.db.import-export: updated README.db.import-export 2013-07-19 Tommaso Galassi De Orchi * utils/README.db.import-export: updated README.db.import-export 2013-07-19 Tommaso Galassi De Orchi * utils/db_schema: removed db_schema 2013-07-18 Tommaso Galassi De Orchi * utils/yhsm-db-export: security improvment for yhsm-db-export 2013-07-17 Tommaso Galassi De Orchi * utils/yhsm-db-import: security fix for url 2013-07-16 Tommaso Galassi De Orchi * utils/yhsm-db-export: minor improvments 2013-07-16 Tommaso Galassi De Orchi * utils/README.db.import-export, utils/yhsm-db-export, utils/yhsm-db-import: minor improvments 2013-07-16 Tommaso Galassi De Orchi * utils/yhsm-db-export, utils/yhsm-db-import: added + on db-import-export 2013-07-16 Tommaso Galassi De Orchi * utils/{README.yhsm-dbimport-aead => README.db.import-export}, utils/db_schema, utils/yhsm-db-export, utils/{yhsm-db-manage => yhsm-db-import}: added yhsm-db-import and yhsm-db-export and db_schema 2013-07-12 Tommaso Galassi De Orchi * doc/db_schema: added db_schema in doc/ 2013-07-12 Tommaso Galassi De Orchi * utils/README.yhsm-dbimport-aead, utils/yhsm-db-manage: Added yhsm-db-manage and README.yhsm-db-manage for importing the AEADs into a Database 2013-05-06 Dain Nilsson * utils/yhsm-daemon: Removed debug print. 2013-05-06 Dain Nilsson * NEWS: Updated NEWS for release. 2013-05-06 Dain Nilsson * maintainer-scripts/README: Edit README 2013-05-06 Dain Nilsson * maintainer-scripts/update-release-page.sh: Removed update-release-page as it is no longer needed. 2013-05-06 Dain Nilsson * utils/yhsm-daemon: Raise YHSM_Error instead of Exception. 2013-05-06 Klas Lindfors * yubikey-ksm/yhsm-yubikey-ksm.1: man page 2013-05-03 Klas Lindfors * yubikey-ksm/yhsm-yubikey-ksm: add stats collection and extraction through it's own url 2013-05-06 Dain Nilsson * Lib/pyhsm/stick_client.py, utils/yhsm-daemon: More daemon fixes. 2013-05-06 Dain Nilsson * NEWS: Updated NEWS. 2013-05-06 Dain Nilsson * Lib/pyhsm/base.py, Lib/pyhsm/stick_client.py, utils/yhsm-daemon, utils/yhsm-daemon.1: Improved yhsm-daemon. 2013-04-22 Dain Nilsson * maintainer-scripts/make-release.sh: Fix release publishing. 2013-04-12 Dain Nilsson * Lib/pyhsm/__init__.py, NEWS, setup.py: Bumped version post release. 2013-04-12 Dain Nilsson * Lib/pyhsm/__init__.py, NEWS, setup.py: Updated NEWS, setup.py, and __init__.py for new version. 2013-04-12 Dain Nilsson * utils/yhsm-daemon, utils/yhsm-daemon.1: Changed default port number. 2013-04-12 Dain Nilsson * utils/yhsm-daemon, utils/yhsm-daemon.1: Add man page for yhsm-daemon. 2013-04-12 Dain Nilsson * Lib/pyhsm/stick_server.py => utils/yhsm-daemon: Added yhsm-daemon. 2013-04-12 Dain Nilsson * Lib/pyhsm/stick_server.py: Improved locking. 2013-04-12 Dain Nilsson * Lib/pyhsm/base.py, Lib/pyhsm/stick_server.py, Tests/README, Tests/test_common.py, Tests/test_init.py: Allow running tests using a different device. 2013-04-12 Dain Nilsson * Lib/pyhsm/stick.py, Lib/pyhsm/stick_client.py, Lib/pyhsm/stick_server.py: Create device from url. 2013-04-04 Dain Nilsson * Lib/pyhsm/base.py, Lib/pyhsm/cmd.py, Lib/pyhsm/stick.py, Lib/pyhsm/stick_client.py, Lib/pyhsm/stick_server.py: Added client and server. 2013-04-11 Dain Nilsson * Tests/test_yubikey_validate.py: Fixed test passig wrong argument. 2013-04-11 Dain Nilsson * Tests/README: Added README for running tests. 2013-04-11 Dain Nilsson * setup.py: Lower required versions of pyserial and pycrypto. 2013-04-10 Dain Nilsson * .gitignore, MANIFEST.in, maintainer-scripts/make-release.sh: Rewrite of make-release.sh 2013-04-08 Fredrik Thulin * setup.py: Also depends on pycrypto. 2013-04-08 Fredrik Thulin * setup.py: Add missing dependency information. 2013-04-06 Simon Josefsson * NEWS: Version 1.0.4e. 2013-03-31 Simon Josefsson * NEWS: Doc fix. 2013-03-31 Simon Josefsson * utils/yhsm-decrypt-aead: Rework logging code so it produces correct results. 2013-03-31 Simon Josefsson * utils/yhsm-decrypt-aead: Cleanup warning/error diagnostics. Print them to stderr. 2013-03-30 Simon Josefsson * NEWS, utils/yhsm-decrypt-aead: yhsm-decrypt-aead: For yubikey-csv output, fix prefix field. 2013-03-18 Klas Lindfors * Lib/pyhsm/__init__.py, NEWS, setup.py: bump versions 2013-03-18 Klas Lindfors * NEWS: News for version 1.0.4d 2013-03-18 Klas Lindfors * yubikey-ksm/yhsm-yubikey-ksm: only set public_id as nonce if we didn't find a nonce in the file also making sure it's encoded correctly 2013-03-18 Klas Lindfors * Lib/pyhsm/__init__.py, NEWS, setup.py: bump versions post-release 2013-03-18 Klas Lindfors * COPYING: bump Copyright 2013-03-18 Klas Lindfors * NEWS: NEWS for 1.0.4c 2013-03-18 Klas Lindfors * maintainer-scripts/README: typo 2013-03-18 Klas Lindfors * README: new ppa and key 2013-03-18 Klas Lindfors * utils/yhsm-generate-keys, utils/yhsm-generate-keys.1, yubikey-ksm/yhsm-import-keys, yubikey-ksm/yhsm-import-keys.1: add --random-nonce to yhsm-generate-keys and yhsm-import-keys 2013-03-15 Klas Lindfors * Lib/pyhsm/yubikey.py: fix the case where public_id and nonce differ the nonce is stored in the aead, so if it's there, use it 2013-02-11 Dain Nilsson * maintainer-scripts/make-release.sh: Added check for correct NEWS header. 2013-02-11 Dain Nilsson * Lib/pyhsm/__init__.py, NEWS, setup.py: Bumped version numbers post release. 2013-02-11 Dain Nilsson * maintainer-scripts/README: Added README to maintainer-scripts. 2013-02-11 Dain Nilsson * maintainer-scripts/update-release-page.sh: Added script to update releases on gh-pages. 2013-02-11 Dain Nilsson * NEWS: Updated NEWS 2013-02-11 Dain Nilsson * utils/yhsm-generate-keys, yubikey-ksm/yhsm-import-keys: Don't create/import a zero (cc...c) key, as the nonce will be wrong. 2013-02-11 Dain Nilsson * Tests/test_aead.py, Tests/test_db.py: Skip some tests when running < 1.0.4 2013-02-08 Dain Nilsson * utils/yhsm-generate-keys: Fixed incorrect nonce when generating keys. 2013-02-08 Dain Nilsson * Lib/pyhsm/__init__.py: Bumped version to 1.0.4b 2013-02-07 Dain Nilsson * yubikey-ksm/yhsm-import-keys: Ignore lines starting with #. 2012-09-12 Simon Josefsson * setup.py: Bump version. 2012-09-12 Simon Josefsson * NEWS: Update. 2012-08-30 Fredrik Thulin * utils/yhsm-decrypt-aead: Better error handling for batch runs. Add --fail-fast to exit as soon as a failure is encountered (default: false). 2012-08-30 Fredrik Thulin * utils/yhsm-decrypt-aead: Ignore non-modhex files (other metadata) 2012-08-30 Fredrik Thulin * yubikey-ksm/yhsm-import-keys: Support soft HSM AEAD generation. 2012-08-30 Fredrik Thulin * utils/yhsm-decrypt-aead: aead.nonce should be 6 bytes, not 12 chars. 2012-08-28 Fredrik Thulin * : commit e1e4abba10f57717c8684a37aab2f080b73974ef Author: Fredrik Thulin Date: Mon Aug 27 13:07:07 2012 +0200 2012-08-27 Fredrik Thulin * utils/yhsm-decrypt-aead: --aes-key-out should not be required. 2012-08-27 Fredrik Thulin * utils/yhsm-generate-keys: typo in documentation 2012-08-27 Fredrik Thulin * utils/yhsm-decrypt-aead: Add '--format aead' to genereate new AEADs. Also adds a number of other arguments : --output-dir --key-handle-out --aes-key-out This is usable to convert AEADs from one KSM to another. 2012-08-24 Fredrik Thulin * utils/yhsm-generate-keys: bugfix: public_id is not in hex here. 2012-08-22 Simon Josefsson * NEWS: Add. 2012-08-22 Fredrik Thulin * : commit 8d24b21e99a58e6ab2331d89806e7a38f422f3e2 Author: Simon Josefsson Date: Wed Aug 22 12:36:42 2012 +0200 2012-06-26 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Prepare version 1.0.4a. 2012-06-26 Fredrik Thulin * Lib/pyhsm/base.py: Verify that data we send to and receive from the YubiHSM isn't mangled. 2012-06-26 Fredrik Thulin * yhsm-val/yhsm-validation-server, yubikey-ksm/yhsm-yubikey-ksm: Handle IPv6 addresses in --addr. We need to set address_family to AF_INET6 manually, since SocketServer.TCPserver() always try to use AF_INET. 2012-06-21 Fredrik Thulin * Lib/pyhsm/aead_cmd.py: mend epydoc generation 2012-06-21 Fredrik Thulin * COPYING, Lib/pyhsm/__init__.py, setup.py: Prepare for release 1.0.4. 2012-06-21 Fredrik Thulin * utils/yhsm-decrypt-aead: Minor bugfixes. 2012-06-21 Fredrik Thulin * utils/yhsm-decrypt-aead.1, utils/yhsm-generate-keys.1: init 2012-06-21 Fredrik Thulin * utils/yhsm-keystore-unlock.1, utils/yhsm-linux-add-entropy.1: copy-paste remains 2012-06-21 Fredrik Thulin * utils/yhsm-generate-keys: Produce meaningful exit-status. 2012-06-21 Fredrik Thulin * examples/yhsm-generate-keys: Not an example anymore (moved to utils/), bye. 2012-06-20 Fredrik Thulin * utils/yhsm-generate-keys: Not an example anymore. 2012-06-20 Fredrik Thulin * examples/yhsm-generate-keys: Update comment with info about new yhsm-decrypt-aead. 2012-06-20 Fredrik Thulin * examples/yhsm-generate-keys: Make work again. 2012-06-20 Fredrik Thulin * utils/yhsm-decrypt-aead: init 2012-06-20 Fredrik Thulin * Lib/pyhsm/soft_hsm.py, Tests/test_init.py, Tests/test_soft_hsm.py: Handle messages not multiple of block-size long. 2012-06-20 Fredrik Thulin * Lib/pyhsm/__init__.py, Lib/pyhsm/base.py, Lib/pyhsm/soft_hsm.py, Tests/test_soft_hsm.py: Implement AES CCM a'la YubiHSM in software. 2012-06-20 Fredrik Thulin * : commit fba63d51ff0badc8b593770b62bca60d49b61a80 Author: Fredrik Thulin Date: Tue Jun 19 16:05:29 2012 +0200 2012-02-24 Fredrik Thulin * Lib/pyhsm/aead_cmd.py: Store nonce used when generating an AEAD when saving an AEAD to file. If a nonce different from the public_id is used when generating an AEAD, we must keep track of the nonce separately. This commit changes the file format used, but should be backwards compatible by using the public_id as nonce on load() if the old file format is detected. 2012-01-25 Fredrik Thulin * Tests/test_yubikey_validate.py: Remove surplus comments. 2012-01-25 Fredrik Thulin * Tests/test_configure.py, Tests/test_otp_validate.py: Key 0x1002 also needs YSM_USER_NONCE now. 2012-01-24 Fredrik Thulin * Lib/pyhsm/base.py, Lib/pyhsm/db_cmd.py, Lib/pyhsm/defines.py, Lib/pyhsm/version.py, Tests/test_db.py, doc/YubiHSM_if.h: Implement YSM_DB_YUBIKEY_AEAD_STORE2. 2012-01-24 Fredrik Thulin * Tests/test_aead.py, Tests/test_configure.py: Test FLAG_USER_NONCE_ENABLE. 2012-01-20 Fredrik Thulin * Lib/pyhsm/aead_cmd.py, Lib/pyhsm/util.py, Tests/test_aead.py: Make AEAD generation with HSM generated nonce work. 2012-02-24 Fredrik Thulin * Lib/pyhsm/aead_cmd.py, Lib/pyhsm/base.py, Lib/pyhsm/cmd.py: Documentation updates. 2012-01-11 Fredrik Thulin * Tests/test_init.py: Another fix for 0.9.8 testing. 2012-01-05 Fredrik Thulin * Tests/test_configure.py: Mend OTP replay in unlock test. 2012-01-05 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Prepare version 1.0.3c. 2012-01-05 Fredrik Thulin * utils/yhsm-keystore-unlock, utils/yhsm-keystore-unlock.1: Add --stdin to read (piped) input from stdin. 2012-01-02 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Prepare version 1.0.3b. 2012-01-02 Fredrik Thulin * .gitattributes: Include maintainer scripts in release tar-balls. Adapt to the Debian (?) definition of source: "preferred form for modification, including build scripts, etc". 2011-12-22 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Prepare version 1.0.3a. 2011-12-21 Fredrik Thulin * {yubikey-ksm => examples}/yhsm-generate-keys: Move currently useless yhsm-generate-keys to examples/. It is currently useless since it generates secrets for YubiKeys without allowing any YubiKeys to be programmed with these secrets. See longer explanation in top pydoc. 2011-12-21 Fredrik Thulin * utils/yhsm-keystore-unlock.1, utils/yhsm-linux-add-entropy.1, yhsm-val/yhsm-init-oath-token.1, yhsm-val/yhsm-validate-otp.1, yhsm-val/yhsm-validation-server.1, yubikey-ksm/yhsm-import-keys.1, yubikey-ksm/yhsm-yubikey-ksm.1: Add man-pages. 2011-12-21 Fredrik Thulin * yubikey-ksm/yhsm-import-keys: Minor usage text improvements. 2011-12-21 Fredrik Thulin * yubikey-ksm/yhsm-import-keys: Don't split --key-handles on comma. (to align with yhsm-yubikey-ksm) 2011-12-21 Fredrik Thulin * yubikey-ksm/yhsm-yubikey-ksm: Minor usage text improvements. 2011-12-21 Fredrik Thulin * yubikey-ksm/yhsm-yubikey-ksm: Remove unused parameter --public-id-chars. 2011-12-21 Fredrik Thulin * yhsm-val/yhsm-validation-server: Minor usage text improvement. 2011-12-21 Fredrik Thulin * yhsm-val/yhsm-init-oath-token: usage text improvements 2011-12-21 Fredrik Thulin * yhsm-val/yhsm-init-oath-token: Convey status in exit code. 2011-12-21 Fredrik Thulin * utils/yhsm-linux-add-entropy: Add proper args-parsing. 2011-12-20 Fredrik Thulin * maintainer-scripts/make-release.sh: Add --no-test and --no-sign. 2011-11-15 Fredrik Thulin * maintainer-scripts/make-release.sh: Produce a ChangeLog from git history. 2011-11-15 Fredrik Thulin * Tests/test_aes_ecb.py, Tests/test_common.py, Tests/test_init.py, Tests/test_misc.py: Make test suite work with 0.9.8 again. 2011-11-08 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Prepare version 1.0.3. 2011-11-08 Fredrik Thulin * yhsm-val/yhsm-validation-server: Assorted minor changes. 2011-11-08 Fredrik Thulin * Lib/pyhsm/yubikey.py: Document these utility functions properly. 2011-11-08 Fredrik Thulin * README: Add note about timeout problems in Python 2.6 (SocketServer.py). 2011-11-02 Fredrik Thulin * : commit a80b8a32016eb9a12253638197c7a0dc088a2656 Author: Fredrik Thulin Date: Wed Nov 2 15:08:32 2011 +0100 2011-11-02 Fredrik Thulin * yhsm-val/yhsm-validation-server: Response tinkering. 2011-11-02 Fredrik Thulin * yhsm-val/yhsm-validation-server: Change Content-Type to text/plain. 2011-11-02 Fredrik Thulin * yhsm-val/yhsm-validation-server: Correctly format error responses for 2.0 OTP validation. 2011-11-02 Fredrik Thulin * yhsm-val/yhsm-validation-server: Working HMAC-SHA-1 request validation. 2011-11-01 Fredrik Thulin * Tests/test_configure.py: Update to handle firmware 1.0.3. 2011-11-01 Fredrik Thulin * Lib/pyhsm/cmd.py: typo 2011-08-29 Fredrik Thulin * Tests/test_common.py: Must ignore errors from OTP unlocking as well. 2011-11-01 Fredrik Thulin * Lib/pyhsm/cmd.py: Raise a nice YHSM_Error on unexpected responses. 2011-10-26 Fredrik Thulin * yhsm-val/yhsm-validation-server: Work in progress, mostly work but client id handling is missing. 2011-10-17 Fredrik Thulin * yubikey-ksm/yhsm-import-keys: Indicate errors in exit code. 2011-10-07 Fredrik Thulin * utils/yhsm-keystore-unlock: Show 'Aborted' message even when not verbose. 2011-09-21 Fredrik Thulin * : commit 762cfd85e7bccc41e6e3eafcc5bac36195523034 Author: Fredrik Thulin Date: Wed Sep 21 13:46:46 2011 +0200 2011-09-21 Fredrik Thulin * COPYING, Lib/pyhsm/__init__.py, Lib/pyhsm/aead_cmd.py, Lib/pyhsm/aes_ecb_cmd.py, Lib/pyhsm/base.py, Lib/pyhsm/basic_cmd.py, Lib/pyhsm/buffer_cmd.py, Lib/pyhsm/cmd.py, Lib/pyhsm/db_cmd.py, Lib/pyhsm/debug_cmd.py, Lib/pyhsm/defines.py, Lib/pyhsm/exception.py, Lib/pyhsm/hmac_cmd.py, Lib/pyhsm/oath_hotp.py, Lib/pyhsm/stick.py, Lib/pyhsm/util.py, Lib/pyhsm/validate_cmd.py, Lib/pyhsm/version.py, Lib/pyhsm/yubikey.py, README, Tests/test_aead.py, Tests/test_aes_ecb.py, Tests/test_basics.py, Tests/test_buffer.py, Tests/test_common.py, Tests/test_configure.py, Tests/test_db.py, Tests/test_hmac.py, Tests/test_init.py, Tests/test_misc.py, Tests/test_oath.py, Tests/test_otp_validate.py, Tests/test_stick.py, Tests/test_util.py, Tests/test_yubikey_validate.py, examples/yhsm-monitor-exit.py, examples/yhsm-password-auth.py, examples/yhsm-sysinfo.py, utils/yhsm-keystore-unlock, utils/yhsm-linux-add-entropy, yhsm-val/yhsm-init-oath-token, yhsm-val/yhsm-validate-otp, yhsm-val/yhsm-validation-server, yubikey-ksm/yhsm-generate-keys, yubikey-ksm/yhsm-import-keys, yubikey-ksm/yhsm-yubikey-ksm: Clarify licensing by refering to the COPYING file. 2011-09-14 Simon Josefsson * README: Doc fix Some were suggested by Tollef Fog Heen . 2011-09-14 Simon Josefsson * README: Reorder and fix names after renaming. 2011-08-29 Fredrik Thulin * : commit 9cc369a1db92ad58c781f7877a687a866a71d2fe Author: Simon Josefsson Date: Wed Aug 24 15:43:19 2011 +0200 2011-08-24 Fredrik Thulin * utils/yhsm-keystore-unlock: Usage experience tweaks. 2011-08-24 Fredrik Thulin * utils/yhsm-keystore-unlock: Add --no-otp for use by init-scripts. 2011-08-21 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Prepare version 1.0.2b. 2011-08-21 Fredrik Thulin * utils/yhsm-keystore-unlock: Add OTP support and improve usability. 2011-08-21 Fredrik Thulin * Lib/pyhsm/version.py, Tests/test_common.py, Tests/test_configure.py, Tests/test_misc.py: Mend tests for 0.9.x that does not have OTP unlock. 2011-08-21 Fredrik Thulin * Lib/pyhsm/version.py: Correct have_YSM_BUFFER_LOAD. 2011-08-21 Fredrik Thulin * maintainer-scripts/make-release.sh: Fix tar-file name. 2011-08-21 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Prepare for version 1.0.2a. 2011-08-21 Fredrik Thulin * Tests/run.sh: Set PYTHONPATH to make sure we test the current version. 2011-08-21 Fredrik Thulin * Lib/pyhsm/base.py, Lib/pyhsm/basic_cmd.py, Tests/test_common.py, Tests/test_configure.py, Tests/test_init.py: Implement missing YSM_HSM_UNLOCK. 2011-08-21 Fredrik Thulin * Tests/test_common.py, Tests/test_yubikey_validate.py, doc/wiki: Move YubiKeyEmu to test_common. 2011-08-18 Fredrik Thulin * Lib/pyhsm/base.py: Introduce unlock(), deprecating key_storage_unlock(). 2011-08-18 Fredrik Thulin * maintainer-scripts/make-release.sh: Keep python-pyhsm prefix in tar. 2011-08-18 Fredrik Thulin * .gitattributes, maintainer-scripts/make-release.sh: Tidy up generated release file. 2011-08-18 Fredrik Thulin * maintainer-scripts/make-release.sh: Exclude doc/wiki/.git. 2011-08-18 Fredrik Thulin * maintainer-scripts/generate_html.sh: No graphs. Made distribution unnecessarily big. 2011-08-18 Fredrik Thulin * maintainer-scripts/make-release.sh: Fix where the releases dir ends up. 2011-08-18 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Prepare for version 1.0.2. 2011-08-18 Fredrik Thulin * maintainer-scripts/make-release.sh: Check that version variables are updated. 2011-08-18 Fredrik Thulin * .gitignore: Add generated doc/html 2011-08-18 Fredrik Thulin * doc/README: init 2011-08-18 Fredrik Thulin * .gitattributes, maintainer-scripts/make-release.sh: Prepare to make official releases. 2011-08-18 Fredrik Thulin * .gitmodules: Add Github wiki submodule. 2011-08-18 Fredrik Thulin * Lib/pyhsm/version.py, Tests/test_otp_validate.py: Fix a test case to work with 0.9.8 as well as 1.0. Hope this works - I don't have any 0.9.8 YubiHSM:s available to verify. 2011-08-18 Fredrik Thulin * Lib/pyhsm/__init__.py, Lib/pyhsm/base.py, Lib/pyhsm/basic_cmd.py, Lib/pyhsm/defines.py, Lib/pyhsm/version.py, Tests/test_aead.py, Tests/test_common.py, Tests/test_configure.py, Tests/test_init.py, Tests/test_misc.py, Tests/test_oath.py, Tests/test_otp_validate.py, doc/YubiHSM_if.h, utils/yhsm-keystore-unlock: Update to support YubiHSM 1.0. The most significant change from version 0.9.8 is two new keystore unlock commands (deprecating the old YSM_KEY_STORAGE_UNLOCK). YSM_KEY_STORE_DECRYPT - decrypt (AES-256) the keystore into YubiHSM RAM with a passphrase YSM_HSM_UNLOCK - unlock HSM operations using a YubiKey OTP Either one, both or none of these two may be enabled when the YubiHSM is configured. 2011-08-02 Fredrik Thulin * examples/yhsm-storage-unlock.py => utils/yhsm-keystore-unlock, examples/yhsm-linux-add-entropy.py => utils/yhsm-linux-add-entropy: Promote two examples to new utils/ dir. 2011-08-02 Fredrik Thulin * yubikey-ksm/yhsm-yubikey-ksm: Add --pid-file support. 2011-08-02 Fredrik Thulin * yhsm-val/yhsm-validation-server: Improve --help output. 2011-08-02 Fredrik Thulin * yubikey-ksm/yhsm-generate-keys, yubikey-ksm/yhsm-import-keys, yubikey-ksm/yhsm-yubikey-ksm: Change default AEAD directory to /var/cache/yubikey-ksm/aeads. Having the default on RAM disk was just convenient during development. Also did some general code/doc improvements. 2011-08-02 Fredrik Thulin * yhsm-val/yhsm-validation-server: pylint improvements 2011-08-01 Fredrik Thulin * {doc => maintainer-scripts}/generate_html.sh: rename 2011-08-01 Fredrik Thulin * examples/yhsm-storage-unlock.py: Add proper args parsing. 2011-07-29 Fredrik Thulin * yhsm-val/yhsm-validation-server: Show default value for all args. 2011-07-27 Fredrik Thulin * Lib/pyhsm/base.py, Lib/pyhsm/validate_cmd.py: More type annotations. 2011-07-26 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Prepare for version 0.9.8e. 2011-07-26 Fredrik Thulin * yhsm-val/yhsm-validation-server: Add --pid-file support. 2011-07-26 Fredrik Thulin * doc/generate_html.sh: create doc/html/ if missing 2011-07-26 Fredrik Thulin * Lib/pyhsm/aes_ecb_cmd.py: Input-validate AES ECB block size on encrypt/decrypt. 2011-07-26 Fredrik Thulin * Lib/pyhsm/base.py: More type information. 2011-07-26 Fredrik Thulin * Lib/pyhsm/debug_cmd.py: comment 2011-07-25 Fredrik Thulin * Lib/pyhsm/__init__.py, Lib/pyhsm/base.py, Lib/pyhsm/basic_cmd.py, Lib/pyhsm/buffer_cmd.py, Lib/pyhsm/cmd.py, Lib/pyhsm/debug_cmd.py, Lib/pyhsm/exception.py: epydoc annotated API documentation. 2011-07-25 Fredrik Thulin * doc/generate_html.sh: init 2011-07-25 Fredrik Thulin * Lib/pyhsm/oath_hotp.py: pylint fix 2011-07-17 Fredrik Thulin * README: Add introduction. 2011-07-17 Fredrik Thulin * Tests/test_yubikey_validate.py: remove debug output 2011-07-04 Fredrik Thulin * Lib/pyhsm/util.py, Tests/test_yubikey_validate.py: Correct max AEAD size. 2011-06-23 Fredrik Thulin * : commit 8f2614ab596623d9c62ad06eaf7e1bdcb6948abb Author: Fredrik Thulin Date: Thu Jun 23 11:53:16 2011 +0200 2011-06-23 Fredrik Thulin * yubikey-ksm/yhsm-yubikey-ksm: default args clarifications. 2011-06-23 Fredrik Thulin * yubikey-ksm/yhsm-yubikey-ksm: Add request timeout. Don't block forever on connections that suffer from network problems. 2011-06-09 Simon Josefsson * README: Mention python-pyhsm-dpkg. 2011-06-07 Fredrik Thulin * README: Add python version info. 2011-06-07 Fredrik Thulin * README: Add installation instructions. 2011-05-30 Fredrik Thulin * Tests/test_basics.py: Add test case for bug fixed in b8f71dceb. 2011-05-30 Fredrik Thulin * : Merge git://github.com/ThomasHabets/python-pyhsm 2011-05-29 Thomas Habets * Lib/pyhsm/basic_cmd.py: Fix type error in nonce repr format string 2011-05-26 Fredrik Thulin * Lib/pyhsm/base.py: speling 2011-05-25 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Prepare for version 0.9.8c. 2011-05-25 Fredrik Thulin * yubikey-ksm/yhsm-generate-keys: Bugfix calculation of bytes to randomize. 2011-05-25 Fredrik Thulin * Lib/pyhsm/util.py, Tests/test_util.py: Fix bug in input_validate_str. Bugfix the raising of exception when input string validation fails exact length check. Only resulted in a TypeError instead of the expected YHSM_WrongInputSize. 2011-05-23 Fredrik Thulin * yubikey-ksm/yhsm-yubikey-ksm: Support more than one key handle. 2011-05-12 Fredrik Thulin * Lib/pyhsm/__init__.py: remove redundantly listed module 'yubikey' 2011-05-04 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Forgot to update version for 0.9.8b. 2011-05-04 Fredrik Thulin * Lib/pyhsm/base.py, Lib/pyhsm/basic_cmd.py, Lib/pyhsm/defines.py, examples/yhsm-storage-unlock.py: Implement YSM_KEY_STORAGE_UNLOCK. 2011-05-02 Fredrik Thulin * Lib/pyhsm/aead_cmd.py, Lib/pyhsm/aes_ecb_cmd.py, Lib/pyhsm/basic_cmd.py, Lib/pyhsm/buffer_cmd.py, Lib/pyhsm/exception.py, Lib/pyhsm/stick.py, Lib/pyhsm/validate_cmd.py: Fix __all__ lists of classes. 2011-04-19 Fredrik Thulin * Lib/pyhsm/__init__.py: Update version to 0.9.8a. 2011-04-19 Fredrik Thulin * examples/yhsm-linux-add-entropy.py: init 2011-04-18 Fredrik Thulin * Lib/pyhsm/__init__.py: fix __all__ list and add basic usage doc. 2011-04-06 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Version 0.9.8. First public beta release. 2011-04-06 Fredrik Thulin * Lib/pyhsm/defines.py, Lib/pyhsm/oath_hotp.py, Tests/test_basics.py, Tests/test_oath.py: Namespace-fix TEMP_KEY_HANDLE -> YSM_TEMP_KEY_HANDLE. 2011-04-06 Fredrik Thulin * Tests/test_common.py: Use new define YSM_PROTOCOL_VERSION. 2011-04-06 Fredrik Thulin * yhsm-val/yhsm-validate-otp: Last minute pre-release fixes. 2011-04-06 Fredrik Thulin * yhsm-val/yhsm-validation-server: Give error message when disabled function is accessed. 2011-04-06 Fredrik Thulin * doc/YubiHSM_if.h: Namespace fixes. 2011-04-06 Fredrik Thulin * yhsm-val/yhsm-validation-server: Documentation and small fixes. 2011-04-06 Fredrik Thulin * yhsm-val/yhsm-init-oath-token: AEADs to be loaded into TEMP_KEY need flags now. 2011-04-06 Fredrik Thulin * yhsm-val/yhsm-validation-server: Change OATH to HOTP/OATH-HOTP. 2011-04-06 Fredrik Thulin * examples/yhsm-password-auth.py, yhsm-val/yhsm-validation-server: Use AEAD encrypt/validate instead of AES ECB encrypt/decrypt_cmd to get the integrity protection of the AEAD's MAC as well. 2011-04-05 Fredrik Thulin * Lib/pyhsm/aead_cmd.py, Lib/pyhsm/aes_ecb_cmd.py, Lib/pyhsm/basic_cmd.py, Lib/pyhsm/db_cmd.py, Lib/pyhsm/defines.py, Lib/pyhsm/hmac_cmd.py, Lib/pyhsm/util.py, Lib/pyhsm/validate_cmd.py, Tests/test_aes_ecb.py, Tests/test_hmac.py: Last-minute change of a lot of defined names. 2011-04-05 Fredrik Thulin * Lib/pyhsm/basic_cmd.py, Tests/test_basics.py, Tests/test_oath.py: Temp key now has flags. When generating an AEAD to be used as temp key, add four bytes with the permission flags the temp key handle should have if the AEAD is later loaded into the temp key. 2011-04-05 Fredrik Thulin * doc/YubiHSM_if.h: Add the C header file as documentation. 2011-04-05 Fredrik Thulin * yhsm-val/yhsm-validation-server: Implement pwhash validation. 2011-04-05 Fredrik Thulin * Lib/pyhsm/aes_ecb_cmd.py: Cleanups. 2011-04-05 Fredrik Thulin * Tests/test_basics.py: Fix test case after nonce change. 2011-04-04 Fredrik Thulin * yubikey-ksm/yhsm-yubikey-ksm: Aead -> AEAD 2011-04-04 Fredrik Thulin * yhsm-val/yhsm-init-oath-token, yhsm-val/yhsm-validate-otp, yhsm-val/yhsm-validation-server: init, validation application(s) 2011-04-04 Fredrik Thulin * Lib/pyhsm/__init__.py, Lib/pyhsm/oath_hotp.py, Tests/test_init.py, Tests/test_oath.py: init, OATH helper functions. 2011-04-04 Fredrik Thulin * Lib/pyhsm/basic_cmd.py: Remember nonce as 6 byte string too, for convenience. 2011-04-02 Fredrik Thulin * {yubikey-val => yhsm-val}/yhsm-validate-otp, yhsm-val/yhsm-validation-server: init 2011-04-02 Fredrik Thulin * yubikey-ksm/yhsm-yubikey-ksm: YHSM -> YubiHSM 2011-04-02 Fredrik Thulin * yubikey-ksm/yhsm-yubikey-ksm: Correct usage summary. 2011-04-02 Fredrik Thulin * yubikey-ksm/yhsm-yubikey-ksm: better function name 2011-04-02 Fredrik Thulin * yubikey-ksm/yhsm-yubikey-ksm: Refactor and adhere more to the YubiKSM communication spec. Errors should not be returned as HTTP non-2xx, but as a HTTP 200 with a body containing the string ERR optional-reason 2011-04-02 Fredrik Thulin * Lib/pyhsm/__init__.py: add missing buffer_cmd and db_cmd 2011-04-02 Fredrik Thulin * Lib/pyhsm/__init__.py, yubikey-ksm/yhsm-generate-keys, yubikey-ksm/yhsm-import-keys, yubikey-ksm/yhsm-yubikey-ksm: secrets_cmd was merged with aead_cmd. 2011-04-02 Fredrik Thulin * yubikey-ksm/yhsm-yubikey-ksm: Add documentation. 2011-04-01 Fredrik Thulin * Tests/run.sh: Avoid deprecation warnings from nosetests. 2011-04-01 Fredrik Thulin * Lib/pyhsm/defines.py, Tests/test_basics.py: Bugs hide in every untested LOC. 2011-04-01 Fredrik Thulin * Lib/pyhsm/base.py, Lib/pyhsm/hmac_cmd.py, Tests/test_hmac.py: Fix (and test) HMAC-SHA1 to_buffer and flags. 2011-04-01 Fredrik Thulin * Lib/pyhsm/base.py, Lib/pyhsm/hmac_cmd.py, Lib/pyhsm/stick.py, Lib/pyhsm/util.py, Tests/test_basics.py, Tests/test_hmac.py, Tests/test_init.py, Tests/test_stick.py, Tests/test_util.py: polishing, more tests 2011-04-01 Fredrik Thulin * Lib/pyhsm/__init__.py, Lib/pyhsm/base.py, setup.py: Version 0.9.4pre1. 2011-04-01 Fredrik Thulin * Lib/pyhsm/aead_cmd.py, Lib/pyhsm/cmd.py, Lib/pyhsm/db_cmd.py, Lib/pyhsm/debug_cmd.py, Lib/pyhsm/hmac_cmd.py, Lib/pyhsm/util.py, Lib/pyhsm/validate_cmd.py, Lib/pyhsm/yubikey.py, Tests/test_basics.py, Tests/test_buffer.py, Tests/test_configure.py, Tests/test_db.py, Tests/test_hmac.py, Tests/test_yubikey_validate.py: Improve test coverage, centralize input validation #2. 2011-04-01 Fredrik Thulin * examples/yhsm-password-auth.py: Mention PBKDF2. 2011-04-01 Fredrik Thulin * Lib/pyhsm/aead_cmd.py, Lib/pyhsm/aes_ecb_cmd.py, Lib/pyhsm/base.py, Lib/pyhsm/basic_cmd.py, Lib/pyhsm/buffer_cmd.py, Lib/pyhsm/util.py, Tests/test_aes_ecb.py, Tests/test_basics.py, Tests/test_configure.py: Improve test coverage, centralize input validation. 2011-04-01 Fredrik Thulin * Tests/run.sh: Implement --cover. 2011-03-31 Fredrik Thulin * Tests/test_aead.py, Tests/test_aes_ecb.py, Tests/test_common.py, Tests/test_configure.py, Tests/test_hmac.py: First flags ACL tests. 2011-03-31 Fredrik Thulin * Lib/pyhsm/base.py, Lib/pyhsm/hmac_cmd.py, Tests/test_hmac.py: Make base.hmac_sha1() execute() like all the others. 2011-03-31 Fredrik Thulin * Lib/pyhsm/aead_cmd.py, Lib/pyhsm/base.py, Lib/pyhsm/cmd.py, Lib/pyhsm/db_cmd.py, Lib/pyhsm/hmac_cmd.py: pylint suggested fixes. 2011-03-31 Fredrik Thulin * Lib/pyhsm/aead_cmd.py, Lib/pyhsm/base.py, Lib/pyhsm/secrets_cmd.py, Tests/test_aead.py, Tests/test_db.py, Tests/test_otp_validate.py, Tests/test_yubikey_validate.py: Move YubiKey_Secret to aead_cmd.py. 2011-03-31 Fredrik Thulin * Lib/pyhsm/__init__.py, Lib/pyhsm/aead_cmd.py, Lib/pyhsm/base.py, Lib/pyhsm/basic_cmd.py, Lib/pyhsm/cmd.py, Lib/pyhsm/db_cmd.py, Lib/pyhsm/debug_cmd.py, Lib/pyhsm/exception.py, Lib/pyhsm/hmac_cmd.py, Lib/pyhsm/secrets_cmd.py, Lib/pyhsm/validate_cmd.py, Lib/pyhsm/yubikey.py: Fix relative imports. 2011-03-31 Fredrik Thulin * Lib/pyhsm/cmd.py: Slightly better debug and comments. 2011-03-31 Fredrik Thulin * Tests/test_aead.py: Two more trivial tests. 2011-03-31 Fredrik Thulin * Lib/pyhsm/validate_cmd.py: Inherit from YHSM_AEAD_Cmd to reduce code duplication. 2011-03-31 Fredrik Thulin * Lib/pyhsm/validate_cmd.py, Tests/test_db.py, Tests/test_yubikey_validate.py: Sort out use/session ctr confusion in YubiKeyEmu. We have a known issue where the names of the 16 bit counter and the 8 bit since-power-up counter in the YubiKey have diverged. 2011-03-31 Fredrik Thulin * Lib/pyhsm/aead_cmd.py, Lib/pyhsm/base.py, Lib/pyhsm/exception.py, Tests/test_aead.py, Tests/test_yubikey_validate.py: Remove confusion about validating only MACs of AEADs. The confusing comment in YubiHSM_if.h means that if you generate an AEAD with empty input, you can validate it using empty input - not that you could validate MACs of AEADs separately from the rest of the AEAD. 2011-03-31 Fredrik Thulin * Lib/pyhsm/basic_cmd.py, Lib/pyhsm/buffer_cmd.py, Lib/pyhsm/hmac_cmd.py, Lib/pyhsm/stick.py, Lib/pyhsm/util.py, Lib/pyhsm/validate_cmd.py, Tests/test_common.py, Tests/test_hmac.py: Increase pylint score. 2011-03-31 Fredrik Thulin * Lib/pyhsm/aes_ecb_cmd.py: Refactor. 2011-03-31 Fredrik Thulin * Lib/pyhsm/base.py, yubikey-ksm/yhsm-generate-keys: Change generate_secret into more generic load_random. 2011-03-31 Fredrik Thulin * Lib/pyhsm/buffer_cmd.py, Tests/test_buffer.py, Tests/test_init.py: Mend YHSM_Cmd_Buffer_Random_Load. 2011-03-31 Fredrik Thulin * Lib/pyhsm/basic_cmd.py, Lib/pyhsm/defines.py, Tests/test_basics.py: Implement YSM_TEMP_KEY_LOAD. 2011-03-31 Fredrik Thulin * Lib/pyhsm/base.py: Make load_secret() handle string input too. 2011-03-31 Fredrik Thulin * Lib/pyhsm/base.py, Lib/pyhsm/stick.py: Implement set_debug(). 2011-03-31 Fredrik Thulin * Lib/pyhsm/base.py, Lib/pyhsm/basic_cmd.py, Lib/pyhsm/defines.py, Tests/test_basics.py: Implement YSM_RANDOM_RESEED. 2011-03-31 Fredrik Thulin * Lib/pyhsm/base.py, Lib/pyhsm/basic_cmd.py, Lib/pyhsm/defines.py, Tests/test_basics.py, examples/yhsm-sysinfo.py: Add command YSM_NONCE_GET. 2011-03-30 Fredrik Thulin * examples/yhsm-password-auth.py: Show AEAD on --set even without --verbose. 2011-03-30 Fredrik Thulin * Lib/pyhsm/basic_cmd.py, Tests/test_basics.py: Fix off-by-one stealing one byte of random. 2011-03-30 Fredrik Thulin * yubikey-ksm/yhsm-import-keys: documentation correction. 2011-03-30 Fredrik Thulin * examples/yhsm-monitor-exit.py: Honor --debug. 2011-03-30 Fredrik Thulin * Lib/pyhsm/yubikey.py, yubikey-val/yhsm-validate-otp: Add CLI to validate YubiKey OTP's. 2011-03-30 Fredrik Thulin * Lib/pyhsm/yubikey.py, Tests/test_yubikey_validate.py: Remove duplicate code in test suite. 2011-03-30 Fredrik Thulin * yubikey-ksm/yhsm-import-keys: Implement --internal-db to store AEADs in YubiHSM internal database. 2011-03-30 Fredrik Thulin * Lib/pyhsm/aead_cmd.py: Check nonce input. 2011-03-30 Fredrik Thulin * Lib/pyhsm/exception.py: correct YHSM_InputTooLong string 2011-03-30 Fredrik Thulin * Lib/pyhsm/aead_cmd.py, Lib/pyhsm/base.py, Lib/pyhsm/db_cmd.py, Lib/pyhsm/defines.py, Lib/pyhsm/util.py, Lib/pyhsm/validate_cmd.py, Tests/test_aead.py, Tests/test_aes_ecb.py, Tests/test_basics.py, Tests/test_db.py, Tests/test_init.py, Tests/test_otp_validate.py, Tests/test_yubikey_validate.py: Implement more commands. 2011-03-29 Fredrik Thulin * Lib/pyhsm/aead_cmd.py, Lib/pyhsm/base.py, Lib/pyhsm/buffer_cmd.py, Lib/pyhsm/defines.py, Lib/pyhsm/secrets_cmd.py, Tests/test_aead.py, Tests/test_hmac.py, Tests/test_init.py, Tests/test_otp_validate.py, Tests/test_yubikey_validate.py, yubikey-ksm/yhsm-generate-keys, yubikey-ksm/yhsm-import-keys, yubikey-ksm/yhsm-yubikey-ksm: More 0.9.2 stuff. 2011-03-29 Fredrik Thulin * Lib/pyhsm/aes_ecb_cmd.py, Lib/pyhsm/basic_cmd.py, Lib/pyhsm/buffer_cmd.py, Lib/pyhsm/cmd.py, Lib/pyhsm/debug_cmd.py, Lib/pyhsm/defines.py, Lib/pyhsm/exception.py, Lib/pyhsm/hmac_cmd.py, Lib/pyhsm/validate_cmd.py, Tests/test_aes_ecb.py, Tests/test_hmac.py, Tests/test_init.py: Don't mangle Jakobs TLA. 2011-03-29 Fredrik Thulin * Lib/pyhsm/__init__.py, Lib/pyhsm/aead_cmd.py, Lib/pyhsm/base.py, Lib/pyhsm/buffer_cmd.py, Lib/pyhsm/db_cmd.py, Lib/pyhsm/defines.py, Lib/pyhsm/secrets_cmd.py, Lib/pyhsm/validate_cmd.py, Lib/pyhsm/yubikey.py, Tests/test_otp_validate.py, Tests/test_yubikey_validate.py, yubikey-ksm/yhsm-yubikey-ksm: Assorted firmware 0.9.2 work. 2011-03-28 Fredrik Thulin * Lib/pyhsm/base.py, Lib/pyhsm/buffer_cmd.py, Lib/pyhsm/defines.py, Lib/pyhsm/secrets_cmd.py, Tests/test_otp_validate.py, yubikey-ksm/yhsm-generate-keys, yubikey-ksm/yhsm-import-keys: Update to match firmware 0.9.2. 2011-03-28 Fredrik Thulin * Tests/test_aes_ecb.py: Fix bugs in tests for bad compare. 2011-03-28 Fredrik Thulin * Lib/pyhsm/hmac_cmd.py: Update to firmware 0.9.2. 2011-03-28 Fredrik Thulin * Lib/pyhsm/basic_cmd.py: Fix YHSM_Cmd_Echo, and add length check. 2011-03-28 Fredrik Thulin * Lib/pyhsm/base.py: Work around small delay on YSM_NULL in firmware 0.9.2. 2011-03-28 Fredrik Thulin * Lib/pyhsm/cmd.py: YHSM_NULL command should NOT have byte count prepended. 2011-03-28 Fredrik Thulin * Lib/pyhsm/basic_cmd.py, Lib/pyhsm/defines.py: rename YHSM_RANDOM_GET to YHSM_RANDOM_GENERATE 2011-03-28 Fredrik Thulin * Lib/pyhsm/aes_ecb_cmd.py, Lib/pyhsm/base.py, Lib/pyhsm/basic_cmd.py, Lib/pyhsm/cmd.py, Lib/pyhsm/defines.py, Lib/pyhsm/hmac_cmd.py, Tests/test_aes_ecb.py, Tests/test_configure.py, Tests/test_hmac.py: Update to hardware version 0.9.2. 2011-03-28 Fredrik Thulin * Lib/pyhsm/stick.py: Add caller debug_info to read/write. 2011-03-28 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Version 0.9.2pre1 2011-03-28 Fredrik Thulin * Tests/test_configure.py: New firmware requires 8 chars key handles in 'keyload'. 2011-03-28 Fredrik Thulin * README: init 2011-03-28 Fredrik Thulin * yhsm-generate-keys => yubikey-ksm/yhsm-generate-keys, yhsm-import-keys => yubikey-ksm/yhsm-import-keys, yhsm-yubikey-ksm => yubikey-ksm/yhsm-yubikey-ksm: Move to subdirectory. 2011-03-28 Fredrik Thulin * Lib/pyhsm/__init__.py, Lib/pyhsm/base.py: more copys of the copyright 2011-03-28 Fredrik Thulin * Lib/pyhsm/__init__.py, setup.py: Version 0.9.0. 2011-03-28 Fredrik Thulin * examples/yhsm-password-auth.py: init 2011-03-28 Fredrik Thulin * Tests/test_aes_ecb.py, Tests/test_basics.py, Tests/test_common.py, Tests/test_configure.py, Tests/test_hmac.py, Tests/test_init.py, Tests/test_otp_validate.py, Tests/test_yubikey_validate.py: Initialization cleanup. 2011-03-28 Fredrik Thulin * Lib/pyhsm/__init__.py, Lib/pyhsm/base.py, Lib/pyhsm/defines.py, Lib/pyhsm/exception.py, Lib/pyhsm/hmac_cmd.py, Tests/test_configure.py, Tests/test_hmac.py, Tests/test_init.py: Implement HMAC SHA1 command. 2011-03-25 Fredrik Thulin * Lib/pyhsm/__init__.py, Lib/pyhsm/aes_ecb_cmd.py, Lib/pyhsm/base.py, Lib/pyhsm/defines.py, Tests/test_aes_ecb.py, Tests/test_configure.py, Tests/test_init.py: Implement AES ECB encrypt/decrypt/compare. 2011-03-25 Fredrik Thulin * Lib/{serveronstick => pyhsm}/__init__.py, Lib/{serveronstick => pyhsm}/base.py, Lib/{serveronstick => pyhsm}/basic_cmd.py, Lib/{serveronstick => pyhsm}/cmd.py, Lib/{serveronstick => pyhsm}/debug_cmd.py, Lib/pyhsm/defines.py, Lib/{serveronstick => pyhsm}/exception.py, Lib/{serveronstick => pyhsm}/secrets_cmd.py, Lib/{serveronstick => pyhsm}/stick.py, Lib/{serveronstick => pyhsm}/util.py, Lib/{serveronstick => pyhsm}/validate_cmd.py, Lib/{serveronstick => pyhsm}/yubikey.py, Lib/serveronstick/defines.py, Tests/test_basics.py, Tests/test_common.py, Tests/test_configure.py, Tests/test_init.py, Tests/test_otp_validate.py, Tests/test_yubikey_validate.py, examples/yhsm-monitor-exit.py, examples/yhsm-sysinfo.py, setup.py, sos-generate-keys => yhsm-generate-keys, sos-import-keys => yhsm-import-keys, sos-yubikey-ksm => yhsm-yubikey-ksm: Massive rename. In-house working name during development was Server-on-Stick, but the product name will be YubiHSM. 2011-03-25 Fredrik Thulin * COPYING: init 2011-03-25 Fredrik Thulin * .gitignore: add tmp 2011-03-25 Fredrik Thulin * examples/yhsm-monitor-exit.py, examples/yhsm-sysinfo.py: Add examples. 2011-03-25 Fredrik Thulin * Tests/test_configure.py, Tests/test_init.py, Tests/test_otp_validate.py, Tests/test_yubikey_validate.py: Add YubiKey OTP validation tests. 2011-03-24 Fredrik Thulin * Tests/test_configure.py: Set up an OTP validation key. 2011-03-22 Fredrik Thulin * Tests/test_common.py, Tests/test_configure.py, Tests/test_init.py, Tests/test_otp_validate.py: More tests. 2011-03-22 Fredrik Thulin * Lib/serveronstick/secrets_cmd.py: Fix off-by-one bug in public_id extraction from generated blob response. 2011-03-22 Fredrik Thulin * Lib/serveronstick/exception.py: Store status for SoS_CommandFailed exception. 2011-03-22 Fredrik Thulin * : commit 9382db56de85abd03092c2bff5554d09b5107c6f Author: Fredrik Thulin Date: Tue Mar 22 17:19:29 2011 +0100 2011-03-22 Fredrik Thulin * Lib/serveronstick/__init__.py, Lib/serveronstick/base.py, Lib/serveronstick/debug_cmd.py, Lib/serveronstick/defines.py: Implement monitor_exit command, for testing. 2011-03-22 Fredrik Thulin * Tests/run.sh, Tests/test_basics.py, Tests/test_common.py, Tests/test_init.py, setup.py: Add first test cases. 2011-03-22 Fredrik Thulin * Lib/serveronstick/base.py, Lib/serveronstick/basic_cmd.py: Bugfix random number generation after refactoring. 2011-03-20 Fredrik Thulin * Lib/serveronstick/__init__.py: HW version 0.9.0 2011-03-20 Fredrik Thulin * Lib/serveronstick/__init__.py: Works with hardware 0.3.0. 2011-03-10 Simon Josefsson * sos-yubikey-ksm: Drop spurious \n. 2011-03-10 Simon Josefsson * sos-yubikey-ksm: Improve log message to keep in sync with PHP yubikey-ksm. 2011-03-10 Fredrik Thulin * sos-yubikey-ksm: Handle exceptions from SoS, respond 'ERR'. 2011-03-10 Simon Josefsson * sos-yubikey-ksm: Make KSM output be consistent with current KSMs (use vs session counter). 2011-03-09 Fredrik Thulin * : commit 4bf1e7093962802d0034ec00ffee43376dae01f9 Author: Fredrik Thulin Date: Wed Mar 9 19:59:08 2011 +0100 2011-03-09 Simon Josefsson * sos-yubikey-ksm: Make syslog output match yubikey-ksm PHP server. 2011-03-09 Simon Josefsson * sos-yubikey-ksm: Low timestamp is also 16-bit. 2011-03-09 Simon Josefsson * sos-yubikey-ksm: Comply with KSM protocol. 2011-03-09 Fredrik Thulin * Lib/serveronstick/util.py, sos-generate-keys, sos-import-keys, sos-yubikey-ksm: Change blob filename hashing to get at most 256 entrys per directory. 2011-03-09 Fredrik Thulin * sos-generate-keys, sos-import-keys, sos-yubikey-ksm: Change default device to /dev/ttyACM0. 2011-03-09 Fredrik Thulin * Lib/serveronstick/defines.py: comment 2011-03-09 Fredrik Thulin * sos-yubikey-ksm: Don't use hard-coded length for serve_url now that it is an option. 2011-03-09 Fredrik Thulin * sos-yubikey-ksm: Check that from_key is 32-48 bytes of modhex. 2011-03-09 Fredrik Thulin * sos-yubikey-ksm: Log to syslog, and reduce number of global variables. 2011-02-25 Fredrik Thulin * sos-generate-keys, sos-import-keys, sos-yubikey-ksm: init, seemingly working tools 2011-02-25 Fredrik Thulin * Lib/serveronstick/util.py: Add key_handle_to_int. 2011-02-25 Fredrik Thulin * Lib/serveronstick/secrets_cmd.py: Add store/load to file. 2011-02-25 Fredrik Thulin * Lib/serveronstick/validate_cmd.py: Fix command name in exception. 2011-02-25 Fredrik Thulin * Lib/serveronstick/yubikey.py: Add split_id_otp function. 2011-02-24 Fredrik Thulin * Lib/serveronstick/defines.py, Lib/serveronstick/exception.py: more readable errors 2011-02-24 Fredrik Thulin * Lib/serveronstick/__init__.py: Import SoS from base. 2011-02-24 Fredrik Thulin * Lib/serveronstick/exception.py: Print bad command status in hex. 2011-02-24 Fredrik Thulin * Lib/serveronstick/stick.py: Handle open errors better. 2011-02-24 Fredrik Thulin * Lib/serveronstick/yubikey.py: add modhex_encode 2011-02-24 Fredrik Thulin * sos-generate-keys: writes to files 2011-02-24 Fredrik Thulin * sos-generate-keys: init, actually writing blobs to disk remain 2011-02-24 Fredrik Thulin * Lib/serveronstick/base.py, Lib/serveronstick/basic_cmd.py, Lib/serveronstick/secrets_cmd.py, Lib/serveronstick/util.py, Lib/serveronstick/yubikey.py: More pylint - variable renames. 2011-02-24 Fredrik Thulin * Lib/serveronstick/validate_cmd.py: Implement class for validation response. 2011-02-24 Fredrik Thulin * Lib/serveronstick/base.py, Lib/serveronstick/basic_cmd.py, Lib/serveronstick/cmd.py, Lib/serveronstick/exception.py, Lib/serveronstick/secrets_cmd.py, Lib/serveronstick/stick.py, Lib/serveronstick/validate_cmd.py, Lib/serveronstick/yubikey.py: pylint run 2011-02-23 Fredrik Thulin * Lib/serveronstick/__init__.py, Lib/serveronstick/exception.py, Lib/serveronstick/secrets_cmd.py: Improve generate_blob. 2011-02-22 Fredrik Thulin * Lib/serveronstick/secrets_cmd.py, Lib/serveronstick/validate_cmd.py: bugfixes 2011-02-22 Fredrik Thulin * Lib/serveronstick/base.py, Lib/serveronstick/basic_cmd.py, Lib/serveronstick/cmd.py, Lib/serveronstick/defines.py, Lib/serveronstick/exception.py, Lib/serveronstick/secrets_cmd.py, Lib/serveronstick/stick.py, Lib/serveronstick/validate_cmd.py, Lib/serveronstick/yubikey.py: Major refactoring. Can actually validate OTPs now. 2011-02-22 Fredrik Thulin * .gitignore, Lib/serveronstick/base.py, Lib/serveronstick/basic_cmd.py, Lib/serveronstick/cmd.py, Lib/serveronstick/defines.py, Lib/serveronstick/secrets_cmd.py: Implement load/generate secrets etc. 2011-02-22 Fredrik Thulin * Lib/serveronstick/basic_cmd.py, Lib/serveronstick/cmd.py: Refactoring 2011-02-21 Fredrik Thulin * init, first nights work pyhsm-1.2.0/PKG-INFO0000664000175000017500000000126213006370267013626 0ustar daindain00000000000000Metadata-Version: 1.1 Name: pyhsm Version: 1.2.0 Summary: Python code for talking to a YubiHSM Home-page: https://github.com/Yubico/python-pyhsm Author: Dain Nilsson Author-email: dain@yubico.com License: BSD 2 clause Description: UNKNOWN Platform: UNKNOWN Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application pyhsm-1.2.0/man/0000775000175000017500000000000013006370267013303 5ustar daindain00000000000000pyhsm-1.2.0/man/yhsm-yubikey-ksm.10000664000175000017500000000410713005606114016606 0ustar daindain00000000000000.\" Copyright (c) 2011-2014 Yubico AB .\" See the file COPYING for license statement. .\" .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH yhsm-yubikey-ksm "1" "December 2011" "python-pyhsm" .SH NAME yhsm-yubikey-ksm \(hy Decrypt YubiKey OTPs using an attached YubiHSM .SH SYNOPSIS .B yhsm-yubikey-ksm \fI--key-handles\fR ... [\fIoptions\fR] .SH DESCRIPTION This is a small network server with a REST-like API that decodes YubiKey OTPs. It can be used as a decryption backend (Key Storage Module) to a validation service such as the YubiCloud. The AES keys of the YubiKeys must be present as AEAD files decryptable to the attached YubiHSM. Such AEADs can for example be created using \fIyhsm-import-keys\fR\|(1). Note that this daemon is single threaded \(hy it will only handle a single request at once. A request timeout is therefor most important. .SH OPTIONS .PP .TP \fB\-D\fR, \fB\-\-device\fR device file name (default: /dev/ttyACM0) .TP \fB\-v\fR, \fB\-\-verbose\fR enable verbose operation .TP \fB\-\-debug\fR enable debug printout, including all data sent to/from YubiHSM .TP \fB\-U\fR, \fB\-\-serve\-url\fR base base of URL for decrypt web service (default: /yhsm/validate?) .TP \fB\-S\fR, \fB\-\-stats\-url\fR url URL where some basic statistics about operations since start can be collected .TP \fB\-\-port\fR num port to listen on (default: 8002) .TP \fB\-\-addr\fR addr address to bind to (default: 127.0.0.1) .TP \fB\-\-key-handles\fR kh, \fB\-\-key-handle\fR kh key handles to use for decoding OTPs. Examples : "1", "0xabcd". .TP \fB\-\-aead-dir\fR dir, \fB\-B\fR dir base directory for AEADs (default: /var/cache/yubikey-ksm/aeads) .TP \fB\-\-reqtimeout\fR num number of seconds before a request times out (default: 5) .TP \fB\-\-pid-file\fR fn write process id of server to this file .SH "BUGS" Report python-pyhsm/yhsm-yubikey-ksm bugs in .URL "https://github.com/Yubico/python-pyhsm/issues/" "the issue tracker" .SH "SEE ALSO" The .URL "https://developers.yubico.com/python-pyhsm/" "home page" .PP YubiHSMs can be obtained from .URL "http://www.yubico.com/" "Yubico" "." pyhsm-1.2.0/man/yhsm-keystore-unlock.10000664000175000017500000000461613005606114017500 0ustar daindain00000000000000.\" Copyright (c) 2011-2016 Yubico AB .\" See the file COPYING for license statement. .\" .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH yhsm-keystore-unlock "1" "December 2011" "python-pyhsm" .SH NAME yhsm-keystore-unlock \(hy Unlock the keystore in a YubiHSM .SH SYNOPSIS .B yhsm-keystore-unlock [\fIoptions\fR] .SH DESCRIPTION In versions of the YubiHSM before 1.0, the YubiHSM could be protected using a 'HSM password'. The YubiHSM would unlock it's cryptographic functions if the correct password was given, but it was a simple comparison test. In YubiHSM 1.0, the password was changed into an actual key that was used to decrypt the contents of the YubiHSM internal key store, which was then AES-256 encrypted using the new 'Master key' when stored in the device. In YubiHSM 1.0, the option to also require an YubiKey OTP to unlock the keystore was also added. One or more 'Admin YubiKeys' can be configured in the YubiHSM, and an OTP from one of these must also be provided before the YubiHSM will enable it's cryptographic functions. The OTP is simply validated against the non-encrypted internal database (not key store) in the YubiHSM though, but together with a 'Master key' not stored on the server with the YubiHSM, it provides enhanced security by being a second factor that an attacker can't just intercept even if the server is compromised. .SH OPTIONS .PP .TP \fB\-D\fR, \fB\-\-device\fR device file name (default: /dev/ttyACM0). .TP \fB\-v\fR, \fB\-\-verbose\fR enable verbose operation. .TP \fB\-\-debug\fR enable debug printout, including all data sent to/from YubiHSM. .TP \fB\-\-no-otp\fR skip the prompt for an OTP. For use by scripts where no OTP is required and the Master Key is stored on the server with the YubiHSM. .TP \fB\-\-stdin\fR read password and/or OTP from stdin rather than prompting for them. Python prompts does not accept piped input, so this option have to be used to unlock the YubiHSM from a script for example. .SH "EXIT STATUS" .IX Header "EXIT STATUS" .IP "\fB0\fR" 4 .IX Item "0" YubiHSM keystore successfully unlocked. .IP "\fB1\fR" 4 .IX Item "1" Failed to unlock keystore. .SH BUGS Report python-pyhsm/yhsm-keystore-unlock bugs in .URL "https://github.com/Yubico/python-pyhsm/issues/" "the issue tracker" .SH "SEE ALSO" The .URL "https://developers.yubico.com/python-pyhsm/" "home page" .PP YubiHSMs can be obtained from .URL "http://www.yubico.com/" "Yubico" "." pyhsm-1.2.0/man/yhsm-db-import.10000664000175000017500000000150413005606114016230 0ustar daindain00000000000000.\" Copyright (c) 2014 Yubico AB .\" See the file COPYING for license statement. .\" .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH yhsm-db-import "1" "September 2014" "python-pyhsm" .SH NAME yhsm-db-import \(hy Import AEADs from filesystem to database .SH SYNOPSIS .B yhsm-db-import [\fIoptions\fR] .SH DESCRIPTION Use this program to import AEADs from a filesystem structure into a sql database. .SH OPTIONS .PP .TP \fB\-\-path\fR filesystem path of where to find AEADs .TP \fB\-\-dburl\fR connection URL for the database .SH BUGS Report python-pyhsm/yhsm-db-import bugs in .URL "https://github.com/Yubico/python-pyhsm/issues/" "the issue tracker" .SH "SEE ALSO" The .URL "https://developers.yubico.com/python-pyhsm/" "home page" .PP YubiHSMs can be obtained from .URL "http://www.yubico.com/" "Yubico" "." pyhsm-1.2.0/man/yhsm-decrypt-aead.10000664000175000017500000000305213005606114016675 0ustar daindain00000000000000.\" Copyright (c) 2012-2014 Yubico AB .\" See the file COPYING for license statement. .\" .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH yhsm-decrypt-aead "1" "June 2012" "python-pyhsm" .SH NAME yhsm-decrypt-aead \(hy Decrypt AEADs (with secrets for YubiKeys) .SH SYNOPSIS .B yhsm\-decrypt\-aead [\fIoptions\fR] \-\-aes\-key KEY file-or-dir [...] .SH DESCRIPTION Decrypt AEADs generated using a YubiHSM. \fBNOTE\fR that this requires knowledge of the AES key used in the YubiHSM. After a number of YubiKey secrets have been generated using .BR yhsm-generate-keys (1) , this tool can decrypt them and produce a CSV file usable to personalize corresponding YubiKeys. .SH OPTIONS .PP .TP \fB\-v\fR, \fB\-\-verbose\fR Enable verbose operation. .TP \fB\-\-debug\fR Enable debug printout. .TP \fB\-\-format str\fR Select output format (raw or yubikey-csv). .TP \fB\-\-print\-filename\fR Prefix any output with the input filename. .TP \fB\-\-key\-handle kh\fR Key handle used when generated AEADs, if not stored in the AEAD file (AEAD generated with python-pyhsm 1.0.3 or lower). .TP \fB\-\-aes\-key hexstr\fR AES key used to generate the AEADs. .SH "EXIT STATUS" .IX Header "EXIT STATUS" .IP "\fB0\fR" 4 .IX Item "0" OK .IP "\fB1\fR" 4 .IX Item "1" Fail .SH BUGS Report python-pyhsm/yhsm-decrypt-aead bugs in .URL "https://github.com/Yubico/python-pyhsm/issues/" "the issue tracker" .SH "SEE ALSO" The .URL "https://developers.yubico.com/python-pyhsm/" "home page" .PP YubiHSMs and YubiKeys can be obtained from .URL "http://www.yubico.com/" "Yubico" "." pyhsm-1.2.0/man/yhsm-linux-add-entropy.10000664000175000017500000000375213005606114017725 0ustar daindain00000000000000.\" Copyright (c) 2011-2014 Yubico AB .\" See the file COPYING for license statement. .\" .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH yhsm-linux-add-entropy "1" "December 2011" "python-pyhsm" .SH NAME yhsm-linux-add-entropy \(hy Seed the Linux entropy pool with data from YubiHSM TRNG .SH SYNOPSIS .B yhsm-linux-add-entropy [\fIoptions\fR] .SH DESCRIPTION The YubiHSM uses "Avalanche Noise" TRNG together with USB SOF jitter sampling to feed a DRBG_CTR algorithm (NIST publication SP800-90). The result has been verified as being random data of good quality by at least one third party cryptographer. .URL "http://sartryck.idg.se/Art/Yubihsm_1_TW072011.html" Use this program to add random data from the YubiHSM to the entropy pool of your Linux operating system. This is useful whenever lots of random data is needed, such as when generating chryptographic keys (GPG-keys), on a server terminating SSL sessions etc. You may run this script from cron, or in a while-loop. Make sure it does not run at the same time as something else accessing the YubiHSM though, or the two tasks may interrupt each other \(hy probably making both fail. .SH OPTIONS .PP .TP \fB\-D\fR, \fB\-\-device\fR device file name (default: /dev/ttyACM0). .TP \fB\-v\fR, \fB\-\-verbose\fR enable verbose operation. .TP \fB\-c\fR, \fB\-\-count\fR number of iterations to run (default: 100). .TP \fB\-r\fR, \fB\-\-ratio\fR bits per byte read to use. 8 is probably fine, but as a conservative default 2 is used. .TP \fB\-\-debug\fR enable debug printout, including all data sent to/from YubiHSM. .SH "EXIT STATUS" .IX Header "EXIT STATUS" .IP "\fB0\fR" 4 .IX Item "0" Entropy added successfully .IP "\fB1\fR" 4 .IX Item "1" Failure .SH BUGS Report python-pyhsm/yhsm-linux-add-entropy bugs in .URL "https://github.com/Yubico/python-pyhsm/issues/" "the issue tracker" .SH "SEE ALSO" The .URL "https://developers.yubico.com/python-pyhsm/" "home page" .PP YubiHSMs can be obtained from .URL "http://www.yubico.com/" "Yubico" "." pyhsm-1.2.0/man/yhsm-validate-otp.10000664000175000017500000000272013005606114016725 0ustar daindain00000000000000.\" Copyright (c) 2011-2014 Yubico AB .\" See the file COPYING for license statement. .\" .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH yhsm-validate-otp "1" "December 2011" "python-pyhsm" .SH NAME yhsm-validate-otp \(hy Validate an OTP using a YubiHSM. .SH SYNOPSIS .B yhsm-validate-otp \fImode\fR [\fIoptions\fR] .SH DESCRIPTION This tool allows simple validation of YubiKey OTP from shell scripts. .SH OPTIONS .PP .TP \fB\-D\fR, \fB\-\-device\fR device file name (default: /dev/ttyACM0). .TP \fB\-v\fR, \fB\-\-verbose\fR enable verbose operation. .TP \fB\-\-debug\fR enable debug printout, including all data sent to/from YubiHSM. .SH MODES \fB\-\-otp\fR Validate YubiKey OTP against entry in the YubiHSM internal database. .\"\fB\-\-oath\fR .\"\fBNot implemented yet.\fR .\"Validate an OATH code using HMAC-SHA-1 in the YubiHSM. The OATH counter .\"database must be initialized using \fIyhsm-init-oath-token\fR\|(1) first. .SH "EXIT STATUS" .IX Header "EXIT STATUS" .IP "\fB0\fR" 4 .IX Item "0" YubiHSM keystore successfully unlocked .IP "\fB1\fR" 4 .IX Item "1" Failed to unlock keystore .IP "\fB255\fR" 4 .IX Item "255" Client ID not found in internal database .SH BUGS Report python-pyhsm/yhsm-validate-otp bugs in .URL "https://github.com/Yubico/python-pyhsm/issues/" "the issue tracker" .SH "SEE ALSO" The .URL "https://developers.yubico.com/python-pyhsm/" "home page" .PP YubiHSMs can be obtained from .URL "http://www.yubico.com/" "Yubico" "." pyhsm-1.2.0/man/yhsm-db-export.10000664000175000017500000000151713005606114016243 0ustar daindain00000000000000.\" Copyright (c) 2014 Yubico AB .\" See the file COPYING for license statement. .\" .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH yhsm-db-export "1" "September 2014" "python-pyhsm" .SH NAME yhsm-db-export \(hy Export AEADs from a database to a filesystem structure .SH SYNOPSIS .B yhsm-db-export [\fIoptions\fR] .SH DESCRIPTION Use this program to export AEADs from a sql database to a filesystem structure. .SH OPTIONS .PP .TP \fB\-\-path\fR filesystem path of where to put AEADs .TP \fB\-\-dburl\fR connection URL for the database .SH BUGS Report python-pyhsm/yhsm-db-export bugs in .URL "https://github.com/Yubico/python-pyhsm/issues/" "the issue tracker" .SH "SEE ALSO" The .URL "https://developers.yubico.com/python-pyhsm/" "home page" .PP YubiHSMs can be obtained from .URL "http://www.yubico.com/" "Yubico" "." pyhsm-1.2.0/man/yhsm-init-oath-token.10000664000175000017500000000360713005606114017353 0ustar daindain00000000000000.\" Copyright (c) 2011-2014 Yubico AB .\" See the file COPYING for license statement. .\" .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH yhsm-init-oath-token "1" "December 2011" "python-pyhsm" .SH NAME yhsm-init-oath-token \(hy Tool to add an OATH token to the \fIyhsm-validation-server\fR\|(1) database. .SH SYNOPSIS .B yhsm-init-oath-token \fI--key-handle kh\fR \fI--uid name\fR [\fIoptions\fR] .SH DESCRIPTION Use this tool to add OATH token entries to the \fIyhsm-validation-server\fR\|(1) database. .SH OPTIONS .PP .TP \fB\-D\fR, \fB\-\-device\fR device file name (default: /dev/ttyACM0) .TP \fB\-v\fR, \fB\-\-verbose\fR enable verbose operation .TP \fB\-\-debug\fR enable debug printout, including all data sent to/from YubiHSM .TP \fB\-\-force\fR overwrite any present entry .TP \fB\-\-key-handle\fR kh key handle to create AEAD. Examples : "1", "0xabcd". .TP \fB\-\-uid\fR name user id (lookup key in token database) .TP \fB\-\-oath-c\fR num initial OATH counter value (integer) .TP \fB\-\-test-oath-window\fR num number of codes to search with \-\-test-code .TP \fB\-\-test-code\fR digits optional OTP from token for verification .TP \fB\-\-oath-k\fR str secret HMAC-SHA-1 key of the token, hex encoded .TP \fB\-\-db-file\fR fn db file for storing AEADs for later use by the \fIyhsm-validation-server\fR\|(1) (default: /var/yubico/yhsm-validation-server.db) .SH "EXIT STATUS" .IX Header "EXIT STATUS" .IP "\fB0\fR" 4 .IX Item "0" YubiHSM keystore successfully unlocked .IP "\fB1\fR" 4 .IX Item "1" Failed to unlock keystore .IP "\fB255\fR" 4 .IX Item "255" Client ID not found in internal database .SH BUGS Report python-pyhsm/yhsm-init-oath-token bugs in .URL "https://github.com/Yubico/python-pyhsm/issues/" "the issue tracker" .SH "SEE ALSO" The .URL "https://developers.yubico.com/python-pyhsm/" "home page" .PP YubiHSMs can be obtained from .URL "http://www.yubico.com/" "Yubico" "." pyhsm-1.2.0/man/yhsm-validation-server.10000664000175000017500000000736613005606114020005 0ustar daindain00000000000000.\" Copyright (c) 2011-2014 Yubico AB .\" See the file COPYING for license statement. .\" .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH yhsm-validation-server "1" "December 2011" "python-pyhsm" .SH NAME yhsm-validation-server \(hy Credential validation server utilizing YubiHSM .SH SYNOPSIS .B yhsm-validation-server [\fImode\fR] .SH DESCRIPTION This is a validation server using the YubiHSM for cryptographic operations. It is primarily built to validate YubiKey OTPs (\fInot\fR stored in the YubiHSM internal database), but it can also validate OATH token codes and legacy passwords. .SH OPTIONS .PP .TP \fB\-D\fR, \fB\-\-device\fR device file name (default: /dev/ttyACM0) .TP \fB\-v\fR, \fB\-\-verbose\fR enable verbose operation .TP \fB\-\-debug\fR enable debug printout, including all data sent to/from YubiHSM .TP \fB\-\-U\fR, \fB\-\-serve-url\fR base base of URL for validation web service (default: /yhsm/validate?) .TP \fB\-\-port\fR num port to listen on (default: 8003) .TP \fB\-\-addr\fR addr address to bind to (default: 127.0.0.1) .TP \fB\-\-hmac-kh\fR kh key handle to use for HMAC\(hySHA\(hy1. Examples : "1", "0xabcd". .TP \fB\-\-hotp-window\fR num number of OATH counter values to try (default: 5) .TP \fB\-\-totp-interval\fR num interval for TOTP time-step to use in seconds (default: 30) .TP \fB\-\-totp-tolerance\fR num number of time-steps on either side of now to allow TOTP codes for (default: 1) .TP \fB\-\-db-file\fR fn db file holding AEADs (see \fIyhsm-init-oath-token\fR\|(1)) (default: /var/yubico/yhsm-validation-server.db) .TP \fB\-\-clients-file\fR fn text file with mode OTP validation client shared secrets (see \fIyhsm-init-oath-token\fR\|(1)) (default: /var/yubico/yhsm-validation-server.db) .TP \fB\-\-pid-file\fR fn write process id of server to this file .SH "MODES" .TP \fB\-\-otp\fR Validate YubiKey OTP against entry in the YubiHSM internal database. Response should be compatible with those of .URL "http://code.google.com/p/yubikey-val-server-php/" "yubikey-val-server-php" "." .TP \fB\-\-short-otp\fR Validate YubiKey OTP against entry in the YubiHSM internal database. Returns a single line with the decrypted information from the OTP, compatible with .URL "http://code.google.com/p/yubikey-ksm/" "yubikey-ksm" "." .TP \fB\-\-hotp\fR Validate codes using the OATH HOTP algorithm, performing the HMAC\(hySHA\(hy1 inside the YubiHSM. .TP \fB\-\-totp\fR Validate codes using the OATH TOTP algorithm, performing the HMAC\(hySHA\(hy1 inside the YubiHSM. .TP \fB\-\-pwhash\fR Validate that a string (a PBKDF2 hash of a password for example) matches the one in an AEAD. Can be used to protect legacy passwords within an AEAD only readable to a YubiHSM, but still recoverable if you know the AEAD key (since you put it in the YubiHSM). .\"\fB\-\-oath\fR .\"\fBNot implemented yet.\fR .\"Validate an OATH code using HMAC\(hySHA\(hy1 in the YubiHSM. The OATH counter .\"database must be initialized using \fIyhsm-init-oath-token\fR\|(1) first. .SH "CLIENTS FILE" This file holds HMAC\(hySHA\(hy1 secrets shared between the validation client and server. An example file, with a single entry for id 4711 would be : .in +4n .nf # hash-style comments and blank lines are ignored 4711,grF5BERXEXPPpww1/TBvFg== # end .fi .in .SH "EXIT STATUS" .IX Header "EXIT STATUS" .IP "\fB0\fR" 4 .IX Item "0" YubiHSM keystore successfully unlocked .IP "\fB1\fR" 4 .IX Item "1" Failed to unlock keystore .IP "\fB255\fR" 4 .IX Item "255" Client ID not found in internal database .SH "BUGS" Report python-pyhsm/yhsm-validation-server bugs in .URL "https://github.com/Yubico/python-pyhsm/issues/" "the issue tracker" .SH "SEE ALSO" The .URL "https://developers.yubico.com/python-pyhsm/" "home page" .PP YubiHSMs can be obtained from .URL "http://www.yubico.com/" "Yubico" "." pyhsm-1.2.0/man/yhsm-generate-keys.10000664000175000017500000000415413005606114017102 0ustar daindain00000000000000.\" Copyright (c) 2012-2014 Yubico AB .\" See the file COPYING for license statement. .\" .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH yhsm-generate-keys "1" "June 2012" "python-pyhsm" .SH NAME yhsm-generate-keys \(hy Generate AEADs with secrets for YubiKeys using a YubiHSM .SH SYNOPSIS .B yhsm\-generate\-keys \-\-key\-handles KEY_HANDLES \-\-start\-public\-id START_ID [\fIoptions\fR] .SH DESCRIPTION With this tool, a YubiHSM can generate random secrets (using it's internal true random number generator), and these secrets protected in AEAD files can be stored on the host computer. The AEADs will be ready to be used by for example .BR yhsm-yubikey-ksm (1) ), as a part of a YubiKey OTP validation service. To program YubiKeys with the generated secrets, it is possible to decrypt the AEADs (knowledge of the AES key used inside the YubiHSM is required) using .BR yhsm-decrypt-aead (1) . .SH OPTIONS .PP .TP \fB\-D\fR, \fB\-\-device\fR Device file name (default: /dev/ttyACM0). .TP \fB\-v\fR, \fB\-\-verbose\fR Enable verbose operation. .TP \fB\-\-debug\fR Enable debug printout, including all data sent to/from YubiHSM. .TP \fB\-O dir\fR Base output directory (default: /var/cache/yubikey-ksm/aeads). .TP \fB\-c integer\fR Number of AEADs to generate. .TP \fB\-\-public\-id\-chars integer\fR Number of chars in generated public ids (default: 12). Changing this might not work well. .TP \fB\-\-key\-handles kh [kh ...]\fR Key handles to encrypt the generated secrets with. Examples : "1", "0xabcd". .TP \fB\-\-start\-public\-id id\fR Public id of the first generated secret, in modhex. .TP \fB\-\-random\-nonce\fR Use random nonce generated from YubiHSM. .SH "EXIT STATUS" .IX Header "EXIT STATUS" .IP "\fB0\fR" 4 .IX Item "0" Secrets generated successfully. .IP "\fB1\fR" 4 .IX Item "1" Failed to generate secrets. .SH BUGS Report python-pyhsm/yhsm-generate-keys bugs in .URL "https://github.com/Yubico/python-pyhsm/issues/" "the issue tracker" .SH "SEE ALSO" The .URL "https://developers.yubico.com/python-pyhsm/" "home page" .PP YubiHSMs and YubiKeys can be obtained from .URL "http://www.yubico.com/" "Yubico" "." pyhsm-1.2.0/man/yhsm-import-keys.10000664000175000017500000000461113005606114016620 0ustar daindain00000000000000.\" Copyright (c) 2011-2014 Yubico AB .\" See the file COPYING for license statement. .\" .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH yhsm-import-keys "1" "December 2011" "python-pyhsm" .SH NAME yhsm-import-keys \(hy import YubiKey secrets to YubiHSM .SH SYNOPSIS .B yhsm-import-keys \fI--key-handles\fR ... [\fIoptions\fR] .SH DESCRIPTION Read YubiKey token data from standard input, and store it in files or in the YubiHSM internal database. The default mode is to turn each YubiKey secret into an AEAD (Authenticated Encryption with Associated Data) block that is stored in a file on the host computer (one file per YubiKey). This enables validation of virtually unlimited numbers of YubiKey's OTPs. If \-\-internal-db is used, the YubiKey secret will be stored inside the YubiHSM, and complete validation (including counter management) will be done inside the YubiHSM. The internal database is currently limited to 1024 entries. .SH OPTIONS .PP .TP \fB\-D\fR, \fB\-\-device\fR device file name (default: /dev/ttyACM0) .TP \fB\-v\fR, \fB\-\-verbose\fR enable verbose operation .TP \fB\-\-debug\fR enable debug printout, including all data sent to/from YubiHSM .TP \fB\-\-public_id_chars\fR num number of chars to pad public id to (default: 12) .TP \fB\-\-key-handles\fR kh key handles to use for decoding OTPs. Examples : "1", "0xabcd" .TP \fB\-\-output-dir\fR dir, \fB\-\-aead-dir\fR dir, \fB\-O\fR dir base directory for AEADs (default: /var/cache/yubikey-ksm/aeads) .TP \fB\-\-internal-db\fR add entries to YubiHSM internal database, rather than creating AEAD files .TP \fB\-\-random-nonce\fR use random nonce generated from YubiHSM .SH "INPUT FORMAT" The format of the input matches the export format of various Yubico tools, notably the PHP based .URL "http://code.google.com/p/yubikey-ksm/" "soft KSM" "." An example file, with a single entry for id 4711 would be : .in +4n .nf # ykksm 1 123456,ftftftcccc,534543524554,fcacd309a20ce1809c2db257f0e8d6ea,000000000000,,, .fi .in (seqno, public id, private uid, AES key, dunno,,,) The #ykksm 1 is a file marker, and has to be on the very first line of input. .SH "BUGS" Report python-pyhsm/yhsm-import-keys bugs in .URL "https://github.com/Yubico/python-pyhsm/issues/" "the issue tracker" .SH "SEE ALSO" The .URL "https://developers.yubico.com/python-pyhsm/" "home page" .PP YubiHSMs can be obtained from .URL "http://www.yubico.com/" "Yubico" "." pyhsm-1.2.0/man/yhsm-daemon.10000664000175000017500000000244613005606114015604 0ustar daindain00000000000000.\" Copyright (c) 2013-2014 Yubico AB .\" See the file COPYING for license statement. .\" .de URL \\$2 \(laURL: \\$1 \(ra\\$3 .. .if \n[.g] .mso www.tmac .TH yhsm-daemon "1" "May 2013" "python-pyhsm" .SH NAME yhsm-daemon \(hy Allow multiple users of a YubiHSM. .SH SYNOPSIS .B yhsm\-daemon [\fIoptions\fR] [device] [port] .SH DESCRIPTION This is a daemon to allow multiple users of a YubiHSM without requiring permission to use the device. The daemon listens on a TCP port on localhost and allows multiple connections to share a YubiHSM. Access the YubiHSM via the daemon by specifying a device string following the yhsm://: syntax: hsm = YHSM('yhsm://localhost:5348') Note that the daemon and clients need to share the same version number to be compatible. .SH OPTIONS .PP .TP \fB\-h\fR, \fB\-\-help\fR Shows this help message and exit .TP \fB\-D\fR, \fB\-\-device\fR YubiHSM device name .TP \fB\-I\fR, \fB\-\-interface\fR Network interface to bind to .TP \fB\-P\fR, \fB\-\-port\fR TCP port to bind to .SH BUGS Report python-pyhsm/yhsm-daemon bugs in .URL "https://github.com/Yubico/python-pyhsm/issues/" "the issue tracker" .SH "SEE ALSO" The .URL "https://developers.yubico.com/python-pyhsm/" "home page" .PP YubiHSMs and YubiKeys can be obtained from .URL "http://www.yubico.com/" "Yubico" "." pyhsm-1.2.0/README0000664000175000017500000000712513005606114013405 0ustar daindain00000000000000== PyHSM - Python YubiHSM Project === Introduction PyHSM is a Python package to talk to a YubiHSM. The YubiHSM is Yubico's take on the Hardware Security Module (HSM), designed for protecting secrets on authentication servers, including cryptographic keys and passwords, at unmatched simplicity and low cost. === License The project is licensed under the BSD license, see the file COPYING for exact wording. === Description PyHSM aims to be a reference implementation implementing all the functions available in the YubiHSM. PyHSM also includes the regression test suite for the YubiHSM. Instructions on running this test suite can be found in test/README.adoc. In general, see the files in `examples/` to get an idea of how to use this code. In addition to the YubiHSM communication library, PyHSM also contains some applications utilizing the YubiHSM: * yhsm-val: a simple validation server supporting validation of YubiKey OTPs, OATH codes and password hashes. * yubikey-ksm: ykval YubiKey OTP decryption backend using the YubiHSM. Some smaller scripts are also available: * yhsm-linux-add-entropy: Feed Linux kernel with random entropy from the TRNG on the YubiHSM. * yhsm-keystore-unlock: Unlock the key storage in the YubiHSM with your HSM password. Use with incorrect password to lock it again. * yhsm-daemon: Talk to the YubiHSM directly over TCP. And some more in `examples/`: * yhsm-sysinfo.py: Print basic system information about the connected YubiHSM. * yhsm-monitor-exit.py: Get a YubiHSM *in debug mode* to enter configuration mode again, without having to press the little button while inserting it into the USB port. * yhsm-password-auth.py: Example of how to turn passwords (or hashes of passwords if you like PBKDF2) into AEADs that can be used to verify the password later on. === Installation PyHSM is known to work with Python 2.6 and 2.7, and is primarily tested using Debian/Ubuntu, but is of course meant to work on as many platforms as possible. NOTE: If you want to use any of the daemons (yhsm-validation-server, yhsm-yubikey-ksm) you will want to use Python 2.7 or later. `SocketServer.py` lacks critical timeout handling in Python 2.6. The http://pyserial.sourceforge.net[pyserial] package is needed. Debian: apt-get install python-serial You will also need http://www.pycrypto.org[pycrypto]. Debian: apt-get install python-crypto Please note that the pycrypto version has to be 2.1 or higher -- it is known that RHEL6 has a lower version. For database support http://www.sqlalchemy.org[SQLAlchemy] is needed. Debian: apt-get install python-sqlalchemy PyHSM easily installed via pip, which will also take care of the dependencies automatically, though some of these require other build dependencies to correctly install (note the added optional dependencies needed for yubikey-ksm, yhsm-val and tools): $ pip install pyhsm[db,daemon] or, on Debian based systems (dependencies will also be handled automatically): $ sudo apt-get install python-pyhsm additional Debian packages available are: yhsm-tools yhsm-yubikey-ksm yhsm-validation-server yhsm-daemon === Working with the source code repository To work with the source code repository, if you wish to build your own release or contribute pull requests, follow these steps to set up your environment. If you just wish to install the application use the source release packages. This project is developed on a Debian based system, other OSes may not be supported for development. ==== Check out the code Run these commands to check out the source code: git clone https://github.com/Yubico/python-pyhsm.git cd python-pyhsm pyhsm-1.2.0/MANIFEST.in0000664000175000017500000000021413006340263014254 0ustar daindain00000000000000include COPYING include ChangeLog include NEWS include doc/* include man/* include examples/*.py include test/*.py include test/README.adoc pyhsm-1.2.0/setup.cfg0000664000175000017500000000007313006370267014351 0ustar daindain00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 pyhsm-1.2.0/COPYING0000664000175000017500000000245713002140277013563 0ustar daindain00000000000000Copyright (c) 2011-2014 Yubico AB All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.