Author: Neil Gorsuch (ngorsuch@ncsa.uiuc.edu) Last Update: October 10, 2007
The /usr/local/sbin/hexderit program looks like this:#!/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 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:#!/usr/bin/perl my $input = <>; foreach my $index ( 0 .. ((length $input) - 1) ) { print ":" if $index > 0; printf "%02x", 0x7f & ord substr( $input, $index, 1 ); }
certificate_extapp /usr/local/sbin/myproxy-maeviz-groups-extapp
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: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
The "target" block looks like this (assuming that our program java source is named GroupsIsMemberOf and it's build file abbreviation is gimo):/usr/local/gridshib-saml-tools-0_2_0/build.xml
Then we install our Java extraction file:<target name="compile-gimo" depends="create-log4j-props, create-jars"> <echo message="Running test of GroupsIsMemberOf"/> <echo message="...Complete."/> </target>
which could look like this:/usr/local/gridshib-saml-tools-0_2_0/tests/org/globus/gridshib/security/GroupsIsMemberOf.java
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./* * 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). TheSAMLToolConfig
* 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()); } }
To build and install our extraction program, do this:
To make our extraction program easier to run by avoiding classpath problems, make a mode 755 shell script at:cd /usr/local/gridshib-saml-tools-0_2_0 ant compile ant install
which contains this code:/usr/local/gridshib-saml-tools-0_2_0/bin/groups-is-member-of
#!/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 "$@"
Assume for example that the Maeviz login corresponding to the myproxy login named MyProxy-login-name has these two Maeviz group memberships:echo MyProxy-password | myproxy-login --stdin_pass -s host-fqdn -l MyProxy-login-name -o certificate-file
To make sure that the Maeviz groups are encoded in the certificate execute this command:MAEviz My Workspace
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:openssl x509 -noout -text -in certificate-file
To use our java program to extract the Maeviz groups from the certificate, simply execute it 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
This should output our Maeviz group names, one per line, like this:/usr/local/gridshib-saml-tools-0_2_0/bin/groups-is-member-of certificate-file
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.MAEviz My Workspace
Use our java program to extract the Maeviz groups from the second certificate:myproxy-init -s host-fqdn -l MyProxy-login-name -o second-certificate-file
This should output our Maeviz group names, one per line, like this:/usr/local/gridshib-saml-tools-0_2_0/bin/groups-is-member-of certificate-file
MAEviz My Workspace