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.

1 comentario:

nicol dijo...

Hola.

Antes de nada, perdona que te escriba esto como un comentario, pero es que no vi tu email en el tu blog

Soy el webmaster de publizida.es



Publizida BLOG'S es un ranking / directorio de clasificación de blogs en español, creado con el único propósito de dar a conocer los mejores blog's



Registrando su blog en Publizida BLOG'S accederás al servicio de estadísticas gratuitas y podrás participar en el TOP.RANKING

También puedes acceder a la valoración que los usuarios hacen de su página.



Y lo mas importante...

darte a conocer y aumentar el numero de visitantes a tu BLOG de manera totalmente gratuita.



Si te interesa puedes darte de alta

ALTA DIRECTORIO DE BLOGS



o visitanos en ......



DIRECTORIO DE BLOGS



HTTP://PUBLIZIDA.ES



Muchas Gracias por tu tiempo... y disculpa si no fue la mejor manera de darme a conocer.



Un saludo.



DAVID T.

Webmaster de Publizida.es