Validar Signature do xml - Java

Eu tenho alguns xmls que recebo por email que são xmls da SEFAZ, preciso validar Signature do xml para verificar se é válido o xml.

Estou fazendo a seguinte classe, entretanto ao verificar a tag Signature está sempre retornando 0.

private static void validarXMl(String xml) throws Exception {
	    DocumentBuilderFactory factorySignature = DocumentBuilderFactory.newInstance();
	    factorySignature.setNamespaceAware(true);
	    Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
				.parse(new ByteArrayInputStream(xml.getBytes()));

	    NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
		if (nl.getLength() == 0) { 
//está sempre caindo nesse if, sempre ficando 0, sendo que no xml tem a tag Signature.
			throw new Exception("Cannot find Signature element");
		}
		
		XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

	    // Cria um DOMValidateContext
		// Create a DOMValidateContext and specify a KeySelector
		// and document context.
		DOMValidateContext valContext = new DOMValidateContext
		    (new X509KeySelector(), nl.item(0));

		XMLSignature signatures = fac.unmarshalXMLSignature(valContext);

	    // Valida a XMLSignature.
	    boolean coreValidity = signatures.validate(valContext);
	    
	 // Check core validation status.
	    if (coreValidity == false) {
	        System.err.println("Signature failed core validation");
	        boolean sv = signatures.getSignatureValue().validate(valContext);
	        System.out.println("signature validation status: " + sv);
	        if (sv == false) {
	            // Check the validation status of each Reference.
	        	Iterator<?> i = signatures.getSignedInfo().getReferences().iterator();
	            for (int j=0; i.hasNext(); j++) {
	            	 boolean refValid = ((Reference) i.next()).validate(valContext);
	                System.out.println("ref["+j+"] validity status: " + refValid);
	            }
	        }
	    } else {
	        System.out.println("Signature passed core validation");
	    }
		
	}

Classe X509KeySelector

public class X509KeySelector extends KeySelector {
	
	@Override
    public KeySelectorResult select(KeyInfo keyInfo,
                                    Purpose purpose,
                                    AlgorithmMethod method,
                                    XMLCryptoContext context)
        throws KeySelectorException {
        Iterator ki = keyInfo.getContent().iterator();
        while (ki.hasNext()) {
            XMLStructure info = (XMLStructure) ki.next();
            if (!(info instanceof X509Data))
                continue;
            X509Data x509Data = (X509Data) info;
            Iterator xi = x509Data.getContent().iterator();
            while (xi.hasNext()) {
                Object o = xi.next();
                if (!(o instanceof X509Certificate))
                    continue;
                final PublicKey key = ((X509Certificate)o).getPublicKey();
                // Make sure the algorithm is compatible
                // with the method.
                if (algEquals(method.getAlgorithm(), key.getAlgorithm())) {
                    return new KeySelectorResult() {
                        public Key getKey() { return key; }
                    };
                }
            }
        }
        throw new KeySelectorException("No key found!");
    }

    static boolean algEquals(String algURI, String algName) {
        if ((algName.equalsIgnoreCase("DSA") &&
            algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) ||
            (algName.equalsIgnoreCase("RSA") &&
            algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1))) {
            return true;
        } else {
            return false;
        }
    }

	
}

Você está criando 2 instâncias de DocumentBuilderFactory deveria ser uma só, assim:

	    DocumentBuilderFactory factorySignature = DocumentBuilderFactory.newInstance();
	    factorySignature.setNamespaceAware(true);
	    Document doc = factorySignature.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes()));

Deu certo em parte, pois agora não está mais dando ‘0’, aí quando nessa parte abaixo para tentar validar a XMLSignature da erro.

		XMLSignature signatures = fac.unmarshalXMLSignature(valContext);

	    // Valida a XMLSignature.
	    boolean coreValidity = signatures.validate(valContext);

Agora está dando esse erro javax.xml.crypto.dsig.XMLSignatureException: cannot find validation key, será que vou precisar do certificado digital? Pois no momento tenho apenas o xml.

Em princípio você deveria passar a chave pública para o construtor do DOMValidateContext, mais ou menos assim:

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        FileInputStream fis = new FileInputStream("caminho do seu arquivo .cer");
        X509Certificate cert = (X509Certificate) cf.generateCertificate(fis);
        PublicKey publicKey = cert.getPublicKey();

        DOMValidateContext valContext = new DOMValidateContext(publicKey, nl.item(0));

Tentei fazer dessa forma que mandou, porém o meu arquivo do certificado é .pfx, aí retorna o seguinte erro ‘java.security.cert.CertificateParsingException: signed fields invalid’

Tentei usar dessa maneira para poder obter o certificado, mas também recebo o mesmo erro.

KeyStore trustStore  = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(null);//Make an empty store
        InputStream fis = new FileInputStream("/home/aline/Documents/Certificado/Script/certificado.pfx");
        BufferedInputStream bis = new BufferedInputStream(fis);

        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        Certificate cert = null;
        while (bis.available() > 0) {
            cert = cf.generateCertificate(bis);
            trustStore.setCertificateEntry("fiddler"+bis.available(), cert);
        }
		PublicKey publicKey = cert.getPublicKey();

Arquivos .pfx é um keystore, eles tem um alias e uma senha, acredito que precisa informar esses dados, vc tem?

Vc não carregou o arquivo pfx, precisa trocar para algo como:

// para ler PFX, usar o tipo PKCS12
KeyStore trustStore  = KeyStore.getInstance("PKCS12");

// abrir o arquivo
InputStream fis = new FileInputStream("/home/aline/Documents/Certificado/Script/certificado.pfx");
// carregar o kesytore com o conteúdo do arquivo
// usar a senha do keystore (ou null caso não tenha)
trustStore.load(fis, "senha_do_keystore".toCharArray());

// usar o alias no qual o certificado está
// se não souber o alias, pode usar o método aliases() para verificar todos que existem,
// e aí escolher o correto
Certificate cert = trustStore.getCertificate("alias_do_certificado");
// ... usar o certificado

O código que vc fez carrega um keystore vazio (load(null)), e depois tenta ler o arquivo pfx como se fosse um certificado (pois vc passa o input stream para o CertificateFactory, ou seja, ele vai tentar ler o arquivo PFX como se fosse um certificado).

Seu código só funcionaria se no lugar do arquivo pfx tivesse um arquivo contendo somente o certificado (o que não é o caso). Ah, e depois o seu código tenta colocar o certificado dentro do PFX (é o que o método setCertificateEntry faz), mas isso não faz sentido porque entendi que o PFX já tem o certificado, certo?


Sendo bem sincero - e sem ironia nenhuma - sugiro voltar alguns passos e estudar o básico de certificados digitais, em vez de tentar coisas aleatórias sem entender o que está fazendo (desculpe se parece rude, mas esta é a impressão que passa). E programar dessa forma (também chamado de “programação orientada a coincidências”) nunca é a melhor alternativa.

Não sei o quanto vc já conhece do assunto, e é difícil indicar links sem saber disso, mas enfim, talvez ajude: