Reworked password handling for import/export 48/77948/6
authorLukasz Pawelczyk <l.pawelczyk@samsung.com>
Thu, 30 Jun 2016 14:32:22 +0000 (16:32 +0200)
committerLukasz Pawelczyk <l.pawelczyk@samsung.com>
Tue, 5 Jul 2016 08:54:37 +0000 (10:54 +0200)
- Always use callback.
- Return INVALID_PASSWORD for combinations that do support password,
  it was not required for import but was given in params.
- Return INVALID_PARAM for combinations that do not support password
  while it was given in params. For both, import and export.
- PKCS8 always requires a password.
- Added few special cases to differentiate INV_PARAM and INV_PASSWORD.

Change-Id: I171e89125600151e33178eadc3df6b6004987f3c

api/yaca/yaca_key.h
examples/key_password.c
src/debug.c
src/key.c
todo.txt

index 75fb2ca..89801ba 100644 (file)
@@ -95,6 +95,11 @@ int yaca_key_get_bit_length(const yaca_key_h key, size_t *key_bit_len);
  *           used. If it's not known if the key is encrypted one should pass NULL as
  *           password and check for the #YACA_ERROR_INVALID_PASSWORD return code.
  *
+ * @remarks  If the imported key will be detected as a format that does not support
+ *           encryption and password was passed #YACA_ERROR_INVALID_PARAMETER will
+ *           be returned. For a list of keys and formats that do support encryption
+ *           see yaca_key_export() documentation.
+ *
  * @remarks  The @a key should be released using yaca_key_destroy()
  *
  * @param[in]  key_type  Type of the key
@@ -132,7 +137,7 @@ int yaca_key_import(yaca_key_type_e key_type,
  * @remarks  For key formats two values are allowed:
  *           - #YACA_KEY_FORMAT_DEFAULT: this is the only option possible in case of symmetric
  *                                       keys (or IV), for asymmetric keys it will choose PKCS#1
- *                                       for RSA and SSLeay for DSA.
+ *                                       for RSA keys and SSLeay for DSA keys.
  *           - #YACA_KEY_FORMAT_PKCS8: this will only work for private asymmetric keys.
  *
  * @remarks  The following file formats are supported:
@@ -141,11 +146,18 @@ int yaca_key_import(yaca_key_type_e key_type,
  *           - #YACA_KEY_FILE_FORMAT_PEM:    used only for asymmetric, PEM file format
  *           - #YACA_KEY_FILE_FORMAT_DER:    used only for asymmetric, DER file format
  *
- * @remarks  If no password is provided the exported key will be unencrypted. Only private
- *           RSA/DSA exported as PEM can be encrypted.
+ * @remarks  Encryption is supported and optional for RSA/DSA private keys in the
+ *           #YACA_KEY_FORMAT_DEFAULT with #YACA_KEY_FILE_FORMAT_PEM format. If no password is
+ *           provided the exported key will be unencrypted. The encryption algorithm used
+ *           in this case is AES-256-CBC.
+ *
+ * @remarks  Encryption is obligatory for #YACA_KEY_FORMAT_PKCS8 format (for both, PEM and DER
+ *           file formats). If no password is provided the #YACA_ERROR_INVALID_PARAMETER will
+ *           be returned. The encryption algorithm used in this case is PBE with DES-CBC.
  *
- * @remarks  TODO:document the default encryption algorithm (AES256 for FORMAT_DEFAULT,
- *           unknown yet for the FORMAT_PKCS8).
+ * @remakrs  Encryption is not supported for the symmetric and public keys in all their
+ *           supported formats. If a password is provided in such case the
+ *           #YACA_ERROR_INVALID_PARAMETER will be returned.
  *
  * @param[in]  key           Key to be exported
  * @param[in]  key_fmt       Format of the key
index ef40fa2..724e472 100644 (file)
@@ -25,7 +25,8 @@
 #include "misc.h"
 #include "../src/debug.h"
 
-void example_password(const yaca_key_h key, yaca_key_format_e key_fmt)
+void example_password(const yaca_key_h key, yaca_key_format_e key_fmt,
+                      yaca_key_file_format_e key_file_fmt)
 {
        char *k = NULL;
        size_t kl;
@@ -37,7 +38,9 @@ void example_password(const yaca_key_h key, yaca_key_format_e key_fmt)
        if (ret != YACA_ERROR_NONE)
                goto exit;
 
-       ret = yaca_key_export(key, key_fmt, YACA_KEY_FILE_FORMAT_PEM, password, &k, &kl);
+       ret = yaca_key_export(key, key_fmt, key_file_fmt, password, &k, &kl);
+       if (ret == YACA_ERROR_INVALID_PARAMETER)
+               printf("invalid parameter, probably a missing password for PKCS8\n");
        if (ret != YACA_ERROR_NONE)
                goto exit;
 
@@ -53,9 +56,6 @@ void example_password(const yaca_key_h key, yaca_key_format_e key_fmt)
                ret = yaca_key_import(YACA_KEY_TYPE_RSA_PRIV, password, k, kl, &lkey);
                if (ret == YACA_ERROR_INVALID_PASSWORD)
                        printf("invalid password\n");
-
-               yaca_free(password);
-               password = NULL;
        }
 
        if (ret != YACA_ERROR_NONE)
@@ -64,7 +64,7 @@ void example_password(const yaca_key_h key, yaca_key_format_e key_fmt)
        yaca_free(k);
        k = NULL;
 
-       ret = yaca_key_export(lkey, key_fmt, YACA_KEY_FILE_FORMAT_PEM, NULL, &k, &kl);
+       ret = yaca_key_export(lkey, key_fmt, YACA_KEY_FILE_FORMAT_PEM, password, &k, &kl);
        if (ret != YACA_ERROR_NONE)
                goto exit;
 
@@ -91,10 +91,12 @@ int main()
        if (ret != YACA_ERROR_NONE)
                goto exit;
 
-       printf("Default format:\n");
-       example_password(key, YACA_KEY_FORMAT_DEFAULT);
-       printf("\nPKCS8 format:\n");
-       example_password(key, YACA_KEY_FORMAT_PKCS8);
+       printf("Default format with PEM:\n");
+       example_password(key, YACA_KEY_FORMAT_DEFAULT, YACA_KEY_FILE_FORMAT_PEM);
+       printf("\nPKCS8 format with PEM:\n");
+       example_password(key, YACA_KEY_FORMAT_PKCS8, YACA_KEY_FILE_FORMAT_PEM);
+       printf("\nPKCS8 format with DER:\n");
+       example_password(key, YACA_KEY_FORMAT_PKCS8, YACA_KEY_FILE_FORMAT_DER);
 
 exit:
        yaca_key_destroy(key);
index 6babb5f..f510ef3 100644 (file)
@@ -26,6 +26,8 @@
 
 #include <openssl/err.h>
 #include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/dsa.h>
 
 #include <yaca_error.h>
 
@@ -124,12 +126,36 @@ int error_handle(const char *file, int line, const char *function)
        case ERR_PACK(ERR_LIB_RSA, RSA_F_PKEY_RSA_CTRL, RSA_R_INVALID_KEYBITS):
        case ERR_PACK(ERR_LIB_EVP, EVP_F_EVP_PKEY_CTX_CTRL, EVP_R_COMMAND_NOT_SUPPORTED):
        case ERR_PACK(ERR_LIB_PEM, PEM_F_PEM_READ_BIO, PEM_R_NO_START_LINE):
-       case ERR_PACK(ERR_LIB_ASN1, ASN1_F_ASN1_CHECK_TLEN, ASN1_R_WRONG_TAG):
        case ERR_PACK(ERR_LIB_ASN1, ASN1_F_ASN1_D2I_READ_BIO, ASN1_R_NOT_ENOUGH_DATA):
                ret = YACA_ERROR_INVALID_PARAMETER;
                break;
+       case ERR_PACK(ERR_LIB_ASN1, ASN1_F_ASN1_GET_OBJECT, ASN1_R_TOO_LONG):
+       case ERR_PACK(ERR_LIB_ASN1, ASN1_F_ASN1_GET_OBJECT, ASN1_R_HEADER_TOO_LONG):
+       case ERR_PACK(ERR_LIB_ASN1, ASN1_F_ASN1_CHECK_TLEN, ASN1_R_WRONG_TAG):
+       {
+               bool found_crypto_error = false;
+
+               while ((err = ERR_get_error()) != 0)
+                       if (err == ERR_PACK(ERR_LIB_PKCS12, PKCS12_F_PKCS12_ITEM_DECRYPT_D2I, PKCS12_R_DECODE_ERROR) ||
+                           err == ERR_PACK(ERR_LIB_PKCS12, PKCS12_F_PKCS12_PBE_CRYPT, PKCS12_R_PKCS12_CIPHERFINAL_ERROR) ||
+                           err == ERR_PACK(ERR_LIB_DSA, DSA_F_OLD_DSA_PRIV_DECODE, ERR_R_DSA_LIB) ||
+                           err == ERR_PACK(ERR_LIB_RSA, RSA_F_OLD_RSA_PRIV_DECODE, ERR_R_RSA_LIB)) {
+                               found_crypto_error = true;
+                               break;
+                       }
+
+               if (found_crypto_error)
+                       ret = YACA_ERROR_INVALID_PASSWORD;
+               else
+                       ret = YACA_ERROR_INVALID_PARAMETER;
+
+               break;
+       }
        case ERR_PACK(ERR_LIB_EVP, EVP_F_EVP_DECRYPTFINAL_EX, EVP_R_BAD_DECRYPT):
        case ERR_PACK(ERR_LIB_PEM, PEM_F_PEM_DO_HEADER, PEM_R_BAD_DECRYPT):
+       case ERR_PACK(ERR_LIB_PEM, PEM_F_PEM_DO_HEADER, PEM_R_BAD_PASSWORD_READ):
+       case ERR_PACK(ERR_LIB_PEM, PEM_F_PEM_READ_BIO_PRIVATEKEY, PEM_R_BAD_PASSWORD_READ):
+       case ERR_PACK(ERR_LIB_PEM, PEM_F_D2I_PKCS8PRIVATEKEY_BIO, PEM_R_BAD_PASSWORD_READ):
                ret = YACA_ERROR_INVALID_PASSWORD;
                break;
        }
index 2511437..1a02ce5 100644 (file)
--- a/src/key.c
+++ b/src/key.c
 
 #include "internal.h"
 
-/* This callback only exists to block the default OpenSSL one and
- * allow us to check for a proper error code when the key is encrypted
- */
-int password_dummy_cb(char *buf, UNUSED int size, UNUSED int rwflag, UNUSED void *u)
+struct openssl_password_data {
+       bool password_requested;
+       const char *password;
+};
+
+int openssl_password_cb(char *buf, int size, UNUSED int rwflag, void *u)
 {
-       const char empty[] = "";
+       struct openssl_password_data *cb_data = u;
+
+       if (cb_data->password == NULL)
+               return 0;
 
-       memcpy(buf, empty, sizeof(empty));
+       size_t pass_len = strlen(cb_data->password);
 
-       return sizeof(empty);
+       if (pass_len > INT_MAX || (int)pass_len > size)
+               return 0;
+
+       memcpy(buf, cb_data->password, pass_len);
+       cb_data->password_requested = true;
+
+       return pass_len;
+}
+
+int openssl_password_cb_error(UNUSED char *buf, UNUSED int size, UNUSED int rwflag, UNUSED void *u)
+{
+       return 0;
 }
 
 int base64_decode_length(const char *data, size_t data_len, size_t *len)
@@ -251,9 +267,10 @@ int import_evp(yaca_key_h *key,
        int ret;
        BIO *src = NULL;
        EVP_PKEY *pkey = NULL;
-       bool wrong_pass = false;
-       pem_password_cb *cb = NULL;
+       pem_password_cb *cb = openssl_password_cb;
+       struct openssl_password_data cb_data = {false, password};
        bool private;
+       bool password_supported;
        yaca_key_type_e type;
        struct yaca_key_evp_s *nk = NULL;
 
@@ -274,74 +291,79 @@ int import_evp(yaca_key_h *key,
                return YACA_ERROR_INTERNAL;
        }
 
-       /* Block the default OpenSSL password callback */
-       if (password == NULL)
-               cb = password_dummy_cb;
-
        /* Possible PEM */
        if (strncmp("----", data, 4) == 0) {
-               if (pkey == NULL && !wrong_pass) {
+               if (pkey == NULL) {
                        BIO_reset(src);
-                       pkey = PEM_read_bio_PrivateKey(src, NULL, cb, (void*)password);
+                       pkey = PEM_read_bio_PrivateKey(src, NULL, cb, (void*)&cb_data);
                        if (ERROR_HANDLE() == YACA_ERROR_INVALID_PASSWORD)
-                               wrong_pass = true;
+                               return YACA_ERROR_INVALID_PASSWORD;
                        private = true;
+                       password_supported = true;
                }
 
-               if (pkey == NULL && !wrong_pass) {
+               if (pkey == NULL) {
                        BIO_reset(src);
-                       pkey = PEM_read_bio_PUBKEY(src, NULL, cb, (void*)password);
-                       if (ERROR_HANDLE() == YACA_ERROR_INVALID_PASSWORD)
-                               wrong_pass = true;
+                       pkey = PEM_read_bio_PUBKEY(src, NULL, openssl_password_cb_error, NULL);
+                       ERROR_CLEAR();
                        private = false;
+                       password_supported = false;
                }
 
-               if (pkey == NULL && !wrong_pass) {
+               if (pkey == NULL) {
                        BIO_reset(src);
-                       X509 *x509 = PEM_read_bio_X509(src, NULL, cb, (void*)password);
-                       if (ERROR_HANDLE() == YACA_ERROR_INVALID_PASSWORD)
-                               wrong_pass = true;
+                       X509 *x509 = PEM_read_bio_X509(src, NULL, openssl_password_cb_error, NULL);
                        if (x509 != NULL) {
                                pkey = X509_get_pubkey(x509);
                                X509_free(x509);
-                               ERROR_CLEAR();
                        }
+                       ERROR_CLEAR();
                        private = false;
+                       password_supported = false;
                }
        }
        /* Possible DER */
        else {
-               if (pkey == NULL && !wrong_pass) {
+               if (pkey == NULL) {
                        BIO_reset(src);
-                       pkey = d2i_PKCS8PrivateKey_bio(src, NULL, cb, (void*)password);
+                       pkey = d2i_PKCS8PrivateKey_bio(src, NULL, cb, (void*)&cb_data);
                        if (ERROR_HANDLE() == YACA_ERROR_INVALID_PASSWORD)
-                               wrong_pass = true;
+                               return YACA_ERROR_INVALID_PASSWORD;
                        private = true;
+                       password_supported = true;
                }
 
-               if (pkey == NULL && !wrong_pass) {
+               if (pkey == NULL) {
                        BIO_reset(src);
                        pkey = d2i_PrivateKey_bio(src, NULL);
                        ERROR_CLEAR();
                        private = true;
+                       password_supported = false;
                }
 
-               if (pkey == NULL && !wrong_pass) {
+               if (pkey == NULL) {
                        BIO_reset(src);
                        pkey = d2i_PUBKEY_bio(src, NULL);
                        ERROR_CLEAR();
                        private = false;
+                       password_supported = false;
                }
        }
 
        BIO_free(src);
 
-       if (wrong_pass)
-               return YACA_ERROR_INVALID_PASSWORD;
-
        if (pkey == NULL)
                return YACA_ERROR_INVALID_PARAMETER;
 
+       /* password was given, but it was not required to perform import */
+       if (password != NULL && !cb_data.password_requested) {
+               if (password_supported)
+                       ret = YACA_ERROR_INVALID_PASSWORD;
+               else
+                       ret = YACA_ERROR_INVALID_PARAMETER;
+               goto exit;
+       }
+
        switch (EVP_PKEY_type(pkey->type)) {
        case EVP_PKEY_RSA:
                type = private ? YACA_KEY_TYPE_RSA_PRIV : YACA_KEY_TYPE_RSA_PUB;
@@ -503,6 +525,8 @@ int export_evp_default_bio(struct yaca_key_evp_s *evp_key,
 
                case YACA_KEY_TYPE_RSA_PUB:
                case YACA_KEY_TYPE_DSA_PUB:
+                       if (password != NULL)
+                               return YACA_ERROR_INVALID_PARAMETER;
                        ret = PEM_write_bio_PUBKEY(mem, evp_key->evp);
                        break;
 
@@ -521,15 +545,21 @@ int export_evp_default_bio(struct yaca_key_evp_s *evp_key,
                switch (evp_key->key.type) {
 
                case YACA_KEY_TYPE_RSA_PRIV:
+                       if (password != NULL)
+                               return YACA_ERROR_INVALID_PARAMETER;
                        ret = i2d_RSAPrivateKey_bio(mem, EVP_PKEY_get0(evp_key->evp));
                        break;
 
                case YACA_KEY_TYPE_DSA_PRIV:
+                       if (password != NULL)
+                               return YACA_ERROR_INVALID_PARAMETER;
                        ret = i2d_DSAPrivateKey_bio(mem, EVP_PKEY_get0(evp_key->evp));
                        break;
 
                case YACA_KEY_TYPE_RSA_PUB:
                case YACA_KEY_TYPE_DSA_PUB:
+                       if (password != NULL)
+                               return YACA_ERROR_INVALID_PARAMETER;
                        ret = i2d_PUBKEY_bio(mem, evp_key->evp);
                        break;
 
@@ -567,10 +597,11 @@ int export_evp_pkcs8_bio(struct yaca_key_evp_s *evp_key,
        assert(mem != NULL);
 
        int ret;
-       int nid = -1;
+       int nid = NID_pbeWithMD5AndDES_CBC;
 
-       if (password != NULL)
-               nid = NID_pbeWithMD5AndDES_CBC;
+       /* PKCS8 export requires a password */
+       if (password == NULL)
+               return YACA_ERROR_INVALID_PARAMETER;
 
        switch (key_file_fmt) {
 
index e2ef626..c33b5d7 100644 (file)
--- a/todo.txt
+++ b/todo.txt
@@ -7,4 +7,5 @@ Global:
 - yaca_key_wrap(), yaca_key_unwrap()
 - We need a way to import keys encrypted with hw (or other) keys. New
   function like yaca_key_load or sth?
-- Add extended description and examples in documentation.
\ No newline at end of file
+- Add extended description and examples in documentation.
+- Check PKCS8 with PKCS5 2.0 (EVP cipher instead of PBE)