aboutsummaryrefslogtreecommitdiff
path: root/openssl/agent/pkcs11/private-key.cxx
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2018-10-15 21:08:04 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2018-10-17 15:02:42 +0300
commitde91921561092689369b56c54950474e0a86e66f (patch)
treea9949058021d911db1106b1a2e4d9e0e9281de16 /openssl/agent/pkcs11/private-key.cxx
parentfb65c93daaf369157bd712f2c4c20161c4840b94 (diff)
Add implementation
Diffstat (limited to 'openssl/agent/pkcs11/private-key.cxx')
-rw-r--r--openssl/agent/pkcs11/private-key.cxx492
1 files changed, 492 insertions, 0 deletions
diff --git a/openssl/agent/pkcs11/private-key.cxx b/openssl/agent/pkcs11/private-key.cxx
new file mode 100644
index 0000000..e85d815
--- /dev/null
+++ b/openssl/agent/pkcs11/private-key.cxx
@@ -0,0 +1,492 @@
+// file : openssl/agent/pkcs11/private-key.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <openssl/agent/pkcs11/private-key.hxx>
+
+#include <cstring> // memset(), strlen()
+
+#include <openssl/agent/pkcs11/pkcs11.hxx>
+
+#define API_STRING(S) api_string (S, sizeof (S))
+
+namespace openssl
+{
+ namespace agent
+ {
+ namespace pkcs11
+ {
+ using namespace std;
+
+ static void
+ close_session (CK_SESSION_HANDLE* p)
+ {
+ if (p != nullptr)
+ api ()->C_CloseSession (*p);
+ }
+
+ private_key::
+ private_key (): session_ (nullptr, close_session)
+ {
+ }
+
+ private_key::
+ private_key (private_key&& k): private_key ()
+ {
+ *this = move (k);
+ }
+
+ private_key& private_key::
+ operator= (private_key&& k)
+ {
+ description = move (k.description);
+ session_ = move (k.session_);
+ handle_ = k.handle_;
+ simulate_ = move (k.simulate_);
+ return *this;
+ }
+
+ private_key::
+ private_key (const identity& idn,
+ const access& acc,
+ const char* pin,
+ optional<simulate_outcome> sim)
+ : private_key ()
+ {
+ simulate_ = move (sim); // Can't initialize because of delegate ctor.
+
+ // Verify the identity and access attributes.
+ //
+ if (idn.type && *idn.type != "private")
+ throw invalid_argument ("unexpected object type " + *idn.type);
+
+ if (pin == nullptr && acc.pin_value)
+ pin = acc.pin_value->c_str ();
+
+ if (pin == nullptr)
+ throw invalid_argument ("PIN is required");
+
+ auto no_key = [] ()
+ {
+ throw runtime_error ("no matching private key found");
+ };
+
+ auto simulate = [this, &no_key] ()
+ {
+ assert (simulate_ && session_ == nullptr);
+
+ switch (*simulate_)
+ {
+ case simulate_outcome::success:
+ {
+ description = "simulated private key";
+ return;
+ }
+ case simulate_outcome::failure:
+ {
+ no_key ();
+ }
+ }
+ };
+
+ // Get the Cryptoki functions pointer.
+ //
+ CK_FUNCTION_LIST* fs;
+ {
+ path p;
+
+ if (acc.module_path)
+ {
+ if (acc.module_name)
+ p = *acc.module_path / (*acc.module_name + ".so");
+ else if (file_exists (*acc.module_path))
+ p = *acc.module_path;
+ else
+ p = *acc.module_path / "opensc-pkcs11.so";
+ }
+ else if (acc.module_name)
+ p = *acc.module_name + ".so";
+ else
+ p = path ("opensc-pkcs11.so");
+
+ // Ignore non-existent PKCS#11 module in the simulated mode. Note
+ // that if the module exists we will simulate as far as possible,
+ // up to opening token sessions (but not logging into them).
+ //
+ fs = api (p, simulate_.has_value ());
+
+ if (fs == nullptr)
+ {
+ assert (simulate_);
+ simulate ();
+ return;
+ }
+ }
+
+ // Search for the private key.
+ //
+ // The overall plan is to match the PKCS#11 module, go though all the
+ // matching slots it provides, log into all the matching tokens these
+ // slots may contain and see if the tokens contain a matching RSA
+ // private keys. Fail if unable to log into the matching tokens or
+ // none or multiple private keys match. Otherwise keep both the
+ // matching token session and the key handle.
+ //
+ auto mult_keys = [] ()
+ {
+ throw runtime_error ("multiple private keys match");
+ };
+
+ auto match = [] (const auto& attr, const auto& value)
+ {
+ return !attr || *attr == value;
+ };
+
+ // Match the module.
+ //
+ CK_INFO pi;
+ CK_RV r (fs->C_GetInfo (&pi));
+
+ if (r != CKR_OK)
+ throw_api_error (r, "unable to obtain PKCS#11 module info");
+
+ if (!match (idn.library_manufacturer,
+ API_STRING (pi.manufacturerID)) ||
+
+ !match (idn.library_version,
+ library_version (pi.libraryVersion.major,
+ pi.libraryVersion.minor)) ||
+
+ !match (idn.library_description,
+ API_STRING (pi.libraryDescription)))
+ no_key ();
+
+ // Traverse through slots.
+ //
+ // In the PKCS#11 terminology a slot is something that may have token
+ // be inserted into it. Not to be confused with Yubikey's 9a, 9c, etc
+ // slots.
+ //
+ // Note that a set of slots is checked at the time when
+ // C_GetSlotList() is called to obtain slots count (pSlotList argument
+ // is NULL and the tokenPresent argument is false).
+ //
+ CK_ULONG nslots;
+ r = fs->C_GetSlotList (false /* tokenPresent */,
+ nullptr /* pSlotList */,
+ &nslots);
+
+ if (r != CKR_OK)
+ throw_api_error (r, "unable to obtain slots count");
+
+ vector<CK_SLOT_ID> slot_ids (nslots);
+ r = fs->C_GetSlotList (false /* tokenPresent */,
+ slot_ids.data (),
+ &nslots);
+
+ if (r != CKR_OK)
+ throw_api_error (r, "unable to obtain slot ids");
+
+ for (CK_SLOT_ID sid: slot_ids)
+ {
+ // Match the slot information.
+ //
+ if (!match (idn.slot_id, sid))
+ continue;
+
+ CK_SLOT_INFO si;
+ r = fs->C_GetSlotInfo (sid, &si);
+
+ if (r != CKR_OK)
+ throw_api_error (
+ r, "unable to obtain slot " + to_string (sid) + " info");
+
+ if ((si.flags & CKF_TOKEN_PRESENT) == 0 ||
+ !match (idn.slot_manufacturer, API_STRING (si.manufacturerID)) ||
+ !match (idn.slot_description, API_STRING (si.slotDescription)))
+ continue;
+
+ auto slot_desc = [&sid, &si] ()
+ {
+ string d (API_STRING (si.slotDescription));
+ return "slot " + to_string (sid) + " (" +
+ (!d.empty () ? d : API_STRING (si.manufacturerID)) + ")";
+ };
+
+ // Match the token information.
+ //
+ CK_TOKEN_INFO ti;
+ r = fs->C_GetTokenInfo (sid, &ti);
+
+ if (r == CKR_TOKEN_NOT_PRESENT || r == CKR_TOKEN_NOT_RECOGNIZED)
+ continue;
+
+ if (r != CKR_OK)
+ throw_api_error (
+ r, "unable to obtain token info for " + slot_desc ());
+
+ if ((ti.flags & CKF_TOKEN_INITIALIZED) == 0 ||
+ (ti.flags & CKF_USER_PIN_INITIALIZED) == 0 ||
+ (ti.flags & CKF_LOGIN_REQUIRED) == 0 ||
+ (ti.flags & CKF_PROTECTED_AUTHENTICATION_PATH) != 0 || // Pinpad?
+
+ !match (idn.serial, API_STRING (ti.serialNumber)) ||
+ !match (idn.token, API_STRING (ti.label)) ||
+ !match (idn.model, API_STRING (ti.model)) ||
+ !match (idn.manufacturer, API_STRING (ti.manufacturerID)))
+ continue;
+
+ auto token_desc = [&ti] ()
+ {
+ string r ("token ");
+ string l (API_STRING (ti.label));
+
+ r += !l.empty ()
+ ? "'" + l + "'"
+ : "'" + API_STRING (ti.model) + "' by " +
+ API_STRING (ti.manufacturerID);
+
+ return r;
+ };
+
+ // Search for the matching RSA private key in the token.
+ //
+ // Open the read-only session with the token. Note that we can't see
+ // private objects until login.
+ //
+ CK_SESSION_HANDLE sh;
+ r = fs->C_OpenSession (sid,
+ CKF_SERIAL_SESSION,
+ nullptr /* pApplication */,
+ nullptr /* Notify */,
+ &sh);
+
+ if (r == CKR_DEVICE_REMOVED ||
+ r == CKR_TOKEN_NOT_PRESENT ||
+ r == CKR_TOKEN_NOT_RECOGNIZED)
+ continue;
+
+ if (r != CKR_OK)
+ throw_api_error (
+ r, "unable to open session with " + token_desc ());
+
+ session_ptr session (new CK_SESSION_HANDLE (sh), close_session);
+
+ // Log into the token unless simulating.
+ //
+ if (simulate_)
+ continue;
+
+ // Note that all sessions with a token share login, so need to login
+ // once per all token sessions.
+ //
+ // Also note that there is no need to logout explicitly if you want
+ // to keep all token sessions logged in during their lifetime.
+ //
+ r = fs->C_Login (
+ *session,
+ CKU_USER,
+ reinterpret_cast<unsigned char*> (const_cast<char*> (pin)),
+ strlen (pin));
+
+ // We can fail because of the wrong PIN or trying to apply the PIN
+ // to a wrong token. Should we just skip the token if that's the
+ // case? Probably we should throw. Otherwise who knows how many pins
+ // we will reset going forward.
+ //
+ if (r != CKR_USER_ALREADY_LOGGED_IN && r != CKR_OK)
+ throw_api_error (r, "unable to login to " + token_desc ());
+
+ // Search for the private key.
+ //
+ // Fill the search attributes.
+ //
+ CK_OBJECT_CLASS oc (CKO_PRIVATE_KEY);
+ CK_KEY_TYPE kt (CKK_RSA);
+
+ vector<CK_ATTRIBUTE> sa ({{CKA_CLASS, &oc, sizeof (oc)},
+ {CKA_KEY_TYPE, &kt, sizeof (kt)}});
+ if (idn.id)
+ sa.push_back (
+ CK_ATTRIBUTE {CKA_ID,
+ const_cast<unsigned char*> (idn.id->data ()),
+ idn.id->size ()});
+
+ if (idn.object)
+ sa.push_back (CK_ATTRIBUTE {CKA_LABEL,
+ const_cast<char*> (idn.object->c_str ()),
+ idn.object->size ()});
+
+ // Initialize the search.
+ //
+ r = fs->C_FindObjectsInit (*session, sa.data (), sa.size ());
+
+ if (r != CKR_OK)
+ throw_api_error (
+ r, "unable to enumerate private keys in " + token_desc ());
+
+ // Finally, search for the key.
+ //
+ // Note that we will query 2 keys to handle the 'multiple keys
+ // match' case.
+ //
+ CK_OBJECT_HANDLE key (0);
+ {
+ session_ptr search_deleter (
+ session.get (),
+ [] (CK_SESSION_HANDLE* p) {api ()->C_FindObjectsFinal (*p);});
+
+ CK_ULONG n;
+ CK_OBJECT_HANDLE keys[2];
+ r = fs->C_FindObjects (*session,
+ keys,
+ 2 /* ulMaxObjectCount */,
+ &n);
+
+ if (r != CKR_OK)
+ throw_api_error (
+ r,
+ "unable to obtain private key handles from " + token_desc ());
+
+ if (n == 1)
+ key = keys[0]; // Exactly one key matches.
+ else if (n == 0)
+ continue; // No key matches.
+ else
+ mult_keys (); // Multiple keys match.
+ }
+
+ if (session_ != nullptr)
+ mult_keys ();
+
+ // Produce description for the found key.
+ //
+ description = "private key ";
+ {
+ CK_ATTRIBUTE attr {CKA_LABEL, nullptr, 0};
+ r = fs->C_GetAttributeValue (*session,
+ key,
+ &attr,
+ 1 /* ulCount */);
+
+ if (r == CKR_OK && attr.ulValueLen != 0)
+ {
+ vector<char> label (attr.ulValueLen);
+ attr.pValue = label.data ();
+
+ r = fs->C_GetAttributeValue (*session,
+ key,
+ &attr,
+ 1 /* ulCount */);
+ if (r == CKR_OK)
+ description += "'" + string (label.data (), label.size ()) +
+ "' ";
+ }
+ }
+
+ description += "in " + token_desc ();
+
+ // Note that for Yubikey 4 we cannot rely on the key's
+ // CKA_ALWAYS_AUTHENTICATE attribute value for detecting if
+ // authentication is required for the sign operation. For the
+ // private key imported into the 9c (SIGN key) slot with the
+ // --pin-policy=never option the sign operation doesn't require
+ // authentication but the CKA_ALWAYS_AUTHENTICATE value is still on.
+ // This seems to be some yubico-piv-tool issue (or deliberate
+ // behavior) as for the 9a (PIV AUTH key) slot the sign
+ // authentication is not required and the CKA_ALWAYS_AUTHENTICATE
+ // value is off. This issue makes it impossible to evaluate if the
+ // key requires sign authentication before the sign attempt. This is
+ // probably not a big deal as, if we want, we can always check this
+ // using some dummy data.
+ //
+#if 0
+ CK_BBOOL always_auth (CK_FALSE);
+
+ CK_ATTRIBUTE attr {
+ CKA_ALWAYS_AUTHENTICATE, &always_auth, sizeof (always_auth)};
+
+ r = fs->C_GetAttributeValue (*session,
+ key,
+ &attr,
+ 1 /* ulCount */);
+
+ if (r != CKR_OK)
+ throw_api_error (
+ r,
+ "unable to obtain 'always auth' attribute for " + description);
+#endif
+
+ // Despite the fact that the key is found we will continue to iterate
+ // over slots to make sure that a single key matches the identity
+ // attributes.
+ //
+ session_ = move (session);
+ handle_ = key;
+ }
+
+ if (simulate_)
+ simulate ();
+ else if (session_ == nullptr)
+ no_key ();
+ }
+
+ vector<char> private_key::
+ sign (const vector<char>& data, const optional<simulate_outcome>& sim)
+ {
+ assert (!empty ());
+
+ if (sim && *sim == simulate_outcome::failure)
+ throw runtime_error ("unable to sign using " + description);
+
+ if (sim || simulate_)
+ {
+ // Otherwise would fail in the private_key constructor.
+ //
+ assert (!simulate_ || *simulate_ == simulate_outcome::success);
+
+ return vector<char> ({
+ 's', 'i', 'g', 'n', 'a', 't', 'u', 'r', 'e', '\n'});
+ }
+
+ CK_FUNCTION_LIST* fs (api ());
+
+ CK_MECHANISM mech;
+ memset (&mech, 0, sizeof (mech));
+ mech.mechanism = CKM_RSA_PKCS;
+
+ CK_RV r (fs->C_SignInit (*session_, &mech, handle_));
+
+ if (r != CKR_OK)
+ throw_api_error (
+ r, "unable to init sign operation using " + description);
+
+ for (vector<char> signature; true; signature_size_ *= 2)
+ {
+ signature.resize (signature_size_);
+
+ CK_ULONG n (signature_size_);
+
+ r = fs->C_Sign (*session_,
+ reinterpret_cast<unsigned char*> (
+ const_cast<char*> (data.data ())),
+ data.size (),
+ reinterpret_cast<unsigned char*> (
+ signature.data ()),
+ &n);
+
+ if (r == CKR_BUFFER_TOO_SMALL)
+ continue;
+
+ if (r != CKR_OK)
+ throw_api_error (r, "unable to sign using " + description);
+
+ assert (n != 0 && n <= signature_size_);
+
+ signature.resize (n);
+ return signature;
+ }
+ }
+ }
+ }
+}