02 February 2013

Code signing jar and exe files

It is possible to buy a single code signing certificate that can be used to sign both Java jar files and Windows executable exe files.

A cheap source for a certificate is Tucows with one year currently costing US$75. Tucows are a reseller for Comodo, but the certificates are more expensive from Comodo direct.

Along the way, I'm going to create P12, 2 x PFX, PEM, PVK, CERT and SPC files. You'll need one of the PFX files to sign JARs and the SPC/PVK files to sign EXEs.

Getting the certificates

Working in Windows, I bought the certificate in Firefox. Within a couple of days I was phoned by Comodo to confirm my identity and the certificate issued. I collected the certificate in the same browser, ie it was installed in Firefox.

Using these instructions, I saved the certificate from Firefox into a .P12 file.

The next task is to import the certificate into Windows Internet Explorer. In Windows Explorer, double-click on the P12 file to start the Certificate Import Wizard. Choose your .p12 file. Tick (a) Enable strong private key encryption (b) Mark this key as exportable and (c) Include all extended properties. Click through until you can set the security level to High with a password of your choice.

The certificate should now be installed in Internet Explorer. Find it in Tools, Internet Options, Content, Certificates. Follow these instructions to create a PFX file suitable for signing JAR files. As per the instructions, tick the "Include all certificates" option. I saved the eventual file with a name like mycert.jar.pfx

To get the SPC/PVK files to sign EXEs, you need to run the Internet Explorer certificate export wizard again. This time do not tick "Include all certificates". I saved the eventual file with a name like mycert.exe.pfx

Now continue with these instructions to create the PVK and SPC files. You will need to install openssl if you don't have it already. I ran these from the openssl bin directory:

openssl pkcs12 -in \certs\mycert.exe.pfx -nocerts -nodes -out \certs\mycert.pem
openssl rsa -in \certs\mycert.pem -outform PVK -pvk-strong -out \certs\mycert.pvk
openssl pkcs12 -in \certs\mycert.exe.pfx -nokeys -out \certs\mycert.cert
openssl crl2pkcs7 -nocrl -certfile \certs\mycert.cert -outform DER -out \certs\mycert.spc

I ignored the warning "WARNING: can't open config file: /usr/local/ssl/openssl.cnf"

Backup all the created files carefully.

Signing JAR files

Follow these instructions to find the alias you have been given - before the first comma which is followed by a date. This can either be a friendly name or a {GUID}. Make a note of the alias.

keytool -list -storetype pkcs12 -keystore \certs\mycert.jar.pfx

You can then sign JAR files like this:

jarsigner -storetype pkcs12 -keystore \certs\mycert.jar.pfx myfile.jar "myalias"
jarsigner.exe -verify -certs myfile.jar

Signing EXE files

Sign EXE files like this, replacing the Description and the website with something appropriate:

signcode -spc \certs\mycert.spc -v \certs\mycert.pvk -n "Description" -i "http://www.example.com/" -t http://timestamp.verisign.com/scripts/timstamp.dll myfile.exe

12 July 2012

CiviCRM profile search block

On a CiviCRM 4 site I help run, there is a profile search form exposed to authorised public users of the Drupal site.  This form provides many search options including the selection of a region of the UK.

My task is to provide a Drupal block on the site home page that shows a map of the UK with each region clickable - when you click, a map of the results is shown, not a text listing of results.

My solution was to build on the existing profile, as it's display templates have been customised in various ways.  To work out what to do, you will have to analyse what is generated by CiviCRM for the actual search form, in particular the (custom) fields that are used.

Basic block structure

My block creates an HTML form that is posts to CiviCRM.  Civi uses session keys to differentiate between each user's search.  A crucial task is therefore to set a valid "qfKey" hidden form parameter.  To get a suitable key, the PHP code creates a suitable form controller and retrieves the key.  The code below shows the PHP and a skeleton of the form itself.

<?php
  civicrm_initialize(TRUE);
  require_once 'CRM/Core/Controller/Simple.php';

  $formController = new CRM_Core_Controller_Simple( 'CRM_Profile_Form_Search', ts('Search Profile'), CRM_Core_Action::ADD );
  $formController->setEmbedded( true );
  $formController->set( 'gid', 123 );

  $ky = $formController->_key;
?>

  <form  action="/civicrm/profile?gid=123" method="post" name="RegionMap" id="RegionMap">
  <input name="qfKey" type="hidden" value="<?php echo $ky ?>" />
  <input name="_qf_default" type="hidden" value="Search:refresh" />

...

  <input name="_qf_Search_refresh" value="Search" type="submit"/>
</form>

Form contents

To work out what needs to go in the form, you will have to copy what's in the standard search form generated by CiviCRM.  It seems like you only need to include the chunks for the sub-search you wish to do, ie you do not need to have dummy/hidden entries for all fields.

For my region search, the standard form output consists of a hidden INPUT, a checkbox INPUT and an associated LABEL.  You can slim it right down to one hidden INPUT like this:

  <input type="hidden" id="northeast" name="custom_21[1.00]" value="" />

"custom_21" is the Civi custom data field internal name.  The string in square brackets is the field value - in my case, one of the mulitple choice option values.  I have added the "northeast" id to this INPUT.

If you set the value for the above hidden field to "1" and submit the form, you should see the standard text listing of results that match that search.  On that page, there should be a "Map these contacts" link.

Showing the result map straight away

To show a map of results straight away, you'd think you'd simply copy the URL of the map page, and it would work:

<form action="/civicrm/profile/map?map=1&gid=123&reset=1" method="post" name="RegionMap" id="RegionMap" >

You do need to do that change, but by itself it doesn't work.  If you try it, you will find that it always uses the last proper search terms that you used.  Civi is storing the last search parameters in the "profileParams" session variable.  So, our first task is to clear this session variable when our block is initialised, ie in the above code:

  $session = CRM_Core_Session::singleton();
  $session->set('profileParams');

Here's how I set the profileParams session value.  First, I created another hidden field which will be set by JavsScript to contain the value chosen on my block:

<input name="regionCode" type="hidden" value="" />

In the custom CiviCRM code for my site, I amended CRM/Profile/Page/Listings.php in getProfileContact() to rebuild the profileParams session value if my new hidden variable is set:

$regionCode = CRM_Utils_Array::value( 'regionCode', $_REQUEST );
if ( isset($regionCode))
{
  $params = array();
  $custom_21 = array();
  $custom_21[$regionCode] = 1;
  $params["custom_21"] = $custom_21;
  $session->set('profileParams',$params);
}

For this to work, the regionCode hidden field needs to be set to a suitable value, such as "1.00" in my case.

And the block image map...

The block uses a standard HTML image map along with JavaScript and jQuery to submit the form when the user clicks on a region.  The map area code looks like the following:

  <area shape="poly" coords="149,123,158,101,205,169,187,166,181,190" href="/" alt="North East" title="North East England" onclick="return ShowRegion('northeast');" />

The following JavaScript is used to respond to map clicks:

<script type="text/javascript">
  var cj = jQuery.noConflict(true);

  function ShowRegion(regionname){
    var regionField = cj('input[id="'+regionname+'"]');
    regionField.val("1");

    var regionFieldName = regionField.attr("name");
    var regionCode = null;
    var lbpos = regionFieldName.indexOf("[");
    if( lbpos!==-1){
      var rbpos = regionFieldName.indexOf("]",lbpos);
      if( lbpos!==-1){
        regionCode = regionFieldName.substring(lbpos+1,rbpos);
      }
    }
    if( regionCode==null){
      alert("Sorry, no regionCode found");
      return false;
    }

    var regionSpecialField = cj('input[name="regionCode"]');
    regionSpecialField.val(regionCode);
    cj("#RegionMap").submit();

    return false;
}
</script>

04 August 2011

Using PHDCC CodeModule with javascript

phdcc Director John Cant writes:

My use of DNN has become -

[a] Maintain any HTML in a text file / copy and paste it into the basic editor / convert to raw / save. (Online editors are too hard to use and keep slim)

[b] Put any functionality into a phdcc.CodeModule

Much valuable functionality - jquery-ui / google maps / facebook / janrain / plupload / galleria - is available as javascript libraries. To get them onto a DNN Page, I have created a template CodeModule as follows.

ASP.NET gives you limited control over where in the page javascript can be inserted - RegisterClientScriptBlock inserts somewhere near the top; RegisterStartupScript somewhere near the bottom. All I can say is - the template works on all browsers tested.

In this example, I load the jquery-ui library at the top, and daisy chain the DNN onload handler at the bottom. The javascript could of course be located in a separate file.

In the onload handler, I create an invisible empty jquery-ui dialog and initialise a jquery-ui accordion. I bind the submit button of a PHDCC form to a handler and examine one of the form fields to give feedback via the dialog.

I add the CodeModule somewhere out of the way on the page - the bottom pane is a good place. Once there, accordions and dialogs can be used anywhere on the page.

If you need any help with this or any of the suggested libraries, please get back to us.

<%@ Control Language="C#" ClassName="JCformWidgets" %>

<script runat="server">

//--------------------------------------
private void loadJS()
{
string scriptKey = "JCjsIncludes:" + this.UniqueID;
if( Page.IsStartupScriptRegistered( scriptKey) || Page.IsPostBack) return;

ClientScriptManager cs = Page.ClientScript;
Type cstype = this.GetType();

// ----------------------------
// load JS files at the top of the page
// ----------------------------
string scriptBlock = "\n";

scriptBlock += "<link type='text/css' href='/jqui/jquery-ui-1.8.14.custom.css' rel='Stylesheet'>\n";
scriptBlock += "<script src='/jqui/jquery-ui-1.8.14.custom.min.js' type='text/javascript'>";
scriptBlock += "</";
scriptBlock += "script>\n"; // avoid microsoft parsing bug

cs.RegisterClientScriptBlock( cstype, scriptKey, scriptBlock);

// ----------------------------
// load onload handler etc. at the bottom of the page
// ----------------------------
string startupScript =
@"<script language='JavaScript'>
<!--

function JConload(){

var oldOnLoad = window.onload;

var myDialog = $('<div></div>').dialog( {autoOpen: false} );

this.newOnload = function(){

// call existing handler
if(typeof oldOnLoad === 'function'){
oldOnLoad();
}

// -------------------------------------

$(function() {
$( '#accordion' ).accordion();
});

// -------------------------------------

$('#' + 'dnn_ctr422_ViewForm_btnSubmit').bind('click', function() {

var theseSkills = $( '#' + 'dnn_ctr422_ViewForm_Group_157_400_Question_157_400_1563_form_157_text_1563').val();
var thisDialogText = '';

if( theseSkills.length <= 0) {
thisDialogText = 'No skills given<br>Please try again.';

myDialog.html( thisDialogText);
myDialog.dialog( 'option', 'title', 'Things you forgot ...' );

myDialog.dialog( 'open');
return false;
}
});

// -------------------------------------

};

}

(function(){
var JCOL = new JConload();
window.onload = JCOL.newOnload;
}());

// -->";

startupScript += "</sc";
startupScript += "ript>"; // avoid microsoft parsing bug

cs.RegisterStartupScript( cstype, scriptKey, startupScript);
}

//--------------------------------------
protected void Page_Load(object sender, EventArgs e)
{
loadJS();
}

</script>