Hi Jasha, Great to hear that you and Okke are taking this on -- it will be awesome to get security tokens and three-legged OAuth working.
One thing that might help in general is to just try to do one piece of this at a time -- it sounds like your trying to get security tokens, three-legged OAuth and alternate config location loading all working at the same time which seems like it could be a challenge... It would also probably be good to commit them one piece at a time too so it will be easier for us to understand what the changes do (which you may have been planning to do already). When I ran through this when working on OSEC internally I ran into issues with getting all the right keys setup in the right format and in the right place. I ended up creating a script to create everything which I thought might be helpful (I did some search/replace on paths and filenames so hopefully I didn't break anything): ----------- BEGIN SCRIPT ----------- # local variables TMP_DIR=/tmp/shindig_keys PRIVATE_KEYS_DIR=/private/shindig/keys PUBLIC_KEYS_DIR=/public/shindig/keys # make a temporary working directory mkdir $TMP_DIR cd $TMP_DIR # generate our private key -- this is used by shindig to sign requests openssl genrsa -out portal_signing_key_private.pem 1024 # create a pkcs8 formatted version of the private key -- this seems to be the format shindig wants the key in -- this is the filed used for shindig.signing.key-file in shindig.properties openssl pkcs8 -in portal_signing_key_private.pem -out portal_signing_key_private.pk8.pem -topk8 -nocrypt -outform PEM # create a DER formatted version of the private key in case we need it at some point in the future openssl rsa -in portal_signing_key_private.pem -inform PEM -out portal_signing_key_private.der -outform DER # export the public key component in PEM format - this could be used by third parties to validate the signature on shindig signed requests openssl rsa -in portal_signing_key_private.pem -pubout -out portal_signing_key_public.pem # export the public key component in DER format - this could be used by third parties to validate the signature on shindig signed requests openssl rsa -in portal_signing_key_private.pem -pubout -out portal_signing_key_public.der -outform DER # create a new x509 certificate based on our private key and write it out in PEM format - this could be used by third parties to validate the signature on shindig signed requests openssl req -new -x509 -key portal_signing_key_private.pem -days 365 -subj '/C=US/ST=MA/L=Bedford/O=Foo Corp/OU=Foo/CN=foo.example.com' -out portal_signing_certificate.pem # create a DER formatted version of our x509 certificate - this could be used by third parties to validate the signature on shindig signed requests openssl x509 -in portal_signing_certificate.pem -inform PEM -out portal_signing_certificate.der -outform DER # generate the shared secret symmetric key used by shindig and the container to encrypt/decrypt gadget security tokens - this is the file used for gadgets.securityTokenKeyFile in container.js dd if=/dev/random bs=32 count=1 | openssl base64 > portal_security_token_key_private.txt # copy the private keys to the shindig directory mkdir $PRIVATE_KEYS_DIR cp portal_signing_key_private.* portal_security_token_key_private.txt $PRIVATE_KEYS_DIR # copy the public keys and certificates to the public keys directory mkdir $PUBLIC_KEYS_DIR cp portal_signing_key_public.* portal_signing_certificate.* $PUBLIC_KEYS_DIR # remove the temporary working directory cd .. rm -rf $TMP_DIR ----------- END SCRIPT ----------- Additional comments inline below. >-----Original Message----- >From: Jasha Joachimsthal [mailto:[email protected]] >Sent: Thursday, June 23, 2011 8:37 AM >To: [email protected] >Subject: 3-legged oAuth > >Hi, > >Okke and I are trying to get 3 legged oAuth working in Rave but we're stuck >on passing the security token from the portal to the (Shindig) container. > >What we have done so far: >- generated a key using >openssl req -newkey rsa:1024 -days 365 -nodes -x509 -keyout testkey.pem - >out >testkey.pem -subj '/CN=mytestkey' >openssl pkcs8 -in testkey.pem -out oauthkey.pem -topk8 -nocrypt -outform >PEM > >- replaced the default PropertiesModule by a properties module that can read >shindig properties from a different location >- configured our custom shindig properties to read a different container.js >in shindig.containers.default > >In our custom container.js we've set >"gadgets.securityTokenType" : "secure", >"gadgets.securityTokenKeyFile" : "/path/to/my/oauthkey.pem", #with the >correct location of course This doesn't look like the right file to me -- this looks like what shindig might use to sign outbound requests, not what's used for encrypting/decrypting security tokens. Using the script I posted above I think the path you'd want would be: /private/shindig/keys/portal_security_token_key_private.txt > >- replaced CommonContainerAuthGuiceModule with a custom module that >loads DefaultSecurityTokenCodec instad of >CommonContainerSecurityTokenCodec It doesn't seem like you should need to do this -- the CommonContainerSecurityTokenCodec seems to use the BlobCrypterSecurityTokenCodec by default when it encounters the "secure" configuration in container.js which seems like what we want. >- added a gadget from http://gadgets.jasha.eu/oauthtest.xml (and registered >the domain in Google) >- added consumer key & secret to config/oauth.json > >Now when I add this gadget to my page, I get a HTTP Status 401 - Malformed >security token %st% Invalid security token %st% response. >This makes sense because the portal doesn't pass a security token to >Shindig. This could be done by generating a token like in [1], in Rave >probably in OpenSocialWidgetRenderer, and then set the security token to Agreed -- OpenSocialWidgetRenderer does seem like the right place to do this but I think I might push the actual work off to a SecurityTokenService which takes the RegionWidget and returns the SecurityToken. We've updated our SecurityTokenService internally and haven't pushed those updates back to the public OSEC codebase yet, so I'll post what we have currently here in hopes that it might be useful (the changes from what's in public OSEC currently is basically that this service no longer does any URL encoding of the values it deals with since this really isn't the right architectural layer to be doing that work): ---------- import org.apache.shindig.auth.SecurityToken; import org.mitre.portal.model.PersonGadget; import org.mitre.portal.service.exception.SecurityTokenException; public interface SecurityTokenService { SecurityToken getSecurityToken(PersonGadget personGadget) throws SecurityTokenException; String getEncryptedSecurityToken(PersonGadget personGadget) throws SecurityTokenException; SecurityToken decryptSecurityToken(String encryptedSecurityToken) throws SecurityTokenException; String refreshEncryptedSecurityToken(String encryptedSecurityToken) throws SecurityTokenException; } ---------- import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.shindig.auth.BlobCrypterSecurityToken; import org.apache.shindig.auth.MitreBlobCrypterSecurityToken; import org.apache.shindig.auth.SecurityToken; import org.apache.shindig.common.crypto.BasicBlobCrypter; import org.apache.shindig.common.crypto.BlobCrypter; import org.apache.shindig.common.util.CharsetUtil; import org.mitre.portal.model.Gadget; import org.mitre.portal.model.Person; import org.mitre.portal.model.PersonGadget; import org.mitre.portal.security.SecurityTokenService; import org.mitre.portal.security.UserService; import org.mitre.portal.service.exception.SecurityTokenException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ResourceBundle; @Service(value = "securityTokenService") public class EncryptedBlobSecurityTokenService implements SecurityTokenService { private static final Log log = LogFactory.getLog(EncryptedBlobSecurityTokenService.class); private static final String SECURITY_PROPERTIES = "application"; private static final String ENCRYPTION_PROPERTY = "security.encryptionkey"; private static final String EMBEDDED_KEY_PREFIX = "embedded:"; //TODO - should this be read in from a properties file? private static final String CONTAINER = "default"; private static final String DOMAIN = " default"; private static final String CHAR_ENCODING = "UTF-8"; private final UserService userService; private final BlobCrypter blobCrypter; @Autowired public EncryptedBlobSecurityTokenService(UserService userService) { this.userService = userService; ResourceBundle resourceBundle = ResourceBundle.getBundle(SECURITY_PROPERTIES); if (resourceBundle.containsKey(ENCRYPTION_PROPERTY)) { String key = resourceBundle.getString(ENCRYPTION_PROPERTY); if (key.startsWith(EMBEDDED_KEY_PREFIX)) { byte[] encryptionKey = CharsetUtil.getUtf8Bytes(key.substring(EMBEDDED_KEY_PREFIX.length())); this.blobCrypter = new BasicBlobCrypter(encryptionKey); } else { try { this.blobCrypter = new BasicBlobCrypter(new File(key)); } catch (IOException e) { throw new SecurityException("Unable to load encryption key from file: " + key); } } } else { throw new SecurityException("Unable to find encryption key!"); } } @Override @PreAuthorize("#personGadget.userId == principal.userId") public SecurityToken getSecurityToken(PersonGadget personGadget) throws SecurityTokenException { return this.getBlobCrypterSecurityToken(personGadget); } @Override @PreAuthorize("#personGadget.userId == principal.userId") public String getEncryptedSecurityToken(PersonGadget personGadget) throws SecurityTokenException { String encryptedToken = null; try { BlobCrypterSecurityToken securityToken = this.getBlobCrypterSecurityToken(personGadget); encryptedToken = this.encryptSecurityToken(securityToken); } catch (Exception e) { throw new SecurityTokenException("Error creating security token from person gadget", e); } return encryptedToken; } @Override public SecurityToken decryptSecurityToken(String encryptedSecurityToken) throws SecurityTokenException { SecurityToken securityToken; try { log.debug("Decrypting security token: " + encryptedSecurityToken); //Remove the header container string and : encryptedSecurityToken = encryptedSecurityToken.substring((CONTAINER + ":").length()); //Decrypt securityToken = MitreBlobCrypterSecurityToken.decrypt(blobCrypter, CONTAINER, DOMAIN, encryptedSecurityToken); } catch (Exception e) { throw new SecurityTokenException("Error creating security token from encrypted string: " + encryptedSecurityToken, e); } return securityToken; } @Override public String refreshEncryptedSecurityToken(String encryptedSecurityToken) throws SecurityTokenException { try { //Decrypt the current token SecurityToken securityToken = this.decryptSecurityToken(encryptedSecurityToken); //Make sure the person is authorized to refresh this token String userId = userService.getCurrentAuthenticatedUser().getUserId(); if (!securityToken.getOwnerId().equalsIgnoreCase(userId)) { throw new SecurityTokenException("Illegal attempt by user " + userId + " to refresh security token owned by " + securityToken.getOwnerId()); } //Create a new PersonGadget instance from it so we can use it to generate a new encrypted token PersonGadget personGadget = new PersonGadget(securityToken.getModuleId(), securityToken.getOwnerId()); personGadget.setGadget(new Gadget(-1L, "", "", new URL(securityToken.getAppUrl()))); //Create and return the newly encrypted token return getEncryptedSecurityToken(personGadget); } catch (MalformedURLException e) { throw new SecurityTokenException(e.getMessage(), e); } } private BlobCrypterSecurityToken getBlobCrypterSecurityToken(PersonGadget personGadget) throws SecurityTokenException { Person user = userService.getCurrentAuthenticatedUser(); BlobCrypterSecurityToken securityToken = new BlobCrypterSecurityToken(blobCrypter, CONTAINER, DOMAIN); securityToken.setAppUrl(personGadget.getGadget().getUrl().toString()); securityToken.setModuleId(personGadget.getPersonGadgetId()); securityToken.setOwnerId(personGadget.getUserId()); securityToken.setViewerId(user.getUserId()); securityToken.setTrustedJson(""); log.debug("Token created for personGadget " + personGadget.toString() + " and user " + user.toString()); return securityToken; } private String encryptSecurityToken(BlobCrypterSecurityToken securityToken) throws SecurityTokenException { String encryptedToken = null; try { encryptedToken = securityToken.encrypt(); log.debug("Encrypted token created from security token: " + securityToken.toString() + " -- encrypted token is: " + encryptedToken); } catch (Exception e) { throw new SecurityTokenException("Error creating security token from person gadget", e); } return encryptedToken; } } ---------- package org.apache.shindig.auth; import org.apache.shindig.common.crypto.BlobCrypter; import org.apache.shindig.common.crypto.BlobCrypterException; /** * This class is needed because the BlobCrypterSecurityToken.decrypt method is marked as protected and we * need to be able to decrypt the security token in the container */ public class MitreBlobCrypterSecurityToken extends BlobCrypterSecurityToken { public MitreBlobCrypterSecurityToken(BlobCrypter crypter, String container, String domain) { super(crypter, container, domain); } public static BlobCrypterSecurityToken decrypt(BlobCrypter crypter, String container, String domain, String token) throws BlobCrypterException { return BlobCrypterSecurityToken.decrypt(crypter, container, domain, token, null); } } ---------- I think some of the API's have changed on the Shindig side since this code was written (this was written for Shindig 2.0) but I think the concepts are still the same. >the >gadget object like in [2]. In rave_opensocial.js we don't pass the gadget as >an object to the container, just its url and render params. I think for now we could just add another securityToken property to the object we push onto the widgets array. I haven't yet looked into how we push this into the Shindig common container code however. Hopefully there's an easy way to do it... > >Is there anyone who does know the right place where to handle the >securityToken between the portal and container? > > >[1] >http://svn.apache.org/repos/asf/incubator/rave/donations/surfconext- >portal/coin-portal/trunk/coin-portal- >war/src/main/java/nl/surfnet/coin/portal/control/HomeController.java >[2] >http://svn.apache.org/repos/asf/incubator/rave/donations/surfconext- >portal/coin-portal/trunk/coin-portal- >war/src/main/webapp/js/coin/shindig/container.js > >Jasha Joachimsthal > >Europe - Amsterdam - Oosteinde 11, 1017 WT Amsterdam - +31(0)20 522 4466 >US - Boston - 1 Broadway, Cambridge, MA 02142 - +1 877 414 4776 (toll free) > >www.onehippo.com
