From 402781f094e9a2450942f9d58215da281ba080c2 Mon Sep 17 00:00:00 2001
From: Timothy Pearson <kb9vqf@pearsoncomputing.net>
Date: Mon, 14 Sep 2015 15:08:14 -0500
Subject: Add cryptographic card decryption method to tdehwlib

---
 tdecore/tdehw/tdecryptographiccarddevice.cpp       | 227 +++++++++++++++++++--
 tdecore/tdehw/tdecryptographiccarddevice.h         |  37 ++++
 tdecore/tdehw/tdecryptographiccarddevice_private.h |  10 +
 3 files changed, 259 insertions(+), 15 deletions(-)

diff --git a/tdecore/tdehw/tdecryptographiccarddevice.cpp b/tdecore/tdehw/tdecryptographiccarddevice.cpp
index 1fb1849ca..99c00b9e5 100644
--- a/tdecore/tdehw/tdecryptographiccarddevice.cpp
+++ b/tdecore/tdehw/tdecryptographiccarddevice.cpp
@@ -61,6 +61,8 @@ CryptoCardDeviceWatcher::CryptoCardDeviceWatcher() {
 #ifdef WITH_PCSC
 	m_readerStates = NULL;
 #endif
+	m_cardPINPromptDone = true;
+	m_pinCallbacksEnabled = false;
 }
 
 CryptoCardDeviceWatcher::~CryptoCardDeviceWatcher() {
@@ -189,6 +191,11 @@ void CryptoCardDeviceWatcher::requestTermination() {
 	m_terminationRequested = true;
 }
 
+void CryptoCardDeviceWatcher::setProvidedPin(TQString pin) {
+	m_cardPIN = pin;
+	m_cardPINPromptDone = true;
+}
+
 TQString CryptoCardDeviceWatcher::getCardATR(TQString readerName) {
 #ifdef WITH_PCSC
 	unsigned int i;
@@ -231,21 +238,54 @@ TQString CryptoCardDeviceWatcher::getCardATR(TQString readerName) {
 #endif
 }
 
+void CryptoCardDeviceWatcher::enablePINEntryCallbacks(bool enable) {
+	m_pinCallbacksEnabled = enable;
+}
+
+TQString CryptoCardDeviceWatcher::doPinRequest(TQString prompt) {
+	if (!m_pinCallbacksEnabled) {
+		return TQString::null;
+	}
+
+	m_cardPINPromptDone = false;
+	emit(pinRequested(prompt));
+	while (!m_cardPINPromptDone) {
+		usleep(100);
+	}
+
+	if (m_cardPIN.length() > 0) {
+		return m_cardPIN;
+	}
+	else {
+		return TQString::null;
+	}
+}
+
 #ifdef WITH_PKCS
 static void pkcs_log_hook(IN void * const global_data, IN unsigned flags, IN const char * const format, IN va_list args) {
 	vprintf(format, args);
 	printf("\n");
 }
+
+static PKCS11H_BOOL pkcs_pin_hook(IN void * const global_data, IN void * const user_data, IN const pkcs11h_token_id_t token, IN const unsigned retry, OUT char * const pin, IN const size_t pin_max) {
+	CryptoCardDeviceWatcher* watcher = (CryptoCardDeviceWatcher*)global_data;
+	TQString providedPin = watcher->doPinRequest(i18n("Please enter the PIN for '%1'").arg(token->display));
+	if (providedPin.length() > 0) {
+		snprintf(pin, pin_max, "%s", providedPin.ascii());
+
+		// Success
+		return 1;
+	}
+	else {
+		// Abort
+		return 0;
+	}
+}
 #endif
 
-int CryptoCardDeviceWatcher::retrieveCardCertificates(TQString readerName) {
+int CryptoCardDeviceWatcher::initializePkcs() {
 #if WITH_PKCS
-	int ret = -1;
-
 	CK_RV rv;
-	pkcs11h_certificate_id_list_t issuers;
-	pkcs11h_certificate_id_list_t certs;
-
 	printf("Initializing pkcs11-helper\n");
 	if ((rv = pkcs11h_initialize()) != CKR_OK) {
 		printf("pkcs11h_initialize failed: %s\n", pkcs11h_getMessage(rv));
@@ -253,29 +293,51 @@ int CryptoCardDeviceWatcher::retrieveCardCertificates(TQString readerName) {
 	}
 
 	printf("Registering pkcs11-helper hooks\n");
-	if ((rv = pkcs11h_setLogHook(pkcs_log_hook, NULL)) != CKR_OK) {
+	if ((rv = pkcs11h_setLogHook(pkcs_log_hook, this)) != CKR_OK) {
 		printf("pkcs11h_setLogHook failed: %s\n", pkcs11h_getMessage(rv));
 		return -1;
 	}
 	pkcs11h_setLogLevel(PKCS11H_LOG_WARN);
+	// pkcs11h_setLogLevel(PKCS11H_LOG_DEBUG2);
 
 #if 0
 	if ((rv = pkcs11h_setTokenPromptHook(_pkcs11h_hooks_token_prompt, NULL)) != CKR_OK) {
 		printf("pkcs11h_setTokenPromptHook failed: %s\n", pkcs11h_getMessage(rv));
 		return -1;
 	}
-	if ((rv = pkcs11h_setPINPromptHook(_pkcs11h_hooks_pin_prompt, NULL)) != CKR_OK) {
+#endif
+
+	if ((rv = pkcs11h_setPINPromptHook(pkcs_pin_hook, this)) != CKR_OK) {
 		printf("pkcs11h_setPINPromptHook failed: %s\n", pkcs11h_getMessage(rv));
 		return -1;
 	}
-#endif
+
 	printf("Adding provider '%s'\n", OPENSC_PKCS11_PROVIDER_LIBRARY);
 		if ((rv = pkcs11h_addProvider(OPENSC_PKCS11_PROVIDER_LIBRARY, OPENSC_PKCS11_PROVIDER_LIBRARY, FALSE, PKCS11H_PRIVATEMODE_MASK_AUTO, PKCS11H_SLOTEVENT_METHOD_AUTO, 0, FALSE)) != CKR_OK) {
 		printf("pkcs11h_addProvider failed: %s\n", pkcs11h_getMessage(rv));
 		return -1;
 	}
 
-	rv = pkcs11h_certificate_enumCertificateIds(PKCS11H_ENUM_METHOD_CACHE, NULL, PKCS11H_PROMPT_MASK_ALLOW_NONE, &issuers, &certs);
+	return 0;
+#else
+	return -1;
+#endif
+}
+
+int CryptoCardDeviceWatcher::retrieveCardCertificates(TQString readerName) {
+#if WITH_PKCS
+	int ret = -1;
+
+	CK_RV rv;
+	pkcs11h_certificate_id_list_t issuers;
+	pkcs11h_certificate_id_list_t certs;
+
+	if (initializePkcs() < 0) {
+		printf("Unable to initialize PKCS\n");
+		return -1;
+	}
+
+	rv = pkcs11h_certificate_enumCertificateIds(PKCS11H_ENUM_METHOD_CACHE, NULL, PKCS11H_PROMPT_MASK_ALLOW_ALL, &issuers, &certs);
 	if ((rv != CKR_OK) || (certs == NULL)) {
 		printf("Cannot enumerate certificates: %s\n", pkcs11h_getMessage(rv));
 		return -1;
@@ -288,13 +350,14 @@ int CryptoCardDeviceWatcher::retrieveCardCertificates(TQString readerName) {
 		printf("Certificate %d name: '%s'\n", i, label.ascii());
 
 		pkcs11h_certificate_t certificate;
-		rv = pkcs11h_certificate_create(certs->certificate_id, NULL, PKCS11H_PROMPT_MASK_ALLOW_NONE, PKCS11H_PIN_CACHE_INFINITE, &certificate);
+		rv = pkcs11h_certificate_create(certs->certificate_id, NULL, PKCS11H_PROMPT_MASK_ALLOW_ALL, PKCS11H_PIN_CACHE_INFINITE, &certificate);
 		if (rv != CKR_OK) {
-			printf("Can not read certificate: %s\n", pkcs11h_getMessage(rv));
+			printf("Cannot read certificate: %s\n", pkcs11h_getMessage(rv));
 			pkcs11h_certificate_freeCertificateId(certs->certificate_id);
 			ret = -1;
 			break;
 		}
+
 		pkcs11h_certificate_freeCertificateId(certs->certificate_id);
 
 		pkcs11h_openssl_session_t openssl_session = NULL;
@@ -335,13 +398,12 @@ int CryptoCardDeviceWatcher::retrieveCardCertificates(TQString readerName) {
 			printf("Unable to copy X509 certificate\n");
 		}
 
-		pkcs11h_certificate_freeCertificateIdList(issuers);
-
 		pkcs11h_openssl_freeSession(openssl_session);
-
 		i++;
 	}
 
+	pkcs11h_certificate_freeCertificateIdList(issuers);
+
 	return ret;
 #else
 	return -1;
@@ -386,6 +448,7 @@ void TDECryptographicCardDevice::enableCardMonitoring(bool enable) {
 		m_watcherObject->cardDevice = this;
 		m_watcherObject->moveToThread(m_watcherThread);
 		TQObject::connect(m_watcherObject, SIGNAL(statusChanged(TQString,TQString)), this, SLOT(cardStatusChanged(TQString,TQString)));
+		TQObject::connect(m_watcherObject, SIGNAL(pinRequested(TQString)), this, SLOT(workerRequestedPin(TQString)));
 		TQTimer::singleShot(0, m_watcherObject, SLOT(run()));
 
 		m_watcherThread->start();
@@ -407,6 +470,12 @@ void TDECryptographicCardDevice::enableCardMonitoring(bool enable) {
 #endif
 }
 
+void TDECryptographicCardDevice::enablePINEntryCallbacks(bool enable) {
+	if (m_watcherObject) {
+		m_watcherObject->enablePINEntryCallbacks(enable);
+	}
+}
+
 int TDECryptographicCardDevice::cardPresent() {
 	if (m_watcherObject && m_watcherThread) {
 		if (m_cardPresent)
@@ -462,6 +531,134 @@ void TDECryptographicCardDevice::cardStatusChanged(TQString status, TQString atr
 	}
 }
 
+void TDECryptographicCardDevice::setProvidedPin(TQString pin) {
+	if (m_watcherObject) {
+		m_watcherObject->setProvidedPin(pin);
+	}
+}
+
+void TDECryptographicCardDevice::workerRequestedPin(TQString prompt) {
+	emit(pinRequested(prompt, this));
+}
+
+int TDECryptographicCardDevice::decryptDataEncryptedWithCertPublicKey(TQByteArray &ciphertext, TQByteArray &plaintext, TQString *errstr) {
+#if WITH_PKCS
+	int ret = -1;
+
+	if (!m_watcherObject) {
+		if (errstr) *errstr = i18n("Card watcher object not available");
+		return -1;
+	}
+
+	CK_RV rv;
+	pkcs11h_certificate_id_list_t issuers;
+	pkcs11h_certificate_id_list_t certs;
+
+	if (m_watcherObject->initializePkcs() < 0) {
+		if (errstr) *errstr = i18n("Unable to initialize PKCS");
+		return -1;
+	}
+
+	rv = pkcs11h_certificate_enumCertificateIds(PKCS11H_ENUM_METHOD_CACHE, NULL, PKCS11H_PROMPT_MASK_ALLOW_ALL, &issuers, &certs);
+	if ((rv != CKR_OK) || (certs == NULL)) {
+		if (errstr) *errstr = i18n("Cannot enumerate certificates: %1").arg(pkcs11h_getMessage(rv));
+		return -1;
+	}
+
+	int i = 0;
+	for (pkcs11h_certificate_id_list_t cert = certs; cert != NULL; cert = cert->next) {
+		TQString label = cert->certificate_id->displayName;
+
+		pkcs11h_certificate_t certificate;
+		rv = pkcs11h_certificate_create(certs->certificate_id, NULL, PKCS11H_PROMPT_MASK_ALLOW_ALL, PKCS11H_PIN_CACHE_INFINITE, &certificate);
+		if (rv != CKR_OK) {
+			if (errstr) *errstr = i18n("Cannot read certificate: %1").arg(pkcs11h_getMessage(rv));
+			pkcs11h_certificate_freeCertificateId(certs->certificate_id);
+			ret = -1;
+			break;
+		}
+
+		pkcs11h_certificate_freeCertificateId(certs->certificate_id);
+
+		pkcs11h_openssl_session_t openssl_session = NULL;
+		if ((openssl_session = pkcs11h_openssl_createSession(certificate)) == NULL) {
+			if (errstr) *errstr = i18n("Cannot initialize openssl session to retrieve cryptographic objects");
+			pkcs11h_certificate_freeCertificate(certificate);
+			ret = -1;
+			break;
+		}
+
+		if (ciphertext.size() < 16) {
+			if (errstr) *errstr = i18n("Cannot decrypt: %1").arg(i18n("Ciphertext too small"));
+			ret = -2;
+			continue;
+		}
+
+		// Get certificate data
+		X509* x509_local;
+		x509_local = pkcs11h_openssl_session_getX509(openssl_session);
+		if (!x509_local) {
+			if (errstr) *errstr = i18n("Cannot get X509 object\n");
+			ret = -1;
+		}
+
+		// Extract public key from X509 certificate
+		EVP_PKEY* x509_pubkey = NULL;
+		RSA* rsa_pubkey = NULL;
+		x509_pubkey = X509_get_pubkey(x509_local);
+		if (x509_pubkey) {
+			rsa_pubkey = EVP_PKEY_get1_RSA(x509_pubkey);
+		}
+
+		// Try to get RSA parameters
+		if (rsa_pubkey) {
+			unsigned int rsa_length = RSA_size(rsa_pubkey);
+			if (ciphertext.size() > rsa_length) {
+				if (errstr) *errstr = i18n("Cannot decrypt: %1").arg(i18n("Ciphertext too large"));
+				ret = -2;
+				continue;
+			}
+		}
+
+		size_t size = 0;
+		// Determine output buffer size
+		rv = pkcs11h_certificate_decryptAny(certificate, CKM_RSA_PKCS, (unsigned char*)ciphertext.data(), ciphertext.size(), NULL, &size);
+		if (rv != CKR_OK) {
+			if (errstr) *errstr = i18n("Cannot determine decrypted message length: %1").arg(pkcs11h_getMessage(rv));
+			ret = -2;
+		}
+		else {
+			// Decrypt data
+			plaintext.resize(size);
+			rv = pkcs11h_certificate_decryptAny(certificate, CKM_RSA_PKCS, (unsigned char*)ciphertext.data(), ciphertext.size(), (unsigned char*)plaintext.data(), &size);
+			if (rv != CKR_OK) {
+				if (errstr) *errstr = i18n("Cannot decrypt: %1").arg(pkcs11h_getMessage(rv));
+				ret = -2;
+			}
+			else {
+				if (errstr) *errstr = TQString::null;
+				ret = 0;
+			}
+		}
+
+		pkcs11h_openssl_freeSession(openssl_session);
+
+		// Only interested in first certificate for now
+		// FIXME
+		// If cards with multiple certificates are used this should be modified to try decryption
+		// using each certificate in turn...
+		break;
+
+		i++;
+	}
+	pkcs11h_certificate_freeCertificateIdList(issuers);
+
+	return ret;
+#else
+	return -1;
+#endif
+}
+
 int TDECryptographicCardDevice::createNewSecretRSAKeyFromCertificate(TQByteArray &plaintext, TQByteArray &ciphertext, X509* certificate) {
 	unsigned int i;
 	int retcode = -1;
diff --git a/tdecore/tdehw/tdecryptographiccarddevice.h b/tdecore/tdehw/tdecryptographiccarddevice.h
index c9de6091b..fd5256d23 100644
--- a/tdecore/tdehw/tdecryptographiccarddevice.h
+++ b/tdecore/tdehw/tdecryptographiccarddevice.h
@@ -57,6 +57,20 @@ class TDECORE_EXPORT TDECryptographicCardDevice : public TDEGenericDevice
 		 */
 		void enableCardMonitoring(bool enable);
 
+		/**
+		 * Enable / disable PIN entry.
+		 *
+		 * @note You must connect to pinRequested and call setProvidedPin with
+		 * the provided PIN, otherwise the TDECryptographicCardDevice object
+		 * will hang waiting for input.
+		 *
+		 * @param enable true to enable, false to disable.
+		 *
+		 * @see setProvidedPin(TQString pin)
+		 * @see pinRequested
+		 */
+		void enablePINEntryCallbacks(bool enable);
+
 		/**
 		 * If monitoring of insert / remove events is enabled,
 		 * return whether or not a card is present.
@@ -82,6 +96,27 @@ class TDECORE_EXPORT TDECryptographicCardDevice : public TDEGenericDevice
 		 */
 		X509CertificatePtrList cardX509Certificates();
 
+		/**
+		 * Sets the user-provided PIN from within the pinRequested callback.
+		 * This method must not be called from anywhere else in user code.
+		 * @param pin the user-provided PIN, TQString::null to abort
+		 *
+		 * @see pinRequested(TQString prompt)
+		 */
+		void setProvidedPin(TQString pin);
+
+		/**
+		 * If monitoring of insert / remove events is enabled, and a card has been inserted,
+		 * decrypt data originally encrypted using a public key from one of the certificates
+		 * stored on the card.
+		 * This operation takes place on the card, and in most cases will require PIN entry.
+		 * @param ciphertext Encrypted data
+		 * @param plaintext Decrypted data
+		 * @param errstr Pointer to TQString to be loaded with error description on failure
+		 * @return 0 on success, -1 on general failure, -2 on encryption failure
+		 */
+		int decryptDataEncryptedWithCertPublicKey(TQByteArray &ciphertext, TQByteArray &plaintext, TQString *errstr=NULL);
+
 		/**
 		 * Create a new random key and encrypt with the public key
 		 * contained in the given certificate.
@@ -94,10 +129,12 @@ class TDECORE_EXPORT TDECryptographicCardDevice : public TDEGenericDevice
 
 	public slots:
 		void cardStatusChanged(TQString status, TQString atr);
+		void workerRequestedPin(TQString prompt);
 
 	signals:
 		void cardInserted();
 		void cardRemoved();
+		void pinRequested(TQString prompt, TDECryptographicCardDevice* cdevice);
 
 	private:
 		TQEventLoopThread *m_watcherThread;
diff --git a/tdecore/tdehw/tdecryptographiccarddevice_private.h b/tdecore/tdehw/tdecryptographiccarddevice_private.h
index 4e6a588c4..3371098f2 100644
--- a/tdecore/tdehw/tdecryptographiccarddevice_private.h
+++ b/tdecore/tdehw/tdecryptographiccarddevice_private.h
@@ -53,6 +53,13 @@ class CryptoCardDeviceWatcher : public TQObject
 
 	signals:
 		void statusChanged(TQString, TQString);
+		void pinRequested(TQString);
+
+	public:
+		int initializePkcs();
+		TQString doPinRequest(TQString prompt);
+		void setProvidedPin(TQString pin);
+		void enablePINEntryCallbacks(bool enable);
 
 	public:
 		TDECryptographicCardDevice *cardDevice;
@@ -62,6 +69,9 @@ class CryptoCardDeviceWatcher : public TQObject
 
 	private:
 		bool m_terminationRequested;
+		bool m_pinCallbacksEnabled;
+		TQString m_cardPIN;
+		bool m_cardPINPromptDone;
 #ifdef WITH_PCSC
 		SCARDCONTEXT m_cardContext;
 		SCARD_READERSTATE *m_readerStates;
-- 
cgit v1.2.3

