Módulo 7 - Figure B

Propaganda
Módulo 7
Segurança
Lição 1
Introdução a Segurança
Versão 1.0 - Jan/2008
JEDITM
Autor
Aldwin Lee
Cheryl Lorica
Equipe
Rommel Feria
John Paul Petines
Necessidades para os Exercícios
Sistemas Operacionais Suportados
NetBeans IDE 5.5 para os seguintes sistemas operacionais:
•
Microsoft Windows XP Profissional SP2 ou superior
•
Mac OS X 10.4.5 ou superior
•
Red Hat Fedora Core 3
•
Solaris™ 10 Operating System (SPARC® e x86/x64 Platform Edition)
NetBeans Enterprise Pack, poderá ser executado nas seguintes plataformas:
•
Microsoft Windows 2000 Profissional SP4
•
Solaris™ 8 OS (SPARC e x86/x64 Platform Edition) e Solaris 9 OS (SPARC e
x86/x64 Platform Edition)
•
Várias outras distribuições Linux
Configuração Mínima de Hardware
Nota: IDE NetBeans com resolução de tela em 1024x768 pixel
Sistema Operacional
Processador
Memória
HD Livre
Microsoft Windows
500 MHz Intel Pentium III
workstation ou equivalente
512 MB
850 MB
Linux
500 MHz Intel Pentium III
workstation ou equivalente
512 MB
450 MB
Solaris OS (SPARC)
UltraSPARC II 450 MHz
512 MB
450 MB
Solaris OS (x86/x64
Platform Edition)
AMD Opteron 100 Série 1.8 GHz
512 MB
450 MB
Mac OS X
PowerPC G4
512 MB
450 MB
Memória
HD Livre
Configuração Recomendada de Hardware
Sistema Operacional
Processador
Microsoft Windows
1.4 GHz Intel Pentium III
workstation ou equivalente
1 GB
1 GB
Linux
1.4 GHz Intel Pentium III
workstation ou equivalente
1 GB
850 MB
Solaris OS (SPARC)
UltraSPARC IIIi 1 GHz
1 GB
850 MB
Solaris OS (x86/x64
Platform Edition)
AMD Opteron 100 Series 1.8 GHz
1 GB
850 MB
Mac OS X
PowerPC G5
1 GB
850 MB
Requerimentos de Software
NetBeans Enterprise Pack 5.5 executando sobre Java 2 Platform Standard Edition
Development Kit 5.0 ou superior (JDK 5.0, versão 1.5.0_01 ou superior), contemplando
a Java Runtime Environment, ferramentas de desenvolvimento para compilar, depurar,
e executar aplicações escritas em linguagem Java. Sun Java System Application Server
Platform Edition 9.
•
Para Solaris, Windows, e Linux, os arquivos da JDK podem ser obtidos para
sua plataforma em http://java.sun.com/j2se/1.5.0/download.html
•
Para Mac OS X, Java 2 Plataform Standard Edition (J2SE) 5.0 Release 4, pode
ser obtida diretamente da Apple's Developer Connection, no endereço: http://
developer.apple.com/java (é necessário registrar o download da JDK).
Para mais informações: http://www.netbeans.org/community/releases/55/relnotes.html
Segurança
2
JEDITM
Colaboradores que auxiliaram no processo de tradução e revisão
Aécio Júnior
Alexandre Mori
Alexis da Rocha Silva
Angelo de Oliveira
Bruno da Silva Bonfim
Denis Mitsuo Nakasaki
Emanoel Tadeu da Silva Freitas
Guilherme da Silveira Elias
Leandro Souza de Jesus
Lucas Vinícius Bibiano Thomé
Luiz Fernandes de Oliveira Junior
Maria Carolina Ferreira da Silva
Massimiliano Giroldi
Paulo Oliveira Sampaio Reis
Ronie Dotzlaw
Auxiliadores especiais
Revisão Geral do texto para os seguintes Países:
•
•
Brasil – Tiago Flach e Vinícius G. Ribeiro (Especialista em Segurança)
Guiné Bissau – Alfredo Cá, Bunene Sisse e Buon Olossato Quebi – ONG Asas de Socorro
Coordenação do DFJUG
•
•
•
•
•
Daniel deOliveira – JUGLeader responsável pelos acordos de parcerias
Luci Campos - Idealizadora do DFJUG responsável pelo apoio social
Fernando Anselmo - Coordenador responsável pelo processo de tradução e revisão,
disponibilização dos materiais e inserção de novos módulos
Rodrigo Nunes - Coordenador responsável pela parte multimídia
Sérgio Gomes Veloso - Coordenador responsável pelo ambiente JEDITM (Moodle)
Agradecimento Especial
John Paul Petines – Criador da Iniciativa JEDITM
Rommel Feria – Criador da Iniciativa JEDITM
Segurança
3
JEDITM
1. Objetivos
Este módulo trata segurança na perspectiva da linguagem Java. Discutiremos porque Java é dito
seguro, o que significa segurança e, mais importante ainda, aprenderemos as melhores formas de
se utilizar os dispositivos de segurança da plataforma Java dentro de seus próprios projetos.
Veremos algumas das características básicas da plataforma Java que proporcionam segurança: o
Class Loader, o verificador de bytecode e o gerente de segurança. Também serão abordadas as
extensões que valorizam o modelo de segurança em Java através das assinaturas digitais,
provedores de segurança, e o controlador de acesso. O objetivo desta lição é fornecer uma
compreensão da arquitetura de segurança do modelo Java e como esse pode ser melhor utilizado.
Pressupõe-se que o leitor tenha um bom conhecimento sobre programação Java. Em particular,
como escrever projetos Java, pois avançados recursos de segurança e algoritmos de criptografia
serão discutidos, é feito de tal forma que o leitor é primariamente interessado em utilizar a
biblioteca para executar determinadas tarefas. Iremos analisar a um nível fundamental o que é
uma assinatura digital e como ela pode ser criada e utilizada; entretanto, não abordaremos a
teoria da criptografia por trás de uma assinatura digital ou provar que uma assinatura digital é
realmente segura.
Para aqueles que são suficientemente versados nestas matérias, mostraremos como as bibliotecas
podem ser incrementadas para suportar novos tipos de algoritmos criptográficos; porém, a
matemática rigorosa e definições de criptografia não serão discutidas neste material.
Ao final desta lição, o estudante será capaz de:
•
Conhecer sobre os principais aspectos de segurança
•
Aprender boas práticas de segurança
•
Observar as práticas de segurança aplicadas à linguagem Java
Segurança
4
JEDITM
2. Introdução a Segurança
O Glossário Nacional de Segurança de Sistemas de Informação dos Estados Unidos (EUA) define
a Segurança de Sistemas de Informação (INFOSEC) como:
Proteção de sistemas de informação contra o acesso não-autorizado a informação ou sua
modificação, seja por meio de armazenamento, processamento ou tráfego, e contra a
negação de serviços aos usuários autorizados ou o fornecimento destes para usuários não
autorizados, incluindo medidas necessárias para detectar, documentar e bloquear tais
ameaças.
Um documento completo para maiores referencias pode ser encontrado em:
http://www.cnss.gov/Assets/pdf/nstissd_501.pdf
Uma forma simples para expressar esta definição é “prover informação e serviço correto para as
pessoas certas no momento certo”. Segurança da informação significa no fornecimento de dados
corretos para pessoas de quem possuem direitos do acesso a tais dados. Além disso, está
incluída o bloqueio da informação para pessoas sem permissão para sua visualização.
Há vários anos atrás, quando as redes de computadores não eram tão dominantes quanto são nos
dias de hoje, era relativamente fácil guardar as informações. Naquela época, para poder causar
algum dano ao sistema, era necessário ter acesso físico ao sistema. Acesso remoto, acessibilidade
das redes e a Internet mudaram tudo isso.
"A rede é o computador", uma expressão criada pela Sun Microsystems em meados dos anos
oitenta, representa uma das grandes verdades destes tempos. Temos mais e mais computadores
conectados à rede a cada dia que passa. Aplicações passaram de um sistema único (por exemplo,
época dos mainframes) para um modelo múltiplo de cooperação entre os diferentes módulos de
sistemas.
À medida que a rede cresce, a segurança da informação torna-se mais vulnerável. Sistemas estão
mais propensos a ataques e outras concessões de acesso e revelam uma janela aberta para
grupos ou indivíduos com motivações hostis.
Especialistas em segurança costumam dizer que a melhor maneira de manter seus dados seguros
é retirar o computador da rede e bloquear todas as portas para o acesso, isso inclui, portas USB,
unidades de CD-ROM, DVD-ROM ou mesmo disquetes.
Infelizmente, nesse estado, o poder do sistema não é explorado de forma eficaz. Em vez de tornar
o sistema indisponível para a rede, medidas de segurança devem ser definidas para proteger as
informações e os serviços.
Segurança
5
JEDITM
3. Os principais aspectos de segurança
Listados abaixo, estão os cinco elementos de segurança da informação aceitos.
●
Confidencialidade – Garantia de que as informações não serão divulgadas a pessoas não
autorizadas, processos ou dispositivos
●
Integridade de Dados – Condição existente que os dados não serão interceptados no
caminho de sua origem e não foram acidentalmente ou maliciosamente, modificados ou
destruídos até seu destino.
●
Disponibilidade – Acesso confiável a dados e informações de serviços para os usuários
autorizados
●
Controle de Acesso – Diz respeito ao acesso físico ou lógico do sistema
●
Não-repúdio – Busca evitar que as partes envolvidas em um acordo ou protocolo neguem
atos realizados. Diz respeito diretamente à votação digital e tratamento com dinheiro.
Confidencialidade, integridade e disponibilidade, também conhecidos como o Infosec da CIA
Triad, são os principais aspectos de segurança. Cada elemento é discutido em detalhe a seguir.
3.1. Confidencialidade
De forma simples, confidencialidade restringe as informações apenas às pessoas que tenham o
acesso autorizado. Isto significa que a informação só estará disponível para aqueles que têm
permissão para acessar os dados. Os indivíduos que não têm acesso por direito à informação e
não devem ser capazes de visualizar os dados e devem ter o acesso negado. A informação não
deve ser fornecida ou difundida além do que é necessário ou autorizado.
Pode ocorrer violação de sigilo quando os dados são acessados, seja, por um indivíduo ou
organização que não estão autorizados a visualizá-los.
Suponha ir a um terminal em uma agência bancária para verificar seu extrato. Coloca-se o cartão
do banco, digita-se a senha e seleciona-se a opção "Emitir Extrato". É possível olhar para o lado
ou para trás, para perceber se existe um “olheiro” observando essa transação. Estes "olheiros"
podem não ser capazes de roubar o seu dinheiro; entretanto, irão saber quanto dinheiro sua
conta contém. Não se deseja permitir que outras pessoas venham a conhecer o conteúdo de sua
conta, quer seja para um fim malicioso ou não. Seu segredo é descoberto no momento em que
alguém vê sua senha, a transação, ou o saldo de sua conta.
Outro exemplo que pode ser pouco claro é o de navegar na Internet em um cybercafé. Algumas
dessas lojas podem ter pacotes de sensores olfativos instalados em seus servidores (sniffers).
Pacotes de sensores olfativos são programas que interceptam e roubam informações que podem
ser usadas para analisar cada registro passado através da rede ou parte da rede. Alguns destes
sensores olfactivos são legitimamente utilizados para monitorar e solucionar problemas de rede.
No entanto, a mesma utilidade pode também causar violações de sigilo. Estes pacotes podem ser
utilizados por indivíduos mal intencionados para obter diversas informações, como o site
navegado, o login do usuário e detalhes mais sigilosos, tais como, a senha de sua conta bancária,
ou seu número de cartão de crédito.
Confidencialidade assegura que as informações estejam seguras, de uma forma que só as pessoas
autorizadas têm a permissão para conhecer os dados.
3.2. Integridade
Garante que as informações passadas não sejam corrompidas ou alteradas de alguma forma. Esta
propriedade não só assegura que sejam autênticas e completas como também, garante que
possam ser confiáveis e invocadas.
Integridade assegura:
●
Modificações não são feitas por pessoal não autorizado aos dados ou processos
Segurança
6
JEDITM
●
Alterações só podem ser realizadas por pessoal autorizado aos dados ou processos
●
Os dados são internamente e externamente consistentes
No entanto, convém notar que a integridade dos dados não é lidar com a exatidão dos dados.
Essa propriedade garante que somente as informações disponíveis no sistema seja a mesma
informação que o usuário acessou em tempos trás. A integridade dos dados pode ser
comprometida quando a informação seja corrompida, voluntariamente ou involuntariamente,
antes que seja lida pelo seu destinatário. Assume-se que, se a informação foi gerada
corretamente, será preservada nesse estado.
A fonte da integridade deve ser de confiança em que o remetente da informação seja alguem que
realmente supomos que seja. A fonte de integridade pode ser comprometida quando um agente
burla ou forja a sua identidade através de informações incorretas enviadas para um destinatário.
Spoofing é uma situação em que os dados de identidade são falsificados por mascaramento numa
situação de verdade.
Uma espécie de falsificação é chamada de phising. Alguns bem conhecidos endereços WEB são
reproduzidos de forma a ter um visual parecido com o do endereço oficial. Este tipo de ataque é
freqüentemente realizado com um endereço falso, onde as vulnerabilidades dos navegadores WEB
são exploradas para encobrir a falha. Estes endereços procuram enganar os usuários solicitando
informações sobre seus dados pessoais, tais como, nome completo, nome de usuário, senha e
informações do cartão de crédito. Sem suspeitas, as vítimas são encaminhados para endereço e
no final uma mensagem de erro é exibida indicando que a senha está incorreta. Neste momento,
a informação já foi recolhida pelos atacantes para o seu próprio benefício.
Integridade de dados é ter confiança de que a informação não foi alterada entre a sua
transmissão e sua recepção. Note que, em um modo formal de segurança, a integridade é mais
interpretada restritivamente no sentido de proteção contra a destruição ou alteração não
autorizada de informações.
3.3. Disponibilidade
No momento em que a informação é necessária, será exibida e pronta para ser utilizada pelo
pessoal autorizado. Todos os recursos autorizados – dados, funcionalidades e serviços - devem
estar disponíveis quando necessários.
É possível que a confidencialidade e a integridade da informação estejam protegidas porém, para
um invasor, estes dados não devem ser acessíveis. Isso faz com que a informação seja inútil e,
portanto, sem garantia.
Um bom exemplo da violação de disponibilidade é o da “negação de serviço” ou “ataque DoS”.
Um “ataque DoS” é um tipo de ataque contra uma determinada rede que se destina a colocar as
redes em situação de submissão de dados, através da inundação de ataques, isto torna o tráfego
inútil. Talvez um dos mais populares foi o ataque "Code Red" realizado em 2001.
Code Red explorava a vulnerabilidade de um determinado servidor WEB para perturbar o serviço.
Code Red era um verme (um programa que conecta-se a outras máquinas e se auto-replica)
que começou a infectar as máquinas rodando suas várias versões no servidor. O vírus enviava seu
código através de uma solicitação HTTP e explorava uma determinada porta – descobrindo a
vulnerabilidade. O vírus era executado no computador do cliente. Em vez de voltar a corrigir uma
página da WEB, o vírus retornava seu próprio código HTML e exibia a seguinte mensagem:
Bem vindo ao http:// www.worm.com!
Atacado por chineses!
Outra versão do mesmo vírus tentou atacar um determinado endereço IP, enviando grandes
quantidades de dados inúteis. O referido endereço IP pertencia ao endereço www.whitehouse.gov.
Em vez de exibir o site oficial da Casa Branca, o texto apresentado acima era exibido. O tráfego
do site da Casa Branca foi redirecionado para um outro endereço IP e atacou o endereço IP numa
URL que não era mais válida.
Disponibilidade
Segurança
é
a
garantia
de
que
os
sistemas
sejam
responsáveis
pela
entrega,
7
JEDITM
armazenamento e processamento de informações e sejam disponíveis quando necessário, por
aqueles que necessitam.
Segurança
8
JEDITM
4. Boas Práticas de Segurança
Boas práticas de segurança dizem respeito aos seguintes atributos:
●
●
●
●
●
Identificação e autenticação
Autorização
Controle de acesso
Não repudiação
Auditoria
4.1. Identificação e Autenticação
É um processo de dois passos que determina quem é autorizado a acessar a informação em um
sistema. Identificação valida a identidade do usuário provendo um identificador único como o
nome de usuário ou o número do usuário. Autenticação é o processo de verificar a identidade
mostrada pelo usuário. Usada para estabelecer a validade da transmissão, do emissor ou da
mensagem.
Autenticação é baseada em, pelo menos, um dos três fatores a seguir:
•
•
•
O usuário sabe – autenticação pode ser verificada provendo algo que somente o usuário
sabe, como a sua senha ou seu número de identificação pessoal (NIP)
O usuário possui – autenticação pode ser verificada provendo detalhes através de um
dispositivo como um cartão inteligente ou um token
O usuário é – autenticação pode ser verificada usando a biometria, tal como, impressão
digital, voz, DNA, retina ou características da íris
Identificação e autenticação são geralmente implementadas para serviços pessoais de aplicações
WEB como programas de envio de e-mail, bancos on-line e leilões on-line para determinar a
identidade do usuário. Sem a identificação e a autenticação, esses serviços não poderiam ser
implementados porque a identidade de outros usuários seria comprometida. Imagine se fosse
possível conhecer a conta bancária de outras pessoas sem nenhuma autenticação. A
confidencialidade dos usuários seria comprometida.
4.2. Autorização
É o privilégio de acesso aos dados de um usuário, sistema ou processo. Autorização define as
permissões e os direitos de um usuário em um sistema. Geralmente, depois de um usuário (ou
processo) ser autenticado, a autorização determina o conjunto de ações que aquele usuário pode
efetuar no sistema.
Os sistemas operacionais mais modernos definem conjuntos de permissões que são variações ou
extensões de três tipos básicos de acesso:
•
Leitura: O usuário pode ler o conteúdo dos arquivos e listar o conteúdo dos diretórios
•
Escrita: O usuário pode mudar o conteúdo de um arquivo ou diretório adicionando,
criando, apagando ou renomeando
•
Execução: Se o arquivo for um programa, o usuário pode executar o programa. Os
usuários podem entrar no diretório
Estas permissões e direitos são implementadas de maneira diferentes nos sistemas baseados em
Controle de Acesso Discrecionária e Controle de Acesso Mandatório.
4.3. Controle de Acesso
Limita o acesso a recursos do sistema a usuários, processos, programas ou outros sistemas
autorizados. Este é um termo genérico para o processo pelo qual um sistema controla a interação
entre os usuários e os recursos do sistema.
O Controle de Acesso provê proteção contra o uso não autorizado de recursos, incluindo:
Segurança
9
JEDITM
●
Uso de recurso de comunicação
●
Leitura, escrita ou deleção em um recurso de informação
●
Execução de um recurso de processamento
Um esquema popular de controle de acesso é manter uma Access Control List (lista de controle de
acesso) ou ACL. ACL especifica quais operações o conjunto de usuários ou grupos podem realizar
nos vários recursos.
Existem dois tipos de Técnicas de Controle de Acesso:
Controle de Acesso Discrecionária (CAD) é um meio de restringir acesso a objetos baseado na
identidade e na necessidade de conhecer dos usuários e/ou grupos dos quais o objeto pertence.
Os controles são Discrecionária na medida em que um objeto com uma certa permissão de acesso
é capaz de passar aquela permissão (direta ou indiretamente) para qualquer outro objeto.
Geralmente, elas são feito na discrição do dono do objeto, como permissões de arquivo/diretório
e posse de usuário/grupo. Sistemas CAD permitem ao usuário determinar inteiramente o acesso
dado aos seus recursos, o que significa que eles podem, por acidente ou malícia, conceder acesso
a usuários não autorizados.
Controle de Acesso Mandatório (CAM) é um meio de restringir acesso a objetos baseado na
sensitividade da informação contida nos objetos e na autorização formal dos assuntos para
acessar informações de tal sensitividade.
A característica mais importante do CAM diz respeito a proibição do controle total de objetos para
os usuários que os criaram. A política de segurança do sistema (como criada pelo administrador)
determina inteiramente os direitos de acesso concedidos, e o usuário não pode conceder acesso
mais restrito ao seus recursos do que aquele que o administrador especifica.
Por exemplo, em CAD, a usuária Alice pode ter a permissão tanto para ler como para alterar um
arquivo, enquanto que o usuário Bob só pode ler o arquivo. Em contraste, um controle de acesso
não discrecionário implica em que todos os usuários acessando um determinado recurso recebem
os mesmos direitos, quaisquer que sejam os níveis de compartilhamento destes.
4.4. Não Repudiação
É o conceito de garantir que um contrato ou um acordo, que tenha sido feito,
negado por nenhuma das partes envolvidas em momento futuro.
não possa ser
●
Não repudiação de origem protege contra o emissor que nega que a mensagem foi
enviada. Prova que o dado foi enviado
●
Não repudiação de entrega protege contra o receptor que nega que os dados foram
recebidos. Prova que o dado foi recebido
Isto significa que a não repudiação garante que o emissor e o receptor foram as partes que
realmente enviaram e receberam as mensagens. Em outras palavras, isto assegura que o emissor
dos dados recebe uma prova da entrega da mensagem e que o receptor recebe uma prova da
identidade do emissor, ou seja, nenhum dos dois poderá negar o fato.
Muitas organizações querem tornar seguras as mensagens de email de seus colaboradores. MIME
Seguro é uma solução de segurança de email comumente suportada pelos servidores nos dias de
hoje pois, não provê somente confidencialidade, autenticação de dados e proteção de integridade
como também a não repudiação para mensagens de email usando assinaturas digitais.
4.5. Auditoria
Auditoria de segurança é a prática de coletar e avaliar as evidências de um sistema das práticas e
operações de uma organização. Auditoria pode envolver avaliar e monitorar sistemas, verificar
computadores em busca de fraquezas de segurança e executar sistemas de detecção de intrusos
que possam sinalizar possíveis falhas. A avaliação das evidências garante se os sistemas de
informação da organização estão seguros, mantem a integridade dos dados e se os mesmos estão
operando efetivamente e eficientemente para atingir os objetivos da organização.
Segurança
10
JEDITM
O objetivo de uma auditoria de TI é revisar e avaliar a disponibilidade, confidencialidade e
integridade dos sistemas de informação das organizações respondendo as seguintes perguntas:
●
Os sistemas da organização estarão disponíveis para o negócio sempre que requeridos?
(Disponibilidade)
●
A informação presente nos sistemas será mostrada apenas para usuários autorizados?
(Confidencialidade)
●
A informação provida pelo sistema é sempre confiável, precisa, e entregue no prazo?
(Integridade)
Vale a pena notar que auditoria de maneira nenhuma prevê ataques. Apenas grava eventos,
maliciosos ou não, para que se tenha registro de possíveis brechas que possam ser consultados
para ajudar a reparar os defeitos.
Trilhas de Auditoria podem ser implementadas em qualquer sistema para gravar todas as ações
dos usuários. Auditoria pode ser usada para monitorar as atividades dos usuários garantindo que
nenhuma atividade maldosa seja executada. Eventos, ações e data e hora que podem ser usados
para auditar uma atividade do usuário em um determinado sistema.
Segurança
11
JEDITM
5. Práticas de Segurança e a linguagem Java
A plataforma Java foi projetada com uma forte ênfase em segurança. A linguagem Java é segura
em tipagem. O código só pode acessar locais de memória que é autorizado a acessar, e somente,
numa maneira bem definida. No mais, na linguagem Java, carregamento seguro de classe e
mecanismo de verificação garantem que apenas código Java legítimo seja executado.
A arquitetura inclui um grande conjunto de interfaces de programação de aplicações,
ferramentas, e implementações de protocolos de segurança, mecanismos e algoritmos
comumente usados,
Interfaces de infra-estrutura de criptografia e chave pública provêm as bases para se desenvolver
aplicações seguras. Interfaces para atribuir autenticação e controle de acesso habilitam aplicações
a se resguardarem contra acessos não autorizados de recursos protegidos.
Isto dá ao usuário um framework de segurança consistente para se escrever aplicações, e
também provê ao usuário ou administrador um conjunto de ferramentas para gerenciar as
aplicações de maneira segura.
A plataforma Java implementa boas práticas de seguranças como mostradas na tabela abaixo.
Cada característica será discutida em maior detalhe ao longo deste módulo.
Confidencialidade
Integridade
Disponibilidade
JVM
X
X
Carregadores de Classe
X
Gerenciadores de Segurança
X
Identificação e
Autenticação
NãoRepudiação
X
X
X
X
Assinaturas e Certificados
Digitais
X
X
Lista de Controle de Acesso
X
X
X
X
X
X
X
Tabela 1: Boas Práticas de Segurança e a linguagem Java
Segurança
Conteúdo
X
Sumário de Mensagens
JAAS
Auditoria
X
Domínios de Segurança
Criptografar, Encriptar, SSL
Autorização
12
Módulo 7
Segurança
Lição 2
Sandbox
Versão 1.0 - Jan/2008
JEDITM
1. Objetivos
Sempre que é discutida a segurança Java, normalmente ela é focada no modelo de Sandbox
(caixa de areia). Este modelo permite ao usuário final configurar restrições e permissões para um
programa.
Tradicionalmente, executamos segurança nos computadores baseados em um sistema de
confiança. Antes de executar um sistema, deveríamos ter a confiança no código-fonte deste. Uma
vez que a aplicação tenha passados por esta checagem, possui mandato no sistema em que está
sendo executada. Se por qualquer motivo o programa possui uma intenção maliciosa, poderá
destruir o sistema sem problemas, visto que não existem restrições ao programa. Expondo isto de
forma simples, para proteger o computador devemos restringir o que fazer em primeiro lugar.
Ao final desta lição, o estudante será capaz de:
•
Identificar o modelo de segurança padrão empregado – Sandbox
•
Conhecer os componentes da Sandbox
•
Realizar as configurações dos componentes da Sandbox
•
Definir os domínios de proteção de sua aplicação
•
Aplicar a política de segurança, por intermédio das permissões
•
Entender como as classes podem ser assinadas (certificação digital)
Segurança
4
JEDITM
2. Modelo Sandbox
O modelo Sandbox torna fácil proteger o computador de programas carregados a partir de fontes
não confiáveis. Em lugar de obter a confiança no recurso, a segurança é alcançada permitindo que
qualquer programa seja executado entretanto, restringindo suas ações que poderia danificar seu
computador. A vantagem é que invés de restringir ou checar qualquer coisa que entra em seu
computador, a Sandbox permite que qualquer programa seja executado e previne que este
realizar qualquer ação que poderia danificar o sistema.
O modelo de segurança Java gira ao redor desta idéia de uma Sandbox. Gira ao redor da idéia de
que quando é permitido que um programa seja executado no computador, deveríamos ser
capazes de fornecer um ambiente onde o programa pode fazer o que foi projetado para fazer e ao
mesmo tempo permite a habilidade de restringir acesso aos recursos que ele não deveria acessar.
Há diferentes tamanhos de Sandbox tipicamente configuradas para um programa Java. Estes são:
●
Mínima – Onde um programa tem acesso à CPU, possui sua própria memória, bem como
acesso para dispositivos de interface humana (isto é, Monitor, Teclado, Mouse)
●
Padrão – Similar a mínima, exceto que também fornece acesso ao servidor WEB da qual
o programa Java foi carregado
●
Restrita – Similar a padrão, exceto que também fornece acesso para certos, mas não
todos, recursos do sistema operacional
●
Aberta – Esta permite ao programa acessar qualquer recurso do sistema anfitrião
O modelo de segurança Java envolve todo aspecto de sua arquitetura. Para ser capaz de
assegurar que este está corretamente no lugar em que necessitamos olhar para diferentes partes
da arquitetura, bem como entender como estas tecnologias funcionam juntas.
Os seguintes componentes são os fundamentos do Modelo de Segurança Java:
•
•
•
•
Características de segurança construídas na JVM (Máquina virtual Java)
A arquitetura do carregador de classes
O verificador de arquivos de classe
O gerenciador de segurança e a API Java
Uma força chave do modelo da Sandbox Java é sua customização. Da lista acima, o carregador de
classes e o gerenciador de segurança são completamente customizáveis. Para definir sua própria
segurança, você define sua própria implementação ou subclasses de java.lang.SecurityManager.
Com sua implementação do gerenciador de segurança, você define seus próprios métodos para
controlar o acesso a um determinado recurso - como escrever em um arquivo, por exemplo.
Adicionalmente, é recomendado criar um gerenciador de segurança quando criamos um
carregador para instanciar classes a partir de recursos não confiáveis.
Segurança
5
JEDITM
3. Componentes Principais de uma Sandbox
Três componentes da Sandbox funcionam juntos para assegurar a segurança Java quando o
código é carregado. O gerenciador de segurança reforça os limites de uma Sandbox e restringe as
ações que são válidas.
3.1. Carregador de Classes
Em Java, o carregador de classes carrega os bytecodes a partir das classes compiladas, força
limites de espaços de nome. Isto controla partes da JVM que o código pode acessar.
Classes carregadas a partir do sistema de arquivos local tem seu próprio nome de espaço.
Adicionalmente, um nome de espaço para cada fonte de rede também é definido. Isto assegura e
protege a JVM do conflito provocado por classes com o mesmo nome.
3.2. Verificador de Bytecodes
Uma vez que o carregador de classes carregou uma classe, chama o verificador de bytecodes. A
tarefa principal deste é conferir o código para ver se o mesmo está de acordo com a especificação
da linguagem de programação Java e procurar por quaisquer das seguintes violações:
●
Regras da linguagem de Programação Java
●
Restrições de nome de espaço
●
Estouros de pilha (overflows e underflows)
●
Conversão ilegal de tipos de dados
O verificador de bytecodes pode provar o seguinte:
●
O arquivo de classe tem o formato correto
●
Classes finais não possuem subclasses e métodos finais não estão realizando polimorfismo
por override
●
Cada classe possui uma única superclasse
●
Não existe conversão ilegal de atributos para tipos primitivos
●
Não existe conversão ilegal em objetos
3.3. Gerenciador de Segurança
O propósito do gerenciador de segurança é determinar quais operações uma classe tem permissão
para executar. De forma simples, o gerenciador de segurança é responsável por determinar a
maioria dos parâmetros de uma Sandbox Java. Se um sistema em Java tenta escrever em um
arquivo ou conectar-se a um recurso de rede, primeiro deve obter permissão do gerenciador de
segurança. Além disso, quando um aplicativo deseja modificar o estado dos serviços, o
gerenciador de segurança controla tais operações se as julgar perigosas.
Por padrão, um gerenciador de segurança não é usado quando um programa está sendo
executado. Para habilitar uma aplicação Java a usá-lo, deve ser especificado:
–D java.security.manager
Para applets e plug-ins Java, o gerenciador de segurança é carregado automaticamente. Para
reforçar: aplicações Java não tem um gerenciador de segurança instalado por padrão, enquanto
Applets Java possuem um gerenciador de segurança muito estrito para proteger recursos locais.
Segurança
6
JEDITM
4. Elementos da Sandbox Java
Em termos de administração de segurança, Java possui os seguintes elementos:
4.1. Permissões
Cada classe tem um conjunto de permissões que define o que está autorizada a executar. A
Sandbox Java é então definida com base nestas permissões. Quando uma ação é executada por
uma classe, as permissões são checadas pela máquina virtual. Se determinada classe não deveria
ter a permissão para executar uma ação em particular, uma exceção é lançada e a operação é
bloqueada.
Classes do núcleo da API Java sempre possuem permissão para executar qualquer ação.
Quaisquer outras classes, mesmo que definidas na variável classpath, devem ter permissão para
executar qualquer ação sensível. Estas permissões são definidas em arquivos de política de
segurança que são administrados pelos usuários finais e são usados pela Sandbox para gerenciar
os arquivos de política de segurança.
Permissões empregadas pela máquina virtual são baseadas em um sistema de classes. Isto
significa que qualquer aplicação ou API pode definir suas próprias permissões e assegurar que os
usuários ou administradores possuem as permissões das APIs antes de executar. A próxima lista
descreve as permissões padrões usadas pelo núcleo de classes Java:
●
java.io.FilePermission
○
●
java.net.SocketPermission
○
●
Ações: nenhuma, as classes tem ou não permissão para executar uma operação em
tempo execução
java.lang.reflect.ReflectPermission
○
●
Ações: nenhuma, as classes tem ou não permissão para executar uma operação em
tempo execução
java.io.SerializablePermission
○
●
Ações: nenhuma, as classes tem ou não permissão para executar uma operação em
tempo execução
java.security.SecurityPermission
○
●
Ações: nenhuma, as classes tem ou não permissão para executar uma operação em
tempo execução
java.net.NetPermission
○
●
Ações: nenhuma, as classes tem ou não permissão para executar uma operação em
tempo execução
java.awt.AWTPermission
○
●
Ações: ler e escrever
java.lang.RuntimePermission
○
●
Ações: aceitar, escutar, conectar e resolver
java.util.PropertyPermission
○
●
Ações: ler, escrever, apagar e executar
Ações: nenhuma, as classes tem ou não permissão para executar uma operação em
tempo execução
java.security.AllPermission
○
Segurança
Ações: nenhuma, as classes tem ou não permissão para executar uma operação em
tempo execução
7
JEDITM
4.2. Code Sources
São localizações que indicam de onde as classes serão carregadas. Pode incluir a URL das classes
bem como quem as assinou. É importante notar que ambos são opcionais. A URL da classe pode
ser o um arquivo de URL do sistema ou da rede.
Pode-se associar permissões baseadas na URL a partir da qual foi carregada ou baseada somente
em quem a assinou. Também é possível assinalar permissões de uma combinação de URL e do
assinador. Esta URL dentro do código fonte também é conhecida como codebase.
Code Sources são uma combinação de codebase e assinante. O campo assinante deve coincidir
com o pseudônimo armazenado na chave keystore. Codebases podem ser quaisquer URL válidas,
e como tais, usam barras "/" como separadores de níveis ou diretórios, mesmo que estejam no
sistema de arquivos local.
O final da URL também tem um impacto na definição. Existem quatro casos:
●
URL especifica um arquivo .jar – Somente classes dentro daquele arquivo jar são parte do
codebase
●
URL termina com uma barra – Somente arquivos .class no diretório são parte do codebase.
Arquivos não estão inclusos
●
URL termina com um asterisco – Todos os arquivos .jar e .class no diretório pertencem ao
codebase
●
URL termina com um hífen – Todos os arquivos .jar e .class pertencem ao diretório e todos
os subdiretórios pertencem ao codebase
Note que a estrutura de um diretório não é afetada pelo nome do pacote. Por exemplo, para
carregar a classe up.jedi.Login, não é necessário adicionar o diretório acima onde aparece. Por
exemplo, jedi.upd.edu.ph/, e não jedi.upd.edu.ph/up/jedi/.
Segurança
8
JEDITM
5. Domínios de Proteção
Um domínio de proteção é uma associação de permissões com um Code Source em particular.
Domínios de proteção são o conceito básico para uma Sandbox padrão. Informam como o código
carregado de um local em particular como por exemplo, www.sun.com tem a permissão de
escrever em um arquivo e possui o código assinado por quem tem a permissão para iniciar os
trabalhos de impressão.
5.1. KeyStores
Classes Java podem ser assinadas através do uso de certificados digitais juntamente com a
ferramenta jarsigner. Deste modo, temos a possibilidade de conceder permissões para código
assinado por uma determinada entidade.
O código assinado pode ser manipulado através do uso de uma keystore. A keystore é
basicamente onde é armazenado os certificados, de chave pública, que são usados para assinar o
código utilizado. Antes de executar qualquer código assinado, a chave pública é usada para
assiná-la dever ser obtida e então instalada na keystore do sistema. Alguns sistemas, como os
navegadores, aceitam certificados de chave pública quando os arquivos são carregados pela
primeira vez, sendo então assinados mas, usualmente, estes certificados devem ser
descarregados e instalados antes de se executar um aplicativo.
Keystores são administradas através do utilitário keytool fornecido na distribuição padrão Java.
Por padrão, a keystore é localizada em um arquivo chamado .keystore encontrada no diretório
home do usuário. Quando um certificado de chave pública é instalado em uma keystore, o
administrador insere no certificado um nome ou um apelido que é usado para uma futura
referência. Este apelido é usado na administração dos vários certificados e também é o nome
usado no arquivo de política de segurança.
5.2. Arquivos de Política de Segurança
A administração da Sandbox Java é feita listando-se várias permissões no arquivo de política de
segurança Java. A JVM pode manipular múltiplos arquivos de política de segurança em várias
localizações. Apesar disso, por padrão, usa dois arquivos de política de segurança específicos. O
arquivo de política de segurança global java.policy, encontrado em $JREHOME/lib/security/, é
usado por todas as instâncias de uma JVM no sistema. É considerado um arquivo de política de
segurança global porque é compartilhado por todas as instâncias da JVM a partir deste JRE
particular.
Em adição ao arquivo de política de segurança global, um usuário específico chamado .java.policy
também pode ser encontrado no diretório do usuário. As permissões definidas neste arquivo mais
aquelas no arquivo de política de segurança global definem as permissões dadas para um
programa.
Arquivos de política de segurança são apenas arquivos de texto simples. Ferramentas como
policytool podem ser usadas para administrá-los. Estes arquivos de política de segurança também
são usados com a JAAS (Java Authentication and Authorization System) e, devido às nuances de
sintaxe, é recomendado editá-los manualmente.
Na maioria dos casos, o código carregado advém de uma única localização. Há também casos
onde existem múltiplas Code Sources para uma simples localização, tal como, utilizar a RMI. Para
casos como esse, as permissões dando acesso a um recurso em particular é sempre a interseção
de permissões e de todas as permissões concedidas em todos os codebases.
Permissões concedidas para uma única Code Source são concedidas como a união de todas as
permissões dos arquivos de política usados pela JVM para todas as Code Sources relacionadas,
bem como os assinantes, enquanto que para uma aplicação remota as permissões são
determinadas pela interseção de todas as permissões em todas as Code Sources ativas.
Segurança
9
JEDITM
5.2.1. A Ferramenta Policytool
É um software gráfico usado para gerenciar o arquivo java.policy. Possui um argumento na linha
de comando
–file nomeDoArquivo
Que se informa o nome do arquivo de política de segurança que se deseja editar. Este argumento
não define o carregamento do arquivo $HOME/.java.policy no diretório do usuário; se este arquivo
não existir, por padrão, nenhum arquivo é carregado.
5.2.2. Permissões além dos Arquivos de Política de Segurança
A maioria das permissões dadas para um conjunto de código vem dos arquivos de política de
segurança. Entretanto, algumas aplicações tem garantia de permissão para o código que elas
carregam, e os carregadores de classe padrão Java concedem permissão adicional para classes
que elas carregam.
Classes carregadas a partir do sistema de arquivos têm permissão para ler arquivos no diretório e
subdiretórios a partir dos quais elas foram carregadas. Classes carregadas a partir de um recurso
de rede através de HTTP têm, por padrão, concessão para conectar-se de volta com o host de
onde elas foram carregadas; elas também têm permissão para aceitar conexões a partir daquele
host.
5.2.3. O arquivo java.policy
Por padrão, usuários não têm acesso ao arquivo java.policy em seu diretório home, o que significa
que o conjunto padrão de permissões para todos os programas Java sendo executados na JVM,
são definidos no arquivo encontrado em $JREHOME/lib/security/java.policy.
Este é o conteúdo do arquivo java.policy para Java 5:
keystore "${user.home}${/}.keystore";
// As extensões padrões obtém as permissões por default
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
// Padrão de
grant {
permission
permission
permission
permission
permission
permission
permission
permission
permission
permission
permission
permission
permission
"read";
permission
"read";
permission
permission
"read";
permission
"read";
permission
"read";
permission
permission
Segurança
permissões garantidas para todos os domínios
java.lang.RuntimePermission "stopThread";
java.net.SocketPermission "localhost:1024-", "listen";
java.util.PropertyPermission "java.version", "read";
java.util.PropertyPermission "java.vendor", "read";
java.util.PropertyPermission "java.vendor.url", "read";
java.util.PropertyPermission "java.class.version", "read";
java.util.PropertyPermission "os.name", "read";
java.util.PropertyPermission "os.version", "read";
java.util.PropertyPermission "os.arch", "read";
java.util.PropertyPermission "file.separator", "read";
java.util.PropertyPermission "path.separator", "read";
java.util.PropertyPermission "line.separator", "read";
java.util.PropertyPermission "java.specification.version",
java.util.PropertyPermission "java.specification.vendor",
java.util.PropertyPermission "java.specification.name", "read";
java.util.PropertyPermission "java.vm.specification.version",
java.util.PropertyPermission "java.vm.specification.vendor",
java.util.PropertyPermission "java.vm.specification.name",
java.util.PropertyPermission "java.vm.version", "read";
java.util.PropertyPermission "java.vm.vendor", "read";
10
JEDITM
permission java.util.PropertyPermission "java.vm.name", "read";
};
A primeira linha define keystores inclusas no arquivo $USER/.keystore, onde verifica os
certificados de entidades que possuem código assinado. As próximas seções definem permissões
para certas propriedades Java.
Por padrão, as classes têm permissão para chamar Thread.stop(), escutar uma porta não
privilegiada e umas poucas propriedades do sistema. É importante notar que, enquanto a
aplicação tem a permissão para escutar as conexões de socket, não lhes é permitida as aceitas,
por padrão.
5.2.4. O arquivo java.security
Por padrão, a Sandbox é controlada por meio dos dados do arquivo java.security encontrado em
$JREHOME/lib/security/java.security/, que pode ser editado por administradores de sistema.
Para definir um novo arquivo de política de segurança, o definimos como:
policy.url.n = url
Onde n é um número. Por exemplo, os arquivos de política padrão são definidos como:
policy.url.1=file:${java.home}/lib/security/java.policy
policy.url.2=file:${user.home}/.java.policy
Outra entrada no arquivo java.security é policy.expandProperties, permite substituições para
outras propriedades para o tornar mais portável.
Também é possível aos usuários definir seu próprio arquivo de política de segurança por ocasião
da execução de uma aplicação através de linha de comando. Para restringir este comportamento,
ajustamos a propriedade policy.AllowSystemProperty para false. Isso determina uma Sandbox
com permissões e propriedades padrões que não podem ser modificadas pelos usuários finais.
Segurança
11
Módulo 7
Segurança
Lição 3
Gerenciadores de Segurança
Versão 1.0 - Jan/2008
JEDITM
1. Objetivos
De acordo com a perspectiva das API’s do Java, existe um gerenciador de segurança que se
encontra no controle do policiamento da segurança de uma aplicação. Existem também outras
facetas que são de importância para a segurança em Java, mas a regra utilizada pelo gerenciador
é vital na definição das rotinas de segurança que serão ativadas na execução de um programa em
particular.
Numa visão simplificada, o gerenciador de segurança é responsável por determinar a maioria dos
argumentos da Java Sandbox. Isto significa que todas as regras estabelecidas pelo gerenciador
determinam quais operações são permitidas ou não. Se um aplicativo Java tenta abrir um
arquivo, deseja se conectar com outra máquina em uma rede, ou se deseja alterar o estado de
um serviço, o gerenciador de segurança decide se autoriza tais operações ou as rejeita,
baseando-se nas regras definidas.
Ao final desta lição, o estudante será capaz de:
•
Compreender a arquitetura dos Gerenciadores de Segurança
•
Conhecer os métodos dos Gerenciadores de Segurança
•
Construir um Gerenciador de Segurança customizado
Segurança
4
JEDITM
2. Gerenciadores de Segurança de Aplicativos
Applets Java possuem uma segurança muito restritiva por padrão. Por outro lado, aplicativos Java
não possuem estas definições de segurança por padrão. Então, é possível notar que o gerenciador
de segurança é utilizado somente se for explicitamente instalado.
Figura 1: Estrutura do Gerenciador de Segurança Java
Para instalar o gerenciador de segurança especifica-se o parâmetro –D java.security.manager na
execução de um aplicativo Java. Este gerenciador será automaticamente instalado pelo Applet
Viewer ou um plug-in Java.
É possível conferir se uma aplicação já se encontra com o gerenciador de segurança carregado
também é possível. Isto é realizado por meio do método getSecurityManager. Este método
retorna uma referência relativa ao gerenciador de segurança ou null, se nenhum gerenciador de
segurança foi carregado ou definido. Como exemplo:
SecurityManager secureApp = System.getSecurityManager();
Outro método que pode ser utilizado é o setSecurityManager. Permite que o usuário configure
manualmente o gerenciador de segurança para o sistema. Como por exemplo:
System.getSecurityManager(new SecurityManagerImpl());
Segurança
5
JEDITM
3. Métodos dos Gerenciadores de Segurança
Agora que conhecemos como o gerenciador de segurança funciona, mostraremos o que o
gerenciador de segurança é capaz de fazer. As classes do gerenciador de segurança,
SecurityManager, oferecem métodos para realizar a verificação de segurança. Estes métodos
agem em conjunto com alguns aspectos do sistema Java, tais como: acesso à arquivos, à rede, à
própria JVM, à serviços, à recursos do sistema e mesmo a aspectos de segurança.
3.1. Rastrear o Acesso
A seguir, temos os métodos utilizados pelo gerenciador de segurança para rastrear o acesso a
arquivos.
public void checkRead(FileDescriptor fd)
public void checkRead(String file)
public void checkRead(String file, Object context)
Estes métodos verificam se o programa tem permissão para ler um arquivo. O primeiro destes
métodos obtém sucesso quando a proteção de domínio permite, em tempo de execução, acesso
ao arquivo por meio do FileDescriptor. Os próximos métodos obtém sucesso se a proteção de
domínio possui permissão de leitura em um arquivo com um nome que coincida ao argumento
contendo o nome do arquivo informado e a partir de um determinado contexto.
public void checkWrite(FileDescriptor fd)
public void checkWrite(String file)
Estes métodos verificam se o programa tem permissão para escrever em um determinado
arquivo. O primeiro método obtém sucesso quando a proteção de domínio permite, em tempo de
execução, acesso ao arquivo por meio do FileDescriptor. E o segundo obtém sucesso se a proteção
de domínio possuir permissão de gravação em arquivo com o nome que coincida ao argumento
contendo o nome do arquivo informado.
public void checkDelete(String file)
Este método verifica se o programa tem permissão para eliminar um determinado arquivo. Obtém
sucesso se a proteção de domínio possuir uma permissão de exclusão de um arquivo com um
nome que coincida ao argumento contendo o nome do arquivo informado.
O gerenciador de segurança utiliza os seguintes métodos para verificar o acesso à rede:
public void checkConnect(String host, int port)
public void checkConnect(String host, int port, Object context)
Estes métodos servem para verificar se o programa pode abrir uma conexão com outro
computador em uma porta no computador host. Obtém sucesso se a proteção de domínio possuir
uma conexão via socket com o mesmo nome do host, porta e em determinado contexto.
public void checkListen(int port)
Este método verifica se o programa pode criar um novo servidor em determinada porta. A
proteção de domínio deve possuir uma permissão de monitoramento de socket onde o host seja
localhost, o alcance da porta inclua a porta especificada aberta com a ação de escutar.
public void checkAccept(String host, int port)
Este método verifica se o programa pode aceitar (em um servidor existente) a conexão de um
cliente que possa ser originária de um dado host e porta. A proteção de domínio deve possuir
uma permissão de socket onde o host e a porta sejam iguais aos argumentos passados para o
método.
public void checkMulticast(InetAddress addr)
public void checkMulticast(InetAddress addr, byte ttl)
Este método verifica se o programa pode criar um multicast em um determinado endereço IP.
Segurança
6
JEDITM
Obtém sucesso se a proteção de domínio possuir uma permissão de se conectar e aceitar
conexões socket com um host que seja igual ao endereço dado, o alcance da porta de todas as
portas, e a ação.
public void checkSetFactory()
Este método verifica se o programa possui permissão para alterar a fábrica de sockets. Quando a
classe Socket é utilizada para criar o socket, recebe um novo socket da fábrica, que fornece um
socket padrão baseando-se no protocolo TCP. Entretanto, um programa poderia instalar uma
fábrica de sockets que fizesse com que todos os sockets fornecidos possuam semânticas
diferentes, tais como sockets que enviam informações sobre o rastreamento de algum dado. A
proteção de domínio deve ter permissão durante a execução de um programa através do método
setFactory.
3.2. Verificar a JVM
A verificação da JVM é realizada empregando-se os seguintes métodos:
public void checkCreateClassLoader()
A distinção realizada sobre as classes confiáveis e não confiáveis é baseada no local de onde a
classe é carregada. Como resultado, o carregador de classe toma uma importante decisão, desde
que o gerenciador de segurança esteja configurado para questionar ao carregador onde a classe
se encontra. O carregador é também responsável por certificar de que determinadas classes são
assinadas. Tipicamente, uma classe não confiável não pode ser permitida para realizar uma
criação de um carregador de classes. Este método é chamado somente pelo construtor da classe
através do Class Loader: se for possível realizar a criação de um objeto desta classe (ou obter
previamente a referência de um objeto) para fazer uso deste. Para obter sucesso, a proteção de
domínio deve possuir permissão durante a execução.
public void checkExec(String cmd)
Este método é utilizado para prevenir a execução arbitrária de comandos de sistema vindos de
classes não confiáveis. Classes não confiáveis não podem, por exemplo, executar um processo
distinto que remova todos os arquivos de seu disco rígido. Este processo necessitaria não ser
escrito em Java, é claro, pois, caso contrário, não existiria gerenciador de segurança que pudesse
restringir esta ação. Para obter sucesso, a proteção de domínio deve possuir uma permissão de
execução com o mesmo nome do comando dado através do argumento passado.
public void checkPropertiesAccess( )
public void checkPropertyAccess(String key)
A JVM possui um conjunto de propriedades globais (de sistema) que contém informações sobre o
usuário e a máquina: nome do usuário, diretório raiz, entre outras informações. Classes não
confiáveis têm, geralmente, acesso negado a algumas dessas informações em uma tentativa de
limitar a quantidade a informações (espionagem) que uma applet pode fazer. Normalmente, estes
métodos apenas previnem acesso às propriedades de sistema; uma classe não confiável é livre
para enviar suas próprias propriedades e compartilhá-las com outras classes se assim desejar.
Para ter sucesso, o domínio corrente de proteção deve carregar uma permissão de propriedade.
Se uma chave é especificada, então o nome da permissão de propriedade deve incluir
determinada chave e ter definida uma ação de leitura. De outra forma, o nome da permissão de
propriedade deve ser “*” e a ação deve ser de leitura e escrita.
public boolean checkTopLevelWindow(Object window)
Classes Java, sem levar em consideração se são confiáveis ou não confiáveis, normalmente tem
permissão para criar janelas top-level no desktop do usuário. Entretanto, há uma preocupação
acerca de uma classe não confiável apresentar uma janela que se pareça exatamente com outra
aplicação no desktop do usuário e, dessa forma, guiar o usuário a fazer algum procedimento que
não deveria ser realizado. Por exemplo, uma applet pode apresentar uma janela que se pareça
exatamente com uma sessão telnet e então capturar a senha do usuário quando este responder a
um pedido de informação da senha. Por essa razão, janelas top-level são criadas por classes não
Segurança
7
JEDITM
confiáveis e possuem um banner de identificação. Esse método é diferente de outros no
gerenciamento de segurança, pois possui três resultados:
●
Retornar verdadeiro, a janela será criada normalmente;
●
Retornar falso, a janela será criada com o banner de identificação.
●
Lançar uma exceção de segurança (assim como todos os outros métodos da classe de
gerenciamento de segurança) para indicar que a janela não deve ser criada; a maioria dos
aplicativos criados realiza esta ação.
Para que este método retorne verdadeiro, o domínio de proteção corrente deve carregar uma
permissão AWT denominada showWindowWithoutWarningBanner.
3.3. Aspectos Gerais de Segurança
Estes são os métodos cuidam de alguns aspectos de segurança:
public void checkMemberAccess(Class clazz, int which)
A API reflection é poderosa o bastante a tal ponto que, por inspeção, um programa pode
determinar atributos de instância e métodos particulares de uma classe (embora não possa
realmente acessar esses atributos ou chamar esses métodos). Todas as classes tem a permissão
de inspecionar qualquer outra classe e descobrir seus atributos e métodos públicos. Todas as
classes carregadas pelo mesmo Class Loader tem a permissão de inspecionar todos os atributos e
métodos de outras. De outra forma, o domínio de proteção corrente deve carregar uma permissão
em tempo de execução com o nome de accessDeclaredMembers. A implementação padrão desse
método é bastante frágil. Diferente de todos os outros métodos que foram vistos, é um erro lógico
sobrescrever esse método e depois chamar super.checkMemberAccess( ).
public void checkSecurityAccess(String action)
O pacote de segurança depende desse método no gerenciador de segurança para decidir quais
classes podem executar certas operações relacionadas a segurança. Como um exemplo, antes de
uma classe ter permissão de uma chave particular, esse método é chamado com um argumento
do tipo String indicando que uma chave particular está sendo lida. Como é esperado, apenas
classes confiáveis têm permissão de executar quaisquer operações relacionadas a segurança.
public void checkPackageAccess(String pkg)
public void checkPackageDefinition(String pkg)
Esses métodos são usados em conjunto com um Class Loader. Quando um Class Loader é
requisitado a carregar uma determinada classe com um nome de pacote em particular, primeiro
perguntará ao gerenciador de segurança se é permitido realizar esta ação através da chamada ao
método checkPackageAccess(). Isso permite ao gerenciador de segurança ter certeza que uma
classe não autorizada não está tentando usar classes específicas da aplicação que ela não deveria
ter conhecimento. Similarmente, quando um Class Loader realmente cria uma classe em um
pacote em particular, pergunta ao gerenciador de segurança se é permitido fazê-lo, através da
chamada ao método checkPackageDefinition(). Isso permite ao gerenciador de segurança prevenir
que uma classe não confiável carregue uma classe a partir da rede e armazene-a dentro, por
exemplo, do pacote java.lang.
Repare na distinção entre esses dois métodos: no caso do método checkPackageAccess(), a
pergunta é se o Class Loader pode referenciar a classe como um todo, isto é, se podemos chamar
uma classe no pacote da Sun. No método checkPackageDefinition(), os bytes da classe foram
carregados e o gerenciador de segurança está sendo questionado se eles podem pertencer a um
pacote em particular. Por padrão, o método checkPackageDefinition() nunca é chamado e o
método checkPackageAccess() é chamado apenas para pacotes listados na propriedade
package.access dentro do arquivo java.security. Para ter sucesso, o domínio de proteção corrente
deve ter uma permissão em tempo de execução com o nome de defineClassInPackage.+<pkg>
ou accessClassInPackage.+<pkg>.
Segurança
8
JEDITM
4. Construir Gerenciadores Customizados
Para criar seu próprio gerenciador de segurança, é necessário estender a classe SecurityManager.
Como visto na sessão anterior, a classe SecurityManager possui uma série de métodos que podem
ser chamados pelas classes Java. Sobrescrevendo esses métodos, pode-se criar políticas de
segurança definidas pelo usuário que, quando carregadas em uma aplicação, irão fornecer o nível
de segurança desejado.
Há algumas considerações a serem observadas ao se construir um gerenciador de segurança. Aqui
estão algumas questões que podem ajudar neste processo:
●
Quais métodos devem ser implementados?
●
Novos métodos precisam ser adicionados?
●
Como os métodos serão implementados?
●
Quão restritas as regras de segurança deverão ser?
Um exemplo de um gerenciador de segurança customizado é a classe PasswordSecurityManager,
detalhada em:
http://java.sun.com/developer/onlineTraining/Programming/JDCBook/signed2.html
O gerenciador de segurança customizado para este programa solicita que o usuário entre com sua
senha antes de permitir um FileIO para escrever um texto em um arquivo ou ler um texto a partir
de um arquivo.
Import java.io.*;
public class PasswordSecurityManager extends SecurityManager{
private String password;
private BufferedReader buffy;
public PasswordSecurityManager(String p, BufferedReader b){
super();
this.password = p;
this.buffy = b;
}
// Solicitar ao usuário final uma senha, verifica a senha e retorna
// verdadeiro se a senha estiver correta ou falso se não estiver.
private boolean accessOK() {
int c;
String response;
System.out.println("Senha, por favor:");
try {
response = buffy.readLine();
if (response.equals(password))
return true;
else
return false;
} catch (IOException e) {
return false;
}
}
public void checkRead(String filename) {
if((filename.equals(File.separatorChar + "home" +
File.separatorChar + "monicap" +
File.separatorChar + "text2.txt"))){
if(!accessOK()){
super.checkRead(filename);
throw new SecurityException("Sem Chance!");
} else {
FilePermission perm = new FilePermission(
File.separatorChar + "home" +
Segurança
9
JEDITM
}
}
}
File.separatorChar + "monicap" +
File.separatorChar + "text2.txt", "read");
checkPermission(perm);
}
public void checkWrite(String filename) {
if((filename.equals(File.separatorChar + "home" +
File.separatorChar + "monicap" +
File.separatorChar + "text.txt"))){
if(!accessOK()){
super.checkWrite(filename);
throw new SecurityException("Sem chance!");
} else {
FilePermission perm = new FilePermission(
File.separatorChar + "home" +
File.separatorChar + "monicap" +
File.separatorChar + "text.txt" ,
"write");
checkPermission(perm);
}
}
}
Como visto, os métodos checkRead e checkWrite foram sobrescritos. A implementação
customizada para SecurityManager.checkWrite testa o caminho /home/monicap/text.txt. Se o
caminho existir, solicita ao usuário a senha. Se estiver correta, o método checkWrite executa a
checagem de acesso através da criação de uma instância da permissão requerida e passando-a ao
método SecurityManager.checkPermission. Essa checagem terá sucesso se o gerenciador de
segurança encontrar um sistema, usuário ou arquivo de política de segurança com a permissão
especificada.
Ao invés de adicionar a política de segurança no código fonte, pode-se optar por colocá-la em um
arquivo separado, como um arquivo .properties ou .xml. Pode-se, então, solicitar ao gerenciador
de segurança a leitura desse arquivo e estabelecer uma política de segurança dessa forma. Essa
técnica faz com que a implementação das políticas de segurança sejam flexíveis, já que novas
regras adicionadas ao código fonte podem requerer a modificação do código, recompilação e
redistribuição da aplicação a cada vez que fossem modificadas.
Por exemplo, em Enterprise Java Beans (EJB), políticas de segurança podem ser especificadas no
elemento security-permission do descritor weblogic-ejb-jar.xml. O exemplo seguinte concede
acesso de leitura e escrita a um diretório temporário no filesystem do servidor para os EJB:
<weblogic-enterprise-bean>
<!-- instruções webLogic enterprise bean vão aqui -->
</weblogic-enterprise-bean>
<security-role-assignment>
<!-- As tarefas opcionais de segurança vão aqui -->
</security-role-assignment>
<security-permission>
<description>
concede permissão de leitura e escrita para a pasta ext no drive d:
</description>
<security-permission-spec>
grant {
permission java.io.FilePermission "d:${/}ext${/}-", "read,write";
}
</security-permission-spec>
<security-permission>
4.1. Instalando os Gerenciadores de Segurança
Uma vez que as regras de segurança foram estabelecidas e a classe gerenciadora de segurança
que irá impor as regras feitas, deste modo, pode-se agora instalar o gerenciador de segurança em
Segurança
10
JEDITM
uma aplicação. Isso é feito usando-se o método estático setSecurityManager da classe System,
como apresentado a seguir.
try {
System.setSecurityManager(new SecurityManagerImpl());
} catch (SecurityException se) {
System.err.println(“Gerenciador de Segurança já está instalado!!!”);
}
Deve-se notar que um gerenciador de segurança pode ser instalado, mas a permissão para
instalar um gerenciador diferente pode ser concedida. Assumindo que uma permissão não seja
concedida, então uma SecurityException será disparada. Depois que o gerenciador de segurança
foi instalado, o código da aplicação não precisa referenciar a este diretamente. Isso porque a JVM
irá fazer todas as referências ao gerenciador depois da instalação, a menos que uma chamada
explícita a um método seja necessária.
Segurança
11
Módulo 7
Segurança
Lição 4
Segurança em Java
Versão 1.0 - Jan/2008
JEDITM
1. Objetivos
Segurança na plataforma Java foi inicialmente concebida para proteger as informações em um
computador de serem acessadas ou modificadas (incluindo alterações que um programa malicioso
poderia introduzir).
Essas ações já foram implementadas no modelo Java de segurança desde a versão 1.0. O serviço
de autenticação foi adicionado em seguida, na versão 1.1, seguido do modelo de criptografia
disponível na versão 2.0 (como uma extensão) e auditoria que pode ser acrescentada por
qualquer programa Java, fornecendo uma auditoria completa de gerenciamento de segurança.
Provavelmente novos serão serão adicionados no futuro.
Ao final desta lição, o estudante será capaz de:
•
Descriminar o que é Segurança
•
Compreender o modelo de Segurança implementado em Java
•
Obter mais dados sobre a Sandbox
Segurança
4
JEDITM
2. O que é Segurança?
Entende-se por segurança como um conjunto de serviços ou aspectos que fornecem maior
confiança ao uso de um sistema, dentre as quais, destacam-se:
●
Proteção contra programas maléficos
Programas não devem ter autorização a executarem processos que podem prejudicar o ambiente
de um usuário, tais como cavalos de Tróia e programas prejudiciais que se replicam, como um
vírus de computador.
●
Bloqueio a programas invasivos
Os programas devem ser impedidos de descobrir informações particulares sobre o computador ou
sobre a rede do computador.
●
Autenticação
A identidade das partes envolvidas no programa, tanto do autor como a do usuário, devem ser
verificadas.
●
Criptografia
Os dados que o programa envia e recebe através da rede, como arquivos ou dados, devem ser
codificados.
●
Auditoria
Operações potencialmente sensíveis sempre devem ser autenticadas.
●
Bem-definida
Especificações de segurança devem ser seguidas.
●
Verificação
Regras de funcionamento devem ser definidas e confirmadas.
●
Bem-comportada
Os programas devem ser impedidos de consumir recursos do sistema em demasia: utilização da
CPU por muito tempo, excesso de memória, entre outros
●
Padrão de segurança
Os programas devem ser compatíveis com algum padrão de segurança. Pode-se possuir algum
tipo de certificação, ou seja, certificar que determinados procedimentos de segurança são
seguidos. Por exemplo, certificação C2 ou B1 do governo dos Estados Unidos.
2.1. Compreendendo como funciona a Segurança em Java
O ponto da condução de segurança é o modelo de distribuição de programas Java. Uma das
maiores forças de Java é a sua capacidade para trazer programas em uma rede ou executar os
programas a partir de outra JVM. Isso é algo a mais que os atuais usuários de computador dentro
de um contexto com uma única JVM no navegador. Embora a idéia por trás de código portátil, que
está começando a se infiltrar em outras aplicações, tais como, as aplicações baseadas na
tecnologia JINI. Conjugado com o aumento da utilização da Internet, Java possui a capacidade de
trazer programas para um usuário conforme sua necessidade. Essa característica de programas já
desenvolvidos serem facilmente distribuídos tem sido a forte razão para a rápida aceitação e
implantação de sistemas Java.
Em última análise, Java é ou não seguro? Este é um julgamento subjetivo e individual no qual os
usuários terão de fazer com base nas suas próprias exigências. Para se ter um sistema livre de
vírus, que possa autenticar corretamente ou encriptar seus dados, é exigido que todas as
operações sejam auditadas. Desta forma, é necessário construir um auditoria nas aplicações e,
em seguida, na plataforma Java.
Uma visão muito pragmática da segurança pode ser então elaborada: a questão não é se um
Segurança
5
JEDITM
sistema que necessita de uma determinada característica é qualificado como "não seguro", de
acordo com a definição de segurança. A questão é saber se Java possui as características que
atendem às suas necessidades.
Segurança
6
JEDITM
3. Implementação Segura em Java
Segurança em Java é fornecida através de algumas partes do núcleo de sua plataforma, além da
biblioteca Java Cryptography Extension (JCE) e Java Secure Sockets Extension (JSSE).
Estas são as facilidades básicas fornecidas pelo núcleo de segurança da plataforma Java:
●
Uma política de segurança configurável que permite evitar que programas Java leiam seus
arquivos, fazendo ligações de rede para outros hospedeiros, acessando a impressora sem a
devida permissão, e assim por diante. Esta política é baseada em Java Access Control, que
por sua vez depende das classes Loaders, Security Manager e proteções da linguagem.
●
A capacidade de gerar Message Digests e obter uma maneira simples (mas não segura) de
determinar se os dados que o seu programa lê foram alterados.
●
A capacidade de gerar assinaturas digitais para detectar se os dados lidos no programa
foram modificados (ou para enviar dados e permitir que o destinatário destes possam
detectar se foram modificados durante o transporte).
●
Um elemento-chave para administração das chaves necessárias para se obter as
assinaturas digitais.
●
Um suporte a infra-estrutura extensível.
JCE alavanca o núcleo da plataforma Java de segurança ao fornecer uma variedade de operações
criptográficas:
●
Criptografia básica e avançada
●
Segurança na chave
●
Segurança no corpo da mensagem
●
Uma alternativa à chave de gerenciamento de sistema
Por último, a JSSE fornece Secure Sockets Layer (SSL), que auxilia na criptografia dos dados.
Para se comunicar com um servidor SSL ou cliente SSL, podemos utilizar estas bibliotecas de
extensão. Se a aplicação escrever no cliente ou no servidor e desejamos aplicar a criptografia
então, podemos utilizar essa extensão ou as facilidades da JCE.
3.1. Sandbox Java
Este modelo de segurança gira em torno da idéia de uma Sandbox (vista em lições anteriores). A
idéia central é que um aplicativo possa ser alojado de forma segura e restrita em um determinado
computador. Pretende proporcionar um ambiente onde este programa possa ser executado
entretanto, controlar sua execução em uma área com certos limites. É possível permitir que este
programa tenha acesso somente a determinados recursos do sistema, em geral, certificar-se de
que o programa estará confinado dentro de uma Sandbox.
A Sandbox é encarregada de proteger um determinado número de recursos. Considere os
recursos típicos de um computador: o usuário da máquina tem acesso, por exemplo, a muitas
coisas, internamente, acesso a memória local (memória RAM) e, externamente, acesso a um
servidor de arquivos ou outras máquinas da rede. Para executar Applets, também devemos ter
acesso ao WEB Services, cujo acesso pode ser feito através da rede particular ou da internet.
Dados trafegam através de todo este modelo, a partir da máquina do usuário, por meio da rede e,
eventualmente, para o disco rígido. Cada um desses recursos deve ser protegido. Essas proteções
da Sandbox formam a base do modelo de segurança do Java, tais como:
●
Um programa que possui acesso a memória e a CPU, bem como ao servidor WEB onde foi
carregado. Isso é muitas vezes é um estado padrão para a Sandbox.
●
Um programa que possui acesso à CPU, a memória, ao servidor WEB e um conjunto de
recursos específicos (tais como, arquivos locais, máquinas locais, entre outros). Uma
palavra de processamento de programa, por exemplo, pode ter acesso aos documentos do
diretório no sistema de arquivo local, mas não em relação a quaisquer outros arquivos
Segurança
7
JEDITM
●
Um programa que possui o acesso a todos os recursos seja qual for a máquina servidora
que se encontra instalado.
Sandbox não é um uniforme padrão para todos os modelos. A expansão de suas fronteiras é
baseada em uma noção de confiança. Em alguns casos, pode-se confiar nos programas Java para
acessar o sistema de arquivos, em outros, pode-se confiar o acesso a apenas parte do sistema de
arquivos, e ainda, em outros casos, não é possível confiar-lhes nenhum acesso ao sistema de
arquivos.
Segurança
8
Módulo 7
Segurança
Lição 5
Classes de Segurança em Java
Versão 1.0 - Jan/2008
JEDITM
1. Objetivos
Cada classe Java possui um conjunto de permissões que definem as atividades que a classe está
autorizada a executar. Quando um programa Java tenta executar uma operação delicada, as
permissões são pesquisadas para todas as classes envolvidas: se cada classe possuir a permissão
para realizar determinada operação desejada, então a autorização é concedida. Caso contrário,
uma exceção é lançada e a operação falha.
Ao final desta lição, o estudante será capaz de:
•
Conhecer as regras e permissões de segurança
•
Entender as classes de regras, de permissão e de acesso
•
Aprender detalhes sobre exceções associadas a segurança
•
Construir uma classe de permissão
Segurança
4
JEDITM
2. Permissões e política de segurança
Classes que compõem o núcleo da API Java possuem permissão para executar qualquer ação.
Todas as outras classes, incluindo aquelas indicadas na variável classpath, devem explicitamente
ter permissão para realizar operações sensíveis. Para a maior parte das operações, as permissões
são listadas em arquivos de política, juntamente com o código fonte a que se aplicam. Usuários
finais e administradores de sistema definem os parâmetros de administração da Sandbox nos
arquivos de política de segurança.
A tecnologia Java possui as seguintes classes relevantes para política de segurança e permissões
da JVM:
●
●
●
●
●
java.security.Policy
java.security.CodeSource
java.security.Permission e algumas de suas sub-classes:
○ java.security.BasicPermission
○ java.io.FilePermission
○ java.net.NetPermission
○ java.net.SocketPermission
○ java.util.PropertyPermission
java.security.PermissionCollection
java.security.Permissions
Segurança
5
JEDITM
3. Classes de Política de Segurança
Segurança em Java possui um recurso para especificar quais permissões devem ser aplicadas a
qual código fonte. Isso é chamado de conjunto de permissões globais da política de segurança.
Esse conceito é atendido pela classe Policy no pacote java.security. Veremos em detalhes esta
classe a seguir.
●
public abstract class Policy
Estabelecer a política de segurança para um programa Java. Incorpora um mapeamento entre os
códigos fontes e as permissões de objetos de tal forma que as classes carregadas de
determinados locais ou assinadas por indivíduos específicos tenham um conjunto de permissões.
A seguir vemos o construtor da classe.
●
public Policy()
Criar uma classe de políticas de segurança. O construtor deve inicializar o objeto de acordo com
as regras internas.
Existem dois outros métodos na classe Policy.
●
public abstract Permissions getPermissions(CodeSource cs)
Criar um objeto que contém um conjunto de permissões que deve ser concedido às classes
geradas a partir de um determinado código fonte, carregadas a partir da URL e assinada por uma
chave no código fonte.
●
public abstract void refresh()
Atualizar a política de segurança do objeto. Por exemplo, se a foi inicializada a partir de um
arquivo, ler novamente o arquivo e instalar um novo objeto de política de segurança baseado na
(presumivelmente alterada) informação do arquivo. Em termos programáticos, escrever uma
classe Policy envolve a Implementação destes métodos. O padrão da classe de política de
segurança é fornecido pela classe PolicyFile disponível no pacote sun.security.provider, a qual
constrói permissões baseada em informações encontradas nos arquivos de política de segurança
adequados.
Segurança
6
JEDITM
4. Classes de Permissão
Um objeto de permissão representa um recurso do sistema, mas não concede o acesso a este.
Objetos de permissão são construídos e atribuídos ao código baseado na política de segurança em
vigor. Quando um objeto de permissão é concedido para qualquer código fonte, então é atribuída
a permissão para acessar qualquer recurso do sistema que será representado por este objeto.
A classe Permission é uma classe abstrata. Então, uma sub-classe é utilizada para representar
acessos específicos. Analisaremos determinados métodos dentro dessa classe para entendermos
como implementar um usuário.
●
public Permission(String name)
Construir um objeto de permissão que representa a permissão desejada.
●
public abstract boolean equals(Object o)
Sub-classes de Permission são obrigadas a implementar seus próprios testes de igualdade. Muitas
vezes isso é feito pela comparação do nome (e ações, se apropriado) da permissão.
●
public abstract int hashCode()
Sub-classes de Permission são obrigadas a implementar seus próprios códigos hash. Para o
controlador de acesso funcionar corretamente, o código hash para um dado objeto de permissão
nunca deve mudar durante a execução da máquina virtual. Além disso, permissões que
comparam a mesma igualdade devem retornar o mesmo código hash.
●
public final String getName()
Retornar o nome que foi usado na construção dessa permissão.
●
public abstract String getActions()
Retornar uma forma canônica das ações que foram usadas para construir essa permissão, se
existirem.
●
public String toString()
A convenção de impressão de uma permissão deve retornar entre parênteses o nome da classe, o
nome da permissão e as ações envolvidas.
●
public abstract boolean implies(Permission p)
Esse é um dos métodos chaves da classe Permission. Responsável por determinar se uma classe
que recebeu uma permissão pode receber outra. Também é responsável pela execução dos
curingas correspondentes. Entretanto, este método não não necessita realmente dos curingas; a
permissão de escrita em um objeto determinado num banco de dados provavelmente implicaria
na permissão de ler o objeto.
●
public PermissionCollection newPermissionCollection()
Retornar uma coleção de permissões apropriada para instâncias seguras deste tipo de permissão.
Este método retorna null por padrão.
●
public void checkGuard(Object o)
Chama o gerente da segurança (definido pelo atributo SecurityManager.checkPermission) para
verificar se a permissão a esta variável foi concedida, gerando um exceção do tipo
SecurityException em caso negativo. Atualmente o parâmetro deste método não é utilizado.
A seguir estão algumas sub-classes de Permission implementadas na API Java:
●
●
●
●
●
java.security.BasicPermission
java.io.FilePermission
java.net.NetPermission
java.net.SocketPermission
java.util.PropertyPermission
Diferentes
Segurança
classes
Permission
podem
ser
agrupadas
usando
a
PermissionCollection
7
ou
JEDITM
Permissions. PermissionCollection representa uma coleção homogênea de objetos do tipo
Permission ou seja, armazena apenas uma sub-classe específica da classe Permission.
Permissions por outro lado, representam uma coleção heterogênea de objetos do tipo Permission.
Segurança
8
JEDITM
5. Controle de acesso
O controle de acesso é feito pelas classes ProtectionDomain, AccessController, SecureClassLoader,
e URLClassLoader.
A classe sun.misc.Launcher é implementada pela Sun Microsystems e provê uma classe de
carregamento de extensões para a plataforma Java (jre/lib/ext) e classes de aplicação, tais como
classes na variável CLASSPATH, especificadas usando a variável java.class.path ou através da
opção -cp no comando de compilação Java.
A classe ProtectionDomain encapsula as características de um domínio que inclui um conjunto de
classes e objetos correspondentes que recebem as mesmas permissões.
Uma proteção de domínio é criada, ou vinculada através da SecureClassLoader se uma proteção
de domínio apropriada já existir, usando o método getProtectionDomain. O objeto Policy é usado
para determinar as permissões associadas à proteção de domínio da classe.
O AccessController decide se o acesso aos recursos do sistema é permitido, com base na política
de segurança em vigor. Se for negado, uma exceção do tipo AccessControllException é lançada.
AccessControllException é uma sub-classe de java.lang.SecurityException.
Chamadas devem ser feitas preferivelmente ao método checkPermission do gerente de segurança
instalado ao invés de diretamente a classe AccessController. Isto deve ser feito para:
●
Permitir a comparação que será omitida se o gerente de segurança não estiver instalado.
●
Possibilitar a possibilidade que um gerente de segurança ao ser instalado execute
verificações adicionais que não são fáceis de serem capturadas com objetos tipo
Permission.
AccessController é uma classe que contém apenas métodos estáticos.
Segurança
9
JEDITM
6. Exceções
Existem dois tipos de exceções associadas ao pacote de segurança: java.lang.SecurityException e
java.security.GeneralSecurityException. A classe java.security.GeneralSecurityException é uma
nova classe de exceção que foi adicionada ao Java 2. Esta é uma subclasse de
java.lang.Exception. Todas as exceções de segurança são sub-classes diretas desses dois tipos,
exceto ProviderException e InvalidParameterException.
A classe SecurityException e suas sub-classes java.security.AccessControlException e
java.security.cert.CertificateException são exceções em tempo de execução que são lançadas
quando ocorre uma violação de segurança (lançada por uma falha em uma chamada de
checkPermission), como na tentativa de acesso a um arquivo quando esta permissão não é
autorizada. Normalmente, desenvolvedores de aplicações não capturam essas exceções.
Em geral, classes de exceção de segurança não são relacionadas a permissões de classes. Esta
exceção é lançada a partir de várias classes de criptografia em que podem ocorrer erros. Por
exemplo, a exceção NoSuchAlgorithmicException indica que um código está solicitando um
algoritmo que não foi instalado na JVM. Esta exceção é mais similar a FileNotFoundException do
que a SecurityException.
Todas as exceções que não foram mencionadas no pacote java.security são sub-classes de
GeneralSecurityException. São as seguintes:
●
●
●
●
●
●
●
●
●
InvalidAlgorithmParameterException
KeyException
KeyManagementException (estende KeyException)
KeyStoreException
InvalidKeyException
DigestException
NoSuchAlgorithmException
SignatureException
UnrecoverableKeyException
Segurança
10
JEDITM
7. Construindo uma classe de permissão
Veremos um exemplo de como é possível construir uma classe de permissão. Implementaremos
um programa para administrar uma folha de pagamento. Desejamos criar permissões para que os
usuários possam ver o histórico de pagamento. Também podemos permitir que o departamento
de RH atualize a faixa de salário dos empregados.
import java.security.*;
import java.util.*;
public class XYZPayrollPermission extends Permission {
protected int mask;
static private int VIEW = 0x01;
static private int UPDATE = 0x02;
public XYZPayrollPermission(String name) {
// Uma permissão deve sempre ter uma ação, então escolheremos um padrão
this(name, "view");
}
public XYZPayrollPermission(String name, String action) {
// Nossa superclasse, contudo, não suporta ações
// deste modo, não será estabelecida
super(name);
parse(action);
}
private void parse(String action) {
// Procura nessa ação uma string com as palavras "view" e "update",
// separadas por espaço ou vírgula
StringTokenizer st = new StringTokenizer(action, ",\t ");
mask = 0;
while (st.hasMoreTokens( )) {
String tok = st.nextToken( );
if (tok.equals("view"))
mask |= VIEW;
else if (tok.equals("update"))
mask |= UPDATE;
else throw new IllegalArgumentException("Unknown action " + tok);
}
}
public boolean implies(Permission permission) {
if (!(permission instanceof XYZPayrollPermission))
return false;
XYZPayrollPermission p = (XYZPayrollPermission) permission;
String name = getName( );
// O nome deve ser o curinga (*), que significa
// todos os nomes possíveis, ou deve corresponder ao nosso nome
if (!name.equals("*") && !name.equals(p.getName( )))
return false;
}
// Do mesmo modo, as ações requisitadas devem corresponder a todas as
// ações construídas
if ((mask & p.mask) != p.mask)
return false;
// Se a ação e o nome correspondem, retornamos true
return true;
public boolean equals(Object o) {
if (!(o instanceof XYZPayrollPermission))
return false;
// Pela igualdade, verificamos o nome e a máscara de ação
Segurança
11
JEDITM
}
// Devemos fornecer um método de definição como este, uma vez que
// a segurança do sistema espera que façamos uma verificação
// profunda da igualdade. É melhor do que confiar na referência do
// objeto de igualdade
XYZPayrollPermission p = (XYZPayrollPermission) o;
return ((p.getName().equals(getName( ))) && (p.mask == mask));
public int hashCode( ) {
// Devemos sempre fornecer um código hash para permissões,
// porque os hashes devem corresponder se as permissões comparadas
// são iguais. O padrão de implementação deste método
// não fornece isso.
return getName().hashCode( ) ^ mask;
}
public String getActions( ) {
// Este método deve retornar a mesma string, não importando como
// a lista de ações foi passada para o construtor.
if (mask == 0)
return "";
else if (mask == VIEW)
return "view";
else if (mask == UPDATE)
return "update";
else if (mask == (VIEW | UPDATE))
return "view, update";
else throw new IllegalArgumentException("Unknown mask");
}
public PermissionCollection newPermissionsCollection( ) {
return new XYZPayrollPermissionCollection( );
}
}
Os atributos de classe são obrigados a manter a informação sobre as ações realizadas. Apesar da
superclasse fazer referência a estas ações, providências não são tomadas para que sejam
armazenadas ou processadas. Essa lógica é fornecida no método parse(). Neste método, adota-se
a convenção de se obter uma String de ação tratada como uma lista de ações que são separadas
por vírgulas ou espaços em branco.
Como solicitado, implementamos os métodos equals() e hashCode(). Consideramos objetos iguais
se seus nomes e suas ações são iguais, e construímos um código hash adequado.
A implementação do método getActions() é típica. Somos obrigados a retornar a mesma String de
ação para um objeto de permissão que foi construído por uma lista de ações de view e update
como para o que foi construído através da lista. Esta exigência é uma das principais razões pelas
quais as ações são armazenadas com uma máscara, pois permite construir essa String de ação no
formato correto.
Por fim, o método implies() é responsável por determinar como o curinga e outras permissões
implícitas serão manipuladas. Se o nome informado ao construtor do nosso objeto for um
asterisco, então igualaremos este a qualquer outro nome. Quando o método implies() for
chamado por este objeto curinga, o nome sempre corresponderá, pois a máscara da ação tem a
lista completa das ações. Então, a máscara de comparação sempre autorizará a máscara que
testamos na comparação. Se o método implies() for chamado por um objeto diferente, retorna
true se os nomes forem iguais a máscara de um objeto é um subconjunto da máscara alvo.
Perceba que também devemos implementar a lógica de tal forma que a permissão para executar
uma atualização que implique na permissão de executar um aspecto simplesmente pela mudança
na lógica de teste da máscara. Não estamos limitados somente ao uso de curingas
correspondentes no método implies().
Segurança
12
Módulo 7
Segurança
Lição 6
JAAS
Versão 1.0 - Jan/2008
JEDITM
1. Objetivos
A tecnologia JAAS – Java Authenthication and Autorization Service (Serviço de Autorização e
Autenticação Java) fornece uma interface para autenticar usuários e conceder autorização
baseada na identidade do usuário ao invés das características da fonte. Com JAAS é possível
restringir um aplicativo baseado na sua localização, no seu autor (quem o assinou) ou no seu
executor.
JAAS disponibiliza um framework através de uma arquitetura plug-in-play. Fornece um conjunto
de classes abstratas e, no momento de sua execução, procura por um provider necessário à
classe. No entanto, esta arquitetura não está acoplada ao framework de segurança. Por isso
iremos nos restringir em consultar as principais classes de JAAS bem como seus detalhes de
funcionamento.
Ao final desta lição, o estudante será capaz de:
•
Trabalhar com a tecnologia JAAS
•
Identificar as políticas de segurança de JAAS e seus arquivos de configuração
•
Conhecer as classes de autenticação e autorização da tecnologia JAAS
•
Programar e administrar através da tecnologia JAAS
Segurança
4
JEDITM
2. Trabalhando com JAAS
Uma aplicação que possui JAAS habilitada trabalha da seguinte forma:
1. O framework solicita ao usuário que efetue login, obtendo assim um objeto login de
usuário.
Esta é uma operação simples para um programador, uma vez que envolve apenas a construção de
um objeto do tipo LoginContext e a invocação de um único método. A complexidade da tarefa
vem após a invocação do método implicando, assim, em um esforço junto ao administrador do
sistema.
O administrador do sistema é responsável pela criação de um arquivo que contém uma ou mais
diretivas que indicam o que acontece quando uma determinada aplicação possui um arquivo de
registro com várias tentativas de login. Essas diretivas estão em forma de módulos de login, que
são invocados para autenticar determinado usuário e uma série de opções que indicam como
essas classes podem ser utilizadas. As próprias classes tipicamente interagem com o sistema
operacional que, por sua vez, pede para autenticar o usuário utilizando os sistemas de
autenticação disponíveis na plataforma.
O administrador do sistema também determina os parâmetros de autenticação. Alternativamente
poderá obrigar a informar uma senha válida, seja uma LDAP (utilizada em sistema Solaris), NT
(utilizada em sistema Windows) ou outra, utilizada por uma base de dados customizada. O uso de
uma ou mais senhas é opcional. O administrador pode trabalhar com poucos ou com muitos
módulos de login, conforme se fizer necessário.
2. O programa chama o método doAs() ou doAsPrivileged() recebendo do objeto login o
código que pode ser executado com a permissão desse usuário.
Este passo é idêntico à chamada ao método doPrivileged() na lista do controlador de acesso.
3. Dentro do contexto criado, apenas o programa executa o código que requer uma
permissão específica (ex.: mostrar os arquivos em um diretório).
Assim como em todas as solicitações, esse código resulta em uma chamada ao gerenciador de
segurança (conhecido como Controlador de Acesso) para verificar se o objeto possui a permissão
apropriada. Normalmente são concedidas as devidas permissões a todas as classes conforme o
arquivo de politica de segurança padrão (ou qualquer que seja a classe de política de segurança
que esteja em vigor).
2.1. Arquitetura JAAS
A arquitetura JAAS é dividida em dois componentes principais: o componente de autenticação e o
componente de autorização.
O componente de autenticação fornece segurança e confiabilidade para determinar quem está
executando o código Java, independentemente se o código está sendo executado através de uma
aplicação, uma Applet, um JavaBean ou uma Servlet.
O componente de autorização fornece recursos para restringir a ação de uma classe Java quanto a
execução de tarefas sensíveis baseadas na fonte na qual originou esta execução (ou seja, de onde
veio esta classe) e como foi autenticada.
2.2. As classes comuns de JAAS
Aqui estão as principais classes e interfaces do JAAS que podem ser usadas, estendidas ou
implementadas para tratar autorização e autenticação.
●
●
●
●
javax.security.auth.AuthPermission
javax.security.auth.Policy
javax.security.auth.Subject
javax.security.auth.PrivateCredentialPermission
Segurança
5
JEDITM
●
●
●
●
●
●
●
●
●
●
●
●
javax.security.auth.login.Configuration
javax.security.auth.login.LoginContext
javax.security.auth.spi.LoginModule (Interface)
javax.security.auth.callback.Callback (Interface)
javax.security.auth.callback.CallbackHandler (Interface)
javax.security.auth.callback.ChoiceCallback
javax.security.auth.callback.ConfirmationCallback
javax.security.auth.callback.LanguageCallback
javax.security.auth.callback.NameCallback
javax.security.auth.callback.PasswordCallback
javax.security.auth.callback.TextInputCallback
javax.security.auth.callback.TextOutputCallback
Segurança
6
JEDITM
3. Políticas de segurança JAAS e arquivos de
configuração
Os seguintes arquivos são utilizados pela JAAS para modificar as autorizações e autenticações
conforme o comportamento do sistema:
1. Arquivo para configuração de autenticação
Este é um arquivo de configuração, indicando o objeto do tipo LoginModule que será utilizado pela
aplicação de forma adaptável e com uma estrutura em formato de stack. Esse arquivo pode ser
modificado para informar uma LoginModule diferente e que possa ser utilizado por uma aplicação
sem que seja necessário modificá-la.
Os módulos de Login são adaptáveis pois podem ser carregados dinamicamente. Ao invés de
chamar um módulo específico de Login inserido no código, o contexto de Login procura o arquivo
de configuração, para verificar quais classes devem ser chamadas. Isto permite a utilização de
módulos de Login de terceiros.
Os módulos de Login estão em uma estrutura em formato de stack pois é possível especificar
mais de um módulo no arquivo de configuração. Estes módulos são postos em uma stack dentro
do arquivo de configuração. São chamados em uma determinada seqüência e cada um pode
adicionar um ou mais objetos principais para determinado assunto (por exemplo, conhecer o
usuário atual). Assim, trata-se do assunto dos objetos que inserem com múltiplos objetos
principais, que podem vir de um único módulo ou podem vir de vários módulos de Login.
A sintaxe do arquivo de configuração está representeada abaixo:
ApplicationName {
LoginModule flag ModuleOptions;
<mais entradas no módulo LoginModule>
};
Há quatro valores para a flag:
●
required – Este módulo será sempre chamado e o usuário deve passar neste teste de
autenticação
●
sufficient – Se o usuário passar no teste de autenticação deste módulo, nenhum outro
módulo (com exceção do required) será executado e o usuário estará suficientemente
autenticado
●
requisite – Se o usuário passar no teste de autenticação deste módulo, outros módulos
poderão ser executados, porém (exceto o required) poderão falhar
●
optional – É permitido ao usuário falhar na autenticação deste módulo. No entanto se
todos os módulos forem optional, o usuário deverá passar pelo menos no teste de
autenticação
Os módulos optional são usualmente definidos localmente dentro de um módulo LoginModule,
podendo, assim, enviar o valor destas opções ao módulo de Login através do arquivo de
configuração.
A seguir, vemos um arquivo de configuração de Login:
Application A {
com.sun.security.auth.module.SolarisLoginModule required;
com.sun.security.auth.module.JndiLoginModule optional;
};
Application B {
com.sun.security.auth.module.NTLoginModule required;
};
2. Arquivo de configuração de políticas de segurança
Um arquivo de política de segurança JAAS é muito similar a um arquivo de politicas de segurança
Segurança
7
JEDITM
padrão. A sintaxe é quase a mesma e os tipos de permissões são exatamente iguais. A única
diferença é a possibilidade de especificar o tipo e o nome principal de cada entrada. Isto significa
que a entrada informada especifica os codebases adicionais, além dos principais, e o código das
assinaturas que são utilizados no arquivo de configuração de politicas de segurança.
As entradas neste arquivo aplicam-se a todo código executado pelo método doAs() analisado
anteriormente; mapas deste arquivo principal são associados ao contexto chamando o método
doAs() específico para as permissões.
Cada entrada de concessão inclui uma ou mais “permissões de entrada” e precedida pelas opções
codeBase ou signedBy contendo pares de nome e valor que especificam que este código pode
conceder a permissão. Temos a seguir, o formato básico de uma entrada de concessão:
grant signedBy "signer_names", codeBase "URL" {
permission permission_class_name "target_name", "action",
signedBy "signer_names";
....
permission permission_class_name "target_name", "action",
signedBy "signer_names";
};
O signedBy e codeBase são pares opcionais contendo nome e valor, e a ordem entre estes campos
não é relevante. Um valor signedBy indica o apelido para um certificado fornecido na database de
chaves. Múltiplos apelidos podem ser separados por vírgulas. Se for omitido isso significa
nenhuma assinatura. Não importa qual dos dois códigos é assinado ou não ou por quem.
Um valor de codeBase indica o local do código fonte; permissões serão concedidas com base
nesse local. Um codeBase vazio significa ausência de local; não importa onde o código se origina.
Veja uma amostra uma entrada de concessão do JAAS:
grant
codebase "file:/files/sdo/jaas/actions/"
signedBy "jra"
Principal com.sun.security.auth.SolarisPrincipal "sdo" {
permission java.io.FilePermission "${/}files", "read";
};
Segurança
8
JEDITM
4. JAAS – Classes de Autenticação
Vamos discutir em maiores detalhes a classe e a interface necessárias para permitir que JAAS
execute autenticação.
4.1. Classe LoginContext
javax.security.auth.login.LoginContext
Esta classe é utilizada para descrever os métodos básicos usados para autenticação e permite
desenvolver um aplicativo independente da tecnologia de autenticação envolvida. LoginContext
consulta um arquivo de configuração para determinar quais LoginModule devem ser usados. Estes
são os métodos vitais para esta classe:
public
public
public
public
LoginContext(String
LoginContext(String
LoginContext(String
LoginContext(String
name)
name, CallbackHandler cb)
name, Subject s)
name, Subject s, CallbackHandler cb)
Estabelecer um contexto pelo qual um usuário pode ser autenticado. As ações que esta
autenticação irá executar são carregadas através de um objeto de configuração. O argumento
name é a identificação utilizada por esse conjunto de ações dentro do objeto de configuração.
public void login()
Executar a autenticação e realiza as ações listadas na configuração. Este método lança
LoginException se o login falhar.
public void logout()
Informar que o usuário está saindo do sistema, isso invalida o objeto subject. Este método lança
LoginException se a operação de logout falhar.
public Subject getSubject()
Retorna o objeto do tipo Subject que representa o usuário.
4.2. Interface LoginModule
javax.security.auth.spi.LoginModule
Esta interface é implementada por serviços de autenticação. Objetos LoginModule dão suporte às
aplicações para fornecer um determinado tipo de autenticação. Estes são os métodos necessários
para implementar a interface.
public void initialize(Subject s, CallbackHandler ch, Map sharedState, Map
options)
Inicializar o LoginModule. O objeto do tipo Subject representa o usuário que será autenticado; o
módulo de login irá armazenar um ou mais usuários, e talvez utilizará o objeto do tipo
CallbackHandler para obter informações de autenticação diretamente com o usuário. O primeiro
objeto do tipo Map serve para compartilhar informações. O segundo objeto tipo Map contém as
opções carregadas do arquivo de configuração.
public boolean login()
Autenticar o usuário. Informações sobre o usuário podem ser obtidas no ambiente ou utilizando o
CallbackHandler. Este método retorna true caso o usuário obtenha sucesso na autenticação ou
false se a autenticação deve ser ignorada. Se, o usuário não puder ser autenticado este método
deve lançar um LoginException.
public boolean commit()
Confirmar o processo de autenticação. Este método é chamado somente se o usuário for
Segurança
9
JEDITM
autenticado para todos os LoginModule do arquivo de configuração. Neste ponto, o LoginModule
deve vincular os objetos Principal apropriados ao usuário. Se, por algum motivo, os LoginModule
desse usuário devam ser ignorados, este método deve retornar false. Um LoginException é
lançado em caso de problema com a confirmação da autenticação.
public boolean abort()
Interromper o processo de autenticação. Este método é chamado quando o usuário não pode ser
autenticado, isto é, um módulo exigido falhou ou não conseguiu obter um módulo adicional. O
módulo deve limpar qualquer estado armazenado. Pode lançar um LoginException ao encontrar
um erro.
public boolean logout()
Realizar a saída do usuário. Implica na limpeza de qualquer estado e remoção dos objetos do tipo
Subject e qualquer assunto salvos.
4.3. Classe Subject
javax.security.auth.Subject
Esta classe é usada para representar um usuário autenticado. Na sua essência, cada usuário é
representado como um array de objetos Principal guardados por esta classe. Existe um array de
objetos, pois cada usuário terá, muito provavelmente, várias características de identificação.
4.4. Interface Callback
javax.security.auth.callback.CallBack
Implementações da interface CallBack são passadas a um CallbackHandler, permitindo que
serviços de segurança possam interagir com uma chamada de aplicação para recuperar dados de
específicos de autenticação específica ou para exibir algumas informações.
4.5. Implementações da interface Callback
JAAS traz algumas implementações da interface Callback.
javax.security.auth.callback.ChoiceCallback
Usada para exibir uma lista de opções e recuperar as opções selecionadas.
javax.security.auth.callback.ConfirmationCallback
Usada para pedir confirmações do tipo sim, não e cancelar.
javax.security.auth.callback.LanguageCallback
Usada para recuperar o local utilizado para formatação de texto.
javax.security.auth.callback.NameCallback
Utilizada para obter a informação do nome.
javax.security.auth.callback.PasswordCallback
Usada para obter informações de senha.
javax.security.auth.callback.TextInputCallback
Usada para obter informações de texto.
javax.security.auth.callback.TextOutputCallback
Usada para mostrar mensagens informativas, de avisos e erros.
javax.security.auth.callback.CallbackHandler
Segurança
10
JEDITM
Ao se construir um LoginContext, tem-se a opção de fornecer um objeto que implementa esta
interface. Este objeto é enviado para os módulos de Login e, caso necessitar de uma informação
do usuário, usam o objeto handler para obtê-la. A implementação de uma CallbackHandler exige
um objeto que forneça este método:
public void handle (Callback [] cb)
Recebe um array de objetos tipo Callback e procura a informação desejada em cada um deles. A
aplicação tem liberdade para usar qualquer método para obter a informação adequada. Se a
aplicação não sabe como lidar com um determinado objeto do tipo Callback, deve lançar uma
UnsupportedCallbackException; outros erros podem ser encapsulados como uma IOException.
Segurança
11
JEDITM
5. JAAS – Classes de Autorização
Estas são as classes de autorização utilizadas pelo JAAS.
5.1. Classe Policy
java.security.Policy
A implementação do arquivo de políticas de segurança do JAAS é fornecido por esta classe. É
semelhante a construção da classe javax.security.auth.Policy, mas está ligada ao núcleo de
políticas de segurança
O classe Policy possui quatro métodos principais:
public static Policy getPolicy()
Retornar o objeto da classe Policy instalado. Deve ter a AuthPermission denominada getPolicy
para chamar este método. O retorno deste método é um objeto que contém as políticas de
segurança do JAAS em vigor.
public static void setPolicy(Policy policy)
Determinar as políticas de segurança do JAAS. Deve ter a AuthPermission denominada setPolicy
para poder chamar este método.
public abstract PermissionCollection getPermissions(Subject subject,
CodeSource cs)
Obter as permissões que devem ser concedidas. São carregadas a partir do código fonte quando
executado pelo objeto tipo Subject informado.
public abstract void refresh()
Atualizar as políticas de segurança em vigor.
5.2. Classe AuthPermission
javax.security.auth.AuthPermission
Esta classe estende a classe BasicPermission e pode ser usada no arquivo de políticas de
segurança geral da tecnologia Java para especificar os tipos de AuthPermission que podem ser
concedidos. O construtor para esta classe é:
public AuthPermission(String name)
A classe concede permissão com base no parâmetro especificado no construtor.
5.3. Classe PrivateCredentialPermission
javax.security.auth.PrivateCredentialPermission
Esta classe é usada para proteger o acesso às credenciais (objetos do tipo Credential) particulares
pertencentes a um determinado Subject. Esta classe estende da classe java.security.Permission.
public PrivateCredentialPermission (String name, String actions)
Construtor da classe. O argumento name indica a classe Credential e o conjunto de Principals. O
único valor válido para o parâmetro actions é "read" (leitura).
Segurança
12
JEDITM
6. JAAS – Programação e Administração
Agora que os conceitos básicos já foram passados, iremos configurar uma aplicação com
autenticação e autorização manipuladas pelo JAAS:
6.1. Particionar o código de configuração e o código de ação e, então,
compilá-los
Há três passos importantes na codificação de uma aplicação com JAAS ativado:
1. Construir um objeto LoginContext
2. Utilizar este objeto para registrar um usuário
3. Passar este usuário como um dos argumentos para o método doAs().
Veja a seguir, um exemplo com autenticação (e autorização) JAAS ativada:
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
public class CountFiles {
static LoginContext lc = null;
}
static class NullCallbackHandler implements CallbackHandler {
public void handle (Callback [] cb) {
throw new IllegalArgumentException ("Não implementado ainda");
}
}
public static void main (String [] args) {
// Usa o LoginModules configurado para a entrada "CountFiles"
try{
lc = new LoginContext("CountFiles", new NullCallbackHandler());
} catch (LoginException le) {
le.printStackTrace ();
System.exit(-1);
}
// Autentica o usuário
try{
lc.login ();
// Se não for retornada uma exceção, a autenticação foi realizada
} catch (Exception e) {
System.out.println ("Login falhou:" + e);
System.exit(-1);
}
// Agora executa o código como o usuário autenticado
Object o = Subject.doAs (lc.getSubject(), new CountFilesAction());
System.out.println ("Usuário " + lc.getSubject () +
" encontrou " + o + " arquivos.");
System.exit (0);
}
No exemplo acima, cria-se um objeto de LoginContext usado para estabelecer um contexto pelo
qual um usuário pode ser autenticado. O usuário não é autenticado ao se criar o LoginContext,
isto é feito através do método login() em nosso exemplo. Considerando que a autenticação foi
bem sucedida, a classe Subject é usada para representar um usuário autenticado. A chamada ao
método doAs() irá chamar o método run() do objeto dado, a partir de um determinado subject
(CountFilesAction).
A classe CountFilesAction foi definida como:
import java.io.*;
import java.security.*;
Segurança
13
JEDITM
class CountFilesAction implements PrivilegedAction {
public Object run () {
File f = new File (File.separatorChar + "files");
File fArray [] = f.listFiles ();
return new Integer (fArray.length);
}
}
6.2. Criar o arquivo de configuração da autenticação
Após realizarmos a programação, iremos proceder a parte da administração. Definiremos o
arquivo de configuração da autenticação do nosso exemplo de programa de JAAS ativado:
CountFiles {
com.sun.security.auth.module.SolarisLoginModule required;
com.sun.security.auth.module.JndiLoginModule optional;
};
A entrada CountFiles no primeiro exemplo é comparada com o nome que é passado para o
construtor de contexto do Login. Quando uma entrada é encontrada, cada uma das classes
listadas são chamadas na ordem.
6.3. Criar o arquivo de política de autorização
Esta é a forma como estabelecemos o arquivo de política de autorização para nosso exemplo:
grant
);
Principal com.sun.security.auth.SolarisPrincipal "sdo"
Principal com.sun.security.auth.SolarisNumericGroupPrincipal "45" (
permission java.io.FilePermission "${/} files","read";
Esta entrada permitirá que um usuário leia em /files apenas se o nome de login for "sdo" e se o
usuário é um participante do grupo "45".
6.4. Executar o programa
Lembre-se de incluir os seguintes argumentos:
●
-Djava.security.manager – para ativar a verificação de acessos
●
-Djava.security.policy – para indicar o arquivo de políticas de segurança padrão
●
-Djava.security.auth.policy – para indicar o arquivo de políticas de segurança do JAAS
●
-Djava.security.auth.login.config – para indicar o arquivo de configuração do Login
Segurança
14
Módulo 7
Segurança
Lição 7
Criptografia
Versão 1.0 - Jan/2008
JEDITM
1. Objetivos
Criptografia é o estudo da transformação de mensagens claras para torná-las secretas. Nos
tempos modernos, tem se tornado um ramo da teoria da informação como um estudo matemático
da informação e especialmente sua transmissão de um lugar para outro. É uma parte central de
diversos campos da segurança da informação e assuntos relacionados, particularmente,
autenticação e controle de acesso. Um dos objetivos primários da criptografia é esconder o
significado das mensagens, mas, geralmente, sem esconder sua existência. Criptografia também
contribui para a ciência da computação, particularmente em técnicas usadas em computadores e
redes de segurança para garantir funcionalidades, tais como, o controle de acesso a informação
de forma confidencial.
Dois conceitos importantes que devem ser analisados são criptografar e decriptar. Criptografar
é, essencialmente, o processo pelo qual certas informações dentro de arquivos ou programas são
alteradas por algoritmos matemáticos. Uma mensagem criptografada é chamada de ciphertext.
Decriptar, por outro lado, é o processo de voltar um ciphertext a mensagem original. Criptografar
e decriptar são feitas através do uso de uma seqüência de caracteres conhecidos como chaves.
Ao final desta lição, o estudante será capaz de:
•
Debater sobre os tipos de algoritmos criptográficos
•
Conhecer as classes da arquitetura de criptografia Java
Segurança
4
JEDITM
2. Algoritmos Criptográficos
Esses são os três principais tipos de algoritmos baseados na chave em uso: algoritmos simétricos,
algoritmos assimétricos e algoritmos híbridos.
Algoritmos simétricos são aqueles em que a chave para criptografar e a chave para decriptar
pode ser calculadas isoladamente. Em muitos casos, a chave para criptografar e decriptar são as
mesmas. Algoritmos simétricos requerem que o remetente e o receptor concordem primeiramente
qual será a chave, antes que a mensagem seja enviada. Um dos algoritmos simétricos mais
usados é o Data Encryption Standard ou DES. O padrão DES usa a mesma chave para criptografar
e decriptar baseada no uso do operador "ou exclusivo" (XOR) e em operações de troca de bits. A
maior vantagem desse algoritmo é a sua velocidade e popularidade. Sua maior limitação é que
tem um tamanho de chave relativamente pequeno (56 bits) e a segurança da comunicação
prioriza o requisito de chaves compartilhadas.
O padrão DES foi criado em 1976, e previa-se o seu uso até 1981. Foi descontinuado em 2002,
sendo substituído pelo AES – Advanced Encryption Standard. Há diversas classes Java que
empregam esse algoritmos, a grande vantagem deste novo padrão é o tamanho de sua chave
(256 bits).
Algoritmos assimétricos (também chamados de algoritmos de chave pública) diferem dos
simétricos no fato de que a chave para criptografar é diferente da chave para decriptar. A chave
para criptografar não é secreta e pode ser pública desde que não seja usada para decifrar a
mensagem. A chave para decriptar é usada para decodificar a mensagem, e deve pertencer ao
receptor. A chave para criptografar é chamada de chave pública e a chave para decriptar é
chamada de chave particular. A seguir, temos uma lista dos algoritmos criptográficos assimétricos
mais comuns:
●
RSA
RSA (Rivest, Shamir and Adleman), a sigla refere-se aos autores do algoritmo. É um algoritmo
popular usado tanto para criptografar quanto para assinaturas digitais. O algoritmo é baseado na
dificuldade em fatorar, ou seja, encontrar os "Fatores Primos" de um determinado número.
●
DSA
DSA (Digital Signature Algorithm), ou seja, Algoritmo de Assinatura Digital. É usado apenas para
assinaturas digitais.
●
Diffie-Hellman
Inventado em 1976 por Whitfield Diffie e Martin Hellman, esse algoritmo é usado primariamente
na distribuição de chaves.
●
RC2 e RC4
RC (Rivest Cipher) foi desenvolvido pelo conhecido criptógrafo Ron Rivest. O RC2 é uma chave
variável block cipher, com tamanho de 64 bits, enquanto o RC4 é uma chave variável Stream
Cipher.
Algoritmo híbrido mescla a capacidade das duas classes anteriores. Essencialmente, essa classe
de algoritmo usa pares de chaves pública e particular (assimétricas) para autenticar e concordar
com uma chave de sessão. A criptografia é então feita usando um algoritmo simétrico com aquela
chave de sessão.
Segurança
5
JEDITM
3. Arquitetura de Criptografia Java
Java Cryptography Architecture (Arquitetura de Criptografia Java), ou JCA, refere-se ao framework
para acessar e desenvolver a funcionalidade criptográfica para a plataforma Java. Inclui
componentes da biblioteca Java Security relacionados à criptografia, assim como um conjunto de
convenções e especificações. A JCA foi criada com foco na independência de implementação e de
algoritmo e interoperabilidade.
3.1. Classes Provider
java.security.Provider
É uma classe abstrata para implementação de algoritmos criptográficos específicos. Refere-se ao
provedor de segurança que será implementado pela aplicação. Estas implementações podem
incluir: implementações nos algoritmos da assinatura digital, algoritmos de Message Digest,
algoritmos de criptografia e esquemas de padding. Os seguintes métodos estão incluídos nesta
classe:
public String getName()
Retornar o nome do provedor de segurança.
public double getVersion()
Retornar a versão do número do provedor de segurança.
public String getInfo()
Retornar as informações do provedor de segurança.
public String toString()
Retornar uma String específica do provedor de segurança, contendo seu nome e versão.
Ao criarmos classes para realizar operações de segurança, devemos estender a classe Provider e
registrar essa classe na infra-estrutura de segurança. Apesar da classe Provider ser abstrata,
nenhum de seus métodos são abstratos. Isso significa que para implementar uma classe real, de
uma certa forma, tudo o que será necessário é estender a classe Provider e fornecer um
construtor apropriado. A subclasse deve implementar um construtor, já que não existe nenhum
construtor padrão na classe Provider. Esse construtor deve ser fornecido conforme a seguinte
assinatura:
protected Provider(String name, double version, String info)
Construir um provedor de segurança com um determinado nome, versão e informações.
A seguir temos um exemplo de implementação de uma classe Provider.
import java.security.*;
public class SomeProvider extends Provider {
public SomeProvider() {
super("Someone", 1.0, "Someone Security Provider v1.0");
}
}
Para adicionar qualquer funcionalidade ao provedor de segurança, colocaremos algumas
associações, tais como:
import java.security.*;
public class SomeProvider extends Provider {
public SomeProvider( ) {
super("Someone", 1.0, "Someone Security Provider v1.0");
put("KeyGenerator.XOR", "xxx.yyy.SomeXORKeyGenerator");
Segurança
6
JEDITM
}
}
put("KeyPairGenerator.XYZ","xxx.yyy.SomeKeyPairGenerator");
put("KeyFactory.XYZ", "xxx.yyy.SomeKeyFactory");
put("MessageDigest.XYZ", "xxx.yyy.SomeMessageDigest");
put("Signature.XYZwithSHA", "xxx.yyy.SomeSignature");
put("Cipher.XOR", "xxx.yyy.SomeXORCipher");
put("KeyManagerFactory.XYZ", "xxx.yyy.SomeSSLKeyManagerFactory");
É necessário que o provedor de segurança mapeie o nome do engine e do algoritmo com o nome
da classe que implemente essa determinada operação.
3.2. Classe Security
Essa classe é usada para administrar os provedores de segurança e centralizar as propriedades e
métodos de segurança.
java.security.Security
Essa classe é definida como final, contém métodos estáticos e o construtor private, portanto,
nunca poderá ser estendida ou gerar objetos. Estes são os métodos desta classe:
public static int addProvider(Provider provider)
Adicionar um novo provedor de segurança à lista de provedores. O provedor é adicionado ao final
de uma coleção interna de provedores.
public static int insertProviderAt(Provider provider, int position)
Inserir um novo provedor de segurança para a lista interno de provedores. O provedor é
adicionado em uma posição especifica, outros provedores tem seu índice modificado se necessário
para criar espaço para esse provedor.
public static int removeProvider(String name)
Remover um determinado provedor de segurança através do seu nome passado como argumento.
public static Provider[] getProviders()
Retornar uma cópia da lista de provedores de segurança que são controlados pela classe. Observe
que essa é apenas uma cópia da lista, retirar ou reordenar seus elementos não causa nenhum
efeito na classe Security.
public static Provider getProvider(String name)
Retornar um provedor de segurança através de um determinado nome passado como argumento.
Se o nome do provedor não estiver na lista contida na classe Security, esse método retorna null.
public static String getProperty(String key)
Obter a propriedade da classe Security com uma determinada chave. Essas propriedades contidas
na classe Security foram lidas do arquivo java.security. O arquivo java.security tem várias outras
propriedades que também podem ser recuperadas por esse método.
public static void setProperty(String property, String value)
Estabelecer uma dada propriedade para um determinado valor passado como argumento do
método.
public static String getAlgorithmProperty(String algName, String propName)
Procurar em todos os provedores de segurança por uma determinada propriedade na forma
Alg.propName.algName e retorna com o primeiro correspondente encontrado.
Como um exemplo da utilização da classe Security, veremos uma classe que permite visualizar
uma lista com todos os provedores de segurança em uma JVM em particular:
Segurança
7
JEDITM
import java.security.*;
import java.util.*;
public class ExamineSecurity {
public static void main(String args[]) {
try {
Provider p[] = Security.getProviders( );
for (int i = 0; i < p.length; i++) {
System.out.println(p[i]);
for (Enumeration e = p[i].keys(); e.hasMoreElements( );)
System.out.println("\t" + e.nextElement( ));
}
} catch (Exception e) {
System.out.println(e);
}
}
}
3.3. Classes Engine
As classes de Engine (também conhecidas como Service Provider Interfaces ou SPI) são
desenvolvidas para que os usuários possam empregar a terça parte dos Security Providers.
Fornecem funcionalidade para calcular uma message digest de um dado especificado, assinar,
verificar as assinaturas digitais e gerar pares de chaves (pública e particular). As classes Engine
incluem as seguintes classes:
java.security.MessageDigest
Implementar operações para criar e verificar um message digest.
java.security.Signature
Fornecer um Engine para criar e verificar assinaturas digitais.
java.security.KeyPairGenerator
Gerar e fornecer informações sobre os pares de chaves.
Segurança
8
Módulo 7
Segurança
Lição 8
Class Loaders
Versão 1.0 - Jan/2008
JEDITM
1. Objetivos
Class Loader é o mecanismo pelo qual arquivos que contenham bytecodes Java são lidos na JVM e
convertidos em definições de classes. Em qualquer situação, um ou mais Class Loaders estão
prontamente disponíveis, pois o Class Loader do sistema (ou o Class Loader primordial ou o Null
Class Loader) existe por padrão. Classes de sistema (são aquelas classes que residem no núcleo
da API Java) são carregadas pelo Class Loader de sistema, ou, possivelmente, em alguma
instância ou subclasse de Class Loader.
Ao final desta lição, o estudante será capaz de:
•
Ter uma visão geral de funcionamento do Class Loader
•
Compreender as considerações de segurança sobre o Class Loader
•
Entender as classes do Class Loader
Segurança
4
JEDITM
2. Visão Geral do Class Loader
O conceito de carregadores de classes também é um componente importante para determinar a
política de segurança de um programa Java. Basicamente, existem três domínios em que o
carregador de classes opera em um modelo de segurança:
1) O carregador de classes colabora com a máquina virtual para definir os namespaces para
proteger a integridade dos recursos de segurança em Java.
2) Chama o gerenciador de segurança para garantir que a devida permissão foi dada a um
código acessando e/ou definindo classes.
3) Cria o mapeamento de permissões para os objetos de classes permitindo ao controlador de
acesso se manter-se a par das classes e suas respectivas permissões.
Como desenvolvedor, o terceiro domínio seria o mais importante. Isso ocorre porque a criação de
um carregador de classes personalizado e a instituição de permissões de classes dentro deste
carregador de classes seria mais conveniente na implementação de diferentes políticas de
segurança dentro do seu aplicativo.
Segurança
5
JEDITM
3. Arquitetura de Carregamento de Classes em Java
Antes de criar e usar Class Loaders personalizados, vamos analisar alguns de seus mecanismos
fundamentais. Para compreender a mecânica dos Class Loaders devemos primeiro visualizar sua
arquitetura.
Class Loaders são organizados como uma árvore hierárquica. Na raiz da árvore está o Class
Loader de sistema que, como foi dito anteriormente, carrega o núcleo da API Java. O Class Loader
de sistema, em seguida, tem pelo menos um filho. Tem pelo menos um filho, o URL Class Loader
que é usado para carregar classes a partir do caminho da classe principal. O Class Loader de
sistema pode ter qualquer outro filho direto, mas normalmente quaisquer outros descendentes
seriam filhos de URL Class Loader.
Figura 1: Hierarquia do Class Loader
Essa hierarquia seria a chave para compreender o que acontece quando uma classe é carregada.
Uma classe pode ser carregada através de uma das seguintes maneiras:
1) Chamando, explicitamente, o método loadClass() de um carregador de classes
2) Chamando o método Class.forName()
3) Carregando implicitamente uma classe quando é referenciada por uma classe já carregada
Na primeira forma, o Class Loader é o objeto cujo o método loadClass() é chamado. Na segunda
forma, ou o Class Loader de sistema ou outro filho deste que carregou a classe através do método
estático Class.forName() será passado para o método (Class.forName()). Na terceira forma, o
Class Loader que carregou a classe de origem será usada para carregar a classe referenciada.
Class Loaders são responsáveis por solicitar que as instâncias superiores carreguem uma classe.
No entanto, se essa operação falhar, o Class Loader tentará definir esta classe. O efeito é que as
classes de sistema serão sempre carregadas pelo Class Loader de sistema, classes no caminho da
classe serão sempre carregadas pelo Class Loader que conhece o caminho da classe e, em geral,
uma classe será carregada pela classe mais antiga na sua hierarquia que sabe onde encontrar
outras classes.
Segurança
6
JEDITM
4. Class Loaders e Namespaces
Uma das funções dos Class Loaders é cumprir determinadas regras relativas aos nomes usados
pelas classes Java. Lembrando que o nome completamente qualificado de uma classe Java é a
união do nome do pacote com o da classe. Por exemplo, não existe uma classe chamada String na
API Java, existe a classe java.lang.String. Por outro lado, uma classe não deve, obrigatoriamente,
fazer parte de um pacote, caso no qual o nome da classe representa o seu nome completo. É
comum dizer que essas classes estão no pacote padrão, mas isso é um pouco enganador: existe
um pacote padrão diferente para cada Class Loader em uso pela JVM.
Considere o seguinte exemplo.
Figura 2: Acessos pelo Class Loader
Quando visitamos uma determinada página (www.site1.com), e carregamos uma applet que
utiliza uma classe chamada SomeClass (sem o nome do pacote). Depois seguimos para outra
página (www.site2.com) e carregamos outra aplicação que utiliza outra classe chamada
SomeClass (também sem o nome do pacote), sabemos que estas são duas classes diferentes,
contudo possuem o mesmo nome totalmente qualificado. Como a JVM conseguirá distinguir estas
duas classes?
Quando uma classe é carregada por um Class Loader é armazenada em uma referência interna a
este Class Loader. Quando a máquina virtual precisa acessar uma determinada classe, a JVM pede
esta ao Class Loader apropriado. Note que o Class Loader usado para carregar uma classe
também faz parte do nome da mesma classe. Portanto, se dois Class Loaders carregam classes
que têm idênticos nomes de pacote e de classe, estas ainda serão tratadas como classes
diferentes.
Segurança
7
JEDITM
5. Resource Loading e Naming
Além de carregar classes, os Class Loaders também são capazes de carregar recursos. Esses
recursos, que podem ser qualquer seqüência de bytes, estão associados a um pacote. O
carregamento de um recurso é feito invocando-se o método getResource() de um carregador de
classes, ou getSystemResource(), pelo Class Loader primordial. Por exemplo, é possível carregar
um arquivo de imagem .gif quando um programa é executado. Este caso pode ser tratado
colocando o arquivo .gif no mesmo diretório da classe:
ClassLoader cl = this.getClass().getClassLoader();
URL url = null;
if (cl != null) {
url = cl.getResource("someImage.gif");
} else {
url = ClassLoader.getSystemResource("someImage.gif");
}
Outro método que pode ser usado para carregar recursos é o getResourceAsStream(), ou o
método getSystemResourceAsStream(). Este método acessa o recurso como um stream. Por
exemplo:
ClassLoader cl = this.getClass().getClassLoader();
InputStream in = null;
if (cl != null) {
in = cl.getResourceAsStream("someImage.gif");
} else {
in = ClassLoader.getSystemResourceAsStream("someImage.gif");
}
Também pode-se usar o método findResource() para encontrar um recurso especificado pelo
nome. Além disso, pode-se usar os métodos findResources() e getResources() que retornam
uma enumeração de todas as URLs que representam recursos através do nome.
No momento da nomeação de um recurso, os métodos getResource() devem refletir à nomeação
do pacote. Por exemplo, se um arquivo SomeResource.txt está dentro do pacote innerPackage
que, por sua vez, está dentro de outra pasta somePackage, então o recurso é referenciado como
somePackage.innerPackage.SomeResource.txt.
Segurança
8
JEDITM
6. Class Loader e Delegação
Que classes são carregadas pelos diferentes Class Loaders? Certas classes são carregadas por
determinados Class Loaders, como a seguir:
●
Classes de sistema – classes do núcleo da API Java – são carregadas pelo Class Loaders
primordial.
●
Extensões padrão – geralmente encontrados em jre diretório/lib/ext – são carregadas pela
ExtClassLoader.
●
Classes de aplicação – encontradas no caminho da classe, ou conforme especificado pela
propriedade java.class.path ou a opção -cp – são carregadas pela AppClassLoader.
Para conferir estes Class Loaders, é possível escrever um aplicativo para invocar o método
getClassLoader. Como resultado o Class Loaders primordial retorna null, o ExtClassLoader retorna
sun.misc.Launcher$ExtClassLoader
e
o
AppClassLoader
retorna
sun.misc.Launcher
$AppClassLoader.
Segurança
9
JEDITM
7. Considerações de Segurança sobre o Class Loader
Os aspectos de segurança da implementação de um Class Loader se dividem em duas grandes
categorias: as características essenciais comuns a todos os Class Loaders, e funcionalidades
adicionais que sejam adequadas para qualquer Class Loader.
Existem quatro aspectos que um Class Loader deve observar:
1) Verificar a validade do nome do pacote ou da classe
2) Verificar a cache antes de carregar uma classe
3) Verificar o caminho local (ou de sistema) da classe antes de tentar carregar uma classe
remota
4) Executar o verificador
Além dos quatro aspectos mencionados, quaisquer restrições adicionais podem ser acrescentadas
em um Class Loader para servir a qualquer propósito em particular. Isso pode incluir
especificações de servidor ou restrições sobre quais servidores o Class Loader poderá carregar.
Dois recursos são comumente implementados: restrição de certas hierarquias de pacote e
manipulação de assinaturas.
7.1. Restrição de hierarquias de pacote
Implementar este recurso implica em controlar o acesso a determinadas propriedades. Isso
envolve conhecer sobre o domínio RuntimePermission, além de controlar o acesso às propriedades
como user.home e os.name. O domínio RuntimePermission controla o acesso a pacotes
especificados por meio da passagem dos argumentos accessClassInPackage.package_name e
defineClassInPackage.package_name para o método. O primeiro argumento permite o acesso ao
pacote especificado em "package_name" através do método loadClass() do Class Loader. O
segundo permite a definição das classes no pacote especificado através do método defineClass()
do Class Loader.
7.2. Manipular assinaturas digitais
A implementar deste recurso envolve a atribuição a uma classe de confiança baseada em
assinatura. Isto pode ser feito através do método setSigners do Class Loader, que deve ser usado
para definir uma classe como assinada. Este método é definido como protected e final e, como tal,
não pode ser modificado. Recebe dois argumentos: um objeto Class a ser registrado como
assinado e um array de Object, que geralmente são subclasses da java.security.cert.Certificate.
Segurança
10
JEDITM
8. Classes do Class Loader
A classe base que define um Class Loader é a classe abstrata java.lang.ClassLoader:
public abstract class ClassLoader
Esta classe transforma uma série de bytecodes em uma definição de classe. Não define a forma
como os bytecodes serão obtidos, mas prevê todas as outras funcionalidades necessárias para
criar a definição da classe.
A classe preferida para ser usada como base para um carregador de classes é a classe
java.security.SecureClassLoader, definida como:
public class SecureClassLoader extends ClassLoader
Esta classe transforma uma série de bytecodes em uma definição de classe. Esta classe
acrescenta funcionalidade seguras à classe ClassLoader, mas não define como os bytecodes serão
obtidos. Deve-se criar uma subclasse desta.
Há uma terceira classe nesta categoria, a classe java.net.URL Class Loader, definida como:
public class URL Class Loader extends SecureClassLoader
Esta classe carrega classes de maneira segura pela obtenção dos bytecodes em um conjunto de
URLs. Se as classes forem carregadas através do sistema de arquivos ou a partir de um servidor
HTTP, a classe URL Class Loader oferece uma definição completa de um Class Loader. Além disso,
é possível substituir alguns de seus métodos ou modificar a política de segurança de classes que
define.
8.1. Implementando Class Loaders
Pela implementação de um Class Loader, podemos estender as permissões que são concedidas
através de arquivos de política de segurança, bem como utilizar certos recursos de segurança
opcionais do Class Loader. Antes de criar um exemplo para implementar um Class Loader,
veremos primeiro certos métodos essenciais na sua utilização e implementação:
public Class loadClass(String name)
Carregar uma classe identificada pelo argumento passado. Uma ClassNotFoundException pode ser
lançada se a classe não for encontrada.
protected Class findClass(String name)
Carregar uma classe especificada pelo argumento passado. O nome será completamente
qualificado do pacote da classe (por exemplo, java.lang.String).
protected final Class defineClass(
String name,
byte[] b,
int off,
int len) throws ClassFormatError
protected final Class defineClass(
String name,
byte[] b,
int off,
int len,
ProtectionDomain protectionDomain) throws ClassFormatError
protected final Class defineClass(
String name, byte[] b,
int off,
int len,
CodeSource cs) throws ClassFormatError
Criar uma classe com base em bytecodes do array informado. A proteção do domínio associado à
classe muda com base no método que está sendo utilizado.
Segurança
11
JEDITM
O método a seguir está disponível dentro da classe SecureClassLoader e suas subclasses,
incluindo a classe URL Class Loader.
protected PermissionCollection getPermissions(CodeSource cs)
Devolver as permissões que devem ser associadas a um determinado código fonte. A
implementação padrão deste método chama o método getPermissions() da classe Policy.
Como exemplo, iremos realizar um exemplo quanto ao uso da classe URL Class Loader.
Etapa 1: Verificar se o programa pode acessar a classe em questão. Este é um passo opcional.
Para tanto, chamamos o método checkPackageAccess(). Se for preciso modificar outro
comportamento do URL Class Loader, então não se pode usar o método newInstance(). Nesse
caso, para utilizar o método checkPackageAccess(), deve anular o método loadClass() do seguinte
modo:
public final synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// Verificar a permissão para acessar o pacote.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
int i = name.lastIndexOf('.');
if (i != −1) {
sm.checkPackageAccess(name.substring(0, i));
}
}
return super.loadClass(name, resolve);
}
Etapa 2: Se houver uma classe previamente definida, o método loadClass() de Class Loader
realiza esta operação. Se o Class Loader já carregou esta classe, então, encontra um objeto da
classe previamente definido e retorna este.
Esta é a razão pela qual o método super.loadClass() é chamado no método override.
Etapa 3: Se não houver uma classe previamente definida, o Class Loader consulta a classe que
este estendeu, para determinar como carregar a classe. Isto é feito através de uma operação
recursiva. Deste modo, o Class Loader de sistema será chamado antes para carregar a classe.
No nosso exemplo, o carregamento da classe é feito de forma diferente para a superclasse. O
método loadClass() da classe ClassLoader realiza esta operação.
Etapa 4: Consultar o Security Manager para saber se o programa está autorizado a criar a classe
em questão. Este também é um passo opcional.
No exemplo mostrado, chamamos o método checkPackageDefinition(). Para tanto, deve-se fazer
um override do método findClass():
protected Class findClass(final String name) throws ClassNotFoundException {
// Primeiro verifica se tem permissão para acessar o pacote
SecurityManager sm = System.getSecurityManager( );
if (sm != null) {
int i = name.lastIndexOf('.');
if (i != −1) {
sm.checkPackageDefinition(name.substring(0, i));
}
}
return super.findClass(name);
}
Etapa 5: Ler o arquivo da classe através de um array de bytes. Isso ocorre no método
findClass().
O URL Class Loader realiza esta operação através da consulta dos URLs que foram passados para
o seu construtor. Se for preciso ajustar a forma de leitura dos bytes da classe, deve-se usar a
classe SecureClassLoader.
Segurança
12
JEDITM
Etapa 6: Criar o domínio de proteção adequado para a classe.
O URL Class Loader irá criar um código fonte para cada classe com base na URL a partir da qual a
classe foi carregada e sua assinatura (se houver). As permissões associadas a essa classe serão
obtidas através do método getPermissions() da classe Policy, que por padrão irá retornar as
permissões lidas nos arquivos de política de segurança ativos. Além disso, o URL Class Loader irá
adicionar outras permissões para esse conjunto.
Se a URL tem um protocolo de arquivo, este deve especificar uma autorização de arquivo que
permita que todos os arquivos que descendem do caminho URL sejam lidos. Por exemplo, se a
URL é file://xyz/classes/ então, um arquivo com um nome de permissão /xyz/classes/ e uma
lista de ação de leitura serão adicionados ao conjunto de permissões. Se a URL é um arquivo jar
(file://xyz/MyApp.jar), o nome do arquivo de permissão será a própria URL.
Se a URL possui um protocolo HTTP, então será adicionada uma permissão socket para se
conectar ou aceitar uma conexão da máquina. Por exemplo, se a URL é http://someurl/classes/
então uma permissão socket com o nome someurl:1 e uma lista de ação de conectar e aceitar
serão adicionadas.
Caso desejamos associar diferentes permissões a uma classe, então devemos realizar um
override com o método getPermissions(). Por exemplo, para que a regra anterior seja aplicada e
fosse permitido que a classe finalizasse a máquina virtual, devemos usar o seguinte método:
protected PermissionCollection getPermissions(CodeSource codesource)
PermissionCollection pc = super.getPermissions(codesource);
pc.add(new RuntimePermission("exitVM"));
}
{
Podemos mudar completamente as permissões associadas à classe, substituindo completamente a
classe Policy, construindo uma nova coleção de permissões neste método, ao invés de chamar
super.getPermissions(). O URL Class Loader irá utilizar qualquer permissão retornada através do
método getPermissions() para definir o domínio de proteção que será associado à classe.
Etapa 7: Definir a classe, verificá-la e resolvê-la.
Estes passos são tratados internamente pelo URL Class Loader.
Segurança
13
Módulo 7
Segurança
Lição 9
Message Digest
Versão 1.0 - Jan/2008
JEDITM
1. Objetivos
Uma message digest, também conhecida como 'message fingerprint' ou 'hash seguro' é uma
String de tamanho fixo que é o resultado da passagem dos dados por um algoritmo chamado
hash – também conhecido por espalhamento, sentido único ou de mão única. A message digest é
produzida através de uma função hash. A função é chamada de sentido único, uma vez que é
impossível que a mensagem original seja extraída da message digest.
A função hash ideal é aquela que nunca produz uma mesma message digest para as possíveis
Strings de entrada. Porém, essa 'perfeição teórica' de uma mensagem de entrada (sem tamanho
fixo) é impossível com o tamanho curto e fixo para a message digest de saída. Para isso seria
necessário que a message digest tivesse o mesmo tamanho da mensagem de entrada. Isto
significa que podemos ter uma String qualquer de entrada e produzir uma message digest curta
de tamanho fixo, mas esta mensagem não necessariamente corresponde SOMENTE à mensagem
que o gerou. Se uma determinada message digest tenha for gerada a partir de duas mensagens
de entrada diferente, teremos o que é chamado de colisão. (para maiores informações acesse
http://www.cits.rub.de/MD5Collisions/)
Uma message digest é uma assinatura digital compacta de um fluxo de dados qualquer. Uma boa
implementação de uma função hash deve produzir uma grande quantidade de mensagens
diferentes, mesmo para uma pequena mudança na string de entrada, como por exemplo a
alteração de um caractere da String de entrada de minúsculo para maiúsculo.
Ao final desta lição, o estudante será capaz de:
•
Conhecer os principais algoritmos de Message Digest
•
Identificar os principais usos da utilização de Message Digest
•
Empregar a classe MessageDigest em um aplicativo
Segurança
4
JEDITM
2. Algoritmos de Message Digest
Os algoritmos hash mais comuns são o MD5 e o SHA. São suportados pelos provedores de
segurança padrões.
MD5 (Message Digest 5)
O algoritmo de message digest mais utilizado atualmente é o algoritmo MD5 de 128 bits,
desenvolvido por Ron Rivest do MIT Laboratory for Computer Science and RSA Data Security. Os
algoritmos hash MD5 de 128 bits (16 bytes) são normalmente representados por uma seqüência
de 32 dígitos hexadecimais. O MD2 e o MD4 foram algoritmos anteriores da família RSA. Ambos
são atualmente considerados obsoletos. Nenhum deles ainda foi quebrado, mas ambos mostramse potencialmente fracos graças ao tamanho da chave. Há estudos de implementação para uma
versão que utilize chave com um tamanho maior – provavelmente, será denominado de MD6.
SHA (Secure Hash Algorithm)
Os algoritmos SHA foram projetados pela Agência Nacional de Segurança (National Security
Agency - NSA) e foram publicados como padrão do governo americano. O SHA-1 foi considerado
como o sucessor do MD5. Para mais informações sobre o SHA, por favor visite o endereço http://
www.itl.nist.gov/fipspubs/fip180-1.htm (Federal Information Processing Standards – Publication
Secure Hash Standard).
Nome
Algoritmo
SHA-1
Produz uma mensagem com 20 bytes (40 dígitos Hex); aplicável a documentos com menos
de 264 bits.
SHA-256
Produz uma mensagem com 32 bytes (64 dígitos Hex); aplicável a documentos com menos
de 264 bits.
SHA-384
Produz uma mensagem de 48 bytes (96 dígitos Hex); aplicável a documentos com menos
de 2128 bits.
SHA-512
Produz uma mensagem de 64 bytes (128 dígitos Hex); aplicável a documentos com menos
de 2128 bits.
Vejamos por exemplo a geração da message digest para a seguinte frase:
Java Security **cks!
MD5: cf93f2442e8e183d865d7fa9c251aa41
SHA-1: 331cc7479ab3571c96bf1c6ad30738ca0507d386
Ou seja, quando passamos a mensagem para o método hash produzimos uma message digest de
tamanho fixo, dependendo do algoritmo utilizado.
Figura 1: Comparação MD5 e SHA-1
Substituindo os asteriscos, encontramos a seguinte frase:
Java Security rocks!
MD5: 6da66136e4d1d0af49d633edb7b53c12
SHA-1: f56f6f92804dfb8935b2185fc520044bba4c8b2a
Como podemos ver, duas simples alterações de caracteres, de '**' para 'ro', produziram grandes
Segurança
5
JEDITM
diferenças nas message digest.
Isto serve como evidência para indicar que a mudança em alguns caracteres altera a message
digest completamente. Na mensagem seguinte o 'r' na palavra 'rock' foi alterada para maiúscula.
Isto produziu uma grande diferença nas message digest do MD5 e do SHA-1.
Java Security Rocks!
MD5: 79f131c274a82294177fbad8b977a707
SHA-1: f6e26ec898b69e563dd2bf2245ed12fb64780e30
Para comparar, lado a lado, as message digest geradas, veja a tabela abaixo.
Mensagem
MD5
SHA-1
Java Security **cks!
cf93f2442e8e183d865d7fa9c251aa41
331cc7479ab3571c96bf1c6ad30738ca0507d386
Java Security rocks!
6da66136e4d1d0af49d633edb7b53c12
f56f6f92804dfb8935b2185fc520044bba4c8b2a
Java Security Rocks!
79f131c274a82294177fbad8b977a707
f6e26ec898b69e563dd2bf2245ed12fb64780e30
Segurança
6
JEDITM
3. Aplicações comuns
Hashing como sabemos é utilizado em muitas aplicações. Isto inclui aumento de desempenho,
autenticação e verificação de dados.
Aumento de desempenho
A tabela hash utiliza um método hash que indexa a chave dentro da posição correta na tabela
hash. Veja a lição 10 do Módulo 3 – Estruturas de Dados para mais informações sobre
Tabelas Hash e técnicas de hashing.
Autenticação
Senhas podem ser guardadas através do uso de métodos hash. No linux, senhas de usuários são
armazenadas em um arquivo chamado /etc/password, se o shadow não estiver habilitado. Para
guardar senhas reais do usuário, elas são criptografadas através das chaves hash.
root:x:0:1:Super-User:/:/sbin/sh
ozzie:6k/7KCFRPNVXg:508:10:& Ozzie:/usr2/ozzie:/bin/csh
A senha do Ozzie acima é '6k/7KCFRPNVXg'. Na prática existe a adição de um bit aleatório,
chamado salt, adicionado à senha antes que ela seja criptografada.
Assinatura digital de documentos
'Assinar' digitalmente um documento é similar a assinar ou autografar um papel. Esta assinatura
digital comprova que você concorda com a mensagem ou dado. O processo de assinatura de um
documento envolve uma assinatura pública e particular e a chave hash da mensagem. A
assinatura digital é gerada a partir da criptografia da chave hash da mensagem com a chave da
assinatura privada.
Figura 2: Assinatura Digital de Documentos - Criptografia
Para provar que uma assinatura pertence a um documento, a assinatura digital é utilizada para
decriptar a chave pública do signatário. Isto resulta na função hash do documento. Se a chave
hash decriptada combinar com a chave hash da mensagem original, então o proprietário da chave
é o signatário do documento.
Figura 3: Assinatura Digital de Documentos - Decriptar
Segurança
7
JEDITM
Cobriremos em detalhes este assunto no próximo capítulo, Assinaturas Digitais e Certificados.
Verificação de Dados
Message digests são freqüentemente utilizadas para verificar quais dados não foram alterados
desde que a assinatura foi publicada. Alterações de dados podem ocorrer durante a transmissão
dos dados, por exemplo, dano acidental (transmissão realizada durante uma tempestade) ou por
acesso indevido (ataque por um programa que possa danificar esta transmissão)
Resumos MD5 ou checksums (verificação de somatório) MD5 têm sido amplamente utilizados para
fornecer segurança para que arquivos particulares não sejam alterados durante a transferência.
Isto pode ocorrer devido a programas maliciosos ou simplesmente por corrompimento durante o
seu envio ou recebimento. O publicador do arquivo ou classe disponibiliza a soma MD5 em seu
site. Depois de ter baixado o arquivo deve-se recalcular o checksum MD5 para verificar a
integridade do arquivo. Compara-se então o checksum MD5 publicado com o checksum MD5
calculado. Se os checksum MD5 calculados não coincidirem então a transmissão terá falhado ou o
arquivo foi alterado.
Um exemplo poderia ser o download do OpenOffice. Uma vez baixado o software OpenOffice,
podemos verificar a lista de checksum MD5 para as diferentes plataformas no endereço
http://download.openoffice.org/1.1.5/md5sums.html. Para o download do OpenOffice Solaris x86,
a checagem da soma (checksum) é a exibida abaixo.
91cbb9d5bda04c9c93d1475b6b806928 Ooo_1.1.5_Solarisx86_install.tar.gz
Calcula-se então o checksum de verificação do arquivo baixado. Sistemas baseados no Linux têm
uma ferramenta para cálculo do checksum MD5 de um arquivo. Sistemas baseados no Windows
podem baixar ferramentas para o cálculo do checksum MD5 de terceiros.
O MD5 publicado e o checksum MD5 calculado geram um mesmo resultado MD5.
Segurança
8
JEDITM
4. A classe MessageDigest
Em Java, message digests são armazenadas em arrays de bytes que são calculados utilizando a
classe java.security.MessageDigest.
java.lang.Object
java.security.MessageDigestSpi
java.security.MessageDigest
Construtor
protected
MessageDigest(String algorithm)
Cria uma message digest com o algoritmo especificado.
Métodos
Object
clone()
Retorna um clone se a implementação for clonable.
byte[]
digest()
Completa o cálculo hash através da execução de operações finais como padding.
byte[]
digest(byte[] input)
Executa uma atualização final sobre o resumo utilizando um vetor de bytes que
completa o cálculo.
int
digest(byte[] buf, int offset, int len)
Completa o cálculo hash através da execução de operações finais como padding.
String
getAlgorithm()
Retorna uma String que identifica o algoritmo, independente dos detalhes da
implementação.
int
getDigestLength()
Retorna o comprimento da mensagem em bytes ou 0 se esta operação não for
suportada pelo provedor de segurança e a implementação não for clonable.
static MessageDigest
getInstance(String algorithm)
Gera uma instância MessageDigest que implementa o algoritmo de resumo (digest)
especificado.
static MessageDigest
getInstance(String algorithm, Provider provider)
Gera uma instância MessageDigest que implementa um algoritmo específico, como
fornecido pelo provedor de segurança, se possuir um.
static MessageDigest
getInstance(String algorithm, String provider)
Gera uma instância MessageDigest implementando um algoritmo específico, como
fornecido pelo provedor de segurança, se possuir um.
Provider
static boolean
getProvider()
Retorna o provedor de segurança desta instância da message digest.
isEqual(byte[] digesta, byte[] digestb)
Compara duas message digest em relação ao seu conteúdo.
void
reset()
Reinicia uma mensagem para uso futuro.
String
toString()
Retorna uma String de representação da mensagem de uma instância message digest.
void
update(byte input)
void
update(byte[] input)
Atualiza uma message digest utilizando o byte especificado.
Atualiza message digest utilizando um array de bytes especificado.
void
update(byte[] input, int offset, int len)
Atualiza um message digest utilizando um array de bytes especificado, a partir da
posição offset especificada. O número de bytes utilizados, iniciando do offset, é especificado
Segurança
9
JEDITM
pelo argumento len.
A MessageDigest é uma classe abstrata que fornece um meio de implementar classes de
algoritmos de message digest. Estes algoritmos são fornecidos por vários provedores de pacotes
de segurança. A versão da Sun de seu JRE vem com um provedor de segurança padrão: o
provedor “SUN”. Outros ambientes de execução Java não necessariamente fornecem o provedor
“SUN”. O pacote do provedor “SUN” inclui as seguintes implementações dos algoritmos para a
classe MessageDigest:
•
•
•
•
•
•
Algoritmo
Tamanho da saída hash
MD2
MD5
SHA-1
SHA-256
SHA-384
SHA-512
128
128
160
256
384
512
bits
bits
bits
bits
bits
bits
Quando qualquer um dos algoritmos listados acima for requisitado, o provedor “SUN” é utilizado
se nenhum provedor de serviço for especificado ou se o provedor “SUN” for explicitamente
indicado. Isto significa que o provedor “SUN” tem maior prioridade entre os provedores de
segurança. Esta preferência é configurável. (Para uma revisão dos pacotes de provedores de
segurança, por favor veja a classe Provider).
Métodos importantes da classe MessageDigest:
getInstance() - O método getInstance() é um método estático polimórfico por overload que
retorna uma instância da message digest de um determinado algoritmo e provedor de segurança
(caso existam estas especifidades). Há três assinaturas para o getInstance:
•
getInstance(String algorithm) – Esta assinatura aceita uma String que especifica o
algoritmo hash a ser utilizado. Caso o provedor não seja especificado a implementação
padrão será utilizada
•
getInstance(String algorithm, Provider provider) – Esta assinatura aceita uma String que
especifica o algoritmo hash e também aceita uma instância Provider que especifica qual a
implementação a ser utilizada
•
getInstance(String algorithm, String provider) – Esta assinatura aceita uma String que
especifica o algoritmo hash e também aceita uma String que especifica qual provedor, que,
por sua vez, indica que implementação deve ser utilizada
update() - Este método adiciona dados à mensagem de entrada para o processamento
•
update(byte input) – Atualiza o message digest utilizando o byte especificado
•
update(byte[] input) – Atualiza o message digest utilizando um array de bytes
especificado
•
update(byte[] input, int offset, int len) – Atualiza o message digest utilizando um array de
bytes especificado a partir de uma posição offset indicada. O número de bytes utilizados,
iniciando do offset, é especificado pelo argumento len.
reset() – Este método redefine a message digest ao seu estado inicial para uso futuro. Utilize
este método para executar outro cálculo sem precisar instanciar o MessageDigest.
digest() – Retorna os dados da mensagem a partir dos dados de entrada da instância message
digest corrente. A message digest é reiniciada após cada chamada. Teoricamente, esta redefinição
da message digest é de responsabilidade do provedor de segurança, e não é possível garantir que
a message digest sempre será redefinida.
•
byte[] digest() - Completa o cálculo do hash pela execução de uma operação final como o
padding e então retorna a message digest em um array de bytes.
•
byte[] digest(byte[] input) – Executa a atualização final sobre a message digest utilizando
um array de bytes específico, e então conclui o cálculo da message digest. Ou seja,
Segurança
10
JEDITM
primeiro chama o método update(input) e o método digest().
•
int digest(byte[] buf, int offset, int len) – Conclui o cálculo do hash pela execução de
operações como o padding.
Segurança
11
JEDITM
5. Como implementar uma message digest
Os passos a seguir ilustram como criar uma message digest.
1. Obter uma instância da classe MessageDigest para o algoritmo apropriado
O JDK fornece implementações de SHA-1 e MD5. Se o pacote do fornecedor padrão fornece uma
implementação do algoritmo com uma message digest específico, uma instância da
MessageDigest contendo essa implementação é retornada. Se o algoritmo não estiver disponível
no pacote padrão, é procurado em outros pacotes. Se for especificado um algoritmo que nenhum
fornecedor implemente, NoSuchAlgorithmException é lançada. O algoritmo SHA-1 resulta em uma
message digest de 20 bytes, enquanto que o MD5 possui um de tamanho de 16 bytes.
MessageDigest md5 = MessageDigest.getInstance("MD5");
ou
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
2. Obter a mensagem como um array de bytes
A mensagem pode vir de um InputStream carregado, de um InputStream da rede, de um arquivo,
ou de uma simples String. Para o nosso propósito, vamos pegar a mensagem de um arquivo.
BufferedInputStream message = new BufferedInputStream(
new FileInputStream(filename));
// Pegar a mensagem de um input stream através de uma linha de comando
BufferedInputStream message = new BufferedInputStream(
new InputStreamReader(System.in));
// Criar um ByteArrayOutputStream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Ler o arquivo por caracteres individuais
int ch;
while ((ch = bis.read()) != -1) {
baos.write(ch);
}
// Obter um array de byte do ByteArrayOutputStream
byte[] buffer = baos.toByteArray();
3. Adicionar a mensagem ao carregador da message digest ao chamar o método update
Lembre-se que ao chamar este método adiciona-se o método carregado do input ao final da
message digest. Então, se o update já houver sido chamado na message digest, e desejamos
limpar o carregador, chamamos o método reset() para limpar o carregador da message digest.
/* chame md5.reset() para reiniciar o carregador se você já houver utilizado
essa instância previamente */
md5.update(message);
4. Gerar o sumário
Alguns algoritmos requerem que um padding seja adicionado a message digest. O método
digest() cria automaticamente o padding requerido pelo algoritmo para que não seja necessário
se preocupar com isso. O método digest() retorna um array de bytes que contém o checksum ou
a message digest.
byte[] digest = md5.digest();
5. Converter para String
Para converter o código hash em um objeto do tipo String, lembre-se de utilizar
Integer.toHexString(), este método não insere zeros à esquerda, então deve-se adicioná-los por
conta própria.
StringBuffer md5Hex = new StringBuffer();
for (int i=0; i<digest.length; i++) {
Segurança
12
JEDITM
}
// Inserir 0xFF para adicionar um zero condutor para cada elemento do array
md5Hex.append(Integer.toHexString(0xFF & digest[i]));
6. Finalizar
Imprimir a message digest, salve-o em um arquivo e insira-o no banco de dados.
System.out.println(md5Hex.toString());
A message digest pode estar qualquer um dos 2 status, 'initialized'(iniciado) ou 'in progress'(em
progresso). Uma vez obtida uma instância do MessageDigest, esse objeto inicia como 'initialized'.
Os dados são processados pela message digest, utilizando qualquer um dos métodos update().
Uma vez chamado este método, o status da message digest muda para 'in progress'. O método
reset() pode se chamado a qualquer momento para reiniciar a message digest, revertendo seu
status para 'initialized'.
Uma vez que todos os dados foram processados, um dos métodos digest() deve ser chamado para
completar o processo da junção. O método digest() pode ser chamado apenas uma vez para um
dado número de alterações. Depois que o método digest() foi chamado, o objeto MessageDigest é
reiniciado para seu status initialized.
Figura 4: Modelo de criação de uma Message Digest
Exemplo:
O programa a seguir processa a união de assinaturas do MD5 de um dado arquivo. Para utilizar o
programa, utilize o nome do arquivo como um argumento. Esse arquivo será lido e o sumário da
mensagem MD5 correspondente será exibido.
/* MD5.java */
package jedi.security.messagedigest.MD5;
import
import
import
import
import
import
import
java.io.BufferedInputStream;
java.io.ByteArrayOutputStream;
java.io.FileInputStream;
java.io.FileNotFoundException;
java.io.IOException;
java.security.MessageDigest;
java.security.NoSuchAlgorithmException;
public class MD5 {
public static void main(String[] args) {
if (args.length < 1){
System.out.println("Usage: MD5 <file>");
return;
}
MD5 md = new MD5();
System.out.println("MD5: " + md.getHashSignature(args[0]));
}
Segurança
13
JEDITM
}
public String getHashSignature(String filename){
StringBuffer md5Hex = new StringBuffer();
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
BufferedInputStream message = new BufferedInputStream(
new FileInputStream(filename));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int ch;
while ((ch = message.read()) != -1) {
baos.write(ch);
}
byte[] buffer = baos.toByteArray();
message.close();
baos.close();
md5.update(buffer);
byte[] digest = md5.digest();
for (int i=0;i<digest.length;i++) {
md5Hex.append(Integer.toHexString(0xFF & digest[i]));
}
} catch (NoSuchAlgorithmException ex) {
System.out.println(
"Sorry, there's no such algorithm on the default provider");
} catch (FileNotFoundException ex) {
System.out.println("Sorry, file " + filename + " was not found");
} catch (IOException ioe){
System.out.println("Sorry, I/O Exception occured.");
} finally {
return md5Hex.toString();
}
}
Segurança
14
JEDITM
6. Exercícios
6.1. Aplicações do Mundo Real
1. Vamos supor uma tabela no banco de dados de usuários que podem acessar seu sistema.
O sistema foi implementado de maneira que as senhas dos usuários são armazenadas
como assinaturas de hash SHA-1. Recebemos uma solicitação de mudança para
implementar uma funcionalidade de modificação da senha. Nos requisitos de negócio
constam:
a) Solicitar a senha atual e a nova senha
b) Se a senha atual corresponde à senha do banco de dados, proceder à mudança da
senha atual para a nova senha
2. Recentemente foi feito o download de um arquivo da internet. Não existe a certeza se o
arquivo foi corrompido ou modificado durante a transmissão. O hash MD5 do arquivo é
fornecido no site. Desconhecendo a existência alguns geradores MD5 de código livre,
devemos criar nossa própria aplicação Java para calcular o checksum MD5.
Segurança
15
Módulo 7
Segurança
Lição 10
Listas de Controle de Acesso
Versão 1.0 - Jan/2008
JEDITM
1. Objetivos
Uma Lista de Controle de Acesso (ACL) é uma estrutura de dados que armazena os recursos
acessados. Uma ACL pode ser analisada de como uma estrutura de dados com múltiplos acessos.
Cada acesso ACL é um conjunto de permissões associadas a um solicitante ou grupo de
solicitantes. Além disso, cada ACL contém um sinal (uma flag), que indica se as permissões
devem ou não ser concedidas ou negadas.
ACL promove uma maneira fácil de criar usuários e grupos, cada um combinado a um conjunto
específico de permissões que controlarão como cada usuário ou grupo irá atuar na aplicação Java.
As Listas de Controle de Acesso respondem à pergunta: “Como autorizar um usuário a acessar
dados de outros usuários ou outros dados, que não são permitidos?”.
Ao final desta lição, o estudante será capaz de:
•
Conhecer as características da ACL
•
Obter maiores informações sobre Java e as Listas de Controle de Acesso
Segurança
4
JEDITM
2. Características das ACL
Uma lista de controle de acesso independe do tipo de autenticação utilizado para verificar a
validade do controlador. A ACL independe do tipo de criptografia utilizado para transmitir os dados
através da rede. A ACL é consultada após a fase de autenticação. A própria ACL é independente
das pesquisas que armazena.
Depois que o solicitante é autenticado para ser um usuário do sistema, pode acessar seus
recursos. Para cada recurso, o solicitante pode ou não ter assegurado seu acesso, dependendo
das permissões que são concedidas na ACL que guarda estes recursos.
A ACL pode ser consultada para encontrar a lista das permissões que um solicitante possui ou
verificar se há ou não permissões especiais para este solicitante.
2.1. Modelo de Controle de Acesso
O modelo a seguir é extraído de Lampson et al.: Authentication in Distributed Systems: Theory
and Practice, ACM ToCS, 1992
Solicitante
Executando
Pesquisa
Solicitação
Referência
Monitorada
Objeto
Registro
Recurso
Figura 1: Modelo de ACL
Solicitante
Fonte dos pedidos. Um solicitante pode representar uma entidade, um usuário, um grupo ou um
processo. Pode ser qualquer coisa que possua identificação e requer acesso a um recurso.
Solicitação
Realiza as operações com os objetos. Podendo, inclusive, ler, escrever ou executar.
Referência monitorada
Guarda cada objeto e avalia cada pedido para os objetos, decidindo se deve ou não conceder
acesso.
Objeto
Recursos tais como: arquivos, dispositivos ou processos.
A referência monitorada baseia sua decisão na apresentação do pedido principal, na operação de
solicitação, bem como nas regras de acesso que controlam quais solicitantes poderão realizar essa
operação sobre o objeto.
Para executar seu trabalho, o monitor necessita de um meio confiável para reconhecer tanto a
origem do pedido, quanto a regra de acesso. A obtenção do código fonte do pedido é conhecido
por "autenticação", interpretar a regra de acesso é chamado "autorização".
Assim, a autenticação responde à pergunta ''Quem disse isso?'', e a autorização responde à
pergunta ''Quem é confiável para acessar isso?''. Normalmente, a regra de acesso é anexada ao
objeto; tal regra é chamada lista de controle de acesso ou ACL. Para cada operação a ACL
especifica um conjunto de indivíduos autorizados e o monitor concede um pedido se o indivíduo
for tão confiável quanto o solicitante que está autorizado a fazer a operação no pedido.
2.2. Cálculo de Permissões Concedidas
Cada acesso na ACL é especificado como positivo ou negativo. Um acesso positivo significa que
seus recursos devem estar disponíveis quando um usuário ou processo tentar acessá-los. Uma
Segurança
5
JEDITM
entrada negativa significa que o recurso deve ser negado.
Quando calculamos as permissões que um solicitante terá, as seguintes regras devem ser usadas:
1. Cada indivíduo ou grupo pode ter uma entrada positiva e outra negativa. Isto quer dizer
que entradas ACL duplicadas não são permitidas;
2. Se não houver entrada de um determinado indivíduo ou de um grupo, significa que o
indivíduo ou o grupo tem permissão nula;
3. O conjunto de grupos positivos que permite definir um indivíduo é a união de todas as
permissões positivas de cada grupo a que o indivíduo pertence;
4. O conjunto de grupos negativos que permite definir um indivíduo é a união de todos as
permissões negativas de cada grupo que o indivíduo pertence;
5. Se existe uma entrada positiva, que concede a um indivíduo ou grupo uma permissão
especial, e uma entrada negativa, que nega alguma destas permissões, então todas as
permissões são removidas, tanto positivas, quanto negativas;
6. Permissões individuais (permissões concedidas ou negadas para um solicitante específico)
sempre tem prioridade sobre as permissões de um grupo. Ou seja, indivíduos com
permissões negativas (specific denial of permissions) tem prioridade sobre os grupos com
permissões positivas. E indivíduos com permissões positivas tem prioridade sobre grupos
com permissões negativas;
7. Presumindo que todo o grupo (g1),que o indivíduo pertença, tem permissão positiva e que
todo grupo (g2), que o indivíduo pertença, tem permissão negativa. Assumimos, também,
que o indivíduo de permissão positiva é definido por (p1) e o indivíduo de permissão
negativa é definido por (p2). Então a resultante das permissões, que os indivíduos
possuem é (p1 + (g1 - p2)) - (p2 + (g2 - p1)).
2.3. Exemplo de Cálculo das Permissões
Presumindo que o solicitante P pertença ao grupos G1 e G2. A tabela abaixo mostra 5 colunas
com alguns exemplos de permissões dadas a G1, G2 e P. O resultado das permissões concedidas a
P é verificado na última coluna.
Permissões
Permissões Permissões da Permissões
do Grupo G1 do Grupo G2 União (G1, G2) Individuais
Positivo
A
B
A+B
C
Negativo
nula
nula
nula
nula
Positivo
A
B
B
C
Negativo
-C
-A
-C
nula
Positivo
A
B
A+B
C
Negativo
nula
nula
nula
-A
Positivo
A
C
A
B
Negativo
-C
-B
-B
-A
Resultado das
Permissões
A+B+C
B+C
B+C
B
Para o primeiro exemplo de permissões, temos:
Permissões Permissões Permissões da Permissões Resultado das
do Grupo G1 do Grupo G2 União (G1, G2) Individuais Permissões
Positivo
A
Negativo nula
B
A+B
C
nula
nula
nula
A+B+C
O grupo positivo tem permissões: (A+B)
Segurança
6
JEDITM
O grupo negativo tem permissões: (nula)
Portanto, a união dos 2 grupos de permissões é: (A e B)
Além disso, a autorização individual é: (C)
Portanto, o resultado das permissões é: (A+B+C)
Para o segundo exemplo de permissões, temos:
Positivo
Permissões
do Grupo G1
Permissões Permissões da Permissões Resultado das
do Grupo G2 União (G1, G2) Individuais Permissões
A
B
B
C
-A
-C
nula
Negativo -C
B+C
O grupo positivo tem permissões: (A+B)
O grupo negativo tem permissões: (A+C)
Portanto, a união dos 2 grupos de permissões é: (B e -C)
Além disso, a autorização individual é: (C)
A regra 6 indica que permissões individuais terão sempre prioridade sobre permissões de um
grupo, as permissões resultantes são (B + C), em vez de apenas (B).
No terceiro exemplo de permissões, temos:
Permissões Permissões Permissões da Permissões Resultado das
do Grupo G1 do Grupo G2 União (G1, G2) Individuais Permissões
Positivo
A
Negativo nula
B
A+B
C
nula
nula
-A
B+C
O grupo positivo tem permissões: (A+B)
O grupo negativo tem permissões: (nula)
Portanto, a união dos 2 grupos de permissões é: (A e B)
Além disso, a autorização individual é: (-A+C)
As permissões resultantes são (B + C), uma vez que a permissão do indivíduo negativo (A), se
sobrepõe à permissão (A) do grupo.
Para finalizar, no quarto exemplo permissões, temos:
Permissões Permissões Permissões da Permissões Resultado das
do Grupo G1 do Grupo G2 União (G1, G2) Individuais Permissões
Positivo
A
Negativo -C
C
A
B
-B
-B
-A
B
O grupo positivo tem permissões: (A+C)
O grupo negativo tem permissões: (B+C)
Portanto, a união dos 2 grupos de permissões é: (A e -B)
Além disso, a autorização individual é: (-A + B)
O resultado das permissões é (B+C), quando a permissão do individuo negativo for A, este irá
sobrescrever a permissão do grupo (positivo), quando esta for também A.
Segurança
7
JEDITM
3. Java e as Listas de Controle de Acesso
O pacote java.security.acl fornece a interface para essa estrutura de dados que guarda o acesso
aos recursos. O sun.security.acl fornece uma estrutura padrão para implementação das interfaces
especificadas no pacote java.security.acl.
3.1. Estrutura da ACL
Em Java, uma ACL é um objeto que implementa a interface java.security.acl.Acl. Cada Acl é uma
lista de objetos AclEntry. Cada AclEntry está associada a um objeto Indivíduo ou Grupo de uma
lista de objetos Permissões. Cada AclEntry também pode ser associada a uma entrada positiva ou
negativa. Uma entrada positiva concede a lista de permissões na entrada do indivíduo, ou grupo,
e uma entrada negativa nega a lista de permissões para o indivíduo ou grupo.
3.2. Pacote java.security.acl
Este pacote contém classes e interfaces usadas durante a execução das Listas de Controle de
Acesso para assegurar ou negar permissões a indivíduos ou grupos de indivíduos.
3.2.1. Interfaces
Interface Acl
Interface representando uma Lista de Controle de Acesso (ACL). Uma ACL é uma estrutura de
dados usada para guardar acessos aos recursos.
Uma ACL pode ser considerada como uma estrutura de dados com múltiplas entradas ACL. Cada
entrada ACL, de interface AclEntry, contém um conjunto de permissões associadas a um
determinado solicitante (Um solicitante representa uma entidade, como um usuário individual ou
de grupo). Além disso, cada entrada ACL é especificada como positiva ou negativa. Caso seja
positiva, as permissões serão concedidos aos respectivos solicitantes. Caso seja negativa, as
permissões serão negadas.
As solicitações ACL em cada ACL devem observar as seguintes regras:
•
Cada solicitante pode ter, no máximo, uma entrada ACL positiva e uma negativa. Isto é,
múltiplas entradas ACL, positivas ou negativas, não são permitidas para nenhum indivíduo.
Cada entrada especifica o conjunto de permissões que serão concedidas (se positiva) ou
negadas (se negativas)
•
Se não houver entrada de nenhum solicitante, então considera-se que o solicitante tem um
conjunto vazio de permissões
•
Se houver uma entrada positiva, que concede uma determinada permissão a um indivíduo
e uma entrada negativa, que nega esta entrada na mesma permissão, o resultado é como
se a permissão nunca fosse concedida ou recusada
•
Permissões individuais sempre terão prioridade sobre permissões de grupo ao qual
pertence o indivíduo. Isto é, permissões individuais negativas substituirão os grupos com
permissões positivas. E permissões individuais positivas substituirão as permissões dos
grupos negativos.
Interface AclEntry
Esta é a interface usada para representar uma entrada na Lista de Controle de Acesso (ACL).
Uma ACL pode ser comparada a uma estrutura de dados com múltiplos objetos de entrada ACL.
Cada objeto de entrada ACL contém um conjunto de permissões associadas com um Principal em
particular. (Um Principal representa uma entidade como um usuário individual ou um grupo).
Adicionalmente, cada entrada ACL é especificada como sendo tanto positiva como negativa. Se
positiva, as permissões devem ser concedidas para o principal associado. Se negativa, as
permissões serão negadas. Cada Principal deve ter pelo menos uma entrada positiva e uma
negativa; assim sendo, múltiplas entradas positivas ou negativas não são permitidas para
Segurança
8
JEDITM
qualquer Principal. As entradas ACL por padrão são positivas. Um entrada se torna negativa
apenas se o método setNegativePermissions for chamado.
Interface Group
Essa interface é usada para representar um grupo de Principals. Um Principal representa uma
entidade como um usuário individual ou um empresa.
Observe que Group estende Principal. Então, tanto um Principal ou um Group podem ser passados
como um argumento para métodos que contém um parâmetro Principal. Por exemplo, podemos
adicionar um Principal ou um Group para um objeto Group chamando o método addMember desse
objeto, passando o Principal ou Group.
Interface Owner
Interface para gerenciamento de Owners de Listas de Controle de Acesso ou configurações ACL.
(Observe que a interface Acl no pacote java.security.acl estende a interface Owner). O Owner
inicial de um Principal deve ser especificado como um argumento para o construtor da classe que
implementa essa interface.
Interface Permission
Essa interface representa uma permissão, como às usadas para conceder um tipo particular de
acesso para um recurso.
3.2.2. Exceções
AclNotFoundException
Essa é uma exceção que é lançada sempre que uma referência é feita a um ACL que não existe.
LastOwnerException
Essa é uma exceção que é lançada sempre que é feita uma tentativa de apagar o último dono de
uma Lista de Controle de Acesso.
NotOwnerException
Essa é uma exceção que é lançada sempre que a modificação de um objeto (como uma Lista de
Controle de Acesso) é permitida ser feita apenas pelo dono do objeto, mas o Principal tentando
modificar não é o dono.
3.3. Implementando uma ACL
O foco do pacote java.security.acl é a interface Acl, que representa uma lista de controle de
acesso. Uma ACL tem um grupo de donos associados com ela, representado pelos objetos da
classe Principal. Principal é um termo usado no meio de segurança para se referir a um usuário
agindo como uma parte em uma transação de segurança. Como ambas classes Identity e Signer
são subclasses de Principal, podemos usar instâncias de qualquer uma onde um Principal estiver
sendo chamado. Apenas Owners da ACL devem ser capazes de modificá-la. Implementações da
interface Acl devem reforçar isso checando as chaves e certificados dos Owners iniciais, para
assegurar que o agente criando ou modificando a ACL tem acesso aos elementos certificados da
identidade de um dos Owners da ACL.
Para definirmos um conjunto de tipos de permissões, um objeto da classe Permission tem que ser
criado. Isso pode ser feito instanciando-se um PermissionImpl e associando-o a um objeto
Permission.
Permission
Permission
Permission
Permission
create = new PermissionImpl("CREATE");
read = new PermissionImpl("READ");
update = new PermissionImpl("UPDATE");
destroy = new PermissionImpl("DELETE");
Para criarmos Principals, instanciamos um PrincipalImpl e o associamos a um objeto Principal. O
pacote sun.security.acl provê uma implementação de Permission chamada PermissionImpl, uma
subclasse da interface Principal, que utiliza Strings para identificar tipos de permissões (ex.,
Segurança
9
JEDITM
"READ", "WRITE").
Em uma aplicação real, poderíamos usar um objeto Identity ou Signer para representar um
Principal na ACL. Isso nos permitiria verificar uma assinatura digital de um cliente remoto antes
de permitir o acesso do cliente remoto a recursos protegidos pela ACL.
Principal hunny = new PrincipalImpl("Hunny");
Principal ozzie = new PrincipalImpl("Ozzie");
Cada entrada na lista de controle de acesso é representada como um objeto AclEntry, o qual
associa identidades específicas com permissões existentes para um recurso sendo controlado.
AclEntry aclEntry1 = new AclEntryImpl(hunny);
AclEntry aclEntry2 = new AclEntryImpl(ozzie);
Uma entrada é adicionada ao Acl usando o método addEntry(), o qual pega o Principal da
entidade e seus AclEntry como argumentos.
Cada AclEntry define um conjunto de permissões dadas para o Principal sobre o recurso sendo
protegido. Tipos específicos de permissões são representados usando a interface Permission, a
qual não implementa qualquer comportamento, mas age como placeholder para subclasses que
distinguem permissões de maneiras específicas da aplicação (nomes de permissões, tipos
binários, entre outros).
Para associar permissões para cada Principal, o método addPermission() em um AclEntry é usado.
Esse método recebe objetos de permissão.
Para Hunny, o qual é um elemento de aclEntry1, apenas permissões read e update são dadas.
aclEntry1.addPermission(read);
aclEntry1.addPermission(update);
Para Ozzie, o qual é elemento de aclEntry2, todas as permissões create, read, update e delete são
concedidas.
aclEntry2.addPermission(create);
aclEntry2.addPermission(read);
aclEntry2.addPermission(update);
aclEntry2.addPermission(delete);
Uma vez que o AclEntrys for criado, podem ser adicionados à ACL através do método addEntry().
O método recebe dois argumentos: um Principal que corresponde ao dono da ACL que realiza a
entrada, e o AclEntry. Por exemplo:
Acl myAcl = new AclImpl(hunny, "SampleACL1");
myAcl.addEntry(hunny, aclEntry1);
myAcl.addEntry(hunny, aclEntry2);
O exemplo a seguir cria um grupo com 2 usuários Principals, user1 e user2. Inicialmente, as
permissões dos grupos são configuradas para read e write. A permissão individual para user1 é
então configurada para não escrever (permissão negativa para write). Cada passo será descrito a
seguir.
Criar Principals
Principal p1 = new PrincipalImpl("user1");
Principal p2 = new PrincipalImpl("user2");
Principal owner = new PrincipalImpl("owner");
Criar Permissions
Permission read = new PermissionImpl("READ");
Permission write = new PermissionImpl("WRITE");
Criar um grupo que contém p1 (user1) e p2 (user2)
Group g = new GroupImpl("group1");
g.addMember(p1);
Segurança
10
JEDITM
g.addMember(p2);
Criar um novo ACL com o nome exampleAcl
Acl acl = new AclImpl(owner, "exampleAcl");
Configurar permissão do grupo para read e write
AclEntry entry1 = new AclEntryImpl(g);
entry1.addPermission(read);
entry1.addPermission(write);
acl.addEntry(owner, entry1);
Remover a permissão write apenas para o user1 (permissão individual negativa)
AclEntry entry2 = new AclEntryImpl(p1);
entry2.addPermission(write);
entry2.setNegativePermissions();
acl.addEntry(owner, entry2);
Testes são criados para verificar os resultados do exemplo ACL
Como p1 tem permissão de read e write, e uma permissão individual de negativo write, uma
enumeration de permissões acessíveis para p1 deverá listar apenas permissões read.
Enumeration e1 = acl.getPermissions(p1);
Como p2 tem permissões de grupo read e write, e permissão individual de null, uma enumeração
das permissões acessíveis para p1 deverá listar permissões read e write.
Enumeration e2 = acl.getPermissions(p2);
Para checar a permissão de um Principal em particular, o checkPermission(Principal prin,
Permission perm) pode ser usado. O método irá retornar verdadeiro se Principal puder executar a
permissão ou falso, caso contrário.
Executando checkPermission de write no Principal p1 retornará falso pois a permissão individual
de write para p1 é configurada para negativo (write não é permitido).
boolean b1 = acl.checkPermission(p1, write);
Executando checkPermission de read no Principal p1 irá retornar verdadeiro pois a permissão do
grupo permiti acesso de leitura para p1. Adicionalmente, executando read e write
checkPermissions no p2 irá retornar verdadeiro pois read e write é permitido para p2. Nos casos
abaixo, nenhuma permissão individual sobrescreve a permissão do grupo, então todos os
comandos abaixo irão retornar verdadeiro.
boolean b2 = acl.checkPermission(p1, read);
boolean b3 = acl.checkPermission(p2, read);
boolean b4 = acl.checkPermission(p2, write);
Um exemplo de classe ACL é definida abaixo
import
import
import
import
java.security.acl.*;
sun.security.acl.*;
java.util.Enumeration;
java.security.Principal;
public class ExampleACL {
public static void main(String[] args) throws NotOwnerException {
Principal p1 = new PrincipalImpl("user1");
Principal p2 = new PrincipalImpl("user2");
Principal owner = new PrincipalImpl("owner");
Permission read = new PermissionImpl("READ");
Permission write = new PermissionImpl("WRITE");
Group g = new GroupImpl("group1");
Segurança
11
JEDITM
}
Segurança
}
g.addMember(p1);
g.addMember(p2);
Acl acl = new AclImpl(owner, "exampleAcl");
AclEntry entry1 = new AclEntryImpl(g);
entry1.addPermission(read);
entry1.addPermission(write);
acl.addEntry(owner, entry1);
AclEntry entry2 = new AclEntryImpl(p1);
entry2.addPermission(write);
entry2.setNegativePermissions();
acl.addEntry(owner, entry2);
Enumeration e1 = acl.getPermissions(p1);
Enumeration e2 = acl.getPermissions(p2);
boolean b1 = acl.checkPermission(p1, write);
boolean b2 = acl.checkPermission(p1, read);
boolean b3 = acl.checkPermission(p2, read);
boolean b4 = acl.checkPermission(p2, write);
System.out.println(b1 + " " + b2 + " " + " " + b3 + " " + b4);
12
JEDITM
Parceiros que tornaram JEDITM possível
Instituto CTS
Patrocinador do DFJUG.
Sun Microsystems
Fornecimento de servidor de dados para o armazenamento dos vídeo-aulas.
Java Research and Development Center da Universidade das Filipinas
Criador da Iniciativa JEDITM.
DFJUG
Detentor dos direitos do JEDITM nos países de língua portuguesa.
Banco do Brasil
Disponibilização de seus telecentros para abrigar e difundir a Iniciativa JEDITM.
Politec
Suporte e apoio financeiro e logístico a todo o processo.
Borland
Apoio internacional para que possamos alcançar os outros países de língua
portuguesa.
Instituto Gaudium/CNBB
Fornecimento da sua infra-estrutura de hardware de seus servidores para que os
milhares de alunos possam acessar o material do curso simultaneamente.
Segurança
13
Download