0% acharam este documento útil (0 voto)
48 visualizações

13 Securing IoT APIs - API Security in Action

O documento discute técnicas de autenticação e autorização para APIs de Internet das Coisas (IoT). Ele explica como identificar dispositivos, armazenar seus perfis em um banco de dados e autenticá-los para acessar APIs. Também aborda a adaptação do OAuth2 para dispositivos restritos e como lidar com decisões de controle de acesso quando um dispositivo está offline.

Enviado por

Marcus Passos
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
48 visualizações

13 Securing IoT APIs - API Security in Action

O documento discute técnicas de autenticação e autorização para APIs de Internet das Coisas (IoT). Ele explica como identificar dispositivos, armazenar seus perfis em um banco de dados e autenticá-los para acessar APIs. Também aborda a adaptação do OAuth2 para dispositivos restritos e como lidar com decisões de controle de acesso quando um dispositivo está offline.

Enviado por

Marcus Passos
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 71

Tópicos Comece a aprender Pesquise mais de 50.

000 cursos, even… O que há de novo

13 Protegendo APIs de IoT


Este capítulocapas

Autenticação de dispositivos para APIs


Evitando ataques de repetição na autenticação de dispositivo de ponta a ponta
Autorizando coisas com a concessão de dispositivo OAuth2
Executando o controle de acesso local quando um dispositivo está offline

No capítulo 12, você aprendeu como proteger as comunicações entre dispositivos


usando Datagrama TLS(DTLS) e segurança de ponta a ponta. Neste capítulo, você
aprenderá como proteger o acesso a APIs em ambientes de Internet das Coisas
(IoT), incluindo APIs fornecidas pelos próprios dispositivos e APIs de nuvem às
quais os dispositivos se conectam. Em sua ascensão para se tornar a tecnologia de
segurança de API dominante, OAuth2 também é popular para aplicativos de IoT,
então você aprenderá sobre adaptações recentes de OAuth2 para dispositivos res-
tritos na seção 13.3. Por fim, veremos como gerenciar as decisões de controle de
acesso quando um dispositivo pode ser desconectado de outros serviços por pe-
ríodos prolongados na seção 13.4.
13.1 Dispositivos de autenticação

DentroEm aplicativos de IoT de consumo, os dispositivos geralmente atuam sob o


controle de um usuário, mas os dispositivos de IoT industriais geralmente são
projetados para agir de forma autônoma sem a intervenção manual do usuário.
Por exemplo, um sistema que monitora os níveis de suprimentos em um depósito
seria configurado para solicitar automaticamente novos estoques quando os ní-
veis de suprimentos críticos ficarem baixos. Nesses casos, os dispositivos IoT
agem sob sua própria autoridade, como as chamadas API serviço a serviço no ca-
pítulo 11. No capítulo 12, você viu como fornecer credenciais a dispositivos para
proteger as comunicações IoT e, nesta seção, você veja como usá-los para autenti-
car dispositivos para acessar APIs.

13.1.1 Identificando dispositivos

ParaPara poder identificar clientes e tomar decisões de controle de acesso sobre


eles em sua API, você precisa acompanhar identificadores de dispositivos legíti-
mos e outros atributos dos dispositivos e vinculá-los às credenciais que o disposi-
tivo usa para autenticar. Isso permite que você procure esses atributos do disposi-
tivo após a autenticação e os use para tomar decisões de controle de acesso. O pro-
cesso é muito semelhante à autenticação de usuários e você pode reutilizar um re-
positório de usuário existente, como o LDAP, para também armazenar perfis de
dispositivos, embora geralmente seja mais seguro separar usuários de contas de
dispositivos para evitar confusão. Onde um perfil de usuário normalmente inclui
uma senha com hash e detalhes como nome e endereço, um perfil de dispositivo
pode incluir uma chave pré-compartilhada para esse dispositivo, juntamente com
informações do fabricante e modelo,
O perfil do dispositivo pode ser gerado no momento em que o dispositivo é fabri-
cado, conforme mostrado na figura 13.1. Como alternativa, o perfil pode ser cri-
ado quando os dispositivos são entregues pela primeira vez a uma organização,
em um processo conhecido como integração.

Figura 13.1 Detalhes do dispositivo e identificadores exclusivos são armazenados


em um repositório compartilhado onde podem ser acessados ​posteriormente.

DEFINIÇÃO Integração do dispositivoé o processo de implantar um dispositivo


e registrá-lo nos serviços e redes que ele precisa acessar.

A Listagem 13.1 mostra o código para um perfil de dispositivo simples com um


identificador, informações básicas do modelo e uma chave pré-compartilhada
criptografada(PSK) que pode ser usado para se comunicar com o dispositivo
usando as técnicas do capítulo 12. O PSK será criptografado usando a Secret-
Box classe NaClque você usou no capítulo 6, então você pode adicionar um mé-
todo para descriptografar o PSK com uma chave secreta. Navegue até
src/main/java/com/manning/ apisecurityinaction e crie um novo arquivo cha-
mado Device.java e copie o conteúdo da listagem.

Listagem 13.1 Um perfil de dispositivo

pacote com.manning.apisecurityinaction;

import org.dalesbred.Database;
import org.dalesbred.annotation.DalesbredInstantiator;
import org.h2.jdbcx.JdbcConnectionPool;
importar software.pando.crypto.nacl.SecretBox;

importar java.io.*;
importar java.security.Key;
import java.util.Opcional;

dispositivo de classe pública {


string final deviceId; ❶
Fabricante final do String; ❶
modelo String final; ❶
byte final[] criptografadoPsk; ❶

@DalesbredInstantiator ❷
public Device(String deviceId, String fabricante,
Modelo de string, byte[] criptografadoPsk) {
this.deviceId = deviceId;
this.manufacturer = fabricante;
this.model = modelo;
this.encryptedPsk = criptografadoPsk;
}

public byte[] getPsk(Key decryptionKey) { ❸


try (var in = new ByteArrayInputStream(encryptedPsk)) { ❸
var box = SecretBox.readFrom(in); ❸
return box.decrypt(decryptionKey); ❸
} catch (IOException e) { ❸
throw new RuntimeException("Não foi possível descriptografar o PSK", e);
} ❸
} ❸
}

❶ Crie campos para os atributos do dispositivo.

❷ Anote o construtor para que Dalesbred saiba como carregar um dispositivo do


banco de dados.

❸ Adicione um método para descriptografar o PSK do dispositivo usando o NaCl's


SecretBox.

Agora você pode preencher o banco de dados com perfis de dispositivo. A Lista-
gem 13.2 mostra como inicializar o banco de dados com um exemplo de perfil de
dispositivo e PSK criptografado. Assim como nos capítulos anteriores, você pode
usar um banco de dados H2 na memória temporário para armazenar os detalhes
do dispositivo, porque isso facilita o teste. Em uma implantação de produção, você
usaria um servidor de banco de dados ou diretório LDAP. Você pode carregar o
banco de dados na biblioteca Dalesbred que você usou desde o capítulo 2 para
simplificar as consultas. Em seguida, você deve criar a tabela para conter os perfis
de dispositivo, neste caso com atributos de string simples ( VARCHAR em SQL) e um
atributo binário para manter o PSK criptografado. Você poderia extrair essas ins-
truções SQL em um arquivo schema.sql separado, como fez no capítulo 2, mas
como há apenas uma única tabela, usei strings literais. Abra o arquivo Device.java
novamente e inclua o novo método da listagem para criar o banco de dados de
dispositivo de exemplo.

Listagem 13.2 Preenchendo o banco de dados do dispositivo

O banco de dados estático createDatabase(SecretBox criptografadoPsk) lança IOExcepti


var pool = JdbcConnectionPool.create("jdbc:h2:mem:devices", ❶
"devices", "password"); ❶
var database = Database.forDataSource(pool); ❶

database.update("CREATE TABLE devices(" + ❷


"device_id VARCHAR(30) PRIMARY KEY," + ❷
"fabricante VARCHAR(100) NOT NULL," + ❷
"model VARCHAR(100) NOT NULL," + ❷
"encrypted_psk VARBINARY(1024) NOT NULL)"); ❷

var out = new ByteArrayOutputStream(); ❸


criptografadoPsk.writeTo(out); ❸
database.update("INSERT INTO devices(" + ❹
"device_id, fabricante, modelo, criptografado_psk) " + ❹
"VALUES(?, ?, ?, ?)", "teste", "exemplo", "ex001", ❹
out.toByteArray()); ❹

banco de dados de retorno;


}

❶ Crie e carregue o banco de dados do dispositivo na memória.

❷ Crie uma tabela para armazenar detalhes do dispositivo e PSKs criptografados.

❸ Serialize o PSK criptografado de exemplo em uma matriz de bytes.

❹ Insira um dispositivo de exemplo no banco de dados.

Você também precisará de uma maneira de localizar um dispositivo pelo ID do


dispositivo ou por outros atributos. A Dalesbred torna isso bastante simples, con-
forme mostrado na Listagem 13.3. o findOptional métodopode ser usado para
procurar um dispositivo; ele retornará um resultado vazio se não houver nenhum
dispositivo correspondente. Você deve selecionar os campos da tabela de disposi-
tivos exatamente na ordem em que aparecem na Device classeconstrutor na Lis-
tagem 13.1. Conforme descrito no capítulo 2, use um parâmetro de ligação na con-
sulta para fornecer o ID do dispositivo, para evitar ataques de injeção de SQL.

Listagem 13.3 Encontrando um dispositivo por ID

static Opcional<Device> find(Banco de dados, String deviceId) {


return database.findOptional(Device.class, ❶
"SELECT device_id, fabricante, modelo,crypt_psk " + ❷
"FROM devices WHERE device_id = ?", deviceId); ❸
}

❶ Use o método findOptional com sua classe Device para carregar dispositivos.

❷ Selecione os atributos do dispositivo na mesma ordem em que aparecem no


construtor.

❸ Use um parâmetro de ligação para consultar um dispositivo com o device_id


correspondente.

Agora que você tem alguns detalhes do dispositivo, pode usá-los para autenticar
dispositivos e executar o controle de acesso com base nessas identidades de dispo-
sitivo, o que você fará nas seções 13.1.2 e13.1.3.

13.1.2 Certificados de dispositivo

Umalternativa para armazenar detalhes do dispositivo diretamente em um banco


de dados é fornecer a cada dispositivo um certificado contendo os mesmos deta-
lhes, assinado por uma autoridade de certificação confiável. Embora tradicional-
mente os certificados sejam usados ​com criptografia de chave pública, você pode
usar as mesmas técnicas para dispositivos restritos que devem usar criptografia
simétrica. Por exemplo, o dispositivo pode receber um JSON Web Token assinado
que contém os detalhes do dispositivo e um PSK criptografado que o servidor da
API pode descriptografar, conforme mostrado na Listagem 13.4. O dispositivo
trata o certificado como um token opaco e simplesmente o apresenta às APIs que
precisa acessar. A API confia no JWT porque ele é assinado por um emissor con-
fiável e pode descriptografar o PSK para autenticar e se comunicar com o
dispositivo.

Listagem 13.4 PSK criptografado em um conjunto de declarações JWT

{
"iss":"https://example.com/devices", ❶
"iat":1590139506, ❶
"exp":1905672306, ❶
"sub":"ada37d7b-e895-4d55-9571-4df602e60c27", ❶
"psk ":" jZvara1OnqqBZrz1HtvHBCNjXvCJptEuIAAAAJInAtaLFnYna9K0WxX4_ ❷
➥ IGPyztb8VUwo0CI_UmqDQgm" ❷
}

❶ Inclua as declarações JWT usuais que identificam o dispositivo.

❷ Adicione um PSK criptografado que pode ser usado para se comunicar com o
dispositivo.

Isso pode ser mais escalável do que um banco de dados se você tiver muitos dis-
positivos, mas torna mais difícil atualizar detalhes incorretos ou alterar chaves.
Um meio-termo é fornecido pelas técnicas de atestação discutidas no capítulo 12,
nas quais um certificado inicial e uma chave são usados ​para provar a marca e o
modelo de um dispositivo quando ele se registra pela primeira vez em uma rede
e, em seguida, negocia uma chave específica do dispositivo para usar a partir de
entãosobre.
13.1.3 Autenticando na camada de transporte

Sehá uma conexão direta entre um dispositivo e a API que está acessando, então
você pode usar mecanismos de autenticação fornecidos pelo protocolo de segu-
rança da camada de transporte. Por exemplo, a chave pré-compartilhada (PSK)
conjuntos de cifras para TLS descritos no capítulo 12 fornecem autenticação mú-
tua do cliente e do servidor. A autenticação de certificado de cliente pode ser
usada por dispositivos mais capazes, assim como você fez no capítulo 11 para cli-
entes de serviço. Nesta seção, veremos a identificação de dispositivos usando a
autenticação PSK.

Durante o handshake, o cliente fornece uma identidade PSK ao servidor na men-


sagem ClientKeyExchange. A API pode usar esse ID PSK para localizar o PSK cor-
reto para esse cliente. O servidor pode pesquisar o perfil do dispositivo para esse
dispositivo usando o PSK ID ao mesmo tempo em que carrega o PSK, conforme
mostrado na figura 13.2. Depois que o handshake é concluído, a API garante a
identidade do dispositivo pela autenticação mútua que os conjuntos de cifras PSK
alcançam.
Figura 13.2 Quando o dispositivo se conecta à API, ele envia um identificador PSK
na mensagem TLS ClientKeyExchange. A API pode usar isso para encontrar um
perfil de dispositivo correspondente com um PSK criptografado para esse disposi-
tivo. A API descriptografa o PSK e conclui o handshake TLS usando o PSK para au-
tenticar o dispositivo.

Nesta seção, você ajustará o PskServer do capítulo 12 para procurar o perfil do


dispositivo durante a autenticação. Primeiro, você precisa carregar e inicializar o
banco de dados do dispositivo. Abra o arquivo PskServer.java e adicione as se-
guintes linhas no início do main() métodologo após o PSK ser carregado:
var psk = loadPsk(args[0].toCharArray()); ❶
var criptografiaChave = SecretBox.key(); ❷
var deviceDb = Device.createDatabase( ❸
SecretBox.encrypt(encryptionKey, psk)); ❸

❶ A linha existente para carregar o PSK de exemplo

❷ Crie uma nova chave de criptografia PSK.

❸ Inicialize o banco de dados com o PSK criptografado.

O cliente apresentará seu identificador de dispositivo como o campo de identi-


dade PSK durante o handshake, que você pode usar para localizar o perfil de dis-
positivo associado e o PSK criptografado para usar para autenticar a sessão. A Lis-
tagem 13.5 mostra uma nova DeviceIdentityManager classeque você pode
usar com Bouncy Castle em vez do gerenciador de identidade PSK existente. O
novo gerenciador de identidade realiza uma pesquisa no banco de dados do dis-
positivo para encontrar um dispositivo que corresponda à identidade PSK forne-
cida pelo cliente. Se um dispositivo correspondente for encontrado, você poderá
descriptografar o PSK associado do perfil do dispositivo e usá-lo para autenticar a
conexão TLS. Caso contrário, retorne null para abortar a conexão. O cliente não
precisa de nenhuma dica para determinar sua própria identidade, então você
também pode retornar null do getHint() métodopara desabilitar a mensagem
ServerKeyExchange no handshake da mesma forma que você fez no capítulo 12.
Crie um novo arquivo chamado DeviceIdentityManager.java na mesma pasta que
o arquivo Device.java criado anteriormente e adicione o conteúdo da listagem.
Listagem 13.5 O dispositivo IdentityManager

pacote com.manning.apisecurityinaction;
importar org.bouncycastle.tls.TlsPSKIdentityManager;
import org.dalesbred.Database;
importar java.security.Key;
importar estático java.nio.charset.StandardCharsets.UTF_8;
public class DeviceIdentityManager implementa TlsPSKIdentityManager {
banco de dados de banco de dados final privado;
Chave final privada pskDecryptionKey; ❶

public DeviceIdentityManager(banco de dados do banco de dados, chave pskDecrypti


this.database = banco de dados; ❶
this.pskDecryptionKey = pskDecryptionKey; ❶
}
@Sobrepor
public byte[] getHint() { ❷
return null; ❷
} ❷
@Sobrepor
public byte[] getPSK(byte[] identidade) {
var deviceId = new String(identidade, UTF_8); ❸
return Device.find(database, deviceId) ❸
.map(device -> device.getPsk(pskDecryptionKey)) ❹
.orElse(null); ❺
}
}
❶ Inicialize o gerenciador de identidade com o banco de dados do dispositivo e a
chave de descriptografia PSK.

❷ Retorne uma dica de identidade nula para desativar a mensagem


ServerKeyExchange.

❸ Converta a dica de identidade PSK em uma string UTF-8 para usar como a identi-
dade do dispositivo.

❹ Se o dispositivo existir, descriptografe o PSK associado.

❺ Caso contrário, retorne null para abortar a conexão.

Para usar o novo gerenciador de identidade do dispositivo, você precisa atualizar


a classe PskServer novamente. Abra PskServer.java em seu editor e altere as li-
nhas de código que criam o objeto PSKTlsServer para usar a nova classe. Desta-
quei o novo código em negrito:

var crypto = new BcTlsCrypto(new SecureRandom());


var server = new PSKTlsServer(crypto,
novo DeviceIdentityManager(deviceDb, encodingKey)) {

Você pode excluir o getIdentityManager() método antigotambém porque não


é usado agora. Você também precisa ajustar o PskClient implementação para
enviar o ID do dispositivo correto durante o handshake. Se você se lembra do ca-
pítulo 12, usamos um hash SHA-512 do PSK como o ID, mas o banco de dados do
dispositivo usa o ID "test" . Abra PskClient.java e altere a pskId variávelno
topo do main() métodopara usar os bytes UTF-8 do ID do dispositivo correto:
var pskId = "teste".getBytes(UTF_8);

Se você executar agora o PskServer e depois o PskClient ele ainda funcionará


corretamente, mas agora está usando o PSK criptografado carregado do banco de
dados do dispositivo.

EXPOR A IDENTIDADE DO DISPOSITIVO À API

Embora agora você esteja autenticando o dispositivo com base em um PSK ane-
xado ao seu perfil de dispositivo, esse perfil de dispositivo não é exposto à API
após a conclusão do handshake. O Bouncy Castle não fornece um método público
para obter a identidade PSK associada a uma conexão, mas é fácil expor você
mesmo adicionando um novo método ao PSKTlsServer , como mostra a Lista-
gem 13.6. Uma variável protegida dentro do servidor contém a TlsCon-
text classe, que contém informações sobre a conexão (o servidor suporta apenas
um único cliente por vez). A identidade PSK é armazenada dentro da
SecurityParameters classepara a conexão. Abra o arquivo PskServer.java e
adicione o novo método destacado em negrito na listagem. Você pode recuperar a
identidade do dispositivo depois de receber uma mensagem ligando para:

var deviceId = server.getPeerDeviceIdentity();

CUIDADO Você só deve confiar na identidade PSK retornada


de getSecurityParametersConnection() , que são os parâmetros finais após a
conclusão do handshake. O de nome
semelhante getSecurityParametersHandshake() contém parâmetros negocia-
dos durante o processo de handshake antes da conclusão da autenticação e pode
estar incorreto.

Listagem 13.6 Expondo a identidade do dispositivo

var server = new PSKTlsServer(crypto,


novo DeviceIdentityManager(deviceDb, encodingKey)) {
@Sobrepor
Protected ProtocolVersion[] getSupportedVersions() {
return ProtocolVersion.DTLSv12.only();
}
@Sobrepor
protected int[] getSupportedCipherSuites() {
return novo int[] {
CipherSuite.TLS_PSK_WITH_AES_128_CCM,
CipherSuite.TLS_PSK_WITH_AES_128_CCM_8,
CipherSuite.TLS_PSK_WITH_AES_256_CCM,
CipherSuite.TLS_PSK_WITH_AES_256_CCM_8,
CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_PSK_WITH_CHACHA20_POLY1305_SHA256
};
}

String getPeerDeviceIdentity() { ❶
return new String(context.getSecurityParametersConnection() ❷
.getPSKIdentity(), UTF_8); ❷
}
};

❶ Adicione um novo método ao PSKTlsServer para expor a identidade do cliente.

❷ Procure a identidade PSK e decodifique-a como uma string UTF-8.

O servidor da API pode usar essa identidade de dispositivo para procurar permis-
sões para esse dispositivo, usando as mesmas técnicas de controle de acesso base-
adas em identidade usadas para usuáriosdentrocapítulo 8.

questionário

1. Verdadeiro ou falso: um ID PSK é sempre uma string UTF-8.


2. Por que você só deve confiar no ID do PSK após a conclusão do handshake?
1. Antes que o handshake seja concluído, o ID é criptografado.
2. Você nunca deve confiar em ninguém até apertar sua mão.
3. O ID muda após o aperto de mão para evitar ataques de fixação de sessão.
4. Antes que o handshake seja concluído, o ID não é autenticado, portanto, pode
ser falso.

As respostas estão no final do capítulo.

13.2 Autenticação de ponta a ponta

Sea conexão do dispositivo à API deve passar por diferentes protocolos, conforme
descrito no capítulo 12, a autenticação de dispositivos na camada de transporte
não é uma opção. No capítulo 12, você aprendeu como proteger solicitações e res-
postas de API de ponta a ponta usando criptografia autenticada comRepresenta-
ção Concisa de Objetos Binários (CBOR) Assinatura e criptografia de objetos(COSE)
ou NaCl's CryptoBox . Esses formatos de mensagem criptografada garantem que
as solicitações não sejam adulteradas, e o servidor da API pode ter certeza de que
a solicitação foi originada do dispositivo que afirma ser. Adicionando um identifi-
cador de dispositivo à mensagem como dados associados. 1 , que você lembrará
do capítulo 6, é autenticado, mas não criptografado, a API pode pesquisar o perfil
do dispositivo para encontrar a chave para descriptografar e autenticar as mensa-
gens desse dispositivo.

Infelizmente, isso não é suficiente para garantir que as solicitações de API real-
mente vieram desse dispositivo, portanto, é perigoso tomar decisões de controle
de acesso com base apenas no código de autenticação de mensagem (MAC) usado
para autenticar a mensagem. O motivo é que as solicitações de API podem ser
capturadas por um invasor e reproduzidas posteriormente para executar a
mesma ação novamente em um momento posterior, conhecido como ataque de
repetição. Por exemplo, suponha que você seja o líder de uma organização ma-
ligna clandestina com a intenção de dominar o mundo. Um dispositivo de monito-
ramento em sua usina de enriquecimento de urânio envia uma solicitação de API
para aumentar a velocidade de uma centrífuga. Infelizmente, o pedido é intercep-
tado por um agente secreto, que repete o pedido centenas de vezes, e a centrífuga
gira muito rápido, causando danos irreparáveis ​e atrasando seus planos covardes
por vários anos.

DEFINIÇÃO Em um ataque de repetição, um invasor captura solicitações de API


genuínas e depois as reproduz novamente para causar ações que não foram in-
tencionadas pelo cliente original. Os ataques de repetição podem causar interrup-
ções, mesmo que a própria mensagem seja autenticada.

Para evitar ataques de repetição, a API precisa garantir que uma solicitação veio
de um cliente legítimo e é recente. A atualização garante que a mensagem seja re-
cente e não tenha sido repetida e é fundamental para a segurança ao tomar deci-
sões de controle de acesso com base na identidade do cliente. O processo de iden-
tificação com quem um servidor de API está se comunicando é conhecido como
autenticação de entidade.

Autenticação de Entidade DE DEFINIÇÃOé o processo de identificação de


quem solicitou a execução de uma operação de API. Embora a autenticação de
mensagempode confirmar quem originalmente criou uma solicitação, a autenti-
cação de entidade também exige que a solicitação seja recente e não tenha sido
repetida. A conexão entre os dois tipos de autenticação pode ser resumida como:
autenticação de entidade = autenticação de mensagem + atualização.

Nos capítulos anteriores, você contou com TLS ou protocolos de autenticação,


como OpenID Connect (OIDC; consulte o capítulo 7) para garantir a atualização,
mas as solicitações de ponta a ponta da API precisam garantir essa propriedade
por si mesmas. Existem três maneiras gerais de garantir o frescor:
As solicitações de API podem incluir carimbos de data/hora que indicam
quando a solicitação foi gerada. O servidor da API pode rejeitar solicitações
muito antigas. Essa é a forma mais fraca de proteção de reprodução porque um
invasor ainda pode reproduzir solicitações até que expirem. Também requer
que o cliente e o servidor tenham acesso a relógios precisos que não podem ser
influenciados por um invasor.
As solicitações podem incluir um nonce exclusivo (número usado uma vez). O
servidor lembra desses nonces e rejeita solicitações que tentam reutilizar um
que já foi visto. Para reduzir os requisitos de armazenamento no servidor, isso
geralmente é combinado com um registro de data e hora, de modo que os non-
ces usados ​só precisam ser lembrados até que a solicitação associada expire.
Em alguns casos, você pode usar um contador de aumento monotônicocomo o
nonce, caso em que o servidor só precisa lembrar o valor mais alto que viu até
agora e rejeitar solicitações que usam um valor menor. Se vários clientes ou
servidores compartilharem a mesma chave, pode ser difícil sincronizar o conta-
dor entre todos eles.
O método mais seguro é usar um protocolo de desafio-respostamostrado na fi-
gura 13.3, em que o servidor gera um valor de desafio aleatório (um nonce) e o
envia ao cliente. O cliente então inclui o valor do desafio na solicitação da API,
comprovando que a solicitação foi gerada após o desafio. Embora mais seguro,
isso adiciona sobrecarga porque o cliente deve falar com o servidor para obter
um desafio antes de enviar qualquer solicitação.
Figura 13.3 Um protocolo de desafio-resposta garante que uma solicitação de API
seja recente e não tenha sido repetida por um invasor. A primeira solicitação de
API do cliente é rejeitada e a API gera um valor de desafio aleatório que envia ao
cliente e armazena localmente. O cliente repete sua solicitação, incluindo uma
resposta ao desafio. O servidor pode então ter certeza de que a solicitação foi ge-
rada recentemente pelo cliente genuíno e não é um ataque de repetição.

DEFINIÇÃO Um contador crescente monotonicamenteé aquele que só aumenta


e nunca retrocede e pode ser usado como um nonce para evitar a repetição de so-
licitações de API. Em um protocolo de desafio-resposta, o servidor gera um desa-
fio aleatório que o cliente inclui em uma solicitação subsequente para garantir a
atualização.

Tanto o TLS quanto o OIDC empregam protocolos de desafio-resposta para auten-


ticação. Por exemplo, no OIDC, o cliente inclui um nonce aleatório na solicitação
de autenticação e o provedor de identidade inclui o mesmo nonce no token de ID
gerado para garantir a atualização. No entanto, em ambos os casos, o desafio é
usado apenas para garantir o frescor de uma solicitação de autenticação inicial e,
a partir daí, outros métodos são usados. No TLS, a resposta do desafio ocorre du-
rante o aperto de mão e, posteriormente, um número de sequência monotonica-
mente crescente é adicionado a cada mensagem. Se um dos lados vir o número de
sequência retroceder, eles abortam a conexão e um novo aperto de mão (e uma
nova resposta de desafio) precisa ser executado. Isso se baseia no fato de que o
TLS é um protocolo stateful entre um único cliente e um único servidor,

Ataques de atrasar, reordenar ou bloquear mensagens

Repetir ataquesnão são a única maneira de um invasor interferir nas solicitações


e respostas da API. Eles também podem bloquear ou atrasar o recebimento de
mensagens, o que pode causar problemas de segurança em alguns casos, além da
simples negação de serviço. Por exemplo, suponha que um cliente legítimo envie
uma solicitação de “desbloqueio” autenticada para um dispositivo de bloqueio de
porta. Se a solicitação incluir um nonce exclusivo ou outro mecanismo descrito
nesta seção, um invasor não poderá reproduzir a solicitação posteriormente. No
entanto, eles podem impedir que a solicitação original seja entregue imediata-
mente e, em seguida, enviá-la para o dispositivo mais tarde, quando o usuário le-
gítimo desistir e for embora. Este não é um ataque de repetição porque a solicita-
ção original nunca foi recebida pela API; em vez disso, o invasor apenas atrasou a
solicitação e a entregou em um momento posterior ao
planejado.http://mng.bz/nzYK descreve uma variedade de ataques contra CoAP
que não violam diretamente as propriedades de segurança de DTLS, TLS ou ou-
tros protocolos de comunicação seguros. Esses exemplos ilustram a importância
de uma boa modelagem de ameaças e do exame cuidadoso das suposições feitas
nas comunicações do dispositivo. Uma variedade de mitigações para CoAP são
descritas em http://mng.bz/v9oM , incluindo uma opção simples de desafio-res-
posta “Echo” que pode ser usada para evitar ataques de atraso, garantindo uma
garantia mais forte de atualização.
13.2.1 OSCORE

ObjetoSegurança para ambientes RESTful restritos (OSCORE; https://tools.ietf


.org/html/rfc8613 ) foi projetado para ser um protocolo de segurança de ponta a
ponta para solicitações de API em ambientes IoT. OSCORE é baseado no uso de
chaves pré-compartilhadas entre o cliente e o servidor e faz uso de CoAP (Constai-
ned Application Protocol) e COSE (CBOR Object Signing and Encryption) para que
algoritmos criptográficos e formatos de mensagem sejam adequados para disposi-
tivos restritos.

OBSERVAÇÃO OSCORE pode ser usado como alternativa aos protocolos de se-
gurança da camada de transporte, como DTLS, ou em adição a eles. As duas abor-
dagens são complementares e a melhor segurança vem da combinação de ambas.
OSCORE não criptografa todas as partes das mensagens que estão sendo trocadas,
então TLS ou DTLS fornecem proteção adicional, enquanto OSCORE garante segu-
rança de ponta a ponta.
Para usar o OSCORE, o cliente e o servidor devem manter uma coleção de estado,
conhecida como contexto de segurança, durante suas interações entre si. O con-
texto de segurança consiste em três partes, mostradas na figura 13.4:

Um Contexto Comum, que descreve os algoritmos criptográficos a serem usados


​e contém um Master Secret (o PSK) e um Master Salt opcional. Estes são usados ​
para derivar chaves e nonces usados ​para criptografar e autenticar mensagens,
como oCommon IV, descrito posteriormente nesta seção.
Um Contexto do Remetente, que contém um ID do remetente, uma chave do re-
metente usada para criptografar as mensagens enviadas por este dispositivo e
um número de sequência do remetente. O número de sequência é um nonce
que começa em zero e é incrementado toda vez que o dispositivo envia uma
mensagem.
Um contexto de destinatário, que contém um ID de Destinatário, uma Chave de
Destinatário e uma Janela de Repetição, que é usada para detectar a repetição
de mensagens recebidas.
Figura 13.4 O contexto OSCORE é mantido pelo cliente e pelo servidor e consiste
em três partes: um contexto comum contém uma chave mestra, um sal mestre e
um componente IV comum. Os contextos de remetente e destinatário são deriva-
dos desse contexto e IDs comuns para o remetente e o destinatário. O contexto no
servidor espelha o do cliente e vice-versa.

AS CHAVES DE AVISO e os nonces são derivados de forma determinística no


OSCORE, portanto, se o mesmo contexto de segurança for usado mais de uma vez,
poderá ocorrer uma reutilização catastrófica do nonce. Os dispositivos devem ar-
mazenar de forma confiável o estado do contexto durante a vida útil da chave
mestra (incluindo as reinicializações do dispositivo) ou então negociar novos pa-
râmetros aleatórios para cada sessão.

DERIVANDO O CONTEXTO

oA ID do remetente e a ID do destinatário são sequências curtas de bytes e geral-


mente podem ter apenas alguns bytes de comprimento, portanto, não podem ser
nomes globalmente exclusivos. Em vez disso, eles são usados ​para distinguir as
duas partes envolvidas na comunicação. Por exemplo, algumas implementações
OSCORE usam um único byte 0 para o cliente e um único byte 1 para o servidor.
Uma string de contexto de ID opcional pode ser incluída no contexto comum, que
pode ser usado para mapear os IDs de remetente e destinatário para identidades
de dispositivos, por exemplo, em uma tabela de pesquisa.

A chave mestra e o sal mestre são combinados usando a função de derivação de


chave HKDF que você usou pela primeira vez no capítulo 11. Anteriormente, você
usou apenas a função HKDF-Expand, mas essa combinação é feita usando o mé-
todo HKDF-Extractque se destina a entradas que não são uniformemente aleató-
rias. O HKDF-Extract é mostrado na Listagem 13.7 e é apenas uma única aplicação
do HMAC usando o Master Salt como chave e a Master Key como entrada. Abra o
arquivo HKDF.java e adicione o extract métodoao código existente.

Listagem 13.7 Extrato HKDF

extração de chave estática pública (byte[] salt, byte[] inputKeyMaterial) ❶


lança GeneralSecurityException {
var hmac = Mac.getInstance("HmacSHA256");
if (sal == nulo) { ❷
sal = new byte[hmac.getMacLength()]; ❷
} ❷
hmac.init(new SecretKeySpec(salt, "HmacSHA256")); ❸
retornar novo SecretKeySpec(hmac.doFinal(inputKeyMaterial), ❸
"HmacSHA256");
}

❶ HKDF-Extract usa um valor de sal aleatório e o material da chave de entrada.

❷ Se um sal não for fornecido, um sal totalmente zero será usado.

❸ O resultado é a saída do HMAC usando o sal como chave e o material da chave


como entrada.

A chave HKDF para OSCORE pode então ser calculada a partir da chave mestra e
do sal mestre da seguinte forma:
var hkdfKey = HKDF.extract(masterSalt, masterKey);

As chaves do remetente e do destinatário são derivadas dessa chave mestre HKDF


usando a função HKDF-Expand do capítulo 10, conforme mostrado na Listagem
13.8. Um argumento de contexto é gerado como uma matriz CBOR, contendo os se-
guintes itens em ordem:

A ID do remetente ou ID do destinatário, dependendo de qual chave está sendo


derivada.
O parâmetro ID Context, se especificado, ou uma matriz de bytes de compri-
mento zero, caso contrário.
O identificador de algoritmo COSE para o algoritmo de criptografia autenticado
que está sendo usado.
A string “Key” codificada como uma string binária CBOR em ASCII.
O tamanho da chave a ser derivada, em bytes.

Isso é passado para o HKDF.expand() métodopara derivar a chave. Crie um novo


arquivo chamado Oscore.java e copie a listagem para ele. Você precisará adicio-
nar as seguintes importações na parte superior do arquivo:

importar COSE.*;
import com.upokecenter.cbor.CBORObject;
importar org.bouncycastle.jce.provider.BouncyCastleProvider;
importar java.nio.*;
importar java.security.*;

Listagem 13.8 Derivando as chaves do remetente e do destinatário


chave estática privada derivaKey(Chave hkdfKey, byte[] id,
byte[] idContext, AlgorithmID coseAlgorithm)
lança GeneralSecurityException {

int keySizeBytes = coseAlgorithm.getKeySize() / 8;


Contexto CBORObject = CBORObject.NewArray(); ❶
context.Add(id); ❶
context.Add(idContext); ❶
context.Add(coseAlgorithm.AsCBOR()); ❶
context.Add(CBORObject.FromObject("Chave")); ❶
context.Add(keySizeBytes); ❶

return HKDF.expand(hkdfKey, context.EncodeToBytes(), ❷


keySizeBytes, "AES"); ❷
}

❶ O contexto é um array CBOR contendo o ID, o contexto do ID, o identificador do al-


goritmo e o tamanho da chave.

❷ HKDF-Expand é usado para derivar a chave da chave mestre HKDF.

O Common IV é derivado quase da mesma forma que as chaves do remetente e do


destinatário, conforme mostrado na Listagem 13.9. O rótulo “IV” é usado em vez
de “Chave” e o comprimento do IV ou nonce usado pelo algoritmo de criptografia
autenticado COSE é usado em vez do tamanho da chave. Por exemplo, o algoritmo
padrão é AES_CCM_16_64_128, que requer um nonce de 13 bytes, então você pas-
saria 13 como ivLength argumento. Como nossa implementação HKDF retorna
um Key objeto, você pode usar o getEncoded() métodopara converter isso nos
bytes brutos necessários para o Common IV. Adicione este método à Oscore clas-
sevocê acabou de criar.

Listagem 13.9 Derivando o IV Comum

byte estático privado[] derivaCommonIV(Chave hkdfKey,


byte[] idContext, AlgorithmID coseAlgorithm, int ivLength)
lança GeneralSecurityException {
Contexto CBORObject = CBORObject.NewArray();
context.Add(novo byte[0]);
context.Add(idContext);
context.Add(coseAlgorithm.AsCBOR());
context.Add(CBORObject.FromObject("IV")); ❶
context.Add(ivLength); ❶

return HKDF.expand(hkdfKey, context.EncodeToBytes(), ❷


ivLength, "dummy").getEncoded(); ❷
}

❶ Use o rótulo "IV" e o comprimento do nonce necessário em bytes.

❶ Use HKDF-Expand, mas retorne os bytes brutos em vez de um objeto Key.

A Listagem 13.10 mostra um exemplo de derivação das chaves do remetente e do


destinatário e Common IV com base no caso de teste do apêndice C da especifica-
ção OSCORE ( https://tools.ietf.org/html/rfc8613#appendix-C.1.1 ). Você pode execu-
tar o código para verificar se obtém as mesmas respostas do RFC. Você pode usar
org.apache.commons.codec .binary.Hex para imprimir as chaves e IV em
hexadecimal para verificar o testesaídas.

AVISO Não use esta chave mestra e sal mestre em um aplicativo real! Novas cha-
ves devem ser geradas para cada dispositivo.

Listagem 13.10 Derivando chaves OSCORE e IV

public static void main(String... args) lança Exception {


var algoritmo = AlgorithmID.AES_CCM_16_64_128; ❶
var masterKey = new byte[] { ❷
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ❷
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 ❷
}; ❷
var masterSalt = new byte[] { ❷
(byte) 0x9e, 0x7c, (byte) 0xa9, 0x22, 0x23, 0x78, ❷
0x63, 0x40 ❷
}; ❷
var hkdfKey = HKDF.extract(masterSalt, masterKey); ❸
var senderId = new byte[0]; ❹
var destinatárioId = new byte[] { 0x01 }; ❹

var senderKey = derivaKey(hkdfKey, senderId, null, algoritmo); ❺


var destinatárioChave = derivaKey(hkdfKey, destinatárioId, null, algoritmo); ❺
var commonIv = derivaCommonIV(hkdfKey, nulo, algoritmo, 13); ❺
}

❶ O algoritmo padrão usado pelo OSCORE


❷ A chave mestra e o sal mestre do caso de teste OSCORE

❸ Obtenha a chave mestra HKDF.

❹ A ID do remetente é uma matriz de bytes vazia e a ID do destinatário é um único


byte de 1.

❺ Derive as chaves e Common IV.

GERANDO NONCES

oCommon IV não é usado diretamente para criptografar dados porque é um valor


fixo, então resultaria imediatamente em vulnerabilidades de reutilização nonce.
Em vez disso, o nonce é derivado de uma combinação do Common IV, o número
de sequência (chamado de Partial IV) e o ID do remetente, conforme mostrado na
Listagem 13.11. Primeiro, o número de sequência é verificado para garantir que
cabe em 5 bytes, e o ID do remetente é verificado para garantir que caberá no res-
tante do IV. Isso coloca restrições significativas no tamanho máximo do ID do re-
metente. Uma matriz binária compactada é gerada consistindo nos seguintes
itens, em ordem:

O comprimento do ID do remetente como um único byte


O próprio ID do remetente, preenchido à esquerda com zero bytes até que seja
6 bytes a menos que o comprimento total do IV
O número de sequência codificado como um inteiro big-endian de 5 bytes

A matriz resultante é então combinada com o Common IV usando XOR bit a bit,
usando o seguinte método:
private static byte[] xor(byte[] xs, byte[] ys) {
for (int i = 0; i < xs.length; ++i) ❶
xs[i] ^= ys[i]; ❶
retornar xs; ❷
}

❶ XOR cada elemento da segunda matriz (ys) no elemento correspondente da pri-


meira matriz (xs).

❷ Retorne o resultado atualizado.

Adicione o xor() métodoe o nonce() métododa listagem 13.11 para a Oscore


classe.

OBSERVAÇÃO Embora o nonce gerado pareça aleatório devido ao XORed com


o IV comum, é na verdade um contador determinístico que muda previsivelmente
à medida que o número de sequência aumenta. A codificação foi projetada para
reduzir o risco de reutilização acidental de nonce.

Listagem 13.11 Derivando o nonce por mensagem

private static byte[] nonce(int ivLength, long sequenceNumber,


byte[] id, byte[] comumIv) {
if (sequenceNumber > (1L << 40)) ❶
throw new IllegalArgumentException( ❶
"Número de sequência muito grande"); ❶
int idLen = ivLength - 6; ❷
if (id.length > idLen) ❷
throw new IllegalArgumentException("ID é muito grande"); ❷

var buffer = ByteBuffer.allocate(ivLength).order(ByteOrder.BIG_ENDIAN);


buffer.put((byte) id.length); ❸
buffer.put(new byte[idLen - id.length]); ❸
buffer.put(id); ❸
buffer.put((byte) ((sequenceNumber >>> 32) & 0xFF)); ❹
buffer.putInt((int) sequenceNumber); ❹
return xor(buffer.array(), commonIv); ❺
}

❶ Verifique se o número de sequência não é muito grande.

❷ Verifique se a ID do remetente cabe no espaço restante.

❸ Codifique o comprimento do Sender ID seguido pelo Sender ID preenchido à es-


querda para 6 menos que o comprimento IV.

❹ Codifique o número de sequência como um inteiro big-endian de 5 bytes.

❺ XOR o resultado com o Common IV para derivar o nonce final.

CRIPTOGRAFANDO UMA MENSAGEM

Uma vezSe você derivou o nonce por mensagem, pode criptografar uma mensa-
gem OSCORE, conforme mostrado na Listagem 13.12, que se baseia no exemplo da
seção C.4 da especificação OSCORE. As mensagens OSCORE são codificadas como
COSE_Encrypt0 estruturas, nas quais não há informações explícitas sobre o des-
tinatário. O IV parcial e o ID do remetente são codificados na mensagem como ca-
beçalhos desprotegidos, com o ID do remetente usando o cabeçalho COSE Key ID
(KID) padrão. Embora marcados como desprotegidos, esses valores são realmente
autenticados porque o OSCORE exige que eles sejam incluídos em um COSE de da-
dos autenticados adicionais externosestrutura, que é um array CBOR com os se-
guintes elementos:

Um número de versão OSCORE, atualmente sempre definido como 1


O identificador do algoritmo COSE
O ID do remetente
A Parcial IV
Uma string de opções. Isso é usado para codificar cabeçalhos CoAP, mas está em
branco neste exemplo.

A estrutura COSE é criptografada com a chave do remetente.

DEFINIÇÃO COSE permite que as mensagens tenham dados autenticados adici-


onais externos, que estão incluídos no código de autenticação da mensagem
(MAC), mas não enviado como parte da própria mensagem. O destinatário deve
ser capaz de recriar independentemente esses dados externos, caso contrário, a
descriptografia falhará.

Listagem 13.12 Criptografando o texto simples

sequência longaNúmero = 20L;


byte[] nonce = nonce(13, sequenceNumber, senderId, commonIv); ❶
byte[] parcialIv = new byte[] { (byte) sequenceNumber }; ❶

var mensagem = new Encrypt0Message();


message.addAttribute(HeaderKeys.Algorithm, ❷
algoritmo.AsCBOR(), Attribute.DO_NOT_SEND); ❷
message.addAttribute(HeaderKeys.IV, ❷
nonce, Attribute.DO_NOT_SEND); ❷
message.addAttribute(HeaderKeys.PARTIAL_IV, ❸
parcialIv, Attribute.UNPROTECTED); ❸
message.addAttribute(HeaderKeys.KID, ❸
senderId, Attribute.UNPROTECTED); ❸
mensagem.SetContent( ❹
novo byte[] { 0x01, (byte) 0xb3, 0x74, 0x76, 0x31}); ❹

varassociatedData = CBORObject.NewArray(); ❺
dadosassociados.Add(1); ❺
dadosassociados.Add(algorithm.AsCBOR()); ❺
associadoData.Add(senderId); ❺associatedData.Add
(partialIv); ❺
dadosassociados.Adicionar(novo byte[0]); ❺
message.setExternal(associatedData.EncodeToBytes()); ❺

Security.addProvider(new BouncyCastleProvider()); ❻
message.encrypt(senderKey.getEncoded()); ❻

❶ Gerar o nonce e codificar o IV parcial.

❷ Configure o algoritmo e o nonce.

❸ Defina Partial IV e Sender ID como cabeçalhos desprotegidos.

❹ Defina o campo de conteúdo para o texto simples a ser criptografado.


❺ Codifique os dados externos associados.

❻ Certifique-se de que o Bouncy Castle esteja carregado para suporte AES-CCM e, em


seguida, criptografe a mensagem.

A mensagem criptografada é então codificada no protocolo do aplicativo, como


CoAP ou HTTP, e enviada ao destinatário. Os detalhes dessa codificação são forne-
cidos na seção 6 da especificação OSCORE. O destinatário pode recriar o nonce de
seu próprio contexto de segurança do destinatário, junto com o IV parcial e a ID
do remetente codificados na mensagem.

O destinatário é responsável por verificar se o Partial IV não foi visto antes para
evitar ataques de repetição. Quando o OSCORE é transmitido por um protocolo
confiável, como HTTP, isso pode ser obtido mantendo o controle do último IV par-
cial recebido e garantindo que todas as novas mensagens sempre usem um nú-
mero maior. Para protocolos não confiáveis, como CoAP sobre UDP, onde as men-
sagens podem chegar fora de ordem, você pode usar o algoritmo do RFC 4303 (
http://mng.bz/4BjV). Essa abordagem mantém uma janela de números de sequên-
cia permitidos entre um valor mínimo e máximo que o destinatário aceitará e re-
gistra explicitamente quais valores nesse intervalo foram recebidos. Se o destina-
tário for um cluster de servidores, como uma API típica hospedada em nuvem,
esse estado deverá ser sincronizado entre todos os servidores para evitar ataques
de repetição. Como alternativa, o balanceamento de carga permanentepode ser
usado para garantir que as solicitações do mesmo dispositivo sejam sempre entre-
gues à mesma instância do servidor, conforme mostrado na figura 13.5, mas isso
pode ser problemático em ambientes onde os servidores são frequentemente adi-
cionados ou removidos. A Seção 13.1.5 discute uma abordagem alternativa para
evitar ataques de repetição que podem ser eficazes paraDESCANSOAPIs.

Figura 13.5 No balanceamento fixo de carga, todas as solicitações de um disposi-


tivo são sempre tratadas pelo mesmo servidor. Isso simplifica o gerenciamento de
estado, mas reduz a escalabilidade e pode causar problemas se esse servidor for
reiniciado ou removido do cluster.
DEFINIÇÃO Balanceamento de carga rígidoé uma configuração compatível
com a maioria dos balanceadores de carga que garante que as solicitações de API
de um dispositivo ou cliente sejam sempre entregues na mesma instância do ser-
vidor. Embora isso possa ajudar com conexões com estado, pode prejudicar a es-
calabilidade e geralmente é desencorajado.

13.2.2 Evitando repetição em APIs REST

Tudoas soluções para a reprodução de mensagens envolvem o cliente e o servidor


mantendo algum estado. No entanto, em alguns casos, você pode evitar a necessi-
dade de estado por cliente para evitar a repetição. Por exemplo, solicitações que
apenas leem dados são inofensivas se repetidas, desde que não exijam processa-
mento significativo no servidor e as respostas sejam mantidas em sigilo. Algumas
solicitações que executam operações também são inofensivas para serem repro-
duzidas se a solicitação for idempotente.

DEFINIÇÃO Uma operação é idempotentese executá-lo várias vezes tem o


mesmo efeito que executá-lo apenas uma vez. As operações idempotentes são im-
portantes para a confiabilidade porque, se uma solicitação falhar devido a um
erro de rede, o cliente poderá repeti-la com segurança.

A especificação HTTP requer que os métodos somente leitura GET, HEAD e OPTI-
ONS, juntamente com as solicitações PUT e DELETE, sejam todos idempotentes.
Apenas os métodos POST e PATCH geralmente não são idempotentes.

ATENÇÃO Mesmo se você se limitar a requisições PUT em vez de POST, isso não
significa que suas requisições estão sempre protegidas contra replay.
O problema é que a definição de idempotência não diz nada sobre o que acontece
se outra solicitação ocorrer entre a solicitação original e a repetição. Por exemplo,
suponha que você envie uma solicitação PUT atualizando uma página em um site,
mas perde sua conexão de rede e não sabe se a solicitação foi bem-sucedida ou
não. Como a solicitação é idempotente, você a envia novamente. Sem que você
saiba, um de seus colegas, entretanto, enviou uma solicitação DELETE porque o
documento continha informações confidenciais que não deveriam ter sido publi-
cadas. Sua solicitação PUT reproduzida chega depois e o documento é ressusci-
tado, com dados confidenciais e tudo. Um invasor pode reproduzir solicitações
para restaurar uma versão antiga de um recurso, mesmo que todas as operações
sejam individualmente idempotentes.

Felizmente, existem vários mecanismos que você pode usar para garantir que ne-
nhuma outra solicitação tenha ocorrido nesse meio tempo. Muitas atualizações de
um recurso seguem o padrão de primeiro ler a versão atual e, em seguida, enviar
uma versão atualizada. Você pode garantir que ninguém alterou o recurso desde
que você o leu usando um dos dois mecanismos HTTP padrão:

O servidor pode retornar um cabeçalho Last-Modified ao ler um recurso que in-


dica a data e a hora em que foi modificado pela última vez. O cliente pode en-
viar um cabeçalho If-Unmodified-Since em sua solicitação de atualização com o
mesmo registro de data e hora. Se o recurso tiver mudado nesse meio tempo, a
solicitação será rejeitada com um status 412 Falha na condição prévia. 2 A prin-
cipal desvantagem dos cabeçalhos Last-Modified é que eles são limitados ao se-
gundo mais próximo, portanto, são incapazes de detectar alterações que ocor-
rem com mais frequência.
Como alternativa, o servidor pode retornar uma ETag (Entity Tag) cabeça-
lhoque deve mudar sempre que o recurso mudar conforme mostrado na figura
13.6. Normalmente, o ETag é um número de versão ou um hash criptográfico do
conteúdo do recurso. O cliente pode então enviar um cabeçalho If-Matches con-
tendo o ETag esperado quando ele executa uma atualização. Se o recurso tiver
mudado nesse meio tempo, o ETag será diferente e o servidor responderá com
um código de status 412 e rejeitará a solicitação.
Figura 13.6 Um cliente pode repetir a reprodução de objetos de solicitação auten-
ticados incluindo um cabeçalho If-Matches com o ETag esperado do recurso. A
atualização modificará o recurso e fará com que o ETag seja alterado, portanto, se
um invasor tentar repetir a solicitação, ele falhará com um erro 412 Precondition
Failed.

AVISO Embora um hash criptográfico possa ser atraente como uma ETag, isso
significa que a ETag reverterá para um valor anterior se o conteúdo o fizer. Isso
permite que um invasor reproduza quaisquer solicitações antigas com uma ETag
correspondente. Você pode evitar isso incluindo um contador ou registro de data
e hora no cálculo da ETag para que a ETag seja sempre diferente, mesmo que o
conteúdo seja o mesmo.

A Listagem 13.13 mostra um exemplo de atualização de um recurso usando um


contador monotônico simples como ETag. Neste caso, você pode usar uma Ato-
micInteger classepara manter o valor ETag atual, usando o compareAnd-
Set método atômicopara incrementar o valor se o cabeçalho If-Matches na solici-
tação corresponder ao valor atual. Alternativamente, você pode armazenar os va-
lores ETag para recursos no banco de dados juntamente com os dados de um re-
curso e atualizá-los em uma transação. Se o cabeçalho If-Matches na solicitação
não corresponder ao valor atual, um cabeçalho 412 Precondition Failed será re-
tornado; caso contrário, o recurso é atualizado e uma nova ETag é retornada.

Listagem 13.13 Usando ETags para evitar repetição

var etag = new AtomicInteger(42);


put("/teste", (solicitação, resposta) -> {
var esperadoEtag = parseInt(request.headers("If-Matches")); ❶

if (!etag.compareAndSet(esperadoEtag, esperadoEtag + 1)) { ❶


response.status(412); ❷
retornar nulo; ❷
} ❷

System.out.println("Atualizando recurso com novo conteúdo: " +


request.body());

resposta.status(200); ❸
response.header("ETag", String.valueOf(expectedEtag + 1)); ❸
response.type("texto/simples");
retornar "OK";
});

❶ Verifique se a ETag atual corresponde à da solicitação.

❷ Caso contrário, retorne uma resposta 412 Falha na condição prévia.

❸ Caso contrário, retorne a nova ETag após atualizar o recurso.

O mecanismo ETag também pode ser usado para impedir a repetição de uma soli-
citação PUT destinada a criar um recurso que ainda não existe. Como o recurso
não existe, não há ETag ou data da última modificação existente para incluir. Um
invasor pode reproduzir essa mensagem para substituir uma versão posterior do
recurso pelo conteúdo original. Para evitar isso, você pode incluir um cabeçalho
If-None-Match com o valor especial * , que informa ao servidor para rejeitar a so-
licitação se houver alguma versão existente desse recurso.

DICA O protocolo de aplicativo restrito(CoAP), geralmente usado para implemen-


tar APIs REST em ambientes restritos, não oferece suporte aos cabeçalhos Last-
Modified ou If-Unmodified-Since, mas oferece suporte a ETags junto com If-Mat-
ches e If-None-Match. No CoAP, os cabeçalhos são conhecidos como opções.

CODIFICAÇÃO DE CABEÇALHOS COM SEGURANÇA DE PONTA A PONTA

Comoexplicado no capítulo 12, em um aplicativo IoT de ponta a ponta, um dispo-


sitivo pode não ser capaz de se comunicar diretamente com a API em HTTP (ou
CoAP), mas deve passar uma mensagem autenticada por vários proxies interme-
diários. Mesmo que cada proxy suporte HTTP, o cliente pode não confiar nesses
proxies para não interferir na mensagem se não houver uma conexão TLS de
ponta a ponta. A solução é codificar os cabeçalhos HTTP junto com os dados da so-
licitação em um objeto de solicitação criptografado, conforme mostrado na Lista-
gem 13.14.

DEFINIÇÃO Um objeto de solicitaçãoé uma solicitação de API encapsulada


como um único objeto de dados que pode ser criptografado e autenticado como
um elemento. O objeto de solicitação captura os dados na solicitação, bem como
os cabeçalhos e outros metadados exigidos pela solicitação.

Neste exemplo, os cabeçalhos são codificados como um mapa CBOR, que é então
combinado com o corpo da solicitação e uma indicação do método HTTP esperado
para criar o objeto geral da solicitação. Todo o objeto é então criptografado e au-
tenticado usando NaCl's CryptoBox funcionalidade. O OSCORE, discutido na se-
ção 13.1.4, é um exemplo de protocolo fim-a-fim usando objetos de requisição. Os
objetos de requisição no OSCORE são mensagens CoAP criptografadas com COSE.

DICA O código fonte completo para este exemplo é fornecido no repositório


GitHub que acompanha o livro em http://mng.bz/QxWj .
Listagem 13.14 Codificando cabeçalhos HTTP em um objeto de solicitação

var revisionEtag = "42"; ❶


var headers = CBORObject.NewMap() ❶
.Add("If-Matches", revisionEtag); ❶
var corpo = CBORObject.NewMap()
.Add("foo", "barra")
.Add("dados", 12345);
var pedido = CBORObject.NewMap()
.Add("método", "PUT") ❷
.Add("cabeçalhos", cabeçalhos) ❷
.Add("corpo", corpo); ❷
var sent = CryptoBox.encrypt(clientKeys.getPrivate(), ❸
serverKeys.getPublic(), request.EncodeToBytes()); ❸

❶ Codifique quaisquer cabeçalhos HTTP necessários em CBOR.

❷ Codifique os cabeçalhos e o corpo, juntamente com o método HTTP, como um


único objeto.

❸ Criptografe e autentique todo o objeto de solicitação.

Para validar a solicitação, o servidor da API deve descriptografar o objeto de soli-


citação e verificar se os cabeçalhos e o método de solicitação HTTP correspondem
aos especificados no objeto. Se não corresponderem, a solicitação deve ser rejei-
tada como inválida.

CUIDADO Você sempre deve garantir que os cabeçalhos reais da solicitação


HTTP correspondam ao objeto da solicitação, em vez de substituí-los. Caso contrá-
rio, um invasor pode usar o objeto de solicitação para contornar a filtragem de se-
gurança executada por Web Application Firewalls e outros controles de segu-
rança. Você nunca deve permitir que um objeto de solicitação altere o método
HTTP porque muitas verificações de segurança em navegadores da Web depen-
dem dele.

A Listagem 13.15 mostra como validar um objeto de solicitação em um filtro para


a estrutura Spark HTTP que você usou nos capítulos anteriores. O objeto de solici-
tação é descriptografado usando NaCl. Como essa é uma criptografia autenticada,
o processo de descriptografia falhará se a solicitação for falsificada ou adulterada.
Você deve verificar se o método HTTP da solicitação corresponde ao método in-
cluído no objeto de solicitação e se todos os cabeçalhos listados no objeto de solici-
tação estão presentes com os valores esperados. Se algum detalhe não correspon-
der, você deve rejeitar a solicitação com um código de erro e uma mensagem
apropriados. Por fim, se todas as verificações forem aprovadas, você poderá ar-
mazenar o corpo da solicitação descriptografada em um atributo para que possa
ser facilmente recuperado sem a necessidade de descriptografaramensagemnova-
mente.

Listagem 13.15 Validando um objeto de solicitação

before((pedido, resposta) -> {


varcryptedRequest = CryptoBox.fromString(request.body()); ❶
var descriptografado = criptografadoRequest.decrypt( ❶
serverKeys.getPrivate(), clientKeys.getPublic()); ❶
var cbor = CBORObject.DecodeFromBytes(descriptografado); ❶
if (!cbor.get("método").AsString() ❷
.equals(request.requestMethod())) { ❷
halt(403); ❷
} ❷

var esperadoCabeçalhos = cbor.get("cabeçalhos"); ❸


for (var headerName : esperadoHeaders.getKeys()) { ❸
if (!expectedHeaders.get(headerName).AsString() ❸
.equals(request.headers(headerName.AsString()))) { ❸
halt(403); ❸
} ❸
} ❸

request.attribute("decryptedRequest", cbor.get("body")); ❹
});

❶ Descriptografar o objeto de solicitação e decodificá-lo.

❷ Verifique se o método HTTP corresponde ao objeto da solicitação.

❸ Verifique se todos os cabeçalhos no objeto de solicitação têm os valores esperados.

❹ Se todas as verificações forem aprovadas, armazene o corpo da solicitação


descriptografada.

questionário

3. A autenticação de entidade requer qual propriedade adicional sobre a autenti-


cação de mensagem?
1. Imprecisão
2. Friskiness
3. Funkiness
4. Frescor
4. Quais das opções a seguir são formas de garantir a atualização da autentica-
ção? (Existem várias respostas corretas.)
1. Desodorante
2. Carimbos de data/hora
3. Nonces únicos
4. Protocolos de desafio-resposta
5. Códigos de autenticação de mensagem
5. Qual cabeçalho HTTP é usado para garantir que o ETag de um recurso corres-
ponda a um valor esperado?
1. Se-Correspondências
2. Cache-Control
3. Se-nenhuma-correspondências
4. Se-Inalterado-Desde

As respostas estão no final do capítulo.

13.3 OAuth2 para ambientes restritos

Por todo Neste livro, o OAuth2 surgiu repetidamente como uma abordagem co-
mum para proteger APIs em muitos ambientes diferentes. O que começou como
uma maneira de fazer autorização delegada em aplicativos da Web tradicionais se
expandiu para abranger aplicativos móveis, APIs de serviço a serviço e microsser-
viços. Portanto, não deve surpreender que também esteja sendo aplicado para
proteger APIs na IoT. É especialmente adequado para aplicações de IoT do consu-
midor em casa. Por exemplo, uma smart TV pode permitir que os usuários façam
login em serviços de streaming para assistir a filmes ou ouvir música ou visuali-
zar atualizações de fluxos de mídia social. Estes são adequados para OAuth2, por-
que envolvem um ser humano que delega parte de sua autoridade a um disposi-
tivo para uma finalidade bem definida.

DEFINIÇÃO Uma smart TV(ou TV conectada) é uma televisão capaz de acessar


serviços pela Internet, como streaming de música ou vídeo ou APIs de mídia so-
cial. Muitos outros dispositivos de entretenimento doméstico agora também são
capazes de acessar a Internet e as APIs estão impulsionando essa transformação.

Mas as abordagens tradicionais para obter autorização podem ser difíceis de usar
em um ambiente IoT por vários motivos:

O dispositivo pode não ter uma tela, teclado ou outros recursos necessários
para permitir que um usuário interaja com o servidor de autorização para
aprovar o consentimento. Mesmo em um dispositivo mais capaz, como uma
smart TV, digitar nomes de usuário longos ou senhas em um pequeno controle
remoto pode ser demorado e irritante para os usuários. A Seção 13.2.1 discute a
concessão de autorização do dispositivoque visa resolver este problema.
Os formatos de token e os mecanismos de segurança usados ​pelos servidores de
autorização geralmente são fortemente focados em clientes de navegador da
web ou aplicativos móveis e não são adequados para dispositivos mais restri-
tos. A estrutura ACE-OAuth discutida na seção 13.2.2 é uma tentativa de adaptar
o OAuth2 para tais ambientes restritos.
DEFINIÇÃO ACE-OAuth (Autorização para Ambientes Restritos usando OAuth2)
é uma especificação de estrutura que adapta OAuth2 para dispositivos restritos.

13.3.1 A concessão de autorização do dispositivo

oA concessão de autorização de dispositivo OAuth2 (RFC 8628,


https://tools.ietf.org/html/rfc8628 ) permite que dispositivos sem recursos normais
de entrada e saída obtenham tokens de acesso dos usuários. Nos fluxos OAuth2
normais discutidos no capítulo 7, o cliente OAuth2 redirecionaria o usuário para
uma página da Web no servidor de autorização(AS), onde eles podem fazer login
e aprovar o acesso. Isso não é possível em muitos dispositivos IoT porque eles não
têm tela para mostrar um navegador da Web e nenhum teclado, mouse ou tela
sensível ao toque para permitir que o usuário insira seus detalhes. A concessão de
autorização do dispositivo ou fluxo do dispositivocomo costuma ser chamado, re-
solve esse problema permitindo que o usuário conclua a autorização em um se-
gundo dispositivo, como um laptop ou telefone celular. A Figura 13.7 mostra o
fluxo geral, que é descrito com mais detalhes no restante desta seção.
Figura 13.7 Na concessão de autorização de dispositivo OAuth2, o dispositivo pri-
meiro chama um endpoint no AS para iniciar o fluxo e recebe um código de dispo-
sitivo e um código curto de usuário. O dispositivo solicita ao usuário que navegue
até o AS em um dispositivo separado, como um smartphone. Após a autenticação
do usuário, ele digita o código do usuário e aprova a solicitação. O dispositivo pes-
quisa o AS em segundo plano usando o código do dispositivo até que o fluxo seja
concluído. Se o usuário aprovar a solicitação, o dispositivo receberá um token de
acesso na próxima vez que consultar o AS.

Para iniciar o fluxo, o dispositivo primeiro faz uma solicitação POST para um
novo endpoint de autorização de dispositivo no AS, indicando o escopo do token
de acesso necessário e autenticando usando suas credenciais de cliente. O AS re-
torna três detalhes na resposta:

Um código de dispositivo, que é um pouco como um código de autorização do


capítulo 7 e eventualmente será trocado por um token de acesso após o usuário
autorizar a solicitação. Isso é tipicamente uma string aleatória impossível de
adivinhar.
Um código de usuário, que é um código mais curto projetado para ser inserido
manualmente pelo usuário quando ele aprova a solicitação de autorização.
Um URI de verificaçãoonde o usuário deve ir para digitar o código do usuário
para aprovar a solicitação. Isso normalmente será um URI curto se o usuário ti-
ver que digitá-lo manualmente em outro dispositivo.

A Listagem 13.16 mostra como iniciar uma solicitação de autorização de conces-


são de dispositivo a partir de Java. Neste exemplo, o dispositivo é um cliente pú-
blico e você só precisa fornecer o client_id e scope parâmetrosno pedido. Se o
seu dispositivo for um cliente confidencial, você também precisará fornecer as
credenciais do cliente usando a autenticação básica HTTP ou outro método de au-
tenticação do cliente suportado pelo seu AS. Os parâmetros são codificados por
URL como são para outras solicitações OAuth2. O AS retorna uma resposta 200 OK
se a solicitação for bem-sucedida, com o código do dispositivo, código do usuário e
URI de verificação no formato JSON. Navegue até
src/main/java/com/manning/apisecurityinaction e crie um novo arquivo chamado
DeviceGrantClient.java. Crie uma nova classe pública no arquivo com o mesmo
nome e adicione o método da listagem 13.16 ao arquivo. Você precisará das se-
guintes importações na parte superior do arquivo:

import org.json.JSONObject;
importar java.net.*;
importar java.net.http.*;
importar java.net.http.HttpRequest.BodyPublishers;
importar java.net.http.HttpResponse.BodyHandlers;
importar java.util.concurrent.TimeUnit;
importar estático java.nio.charset.StandardCharsets.UTF_8;

Listagem 13.16 Iniciando um fluxo de concessão de autorização de dispositivo

private static final HttpClient httpClient = HttpClient.newHttpClient();

private static JSONObject beginDeviceAuthorization(


String clientId, escopo da string) lança Exception {
var form = "client_id=" + URLEncoder.encode(clientId, UTF_8) + ❶
"&scope=" + URLEncoder.encode(scope, UTF_8); ❶
var request = HttpRequest.newBuilder() ❶
.header("Content-Type", ❶
"application/x-www-form-urlencoded") ❶
.uri(URI.create( ❶
"https://as.example. com/device_authorization")) ❶
.POST(BodyPublishers.ofString(form)) ❶
.construir(); ❶
var response = httpClient.send(request, BodyHandlers.ofString()); ❶

if (response.statusCode() != 200) { ❷
throw new RuntimeException("Resposta incorreta do AS: " + ❷
response.body()); ❷
} ❷
return new JSONObject(response.body()); ❸
}

❶ Codifique o ID do cliente e o escopo como parâmetros de formulário e envie-os


para o endpoint do dispositivo.

❷ Se a resposta não for 200 OK, ocorreu um erro.

❸ Caso contrário, analise a resposta como JSON.

O dispositivo que iniciou o fluxo comunica o URI de verificação e o código do


usuário ao usuário, mas mantém o código do dispositivo em segredo. Por exem-
plo, o dispositivo pode exibir um código QR (figura 13.8) que o usuário pode esca-
near em seu telefone para abrir o URI de verificação ou o dispositivo pode se co-
municar diretamente com o telefone do usuário por meio de uma conexão Blueto-
oth local. Para aprovar a autorização, o usuário abre o URI de verificação em seu
outro dispositivo e faz login. Em seguida, ele digita o código do usuário e pode
aprovar ou negar a solicitação depois de ver os detalhes dos escopos solicitados.
Figura 13.8 Um código QR é uma forma de codificar um URI que pode ser facil-
mente escaneado por um telefone celular com uma câmera. Isso pode ser usado
para exibir o URI de verificação usado na concessão de autorização do dispositivo
OAuth2. Se você digitalizar este código QR em seu telefone, ele o levará à página
inicial deste livro.

DICA O AS também pode retornar um verification_uri_complete cam-


poque combina o URI de verificação com o código do usuário. Isso permite que o
usuário apenas siga o link sem precisar digitar manualmente o código.

O dispositivo original que solicitou autorização não é notificado de que o fluxo foi
concluído. Em vez disso, ele deve pesquisar periodicamente o endpoint do token
de acesso no AS, passando o código do dispositivo recebido na solicitação inicial,
conforme mostrado na Listagem 13.17. Este é o mesmo endpoint de token de
acesso usado nos outros tipos de concessão OAuth2 discutidos no capítulo 7, mas
você define o grant_type parâmetropara

urn:ietf:params:oauth:grant-type:device_code

para indicar que a concessão de autorização do dispositivo está sendo usada. O


cliente também inclui seu ID de cliente e o próprio código do dispositivo. Se o cli-
ente for confidencial, ele também deverá autenticar usando suas credenciais de
cliente, mas este exemplo está usando um cliente público. Abra o arquivo
DeviceGrantClient.java novamente e inclua o método da listagem a seguir.

Listagem 13.17 Verificando o status da solicitação de autorização

private static JSONObject pollAccessTokenEndpoint(


String clientId, String deviceCode) gera exceção {
var form = "client_id=" + URLEncoder.encode(clientId, UTF_8) + ❶
"&grant_type=urn:ietf:params:oauth:grant-type:device_code" + ❶
"&device_code=" + URLEncoder.encode(deviceCode, UTF_8) ; ❶

var request = HttpRequest.newBuilder() ❷


.header("Content-Type", ❷
"application/x-www-form-urlencoded") ❷
.uri(URI.create("https://as.example.com/ access_token")) ❷
.POST(BodyPublishers.ofString(form)) ❷
.build(); ❷
var resposta = httpClient.send(request, BodyHandlers.ofString()); ❷
return new JSONObject(response.body()); ❸
}
❶ Codifique o ID do cliente e o código do dispositivo junto com o URI do tipo de con-
cessão device_code.

❷ Publique os parâmetros no endpoint do token de acesso no AS.

❸ Analise a resposta como JSON.

Se o usuário já tiver aprovado a solicitação, o AS retornará um token de acesso,


um token de atualização opcional e outros detalhes, como acontece com outras so-
licitações de token de acesso que você aprendeu no capítulo 7. Caso contrário, o
AS retornará um dos seguintes status códigos:

authorization_pending indica que o usuário ainda não aprovou ou negou a


solicitação e o dispositivo deve tentar novamente mais tarde.
slow_down indica que o dispositivo está pesquisando o endpoint de autoriza-
ção com muita frequência e deve aumentar o intervalo entre as solicitações em
5 segundos. Um AS pode revogar a autorização se o dispositivo ignorar esse có-
digo e continuar a pesquisar com muita frequência.
access_denied indica que o usuário recusou a solicitação.
expired_token indica que o código do dispositivo expirou sem que a solicita-
ção tenha sido aprovada ou negada. O dispositivo terá que iniciar um novo
fluxo para obter um novo código de dispositivo e código de usuário.

A Listagem 13.18 mostra como lidar com o fluxo de autorização total no cliente
com base nos métodos anteriores. Abra o arquivo DeviceGrantClient.java nova-
mente e adicione o método principal dolistagem.
DICA Se você quiser testar o cliente, o ForgeRock Access Management(AM) o pro-
duto oferece suporte à concessão de autorização do dispositivo. Siga as instruções
no apêndice A para configurar o servidor e depois as instruções em http://mng.bz/
X0W6 para configurar a concessão de autorização do dispositivo. AM implementa
uma versão de rascunho mais antiga do padrão e requer um
response_type=device _code parâmetro extrana solicitação inicial para ini-
ciar o fluxo.

Listagem 13.18 O fluxo de concessão de autorização de dispositivo completo

public static void main(String... args) lança Exception {


var clientId = "deviceGrantTest";
var escopo = "ab c";

var json = beginDeviceAuthorization(clientId, escopo); ❶


var deviceCode = json.getString("device_code"); ❶
var intervalo = json.optInt("intervalo", 5); ❶
System.out.println("Por favor abra " + ❷
json.getString("verification_uri")); ❷
System.out.println("E digite o código:\n\t" + ❷
json.getString("user_code")); ❷

while (true) { ❸
Thread.sleep(TimeUnit.SECONDS.toMillis(interval)); ❸
json = pollAccessTokenEndpoint(clientId, deviceCode); ❸
var erro = json.optString("erro", nulo);
if (erro != nulo) {
alternar (erro) {
case "slow_down": ❹
System.out.println("Slowing down"); ❹
intervalo += 5; ❹
parar;
case "authorization_pending": ❺
System.out.println("Ainda esperando!"); ❺
parar;
predefinição:
System.err.println("Falha na autorização: " + erro);
System.exit(1);
parar;
}
} senão {
System.out.println("Token de acesso: " + ❻
json.getString("access_token")); ❻
parar;
}
}
}

❶ Inicie o processo de autorização e armazene o código do dispositivo e o intervalo


de pesquisa.

❷ Exiba o URI de verificação e o código do usuário para o usuário.

❸ Pesquise o endpoint do token de acesso com o código do dispositivo de acordo com


o intervalo de votação.
❹ Se o AS disser para diminuir a velocidade, aumente o intervalo de pesquisa em 5
segundos.

❺ Caso contrário, continue esperando até que uma resposta seja recebida.

❻ O AS retornará um token de acesso quando a autorização for concluída.

13.3.2 ACE-OAuth

O grupo de trabalho de autorização para ambientes restritos (ACE) no IETF está


trabalhando para adaptar o OAuth2 para aplicativos de IoT. A principal saída
desse grupo é a definição da estrutura ACE-OAuth ( http://mng.bz/yr4q ), que des-
creve como executar solicitações de autorização OAuth2 por CoAP em vez de
HTTP e usando CBOR em vez de JSON para solicitações e respostas . COSE é usado
como um formato padrão para tokens de acesso e também pode ser usado como
prova de posse(PoP) esquema para proteger tokens contra roubo (consulte a seção
11.4.6 para uma discussão sobre tokens PoP). O COSE também pode ser usado
para proteger as próprias solicitações e respostas da API, usando a estrutura OS-
CORE que você viu na seção 13.1.4.

No momento da redação deste artigo, as especificações ACE-OAuth ainda estão em


desenvolvimento, mas estão se aproximando da publicação como padrões. A es-
trutura principal descreve como adaptar solicitações e respostas OAuth2 para
usar CBOR, incluindo suporte para o código de autorização, credenciais do cliente
e concessões de token de atualização. 3 O endpoint de introspecção de token tam-
bém é suportado, usando CBOR sobre CoAP, fornecendo uma maneira padrão
para os servidores de recursos verificarem o status de um token de acesso.
Ao contrário do OAuth2 original, que usava tokens de portador exclusivamente e
só recentemente começou a oferecer suporte à prova de posse (PoP), o ACE-OAuth
foi projetado em torno do PoP desde o início. Os tokens de acesso emitidos estão
vinculados a uma chave criptográfica e só podem ser usados ​por um cliente que
possa provar a posse dessa chave. Isso pode ser feito com criptografia de chave
pública ou simétrica, fornecendo suporte para uma ampla gama de recursos de
dispositivos. As APIs podem descobrir a chave associada a um dispositivo por
meio de introspecção de token ou examinando o próprio token de acesso, que ge-
ralmente está no formato CWT. Quando a criptografia de chave pública é usada, o
token conterá a chave pública do cliente, enquanto para a criptografia de chave
simétrica, a chave secreta estará presente na forma criptografada por COSE, con-
forme descrito no RFC 8747 ( https://datatracker.ietf.org/doc/html/rfc8747 ).

13.4 Controle de acesso off-line

MuitosOs aplicativos de IoT envolvem dispositivos operando em ambientes onde


eles podem não ter uma conexão permanente ou confiável com os serviços de au-
torização central. Por exemplo, um carro conectado pode ser conduzido por lon-
gos túneis ou para locais remotos onde não há sinal. Outros dispositivos podem
ter bateria limitada e, portanto, devem evitar solicitações de rede frequentes. Ge-
ralmente não é aceitável que um dispositivo pare completamente de funcionar
nesse caso, então você precisa de uma maneira de realizar verificações de segu-
rança enquanto o dispositivo está desconectado. Isso é conhecido como autoriza-
ção off-line. A autorização off-line permite que os dispositivos continuem acei-
tando e produzindo solicitações de API para outros dispositivos e usuários locais
até que a conexão seja restaurada.
DEFINIÇÃO A autorização off-line permite que um dispositivo tome decisões de
segurança local quando é desconectado de um servidor de autorização central.

Permitir a autorização off-line geralmente apresenta riscos maiores. Por exemplo,


se um dispositivo não puder verificar com um servidor de autorização OAuth2 se
um token de acesso é válido, ele poderá aceitar um token que foi revogado. Esse
risco deve ser equilibrado com os custos do tempo de inatividade se os dispositi-
vos estiverem off-line e o nível apropriado de risco determinado para seu aplica-
tivo. Você pode querer aplicar limites a quais operações podem ser executadas no
modo offline ou impor um limite de tempo para quanto tempo os dispositivos
operarão em um estado desconectado.

13.4.1 Autenticação de usuário offline

Algumos dispositivos podem nunca precisar interagir com um usuário, mas para
alguns aplicativos de IoT essa é uma preocupação principal. Por exemplo, muitas
empresas agora operam armários inteligentes onde as mercadorias encomenda-
das online podem ser entregues para coleta posterior. O usuário chega mais tarde
e usa um aplicativo em seu smartphone para enviar uma solicitação para abrir o
armário. Os dispositivos usados ​em implantações industriais de IoT podem funci-
onar de forma autônoma na maior parte do tempo, mas ocasionalmente precisam
de manutenção por um técnico humano. Seria frustrante para o usuário se ele
não pudesse obter sua última compra porque o armário não pode se conectar a
um serviço de nuvem para autenticá-lo, e um técnico geralmente só está envol-
vido quando algo deu errado, então você não deve presumir que os serviços de
rede estarão disponíveis nesta situação.
A solução é disponibilizar as credenciais do usuário para o dispositivo para que
ele possa autenticar o usuário localmente. Isso não significa que o hash da senha
do usuário deva ser transmitido ao dispositivo, pois isso seria muito perigoso: um
invasor que interceptasse o hash poderia realizar um ataque de dicionário off-
line para tentar recuperar a senha. Pior ainda, se o invasor comprometer o dispo-
sitivo, ele poderá simplesmente interceptar a senha diretamente enquanto o
usuário a digita. Em vez disso, a credencial deve ser de curta duração e limitada
apenas às operações necessárias para acessar esse dispositivo. Por exemplo, um
usuário pode receber um código único que pode ser exibido em seu smartphone
como um código QR que o armário inteligente pode escanear. O mesmo código é
hash e enviado para o dispositivo, que pode comparar o hash com o código QR e,
se forem iguais, abre o armário,
Figura 13.9 Códigos únicos podem ser enviados periodicamente para um disposi-
tivo IoT, como um armário seguro. Um hash seguro do código é armazenado local-
mente, permitindo que o armário autentique os usuários, mesmo que não consiga
entrar em contato com o serviço de nuvem naquele momento.

Para que essa abordagem funcione, o dispositivo deve estar online periodica-
mente para baixar novas credenciais. Um formato de token autocontido e assi-
nado pode superar esse problema. Antes de sair para atender um dispositivo em
campo, o técnico pode se autenticar em um servidor de autorização central e re-
ceber um token de acesso OAuth2 ou token OpenID Connect ID. Esse token pode
incluir uma chave pública ou uma credencial temporária que pode ser usada para
autenticar localmente o usuário. Por exemplo, o token pode ser vinculado a um
certificado de cliente TLS, conforme descrito no capítulo 11, ou a uma chave
usando os tokens CWT PoP mencionados na seção 13.3.2. Quando o técnico chega
para atender o dispositivo, ele pode apresentar o token de acesso para acessar as
APIs do dispositivo por meio de uma conexão local, como Bluetooth Low
Energy(BLE). A API do dispositivo pode verificar a assinatura no token de acesso e
verificar o escopo, emissor, público, tempo de expiração e outros detalhes. Se o to-
ken for válido, as credenciais incorporadas podem ser usadas para autenticar o
usuário localmente para permitir o acesso de acordo com as condições anexadas
aosímbolo.

13.4.2 Autorização off-line

desligadaA autenticação resolve o problema de identificar usuários sem uma co-


nexão direta com um serviço de autenticação central. Em muitos casos, as deci-
sões de controle de acesso ao dispositivo são simples o suficiente para serem codi-
ficadas com base em relações de confiança pré-existentes. Por exemplo, um dispo-
sitivo pode permitir acesso total a qualquer usuário que tenha uma credencial
emitida por uma fonte confiável e negar acesso a todos os outros. Mas nem todas
as políticas de controle de acesso são tão simples e o acesso pode depender de
uma série de fatores dinâmicos e condições variáveis. A atualização de políticas
complexas para dispositivos individuais torna-se difícil à medida que o número
de dispositivos aumenta. Como você aprendeu no capítulo 8, as políticas de con-
trole de acesso podem ser centralizadas usando um mecanismo de política aces-
sado por meio de sua própria API. Isso simplifica o gerenciamento de políticas de
dispositivo, mas, novamente, pode levar a problemas se o dispositivo estiver
offline.

As soluções são semelhantes às soluções para autenticação offline descritas na úl-


tima seção. A solução mais básica é o dispositivo baixar periodicamente as políti-
cas mais recentes em um formato padrão como XACML, discutido no capítulo 8. O
dispositivo pode, então, tomar decisões de controle de acesso local de acordo com
as políticas. XACML é um formato complexo baseado em XML, então você pode
querer considerar uma linguagem de política mais leve codificada em CBOR ou
outro formato compacto, mas não tenho conhecimento de nenhum padrão para
tal linguagem.

Formatos de token de acesso independentes também podem ser usados ​para per-
mitir a autorização off-line. Um exemplo simples é o escopo incluído em um token
de acesso, que permite que um dispositivo off-line determine quais operações de
API um cliente deve ter permissão para chamar. Condições mais complexas po-
dem ser codificadas como advertências usando um formato de token de biscoito,
discutido no capítulo 9. Suponha que você tenha usado seu smartphone para re-
servar um carro alugado. Um token de acesso em formato macaroon é enviado
para o seu telefone, permitindo que você desbloqueie o carro transmitindo o to-
ken para o carro via BLE, como no exemplo no final da seção 13.4.1. Mais tarde,
você dirige o carro para um evento noturno em um hotel de luxo em um local iso-
lado, sem cobertura de rede celular. O hotel oferece estacionamento com mano-
brista, mas você não confia no atendente, então você só quer permitir que eles te-
nham uma capacidade limitada de dirigir o carro caro que você alugou. Como seu
token de acesso é um biscoito, você pode simplesmente anexar advertências a ele,
restringindo o token a expirar em 10 minutos e permitir que o carro seja condu-
zido apenas em um raio de um quarto de milha do hotel.

Os macaroons são uma ótima solução para autorização off-line, pois as advertên-
cias podem ser adicionadas por dispositivos a qualquer momento sem qualquer
coordenação e podem ser verificadas localmente por dispositivos sem a necessi-
dade de entrar em contato com um serviço central. As advertências de terceiros
também podem funcionar bem em um aplicativo IoT, porque exigem que o cliente
obtenha prova de autorização da API de terceiros. Essa autorização pode ser ob-
tida antecipadamente pelo cliente e posteriormente verificada pelo dispositivo,
verificando a descarga do macaroon, sem a necessidade de contato direto com o
terceiro.

questionário

6. Qual concessão de autorização OAuth pode ser usada em dispositivos que não
possuem recursos de entrada do usuário?
1. A concessão de credenciais do cliente
2. A concessão do código de autorização
3. A concessão de autorização do dispositivo
4. A concessão de senha do proprietário do recurso
A resposta está no final do capítulo.

Respostas para perguntas do questionário

1. Falso. O PSK pode ser qualquer sequência de bytes e pode não ser uma string
válida.
2. d. o ID é autenticado durante o aperto de mão, portanto, você só deve confiar
nele após a conclusão do aperto de mão.
3. d. A autenticação de entidade requer que as mensagens sejam novas e não te-
nham sido reproduzidas.
4. b, c e d.
5. uma.
6. c. O dispositivoautorizaçãoconceder.

Resumo

Os dispositivos podem ser identificados usando credenciais associadas a um


perfil de dispositivo. Essas credenciais podem ser uma chave pré-comparti-
lhada criptografada ou um certificado contendo uma chave pública para o
dispositivo.
A autenticação do dispositivo pode ser feita na camada de transporte, usando
recursos em TLS, DTLS ou outros protocolos seguros. Se não houver conexão se-
gura de ponta a ponta, você precisará implementar seu próprio protocolo de
autenticação.
A autenticação de dispositivo de ponta a ponta deve garantir a atualização para
evitar ataques de repetição. A atualização pode ser alcançada com carimbos de
data/hora, nonces ou protocolos de resposta de desafio. Impedir a reprodução
requer o armazenamento do estado por dispositivo, como um contador cres-
cente monotonicamente ou nonces usados ​recentemente.
As APIs REST podem impedir a reprodução fazendo uso de objetos de solicita-
ção autenticados que contêm uma ETag que identifica uma versão específica do
recurso que está sendo usado. A ETag deve mudar sempre que o recurso for al-
terado para evitar a repetição de solicitações anteriores.
A concessão de dispositivo OAuth2 pode ser usada por dispositivos sem capaci-
dade de entrada para obter tokens de acesso autorizados por um usuário. O
grupo de trabalho ACE-OAuth no IETF está desenvolvendo especificações que
adaptam o OAuth2 para uso em ambientes restritos.
Os dispositivos nem sempre podem se conectar aos serviços de nuvem centrais.
A autenticação offline e o controle de acesso permitem que os dispositivos con-
tinuem a operar com segurança quando desconectados. Os formatos de token
independentes podem incluir credenciais e políticas para garantir que a autori-
dade não seja excedida, e as restrições de prova de posse (PoP) podem ser usa-
das para fornecer segurança mais fortegarantias.

1.
Uma das poucas desvantagens do NaCl CryptoBox e SecretBox das APIs é que
eles não permitem dados associados autenticados.

2.
Se o servidor puder determinar que o estado atual do recurso corresponde ao es-
tado solicitado, ele também poderá retornar um código de status de sucesso como
se a solicitação fosse bem-sucedida neste caso. Mas, neste caso, a solicitação é re-
almente idempotente de qualquer maneira.

3.
Estranhamente, a concessão de autorização do dispositivo ainda não é suportada.

Você também pode gostar