summaryrefslogtreecommitdiffstats
path: root/src/cardpincheck.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cardpincheck.c')
-rw-r--r--src/cardpincheck.c330
1 files changed, 330 insertions, 0 deletions
diff --git a/src/cardpincheck.c b/src/cardpincheck.c
new file mode 100644
index 0000000..c79012b
--- /dev/null
+++ b/src/cardpincheck.c
@@ -0,0 +1,330 @@
+/* Cryptographic card PIN check and RSA decryption utility
+ * Copyright (C) 2015 Timothy Pearson <kb9vqf@pearsoncomputing.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <pkcs11-helper-1.0/pkcs11h-certificate.h>
+#include <pkcs11-helper-1.0/pkcs11h-openssl.h>
+
+#define CARD_MAX_LOGIN_RETRY_COUNT 3
+
+char has_plymouth = 0;
+char use_cached_pin = 0;
+char* cached_pin = NULL;
+
+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) {
+ int pos;
+ char *line = NULL;
+ size_t size;
+ ssize_t read;
+
+ if (use_cached_pin && cached_pin) {
+ // Copy PIN to buffer
+ snprintf(pin, pin_max, "%s", cached_pin);
+
+ // Success
+ return 1;
+ }
+
+ // Hide input
+ struct termios oldt;
+ tcgetattr(STDIN_FILENO, &oldt);
+ struct termios newt = oldt;
+ newt.c_lflag &= ~ECHO;
+ tcsetattr(STDIN_FILENO, TCSANOW, &newt);
+
+ if (has_plymouth) {
+ char buffer[1024];
+ snprintf(buffer, 1024, "plymouth ask-for-password --prompt=\"Please enter the PIN for '%s'\"", token->display);
+ system(buffer);
+ }
+ else {
+ fprintf(stderr, "Please enter the PIN for '%s'\n", token->display);
+ }
+ fflush(stdout);
+
+ read = getline(&line, &size, stdin);
+ if ((read < 0) || (read >= pin_max)) {
+ free(line);
+
+ // Abort
+ tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
+ return 0;
+ }
+ else {
+ // Strip newlines
+ pos = 0;
+ while (line[pos] != 0) {
+ if ((line[pos] == '\n') || (line[pos] == '\r')) {
+ line[pos] = 0;
+ break;
+ }
+ pos++;
+ }
+
+ // Copy PIN to cache
+ if (cached_pin) {
+ free(cached_pin);
+ }
+ cached_pin= malloc(sizeof(char) * pin_max);
+ snprintf(cached_pin, pin_max, "%s", line);
+
+ // Copy PIN to buffer
+ snprintf(pin, pin_max, "%s", line);
+ free(line);
+
+ // Success
+ tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
+ return 1;
+ }
+}
+
+static void pkcs_log_hook(IN void * const global_data, IN unsigned flags, IN const char * const format, IN va_list args) {
+ if (!has_plymouth) {
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+ }
+}
+
+int main(int argc, char* argv[]) {
+ CK_RV rv;
+ pkcs11h_certificate_id_list_t issuers;
+ pkcs11h_certificate_id_list_t certs;
+
+ has_plymouth = 0;
+ const char* with_plymount_var = getenv("HAS_PLYMOUTH");
+ if (with_plymount_var && (with_plymount_var[0] == '1')) {
+ has_plymouth = 1;
+ }
+
+ if ((argc < 2) || (argv[1][0] == 0)) {
+ fprintf(stderr, "Usage: ./cardpincheck <opensc provider library> <file to decrypt>\n"
+ "Example: ./cardpincheck /usr/lib/opensc-pkcs11.so\n");
+ return -5;
+ }
+
+ char* opensc_provider_library = argv[1];
+
+ char decryption_requested = 0;
+ char* file_to_decrypt = NULL;
+ if (argc > 2) {
+ decryption_requested = 1;
+ file_to_decrypt = argv[2];
+ }
+
+ fprintf(stderr, "Initializing pkcs11-helper\n");
+ if ((rv = pkcs11h_initialize()) != CKR_OK) {
+ fprintf(stderr, "pkcs11h_initialize failed: %s\n", pkcs11h_getMessage(rv));
+ return -1;
+ }
+
+ fprintf(stderr, "Registering pkcs11-helper hooks\n");
+ if ((rv = pkcs11h_setLogHook(pkcs_log_hook, NULL)) != CKR_OK) {
+ fprintf(stderr, "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) {
+ fprintf(stderr, "pkcs11h_setTokenPromptHook failed: %s\n", pkcs11h_getMessage(rv));
+ return -1;
+ }
+#endif
+
+ if ((rv = pkcs11h_setMaxLoginRetries(CARD_MAX_LOGIN_RETRY_COUNT)) != CKR_OK) {
+ fprintf(stderr, "pkcs11h_setMaxLoginRetries failed: %s\n", pkcs11h_getMessage(rv));
+ return -1;
+ }
+
+ if ((rv = pkcs11h_setPINPromptHook(pkcs_pin_hook, NULL)) != CKR_OK) {
+ fprintf(stderr, "pkcs11h_setPINPromptHook failed: %s\n", pkcs11h_getMessage(rv));
+ return -1;
+ }
+
+ fprintf(stderr, "Adding provider '%s'\n", opensc_provider_library);
+ if ((rv = pkcs11h_addProvider(opensc_provider_library, opensc_provider_library, FALSE, PKCS11H_PRIVATEMODE_MASK_AUTO, PKCS11H_SLOTEVENT_METHOD_AUTO, 0, FALSE)) != CKR_OK) {
+ fprintf(stderr, "pkcs11h_addProvider failed: %s\n", pkcs11h_getMessage(rv));
+ return -1;
+ }
+
+ rv = pkcs11h_certificate_enumCertificateIds(PKCS11H_ENUM_METHOD_CACHE, NULL, PKCS11H_PROMPT_MASK_ALLOW_PIN_PROMPT, &issuers, &certs);
+ if ((rv != CKR_OK) || (certs == NULL)) {
+ fprintf(stderr, "Cannot enumerate certificates: %s\n", pkcs11h_getMessage(rv));
+ return -1;
+ }
+
+ int ret = -1;
+ int i = 0;
+ pkcs11h_certificate_id_list_t cert;
+ pkcs11h_certificate_t certificate = NULL;
+ RSA* rsa_pubkey = NULL;
+ for (cert = certs; cert != NULL; cert = cert->next) {
+ rv = pkcs11h_certificate_create(certs->certificate_id, NULL, PKCS11H_PROMPT_MASK_ALLOW_PIN_PROMPT, PKCS11H_PIN_CACHE_INFINITE, &certificate);
+ if (rv != CKR_OK) {
+ fprintf(stderr, "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;
+ if ((openssl_session = pkcs11h_openssl_createSession(certificate)) == NULL) {
+ fprintf(stderr, "Cannot initialize openssl session to retrieve cryptographic objects\n");
+ pkcs11h_certificate_freeCertificate(certificate);
+ ret = -1;
+ break;
+ }
+
+ // Get certificate data
+ X509* x509_local;
+ x509_local = pkcs11h_openssl_session_getX509(openssl_session);
+ if (!x509_local) {
+ fprintf(stderr, "Cannot get X509 object\n");
+ ret = -1;
+ }
+
+ // Extract public key from X509 certificate
+ EVP_PKEY* x509_pubkey = NULL;
+ x509_pubkey = X509_get_pubkey(x509_local);
+ if (x509_pubkey) {
+ rsa_pubkey = EVP_PKEY_get1_RSA(x509_pubkey);
+ }
+
+ // Check PIN
+ rv = pkcs11h_certificate_ensureKeyAccess(certificate);
+ if (rv != CKR_OK) {
+ if (rv == CKR_CANCEL) {
+ ret = -3;
+ break;
+ }
+ else if ((rv == CKR_PIN_INCORRECT) || (rv == CKR_USER_NOT_LOGGED_IN)) {
+ ret = -2;
+ break;
+ }
+ else {
+ ret = -2;
+ break;
+ }
+ }
+ else {
+ // Success!
+ ret = 0;
+ break;
+ }
+
+ pkcs11h_certificate_freeCertificate(certificate);
+ certificate = NULL;
+
+ i++;
+ }
+
+ if (decryption_requested && (ret == 0)) {
+ // We know the cached PIN is correct; disable any further login prompts
+ use_cached_pin = 1;
+
+ char abort_decryption = 0;
+ if (file_to_decrypt) {
+ long ciphertextfilesize = 0;
+ FILE *ciphertextfile = fopen(file_to_decrypt, "r");
+ if (ciphertextfile) {
+ fseek(ciphertextfile, 0, SEEK_END);
+ ciphertextfilesize = ftell(ciphertextfile);
+ fseek(ciphertextfile, 0, SEEK_SET);
+
+ char* ciphertext = malloc(ciphertextfilesize + 1);
+ fread(ciphertext, ciphertextfilesize, 1, ciphertextfile);
+ fclose(ciphertextfile);
+
+ // Verify minimum size
+ if (ciphertextfilesize < 16) {
+ fprintf(stderr, "Cannot decrypt: ciphertext too small\n");
+ abort_decryption = 1;
+ }
+
+ // Try to get RSA parameters and verify maximum size
+ if (rsa_pubkey) {
+ unsigned int rsa_length = RSA_size(rsa_pubkey);
+ if (ciphertextfilesize > rsa_length) {
+ fprintf(stderr, "Cannot decrypt: ciphertext too large\n");
+ abort_decryption = 1;
+ }
+ }
+
+ if (!abort_decryption) {
+ // Try decryption
+ size_t size = 0;
+
+ // Determine output buffer size
+ rv = pkcs11h_certificate_decryptAny(certificate, CKM_RSA_PKCS, ciphertext, ciphertextfilesize, NULL, &size);
+ if (rv != CKR_OK) {
+ fprintf(stderr, "Cannot determine decrypted message length: %s (%d)\n", pkcs11h_getMessage(rv), rv);
+ if (rv == CKR_CANCEL) {
+ ret = -1;
+ abort_decryption = 1;
+ }
+ else if ((rv == CKR_PIN_INCORRECT) || (rv == CKR_USER_NOT_LOGGED_IN)) {
+ ret = -1;
+ abort_decryption = 1;
+ }
+ else {
+ abort_decryption = 1;
+ }
+ }
+ else {
+ // Decrypt data
+ char* plaintext = malloc(size);
+ rv = pkcs11h_certificate_decryptAny(certificate, CKM_RSA_PKCS, ciphertext, ciphertextfilesize, plaintext, &size);
+ if (rv != CKR_OK) {
+ fprintf(stderr, "Cannot decrypt: %s (%d)\n", pkcs11h_getMessage(rv), rv);
+ if (rv == CKR_CANCEL) {
+ ret = -1;
+ abort_decryption = 1;
+ }
+ else if ((rv == CKR_PIN_INCORRECT) || (rv == CKR_USER_NOT_LOGGED_IN)) {
+ ret = -1;
+ abort_decryption = 1;
+ }
+ }
+ else {
+ // Write decrypted data to stdout
+ fwrite(plaintext, sizeof(char), size, stdout);
+ fflush(stdout);
+ }
+ free(plaintext);
+ }
+ }
+ free(ciphertext);
+ }
+ }
+ }
+ else if (ret == 0) {
+ printf("%s", cached_pin);
+ }
+
+ if (certificate) {
+ pkcs11h_certificate_freeCertificate(certificate);
+ }
+ pkcs11h_certificate_freeCertificateIdList(issuers);
+
+ return ret;
+}