blog archive contact about feed

Rob's Blog - April 2007

Contents

Here are Rob's Blog entries for April 2007.

Blog entries for other months can be found in the main blog index.

Atom WSSE Authentication Using C# .NET

I received an email asking for a hand with a C# .NET implementation of the WSSE atom authentication for Nokia's Lifeblog.

I took a look at this as I'm brushing up on my C# at the moment for work, and I relish a challenge.

As there are two different methods of authentication depending on the version of Lifeblog being used, the code has to handle this.

The first method is to append the nonce, timestamp and password together, create a SHA1 hash with this and Base64 encode the result. The second way is almost the same, but the nonce has to be Base64 decoded first before being appended with the timestamp and password.

See my previous article on WSSE Authentication For Atom Using Perl for more details on how this works.

The first method can be calculate a digest value using code like this.

string mystring = nonce + created + password; SHA1Managed SHhash = new SHA1Managed(); string mydigest = Convert.ToBase64String(SHhash.ComputeHash(System.Text.Encoding.ASCII.GetBytes(mystring)));

My first attempt at the second method looked very similar.

string mystring = (System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(nonce))) + created + password; SHA1Managed SHhash = new SHA1Managed(); string mydigest = Convert.ToBase64String(SHhash.ComputeHash(System.Text.Encoding.ASCII.GetBytes(mystring)));

Looks OK on the surface, but it fails to validate when posting from my Nokia N93 phone.

What's gone wrong?

Well looking in the debugger, the process of converting the nonce back from the array of bytes Convert.FromBase64String() produces and appending them to form mystring corrupts the data compared with the Perl version in my previous article.

We know the approach is basically right, so we need to cut out this corrupting step.

The best way to do this is to keep the data in byte arrays as we need the data in this format anyway to computer the SHA1.

System.Text.Encoding enc = System.Text.Encoding.UTF8; byte[] noncebytes = enc.GetBytes(nonce); byte[] passwordbytes = enc.GetBytes(password); byte[] createdbytes = enc.GetBytes(created); byte[] code = new byte[nonce.Length + password.Length + created.Length]; Array.Copy(nonce, code, nonce.Length); Array.Copy(created, 0, code, nonce.Length, created.Length); Array.Copy(password, 0, code, nonce.Length + created.Length, password.Length); System.Security.Cryptography.SHA1Managed SHhash = new System.Security.Cryptography.SHA1Managed(); string digest = Convert.ToBase64String(SHhash.ComputeHash(code));

I'm sure there is a nicer way to achieve the appending of the 3 arrays together in C#, but this works for now.

We can tie this up to form a nice static class to handle our WSSE authentication.

/// <summary> /// Computes WSSE codes from input for validation. /// </summary> public static class WSSE { /// <summary> /// Calculate the digest. /// </summary> /// <param name="password">A string with your unencrypted password.</param> /// <param name="nonce">The nonce the digest is to be created with as a string.</param> /// <param name="created">The timestamp as a string.</param> /// <returns>A string containing the digest.</returns> public static string GetDigest(string password, string nonce, string created) { return GetDigest(password, nonce, created, System.Text.Encoding.ASCII); } /// <summary> /// Calculate the digest. /// </summary> /// <param name="password">A string with your unencrypted password.</param> /// <param name="nonce">The nonce the digest is to be created with as a string.</param> /// <param name="created">The timestamp as a string.</param> /// <param name="enc">The System.Text.Encoding to use.</param> /// <returns>A string containing the digest.</returns> public static string GetDigest(string password, string nonce, string created, System.Text.Encoding enc) { byte[] noncebytes = enc.GetBytes(nonce); byte[] passwordbytes = enc.GetBytes(password); byte[] createdbytes = enc.GetBytes(created); return generatecode(noncebytes, passwordbytes, createdbytes); } /// <summary> /// Calculate the alternative digest. /// </summary> /// <param name="password">A string with your unencrypted password.</param> /// <param name="nonce">The nonce the digest is to be created with as a string.</param> /// <param name="created">The timestamp as a string.</param> /// <returns>A string containing the digest.</returns> public static string GetDigestAlt(string password, string nonce, string created) { return GetDigestAlt(password, nonce, created, System.Text.Encoding.ASCII); } /// <summary> /// Calculate the alternative digest. /// </summary> /// <param name="password">A string with your unencrypted password.</param> /// <param name="nonce">The nonce the digest is to be created with as a string.</param> /// <param name="created">The timestamp as a string.</param> /// <param name="enc">The System.Text.Encoding to use.</param> /// <returns>A string containing the digest.</returns> public static string GetDigestAlt(string password, string nonce, string created, System.Text.Encoding enc) { byte[] noncebytes = Convert.FromBase64String(nonce); byte[] passwordbytes = enc.GetBytes(password); byte[] createdbytes = enc.GetBytes(created); return generatecode(noncebytes, passwordbytes, createdbytes); } private static string generatecode(byte[] nonce, byte[] password, byte[] created) { byte[] code = new byte[nonce.Length + password.Length + created.Length]; Array.Copy(nonce, code, nonce.Length); Array.Copy(created, 0, code, nonce.Length, created.Length); Array.Copy(password, 0, code, nonce.Length + created.Length, password.Length); System.Security.Cryptography.SHA1Managed SHhash = new System.Security.Cryptography.SHA1Managed(); return Convert.ToBase64String(SHhash.ComputeHash(code)); } /// <summary> /// Validates password, nonce and created create the same digest code as digest. /// </summary> /// <param name="password">A string with your unencrypted password.</param> /// <param name="nonce">The nonce the digest is to be created with as a string.</param> /// <param name="created">The timestamp as a string.</param> /// <param name="digest">The digest to validate the password, nonce and created strings against as a string.</param> /// <returns>true or false depending on wether the digest validates</returns> public static bool IsValid(string password, string nonce, string created, string digest) { return digest == GetDigest(password, nonce, created) || digest == GetDigestAlt(password, nonce, created); } /// <summary> /// Validates password, nonce and created create the same digest code as digest. /// </summary> /// <param name="password">A string with your unencrypted password.</param> /// <param name="nonce">The nonce the digest is to be created with as a string.</param> /// <param name="created">The timestamp as a string.</param> /// <param name="digest">The digest to validate the password, nonce and created strings against as a string.</param> /// <param name="enc">A System.Text.Encoding encoding.</param> /// <returns>true or false depending on wether the digest validates.</returns> public static bool IsValid(string password, string nonce, string created, string digest, System.Text.Encoding enc) { return digest == GetDigest(password, nonce, created, enc) || digest == GetDigestAlt(password, nonce, created, enc); } }

We can test the code using the following code snippet.

string digest = "nvvHvNuLb+7wGFrop+cC2tjgQqs="; string nonce = "bgZ4BHcjmWcg7gVhCxyQOg=="; string timestamp = "2006-04-25T19:40:45Z"; string password = "a"; if (WSSE.IsValid(password, nonce, timestamp, digest, System.Text.Encoding.UTF8)) { Console.WriteLine("valid"); } else { Console.WriteLine("invalid"); }

This gives us the result "valid" as we'd hope.

To save you time, feel free to download the C# code WSSE.cs.

Entered: 2007-04-30 22:53:17

Links for 2007-04-25

Bookmarks from del.icio.us
Entered: 2007-04-26 00:15:03

Links for 2007-04-20

Bookmarks from del.icio.us
Entered: 2007-04-21 00:15:02

Links for 2007-04-18

Bookmarks from del.icio.us
Entered: 2007-04-19 00:15:04

Links for 2007-04-16

  • Nokia - Beta Labs Nokia's new beta labs, somewhere to keep checking for more cool mobile apps
Bookmarks from del.icio.us
Entered: 2007-04-17 00:15:03

James and ash

A test post

Mon 16/04/2007 14:48 Image088
PERMALINK - James and ash
Lifeblog Entry - Posted via Lifeblog from a Nokia smart phone
Entered: 2007-04-16 14:50:33

Links for 2007-04-14

Bookmarks from del.icio.us
Entered: 2007-04-15 00:15:02

Links for 2007-04-13

  • Top 10 Most Influential Amiga Games - The heyday for Amiga games was the late '80s and early '90s. The computer's custom chipset and advanced (for the time) graphics capability led to sumptuous 2-D titles in a variety of styles, and even some basic 3-D games. Here's a look at some of the more
  • BBC NEWS | Business | Google unveils UK payments system Checkout will compete with both the mainstream card processing services used by many online merchants and auction site eBay's Paypal service.
Bookmarks from del.icio.us
Entered: 2007-04-14 00:15:08

Links for 2007-04-12

Bookmarks from del.icio.us
Entered: 2007-04-13 00:15:04

Python, XML and iTunes

As I've taken the week off work, I thought as well as spending time with my family, I'd brush up my Python skills as they've been a bit neglected of late.

I've never tried XML parsing with Python so thought I'd cover that. Apple's iTunes has the ability to export information about your music in XML and I'd been meaning to take a look at that for a while. Why not combine the two, so here's my take on parsing iTunes export information with Python.

I thought i'd work on a small subset of my library, the ones I've actually paid to download from iTunes compared to the ones converted from CD.

Rob's purchased iTunes tracks

The exported XML data is a bit peculiar. I would have assumed it to be values enclosed by sensible tag names e.g <artist>Human League</artist>. However, it's actually a bunch of neighbouring tags and values like this <key>Artist</key><string>Sheb Wooley</string>

Here's a snippet from the actual data export I ran...

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Major Version</key><integer>1</integer> <key>Minor Version</key><integer>1</integer> <key>Application Version</key><string>6.0.5</string> <key>Features</key><integer>1</integer> <key>Music Folder</key><string>file://localhost/D:/Documents%20and%20Settings/Windows%20User/My%20Documents/My%20Music/iTunes/iTunes%20Music/</string> <key>Library Persistent ID</key><string>C5DD29C89369B278</string> <key>Tracks</key> <dict> <key>312</key> <dict> <key>Track ID</key><integer>312</integer> <key>Name</key><string>The Purple People Eater</string> <key>Artist</key><string>Sheb Wooley</string> <key>Album</key><string>20th Century Rocks: 50's Rock 'n Roll - At the Hop</string> <key>Genre</key><string>Pop</string> <key>Kind</key><string>Protected AAC audio file</string> <key>Size</key><integer>2260837</integer> <key>Total Time</key><integer>135533</integer> <key>Disc Number</key><integer>1</integer> <key>Disc Count</key><integer>1</integer> <key>Track Number</key><integer>5</integer> <key>Year</key><integer>2001</integer> <key>Date Modified</key><date>2006-09-28T09:54:23Z</date> <key>Date Added</key><date>2006-09-28T09:54:10Z</date> <key>Bit Rate</key><integer>128</integer> <key>Sample Rate</key><integer>44100</integer> <key>Play Count</key><integer>11</integer> <key>Play Date</key><integer>-1042489964</integer> <key>Play Date UTC</key><date>2007-01-24T08:55:32Z</date> <key>Normalization</key><integer>7764</integer> <key>Compilation</key><true/> <key>Artwork Count</key><integer>1</integer> <key>Persistent ID</key><string>302B45E87F01479F</string> <key>Track Type</key><string>File</string> <key>Protected</key><true/> <key>Location</key><string>file://localhost/D:/Documents%20and%20Settings/Windows%20User/My%20Documents/My%20Music/iTunes/iTunes%20Music/Compilations/20th%20Century%20Rocks_%2050's%20Rock%20'n%20Roll%20-/05%20The%20Purple%20People%20Eater.m4p</string> <key>File Folder Count</key><integer>4</integer> <key>Library Folder Count</key><integer>1</integer> </dict> <key>313</key> <dict> <key>Track ID</key><integer>313</integer> <key>Name</key><string>Daisy Daisy</string> <key>Artist</key><string>Johnny O'Tolle &#38; His Naughty Band</string> <key>Album</key><string>Gay 90's</string> <key>Genre</key><string>Vocal</string> <key>Kind</key><string>Protected AAC audio file</string> <key>Size</key><integer>2346412</integer> <key>Total Time</key><integer>125084</integer> <key>Disc Number</key><integer>1</integer> <key>Disc Count</key><integer>1</integer> <key>Track Number</key><integer>2</integer> <key>Track Count</key><integer>10</integer> <key>Year</key><integer>2006</integer> <key>Date Modified</key><date>2006-09-28T09:59:52Z</date> <key>Date Added</key><date>2006-09-28T09:59:38Z</date> <key>Bit Rate</key><integer>128</integer> <key>Sample Rate</key><integer>44100</integer> <key>Play Count</key><integer>6</integer> <key>Play Date</key><integer>-1038647848</integer> <key>Play Date UTC</key><date>2007-03-09T20:10:48Z</date> <key>Artwork Count</key><integer>1</integer> <key>Persistent ID</key><string>302B45E87F01490F</string> <key>Track Type</key><string>File</string> <key>Protected</key><true/> <key>Location</key><string>file://localhost/D:/Documents%20and%20Settings/Windows%20User/My%20Documents/My%20Music/iTunes/iTunes%20Music/Johnny%20O'Tolle%20&#38;%20His%20Naughty%20Band/Gay%2090's/02%20Daisy%20Daisy.m4p</string> <key>File Folder Count</key><integer>4</integer> <key>Library Folder Count</key><integer>1</integer> </dict>

This makes parsing the data a bit trickier than I had hoped for. I was hoping to use a nice simple XPath expression, but data like this looks like it's more a job for a SAX based approach.

I took a look in O'Reilly's excellent Programming Python, and found a nice SAX parser example to modify.

As it's just a quick test, I'm making a few assumptions on the XML data that a production system would have to handle. In this case, I'm assume a tag order of Track ID, Name and Artist. Using this order, each time we see one of those tags come past, we can make up a Track object and store the relevant data. In this case, when we see Track ID we need a new Track object to store the data in. When we see Name, we store the track name in the object and when we see Artist we save the artist, push the Track object to our list of Tracks and clear the current Track object.

That's a bit long winded, so here's the code.

import xml.sax.handler class ITunesHandler(xml.sax.handler.ContentHandler): def __init__(self): self.parsing_tag = False self.tag = '' self.value = '' self.tracks = [] self.track = None def startElement(self, name, attributes): if name == 'key': self.parsing_tag = True def characters(self, data): if self.parsing_tag: self.tag = data self.value = '' else: # could be multiple lines, so append data. self.value = self.value + data def endElement(self,name): if name == 'key': self.parsing_tag = False else: if self.tag == 'Track ID': # start of a new track, so a new object # is needed. self.track = Track() elif self.tag == 'Name' and self.track: self.track.track = self.value elif self.tag == 'Artist' and self.track: self.track.artist = self.value # assume this is all the data we need # so append the track object to our list # and reset our track object to None. self.tracks.append(self.track) self.track = None class Track: def __init__(self): self.track = '' self.artist = '' def __str__(self): return "Track = %s\nArtist = %s" % (self.track,self.artist)

In the real world, the Track class would offer a lot more functionality, in this case, it's just for holding data and providing a pretty printer.

Now we need to parse the XML and display the results, here's the code...

parser = xml.sax.make_parser() handler = ITunesHandler() parser.setContentHandler(handler) parser.parse('D:\\Documents and Settings\\Windows User\\Desktop\\Purchased.xml') for track in handler.tracks: print track

Let's run that code and see what we get...

Track = The Purple People Eater Artist = Sheb Wooley Track = Daisy Daisy Artist = Johnny O'Tolle & His Naughty Band Track = Don't Dilly Dally Artist = Kidzone Track = Jump In My Car Artist = David Hasselhoff Track = Puff, the Magic Dragon Artist = Peter, Paul And Mary Track = You Give Love a Bad Name Artist = Bon Jovi Track = Heart of Glass Artist = Blondie Track = Grace Kelly Artist = Mika Track = Standing In the Way of Control Artist = Gossip Track = Physical Artist = Olivia Newton-John Track = Don't You Want Me Artist = The Human League Track = Have a Drink On Me Artist = Lonnie Donegan Track = My Old Man's a Dustman Artist = Lonnie Donegan

That's great! OK, I'm not going to win any awards for my taste in music, but at least I can now think about building music services that use this data.

Entered: 2007-04-11 22:22:58

Links for 2007-04-10

Bookmarks from del.icio.us
Entered: 2007-04-11 00:15:05