jueves, abril 08, 2010

Verificar firma con OpenSAML


Suponiendo una llamada webservice que contenga una Assertion SAML firmada, como verificamos esa firma? CXF provee "interceptores" para incluir código antes y después de una invocación a webservice.

Primero debemos configurar (con Spring en este caso) la invocación:
<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>

Luego creamos nuestro fichero de propiedades del keystore ("keystore.properties"):
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.alias=MyAlias
org.apache.ws.security.crypto.merlin.keystore.password=MyPassword
org.apache.ws.security.crypto.merlin.file=keystore.jks

Y finalmente la clase encargada de dar las contraseñas de acceso a los certificados. Esta clase es claramente mejorable, pero sirve como ejemplo:
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 < callbacks.length; i++) {
            WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];
            String pass = passwords.get(pc.getIdentifier());
            if (pass != null) {
                pc.setPassword(pass);
                return;
            }
        }
    }
}
Teniendo todo configurado, solo nos queda el código del interceptor, que a su vez se apoya en una clase de utilidades:
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;
    }
}
Como ven, todo el trabajo lo hace la línea trustEngine.validate(assertion.getSignature(), criteriaSet);, solo hay que llegar hasta ella con los datos adecuados.

No hay comentarios: