/*
 * Parse the content of a PFX/PKCS12 file. List certificates and RSA private keys.
 *
 * Copyright (c) 2010 Mounir IDRASSI <mounir.idrassi@idrix.fr>. All rights reserved.
 *
 * 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.
 * 
 */


#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif		

#define _CRT_SECURE_NO_WARNINGS

#include <windows.h>

#include <stdio.h>
#include <string.h>
#include <tchar.h>
#include <io.h>


#pragma comment(lib, "Crypt32")


// Check if the given certificate has the Certificate Sign key usage
BOOL IsCACert(PCCERT_CONTEXT pContext)
{
   BOOL bStatus = FALSE;
   BYTE bKeyUsage;
   DWORD cbSize = 0;

   // Look at the key usage of the certificate
   if (CertGetIntendedKeyUsage(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
                               pContext->pCertInfo,
                               &bKeyUsage,
                               1))
   {
      if (bKeyUsage & CERT_KEY_CERT_SIGN_KEY_USAGE)
      {
         bStatus = TRUE;
      }
   }
   return bStatus;
}

void ReverseBuffer(LPBYTE pbData, DWORD cbData)
{
   DWORD i;
   for (i=0; i<(cbData/2); i++)
   {
      BYTE t = pbData[i];
      pbData[i] = pbData[cbData - 1 - i];
      pbData[cbData - 1 - i] = t;
   }
}

void PrintBuffer(LPBYTE pbData, DWORD cbData)
{
   for (DWORD i=0; i<cbData; i++)
   {
      _tprintf(_T("%.2X"), pbData[i]);
   }
   _tprintf(_T("\n"));
}

// Retreive the private key component from a blob
void DisplayPrivateKey(LPBYTE pbBlob, DWORD cbSize)
{
   LPBYTE pbModulus, pbPrime1, pbPrime2, pbExp1, pbExp2, pbCoeff, pbPriExp;
   DWORD cbModulus, cbPrime1, cbPrime2, cbExp1, cbExp2, cbCoeff, cbPriExp;
   RSAPUBKEY* pRsa = (RSAPUBKEY*) (pbBlob + sizeof(BLOBHEADER));
   LPBYTE pbKeyData = pbBlob + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY);

   cbModulus = (pRsa->bitlen + 7)/8;
   cbPriExp = cbModulus;
   cbPrime1 = cbPrime2 = cbExp1 = cbExp2 = cbCoeff = cbModulus / 2;
   pbModulus = pbKeyData;
   pbPrime1 = pbModulus + cbModulus;
   pbPrime2 = pbPrime1 + cbPrime1;
   pbExp1 = pbPrime2 + cbPrime2;
   pbExp2 = pbExp1 + cbExp1;
   pbCoeff = pbExp2 + cbExp2;
   pbPriExp = pbCoeff + cbCoeff;

   ReverseBuffer(pbModulus, cbModulus);
   ReverseBuffer(pbPrime1, cbPrime1);
   ReverseBuffer(pbPrime2, cbPrime2);
   ReverseBuffer(pbExp1, cbExp1);
   ReverseBuffer(pbExp2, cbExp2);
   ReverseBuffer(pbCoeff, cbCoeff);
   ReverseBuffer(pbPriExp, cbPriExp);

   _tprintf(_T("   => Private Key Details : \n"));
   _tprintf(_T("     => RSA Bit Length = %d\n"), pRsa->bitlen);
   _tprintf(_T("     => Public Exponent = 0x%.8X\n"), pRsa->pubexp);
   _tprintf(_T("     => Modulus = "));
   PrintBuffer(pbModulus, cbModulus);
   _tprintf(_T("     => Private Exponent = "));
   PrintBuffer(pbPriExp, cbPriExp);
   _tprintf(_T("     => P = "));
   PrintBuffer(pbPrime1, cbPrime1);
   _tprintf(_T("     => Q = "));
   PrintBuffer(pbPrime2, cbPrime2);
   _tprintf(_T("     => DP = "));
   PrintBuffer(pbExp1, cbExp1);
   _tprintf(_T("     => DQ = "));
   PrintBuffer(pbExp2, cbExp2);
   _tprintf(_T("     => Coefficient = "));
   PrintBuffer(pbCoeff, cbCoeff);
}


int _tmain(int argc, _TCHAR* argv[])
{
   LPWSTR szPassword = NULL;
   size_t passLen = 0;

   if (argc <= 1 || argc > 3)
   {
      _tprintf(_T("Usage: %s PFX_FILE [password]\n"), argv[0]);
      return 0;
   }

   // Get the password from the command line if any
   if (argc == 3)
   {
#if defined(UNICODE) || defined(_UNICODE)
      szPassword = argv[2];
      passLen = wcslen(argv[2]) + 1;
#else
      passLen = MultiByteToWideChar(CP_ACP, 0, argv[2], -1, NULL, 0);
      szPassword = (LPWSTR) LocalAlloc(0, passLen * sizeof(WCHAR));
      MultiByteToWideChar(CP_ACP, 0, argv[2], -1, szPassword, passLen);
#endif
   }

   // Read the content of the PFX file
   FILE* pfxFile = _tfopen(argv[1], _T("rb"));
   if (!pfxFile)
   {
      _tprintf(_T("Failed to open PFX file for reading\n"));
      return -1;
   }

   long pfxLength = _filelength(_fileno(pfxFile));
   LPBYTE pbPfxData = (LPBYTE) LocalAlloc(0, pfxLength);
   fread(pbPfxData, 1, pfxLength, pfxFile);
   fclose(pfxFile);

   // Decrypt the content of the PFX file
   CRYPT_DATA_BLOB pfxBlob;
   pfxBlob.cbData = pfxLength;
   pfxBlob.pbData = pbPfxData;

   HCERTSTORE hPfxStore = PFXImportCertStore(&pfxBlob, szPassword, CRYPT_EXPORTABLE);
   if (!hPfxStore)
   {
      if (!szPassword)
      {
         // Empty password case. Try with empty string as advised by MSDN
         hPfxStore = PFXImportCertStore(&pfxBlob, L"", CRYPT_EXPORTABLE);
      }
      else if (wcslen(szPassword) == 0)
      {
         // Empty password case. Try with NULL as advised by MSDN
         hPfxStore = PFXImportCertStore(&pfxBlob, NULL, CRYPT_EXPORTABLE);
      }
   }

   if (!hPfxStore)
   {
      _tprintf(_T("Failed to decrypt PFX file content. Please check you typed the correct password"));
      goto ret;
   }

   // Enumerate all certificate on the PFX file
   PCCERT_CONTEXT pCertContext = NULL;
   DWORD dwTotalCertsCount = 0;
   DWORD dwCertsWithKeyCount = 0;
   DWORD cbSize = 0;
   PCRYPT_KEY_PROV_INFO pKeyInfo = NULL;
   LPTSTR szValue = NULL;

   while( (pCertContext = CertEnumCertificatesInStore(hPfxStore, pCertContext)) )
   {
      dwTotalCertsCount++;

      // display certificate
      cbSize = CertNameToStr(X509_ASN_ENCODING, &pCertContext->pCertInfo->Subject, CERT_X500_NAME_STR, NULL, 0);
      szValue = (LPTSTR) LocalAlloc(0, cbSize * sizeof(TCHAR));
      CertNameToStr(X509_ASN_ENCODING, &pCertContext->pCertInfo->Subject, CERT_X500_NAME_STR, szValue, cbSize);

      // Print subject
      if (IsCACert(pCertContext))
         _tprintf(_T("\n%d) CA certificate.\n"), dwTotalCertsCount);
      else
         _tprintf(_T("\n%d) User certificate.\n"), dwTotalCertsCount);
      _tprintf(_T("   => Subject : %s\n"), szValue);
      LocalFree(szValue);

      // Print issuer
      cbSize = CertNameToStr(X509_ASN_ENCODING, &pCertContext->pCertInfo->Issuer, CERT_X500_NAME_STR, NULL, 0);
      szValue = (LPTSTR) LocalAlloc(0, cbSize * sizeof(TCHAR));
      CertNameToStr(X509_ASN_ENCODING, &pCertContext->pCertInfo->Issuer, CERT_X500_NAME_STR, szValue, cbSize);
       _tprintf(_T("   => Issuer  : %s\n"), szValue);
      LocalFree(szValue);
      
      // Check if it has a private key
      if (CertGetCertificateContextProperty(pCertContext, 
              CERT_KEY_PROV_INFO_PROP_ID,
              NULL,
              &cbSize)
         )
      {
         dwCertsWithKeyCount++;
         // Get private key components
         pKeyInfo = (PCRYPT_KEY_PROV_INFO) LocalAlloc(0, cbSize);
         if (CertGetCertificateContextProperty(pCertContext, 
                 CERT_KEY_PROV_INFO_PROP_ID,
                 pKeyInfo,
                 &cbSize)
            )
         {
            // Acquire a context and export the private key
            HCRYPTPROV hProv = NULL;
            HCRYPTKEY hKey = NULL;
            BOOL bStatus = CryptAcquireContextW(&hProv, 
               pKeyInfo->pwszContainerName,
               pKeyInfo->pwszProvName,
               pKeyInfo->dwProvType,
               pKeyInfo->dwFlags);
            if (bStatus)
            {
               bStatus = CryptGetUserKey(hProv, pKeyInfo->dwKeySpec, &hKey);
               if (bStatus)
               {
                  bStatus = CryptExportKey(hKey, NULL, PRIVATEKEYBLOB, 0, NULL, &cbSize);
                  if (bStatus)
                  {
                     LPBYTE pbBlob = (LPBYTE) LocalAlloc(0, cbSize);
                     bStatus = CryptExportKey(hKey, NULL, PRIVATEKEYBLOB, 0, pbBlob, &cbSize);
                     if (bStatus)
                     {
                        // Display the RSA private key
                        DisplayPrivateKey(pbBlob, cbSize);
                     }
                     SecureZeroMemory(pbBlob, cbSize);
                     LocalFree(pbBlob);
                  }

                  CryptDestroyKey(hKey);
               }
               CryptReleaseContext(hProv, 0);

               // Delete the key and its container from disk
               // We don't want the key to be persistant
               CryptAcquireContextW(&hProv, 
                  pKeyInfo->pwszContainerName,
                  pKeyInfo->pwszProvName,
                  pKeyInfo->dwProvType,
                  CRYPT_DELETEKEYSET);
            }
         }

         LocalFree(pKeyInfo);
      }
   }

   _tprintf(_T("\n--------------------------------------------------------\n"));
   _tprintf(_T("Total = %d certificate(s) found\n%d certificate(s) have a private key\n"), dwTotalCertsCount, dwCertsWithKeyCount);

ret:

   if (szPassword)
   {
      SecureZeroMemory(szPassword, passLen * sizeof(wchar_t));
#if !defined(UNICODE) && !defined(_UNICODE)
      LocalFree(szPassword);
#endif
   }

   if (pbPfxData) LocalFree(pbPfxData);
   if (hPfxStore) CertCloseStore(hPfxStore, CERT_CLOSE_STORE_FORCE_FLAG);

   return 0;
}
