Thursday, April 08, 2010

OpenSAML signature verification


Assuming that a webservice call containing a signed SAML Assertion, how do we verify that signature? CXF provides "interceptors" to add code before and after a call to a webservice.

First we must configure the call (with Spring in this case):
<jaxws:client id="clientWS" serviceClass="es.mycompany.MyService" address="http://localhost:8080/app/services/myservice">    <jaxws:dataBinding>        <ref bean="aegisBean" />    </jaxws:dataBinding>    <jaxws:inInterceptors>        <ref bean="clientWS.InInterceptor" />    </jaxws:inInterceptors></jaxws:client><bean id="clientWS.InInterceptor" class="es.mycompany.MyInterceptor">    <constructor-arg>        <map>            <entry key="action" value="Timestamp Signature"/>            <entry key="passwordCallbackClass" value="es.mycompany.MyPasswordCallback"/>            <entry key="signaturePropFile" value="keystore.properties"/>        </map>    </constructor-arg></bean>

After that, we need to create a "keystore.properties" file:
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlinorg.apache.ws.security.crypto.merlin.keystore.type=jksorg.apache.ws.security.crypto.merlin.keystore.alias=MyAliasorg.apache.ws.security.crypto.merlin.keystore.password=MyPasswordorg.apache.ws.security.crypto.merlin.file=keystore.jks

And finally the class for managing the certificate passwords. This class needs improvements, but it's good as an example:
public class MyPasswordCallback implements CallbackHandler {    private Map passwords = new HashMap();    public STSPasswordCallback() {        passwords.put("MyAlias", "MyPassword");    }    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {        for (int i = 0; i < pc =" (WSPasswordCallback)" pass =" passwords.get(pc.getIdentifier());">
With everything in place, we only have to code the interceptor, which in turn uses a utilities class:
public class MyInterceptor extends WSS4JInInterceptor {    private String issuer = "http://www.mycompany.com";    public MyInterceptor() {        super();    }    public MyInterceptor(Map properties) {        super(properties);    }        protected void doReceiverAction(int doAction, RequestData reqData) throws WSSecurityException {                // Get SOAP Header        SOAPMessage message = ((org.apache.cxf.binding.soap.SoapMessage) reqData.getMsgContext()).getContent(javax.xml.soap.SOAPMessage.class);        SOAPHeader soapHeader = message.getSOAPHeader();                // Get Assertion XML        XPathUtils xu = new XPathUtils();        Element xmlAssertion = (Element) xu.getValueNode("//saml2:Assertion", soapHeader);                // Unmarshall        Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(xmlAssertion);        Assertion assertion = (Assertion) unmarshaller.unmarshall(xmlAssertion);        // Get Handler properties        String sigPropFile = getString(WSHandlerConstants.SIG_PROP_FILE, reqData.getMsgContext());        String callback = getString(WSHandlerConstants.PW_CALLBACK_CLASS, reqData.getMsgContext());        // Verify        SAMLUtils.verifySignature(assertion, issuer, sigPropFile, callback);        super.doReceiverAction(doAction, reqData);    }    public void handleMessage(SoapMessage message) throws Fault {        super.handleMessage(message);    }}public class SAMLUtils {    public static void verifySignature(Assertion assertion, String issuer, String sigPropFile, String callback) throws WSSecurityException {        X509Credential cred = getX509Credential(sigPropFile, callback);        StaticCredentialResolver credResolver = new StaticCredentialResolver(cred);        KeyInfoCredentialResolver kiResolver = SecurityHelper.buildBasicInlineKeyInfoResolver();        ExplicitKeySignatureTrustEngine trustEngine = new ExplicitKeySignatureTrustEngine(credResolver, kiResolver);        CriteriaSet criteriaSet = new CriteriaSet();        criteriaSet.add(new EntityIDCriteria(issuer));        criteriaSet.add(new UsageCriteria(UsageType.SIGNING));        criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS));        try {            trustEngine.validate(assertion.getSignature(), criteriaSet);        } catch (SecurityException e) {            throw new WSSecurityException(e.getMessage());        }    }    public static X509Credential getX509Credential(String sigPropFile, String callback) throws WSSecurityException {        PrivateKey privateKey = null;        X509Certificate[] certs = null;        Crypto crypto = crypto = CryptoFactory.getInstance(sigPropFile);        try {            CallbackHandler cbhandler = getPasswordCallBack(callback);            WSPasswordCallback cb = new WSPasswordCallback(crypto.getDefaultX509Alias(), WSPasswordCallback.SIGNATURE);            cbhandler.handle(new Callback[] { cb });            privateKey = crypto.getPrivateKey(crypto.getDefaultX509Alias(), cb.getPassword());            certs = crypto.getCertificates(crypto.getDefaultX509Alias());        } catch (Exception e) {            throw new WSSecurityException(e.getMessage());        }        if (certs.length != 1) {            throw new WSSecurityException("Couldn't get the (" + crypto.getDefaultX509Alias() + ") signature certificate.");        }        X509Credential cred = SecurityHelper.getSimpleCredential(certs[0], privateKey);        return cred;    }    public static CallbackHandler getPasswordCallBack(String callback) throws WSSecurityException {        CallbackHandler cbHandler = null;        try {            Class cbClass = Loader.loadClass(callback);            cbHandler = (CallbackHandler) cbClass.newInstance();        } catch (Exception e) {            throw new WSSecurityException("WSHandler: cannot create instance of password callback: " + callback, e);        }        return cbHandler;    }}
As you can see, all the work is done in the trustEngine.validate(assertion.getSignature(), criteriaSet); line, we only need to reach there with the right data.

No comments: