Technology Musings

March 02, 2018

Snippets / Hot-Patching Ruby for TLS 1.2

JB

Authorize.net recently started denying TLS requests that were less than 1.2.  I thought I was fine, but I had one server running CentOS 5.  It was not fine.  I had a very ancient Rails application on an ancient box unable to connect to Authorize.net.

Anyway, if you are in a similar situation, here is how I solved it.  This may not be exact, but should hopefully get you far enough along to figure it out yourself:

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px 'Helvetica Neue'; color: #454545} p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px 'Helvetica Neue'; color: #454545; min-height: 14.0px}

For authorize.net's TLS upgrade, it is based on your server's OpenSSL library version.  To see if you are compatible, run the following command:

 

    openssl s_client -connect secure.authorize.net:443

 

If you get a one-line error message like this:


    6641:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure:s23_clnt.c:586:

 

Then you are NOT compatible.  To fix, you need to either update your OpenSSL in Linux or get the latest OpenSSL from here - https://www.openssl.org/source/

 

For building, you may have to configure it something like this:


    export CFLAGS=-fPIC
    ./config shared
    make
    make install     # this one as root

 

I also copied the generated shared libs into /lib64.  In theory, after updating, it would be best to rebuild ruby from source.  However, absent that, there is a trick you can do:

 

Create a new file, I called mine ruby_openssl in the same directory as the old ruby.  The file should have the following contents:

 

    #!/bin/sh
    export LD_PRELOAD=/lib64/libssl.so.1.0.0:/lib64/libcrypto.so.1.0.0
    exec /opt/ruby-1.8.7-p352/bin/ruby $*

 

The LD_PRELOAD should go to the shared files that were installed by installing OpenSSL, wherever you happened to install them.

 

Then, in your apache config, reference your new ruby_newopenssl wherever you were referencing ruby.

October 24, 2017

Snippets / Calculating an Even Baseline

JB

So, I've been working in Xamarin for cross-platform phone development.  I was using their AbsoluteLayout class to try to get a bar graph working using relative sizes (AbsoluteLayout with relative sizes gives everything an absolute position from 0, top or left of the screen, to 1, the bottom or right of the screen).  The goal was to have the bottom of the bar graph halfway down the screen (to allow room for negative values to point down).  The goal was to look like this:

 

The problem, though is getting the boxes to align at the center line.  Now, part of the problem may be my inexperience with the platform.  There may be a really easy way to do this.  However, here is what I was running into:

I thought that Xamarin worked by aligning the your specified position to the middle of the box.  Therefore, I started by calculated the box height, and then calculating the origin of the box by setting it above the 0.5 (i.e., 50%) line by half of the box height.  Therefore, when it extended, it should extend down to the 0.5 line.

The problem, though, is that Xamarin doesn't  align to the middle of the box.  It varies the alignment by where in the positioning it is positioned.  So, if you position a box at 0.2, not only is it positioned at 0.2, but the anchoring point is also 0.2 down the box!

Therefore, I needed a way to calculate where I needed to actually position the box to get it to rest on the 0.5 line.

The way I calculated it is this - let's call the y-position that you want the box to rest on as P.  Let's call the y-position which you are going to set your box to as A (for the anchoring position).  Let's call the height of the box itself H.

What we want to happen is for the bottom of the box to hit P.

Now, we can split up the measurements from the top of the box to P into two lengths - the length from the top to the anchoring point (which is actually just A) and the length from the anchoring point to the bottom of the box.

Since the anchoring point sits A down on the box, the length of the top half of the box is given by A*H.  However, this is not the distance we want.  We want the distance down the rest of the box.  That will be (1 - A)*H.

So, our two distances are: A and (1 - A)*H.  They should add together to be P.  Therefore, our equation is:

A + (1 - A)*H = P

However, we want to solve this for A - we already know our box height (H), and we know what position we want this to extend to (P).  Therefore, we have to solve for A:

A + (1 - A)*H = P
A + H - A*H = P
A - A*H = P - H
A*(1 - H) = P - H
A = (P - H) / (1 - H)

And this gives a perfect calculation for A.  In my case, since P is 0.5, I just do A = (0.5 - H) / (1 - H) and it comes out perfectly.

It should be fairly simple to come up with similar calculations for top alignment and center alignment.  Additionally, these work with right-to-left alignments as well.

May 28, 2015

Snippets / ActiveMerchant, Authorize.Net Root Certificates, and Linux SSL

JB

This week, Authorize.net is starting its rollout of new server certificates, signed by a new certificate authority - Entrust.  If you are using ActiveMerchant, this could break your stuff!  It may or may not affect newer versions of ActiveMerchant - I don't know, I only have sites using old versions (like 1.18).  So, even if it breaks, the solution may be different depending on your version of ActiveMerchant.  Still, I'll try to keep the info detailed enough that you can find the solution yourself.

First, the problem.  This affects you if, when ActiveMerchant tries to connect to Authorize.net, you receive the error:

SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B:
  certificate verify failed

Note that you may not get this error when trying to connect to Authorize.net via manual Ruby code, but only via ActiveMerchant.

The reason for this is ActiveMerchant maintains its own list of Root CAs!

Therefore, to fix, you have to, (a) make sure that the new Root CAs for authorize.net are stored in your server's root CA list, and (b) make sure that ActiveMerchant is using your server's list rather than its own.  For good measure, you can also copy ActiveMerchant's CA list to yours.

So, first, to add the Entrust root CAs to your Linux server (assuming it is a fairly recent CentOS-based distro), do the following as root:

  1. Enable the trust infrastructure with the following command:update-ca-trust enable
  2. Go to /etc/pki/ca-trust/source/anchors
  3. Grab the .cer files from https://www.entrust.net/downloads/root_request.cfm (just right-click on the links, copy the URLs, and download them to the current directory with wget)
  4. Go to wherever the gem files for your project are, and find the active_utils gem.  In the lib/certs directory, there is a file called cacert.ca or something like that.  Copy that to /etc/pki/ca-trust/source/anchors
  5. Now run the following command to update your CA list:
    update-ca-trust

Now you have all of the certs installed.  Now we need to tell ActiveMerchant to use your own certs rather than its internal list.  However, again, this is in the active_utils gem, not the activemerchant gem.  Within the gem, find the file called lib/active_utils/common/connection.rb.  Now look for a function called configure_ssl.  Copy that function to your clipboard.

Now create a file in your own project called config/initializers/active_merchant_pem_fix.rb (the name doesn't matter as long as it is in config/initializers).  In this file you need to have:

module ActiveMerchant
  class Connection
    # Paste the configure_ssl function you copied from lib/active_utils/common/connection.rb here.
  end
end

Now, there will be a line that says something like this:

        http.ca_file = File.dirname(__FILE__) + '/../../certs/cacert.pem'

This is the offending line!!!  Comment that sucker out!!!

For my version of ActiveMerchant, this is what my file looks like:

   def configure_ssl(http)
      return unless endpoint.scheme == "https"

      http.use_ssl = true

      if verify_peer
        http.verify_mode = OpenSSL::SSL::VERIFY_PEER
        # http.ca_file = File.dirname(__FILE__) + '/../../certs/cacert.pem'
      else
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
      end
    end

Now that you have the updated root CA on your server, and ActiveMerchant is using your server's list rather than your own, you are all set!

February 12, 2014

Snippets / Creating a WSDL file for a RESTful Web Service

JB

NOTE - this is about XML-based REST services.  There *might* be a way to write WSDL for JSON, but I don't know it yet.

Most of my work is done in RESTful environments.  However, the Microsoft world seems to favor SOAP.  REST is simple.  It is so simple, that you don't even really need special APIs for the different services.  You just need the URL and the documentation.  However, SOAP is complex, and requires complex tooling. SOAP services are described using the WSDL format.  Usually what happens is that a program reads the WSDL file and spits out stubs that you can code against directly.

In any case, the people that come from a SOAP environment don't realize that you can do cool stuff without tooling, and the idea that I could just give them a URL to hit and they could just hit it directly boggles their mind.  So, if I have a handy-dandy REST service, they still want to ask me for the WSDL file.  There's really no reason, but that's what they want.  So, thankfully, WSDL 2 allows for us to create WSDL descriptions of RESTful services.

So, before I describe how WSDL works, let me first say that, if you are used to the simplicity of REST coding, WSDL will blow your mind.  WSDL is built around the complexities of SOAP.  There are a lot of things where you will say, "why did they put in that extra layer?"  The answer is, "because the SOAP protocol was built by crack addicts."

WSDL is grouped into 4 parts - services, bindings, interfaces, and types.

An interface is a list of operations you might want to perform.  This is, essentially, the API itself.  Our API will be for retrieving form submissions to our website.  The interface is a list of operations, where each operation is an individual API call.  In our example, we will have a "list" and a "get" operation in our inteface.  The interface relies on types in order to define the input and output of the operations.

A binding tells you how to make an API call for an operation.  This is significantly simpler in REST than in SOAP, as you really only need the URL path and the method (this is the relative URL - the absolute path will be defined in the service).  The binding will have a operation tag for each operation it binds.

A service defines where on the web you can go to in order to find the interfaces.  It defines a set of endpoints, each of which tells the top-level URL of the resources, like http://www.example.com/api, and what interfaces they support and which bindings to use on them (telling both the interface and the binding seems redundant to me, but then again so does writing a WSDL file to begin with).

So, below, is a simple REST web service.  I've excluded the "types" section, which I will discuss later.

<?xml version="1.0" ?>
<wsdl2:description
xmlns:wsdl2="http://www.w3.org/ns/wsdl"
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl"
xmlns:whttp="http://www.w3.org/ns/wsdl/http"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:myexample="http://www.example.com/api"
targetNamespace="http://www.example.com/api"
<wsdl2:types>
<xs:schema
attributeFormDefault="unqualified"
elementFormDefault="unqualified">
<!-- omitted -->
</xs:schema>
</wsdl2:types>

<wsdl2:interface name="FormSubmissionsInterface">
<wsdl2:operation
name="listFormSubmissionsOperation"
pattern="http://www.w3.org/ns/wsdl/in-out"
whttp:method="GET">
<wsdl2:input element="formSubmissionSearchParameters" />
<wsdl2:output element="response" />
</wsdl2:operation>
<wsdl2:operation
name="getFormSubmissionOperation"
pattern="http://www.w3.org/ns/wsdl/in-out"
whttp:method="GET">
<wsdl2:input element="formSubmissionIdentifier" />
<wsdl2:output element="response" />
</wsdl2:operation>
</wsdl2:interface>

<wsdl2:binding name="FormSubmissionBinding"
interface="myexample:FormSubmissionBinding"
type="http://www.w3.org/ns/wsdl/http">
<wsdl2:operation
ref="myexample:getFormSubmissionOperation"
whttp:method="GET"
whttp:location="form_submissions/{id}.xml"
/>
<wsdl2:operation
ref="myexample:listFormSubmissionsOperation"
whttp:method="GET"
whttp:location="form_submissions.xml"
/>
</wsdl2:binding>

<wsdl2:service
name="FormSubmissionsServiceSSL"
interface="myexample:FormSubmissionsInterface">
<wsdl2:endpoint
name="FormSubmissionEndpoint"
binding="myexample:FormSubmissionBinding"
address="https://www.example.com/api"
/>
</wsdl2:service>
<wsdl2:service
name="FormSubmissionsService"
interface="myexample:FormSubmissionsInterface">
<wsdl2:endpoint
name="FormSubmissionEndpoint"
binding="myexample:FormSubmissionBinding"
address="http://www.example.com/api"
/>
</wsdl2:service>
</wsdl2:description>

So, starting at the bottom, we have 2 services defined - an HTTP service and an HTTPS service.  They both use the same binding and interface.  So, they are called exactly the same way, buy have a different base URL.  Depending on your client, the service name often corresponds to the main class name used to access the service.

The services implement the myexample:FormSubmissionsInterface.  This interface defines two operations: listFormSubmissionsOperation and getFormSubmissionsOperation.  Each one defines its input and output parameters.  Now, the output parameter is just a regular XML element from your schema (defined in the "types" section).  The input parameter is special.  It, too, is defined in your types section, but, when bound to an HTTP endpoint, rather than being treated as an XML document, it will be treated as a combination of URL pieces and query string parameters.  Here is mine:

                       <xs:element name="formSubmissionSearchParameters">
<xs:complexType>
<xs:all>
<xs:element name="since" type="xs:dateTime" minOccurs="0" />
<xs:element name="since_id" type="xs:integer" minOccurs="0" />
<xs:element name="until" type="xs:dateTime" minOccurs="0" />
<xs:element name="until_id" type="xs:integer" minOccurs="0" />
</xs:all>
</xs:complexType>
</xs:element>

Each of the elements under xs:all actually become query-string parameters.  WSDL treats it like an XML document, because of its SOAP roots.  But, when we specify an HTTP binding, then this gets transmogrified from an XML document into query-string parameters.  I have a separate type for the "get" operation, which defines the ID number of the form submission I want:

			<xs:element name="formSubmissionIdentifier">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:integer" />
</xs:sequence>
</xs:complexType>
</xs:element>

As we will see, in the bindings section, we can move a named parameter into the URL itself, rather than just being sent as a query string.

The output is simply an XML document, whose structure is defined in the types section.  I would need to teach you XSD documents to tell you how this works, but you can google it yourself.  The one thing I will say is that because my result document does not use namespaces at all, in the xs:schema declaration I set elementFormDefault to "unqualified".

The binding is where we tell WSDL that we are using REST rather than SOAP.  We tell it what interface we are binding, and the fact that we are using an HTTP binding.  Then, for each operation, we define the HTTP method and relative path for the binding.  We can substitute in our parameters into the URL path by saying {name-of-xml-input-element} in the location.  Any parameters given which are not inserted into the URL path will be put in the query string.  So, for instance

whttp:location="form_submissions/{id}.xml"

Says to take the "id" element and substitute it in the URL.  The other location,

whttp:location="form_submissions.xml"

just puts all of the parameters in the query string.

So that's it!  That's a WSDL file for a REST service!

I tested my service and WSDL using AXIS 2's WSDL2JAVA command:

wsdl2java.sh -uri path/to/wsdl -wv 2

Then I created a simple TestMe.java to make sure it ran right.  The example also shows how to use HTTP Basic Authentication with the service:

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.math.*;

import org.apache.axis2.databinding.ADBBean;
import org.apache.axis2.Constants;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.XmlValidationError;
import org.apache.axis2.client.*;


import com.example.www.FormSubmissionsServiceStub;
import com.example.www.FormSubmissionsServiceStub.*;

import org.apache.axis2.transport.http.*;

public class TestMe {
public static void main(String[] args) throws RemoteException {
/* Setup Service */
FormSubmissionsServiceStub stub = new FormSubmissionsServiceStub();

/* If you need authentication */
HttpTransportProperties.Authenticator basicAuth = new HttpTransportProperties.Authenticator();
basicAuth.setUsername("USERNAMEHERE");
basicAuth.setPassword("PASSHERE");
basicAuth.setPreemptiveAuthentication(true);
final Options clientOptions = stub._getServiceClient().getOptions();
clientOptions.setProperty(HTTPConstants.AUTHENTICATE, basicAuth);

/* Setup Single Get */
BigInteger subm_id = BigInteger.valueOf(500);
FormSubmissionIdentifier myid = new FormSubmissionIdentifier();
myid.setId(subm_id);
System.out.println(TestMe.debugAxisObject(myid));

/* Run Single Get */
Response r = stub.getFormSubmissionOperation(myid);

/* Setup Multi Get */
FormSubmissionSearchParameters p = new FormSubmissionSearchParameters();
p.setSince_id(BigInteger.valueOf(3200));
System.out.println(TestMe.debugAxisObject(p));

/* Run Multi Get */
Response r2 = stub.listFormSubmissionsOperation(p);
}
}

Note that "Response" is not a generic Java class, it is actually com.example.www.FormSubmissionsServiceStub.Response, because the name of the output element is "response".

Other Resources:

October 30, 2013

Snippets / Getting my Inspiron 8000 to run Ubuntu 13.10

JB

This is for my own benefit if I need to reinstall again.

Links to helpful articles:

What I remember doing:
  1. I installed using a text-mode install from the network installer
  2. I booted into recovery mode root prompt (don't forget to remount / readwrite!)
  3. I modified /etc/default/grub to add "nolapic nomodeset" to the default parameter
  4. I made s imple xorg.conf file to say that Card0 should use vesa
Then everything seemed to work.  It shows it in a small box, though, and that is better than nothing.

May 29, 2013

Snippets / Adding a "Copy" context menu to a disabled UITextfield in iOS

JB

I have a project which has a lot of fields which, at any moment, may be enabled or disabled.  However, even when they are disabled, I want to show a context menu.  It turns out that no one has a solution, so I hacked one together for myself.  This code is adapted from a working project, but I have not tested the version presented here, but let me know if you have problems.  

The general problem you run into is this: if your UITextField is disabled, then it cannot receive touch events.  Therefore, it cannot fire the menu, even if you try to do so explicitly using Gesture recognizers.  The normal answer is to simply not allow any editing on the field.  However, while this prevents you from editing the field, it still *draws* the textfield as active.  We want it to *look* inactive as well, which you only get by disabling it.

So, what I did was to create a subclass of UITextField (JBTextField) which has a subview (a specialized subclass of UIView - JBTextFieldResponderView) that has a gesture recognizer attached.  We override hitTest on JBTextField to do this: if JBTextField is disabled, then we delegate hitTest to our JBTextFieldResponderView subclass.

As I said, this is from another project, which had a whole bunch of other stuff mixed in.  I have tried to separate out the code for you here, but I don't guarantee that it will work out-of-the-box.

@interface JBTextFieldResponderView : UIView
@property (strong, nonatomic) UIGestureRecognizer *contextGestureRecognizer;
@end

@interface JBTextField : UITextField
@property (strong, nonatomic) JBTextFieldResponderView *contextGestureRecognizerViewForDisabled;
@end
@implementation JBTextFieldResponderView
@synthesize contextGestureRecognizer = _contextGestureRecognizer;

-(id) initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];

  // Add the gesture recognizer
  self.contextGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGestureDidFire:)];
  [self addGestureRecognizer:self.contextGestureRecognizer];

  return self;
}

// First-level of response, filters out some noise
-(IBAction) longPressGestureDidFire:(id)sender {
  UILongPressGestureRecognizer *recognizer = sender;
  if(recognizer.state == UIGestureRecognizerStateBegan) { // Only fire once
    [self initiateContextMenu:sender];
  }
}

// Second level of response - actually trigger the menu
-(IBAction) initiateContextMenu:(id)sender {
  [self becomeFirstResponder]; // So the menu will be active.  We can't set the Text field to be first responder -- doesn't work if it is disabled
  UIMenuController *menu = [UIMenuController sharedMenuController];
  [menu setTarget:self.bounds inView:self];
  [menu setMenuVisible:YES animated:YES];
}

// The menu will automatically add a "Copy" command if it sees a "copy:" method.  
// See UIResponderStandardEditActions to see what other commands we can add through methods.
-(IBAction) copy:(id)sender {
  UIPasteboard *pb = [UIPasteboard generalPasteboard];
  UITextField *tf = (UITextField *)self.superview;
  [pb setString: tf.text];
}

// Normally a UIView doesn't want to become a first responder.  This forces the issue.
-(BOOL) canBecomeFirstResponder {
  return YES;
}

@end

@implementation JBTextField
@synthesize contextGestureRecognizerViewForDisabled = _contextGestureRecognizerViewForDisabled;

/* Add the recognizer view no matter which path this is initialized through */

-(id) initWithCoder:(NSCoder *)aDecoder {
  self = [super initWithCoder:aDecoder];
  [self addDisabledRecognizer];
  return self;
}

-(id) initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  [self addDisabledRecognizer];
  return self;
}

// This creates the view and adds it at the end of the view chain so it doesn't interfere, but is still present */
-(void) addDisabledRecognizer {
  self.contextGestureViewForDisabled = [[JBTextFieldResponderView alloc] initWithFrame:self.bounds];
  self.contextGestureViewForDisabled.userInteractionEnabled = NO;
  [self addSubview:self.contextGestureViewForDisabled];
}

-(void) setEnabled:(BOOL) enabled {
  [super setEnabled:enabled];
  self.contextGestureViewForDisabled.userInteractionEnabled = !enabled;
}

// This is where the magic happens
-(UIView *) hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  if(self.enabled) {
    // If we are enabled, respond normally
    return [super hitTest:point withEvent:event];
  } else {
    // If we are disabled, let our specialized view determine how to respond
    UIView *v = [self.contextGestureViewForDisabled hitTest:point withEvent:event];
  }
}

@end

Now, in interface builder, we can enabled copy on disabled fields just by setting the class to JBTextField, or, if built programmatically, by replacing [UITextField alloc] with [JBTextField alloc].

I hope this helps!

Additional Resources:

 

 

 

August 05, 2012

Snippets / Simple Sage Plots for Teaching Functions

JB

I'm teaching calculus to some homeschool students this year, and I wanted to have a bunch of function plots printed out to show them.  Therefore, I turned to Sage, which I have never used.  It is great!  You have to know a few other math software pieces to really understand it, but it's not that bad.  Someone needs to do a general "how to use sage", and some day I might.

Anyway, here's my quick function for plotting graphs for students that some of you might like.

In one of the evaluation boxes, type the following:

def simpleplot(f, range=(-5,5,-5,5)):
p = plot(f, (x, range[0], range[1]))
p += text("$" + latex(f) + "$", (0, range[2] - (range[3] - range[2]) * 0.1), fontsize=30, rgbcolor=(1,0,0))
p.axes_range(range[0], range[1], range[2], range[3])
show(p, figsize=12)

What this does is create a function called "simpleplot" which will do the following:

  • Plot the given function on a default domain of -5, 5
  • Typeset the function using LaTeX, and insert the function slightly below the graph (I calculated it at 10% of the graph size).  It prints in red at 30 point size.
  • Set the axes to be -5, 5, -5, 5 (I used uniform axes on all the functions so they can compare the differences between graphs easier)
  • Draw the whole thing at 3x the normal size (figsize=12 - default is 4).

Now, I can just do:

simpleplot(1/x)

And it gives me my output!

July 23, 2012

Snippets / DVD Rescue! Getting files off of an unfinalized DVD

JB

Just wanted to shout out and say thanks to Flay for saving me!  I had a DVD that I forgot to finalize, but Flay had a nice set of instructions for how to manually copy off the files from the DVD drive.

Now I can finish editing and posting the videos for the Engineering and Metaphysics conference.

June 26, 2011

Snippets / Misc stuff I found out

JB

Objective-C PostgreSQL stuff:


Misc:
Building a shared library on OSX: (see here):
gcc -dynamiclib -install_name /usr/local/lib/libfoo.2.dylib  -compatibility_version 2.4 -current_version 2.4.5 -o libfoo.2.4.5.dylib source.o code.o

I have also seen the options -undefined suppress -flat_namespace here

Another dylib link

Caution mixing shared and archive libs

Seth Godin - how to run a useless conference

 

June 10, 2010

Snippets / Cross-browser DIV Rotation by arbitrary degrees in CSS and/or javascript

JB

I've spent most of the day on this, so I thought I would share.  Most people don't know this, but CSS3 has some *really* cool stuff coming - auto-rounded corners, gradients, and.... DIV rotations!  Unfortunately, it will be about 5 years before any of this becomes mainstream.  IE8 only has the very beginnings of CSS3 support.

So is there any way to get this on current browsers?  Yes!  The fine folks at CSS3Please.com have given us a way to do some of these cool effects using existing CSS.  If you just want, just go their, edit the stylesheet on the web page (yes! it is editable!), and copy/paste the resulting CSS into your CSS file.

But what if you want dynamic rotation?  Well, I looked at the CSS3Please source code, and figured out how it was all being calculated.  The IE6, IE7, and IE8 part is the worst, because it relies on a DirectX Matrix transform operation (isn't Internet Explorer always fun to work with?).  Not superfun, but do-able.  However, to get this calculated, we will need a Matrix library.  So, first, download and install the sylvester javascript library.  Then, put this javascript into your page:

<script src="sylvester.js"></script>
<script type="text/javascript">
function degreesToRadians(num) {
	return (num) * Math.PI / 180;
}
function createIEMatrixString(M) {
	return 'M11=' + M.e(1, 1) + ', M12=' + M.e(1,2) + ', M21=' + M.e(2,1) + ', M22=' + M.e(2,2);
}
function rotateElement(e, deg) {
	deg_str = deg + "";
	rotate_transform = "rotate(" + deg + "deg)";
	matrix_str = createIEMatrixString(Matrix.Rotation(degreesToRadians(deg)));
	filter_str = "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', " + matrix_str + ")";

	e.style["rotation"] = deg_str + "deg"; // CSS3
	e.style.MozTransform = rotate_transform; // Moz
	e.style.OTransform = rotate_transform; // Opera
	e.style.WebkitTransform = rotate_transform; // Webkit/Safari/Chrome
	e.style.filter = filter_str; // IE 6/7
	e.style.MsFilter = filter_str; // IE 8
	e.style["zoom"] = "1"; // ??? Probably IEs
}
</script>

Now, you can just do:

rotateElement(document.getElementById("whatever"), 20);

And that will rotate the element.  Note that this *sets* the rotation, so if I do this several times it will only keep the rotation at what I set it to, it won't keep on adding rotations.

Anyway, a little bit of work, a little bit of code for your client to download, but it works.

UPDATE - just found this jquery plugin that *might* do something similar, but haven't looked at it closely yet.