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