NCSA CyberSecurity


Author: Neil Gorsuch (ngorsuch@ncsa.uiuc.edu)
Last Update: October 10, 2007

Introduction

This document explains how to use MyProxy extapp callouts and the GridShib SAML tools to embed and access SAML assertions and Maeviz groups information in credentials. The Maeviz portal uses a MyProxy server for single sign-on functionality, but lacked the ability to store which Maeviz groups a user was a member of in the user's sign-on credential. To demonstrate the feasability of this solution a test system was setup in a similar fashion to the "Using MyProxy To Create Session Passwords" instructions. This system was then modified to use a MyProxy extapp callout that retrieved a user's Maeviz groups information and then encoded the list of groups as a SAML assertion using the SAML field "IsAMemberOf" with an OID of 1.3.6.1.4.1.5923.1.5.1.1. To demonstrate retrieval of the user's group memberships information from the user's temporary sign-on credential, a Java application was written using the GridShib SAML tools.

Installation Overview

The test system was a virtual VMware system initialized with Fedora release 7, but any *nix system should suffice. The MyProxy server portions of the Globus Toolkit version 4.0.5 was used.

Installation and configuration of the MyProxy server portions of the Globus Toolkit, GridShib SAML tools, the MyProxy extapp callout, and then testing of the system consisted of these steps:

  1. Use the instructions here to install the MyProxy server portions of the Globus Toolkit as a single sign-on system.
  2. Install the MyProxy extapp callout.
  3. Install the Java extraction program based on the GridShib SAML tools.
  4. Test the system.

Installation and Configuration Details

Note: If your browser supports it, you can click on many of the user defined parameters shown in red and enter new values for your particular configuration. All related values on the page will be updated to reflect the new value you entered.
  1. Use the instructions here to install the MyProxy server portions of the Globus Toolkit as a single sign-on system. There shouldn't need to be any changes, use the latest Globus Toolkit version, which was 4.0.5 as of this writing.
  2. To install the MyProxy extapp callout, you need to create a root owned mode 700 file at an appropriate location such as /usr/local/sbin/myproxy-maeviz-groups-extapp . This file will be called during the MyProxy authentication process, with the user name that is being authenticated passed as the first command line argument. This can be any executable object such as a shell or perl script or even a compiled binary. In the test system a shell script was used. MyProxy expects this file to return on standard output a portion of an openssl configuraiton file. Here is an example of a shell script that can be used (this is a pared-down version of the actual script used on the test system):
    #!/bin/sh                                                                                                         
    # has to be just one command line input argument
    if ! echo "$1" | egrep '^[A-Za-z][A-Za-z0-9_-]*$' >/dev/null ; then
        echo extapp error bad input parameter \'$1\' >&2
        exit 1
    fi
    
    # make a temprary directory for intermediate stage files
    tmpdir=/tmp/`basename $0`.$$
    trap "cd ; rm -rf $tmpdir ; exit 1" 1 2 3 15
    if ! mkdir $tmpdir ; then
        echo extapp error cannot create temporary directory \'$tmpdir\' >&2
        exit 1
    fi
    
    cat <<EOF |
    ... mysql commands to retrieve a user's group ...
    ... names with the user name denoted by "%USER%" ...
    ... for more information on the actual command ...
    ... see the mysql queries sub-section of the ...
    ... "MaeViz Group Authorization" section of the cet-wiki ...
    EOF
    sed "s,%USER%,$1,g" | \
    mysql --host=mysql-server --user=mysql-user --password=mysql-password | \
    egrep -v '^TITLE$' > $tmpdir/groups.txt
    # now we have a text file with each group as a string on it's own line:                      
    #    groupA                                                                                  
    #    groupB                                                                                  
    
    # time to convert the group strings to something like this:                                  
    #    http://groupA        http://groupb                                                      
    # note that the spacing between each http'ed group string HAS to be                          
    # a single tab character!!                                                                   
    cat $tmpdir/groups.txt | \
    sed 's,^,http://,g' | \
    sed 's,$,/,g' | \
    tr '\012' '\t' | \
    sed 's,\t$,,g' > $tmpdir/groups.str
    
    # now we make a partial openssl configuration file that includes                             
    # the information that the GridShib SAML Tools needs to generate                             
    # a SAML assertion                                                                           
    samlconfig=$tmpdir/saml.config
    cat <<EOF |
    NameID.Format=urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified                          
    NameID.Format.template=%PRINCIPAL%                                                           
    Attribute.isMemberOf.Namespace=urn:mace:shibboleth:1.0:attributeNamespace:unspecified        
    Attribute.isMemberOf.Name=urn:oid:1.3.6.1.4.1.5923.1.5.1.1                                   
    Attribute.isMemberOf.Value=%ISMEMBEROF%                                                      
    Attribute.countryName.Namespace=urn:mace:shibboleth:1.0:attributeNamespace:uri               
    Attribute.countryName.Name=urn:oid:2.5.4.6                                                   
    Attribute.countryName.Value=USUSUS                                                           
    certLocation=file:///etc/grid-security/hostcert.pem                                          
    keyLocation=file:///etc/grid-security/hostkey.pem                                            
    EOF                                                                                          
    sed "s,%USER%,$1,g" | \
    sed "s,%ISMEMBEROF%,`cat $tmpdir/groups.str`,g" >$samlconfig
    
    # now use the GridSHib SAML Tools to make the SAML assertion string                          
    # note these environmental variables should be correctly defined:
    #    GRIDSHIB_HOME
    #    GLOBUS_PATH
    #    JAVA_HOME
    #    PATH
    echo $1 > /tmp/user
    #/usr/local/gridshib-saml-tools-0_2_1-alpha/bin/gridshib-saml-issuer --user $1 --config=file\
    ://$samlconfig --der > $tmpdir/assertions.der                                                
    /usr/local/gridshib-saml-tools-0_2_1-alpha/bin/gridshib-saml-issuer --user $1 --config=file:\
    //$samlconfig --saml > $tmpdir/assertions.der
    
    # now encode the SAML assertion string:                                                      
    #                                                                             
    # into hex-encoded like this:                                                                
    # 3c:41:73:73:65:72:74:...:69:6f:6e:3e:0a                                                    
    /usr/local/sbin/hexderit < $tmpdir/assertions.der > $tmpdir/assertions.hexder
    
    printenv > /tmp/env
    
    # and then output it as an openssl config file fragment like this to standard output:        
    # 1.3.6.1.4.1.3536.1.1.1.10=DER:3c:41:73:73:65:72:74:...:69:6f:6e:3e:0a                      
    if [ `wc -c < $tmpdir/assertions.hexder 2>/dev/null` -gt 0 ] ; then
        echo 1.3.6.1.4.1.3536.1.1.1.10=DER:`cat $tmpdir/assertions.hexder` >$tmpdir/ssc.txt
        cat $tmpdir/ssc.txt
    fi
    
    rm -rf $tmpdir
    
    exit 0
    
    The /usr/local/sbin/hexderit program looks like this:
    #!/usr/bin/perl
    
    my $input = <>;
    foreach my $index ( 0 .. ((length $input) - 1) ) {
            print ":" if $index > 0;
            printf "%02x", 0x7f & ord substr( $input, $index, 1 );
    }
    
    The MyProxy system needs to call the /usr/local/sbin/myproxy-maeviz-groups-extapp executable file whenever a MyProxy login occurs. Modify the MyProxy configuration file, normally located at /usr/local/sbin/myproxy-maeviz-groups-extapp, to include a line like this:
    certificate_extapp /usr/local/sbin/myproxy-maeviz-groups-extapp
    
  3. To install the GridShib SAML tools, first we need to look in the GridShib SAML Tools download section for the latest version, and then download and install like this:
    cd /usr/local
    wget http://gridshib.globus.org/downloads/gridshib-saml-tools-0_2_0-src.tar.gz
    tar xpzf gridshib-saml-tools-0_2_0-src.tar.gz
    cd gridshib-saml-tools-0_2_0
    ant install
    
    To avoid complications with classpaths and to easily use the GridShib SAML tools libraries, our java extraction program can be put into the GridShib SAML tools source tree. First we add a compile target for our program to the GridShib SAML tools build configuration file at:
    /usr/local/gridshib-saml-tools-0_2_0/build.xml
    
    The "target" block looks like this (assuming that our program java source is named GroupsIsMemberOf and it's build file abbreviation is gimo):
      <target name="compile-gimo" depends="create-log4j-props, create-jars">
        <echo message="Running test of GroupsIsMemberOf"/>
        <echo message="...Complete."/>
      </target>
    
    Then we install our Java extraction file:
    /usr/local/gridshib-saml-tools-0_2_0/tests/org/globus/gridshib/security/GroupsIsMemberOf.java
    
    which could look like this:
    /*                                                                                        
     * Copyright 2006-2007 University of Illinois                                             
     *                                                                                        
     * Licensed under the Apache License, Version 2.0 (the "License"); you may not            
     * use this file except in compliance with the License. You may obtain a copy             
     * of the License at                                                                      
     *                                                                                        
     *    http://www.apache.org/licenses/LICENSE-2.0                                          
     *                                                                                        
     * Unless required by applicable law or agreed to in writing, software                    
     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT              
     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the               
     * License for the specific language governing permissions and limitations                
     * under the License.                                                                     
     */
    
    package org.globus.gridshib.security;
    
    import java.io.File;
    import java.io.IOException;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.security.auth.Subject;
    import javax.security.auth.x500.X500Principal;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    import org.globus.gridshib.common.mapper.EntityMap;
    import org.globus.gridshib.common.mapper.GridShibEntityMapper;
    import org.globus.gridshib.security.SAMLSecurityContext;
    import org.globus.gridshib.security.SecurityContextFactory;
    import org.globus.gridshib.security.saml.SelfIssuedAssertion;
    import org.globus.gridshib.security.saml.SimpleAttribute;
    import org.globus.gridshib.security.util.CertUtil;
    import org.globus.gridshib.security.util.GSIUtil;
    import org.globus.gridshib.security.util.SAMLUtil;
    import org.globus.gridshib.tool.GridShibToolConfigException;
    import org.globus.gridshib.tool.saml.SAMLToolConfig;
    import org.globus.gridshib.tool.saml.SAMLToolConfigLoader;
    
    import org.globus.gsi.GlobusCredential;
    import org.globus.gsi.GlobusCredentialException;
    
    import org.globus.opensaml11.md.common.Constants;
    import org.globus.opensaml11.saml.SAMLAuthenticationStatement;
    import org.globus.opensaml11.saml.SAMLException;
    import org.globus.opensaml11.saml.SAMLSubjectAssertion;
    
    /**                                                                                       
     * A test application that illustrates the use of the                                     
     * standalone GridShib Security Framework.                                                
     */
    public class GroupsIsMemberOf {
    
        private static Log logger =
            LogFactory.getLog(GroupsIsMemberOf.class.getName());
    
        // the issuing credential (EEC or proxy):                                             
        private static GlobusCredential credential = null;
    
        // a mapping from SAML entities to X.509 entities:                                    
        private static DifferentTrivialEntityMap entityMap = new DifferentTrivialEntityMap();
    
        public static void main(String[] args) {
    
            /* The SAML Assertion Issuer Tool has a config file                               
             * that specifies a log file and a default credential                             
             * (among other things).  The SAMLToolConfig                         
             * class is not common code, but is useful here to                                
             * bootstrap the config of this test application.                                 
             */
            SAMLToolConfig config = null;
            try {
                config = SAMLToolConfigLoader.getToolConfig();
            } catch (GridShibToolConfigException e) {
                String msg = "Unable to get the config";
            } catch (GridShibToolConfigException e) {
                String msg = "Unable to get the config";
                logger.error(msg, e);
                System.err.println(msg);
                System.exit(1);
            }
    
            /* There should be one command line argument, use it as                           
             * a combined cert and key file name, and try to open                             
             * the certificate from that.                                                     
             */
            if ( args.length > 2 || args.length < 1 ) {
                System.err.println("usage: " + args[0] + " CredentialFile [ KeyFile ]");
                System.exit(1);
            }
            String certPath = args[0];
            String keyPath = (args.length > 1) ? args[1] : args[0];
            //System.out.println("cert path: " + certPath);
            //System.out.println("key path: " + certPath);
            logger.debug("cert path: " + certPath);
            logger.debug("key path: " + keyPath);
            File certFile = new File(certPath);
            File keyFile = new File(keyPath);
    
            try {
                credential = GSIUtil.getCredential(certFile, keyFile);
            } catch (GlobusCredentialException e2) {
                String msg = "Unable to obtain credential from " +
                    args[0] + ((args.length > 1) ? args[1] : "");
                logger.error(msg, e2);
                System.err.println(msg);
                System.exit(1);
            }
            //System.out.println("Issuing credential (chain length " +
            //    credential.getCertificateChain().length + "):");
            //System.out.println(credential.toString());
    
            initializeEntityMapping();
            consumeX509BoundSAML();
        }
    
        /**                                                                                   
         * Initializes an entity mapping, that is, a mapping of SAML                          
         * issuers to X.509 issuers.  For each bound SAML assertion,                          
         * add a map from the issuer of the SAML assertion to the                             
         * issuer of the containing certificate (which may be an                              
         * EEC or a proxy certificate).                                                       
         *                                                                                    
         * Note: In practice, a consumer depends on a static entity                           
         * map configured into the runtime environment.  In GridShib                          
         * for GT, for example, entity mappings are stored in the                             
         * file system and loaded when the runtime initializes.                               
         */
        private static void initializeEntityMapping() {
    
            /* Determine the proxy issuer, which is the EEC subject,                          
             * by definition.                                                                 
             */
            X509Certificate[] certs = credential.getCertificateChain();
            X509Certificate eec = null;
            try {
                logger.debug("Getting EEC...");
                eec = CertUtil.getEEC(certs);
            } catch (CertificateException e) {
                String msg = "Unable to determine if certificate is an " +
                             "impersonation proxy";
                logger.error(msg, e);
                System.err.println(msg);
                System.exit(1);
            }
            if (eec == null) {
                String msg = "Unable to find end entity certificate";
                logger.error(msg);
                System.err.println(msg);
                System.exit(1);
            }
            logger.debug("End entity cert: " + eec.toString());
            X500Principal proxyIssuer = eec.getSubjectX500Principal();
            String proxyIssuerDN = proxyIssuer.getName(X500Principal.RFC2253);
            logger.debug("Proxy issuer: " + proxyIssuerDN);
    
            /* Traverse the certificate chain and add an entity                               
             * map for each bound SAML assertion.                             
             */
            for (int i = 0; i < certs.length; i++) {
                logger.debug("Processing certificate " + i + ": " +
                             certs[i].toString());
    
                String entityID = null;
                SAMLSubjectAssertion assertion = null;
                try {
                    assertion = SAMLUtil.getSAMLAssertion(certs[i]);
                } catch (IOException e) {
                    String msg = "Unable to decode certificate extension";
                    logger.error(msg, e);
                    System.err.println(msg);
                    System.exit(1);
                } catch (SAMLException e) {
                    String msg = "Unable to convert extension to SAMLAssertion";
                    logger.error(msg, e);
                    System.err.println(msg);
                    System.exit(1);
                }
                if (assertion == null) {
                    logger.debug("Certificate " + i +
                                 " does not contain a SAML assertion");
                } else {
                    logger.debug("Bound SAML assertion: " + assertion.toString());
                    entityID = assertion.getIssuer();
                }
    
                try {
                    if (!CertUtil.isImpersonationProxy(certs[i])) {
                        if (assertion != null) {
                            assert (entityID != null);
                            // map the SAML issuer to the certificate issuer:                 
                            X500Principal certIssuer =
                                certs[i].getIssuerX500Principal();
                            String dn = certIssuer.getName(X500Principal.RFC2253);
                            logger.debug("Mapping SAML issuer to " +
                                         "certificate issuer: " + dn);
                            entityMap.addMapping(entityID, dn);
                        }
                        logger.debug("All certificates processed");
                        break;
                    } else {
                        if (assertion != null) {
                            assert (entityID != null);
                            // map the SAML issuer to the proxy issuer:                       
                            logger.debug("Mapping SAML issuer to " +
                                         "proxy issuer: " + proxyIssuerDN);
                            entityMap.addMapping(entityID, proxyIssuerDN);
                        }
                        continue;
                    }
                } catch (CertificateException e) {
                    String msg = "Unable to determine if certificate is an " +
                                 "impersonation proxy";
                    logger.error(msg, e);
                    System.err.println(msg);
                    System.exit(1);
                }
            }
    
            GridShibEntityMapper.register(entityMap);
        }
    
        /**                                                                                   
         * Creates a security context from the SAML assertions bound                          
         * to the certificate chain.                                                          
         */
        private static void consumeX509BoundSAML() {
    
            /* Get a security context for the subject and                                     
             * add the certificate chain of the proxy to the                                  
             * security context.                                                              
             */
            Subject subject = new Subject();
            SAMLSecurityContext secCtx =
               (SAMLSecurityContext)SecurityContextFactory.getInstance(subject);
            assert (secCtx != null);
            secCtx.addCertificateChain(credential.getCertificateChain());
    
            try {
                SAMLUtil.consumeSAMLAssertions(subject);
                /* Now get the attributes from the certificate. */
                BasicAttribute[] rawAttributes = secCtx.getRawAttributes();
                assert (rawAttributes != null);
                //System.out.println("Found " + rawAttributes.length + " raw attribute(s)");
                for (int i = 0; i < rawAttributes.length; i++) {
                    String entityID = rawAttributes[i].getIssuer();
                    String name = rawAttributes[i].getName();
                    String[] values = rawAttributes[i].getValues();
                    //System.out.println("attribute #" + (i+1) + " entityID=" + entityID);
                    //System.out.println("attribute #" + (i+1) + " name=" + name);
                    //System.out.println("attribute #" + (i+1) + " values:");
                    for (int j = 0; j < values.length; j++) {
                        String value = values[j];
                        //System.out.println("attribute #" + (i+1) + " " + name + " value #" + \
                        if (name.compareTo("urn:oid:1.3.6.1.4.1.5923.1.5.1.1") == 0) {
                            value = value.replaceFirst("^http://", "");
                            value = value.replaceFirst("/$", "");
                            System.out.println( value );
                        }
                    }
                }
            } catch (IOException e) {
                String msg = "Unable to decode extension";
                logger.error(msg, e);
                System.err.println(msg);
                System.exit(1);
            } catch (SAMLException e) {
                String msg = "Unable to convert extension to SAMLAssertion";
                logger.error(msg, e);
                System.err.println(msg);
                System.exit(1);
            } catch (CertificateException e) {
                String msg = "Unable to determine if certificate is an " +
                             "impersonation proxy";
                logger.error(msg, e);
                System.err.println(msg);
                System.exit(1);
            }
    
            //System.out.println("secCtx: " + secCtx.toString());
        }
    }
    
    Note that this example file simply prints out the embedded Maeviz group membership strings on stdout, one string per line, from a specified user certificate. The program requires a credential file name as the first command line argument with an optional keyfile name as the second command line argument.

    To build and install our extraction program, do this:

    cd /usr/local/gridshib-saml-tools-0_2_0
    ant compile
    ant install
    
    To make our extraction program easier to run by avoiding classpath problems, make a mode 755 shell script at:
    /usr/local/gridshib-saml-tools-0_2_0/bin/groups-is-member-of
    
    which contains this code:
    #!/bin/sh
    
    ## the GridShib installation directory
    if [ ! -n "$GRIDSHIB_HOME" ] ; then
      echo "Error: GRIDSHIB_HOME is not defined."
      exit 1
    fi
    
    $GRIDSHIB_HOME/bin/java org.globus.gridshib.security.GroupsIsMemberOf "$@"
    
  4. To test the system, log into this hosts's MyProxy server which will produce a MyProxy certificate, and extract the certificate into a local file.
    echo MyProxy-password | myproxy-login --stdin_pass -s host-fqdn -l MyProxy-login-name -o certificate-file
    
    Assume for example that the Maeviz login corresponding to the myproxy login named MyProxy-login-name has these two Maeviz group memberships:
    MAEviz
    My Workspace
    
    To make sure that the Maeviz groups are encoded in the certificate execute this command:
    openssl x509 -noout -text -in certificate-file
    
    This will dump the certificate in test form. You should see an X509v3 extension of OID 1.3.6.1.4.1.3536.1.1.1.10 with the Maeviz group strings embedded in the extension. The output might look like this:
    Certificate:
        Data:
            Version: 3 (0x2)
            Serial Number: 206 (0xce)
            Signature Algorithm: sha1WithRSAEncryption
            Issuer: C=US, O=host-fqdn, OU=MAEviz, CN=MAEviz Simple CA
            Validity
                Not Before: Oct 29 07:32:22 2007 GMT
                Not After : Oct 29 19:37:22 2007 GMT
            Subject: C=US, O=host-fqdn, OU=MAEviz, CN=MyProxy-login-name
            Subject Public Key Info:
                Public Key Algorithm: rsaEncryption
                RSA Public Key: (1024 bit)
                    Modulus (1024 bit):
                        00:a6:8d:af:15:1c:d1:94:d3:47:fc:68:45:1d:6f:
                        07:0a:bc:e1:a5:7f:e6:eb:c7:d5:e2:55:f4:d3:3c:
                        45:1c:d3:14:1e:4c:15:3b:82:67:e9:28:32:4b:ed:
                        f7:99:40:73:ea:1d:28:66:dd:9e:4c:dc:cf:eb:01:
                        80:b1:f2:90:f6:61:0e:74:34:6c:61:3b:ad:c6:c1:
                        59:67:b0:7d:72:57:d0:07:51:7c:e9:19:d2:b5:a7:
                        c0:4d:46:48:df:d1:1b:51:c7:13:df:4d:ab:98:f5:
                        99:70:74:95:c2:a0:71:30:8a:56:15:96:c9:7b:ee:
                        d1:c7:e0:ff:eb:b8:aa:eb:d5
                    Exponent: 65537 (0x10001)
            X509v3 extensions:
                1.3.6.1.4.1.3536.1.1.1.10: 
                    <Assertion xmlns="urn:oasis:names:tc:SAML:1.0:assertion" \
    xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" \
    xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" \
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" \
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" \
    AssertionID="_57c94d23b4d16ca258d8f83438d8c0af" \
    IssueInstant="2007-10-29T07:37:25.025Z" \
    Issuer="CN=host/host-fqdn,OU=MAEviz,O=host-fqdn,C=US" 
    MajorVersion="1" MinorVersion="1"><AttributeStatement><Subject><NameIdentifier \
    Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"> \
    MyProxy-login-name</NameIdentifier></Subject><Attribute \
    AttributeName="urn:oid:2.5.4.6" \
    AttributeNamespace="urn:mace:shibboleth:1.0:attributeNamespace:uri"> \
    <AttributeValue xsi:type="xsd:normalizedString">US</AttributeValue></Attribute> \
    <Attribute AttributeName="urn:oid:1.3.6.1.4.1.5923.1.5.1.1" \
    AttributeNamespace="urn:mace:shibboleth:1.0:attributeNamespace:unspecified"> \
     \
    <AttributeValue xsi:type="xsd:normalizedString">http://MAEviz/</AttributeValue> \
    <AttributeValue xsi:type="xsd:normalizedString">http://My Workspace/</AttributeValue></Attribute> \
    </AttributeStatement></Assertion>
    
        Signature Algorithm: sha1WithRSAEncryption
            1e:da:7b:fd:6c:a1:09:cd:0a:7d:33:e2:11:e9:f0:df:91:fc:
            b2:2f:a0:c9:8f:c1:2c:17:55:fe:d7:0e:ad:93:3d:b7:63:ce:
            8b:da:63:ce:94:87:bd:cb:2c:9f:ea:a2:03:ed:0e:04:8a:b3:
            07:5d:49:5a:db:46:47:a1:51:bf:14:b7:66:fb:ea:44:a7:44:
            9c:3e:43:e1:37:a7:c4:d4:f7:85:b7:e5:04:fc:a2:86:29:dc:
            b6:d4:be:95:a2:bf:69:ef:43:e5:31:7d:c5:bc:75:2c:28:f5:
            94:2e:a2:b9:a4:4d:75:de:31:46:62:a4:00:12:49:56:5f:93:
            27:a2
    
    To use our java program to extract the Maeviz groups from the certificate, simply execute it like this:
    /usr/local/gridshib-saml-tools-0_2_0/bin/groups-is-member-of certificate-file
    
    This should output our Maeviz group names, one per line, like this:
    MAEviz
    My Workspace
    
    Now use this host's MyProxy server to create a temporary certificate which includes the original login certificate, and extract the temporary certificate into another local file.
    myproxy-init -s host-fqdn -l MyProxy-login-name -o second-certificate-file
    
    Use our java program to extract the Maeviz groups from the second certificate:
    /usr/local/gridshib-saml-tools-0_2_0/bin/groups-is-member-of certificate-file
    
    This should output our Maeviz group names, one per line, like this:
    MAEviz
    My Workspace