javacommons/security/src/midpauthenticationmoduleimpl.cpp
branchRCL_3
changeset 19 04becd199f91
child 46 4376525cdefb
child 49 35baca0e7a2e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javacommons/security/src/midpauthenticationmoduleimpl.cpp	Tue Apr 27 16:30:29 2010 +0300
@@ -0,0 +1,613 @@
+/*
+* Copyright (c) 2007 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:
+*
+*/
+
+
+#include "javajniutils.h"
+#include "com_nokia_mj_impl_security_midp_authentication_AuthenticationModule.h"
+#include "midpauthenticationmodule.h"
+#include "midpauthenticationmoduleimpl.h"
+#include "storagehandler.h"
+#include "securityutils.h"
+#include "javacertstorehandler.h"
+#include "securitycommsmessagedefs.h"
+#include "javastorage.h"
+#include "javastoragenames.h"
+#include "logger.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/x509_vfy.h>
+#include <openssl/asn1.h>
+#include <openssl/err.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+#include <sys/time.h>
+#include <string.h>
+
+using namespace java::security;
+using namespace java::storage;
+using namespace java::util;
+using namespace std;
+
+/* The DER encoding of the algorithm ID for the SHA-1 hash function */
+const char SHA_1_ALG_FOOTPRINT[] =
+    {0x30,0x21,0x30,0x09,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x05,0x00,0x04,0x14};
+
+/* forward declarations of local/private methods */
+static int verify_callback(int, X509_STORE_CTX *);
+int getErrCode(int);
+int verifyCertChain(char **, int, const unsigned char *, int, vector<string> CAs, char *, char *, CERT_DETAILS*);
+
+JNIEXPORT jobjectArray JNICALL Java_com_nokia_mj_impl_security_midp_authentication_AuthenticationModule__1validateChainsAndSignatures
+(JNIEnv * env, jobject, jobjectArray authInfos)
+{
+    // get the roots from JavaCertStore
+    vector<string> CAs;
+    JavaCertStoreHandler::retrieveRootsContents(CAs);
+    if (CAs.size() == 0)
+    {
+        SecurityUtils::throw_exception(env, "CONNECTION_TO_CAPTAIN_FAILED");
+        return NULL;
+    }
+
+    // validate each of the chains&signatures and
+    jint len = env->GetArrayLength(authInfos);
+    vector<AUTH_CREDENTIALS*> all_auth_credentials;
+    all_auth_credentials.reserve(len);
+    AUTH_CREDENTIALS * auth_credentials = NULL;
+    CERT_DETAILS* details = NULL;
+    AUTH_INFO* authInfo = NULL;
+    char * jar_hash = NULL;
+    char * root_hash = NULL;
+    int validation_result = KDefault;
+    for (int i=0; i<len; i++)
+    {
+        // validate the chain
+        authInfo = new AUTH_INFO();
+        SecurityUtils::getAuthInfo(env, authInfos, i, authInfo);
+        jar_hash = new char[2*SHA_1_DIGEST_LEN + 1];
+        jar_hash[0] = '\0';
+        root_hash = new char[MD5_DIGEST_LEN + 1];
+        root_hash[0] = '\0';
+        details = new CERT_DETAILS();
+        int chain_verification_result = verifyCertChain(
+                                            authInfo->cert_chain, authInfo->cert_chain_len,
+                                            (const unsigned char *)authInfo->signature,
+                                            authInfo->signature_len, CAs, jar_hash,
+                                            root_hash, details);
+        if (chain_verification_result == KCertAndSignatureOk)
+        {
+            validation_result = KCertAndSignatureOk;
+            auth_credentials = new AUTH_CREDENTIALS();
+            auth_credentials->jar_hash = new char[2*SHA_1_DIGEST_LEN + 1];
+            auth_credentials->root_hash = new char[MD5_DIGEST_LEN + 1];
+            memmove(auth_credentials->jar_hash, jar_hash, 2*SHA_1_DIGEST_LEN + 1);
+            memmove(auth_credentials->root_hash, root_hash, MD5_DIGEST_LEN + 1);
+            auth_credentials->chain_index = i+1;
+            auth_credentials->signing_cert = details;
+            all_auth_credentials.push_back(auth_credentials);
+            auth_credentials->predefined_domain_category = details->domain_category;
+            // the domain info is coming shortly
+        }
+        else
+        {
+            delete details;
+            details = NULL;
+            delete[] jar_hash;
+            jar_hash = NULL;
+            delete[] root_hash;
+            root_hash = NULL;
+            // just record the failure of the chain validation
+            if (chain_verification_result > validation_result)
+            {
+                validation_result = chain_verification_result;
+            }
+        }
+        // release the elements
+        delete[] authInfo->signature;
+        authInfo->signature = NULL;
+        for (int j=0 ; j<authInfo->cert_chain_len; j++)
+        {
+            delete[] authInfo->cert_chain[j];
+            authInfo->cert_chain[j] = NULL;
+        }
+        delete[] authInfo->cert_chain;
+        authInfo->cert_chain = NULL;
+
+        // free memory
+        delete authInfo;
+        authInfo = NULL;
+    }
+    // release the CAs
+    CAs.clear();
+
+    // analyze the result of authentication
+    switch (validation_result)
+    {
+    case KDefault:
+        // there were no chains to be validated
+        return NULL;
+    case KCertValidationFailure:
+        SecurityUtils::throw_exception(env, "CERT_VERIFICATION_FAILED");
+        return NULL;
+    case KSignatureVerificationFailure:
+        SecurityUtils::throw_exception(env, "SIG_VERIFICATION_FAILED");
+        return NULL;
+    case KMissingRoot:
+        SecurityUtils::throw_exception(env, "MISSING_ROOT");
+        return NULL;
+    case KCertNotYetValidFailure:
+        SecurityUtils::throw_exception(env, "CERT_NOT_YET_VALID");
+        return NULL;
+    case KCertExpiredFailure:
+        SecurityUtils::throw_exception(env, "CERT_EXPIRED");
+        return NULL;
+    case KSelfSignedCertInChainFailure:
+        SecurityUtils::throw_exception(env, "ROOT_CERT_IN_CHAIN");
+        return NULL;
+    case KUnknownExtendedKeyUsage:
+        SecurityUtils::throw_exception(env, "UNKNOWN_EXT_KEY_USAGE");
+        return NULL;
+    }
+
+    // try to fill in the domain info for each of the existing credentials
+    int i=0;
+    int domain_mappings = 0;
+    while (i < all_auth_credentials.size())
+    {
+        // init the domain name and category
+        all_auth_credentials[i]->domain_name = NULL;
+        all_auth_credentials[i]->domain_category = NULL;
+        std::string protection_domain_name;
+        std::string protection_domain_category;
+        JavaCertStoreHandler::retrieveRootProtDomainInfo(
+            all_auth_credentials[i]->root_hash,
+            protection_domain_name,
+            protection_domain_category);
+        if (strcmp(protection_domain_name.c_str(),""))
+        {
+            // DeveloperCertificates: if domain_category is manufacturer and we have predefined_domain_category use the predefined one
+            // Get the domain constants from ApplicationInfo&policy
+            if ((strcmp(protection_domain_category.c_str(),"MFD") == 0
+                    && all_auth_credentials[i]->predefined_domain_category == DEVCERT_UNKNOWN_DOMAIN)
+                    || (strcmp(protection_domain_category.c_str(),"OPD") == 0
+                        && all_auth_credentials[i]->predefined_domain_category == DEVCERT_UNKNOWN_DOMAIN))
+            {
+                i++;
+                continue;
+            }
+            if ((strcmp(protection_domain_category.c_str(),"MFD") == 0
+                    && (all_auth_credentials[i]->predefined_domain_category == DEVCERT_OPERATOR_DOMAIN
+                        || all_auth_credentials[i]->predefined_domain_category == DEVCERT_IDENTIFIEDTHIRDPARTY_DOMAIN))
+                    || (strcmp(protection_domain_category.c_str(),"OPD") == 0
+                        && all_auth_credentials[i]->predefined_domain_category == DEVCERT_IDENTIFIEDTHIRDPARTY_DOMAIN))
+            {
+                switch (all_auth_credentials[i]->predefined_domain_category)
+                {
+                case DEVCERT_OPERATOR_DOMAIN:
+                    all_auth_credentials[i]->domain_name = new char[strlen("Operator") + 1];
+                    strcpy(all_auth_credentials[i]->domain_name,"Operator");
+                    all_auth_credentials[i]->domain_category = new char[strlen("OPD") + 1];
+                    strcpy(all_auth_credentials[i]->domain_category,"OPD");
+                    break;
+                case DEVCERT_IDENTIFIEDTHIRDPARTY_DOMAIN:
+                    all_auth_credentials[i]->domain_name = new char[strlen("IdentifiedThirdParty") + 1];
+                    strcpy(all_auth_credentials[i]->domain_name,"IdentifiedThirdParty");
+                    all_auth_credentials[i]->domain_category = new char[strlen("ITPD") + 1];
+                    strcpy(all_auth_credentials[i]->domain_category,"ITPD");
+                    break;
+                }
+            }
+            else
+            {
+                all_auth_credentials[i]->domain_name = new char[protection_domain_name.size() + 1 /* for the \n */];
+                strcpy(all_auth_credentials[i]->domain_name,protection_domain_name.c_str());
+                all_auth_credentials[i]->domain_category = new char[protection_domain_category.size() + 1 /* for the \n */];
+                strcpy(all_auth_credentials[i]->domain_category,protection_domain_category.c_str());
+            }
+            domain_mappings++;
+        }
+        i++;
+    }
+
+    // if no protection domain was found -> throw corresponding exception
+    if (domain_mappings == 0)
+    {
+        SecurityUtils::throw_exception(env, "MISSING_DOMAIN_MAPPING");
+        return NULL;
+    }
+
+    // send the response
+    return SecurityUtils::getJNIAuthCredentials(env, all_auth_credentials);
+}
+
+JNIEXPORT jstring JNICALL Java_com_nokia_mj_impl_security_midp_authentication_AuthenticationModule__1computeHash
+(JNIEnv * env, jobject, jstring appJARPath)
+{
+    jboolean isCopy;
+    const char* app_jar_path(env->GetStringUTFChars(appJARPath, &isCopy));
+    char * jar_hash_value = SecurityUtils::computeDigest(app_jar_path);
+    env->ReleaseStringUTFChars(appJARPath, app_jar_path);
+    if (jar_hash_value != NULL)
+    {
+        jstring hash = env->NewStringUTF(jar_hash_value);
+        delete[] jar_hash_value;
+        jar_hash_value = NULL;
+        return hash;
+    }
+    return NULL;
+}
+
+JNIEXPORT jobject JNICALL Java_com_nokia_mj_impl_security_midp_authentication_AuthenticationModule__1parseCertificate
+(JNIEnv * env, jobject, jstring rawCertificate)
+{
+    int len = env->GetStringLength(rawCertificate);
+    jboolean isCopy;
+    const char* raw_cert(env->GetStringUTFChars(rawCertificate, &isCopy));
+    if (len == 0 || raw_cert == NULL)
+    {
+        return NULL;
+    }
+    char * encoded_cert = SecurityUtils::encodePEM(raw_cert, len);
+    if (encoded_cert == NULL)
+    {
+        return NULL;
+    }
+    env->ReleaseStringUTFChars(rawCertificate, raw_cert);
+    X509 * x509_cert = SecurityUtils::readCert(encoded_cert, strlen(encoded_cert), PEM);
+    delete[] encoded_cert;
+    encoded_cert = NULL;
+    if (x509_cert != NULL)
+    {
+        CERT_DETAILS * details = new CERT_DETAILS();
+        SecurityUtils::getCertDetails(*x509_cert, details, false /* don't parse domain info */);
+        delete x509_cert;
+        x509_cert = NULL;
+        jobject cert_details = SecurityUtils::getJNICertDetails(env, *details);
+        delete[] details->issuer;
+        details->issuer = NULL;
+        delete[] details->subject;
+        details->subject = NULL;
+        delete[] details->organization;
+        details->organization = NULL;
+        delete[] details->notBefore;
+        details->notBefore = NULL;
+        delete[] details->notAfter;
+        details->notAfter = NULL;
+        delete[] details->serial_number;
+        details->serial_number = NULL;
+        delete[] details->fingerprint;
+        details->fingerprint = NULL;
+        delete details;
+        details = NULL;
+        return cert_details;
+    }
+    return NULL;
+}
+
+JNIEXPORT jobject JNICALL Java_com_nokia_mj_impl_security_midp_authentication_AuthenticationModule__1getRootCertificate
+(JNIEnv * env, jobject, jstring jRootHash)
+{
+    // get the certificate content from JavaCertStore
+    std::wstring rootHash = JniUtils::jstringToWstring(env, jRootHash);
+    std::string rootContent;
+    long long len;
+    JavaCertStoreHandler::retrieveRootContent(rootHash, &len, rootContent);
+    X509* root = SecurityUtils::readCert((char *)rootContent.c_str(), len  , DER);
+    if (root != NULL)
+    {
+        CERT_DETAILS * details = new CERT_DETAILS();
+        SecurityUtils::getCertDetails(*root, details, false /* don't parse domain info */);
+        delete root;
+        root = NULL;
+        jobject root_details = SecurityUtils::getJNICertDetails(env, *details);
+        delete[] details->issuer;
+        details->issuer = NULL;
+        delete[] details->subject;
+        details->subject = NULL;
+        delete[] details->organization;
+        details->organization = NULL;
+        delete[] details->notBefore;
+        details->notBefore = NULL;
+        delete[] details->notAfter;
+        details->notAfter = NULL;
+        delete[] details->serial_number;
+        details->serial_number = NULL;
+        delete[] details->fingerprint;
+        details->fingerprint = NULL;
+        delete details;
+        details = NULL;
+        return root_details;
+    }
+
+    // return the prased cert to java
+    return NULL;
+}
+
+OS_EXPORT void MIDPAuthenticationModuleImpl::getCertChains(
+    const Uid& aUid,
+    std::list<std::string>& aChains)
+{
+    auto_ptr<StorageHandler> storageHandler(new StorageHandler());
+    list<int> indexes;
+    storageHandler->readValidCerts(aUid, indexes);
+
+    JavaStorageEntry attr;
+    JavaStorageApplicationEntry_t entry;
+
+    attr.setEntry(ID, aUid.toString());
+    entry.insert(attr);
+
+    auto_ptr<JavaStorage> js(JavaStorage::createInstance());
+    JavaStorageApplicationList_t foundApps;
+
+    try
+    {
+        js->open();
+        js->search(APPLICATION_PACKAGE_ATTRIBUTES_TABLE, entry, foundApps);
+        js->close();
+    }
+    catch (JavaStorageException& aJse)
+    {
+        ELOG1(EJavaStorage, "CertChains: %s", aJse.toString().c_str());
+    }
+
+    entry.clear();
+
+    list<int>::const_iterator iter;
+
+    for (iter = indexes.begin(); iter != indexes.end(); iter++)
+    {
+
+        string chain("");
+        storageHandler->getChainFromIndex(foundApps, (*iter), chain);
+
+        if (chain.size() > 0)
+        {
+            aChains.push_back(chain);
+        }
+    }
+}
+
+MIDPAuthenticationModuleImpl::MIDPAuthenticationModuleImpl()
+{
+}
+
+int verifyCertChain(char **cert_chain, int no_certs,
+                    const unsigned char * sig, int sig_len,
+                    vector<string> CAs, char * jar_hash,
+                    char * root_hash, CERT_DETAILS* details)
+{
+    X509 *end_entity_cert;
+    X509_STORE_CTX *x509_ctx = NULL;
+    X509_STORE *x509_store = NULL;
+    STACK_OF(X509) *validated_certs_st = sk_X509_new_null();
+    STACK_OF(X509) *roots_certs_st = sk_X509_new_null();
+    RSA * rsakey = NULL;
+    unsigned char * plainSig = NULL;
+    int ret_code = KCertAndSignatureOk;
+
+    // Calling OpenSSL_add_all_algorithms() links in all algorithms: as a result a statically linked executable can be quite large. Pick up the supported algorithms only
+    OpenSSL_add_all_algorithms();
+
+    // get the end entity certificate
+    end_entity_cert = SecurityUtils::readCert(cert_chain[0], strlen(cert_chain[0]), PEM);
+
+    while (true)
+    {
+        // add certs 1.. into the STACK
+        for (int i=1; i<no_certs; i++)
+        {
+            X509 *x = SecurityUtils::readCert(cert_chain[i], strlen(cert_chain[i]), PEM);
+            if (!x)
+            {
+                ret_code = getErrCode(ERR_get_error());
+                break;
+            }
+            sk_X509_push(validated_certs_st,x);
+        }
+
+        // create the cerificate storing object
+        x509_store = X509_STORE_new();
+        if (!(x509_store))
+        {
+            ret_code = getErrCode(ERR_get_error());
+            break;
+        }
+
+        // load the CAs
+        for (int i=0; i<CAs.size(); i++)
+        {
+            X509 *x = SecurityUtils::readCert((char *)CAs[i].c_str(), CAs[i].size(), DER);
+            sk_X509_push(roots_certs_st,x);
+            X509_STORE_add_cert(x509_store, x);
+        }
+
+        // create and initialize X509 vertification context
+        x509_ctx = X509_STORE_CTX_new();
+        if (!(x509_ctx))
+        {
+            ret_code = getErrCode(ERR_get_error());
+            break;
+        }
+
+        if (X509_STORE_CTX_init(x509_ctx, x509_store, end_entity_cert, validated_certs_st)
+                != 1)
+        {
+            ret_code = getErrCode(ERR_get_error());
+            break;
+        }
+
+        struct timeval tv;
+        int i = gettimeofday(&tv, NULL);
+        X509_STORE_CTX_set_time(x509_ctx, X509_V_FLAG_USE_CHECK_TIME, tv.tv_sec);
+        // set the callback for validation - needed for the critical extension
+        // used by developer certificates
+        X509_STORE_CTX_set_verify_cb(x509_ctx, verify_callback);
+
+        // verify certificate
+        if (X509_verify_cert(x509_ctx) != 1)
+        {
+            ret_code = getErrCode(X509_STORE_CTX_get_error(x509_ctx));
+            break;
+        }
+        // verify the extended key usage: it must point to id-kp-codeSigning (RFC3280 code signing)
+        // or 1.3.6.1.4.1.94.1.49.1.2.2.3 (Nokia Java Code Signing Extension)
+        EXTENDED_KEY_USAGE *extKeyUsage;
+        if ((extKeyUsage=(EXTENDED_KEY_USAGE*)X509_get_ext_d2i(end_entity_cert, NID_ext_key_usage, NULL, NULL)) != NULL)
+        {
+            bool extKeyUsageKnown = false;
+            char EXT_KEY_USAGE_OID[80];
+            for (i = 0; i < sk_ASN1_OBJECT_num(extKeyUsage); i++)
+            {
+                ASN1_OBJECT *usage = sk_ASN1_OBJECT_value(extKeyUsage,i);
+                OBJ_obj2txt(EXT_KEY_USAGE_OID,
+                            sizeof(EXT_KEY_USAGE_OID),
+                            usage,
+                            1 /* use numerical form */);
+                if (strcmp(EXT_KEY_USAGE_OID, X509_CODE_SIGNING_OID) == 0
+                        || strcmp(EXT_KEY_USAGE_OID, NOKIA_CODE_SIGNING_OID) == 0)
+                {
+                    extKeyUsageKnown = true;
+                    break;
+                }
+            }
+            sk_ASN1_OBJECT_pop_free(extKeyUsage, ASN1_OBJECT_free);
+            if (!extKeyUsageKnown)
+            {
+                ret_code = KUnknownExtendedKeyUsage;
+                break;
+            }
+        }
+
+        // compute the root hash value if requested
+        sprintf(root_hash,"%08lX",X509_issuer_name_hash(x509_ctx->current_issuer));
+        // add the '\0'
+        root_hash[MD5_DIGEST_LEN] = '\0';
+
+        // 1. get the public key of the signing cert
+        // 2. decode the provided signature using the signing cert's public key
+        // 3. parse the digest/decrypted signature
+        EVP_PKEY *pkey = X509_get_pubkey(end_entity_cert);
+        rsakey = EVP_PKEY_get1_RSA(pkey);
+        EVP_PKEY_free(pkey);
+        plainSig = new unsigned char[RSA_size(rsakey) * 2];
+        int digest_len = RSA_public_decrypt(sig_len, sig, plainSig, rsakey, RSA_PKCS1_PADDING);
+        if (digest_len != (sizeof(SHA_1_ALG_FOOTPRINT) + SHA_1_DIGEST_LEN))
+        {
+            ret_code = KSignatureVerificationFailure;
+            break;
+        }
+        if (memcmp(SHA_1_ALG_FOOTPRINT,
+                   (const char *)plainSig,
+                   sizeof(SHA_1_ALG_FOOTPRINT)))
+        {
+            ret_code = KSignatureVerificationFailure;
+            break;
+        }
+        // save the plainSig into jar_hash
+        char * tmp_jar_hash = NULL;
+        tmp_jar_hash = jar_hash;
+        // format it as hex
+        for (int i=0; i<SHA_1_DIGEST_LEN; i++)
+        {
+            sprintf(tmp_jar_hash, "%02X", *(plainSig + sizeof(SHA_1_ALG_FOOTPRINT) + i));
+            tmp_jar_hash = tmp_jar_hash + 2;
+        }
+        // add the '\0'
+        jar_hash[2*SHA_1_DIGEST_LEN] = '\0';
+        tmp_jar_hash = NULL;
+        break;
+    }
+
+    // cleanup
+    RSA_free(rsakey);
+    delete[] plainSig;
+    plainSig = NULL;
+    EVP_cleanup();
+    if (NULL != x509_ctx)
+    {
+        X509_STORE_CTX_free(x509_ctx);
+    }
+    X509_STORE_free(x509_store);
+    for (;;)
+    {
+        X509* x = sk_X509_pop(validated_certs_st);
+        if (!x) break;
+        X509_free(x);
+    }
+    sk_X509_free(validated_certs_st);
+    for (;;)
+    {
+        X509* x = sk_X509_pop(roots_certs_st);
+        if (!x) break;
+        X509_free(x);
+    }
+    sk_X509_free(roots_certs_st);
+
+    // if things are ok, save the cert_details
+    if (end_entity_cert != NULL && ret_code == KCertAndSignatureOk)
+    {
+        SecurityUtils::getCertDetails(*end_entity_cert, details, true /* parse domain info */);
+    }
+    X509_free(end_entity_cert);
+
+    return ret_code;
+}
+
+static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
+{
+    int    err;
+    err = X509_STORE_CTX_get_error(ctx);
+    if (!preverify_ok)
+    {
+        switch (err)
+        {
+        case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION:
+            if (SecurityUtils::areAllCriticalExtsKnown(X509_STORE_CTX_get_current_cert(ctx)))
+            {
+                return !preverify_ok;
+            }
+            break;
+        case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+            return !preverify_ok;
+        }
+    }
+    return preverify_ok;
+}
+
+int getErrCode(int x509_err_code)
+{
+    // Needed to handle other error codes?
+    switch (x509_err_code)
+    {
+    case X509_V_ERR_CERT_NOT_YET_VALID:
+        return KCertNotYetValidFailure;
+    case X509_V_ERR_CERT_HAS_EXPIRED:
+        return KCertExpiredFailure;
+    case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+    case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+        return KSelfSignedCertInChainFailure;
+    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+        return KMissingRoot;
+    default:
+        return KCertValidationFailure;
+    }
+}