vpnengine/utlpkcs12/src/pkcs12vpn.cpp
changeset 0 33413c0669b9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vpnengine/utlpkcs12/src/pkcs12vpn.cpp	Thu Dec 17 09:14:51 2009 +0200
@@ -0,0 +1,642 @@
+/*
+* Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description:   PKCS#12 data handler
+*
+*/
+
+
+
+#include <x509cert.h>
+#include <mpkcs12.h>
+
+#include <e32std.h>
+
+#include <securityerr.h>
+#include <sysutil.h>
+
+#include "pkcs12vpn.h"
+#include "logvpncommon.h"
+#include "vpnnotifierdefs.h"
+
+///////////////////////////////////
+// Construction and destruction
+
+EXPORT_C CPKCS12Handler* CPKCS12Handler::NewL(RPKIServiceAPI& aPkiServiceApi)
+    {
+    LOG_("-> CPKCS12Handler::NewL()");
+    CPKCS12Handler* self = NewLC(aPkiServiceApi);
+    CleanupStack::Pop(self);
+    LOG_("<- CPKCS12Handler::NewL()");
+    return self;
+    }
+
+EXPORT_C CPKCS12Handler* CPKCS12Handler::NewLC(RPKIServiceAPI& aPkiServiceApi)
+    {
+    LOG_("-> CPKCS12Handler::NewLC()");
+    CPKCS12Handler* self = new (ELeave) CPKCS12Handler(aPkiServiceApi);
+    CleanupStack::PushL(self);
+    self->ConstructL();
+    LOG_("<- CPKCS12Handler::NewLC()");
+    return self;
+    }
+
+
+EXPORT_C CPKCS12Handler* CPKCS12Handler::NewL()
+    {
+    LOG_("-> CPKCS12Handler::NewL(empty)");
+    CPKCS12Handler* self = NewLC();
+    CleanupStack::Pop(self);
+    LOG_("<- CPKCS12Handler::NewL(empty)");
+    return self;
+    }
+
+EXPORT_C CPKCS12Handler* CPKCS12Handler::NewLC()
+    {
+    LOG_("-> CPKCS12Handler::NewLC(empty)");
+    CPKCS12Handler* self = new (ELeave) CPKCS12Handler();
+    CleanupStack::PushL(self);
+    self->ConstructL();
+    LOG_("<- CPKCS12Handler::NewLC(empty)");
+    return self;
+    }
+
+/**
+ * Release resources.
+ * Note: CPKCS12Handler's parent owns the RPKIServiceAPI instance
+ */
+EXPORT_C CPKCS12Handler::~CPKCS12Handler() 
+    {
+    LOG_("-> CPKCS12Handler::~CPKCS12Handler()");
+    iPkcsHandler->Release();
+    iNotifier.Close();
+    delete iPassword;
+    delete iOutputDir;
+
+    iFileOut.Close();
+    iFs.Close();
+
+    LOG_("<- CPKCS12Handler::~CPKCS12Handler()");
+    }
+
+/**
+ * Maintain a reference to an instance of PKI API provided by the caller
+ */
+CPKCS12Handler::CPKCS12Handler(RPKIServiceAPI& aPkiServiceApi) : 
+                iPkiService(&aPkiServiceApi), iDeletable(ETrue)
+    {
+    LOG_("-> CPKCS12Handler::CPKCS12Handler(RPKIServiceAPI&)");
+    LOG_("<- CPKCS12Handler::CPKCS12Handler(RPKIServiceAPI&)");
+    }
+
+CPKCS12Handler::CPKCS12Handler()
+    {
+    LOG_("-> CPKCS12Handler::CPKCS12Handler()");
+    LOG_("<- CPKCS12Handler::CPKCS12Handler()");
+    }
+
+/**
+ * Instantiate S60 PKCS#12 handler upon construction.
+ */
+void CPKCS12Handler::ConstructL()
+    {
+    LOG_("-> CPKCS12Handler::ConstructL()");
+
+    iPkcsHandler = PKCS12Factory::CreateL();
+    User::LeaveIfError(iNotifier.Connect());
+    User::LeaveIfError(iFs.Connect());
+
+    LOG_("<- CPKCS12Handler::ConstructL() OK");
+    }
+
+
+///////////////////////////////////
+// Public methods
+
+EXPORT_C void CPKCS12Handler::SaveSecurityObjectsToDiskL(const TDesC8& aData,
+                                                         const TDesC& aPwd,
+                                                         const TDesC& aDir) 
+    {
+    LOG_("-> CPKCS12Handler::SaveSecurityObjectsToDiskL()");
+
+    if (iOutputDir) 
+        {
+        delete iOutputDir;
+        iOutputDir = NULL;
+        }
+
+    // Output dir needs to end with backslash
+    iOutputDir = aDir.AllocL();
+
+    LOG_1(" Using output dir: '%S'", iOutputDir);
+
+    ExtractSecurityObjectsL(aData, aPwd);
+
+    // Save CA/user certificates to disk
+    SaveCertificatesToDiskL();
+
+    // Save private keys to disk
+    SaveKeysToDiskL();
+
+    LOG_("<- CPKCS12Handler::SaveSecurityObjectsToDiskL()");
+    }
+EXPORT_C void CPKCS12Handler::StorePKCS12ObjectL(const TDesC8& aData, 
+                                                 const TDesC16& aPwd) 
+    {
+    LOG_("-> CPKCS12Handler::StorePKCS12ObjectL()");
+
+    ExtractSecurityObjectsL(aData, aPwd);
+
+    // Store CA certificates to PKI
+    StoreCertificatesL();
+
+    // Store private keys to PKI
+    StoreKeyPairsL();
+
+    // Attach related user certificates to PKI priv keys
+    AttachCertificatesL();
+
+    LOG_("<- CPKCS12Handler::StorePKCS12ObjectL() OK");
+    }
+
+EXPORT_C void CPKCS12Handler::SetApplicability(const RArray<TUid>& aUids) 
+    {
+    LOG_("-> CPKCS12Handler::SetApplicability()");
+    iApplications = &aUids;
+    LOG_("<- CPKCS12Handler::SetApplicability()");
+    }
+
+EXPORT_C void CPKCS12Handler::SetDeletable(TBool aDeletable) 
+    {
+    LOG_1("-> CPKCS12Handler::SetDeletable() Deletable: %d", aDeletable);
+    iDeletable = aDeletable;
+    LOG_("<- CPKCS12Handler::SetDeletable()");
+    }
+
+
+
+
+///////////////////////////////////
+// Internal methods
+
+void CPKCS12Handler::ExtractSecurityObjectsL(const TDesC8& aData, 
+                                             const TDesC16& aPwd) 
+    {
+    LOG_("-> CPKCS12Handler::ExtractSecurityObjectsL()");
+    if (iPassword) 
+        {
+        delete iPassword;
+        iPassword = NULL;
+        }
+
+    // Make sure the data is in PKCS#12 format
+    if (!VerifyType(aData)) 
+        {
+        LOG_("<- CPKCS12Handler::ExtractSecurityObjectsL() LEAVE (KErrNotSupported)");
+        User::Leave(KErrArgument);
+        }
+        
+    // If we have been provided with a valid password, then proceed
+    // to decrypt / parse; otherwise, prompt for pwd.
+    if (aPwd.Length() > 0)
+        {
+        iPassword = aPwd.AllocL();
+        LOG_(" Password provided by OMADM...");
+        }
+    else 
+        {
+        LOG_(" No password provided, prompting the user for one");
+
+        iPassword = QueryPasswordL();
+        }
+
+    // Keep asking for the password until user cancels or inputs the
+    // correct password
+    while (ETrue) 
+        {
+        if (iPassword)
+            {
+            LOG_1(" Non-NULL password '%S' in use, decrypting", iPassword);
+            TRAPD(err, iPkcsHandler->ParseL(aData, *iPassword));
+            if (err != KErrNone) 
+                {
+                LOG_(" Breaking news: Password proved a miserable failure! Program terminated abruptly!");
+                DisplayWrongPasswordNote();
+                delete iPassword;
+                iPassword = NULL;
+                iPassword = QueryPasswordL();
+                }
+            else 
+                {
+                // Correct password provided by the user, 
+                // break free from the vicious cycle.
+                delete iPassword;
+                iPassword = NULL;
+                break;
+                }
+            }
+        else
+            {
+            // User got tired of guessing and resorted to cancel
+            LOG_("<- CPKCS12Handler::ExtractSecurityObjectsL() LEAVE (KErrCancel)");
+            User::Leave(KErrBadPassphrase);
+            }
+        }
+
+    // Fetch references to keys and certs
+    ExtractKeysAndCerts();
+
+    LOG_("<- CPKCS12Handler::ExtractSecurityObjectsL()");
+    }
+
+void CPKCS12Handler::SaveCertificatesToDiskL() 
+    {
+    LOG_("-> CPKCS12Handler::SaveCertificatesToDiskL()");
+
+    TInt certCount(0);
+
+    // first the CAs (if any; not required necessarily)
+    if (iCACerts) 
+        {
+        certCount = iCACerts->Count();
+        LOG_1(" Saving %d CA Certificates", certCount);
+
+        for (TInt i = 0; i < certCount; i++)
+            {
+            CX509Certificate* cert = iCACerts->At(i);
+
+            // Generate filename with running identifier
+            // Use TFileName, because the function isn't stack
+            // critical
+            TFileName fn;
+            fn.Format(KCAFileNameStem(), i+1);
+
+            WriteToFileL(cert->Encoding(), fn);
+            }
+        }
+    LOG(else LOG_(" No CA Certs found!"));
+
+    // Then the user certs
+    if (iUserCerts)
+        {
+        certCount = iUserCerts->Count();
+        LOG_1(" Saving %d User Certificates", certCount);
+
+        for (TInt i = 0; i < certCount; i++) 
+            {
+            CX509Certificate* cert = iUserCerts->At(i);
+
+            TFileName fn;
+            fn.Format(KUserCertFileNameStem(), i+1);
+
+            WriteToFileL(cert->Encoding(), fn);
+            }
+        }
+
+    LOG(else LOG_(" No User Certs found!"));
+
+    LOG_("<- CPKCS12Handler::SaveCertificatesToDiskL()");
+    }
+
+void CPKCS12Handler::SaveKeysToDiskL() 
+    {
+    LOG_("-> CPKCS12Handler::SaveKeysToDiskL()");
+
+    if (iPrivKeys) 
+        {
+        TInt keycount = iPrivKeys->Count();
+        LOG_1(" Saving %d Private Keys", keycount);
+
+        for (TInt i = 0; i < keycount; i++) 
+            {
+            HBufC8* key = iPrivKeys->At(i);
+            TPtrC8 keyPtr = *key;
+
+            TFileName fn;
+            fn.Format(KPrivateKeyFileNameStem(), i+1);
+
+            WriteToFileL(keyPtr, fn);
+            }
+        }
+    LOG(else LOG_(" No Private Keys found!"));
+
+    LOG_("<- CPKCS12Handler::SaveKeysToDiskL()");
+    }
+
+// Note: directory needs to end with a backslash
+// writes binary data
+void CPKCS12Handler::WriteToFileL(const TDesC8& aData, 
+                                  const TDesC& aFileName) 
+    {
+    LOG_("-> CPKCS12Handler::WriteToFileL()");
+
+    ASSERT(iOutputDir);
+    ASSERT(aFileName.Length() > 0);
+
+    // Disk space criticality check before attempting
+    // to install
+    if (SysUtil::FFSSpaceBelowCriticalLevelL(0, 0)) 
+        {
+        User::Leave(KErrDiskFull);
+        }
+
+    TFileName fn;
+    fn.Append(*iOutputDir);
+    fn.Append(aFileName);
+
+    LOG_1(" Opening file: '%S'", &fn);
+
+    TInt ret = iFileOut.Replace(iFs, fn, EFileWrite|EFileShareExclusive|EFileStream);
+    
+    LOG_1(" File open result: %d", ret);
+
+    if (ret != KErrNone) 
+        {
+        User::Leave(ret);
+        }
+
+    LOG_(" Writing data");
+
+    ret = iFileOut.Write(aData);
+    LOG_1(" Write result: %d", ret);
+
+    ret = iFileOut.Flush();
+    LOG_1(" Flush result: %d", ret);
+    
+    iFileOut.Close();
+    
+    LOG_("<- CPKCS12Handler::WriteToFileL()");
+    }
+
+TBool CPKCS12Handler::VerifyType(const TDesC8& aData) const
+    {
+    ASSERT(iPkcsHandler);
+
+    LOG_("-> CPKCS12Handler::VerifyType()");
+
+    TBool isPKCS12(EFalse);
+
+    // Need to check the data length before IsPKCS12Data call,
+    // otherwise an assert (instead of a more suitable) 
+    // might occur
+    if (aData.Length() >= KPKCS12DataMinLength) 
+        {
+        isPKCS12 = iPkcsHandler->IsPKCS12Data(aData);
+        }
+
+    LOG_1("<- CPKCS12Handler::VerifyType() RET: %d", isPKCS12);
+    return isPKCS12;
+    }
+
+void CPKCS12Handler::StoreKeyPairsL()
+    {
+    LOG_("-> CPKCS12Handler::StoreKeyPairsL()");
+    
+    TInt keycount = iPrivKeys->Count();
+    
+    for (TInt i = 0; i < keycount; i++) 
+        {
+        HBufC8* key = iPrivKeys->At(i);
+        TPtrC8 keyPtr = *key;
+        StoreSingleKeyL(*key);
+        }
+    
+    LOG_("<- CPKCS12Handler::StoreKeyPairsL() OK");
+    }
+
+/**
+ * NOTE: It should be decided what to do in following cases:
+ * 1. Key storage operation fails for a key (there can be multiple keys 
+ *    within a PKCS#12 package)
+ * 2. User cert attachment fails for a key (there can be multiple user 
+ *    certificates for any given key)
+ *
+ * At the moment, the code leaves if anything unexpected occurs.
+ * There is no rollback mechanism (anything that was added before
+ * the failure will still be in PKI stores).
+ *
+ */
+void CPKCS12Handler::StoreSingleKeyL(const TDesC8& aKey) 
+    {
+    LOG_("-> CPKCS12Handler::StoreSingleKeyL()");
+    
+    // Setup initial values
+    TRequestStatus requestStatus;
+    TPKIKeyIdentifier keyId;
+    
+    // Perform asynchronous PKI operation synchronously
+    iPkiService->StoreKeypair(keyId, aKey, requestStatus);
+
+    User::WaitForRequest(requestStatus);
+
+    // Check for operation status    
+    TInt status = requestStatus.Int();
+    if (status != KErrNone)
+        {
+        LOG_1("<- CPKCS12Handler::StoreSingleKeyL() LEAVE (%d)", status);
+        User::Leave(status);
+        }
+
+    LOG_("<- CPKCS12Handler::StoreSingleKeyL() OK");
+    }
+
+
+void CPKCS12Handler::AttachCertificatesL() 
+    {
+    LOG_("-> CPKCS12Handler::AttachCertificatesL()");
+    TInt certCount = iUserCerts->Count();
+    for (TInt i = 0; i < certCount; i++) 
+        {
+        CX509Certificate* cert = iUserCerts->At(i);
+        TKeyIdentifier certKeyId = cert->KeyIdentifierL();
+        
+        // Note: KeyID parameter is effectively redundant
+        // (it can always be fetched from CX509Certificate object)
+        AttachCertificateL(cert, certKeyId);
+
+        }
+    LOG_("<- CPKCS12Handler::AttachCertificatesL() OK");
+    }
+
+void CPKCS12Handler::StoreCertificatesL() 
+    {
+    LOG_("-> CPKCS12Handler::StoreCertificatesL()");
+    TInt certCount = iCACerts->Count();
+    for (TInt i = 0; i < certCount; i++)
+        {
+        CX509Certificate* cert = iCACerts->At(i);
+        StoreCertificateL(cert);
+        }
+    LOG_("<- CPKCS12Handler::StoreCertificatesL() OK");
+    }
+
+void CPKCS12Handler::StoreCertificateL(CX509Certificate* aCert)
+    {
+
+    LOG_("-> CPKCS12Handler::StoreCertificateL()");
+
+    LOG_1(" Deletable: %d", iDeletable);
+
+    TInt status = iPkiService->StoreCertificate(EPKICACertificate,
+                                                iDeletable,
+                                                0,
+                                                EPKIRSA,
+                                                aCert->Encoding());
+
+    if (status) 
+        {
+        LOG_1("<- CPKCS12Handler::StoreCertificateL() LEAVE (%d)", status);
+        User::Leave(status);
+        }
+
+    SetApplicabilityL(aCert);
+
+    LOG_("<- CPKCS12Handler::StoreCertificateL() OK");
+    }
+
+
+void CPKCS12Handler::AttachCertificateL(CX509Certificate* aCert, 
+                                        const TPKIKeyIdentifier& aKeyId)
+    {
+
+    LOG_("-> CPKCS12Handler::AttachCertificateL()");
+
+    TRequestStatus requestStatus;
+    TAny* resArray(NULL);
+
+    // Perform asynchronous PKI operation synchronously
+    iPkiService->AttachCertificateL(aKeyId, DEFAULT_KEY_LEN, EPKIRSA, 
+                                    aCert->Encoding(), &resArray, 
+                                    requestStatus);
+
+    User::WaitForRequest(requestStatus);
+    iPkiService->Finalize(resArray);
+
+    // Check for operation status    
+    TInt status = requestStatus.Int();
+    if (status != KErrNone)
+        {
+        LOG_1("<- CPKCS12Handler::AttachCertificateL() LEAVE: %d", status);
+        User::Leave(status);
+        }
+
+    LOG_("<- CPKCS12Handler::AttachCertificateL() OK");
+    }
+
+/**
+ * Not in use currently -- is Applicability a meaningful parameter
+ * for user certificates?
+ */
+void CPKCS12Handler::SetApplicabilityL(CX509Certificate* aCert) 
+    {
+    LOG_("-> CPKCS12Handler::SetApplicabilityL()");
+
+    if (aCert) 
+        {
+        // Only set applicability if there is atleast one applicability
+        // setting defined
+        if (iApplications && iApplications->Count() > 0)
+            {
+            LOG_(" Resolving subject  name");
+
+            // Use subject name for CA certs
+            const TPtrC8* issuerName = 
+                aCert->DataElementEncoding(CX509Certificate::EIssuerName);
+            
+            LOG_(" Resolving serial number");
+            const TPtrC8* serialNumber = 
+                aCert->DataElementEncoding(CX509Certificate::ESerialNumber);
+            
+            LOG_(" Issuing PKI call");
+            iPkiService->SetApplicabilityL(*issuerName, *serialNumber, *iApplications);
+            }
+        }
+    else 
+        {
+        LOG_("<- CPKCS12Handler::SetApplicabilityL() Leave: NULL argument");
+        User::Leave(KErrArgument);
+        }
+
+    LOG_("<- CPKCS12Handler::SetApplicabilityL() OK");
+    }
+
+void CPKCS12Handler::ExtractKeysAndCerts() 
+    {
+    ASSERT(iPkcsHandler);
+    iCACerts = &iPkcsHandler->CACertificates();
+    iUserCerts = &iPkcsHandler->UserCertificates();
+    iPrivKeys = &iPkcsHandler->PrivateKeys();
+    }
+
+HBufC* CPKCS12Handler::QueryPasswordL()
+    {
+    LOG_("-> CPKCS12Handler::QueryPasswordL()");
+    TRequestStatus status(KErrNone);
+    LOG_(" SANITY2");
+
+    HBufC* ret(NULL);
+
+    TVpnDialogInfo dialogInfo(TVpnDialog::EPKCS12Password, 0);
+
+    TPckgBuf<TVpnDialogInfo>                dialogInfoDes;
+    TPckgBuf<TVpnDialogOutput>              dialogResponseDes;
+
+
+    dialogInfoDes() = dialogInfo;
+
+    iNotifier.StartNotifierAndGetResponse(status, KUidVpnDialogNotifier,
+                                          dialogInfoDes, dialogResponseDes);
+
+    // Wait until user has given the input
+    LOG_(" Waiting for request");
+    User::WaitForRequest(status);
+    LOG_1(" Dialog terminated with status: %d", status.Int());
+    
+    if (status != KErrCancel && dialogResponseDes().iOutBuf.Length() >= 0)
+        {
+        LOG_(" Allocating");
+        ret = dialogResponseDes().iOutBuf.AllocL();
+        LOG_(" Canceling");
+        iNotifier.CancelNotifier(KUidVpnDialogNotifier);
+        LOG_1("<- CPKCS12Handler::QueryPasswordL() return: '%S'", ret);
+        }
+    else 
+        {
+        LOG_("<- CPKCS12Handler::QueryPasswordL() return: NULL");
+        }
+
+    return ret;
+    }
+ 
+void CPKCS12Handler::DisplayWrongPasswordNote() 
+    {
+    LOG_("-> CPKCS12Handler::DisplayWrongPasswordNote()");
+    TRequestStatus status(KErrNone);
+
+    LOG_1(" Constructing dialoginfo, DID: %d", 
+            TVpnNoteDialog::EVpnWrongPKCS12Password);
+
+    TIPSecDialogInfo info(TNoteDialog::EInfo, 
+                          TVpnNoteDialog::EVpnWrongPKCS12Password); //create the input information
+    TPckgBuf<TIPSecDialogInfo> infoBuf(info); //package it in appropriate buf
+    TPckgBuf<TIPSecDialogOutput> responseBuf; //create the buf to receive the response
+    
+    iNotifier.StartNotifierAndGetResponse(status, KUidVpnDialogNotifier,
+                                          infoBuf, responseBuf);
+    User::WaitForRequest(status);
+
+    iNotifier.CancelNotifier(KUidVpnDialogNotifier);
+
+    LOG_("<- CPKCS12Handler::DisplayWrongPasswordNote()");
+    }
+