Using ColdFusion to sign data for single sign
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!
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.
链接地址: http://www.djcxy.com/p/37232.html下一篇: 使用ColdFusion为单点登录数据