8

I apologize in advance for the length of this post. I don't really know enough about this issue to properly identify what the specific problem may actually be! But at any rate, we've been making calls against our membership API to query info about our members (join dates, membership types, etc.) using steps and advice provided by @Leigh here and they have been working great! Thanks again, Leigh, our members are very happy to be able to do this!

Now I want to set up single sign-on for our members, allowing them to log in at our page and then be whisked over to their member profile, already logged in on that site. According to the API documentation, one thing I need to do is:

"Use your Signing Certificate to sign the Portal username of the person to log in."

I am totally stuck on this. I've been provided with an XML private key (generated by their .NET application) in the form

<RSAKeyValue><Modulus>{stuff}</Modulus><Exponent>{stuff}</Exponent><P>... etc etc

I gather that I am unable to work with this format directly and must convert it to PEM format or similar. Using OpenSSL, I think I have done this and now have a file in the format of "-----BEGIN PRIVATE KEY-----{stuff}-----END PRIVATE KEY-----."

Using Leigh's solution does give me a signature, but it does not match the example provided in the API docs. I think this is because it uses HmacSHA1, whereas they note that "the signature in the header uses HMAC SHA1 whereas the signature for creating security tokens uses a public/private keypair and RSA-SHA1. The same method cannot be used to generate both." I tried changing

<cfset key = key.init(jKey,"HmacSHA1") />

to

<cfset key = key.init(jKey,"RSA-SHA1") />

and got "Algorithm RSA-SHA1 not available."

I have tried copying and pasting some other suggested solutions, but none of them work. One example (gotten from 12Robots.com):

<!--- Create a Java Cipher object and get a mode --->
<cfset cipher = createObject('java', 'javax.crypto.Cipher').getInstance("RSA") />

<!--- The mode tells the Cipher whether is will be encrypting or decrypting --->
<cfset encMode = cipher.ENCRYPT_MODE />

<cfset encryptedValue = "" /> <!--- Return variable --->

<!--- Initialize the Cipher with the mode and the key --->
<cfset cipher.init(encMode, key) />

<!--- Convert the string to bytes --->
<cfset stringBytes = stringToSign.getBytes("UTF8") />

<!--- Perform encryption --->
<cfset encryptedValue = cipher.doFinal(stringBytes, 0, len(inputString)) />

<cfdump var="#encryptedValue#">

"Key" in this instance is the PEM text I mentioned earlier and "stringToSign" is the username. The error I get is "Either there are no methods with the specified method name and argument types or the init method is overloaded with argument types that ColdFusion cannot decipher reliably. ColdFusion found 0 methods that match the provided arguments. If this is a Java object and you verified that the method exists, use the javacast function to reduce ambiguity."

Another thing I have tried is:

<cfset rsaPrivateKey = toBase64(key, "utf-8")>

<cfset jKey = JavaCast("string", rsaPrivateKey)>
<cfset jMsg = JavaCast("string", stringToSign).getBytes("ASCII")>

<cfset key = createObject("java", "java.security.PrivateKey")>
<cfset keySpec = createObject("java", "java.security.spec.PKCS8EncodedKeySpec")>

<cfset keyFactory = createObject("java", "java.security.KeyFactory")>
<cfset b64dec = createObject("java", "sun.misc.BASE64Decoder")>
<cfset sig = createObject("java", "java.security.Signature")>

<cfset byteClass = createObject("java", "java.lang.Class")>
<cfset byteArray = createObject("java", "java.lang.reflect.Array")>

<cfset byteClass = byteClass.forName(JavaCast("string", "java.lang.Byte"))>
<cfset keyBytes = byteArray.newInstance(byteClass, JavaCast("int", "1024"))>
<cfset keyBytes = b64dec.decodeBuffer(jKey)>

<cfset sig = sig.getInstance("SHA1withRSA", "SunJSSE")>
<cfset sig.initSign(keyFactory.getInstance("RSA").generatePrivate(keySpec.init(keyBytes)))>
<cfset sig.update(jMsg)>
<cfset signBytes = sig.sign()>

<cfset finalSig = ToBase64(signBytes)>

<cfdump var="#finalSig#">

Which gives me "java.security.InvalidKeyException: invalid key format." BTW if I set rsaPrivateKey to just "key" I get a different error, "java.security.InvalidKeyException: IOException : DerInputStream.getLength(): lengthTag=127, too big." I am pleased to be getting different error messages; at least something is happening! :-)

Again, I do not know what these Java functions are doing. And I surely am not getting why something seemingly straightforward has ended up being so complicated! But my suspicion is, I have either stored the private key PEM incorrectly, or am reading out of the database incorrectly (or both), and that is what is contributing to causing these various solutions to fail. But I don't know enough to say for sure if that is the case.

I would welcome any insight or suggestions that might help me out! If anybody needs more info, I am happy to provide that. Thank you all very much in advance!

Community
  • 1
  • 1
daltec
  • 447
  • 1
  • 5
  • 15

1 Answers1

5

I gather that I am unable to work with this format directly and must convert it to PEM format or similar

Nothing wrong with doing that, but it is not technically required. The key information can be loaded either from a PEM file OR directly from the XML.

Option 1: Load Key from XML:

Parse the sample XML string into an object. Then extract the modulus and private exponent (ie the <D> element). Use the modulus and exponent to create a RSAPrivateKeySpec and load the RSA private key:

xmlKeyString = "<RSAKeyValue><Modulus>........</D></RSAKeyValue>";
xmlDoc = xmlParse(xmlKeyString);
modBytes = binaryDecode(xmlDoc.RSAKeyValue.Modulus.xmlText, "base64");
dBytes = binaryDecode(xmlDoc.RSAKeyValue.D.xmlText, "base64");
modulus = createObject("java","java.math.BigInteger").init(1, modBytes);
exponent = createObject("java","java.math.BigInteger").init(1, dBytes);
keySpec = createObject("java", "java.security.spec.RSAPrivateKeySpec").init(modulus, exponent);
keyFactory = createObject("java", "java.security.KeyFactory").getInstance("RSA");
privateKey = keyFactory.generatePrivate(keySpec);

Option 2: Load Key from PEM file:

Read the PEM file into a variable. Remove the header/trailer ie, "---BEGIN/END RSA PRIVATE KEY-----". Then decode the base64 contents and load the private key using a KeyFactory:

rawKey = replace( pemContent, "-----BEGIN RSA PRIVATE KEY-----", "" );
rawKey = replace( rawKey, "-----END RSA PRIVATE KEY-----", "" );
keyBytes = rawKey.trim().binaryDecode("base64");
keySpec = createObject("java", "java.security.spec.PKCS8EncodedKeySpec");
keyFactory = createObject("java", "java.security.KeyFactory").getInstance("RSA");
privateKey = keyFactory.generatePrivate(keySpec.init(keyBytes));

After you have loaded the private key, you can use a Signature object to perform the SHA1 hash and generate the signature with the RSA key:

stringToSign = "test@membersuite.com";
signer = createObject("java", "java.security.Signature").getInstance("SHA1withRSA");;
signer.initSign(privateKey);
signer.update( stringToSign.getBytes("us-ASCII"));
signedBytes = binaryEncode(signer.sign(), "base64");

writeDump(signedBytes);

Result (using sample XML):

jTDKoH+INi19kGWn7WRk/PZegLv/9fPUOluaM57x8y1tkuwxOiyX86gxsZ7gU/OsStIT9Q5SVSG5NoaL3B+AxjuLY8b7XBMfTXHv2vidrDkkTTBW0D2LsrkZ3xzmvvPqqfA3tF2HXUYF+zoiTsr3bQdA32CJ+lDNkf+QjV3ZEoc= 

NB: Whichever method you choose, properly securing private keys is VERY important. Once you have the sample working, definitely read up on how to best store and secure private keys.

Community
  • 1
  • 1
Leigh
  • 28,765
  • 10
  • 55
  • 103
  • 1
    Leigh, truly, I don't know how to thank you. I've spent many hours trying to get my head around this, to no avail. So I tried your initial post (Option 1) and it "just works." Using the test data, I got signed data that matches the example! I'm using it now to test against the API, and *finally* feel as if I am making progress! I've taken your other advice to heart and am reading up now on storing my key. In fact, once I have finished testing everything, I will generate a new one. So THANK YOU very much, Leigh, again! I had been very frustrated -- you assistance is greatly appreciated! – daltec Nov 23 '16 at 02:02
  • 1
    Another thing is that I am reading up on RSA signing and cryptography in general. It's way over my head, but I am curious: is the element always the private exponent? What then are

    , , and all the other parts? And most importantly, how the heck do you KNOW all of this stuff? :-) Are you the CEO of Setec Astronomy? C'mon, admit it, haha! Well at any rate, many many thanks, again. I greatly appreciate your help and willingness to so patiently explain things. You are a great asset to Stack Overflow!

    – daltec Nov 23 '16 at 02:14
  • Hehe ... maybe ;-) or lots of reading. Anyway, glad it helped. Yes, the private exponent is always ``, see [XML specs](https://www.w3.org/TR/2003/WD-xkms2-20030418/#XKMS_2_0_LC2_Section_1_3). The RSA stuff is a bit involved, but you can get a high level view in the section ["..here is the procedure for creating the RSA components.."](http://www.codeproject.com/Articles/10877/Public-Key-RSA-Encryption-in-C-NET). The other numbers are [the original primes .. and some precomputed values](http://stackoverflow.com/questions/5527423/porting-net-rsa-xml-keys-to-java#comment6280234_5527566). – Leigh Nov 23 '16 at 14:44
  • Thanks a lot for these pointers, Leigh. I'm working through the RSA Crypto Tutorial you at codeproject.com. The explanations on the other links are starting to make a bit more sense now! I really appreciate the assistance -- I truly was guessing wildly and had no idea of what I was trying to do. I'm still guessing, but maybe not quite as wildly as I was the other day! My weekend project will be to actually implement SSO on our site - it will be a nice new feature that our members will be happy with. So THANK YOU again for all of your help, on their behalf as well. Have a great weekend, Leigh! – daltec Nov 23 '16 at 16:24
  • You are very welcome. I remember that stumbling-around-in-the-dark feeling when reading up on DH key exchanges ;-) For non-mathematicians it usually takes a few reads (and then a few more ...) to begin sinking in. Happy Thanksgiving! – Leigh Nov 23 '16 at 17:45
  • Leigh, I wanted to thank you again for your kindness in showing me how to get SSO going. Members can now renew, search the directory, register for events and classes, make payments, and more, all straight from our website and with only having to log in once. Members are happy with these new features, and I will soon be adding more. In fact I am taking what I am learning about security and applying it to other apps as well. None of this would have been possible without your patience and expertise in showing the way! So on behalf of our members and myself, THANK YOU! Happy Holidays, Leigh! – daltec Dec 03 '16 at 01:56
  • Any time :-) Always nice to add features that make for a smoother user experience, and "happy" users. Cheers. – Leigh Dec 06 '16 at 17:36