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

Reply via email to