Saturday, February 2, 2008

S/MIME Encryption using Bouncy Castle Crypto API for Java

At this time and age, it is somewhat irresponsible for banks to accept uploads of plain-text files to their online banking services. Secure HHTP (HTTPS) alone does not address total security as somebody in one's organization has the ability to tamper the files prior to upload. And end-to-end encryption is not a one-sided affair --- both the bank and the customer application has to support it before it can be realized.

Of the four banks I've been involved with, two international banks support both plain-text and encrypted file uploads. One local bank accepts plain-text only; the other uses proprietary encryption BUT gives the encryption algo to your programmers!


At this time and age, there should be no excuse not to adopt industrial-strength cryptography. I managed to create a BeanShell script to encrypt files using the Bouncy Castle cryptography API for Java. The script produces an encrypted file named Encrypted.eml which can be opened and viewed by Mozilla Thunderbird. The encrypted payload can be delivered using this companion code.


You would need your own digital certificate, a public-key for your recipient and the file that you want encrypted. Get your free digital certificates from Comodo. To run the script, execute the command java -classpath ./bsh-2.0b4.jar bsh.Interpreter SnE.bsh, where SnE.bsh is the script name. Lastly, here's the code..


addClassPath( "./mail.jar" );
addClassPath( "./activation.jar" );
addClassPath( "./bcprov-jdk16-138.jar" );
addClassPath( "./bcmail-jdk16-138.jar" );

import javax.activation.FileDataSource;
import javax.activation.DataHandler;

import java.io.FileInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.security.KeyStore;
import java.security.Security;
import java.security.PrivateKey;
import java.security.cert.CertificateFactory;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.cert.CertStore;
import java.security.cert.CollectionCertStoreParameters;
import java.util.Properties;
import java.util.Enumeration;
import java.util.List;
import java.util.ArrayList;

import javax.mail.Message;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.activation.MailcapCommandMap;
import javax.activation.CommandMap;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
import org.bouncycastle.mail.smime.SMIMEException;
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
import org.bouncycastle.util.Strings;
import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
import org.bouncycastle.asn1.smime.SMIMECapability;
import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.cms.AttributeTable;

public static final String pkcs12Keystore = "./certs/myOwn.p12";
public static final String rcptCertfile = "./certs/myBank.cer";
public static final String sendFile = "./messages/hello.txt";
public static final String ksPassword = "pipoltek";
public static final String frAddress = "rexjun@somewhere.com";
public static final String toAddress = "ca@myBank.com";

try
{
MailcapCommandMap mailcap = (MailcapCommandMap)CommandMap .getDefaultCommandMap();
mailcap .addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");
mailcap .addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
mailcap .addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");
mailcap .addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
mailcap .addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");
CommandMap.setDefaultCommandMap(mailcap);

/* Add BC */
Security.addProvider(new BouncyCastleProvider());

/* Open the keystore */
KeyStore keystore = KeyStore.getInstance("PKCS12", "BC");
keystore.load (new FileInputStream(pkcs12Keystore), ksPassword.toCharArray());
Enumeration e = keystore.aliases();
String keyAlias = null;
while (e.hasMoreElements() && (keyAlias == null)) {
String alias = (String)e.nextElement();
keyAlias = keystore.isKeyEntry(alias) ? alias : null;
}
if (keyAlias == null) {
System.err.println("Can't find a private key!");
System.exit(0);
}
Certificate[] chain = keystore.getCertificateChain(keyAlias);

/* Get the private key to sign the message with */
PrivateKey privateKey = (PrivateKey)keystore.getKey(keyAlias,
ksPassword.toCharArray());
if (privateKey == null) {
throw new Exception("No private key for alias: " + keyAlias);
}

/* Get public key of recipient for encryption */
FileInputStream fis = new FileInputStream(rcptCertfile);
BufferedInputStream bis = new BufferedInputStream(fis);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate rcptCert = cf.generateCertificate(bis);

/* Attach the file to encrypt */
MimeBodyPart bodyPart = new MimeBodyPart();
FileDataSource fds = new FileDataSource(sendFile);
bodyPart.setDataHandler(new DataHandler(fds));
bodyPart.setFileName(fds.getName());
MimeMultipart bodyMulti = new MimeMultipart();
bodyMulti.addBodyPart(bodyPart);

Session session = Session.getDefaultInstance(System.getProperties());
MimeMessage body = new MimeMessage(session);
body.setFrom(new InternetAddress(frAddress));
body.setRecipient(Message.RecipientType.TO, new InternetAddress(
toAddress));
body.setSentDate(new Date());
body.setSubject("Encrypted Mail");
body.setContent(bodyMulti,bodyMulti.getContentType());
body.saveChanges();

/* Create the SMIMESignedGenerator */
SMIMECapabilityVector capabilities = new SMIMECapabilityVector();
capabilities.addCapability(SMIMECapability.dES_EDE3_CBC);
capabilities.addCapability(SMIMECapability.rC2_CBC, 128);
capabilities.addCapability(SMIMECapability.dES_CBC);

ASN1EncodableVector attributes = new ASN1EncodableVector();
attributes.add(new SMIMEEncryptionKeyPreferenceAttribute(
new IssuerAndSerialNumber(
new X509Name(((X509Certificate)chain[0])
.getIssuerDN().getName()),
((X509Certificate)chain[0]).getSerialNumber())));
attributes.add(new SMIMECapabilitiesAttribute(capabilities));

SMIMESignedGenerator signer = new SMIMESignedGenerator();
signer .addSigner(privateKey, (X509Certificate)chain[0],
"DSA".equals(privateKey.getAlgorithm()) ?
SMIMESignedGenerator.DIGEST_SHA1 : SMIMESignedGenerator.DIGEST_MD5,
new AttributeTable(attributes), null);

/* Add the list of certs to the generator */
List certList = new ArrayList();
certList.add(chain[0]);
CertStore certs = CertStore.getInstance("Collection",
new CollectionCertStoreParameters(certList), "BC");
signer.addCertificatesAndCRLs(certs);

/* Sign the message and copy all headers from original message */
MimeMultipart multiPart = signer.generate(body, "BC");
MimeMessage signedMessage = new MimeMessage(session);
Enumeration headers = body.getAllHeaderLines();
while (headers.hasMoreElements()) {
signedMessage.addHeaderLine((String)headers.nextElement());
}
signedMessage.setContent(multiPart);
signedMessage.saveChanges();

/* Create the encrypter and encrypt the message */
SMIMEEnvelopedGenerator encrypter = new SMIMEEnvelopedGenerator();
encrypter.addKeyTransRecipient((X509Certificate)chain[0]);
encrypter.addKeyTransRecipient(rcptCert);
MimeBodyPart encryptedPart = encrypter.generate(signedMessage,
SMIMEEnvelopedGenerator.RC2_CBC, 128, "BC");
ByteArrayOutputStream out = new ByteArrayOutputStream();
encryptedPart.writeTo(out);

/* Create a new MimeMessage for the encrypted and signed content */

Properties props = new Properties();
Session smtpSession = Session.getInstance(props, null);
MimeMessage smtpMessage = new MimeMessage(smtpSession,
new ByteArrayInputStream(out.toByteArray()));
smtpMessage.saveChanges();

/* Set all original MIME headers in the encrypted message */
headers = body.getAllHeaderLines();
while (headers.hasMoreElements()) {
String headerLine = (String)headers.nextElement();
/* Do not override content-* headers from the original message */
if (!Strings.toLowerCase(headerLine).startsWith("content-")) {
smtpMessage.addHeaderLine(headerLine);
}
}

smtpMessage.writeTo(new FileOutputStream("Encrypted.eml"));

}
catch (SMIMEException ex)
{
ex.getUnderlyingException().printStackTrace(System.err);
ex.printStackTrace(System.err);
}
catch (Exception ex)
{
ex.printStackTrace(System.err);
}

Tuesday, January 29, 2008

Getting a Commercial Digital Certificate

I need to acquire a digital certificate to be used for encrypting files prior to uploading it to a bank's online service. Three commercial Certificate Authorities (CAs) were recommended by the bank, namely: Entrust, Thawte and Verisign.

I was given six (6) characteristics of what I should be looking for. These items cover specs like the type of MDA, encryption algo, key size, certificate export formats, etc. Couldn't I just have the SKU number?

An SKU would have been most useful. The CA websites and storefronts were basically stocked with a slew of server SSL products -- my digital certificate is buried in some obscure page somewhere. Before I entirely lost my shopping appetite, Verisign's livechat popped into peripheral view. Good thing! I was about to put this off for another day.


After a couple of minutes, I got my answer. I need a Class 1 Digital ID and the product page is tucked here. Thawte has a livechat button, too -- but after three reps and twice the wait, I was informed politely that Thawte does not sell client certificates for commercial use. Entrust has no livechat and email response is rather sluggish.

You can purchase a Verisign Digital ID online for US$19.95 per year. A 60-day trial is also available for free. International customers, though, are served by the VeriSign Trust Network of International affiliates. That's me! I pray that dealing with their local affiliate is just as breezy.

Monday, January 21, 2008

A Tcl Web Service client for KnowledgeTree Document Management System

Sometime last year, I dabbled with KnowledgeTree's web services and (somehow I've forgotten why) I ended up with a Tcl client to perform a document upload.

Before I lose this chunk of code or forget about it altogether (and it happens a lot), I decided to post it here:


package require SOAP
package require http
package require TclCurl

set prx "http://192.168.211.128/ktwebservice/webservice.php"
set url "http://192.168.211.128/ktwebservice/upload.php"
set usr "admin"
set pas "admin"
set ipa "any"

set upf "upload-me.txt"
set typ "multipart/form-data"

proc log_it { chn msg } {
set stm [clock format [clock seconds] -format "%Y/%m/%d %H:%M:%S %a"]
puts $chn "$stm : $msg"
}

SOAP::create kt_login \
-proxy $prx \
-params { "username" "string" "password" "string" "ip" "string"} \
-name login
SOAP::create kt_add_document \
-proxy $prx \
-params { "session_id" "string" "folder_id" "int" "title" "string" "filename" "string" \
"documentype" "string" "tempfilename" "string" } \
-name add_document
SOAP::create kt_logout \
-proxy $prx \
-params { "session_id" "string"} \
-name logout

set log "debug.log"

if { [catch {open $log a} out] } {
puts stderr "Error: $out"
set out "stderr"
}

set rsp [kt_login $usr $pas $ipa]
set sta [lindex $rsp 1]
set ses [lindex $rsp 3]
log_it $out "Login: status ($sta); session ($ses)"

set opt "session_id $ses action A"
set crl [curl::init]
set rsp [$crl configure -url $url -bodyvar rsp -post 1 \
-httppost [list name "file1" file $upf contenttype $typ] \
-httppost [list name "session_id" contents $ses] \
-httppost [list name "action" contents A] \
-httppost [list name "output" contents php] \
]
catch { $crl perform } curlErrorNumber
if { $curlErrorNumber != 0 } {
error [curl::easystrerror $curlErrorNumber]
}
$crl cleanup

log_it $out "Upload: response ($rsp)"

set ps1 [string first "\"tmp_name\";s:" $rsp 0]
set ps2 [string first ":\"" $rsp [expr $ps1 + 12]]
set ps3 [string first "\";s:5:\"error\"" $rsp 0]
set tmp [string range $rsp [expr $ps2 + 2] [expr $ps3 - 1]]

# Define folder where to upload files
set fld 1;
set ttl "My Document";
set doc "Default"

set rsp [kt_add_document $ses $fld $ttl $upf $doc $tmp]
log_it $out "Add Document: response ($rsp)"

set rsp [kt_logout $ses]
log_it $out "Logout: response ($rsp)"

close $out

The upload facility is implemented by upload.php and returns a response coming out of php's serialize() function. I've mangled upload.php to return a response in xml or json format. The code changes are posted in the Knowledgetree community forum here.

The above Tcl code digests the php's serialize() output.

For xml output, here are the relevant changes:

set crl [curl::init]
set rsp [$crl configure -url $url -bodyvar xml -post 1 \
-httppost [list name "file1" file $upf contenttype $typ] \
-httppost [list name "session_id" contents $ses] \
-httppost [list name "action" contents A] \
-httppost [list name "output" contents xml] \
]
...
set top [dom parse $xml]
set sel [$top selectNodes /results/uploads/document/tmp_name/text()]
set tmp [$sel nodeValue]
$top delete

And for json..

set rsp [$crl configure -url $url -bodyvar rsp -post 1 \
-httppost [list name "file1" file $upf contenttype $typ] \
-httppost [list name "session_id" contents $ses] \
-httppost [list name "action" contents A] \
-httppost [list name "output" contents json] \
]
...
set ps1 [string first "\"tmp_name\":\"" $rsp 0]
set ps2 [string first "\",\"error\"" $rsp 0]
set tmp [string range $rsp [expr $ps1 + 12] [expr $ps2 - 1]]

Check the contents of debug.log for any errors. A successful operation produces this output:

Tuesday, December 4, 2007

Postgres on Cygwin

I'm working on a project that requires database support and I decided to use Postgres on Cygwin. I encountered mild difficulty (and lots of confusion) in setting it up so I decided to document what I have done. Here goes..

Install and run Cygserver as background service

The Cygwin User's Manual states that Cygserver provides Cygwin applications with services which require security arbitration or which need to persist while no other cygwin application is running.

Once installed as a service, you can start cygserver by running the net start cygserver command. Consequently, the service can be stopped by issuing a net stop cygserver command.

Create the postgres Windows user account

The account shall be used to run postgres as a service. Execute the command mkpasswd -l > /etc/passwd to update the local password file.

Additionally, grant the postgres account the rights to Logon as a service. This can be done by using the local security policy editor found under the Administrative Tools of the Windows Control Panel.

Initialize the database

Initialize the database by executing the command initdb -D {dir}. Popular choices for data directory are /var/postgresql/data, /usr/local/pgsql/data, or variations thereof. Change the ownership of the data directory and its sub-directories to postgres.

Install the postmaster service

Firstly, change the ownership of /usr/sbin/postgres.exe to postgres. Install postmaster as a service by running the command

cygrunsrv --install postmaster --disp "CYGWIN postmaster" --path /usr/sbin/postmaster --args "-D /usr/local/pgsql/data -i" --dep cygserver --termsig INT --shutdown --env CYGWIN=server --user postgres

The dependency (--dep) option automatically launches cygserver, if not yet started, when the postmaster service is started.

My Configuration:

- Windows XP Prof Service Pack 2
- Cygwin DLL release version 1.5.24(0.156/4/2)
- PostgreSQL version 8.0.7

Saturday, November 3, 2007

SNMP monitoring using getif and nagios

I use getif. a free Windows GUI-based network tool written by Philippe Simonet, to make a quick query of SNMP values from SNMP informant. You may download getif here.

Once installed, place the downloaded MIBs of SNMP-Informant under the Mibs sub-directory of GETIF. Here's a screenshot of getif in action:


For continuous monitoring of servers and networks, I use nagios to regularly poll environmental and performance statistics. The results can be graphed for better visualization and reporting.

Here are the pertinent nagios service and command configuration entries..

define service{
hostgroup_name windows
service_description mb-temp
check_command check_mbtemp!40!42
use template-service
}

define command{
command_name check_mbtemp
command_line $USER1$/check_snmp -H $HOSTADDRESS$ -w $ARG1$ -c $ARG2$ -o .1.3.6.1.4.1.9600.1.10.6.1.5.1 -l 'MB temp' -u 'Celsius (Warn=$ARG1$ Crit=$ARG2$)'
}