JavaRush /Blogue Java /Random-PT /De HTTP para HTTPS
Viacheslav
Nível 3

De HTTP para HTTPS

Publicado no grupo Random-PT
De HTTP para HTTPS - 1
Contente:

Introdução

No mundo moderno, você não pode viver sem aplicativos web. E começaremos com um pequeno experimento. Quando criança, lembro-me de como todas as barracas vendiam um jornal chamado “Argumentos e Fatos”. Lembrei-me deles porque, segundo minha percepção pessoal desde a infância, esses jornais sempre pareciam estranhos. E decidi se deveríamos ir ao site deles:
De HTTP para HTTPS - 2
Se formos à ajuda do Google Chrome, leremos que este site não utiliza uma conexão segura e as informações que você troca com o site podem estar acessíveis a terceiros. Vejamos algumas outras notícias, por exemplo, notícias de São Petersburgo da Fontanka, uma mídia eletrônica:
De HTTP para HTTPS - 3
Como você pode ver, o site Fontanka não apresenta problemas de segurança com base nesses dados. Acontece que os recursos da web podem ou não ser seguros. Vemos também que o acesso a recursos desprotegidos ocorre através do protocolo HTTP. E se o recurso estiver protegido, a troca de dados é realizada através do protocolo HTTPS, onde o S no final significa “Seguro”. O protocolo HTTPS é descrito na especificação rfc2818: " HTTP Over TLS ". Vamos tentar criar nossa própria aplicação web e ver por nós mesmos como ela funciona. E ao longo do caminho entenderemos os termos.
De HTTP para HTTPS - 4

Aplicação web em Java

Portanto, precisamos criar uma aplicação web muito simples em Java. Primeiro, precisamos do próprio aplicativo Java. Para fazer isso, usaremos o sistema de construção automática do projeto Gradle. Isso nos permitirá não criar manualmente a estrutura de diretórios necessária + Gradle gerenciará para nós todas as bibliotecas necessárias para o projeto e garantirá que elas estejam disponíveis ao executar o código. Você pode ler mais sobre o Gradle em uma breve revisão: " Uma Breve Introdução ao Gradle ". Vamos usar o Gradle Init Plugin e executar o comando:
gradle init --type java-application
Depois disso, vamos abrir o script de construção build.gradle, que descreve em quais bibliotecas consiste nosso projeto, que o Gradle nos fornecerá. Vamos adicionar uma dependência ao servidor web no qual iremos experimentar:
dependencies {
    // Web server
    implementation 'io.undertow:undertow-core:2.0.20.Final'
     // Use JUnit test framework
     testImplementation 'junit:junit:4.12'
}
Para que uma aplicação web funcione, definitivamente precisamos de um servidor web onde nossa aplicação será hospedada. Existe uma grande variedade de servidores web, mas os principais são: Tomcat, Jetty, Undertow. Desta vez escolheremos Undertow. Para entender como podemos trabalhar com este nosso servidor web, vamos ao site oficial do Undertow e vamos para a seção de documentação . Você e eu conectamos uma dependência do Undertow Core, por isso estamos interessados ​​​​na seção sobre esse mesmo Core , ou seja, o núcleo, a base do servidor web. A maneira mais fácil é usar a API Builder para Undertow:
public static void main(String[] args) {
	Undertow server = Undertow.builder()
            .addHttpListener(8080, "localhost")
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {
                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");
                }
            }).build();
    server.start();
}
Se executarmos o código, podemos navegar para o seguinte recurso da web:
De HTTP para HTTPS - 5
Funciona de forma simples. Graças à API Undertow Builder, adicionamos um ouvinte HTTP ao localhost e à porta 8080. Este ouvinte recebe solicitações do navegador da web e retorna a string "Hello World" em resposta. Ótimo aplicativo da web. Mas, como vemos, usamos o protocolo HTTP, ou seja, Este tipo de troca de dados não é seguro. Vamos descobrir como as trocas são realizadas usando o protocolo HTTPS.
De HTTP para HTTPS - 6

Requisitos para HTTPS

Para entender como habilitar o HTTPS, vamos voltar à especificação HTTPS: “ RFC-2818: HTTP Over TLS ”. De acordo com a especificação, os dados no protocolo HTTPS são transmitidos pelos protocolos criptográficos SSL ou TLS. As pessoas são frequentemente enganadas pelo conceito de SSL e TLS. Na verdade, o SSL evoluiu e mudou suas versões. Mais tarde, o TLS tornou-se o próximo passo no desenvolvimento do protocolo SSL. Ou seja, o TLS é simplesmente uma nova versão do SSL. A especificação diz: “SSL e seu sucessor TLS”. Assim, aprendemos que existem protocolos criptográficos SSL/TLS. SSL é uma abreviatura de Secure Sockets Layer e se traduz como “camada de soquete seguro”. Soquete traduzido do inglês é um conector. Os participantes da transmissão de dados em uma rede usam soquetes como interface de programação (ou seja, uma API) para se comunicarem entre si pela rede. O navegador atua como um cliente e usa um soquete de cliente, e o servidor que recebe uma solicitação e emite uma resposta usa um soquete de servidor. E é entre esses soquetes que ocorre a troca de dados. É por isso que o protocolo foi originalmente chamado de SSL. Mas o tempo passou e o protocolo evoluiu. E a certa altura, o protocolo SSL tornou-se o protocolo TLS. TLS é a abreviação de Transport Layer Security. O protocolo TLS, por sua vez, é baseado na especificação do protocolo SSL versão 3.0. O protocolo TLS é tema de artigos e análises separados, então simplesmente indicarei materiais que considero interessantes: Resumindo, a base do HTTPS é o handshake TLS e a verificação da “Identidade do Servidor” (ou seja, identificação do servidor) utilizando o seu certificado digital. É importante. Vamos lembrar disso, porque... Voltaremos a este fato mais adiante. Portanto, anteriormente usamos HttpListener para informar ao servidor como operar no protocolo HTTP. Se no exemplo acima adicionamos um HttpListener para funcionar em HTTP, então para trabalhar em HTTPS precisamos adicionar um HttpsListener:
De HTTP para HTTPS - 7
Mas para adicioná-lo precisamos de SSLContext. Curiosamente, SSLContext não é uma classe do Undertow, mas javax.net.ssl.SSLContext. A classe SSLContext faz parte da chamada " Java Secure Socket Extension " (JSSE) - uma extensão Java para garantir a segurança da conexão com a Internet. Esta extensão é descrita no " Java Secure Socket Extension (JSSE) Reference Guide ". Como você pode ver na parte introdutória da documentação, JSSE fornece uma estrutura e implementação Java dos protocolos SSL e TLS. Como obtemos o SSLContext? Abra o JavaDoc SSLContext e encontre o método getInstance . Como você pode ver, para obter o SSLContext precisamos especificar o nome "Secure Socket Protocol". A descrição dos parâmetros indica que esses nomes podem ser encontrados na " Java Cryptography Architecture Standard Algorithm Name Documentation ". Portanto, vamos seguir as instruções e passar para a documentação. E vemos que podemos escolher entre SSL e TLS:
De HTTP para HTTPS - 8
Agora entendemos que precisamos criar o SSLContext da seguinte forma:
public SSLContext getSSLContext() {
	// 1. Получаем контекст, в рамках которого будем работать по TLS протоколу
	SSLContext context = null;
	try {
		context = SSLContext.getInstance("TLS");
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	}
	return context;
}
Tendo criado um novo contexto, lembramos que SSLContext foi descrito no " Java Secure Socket Extension (JSSE) Reference Guide ". Lemos e vemos que “Um SSLContext recém-criado deve ser inicializado chamando o método init”. Ou seja, criar um contexto não é suficiente. Ele precisa ser inicializado. E isso é lógico, porque sobre segurança, apenas dissemos que queremos usar o protocolo TLS. Para inicializar o SSLContext precisamos fornecer três coisas: KeyManager, TrustManager, SecureRandom.
De HTTP para HTTPS - 9

Gerente de Chaves

KeyManager é um gerenciador de chaves. Ele é responsável por qual “credencial de autenticação” fornecer a alguém que nos contate. Credencial pode ser traduzida como identidade. A identidade é necessária para que o cliente tenha certeza de que o servidor é quem ele afirma ser e é confiável. O que será usado como identificação? Como lembramos, a Identidade do Servidor é verificada pelo certificado digital do servidor. Este processo pode ser representado da seguinte forma:
De HTTP para HTTPS - 10
Além disso, o " JSSE Reference Guide: How SSL Works " diz que SSL usa "criptografia assimétrica", o que significa que precisamos de um par de chaves: uma chave pública e uma chave privada. Já que estamos falando de criptografia, entra em cena a “Arquitetura de Criptografia Java” (JCA). A Oracle fornece um excelente documento sobre esta arquitetura: " Java Cryptography Architecture (JCA) Reference Guide ". Além disso, você pode ler uma breve visão geral do JCA no JavaRush: " Arquitetura de criptografia Java: primeiro contato ". Portanto, para inicializar o KeyManager, precisamos de um KeyStore, que irá armazenar o certificado do nosso servidor. A maneira mais comum de criar um armazenamento de chaves e certificados é o utilitário keytool, incluído no JDK. Um exemplo pode ser visto na documentação do JSSE: " Criando um Keystore para Usar com JSSE ". Portanto, precisamos usar o utilitário KeyTool para criar um armazenamento de chaves e gravar o certificado nele. Curiosamente, a geração de chaves foi especificada anteriormente usando -genkey, mas agora é recomendado usar -genkeypair. Precisaremos definir o seguinte:
  • alias : Alias ​​ou simplesmente o nome sob o qual a entrada será salva no Keystore
  • keyalg : Algoritmo de criptografia de chave. Vamos escolher o algoritmo RSA, que é essencialmente uma solução padrão para o nosso propósito.
  • keysize : Tamanho da chave (em bits). O tamanho mínimo recomendado é 2048, porque... um tamanho menor já foi quebrado. Você pode ler mais aqui: “ um certificado SSL em 2048 bits ”.
  • dname : Nome Distinto, nome distinto.
É importante entender que o recurso solicitado (por exemplo, https://localhost) será comparado com ele. Isso é chamado de "correspondência de assunto cn".
  • validade : Duração em dias durante a qual o certificado gerado é válido, ou seja, válido.
  • ext : extensão de certificado especificada em " Extensões nomeadas ".
Para certificados autoassinados (ou seja, para certificados criados de forma independente), você deve especificar as seguintes extensões:
  • -ext san:critical=dns:localhost,ip:127.0.0.1 > para realizar a correspondência de assunto por SubjectAlternativeName
  • -ext bc=ca:false > para indicar que este certificado não é usado para assinar outros certificados
Vamos executar o comando (exemplo para sistema operacional Windows):
keytool -genkeypair -alias ssl -keyalg RSA -keysize 2048 -dname "CN=localhost,OU=IT,O=Javarush,L=SaintPetersburg,C=RU,email=contact@email.com" -validity 90 -keystore C:/keystore.jks -storepass passw0rd -keypass passw0rd -ext san:critical=dns:localhost,ip:127.0.0.1 -ext bc=ca:false
Porque o arquivo será criado, certifique-se de ter todos os direitos para criar o arquivo. Você provavelmente também verá conselhos como este:
De HTTP para HTTPS - 11
Aqui somos informados de que JKS é um formato proprietário. Proprietário significa que é propriedade privada dos autores e destina-se ao uso apenas em Java. Ao trabalhar com utilitários de terceiros, pode surgir um conflito, por isso somos avisados. Além disso, podemos receber o erro: The destination pkcs12 keystore has different storepass and keypass. Este erro ocorre porque as senhas da entrada no Keystore e do próprio keystore são diferentes. Como diz a documentação do keytool : "Por exemplo, a maioria das ferramentas de terceiros exige que storepass e keypass em um keystore PKCS #12 sejam iguais." Podemos especificar a chave nós mesmos (por exemplo, -destkeypass entrypassw). Mas é melhor não violar os requisitos e definir a mesma senha. Portanto, a importação pode ficar assim:
keytool -importkeystore -srckeystore C:/keystore.jks -destkeystore C:/keystore.jks -deststoretype pkcs12
Exemplo de sucesso:
De HTTP para HTTPS - 12
Para exportar o certificado para um arquivo, você pode executar:
keytool -export -alias ssl -storepass passw0rd -file C:/server.cer -keystore C:/keystore.jks
Além disso, podemos obter o conteúdo do Keystore assim:
keytool -list -v -keystore C:/keystore.jks -storepass passw0rd
Ótimo, agora temos um keystore que contém o certificado. Agora você pode obtê-lo a partir do código:
public KeyStore getKeyStore() {
	// Согласно https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore
	try(FileInputStream fis = new FileInputStream("C:/keystore.jks")){
		KeyStore keyStore = KeyStore.getInstance("pkcs12");
		keyStore.load(fis, "passw0rd".toCharArray());
		return keyStore;
	} catch (IOException ioe) {
		throw new IllegalStateException(ioe);
	} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
		throw new IllegalStateException(e);
	}
}
Se houver um KeyStore, podemos inicializar o KeyManager:
public KeyManager[] getKeyManagers(KeyStore keyStore) {
	String keyManagerAlgo = KeyManagerFactory.getDefaultAlgorithm();
	KeyManagerFactory keyManagerFactory = null;
	try {
		keyManagerFactory = KeyManagerFactory.getInstance(keyManagerAlgo);
		keyManagerFactory.init(keyStore, "passw0rd".toCharArray());
		return keyManagerFactory.getKeyManagers();
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	} catch (UnrecoverableKeyException | KeyStoreException e) {
		throw new IllegalStateException(e);
	}
}
Nosso primeiro objetivo foi alcançado. Resta descobrir o que é TrustManager. TrustManager é descrito na documentação JSSE na seção " A Interface TrustManager ". É muito semelhante ao KeyManager, mas seu objetivo é verificar se a pessoa que solicita a conexão é confiável. Para ser franco, este é o KeyManager ao contrário =) Não precisamos do TrustManager, então passaremos nulo. Será então criado um TrustManager padrão que não verifica o usuário final que faz solicitações ao nosso servidor. A documentação diz o seguinte: "será usada a implementação padrão". O mesmo com SecureRandom. Se especificarmos null, a implementação padrão será usada. Vamos apenas lembrar que SecureRandom é uma classe JCA e está descrita na documentação JCA na seção " A Classe SecureRandom ". No total, a preparação levando em consideração todos os métodos descritos acima pode ser assim:
public static void main(String[] args) {
	// 1. Подготавливаем приложение к работе по HTTPS
	App app = new App();
	SSLContext sslContext = app.getSSLContext();
	KeyStore keyStore = app.getKeyStore();
	KeyManager[] keyManagers = app.getKeyManagers(keyStore);
	try {
		sslContext.init(keyManagers, null, null);
	} catch (KeyManagementException e) {
		throw new IllegalStateException(e);
	}
Resta apenas iniciar o servidor:
// 2. Поднимаем server
 	int httpsPort = 443;
	Undertow server = Undertow.builder()
            .addHttpsListener(httpsPort, "localhost", sslContext)
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {
                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");
                }
            }).build();
	server.start();
}
Desta vez nosso servidor estará disponível no endereço https://localhost:443 No entanto, ainda receberemos um erro informando que este recurso não é confiável:
De HTTP para HTTPS - 13
Vamos descobrir o que há de errado com o certificado e o que fazer a respeito.
De HTTP para HTTPS - 14

Gerenciamento de certificados

Então, nosso servidor já está pronto para funcionar via HTTPS, mas o cliente não confia nele. Por que? Vamos dar uma olhada:
De HTTP para HTTPS - 15
A razão é que este certificado é um certificado autoassinado. Um certificado SSL autoassinado refere-se a um certificado de chave pública emitido e assinado pela mesma pessoa que identifica. Ou seja, não foi emitido por nenhuma autoridade certificadora (CA, também conhecida como Autoridade Certificadora) respeitada. A Autoridade Certificadora atua como um administrador e é semelhante a um notário na vida cotidiana. Ele garante que os certificados que emite são confiáveis. O serviço de emissão de certificados por essas CAs é pago, portanto, ninguém precisa de perda de confiança e riscos de reputação. Por padrão, existem diversas autoridades de certificação confiáveis. Esta lista é editável. E cada sistema operacional possui seu próprio gerenciamento da lista de autoridades certificadoras. Por exemplo, o gerenciamento desta lista no Windows pode ser lido aqui: " Gerenciar certificados raiz confiáveis ​​no Windows ". Vamos adicionar o certificado aos confiáveis ​​conforme indicado na mensagem de erro. Para fazer isso, primeiro baixe o certificado:
De HTTP para HTTPS - 16
No sistema operacional Windows, pressione Win+R e execute mmcpara chamar o console de controle. Em seguida, pressione Ctrl+M para adicionar a seção “Certificados” ao console atual. A seguir, na subseção "Autoridades de certificação raiz confiáveis", executaremos Действия / Все задачи / Импорт. Vamos importar o arquivo que foi baixado anteriormente para o arquivo. O navegador pode ter se lembrado do estado de confiança anterior do certificado. Portanto, antes de abrir a página é necessário reiniciar o navegador. Por exemplo, no Google Chrome, na barra de endereço, você precisa executar chrome://restart. No sistema operacional Windows, você também pode usar o utilitário para visualizar certificados certmgr.msc:
De HTTP para HTTPS - 17
Se fizermos tudo corretamente, veremos uma chamada bem-sucedida ao nosso servidor via HTTPS:
De HTTP para HTTPS - 18
Como você pode ver, o certificado agora é considerado válido, o recurso está disponível e não há erros.
De HTTP para HTTPS - 19

Resultado final

Então, descobrimos como é o esquema para habilitar o protocolo HTTPS em um servidor web e o que é necessário para isso. Espero que neste ponto esteja claro que o suporte é fornecido pela interação da Java Cryptography Architecture (JCA), que é responsável pela criptografia, e do Java Secure Socket Extension (JSSE), que fornece a implementação do TLS no lado Java. Vimos como o utilitário keytool incluído no JDK é usado para trabalhar com a chave KeyStore e o armazenamento de certificados. Além disso, percebemos que HTTPS usa protocolos SSL/TLS para segurança. Para reforçar, aconselho a leitura de excelentes artigos sobre este tema: Esperançosamente, após esta pequena revisão, o HTTPS se tornará um pouco mais transparente. E se precisar habilitar HTTPS, você poderá entender facilmente os termos na documentação de seus servidores de aplicativos e estruturas. #Viacheslav
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION