A segurança é crucial quando os serviços da Web trocam dados de negócios. Podem haver consequências financeiras ou legais negativas se dados forem interceptados por terceiros ou se dados fraudulentos forem aceitos como válidos. Sempre é possível projetar e implementar os procedimentos de segurança próprios de um aplicativo para serviços da Web — para qualquer tipo de troca de dados —. Contudo, essa é uma abordagem arriscada, pois mesmo pequenas distrações podem resultar em vulnerabilidades graves. Um dos principais benefícios da SOAP, se comparada a outras formas de troca de dados, é que a mesma permite extensões modulares. Desde o lançamento da SOAP, as extensões têm focado muito na segurança, o que resultou na padronização da WS-Security e das tecnologias relacionadas que permitem que a segurança seja configurada de forma apropriada para cada serviço.
Os requisitos para a segurança relacionada à troca de informações geralmente envolvem três aspectos:
- Confidencialidade: Apenas o destinatário-alvo para quem uma certa mensagem é destinada tem acesso ao seu conteúdo.
- Integridade: A mensagem é recebida sem modificações.
- Autenticidade: A origem da mensagem pode ser verificada.
A WS-Security permite atender esses três aspectos facilmente. Neste artigo, você verá como fazer isso usando Axis2 e a extensão WS-Security Rampart. Em primeiro lugar, irei apresentar uma breve descrição dos princípios da criptografia de chave pública — a base para a maioria dos recursos de criptografia e assinatura da WS-Security.
Ao longo de toda a história, a troca de mensagem tem se baseado em algum tipo de segredo compartilhado. O segredo pode ter a forma de um código, no qual as partes envolvidas na troca de mensagem possuem um conjunto em comum de substituições para frases ou ações. Ou pode ser um código no qual texto é convertido em outro tipo de texto usando algum algoritmo. O segredo também pode tomar outras formas, como por exemplo uma língua desconhecida para pessoas que possam vir a ter acesso às mensagens. O segredo compartilhado tornava a troca de mensagens segura. Contudo, se outra pessoa descobrisse o segredo, a troca de mensagem seria comprometida, com resultados potencialmente devastadores para as partes envolvidas na troca. (é só lembrar, por exemplo, do Enigma e das comunicações militares alemãs na Segunda Guerra Mundial).
A criptografia de chave pública é uma abordagem de segurança inerentemente diferente que não requer um segredo compartilhado. Ela baseia-se na idéia das funções matemáticas "trap-door", as quais são facilmente computáveis em uma direção, mas muito dificilmente computáveis na direção reversa. Por exemplo, é fácil encontrar o produto de dois números primos (mesmo números primos muito altos, caso se esteja trabalhando em um computador), mas é muito mais difícil analisar o produto para encontrar os dois fatores originais. Quando se constrói um algoritmo de criptografia baseado na direção fácil de uma função, alguém que queira quebrar sua criptografia precisa trabalhar a partir do lado difícil. Com um algoritmo bem escolhido, a tarefa de quebrar a criptografia é dificultada em um nível que a torna impraticável de ser executada em um espaço de tempo que chegue a ameaçar a troca de mensagens (pelo menos até que alguém desenvolva um computador quantum utilizável ou capacidades muito boas em física).
Na criptografia de chave pública, uma pessoa que deseje receber mensagens criptografadas cria um par de valores de chave. Cada um dos valores de chave pode ser utilizado separadamente para criptografar mensagens, mas não pode ser usado para decriptografar mensagens da forma que foi utilizado para criptografá-las. Ao invés disso, a outra chave do par deve ser utilizada para fazer a decriptografia. Desde que o proprietário das chaves mantenha uma delas em segredo, a outra pode ser tornada pública. Qualquer pessoa que tenha acesso à chave pública pode utilizar a mesma para criptografar mensagens, as quais só poderão ser decriptografadas pelo proprietário. Uma vez que são utilizadas chaves diferentes para criptografar e decriptografar mensagens, essa forma de criptografia é conhecida como criptografia assimétrica.
Quando sua chave pública é utilizada para criptografar a mensagem, apenas você, o proprietário da chave privada, pode decriptografar a mensagem. Isso garante a confidencialidade, um dos três aspectos da troca de mensagens segura. contudo, também é possível utilizar sua chave privada para criptografar uma mensagem. quando isso é feito, qualquer pessoa que possuir uma cópia de sua chave pública pode decriptografar essa mensagem. Isso não parece muito útil, pois — para que serve uma mensagem criptografada que qualquer um pode ler? — mas é uma boa maneira de verificar a autenticidade de mensagens. Uma pessoa que receba uma mensagem criptografada supostamente enviada por você pode utilizar sua chave pública para decriptografar a mensagem e compará-la a um determinado valor esperado. Se o resultado bater, a pessoa tem a certeza de que foi você que enviou a mensagem.
Na prática, existem mais fatores envolvidos na assinatura de mensagens do que simplesmente a criptografia com uma chave privada. Em primeiro lugar, você precisa de algum meio de estabelecer o valor esperado para a mensagem decriptografada. Isso geralmente é feito usando uma outra variação da função "trap-door": uma função hash é fácil de ser computada, mas difícil de ser duplicada (o que significa que é difícil realizar alterações na mensagem sem alterar o valor do hash para a mensagem, ou encontrar outra mensagem com o mesmo valor do hash que uma mensagem fornecida). Com uma função hash desse tipo, é possível gerar o valor do hash (geralmente chamado de resumo em assuntos relacionados à segurança) para a mensagem que deseja assinar e depois criptografar esse resumo usando sua chave privada e enviar o valor criptografado do resumo com a mensagem. Qualquer um que receba a mensagem pode utilizar o mesmo algoritmo hash na mesma e, em seguida, decriptografar o resumo criptografado fornecido usando sua chave pública e comparar os dois valores. Se os valores baterem, o destinatário terá certeza (dentro dos limites tecnológicos atuais e considerando que você manteve sua chave privada em segredo) de que a mensagem foi enviada por você.
No caso de XML, há mais uma etapa envolvida na assinatura de mensagens. As mensagens XML são expressas em forma de texto, mas alguns aspectos da representação do texto são considerados irrelevantes pelo XML (como a ordem dos atributos em um elemento ou espaço em branco utilizado no interior da tag inicial ou final de um elemento). Devido a essas questões relativas a representações de textos, o W3c (proprietário da especificação XML) decidiu converter mensagens XML em um texto canônico antes de computar um valor de resumo (consulte Recursos). São definidos vários algoritmos de canonização que podem ser utilizados com mensagens XML. Geralmente, o algoritmo particular utilizado não importa muito, desde o utilizado por ambas as partes envolvidas em uma troca de mensagem seja o mesmo.
A utilização de um resumo de mensagem assinado garante a integridade da mensagem (uma vez que alterações na mensagem modificam o valor do resumo), bem como sua autenticidade (uma vez que a sua chave privada é utilizada para criptografar o resumo). Uma vez que a confidencialidade é assegurada em mensagens enviadas para você por meio de uma criptografia que utiliza sua chave pública, todos os aspectos principais da segurança de troca de mensagem são cobertos ao se utilizar um par de chaves público-privado. Obviamente, com apenas um par de chaves, somente uma direção de uma troca de mensagens é assegurada — mas, se a outra pessoa envolvida na troca também possuir seu par de chaves público-privado, é possível obter segurança total para mensagens indo em ambas as direções.
Assim, pares de chaves público-privados podem ser utilizados tanto para a criptografia quanto para a assinatura de mensagens trocadas entre duas partes, desde que cada uma das partes tenha acesso à chave pública da outra parte. Assim, só nos resta a questão de como obter a chave pública de uma forma segura. Dentre as diversas maneiras de se fazer isso, a mais utilizada é fazer com que uma ou mais entidades confiáveis garantam a autenticidade da chave pública. Os Certificados Digitais são o mecanismo de fornecimento de chaves públicas nesse modelo de garantia.
Um certificado digital é basicamente uma etiqueta que acompanha uma chave pública e inclui informações de identificação para o proprietário da chave. Essa etiqueta é então assinada por uma terceira entidade confiável e a assinatura é incluída no certificado. A terceira entidade confiável garante a autenticidade da chave pública e das informações de identificação ao emitir o certificado com sua assinatura. É claro que ainda permanece a questão de como estabelecer a identidade de entidades confiáveis. Isso geralmente é feito fixando certificados para determinados terceiros confiáveis chamados autoridades de emissão no código de softwares (como o JVM).
Há muito mais por trás dos certificados digitais além dos fundamentos descritos aqui, como maneiras de anular certificados emitidos acidentalmente (o que pode acontecer, infelizmente) ou com chaves privadas, períodos de validade do certificado e extensões para especificar o uso pretendido de um certificado. Consulte Recursos para encontrar links para mais informações a respeito de certificados digitais e criptografia de chave pública em geral. Você também pode consultar a documentação da ferramenta de segurança keytool incluída em sua instalação JDK. A documentação keytool fornece uma boa introdução sobre estrutura e manuseio de certificados, acompanhada de keystores (discutido a seguir) e outros aspectos de segurança. Você também verá o exemplo mais à frente neste artigo do trabalho com a keytool.
A maioria dos códigos de segurança de Java trabalha com chaves privadas e certificados digitais usando keystores. Um keystore é simplesmente um arquivo que contém uma chave, bem como entradas de certificado em forma criptografada. É necessária uma senha para acessar o keystore. Cada chave privada no keystore também é criptografada, exigindo uma senha adicional para manter a segurança das chaves. Qualquer software que utilizar o keystore e a chave privada precisa ter tanto a senha do keystore quanto a da chave privada durante o tempo de execução. Isso limita a segurança oferecida por essas senhas (porque qualquer pessoa com acesso ao código fonte pode descobrir como as senhas são carregadas). Assim, é necessário que você mantenha a segurança física do sistema que esteja hospedando o software e quaisquer backups desse sistema. E você deve manter o keystore e a senha apenas nesse sistema e nesses backups para preservar a segurança de suas chaves privadas.
Chaves secretas e criptografia simétrica
Embora a criptografia de chave pública usando criptografia simétrica seja a base de vários dos recursos úteis da WS-Security, a tradicional criptografia de chave secreta também desempenha um papel importante. Os algoritmos de criptografia assimétrica costumam envolver uma computação mais intensa que algoritmos simétricos baseados em chaves secretas (sendo que a mesma chave é utilizada tanto para criptografar quanto para decriptografar, ou seja, a chave deve sempre ser mantida em segredo) para garantir os mesmos níveis de proteção. Por isso, as duas técnicas são geralmente combinadas: a criptografia assimétrica de alto custo é utilizada para proteger a troca de uma chave secreta, que pode então ser utilizada para criptografia simétrica de baixo custo. Você verá um exemplo dessa abordagem mais adiante neste artigo, quando estiver lendo sobre criptografia WS-Security de uma mensagem.
Este artigo utiliza o mesmo exemplo de aplicativo que o "Fundamentos da WS-Security no Axis2," que mostra como implementar a WS-Security UsernameToken usando Axis2 e Rampart. Contudo, são necessárias alguma mudanças para suportar o uso dos recursos de criptografia de chave pública da WS-Security, por isso, um pacote de código separado acompanha este artigo (consulte Download).
O diretório-raiz do código de exemplo é jws05code. Dentro desse diretório, você irá encontrar:
- Um arquivo Ant build.xml
- Um arquivo Ant build.properties, que configura a operação do aplicativo exemplo
- O arquivo library.wsdl, fornecendo a definição do serviço para o aplicativo exemplo
- Um arquivo de log4j.properties usado para configurar o registro no lado do cliente
- Diversos arquivos XML de definição de propriedade (todos chamados XXX-policy-client.xml ou XXX-policy-server.xml)
Antes de utilizar o código de exemplo, você precisa:
- Editar o arquivo build.properties para configurar o caminho para a sua instalação do Axis2.
- Certificar-se de que sua instalação do Axis2 seja atualizada com o código Rampart, como mencionado na seção Instalando o Rampart do "Fundamentos da WS-Security no Axis2." (uma boa forma de checar é procurar no diretório repositório/módulos por um arquivo de módulo rampart-x.y.mar.)
- Adicionar o provedor de segurança Bouncy Castle
org.bouncycastle.jce.provider.BouncyCastleProvider(necessário para os recursos de criptografia de chave pública utilizados no código de exemplo) à sua configuração de segurança JVM (o arquivo lib/security/java.security file) (consulte Recursos). - Adicionar o JAR Bouncy Castle (nomeado de bcprov-jdkxx-vvv.jar, onde xx é sua versão de Java e vvv é a versão do código Bouncy Castle) tanto ao diretório lib de instalação do seu Axis2 quanto ao diretório WEB-INF/lib de seu aplicativo de servidor Axis2.
Agora você está pronto para compilar o aplicativo exemplo e experimentar os exemplos de segurança descritos nas seções seguintes.
A assinatura requer muito mais especificações que o exemplo UsernameToken em "Fundamentos da WS-Security no Axis2." Você precisa:
- Identificar o par de chaves público-privado usado para criar a assinatura em cada direção e fornecer as senhas utilizadas para acessar o keystore e a chave privada.
- Especificar o conjunto de algoritmos utilizado para a canonização XML, geração do resumo e a assinatura em si.
- Especificar quais partes da mensagem devem ser incluídas na assinatura.
Parte dessas informações é tratada como dados de configuração, integrada ao documento WS-SecurityPolicy para o serviço. Outras partes são incluídas na troca de mensagem em tempo de execução.
A Listagem 1 mostra um documento WS-Policy utilizado para configurar o cliente de Axis2 para assinar mensagens. (a Listagem 1 foi adaptada à largura da página. Veja o texto completo em sign-policy-client.xml no código de exemplo.)
Listagem 1.WS-Policy/WS-SecurityPolicy para assinatura (cliente)
<!-- Política do cliente para assinar todas as mensagens, com certificados incluídos em
cada mensagem--> <wsp:Policy wsu:Id="SignOnly"
xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsp:ExactlyOne>
<wsp:All>
<sp:AsymmetricBinding
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:InitiatorToken>
<wsp:Policy>
<sp:X509Token
sp:IncludeToken="http://.../IncludeToken/AlwaysToRecipient"/>
</wsp:Policy>
</sp:InitiatorToken>
<sp:RecipientToken>
<wsp:Policy>
<sp:X509Token
sp:IncludeToken="http://.../IncludeToken/AlwaysToInitiator"/>
</wsp:Policy>
</sp:RecipientToken>
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:TripleDesRsa15/>
</wsp:Policy>
</sp:AlgorithmSuite>
<sp:Layout>
<wsp:Policy>
<sp:Strict/>
</wsp:Policy>
</sp:Layout>
<sp:IncludeTimestamp/>
<sp:OnlySignEntireHeadersAndBody/>
</wsp:Policy>
</sp:AsymmetricBinding>
<sp:SignedParts xmlns:sp="http://.../ws-securitypolicy/200702">
<sp:Body/>
</sp:SignedParts>
<ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
<ramp:user>clientkey</ramp:user>
<ramp:passwordCallbackClass
>com.sosnoski.ws.library.adb.PWCBHandler</ramp:passwordCallbackClass>
<ramp:signatureCrypto>
<ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
<ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
>JKS</ramp:property>
<ramp:property name="org.apache.ws.security.crypto.merlin.file"
>client.keystore</ramp:property>
<ramp:property
name="org.apache.ws.security.crypto.merlin.keystore.password"
>nosecret</ramp:property>
</ramp:crypto>
</ramp:signatureCrypto>
</ramp:RampartConfig>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy> |
Na Listagem 1, o elemento <sp:AsymmetricBinding> na política fornece a informação de configuração básica para a utilização de criptografia de chave pública na troca de mensagem. Dentro desse elemento, vários elementos aninhados são utilizados para as especificidades da configuração. O <sp:InitiatorToken> identifica o par de chaves utilizado para assinar mensagens do cliente (o iniciador) para o servidor (o destinatário), nesse caso informando que a chave pública irá vir na forma de um certificado X.509 e será enviada com cada mensagem do cliente (sp:IncludeToken=".../AlwaysToRecipient"). O <sp:RecipientToken> identifica o par de chaves utilizado para assinar mensagens no caminho de resposta do servidor para o cliente, mais uma vez, usando um certificado X.509 e com o certificado incluído em cada mensagem do servidor (sp:IncludeToken=".../AlwaysToInitiator").
O elemento <sp:AlgorithmSuite> identifica o conjunto de algoritmos utilizado para a assinatura. O <sp:IncludeTimestamp> diz que um registro de data e hora será usado com cada mensagem (útil para prevenir ataques tipo repetição em um servidor, nos quais uma mensagem é capturada em trânsito e re-enviada na tentativa de confundir ou interromper o serviço). O elemento <sp:OnlySignEntireHeadersAndBody> diz que a assinatura será realizada em todo o cabeçalho ou corpo da mensagem, ao invés de em alguns elementos aninhados (outra medida de segurança, a qual previne certos tipos de ataque que reescrevem a mensagem). O elemento <sp:SignedParts> identifica as partes da mensagem que devem ser assinadas — nesse caso, o Corpo da mensagem SOAP.
A última parte do documento de WS-Policy da Listagem 1 fornece informações de configuração específicas de Rampart. Trata-se de uma versão mais complexa da configuração de Rampart utilizada em "Fundamentos da WS-Security no Axis2," dessa vez incluindo um elemento <ramp:user> para identificar a chave que será utilizada para assinar mensagens e um elemento <ramp:signatureCrypto> para configurar o keystore contendo a chave privada do cliente e o certificado do servidor. O arquivo keystore de referência deve estar presente no caminho de classe durante o tempo de execução. No aplicativo exemplo, o arquivo keystore é copiado para o diretório cliente/bin durante a compilação.
As classes de retorno de senha são um pouco diferentes das usadas no "Fundamentos de WS-Security no Axis2." Nesse artigo, o retorno de senha só era necessário no servidor e só era necessário para verificar (para um texto simples UsernameToken) ou configurar (para um hash UsernameToken) a senha correspondente a um determinado nome de usuário. Para a criptografia de chave pública utilizada neste artigo, o retorno deve fornecer a senha utilizada para proteger uma chave privada dentro do keystore. São necessários retornos separados para o cliente (para fornecer a senha para a chave privada do cliente) e servidor (para fornecer a senha para a chave privada do servidor). A Listagem 2 mostra a versão do retorno do lado do cliente:
Listagem2: Retorno de senha do cliente
/** * Manipulador de retorno de senha simples. Isso simplesmente verifica se a senha
para a chave privada * está sendo requisitada e, caso esteja, configura esse
valor. */ public class PWCBHandler implements CallbackHandler
{
public void handle(Callback[] callbacks) throws IOException {
for (int i = 0; i < callbacks.length; i++) {
WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
String id = pwcb.getIdentifer();
int usage = pwcb.getUsage();
if (usage == WSPasswordCallback.DECRYPT ||
usage == WSPasswordCallback.SIGNATURE) {
// used to retrieve password for private key
if ("clientkey".equals(id)) {
pwcb.setPassword("clientpass");
}
}
}
}
} |
O retorno na Lista 2 é projetado para suportar tanto a assinatura quanto a de criptografia usando o mesmo par de chaves público-privado, assim, ele checa a existência dos dois casos e retorna a mesma senha em ambos os casos.
Além da WS-Policy da Listagem 1 usada para o cliente, existe uma correspondente para o servidor (sign-policy-server.xml, no código de exemplo), a qual se diferencia apenas quanto aos detalhes da configuração Rampart. De forma semelhante, a versão do servidor da classe de retorno de senha da Listagem 2 se diferencia apenas quanto ao valor e senha identificadores retornados.
Executando o aplicativo exemplo
O arquivo build.properties possui linhas que fazem referência aos arquivos client.policy e server.policy a serem utilizados pelo aplicativo exemplo. Os mesmos estão configurados, respectivamente, para sign-policy-client.xml e sign-policy-server.xml na versão fornecida do arquivo. Assim, você só precisará compilar o aplicativo. É possível fazer isso com Ant, abrindo um console para o diretório jws05code e inserindo ant. Caso tudo esteja configurado corretamente, no final você deve ter um archive Axis2 library-signencr.aar no diretório jws05code. Implemente o serviço na instalação de seu servidor Axis2 carregando o arquivo .aar através da página de administração do Axis2 e depois teste o cliente, inserindo ant run no console. Se tudo estiver configurado corretamente você deverá ver as informações de saída exibidas na Figura 1:
Figura 1. Saída de console para o aplicativo em execução
Para visualizar a informação de WS-Security nas mensagens, você precisa utilizar uma ferramenta como TCPMon (consulte Recursos). Primeiro, obtenha do cliente as conexões de configuração e aceitação do TCPMon em uma porta, as quais são em seguida encaminhadas ao servidor executado em uma porta diferente (ou host diferente). Então é possível editar o arquivo build.properties e mudar o valor do host-port para a porta de escuta para o TCPMon. Ao entrar mais uma vez com ant run no console, você deverá ver as mensagens sendo trocadas. A Listagem 3 mostra uma captura de mensagem de cliente como exemplo:
Listagem 3: Primeira mensagem do cliente para o servidor
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<wsse:Security xmlns:wsse=".../oasis-200401-wss-wssecurity-secext-1.0.xsd"
soapenv:mustUnderstand="1">
<wsu:Timestamp xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="Timestamp-3753023">
<wsu:Created>2009-04-18T19:26:14.779Z</wsu:Created>
<wsu:Expires>2009-04-18T19:31:14.779Z</wsu:Expires>
</wsu:Timestamp>
<wsse:BinarySecurityToken
xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
EncodingType=".../oasis-200401-wss-soap-message-security-1.0#Base64Binary"
ValueType=".../oasis-200401-wss-x509-token-profile-1.0#X509v1"
wsu:Id="CertId-2650016">MIICoDC...0n33w==</wsse:BinarySecurityToken>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
Id="Signature-29086271">
<ds:SignedInfo>
<ds:CanonicalizationMethod
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#Id-14306161">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>SiU8LTnBL10/mDCPTFETs+ZNL3c=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#Timestamp-3753023">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>2YopLipLgBFJi5Xdgz+harM9hO0=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>TnUQtz...VUpZcm3Nk=</ds:SignatureValue>
<ds:KeyInfo Id="KeyId-3932167">
<wsse:SecurityTokenReference
xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="STRId-25616143">
<wsse:Reference URI="#CertId-2650016"
ValueType=".../oasis-200401-wss-x509-token-profile-1.0#X509v1"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</soapenv:Header>
<soapenv:Body
xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Id-14306161">
<ns2:getBook xmlns:ns2="http://ws.sosnoski.com/library/wsdl">
<ns2:isbn>0061020052</ns2:isbn>
</ns2:getBook>
</soapenv:Body>
</soapenv:Envelope> |
O cabeçalho <wsse:Security> com a mensagem SOAP possui todas as informações de configuração de segurança de tempo de execução e dados de assinatura. O primeiro item presente é um <wsu:Timestamp>, como requerido pela configuração da WS-SecurityPolicy. O registro de data e hora inclui dois valores de tempo: o tempo no qual ele foi criado e o tempo no qual expira. Esses dois valores diferem em cinco minutos nesse caso, que é o padrão para Rampart. (É possível alterar os valores como parte da configuração de política do Rampart.) A quantidade de tempo entre os dois é arbitrária de certa forma, mas cinco minutos é um valor razoável — é suficiente para compensar uma quantidade razoável de distorção de relógio (diferença nos tempos de relógio de sistema) entre o cliente e o servidor e ao mesmo tempo curto o bastante para limitar o potencial de ataques de repetição usando a mensagem. Após o registro de data e hora, o próximo item no cabeçalho de segurança é um <wsse:BinarySecurityToken>. Esse token de segurança é o certificado de cliente em formato binário codificado base64.
O terceiro item no cabeçalho de segurança é um bloco <ds:Signature> com três elementos filhos. O primeiro elemento filho, <ds:SignedInfo>, é a única parte da mensagem que é diretamente assinada. Os primeiros elementos filhos em <ds:SignedInfo> identificam os algoritmos utilizados para sua própria canonização e assinatura. Eles são acompanhados de um elemento filho <ds:Reference> para cada componente de mensagem incluído na assinatura. Cada elemento filho <ds:Reference> faz referência a um determinado componente de mensagem através de identificador e fornece os algoritmos de canonização e resumo aplicados a esse componente, acompanhado do valor de resumo resultante. Os elementos filhos restantes de <ds:SignedInfo> fornecem o valor da assinatura e uma referência à chave pública usada para verificar a assinatura (nesse caso, o certificado incluído no <wsse:BinarySecurityToken> antes no cabeçalho, como identificado por wsu:Id="CertId-2650016").
Criptografando e assinando mensagens
Adicionar criptografia à troca de mensagens assinada é simples e requer apenas a adição de um elemento <sp:EncryptedParts> à política para identificar o(s) componente(s) a serem assinados e algumas informações de configuração Rampart adicionais. A Listagem 4 mostra a versão do cliente de uma política com esse propósito (novamente editada de acordo com a largura da página — consulte o arquivo signencr-policy-client.xml no código de exemplo para ter acesso ao texto completo), com as adições à política da Listagem 1 em negrito:
Listagem 4: WS-Policy/WS-SecurityPolicy para assinatura e posterior criptografia (cliente)
<!-- Política de cliente para primeiro assinar e depois criptografar todas as mensagens,
com o certificado incluído na mensagem do cliente para o servidor, mas apenas uma
impressão digital em mensagens do servidor para o
cliente. --> <wsp:Policy wsu:Id="SignEncr"
xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsp:ExactlyOne>
<wsp:All>
<sp:AsymmetricBinding
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:InitiatorToken>
<wsp:Policy>
<sp:X509Token
sp:IncludeToken="http://.../IncludeToken/AlwaysToRecipient"/>
</wsp:Policy>
</sp:InitiatorToken>
<sp:RecipientToken>
<wsp:Policy>
<sp:X509Token
sp:IncludeToken="http://.../IncludeToken/Never">
<wsp:Policy>
<sp:RequireThumbprintReference/>
</wsp:Policy>
</sp:X509Token>
</wsp:Policy>
</sp:RecipientToken>
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:TripleDesRsa15/>
</wsp:Policy>
</sp:AlgorithmSuite>
<sp:Layout>
<wsp:Policy>
<sp:Strict/>
</wsp:Policy>
</sp:Layout>
<sp:IncludeTimestamp/>
<sp:OnlySignEntireHeadersAndBody/>
</wsp:Policy>
</sp:AsymmetricBinding>
<sp:SignedParts xmlns:sp="http://.../200702">
<sp:Body/>
</sp:SignedParts>
<sp:EncryptedParts xmlns:sp="http://.../200702">
<sp:Body/>
</sp:EncryptedParts>
<ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
<ramp:user>clientkey</ramp:user>
<ramp:encryptionUser>serverkey</ramp:encryptionUser>
<ramp:passwordCallbackClass
>com.sosnoski.ws.library.adb.PWCBHandler</ramp:passwordCallbackClass>
<ramp:signatureCrypto>
<ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
<ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
>JKS</ramp:property>
<ramp:property name="org.apache.ws.security.crypto.merlin.file"
>client.keystore</ramp:property>
<ramp:property
name="org.apache.ws.security.crypto.merlin.keystore.password"
>nosecret</ramp:property>
</ramp:crypto>
</ramp:signatureCrypto>
<ramp:encryptionCrypto>
<ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
<ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
>JKS</ramp:property>
<ramp:property name="org.apache.ws.security.crypto.merlin.file"
>client.keystore</ramp:property>
<ramp:property
name="org.apache.ws.security.crypto.merlin.keystore.password"
>nosecret</ramp:property>
</ramp:crypto>
</ramp:encryptionCrypto>
</ramp:RampartConfig>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy> |
A primeira alteração na política da Listagem 4 não é necessária para utilizar criptografia, mas é uma boa idéia. Ao utilizar criptografia, o cliente precisa possuir o certificado do servidor disponível ao enviar a solicitação inicial (porque a chave pública do servidor do certificado é utilizada para a criptografia). Uma vez que o cliente precisa possuir o certificado do servidor de qualquer forma, nunca há motivo para enviar o certificado do servidor para o cliente. O <sp:RecipientToken> alterado na política da Listagem 4 reflete esse tipo de utilização, dizendo que não deve ser enviado um certificado (sp:IncludeToken=".../Never") e que uma referência de impressão digital (basicamente um hash do certificado) deve ser utilizada no lugar do mesmo. A referência da impressão digital é muito mais compacta que um certificado completo. Por isso, a utilização da referência reduz o tamanho da mensagem e os gastos adicionais com processamento.
A mudança que realmente implementa a criptografia é o elemento <sp:EncryptedParts> adicionado. Esse elemento diz que a criptografia deve ser utilizada, e o elemento de conteúdo <sp:Body> diz que o corpo da mensagem SOAP é a parte da mensagem que deve ser criptografada.
A informação de configuração Rampart adicionada na Listagem 4 consiste em um elemento <ramp:encryptionUser> fornecendo o alias para a chave pública (ou seja, certificado) a ser utilizado para criptografar a mensagem, e em um elemento <ramp:encryptionCrypto> dizendo como acessar o keystore contendo o certificado. No aplicativo exemplo, o mesmo keystore é utilizado para a chave privada utilizada para assinar e a chave pública, utilizada para criptografar, assim, o elemento <ramp:encryptionCrypto> é apenas uma duplicata renomeada do elemento <ramp:signatureCrypto> existente.
Durante o tempo de execução, o Rampart precisa obter a senha utilizada para proteger a chave privada que será utilizada para decriptografar os dados criptografados. O retorno de senha utilizado anteriormente para obter a senha de chave privada para assinar, exibido na Listagem 2, também fornece a senha para decriptografia. Assim, não são necessárias alterações nesse ponto.
Executando o aplicativo exemplo
Para testar o aplicativo exemplo usando assinatura seguida de criptografia, você primeiro precisa editar o arquivo build.properties. Altere a linha de política do cliente para client.policy=signencr-policy-client.xml e a política do servidor para server-policy=signencr-policy-server.xml. Em seguida, é possível recompilar o aplicativo, executando ant, implementar o arquivo library-signencr.aar gerado em sua instalação do Axis2 e executar ant run para testar.
A Listagem 5 exibe uma captura de mensagem de solicitação quando é utilizada assinatura seguida de criptografia, com as diferenças significativas com relação à versão de apenas assinatura da Listagem 3 em negrito:
Listagem 5. Mensagem usando assinatura e criptografia
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<soapenv:Header>
<wsse:Security xmlns:wsse=".../oasis-200401-wss-wssecurity-secext-1.0.xsd"
soapenv:mustUnderstand="1">
<wsu:Timestamp xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="Timestamp-4067003">
<wsu:Created>2009-04-21T23:15:47.701Z</wsu:Created>
<wsu:Expires>2009-04-21T23:20:47.701Z</wsu:Expires>
</wsu:Timestamp>
<xenc:EncryptedKey Id="EncKeyId-urn:uuid:6E12E251E439C034FA12403557497352">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference>
<wsse:KeyIdentifier
EncodingType="http://...-wss-soap-message-security-1.0#Base64Binary"
ValueType="http://.../oasis-wss-soap-message-security-1.1#ThumbprintSHA1"
>uYn3PK2wXheN2lLZr4n2mJjoWE0=</wsse:KeyIdentifier>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>OBUcMI...OIPQEUQaxkZps=</xenc:CipherValue>
</xenc:CipherData>
<xenc:ReferenceList>
<xenc:DataReference URI="#EncDataId-28290629"/>
</xenc:ReferenceList>
</xenc:EncryptedKey>
<wsse:BinarySecurityToken
xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
EncodingType="http://...-wss-soap-message-security-1.0#Base64Binary"
ValueType="http://.../oasis-200401-wss-x509-token-profile-1.0#X509v1"
wsu:Id="CertId-2650016">MIICo...QUBCPx+m8/0n33w==</wsse:BinarySecurityToken>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
Id="Signature-12818976">
<ds:SignedInfo>
<ds:CanonicalizationMethod
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#Id-28290629">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>5RQy7La+tL2kyz/ae1Z8Eqw2qiI=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#Timestamp-4067003">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>GAt/gC/4mPbnKcfahUW0aWE43Y0=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>DhamMx...+Umrnims=</ds:SignatureValue>
<ds:KeyInfo Id="KeyId-31999426">
<wsse:SecurityTokenReference
xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="STRId-19267322">
<wsse:Reference URI="#CertId-2650016"
ValueType=".../oasis-200401-wss-x509-token-profile-1.0#X509v1"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</soapenv:Header>
<soapenv:Body
xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Id-28290629">
<xenc:EncryptedData Id="EncDataId-28290629"
Type="http://www.w3.org/2001/04/xmlenc#Content">
<xenc:EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference
xmlns:wsse="http://.../oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:Reference URI="#EncKeyId-urn:uuid:6E12E251E439C034FA12403557497352"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>k9IzAEG...3jBmonCsk=</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</soapenv:Body>
</soapenv:Envelope> |
A primeira diferença é a presença de um elemento <xenc:EncryptedKey> no cabeçalho de segurança. O conteúdo desse elemento fornece uma chave secreta em formato criptografado, usando a chave pública do servidor para realizar a criptografia. A segunda diferença é o conteúdo Body SOAP em si, que foi substituído por um elemento <xenc:EncryptedData>. Esse elemento de dados criptografado faz referência ao valor <xenc:EncryptedKey> do cabeçalho de segurança como chave para a criptografia simétrica utilizada no conteúdo Body.
Usando seus próprios certificados autoassinados
Para obter um certificado digital oficial assinado por uma autoridade de certificados reconhecida, você deve gerar um par de chaves público-privado e utilizar a chave pública para criar uma solicitação por certificado. Em seguida, a solicitação por certificado deve ser enviada para a autoridade selecionada e paga. A autoridade verifica sua identidade (um passo importante para a integridade do processo como um todo, embora às vezes esteja sujeito a falhas embaraçosas) e emite o certificado com sua assinatura.
Para testes ou uso interno, é possível, ao invés disso, gerar os seus próprios certificados autoassinados. O código de exemplo utilizado neste artigo utiliza dois certificados autoassinados: um para o cliente e um para o servidor. O keystore client.keystore utilizado no lado do cliente contém a chave e o certificado privados do cliente, assim como o certificado do servidor (que deve ser armazenado no cliente, de forma que seja aceito como válido sem a aprovação de uma autoridade de certificado ao ser utilizado para assinar, e também de forma que possa ser usado diretamente para criptografia). O keystore server.keystore utilizado no lado do servidor contém a chave e o certificado privados do servidor, além do certificado do cliente (novamente necessário para que o certificado seja aceito como válido).
É possível gerar suas próprias chaves privadas e certificados autoassinados e substituir os seus pares de chave-certificado gerados pelos fornecidos no download. Para fazer isso usando o programa keytool incluído no JDK, abra um console e, primeiramente, entre com essa linha de comando (aqui dividida para se adequar à largura da página: o comando deve ser inserido como uma linha única):
keytool -genkey -alias serverkey -keypass serverpass -keyalg RSA -sigalg SHA1withRSA -keystore server.keystore -storepass nosecret |
O comando anterior gera a chave e o certificado do servidor com o alias serverkey em um novo keystore, chamado server.keystore (caso já possua um server.keystore nesse diretório, primeiro é necessário excluir o par de chaves existente usando esse alias.) O keytool avisa sobre alguns itens de informação utilizados para a geração do certificado (nenhum deles importa realmente para a utilização em testes) e, em seguida, pede que você aprove a informação. Após isso ter sido feito, através da digitação do comando yes, o keytool cria o keystorecom a chave e o certificado privados e depois sai.
Em seguida, siga o mesmo procedimento para criar o par de chaves e o keystore do cliente. Dessa vez, utilize a linha de comando (inserida em uma só linha):
keytool -genkey -alias clientkey -keypass clientpass -keyalg RSA -sigalg SHA1withRSA -keystore client.keystore -storepass nosecret |
O próximo passo é exportar o certificado a partir do keystore de servidor e importar o mesmo para o keystore do cliente. Para exportar, utilize essa linha de comando (aqui dividida para se adequar à largura da página; deve ser inserida como uma só linha):
keytool -export -alias serverkey -keystore server.keystore -storepass nosecret -file servercert.cer |
A exportação cria um arquivo de certificado chamado servercert.cer, o qual pode ser importado em seguida para o keystore do cliente usando essa linha de comando (inserida como linha única):
keytool -import -alias serverkey -keystore client.keystore -storepass nosecret -file servercert.cer |
Quando você executa o comando de importação, o keytool imprime os detalhes do certificado e pergunta se você confia no mesmo. Após você aceitar a chave, digitando yes, ele adiciona o certificado ao keystore e sai.
Para finalizar, exporte o certificado de cliente e importe para o keystore do servidor, executando (em uma só linha):
keytool -export -alias clientkey -keystore client.keystore -storepass nosecret -file clientcert.cer |
Depois, execute (aqui dividido para se adequar à largura da página; deve ser inserido como uma só linha):
keytool -import -alias clientkey -keystore server.keystore -storepass nosecret -file clientcert.cer |
Para utilizar as novas chaves e certificados, o arquivo client.keystore deve ser copiado no diretório cliente/src do código de exemplo antes da execução da compilação do cliente (ou simplesmente copie-o para o diretório cliente/bin para efeito imediato) e o arquivo server.keystore deve ser copiado no diretório servidor/src do código de exemplo antes de executar a compilação do servidor.
As linhas de comando de exemplo nesta seção utilizam os mesmos nomes de arquivo e senhas que no código de exemplo fornecido. Você também pode alterar esses valores ao gerar suas próprias chaves e certificados, mas precisa alterar o código de exemplo para que haja correspondência. a senha do keystore e o nome do arquivo são parâmetros contidos na seção RampartConfig do arquivo de política de cada parte envolvida. A senha da chave do cliente é codificada permanentemente na versão do cliente da classe com.sosnoski.ws.library.adb.PWCBHandler, e a senha da chave do servidor na versão do servidor da mesma classe.
Neste artigo, você viu como utilizar Axis2 e Rampart para criptografia e assinatura WS-Security baseado em políticas. Esses eficientes recursos de segurança são essenciais para diversos tipos de troca de dados de negócios, mas envolvem um custo em termos de processamento adicional. No próximo artigo Serviços Web Java, você verá uma comparação entre os custos de performance relativos a diferentes tipos de segurança, podendo decidir melhor como utilizar a segurança em seus aplicativos.
| Descrição | Nome | Tamanho | Método de download |
|---|---|---|---|
| Source code for this article | j-jws5.zip | 36KB | HTTP |
Informações sobre métodos de download
Aprender
-
Apache Axis2/Java: Visite o local de projeto da ferramenta Axis2 Web services.
-
Rampart: Saiba mais sobre o módulo de WS-Security Rampart para Axis2.
-
"Java Web Services: Ligação de dados em Axis2" (Dennis Sosnoski, developerWorks, July 2007): Esse artigo discute as principais opções de ligação de dados para Axis2. O código de exemplo contido no mesmo é a base para o código de exemplo desta coluna.
-
"Criptografia de chave pública" e "Assinatura digital" (Wikipedia): A Wikipedia oferece artigos de introdução com alto grau de interligação sobre esses tópicos de segurança, incluindo vários links úteis que permitem uma exploração profunda do tema.
-
"Introduzindo a forma canônica de XML" (Uche Ogbuji, developerWorks, dezembro de 2004): Leia uma introdução sobre canonização de XML, um importante passo na utilização de assinaturas digitais para dados XML.
-
"Geração de cadeias de certificado para testar aplicações de Java" (Paul Abbott, developerWorks, August 2004): Descubra como utilizar o kit de ferramentas do software livre OpenSSL para criar cadeias de certificado, basicamente implementando sua própria autoridade de certificados.
-
Compreendendo as especificações dos Serviços Web: Essa série de tutoriais introduz vários dos padrões importantes de serviços Web, incluindo:
- "Compreendendo as especificações dos Serviços Web: Linguagem de Descrição de Serviços Web (Web Services Description Language - WSDL)" (Nicholas Chase, developerWorks, julho de 2006)
- "Compreendendo as especificações dos Serviços Web: Segurança WS" (Nicholas Chase, developerWorks, agosto de 2006)
- "Compreendendo as especificações dos Serviços Web: WS-Policy" (Tyler Anderson, developerWorks, fevereiro de 2007)
-
Segurança de Serviços Web OASIS (OASIS Web Services Security - WSS) TC: Essa é a organização responsável pela especificação de WS-Security e perfis de token. Aqui você encontra links para todas as versões desses padrões.
-
Grupo de Trabalho de Políticas de Serviços Web W3C: Esse grupo define a especificação da WS-Policy.
-
Intercâmbio Seguro de Serviços Web OASIS (OASIS Web Services Secure Exchange - WSS) TC: Essa organização é responsável pela WS-SecurityPolicy e especificações relacionadas.
-
Procure por livros sobre esses e outros assuntos técnicos da livraria de tecnologia.
-
developerWorks Java technology zone: Encontre centenas de artigos sobre todos os aspectos da programação Java.
Obter produtos e tecnologias
-
Apache Axis2: Baixe a mais nova versão do Axis2.
-
Rampart: Baixe o módulo Rampart para o Axis2.
-
A Legião de Bouncy Castle: Baixe o .jar do Bouncy Castle correto para o seu ambiente de execução.
-
TCPMon: Baixe esse software utilitário livre para monitorar conexões TCP.
Discutir
-
entre nos blogs da developerWorks e relacione-se com a comunidade developerWorks.

Dennis Sosnoski é consultor e instrutor especializado em serviços de XML e Web baseados em Java. Possui experiência profissional de mais de 30 anos como desenvolvedor de software, tendo focado seu trabalho nos últimos 10 anos em tecnologias XML e Java no lado do servidor. Dennis é o desenvolvedor principal da estrutura de software livre JiBX XML Data Binding e da estrutura de serviços da Web associada JiBX/WS. Também é "committer" (pessoa autorizada a modificar o código fonte de um determinado software) na estrutura de serviços Web Apache Axis2. Também foi um dos membros do Expert Group para as especificações JAX-WS 2.0 e JAXB 2.0.