Garantir a confidencialidade de dados e código em imagens de contêiner
Nos últimos anos, o setor de nuvem viu uma grande mudança da implantação de aplicativos monolíticos em máquinas virtuais para dividir aplicativos em componentes menores (microsserviços) e empacotá-los em contêineres. A popularidade da conteinerização hoje se deve em grande parte ao trabalho do Docker. Docker é a empresa que se tornou a principal força motriz por trás dos contêineres: ela forneceu uma ferramenta fácil de usar para construir e executar contêineres Docker e um registro de contêiner Docker para o desafio da distribuição.
O sucesso da tecnologia de conteinerização depende principalmente da segurança dos contêineres em vários estágios de seu ciclo de vida. Uma das preocupações de segurança é a presença de vulnerabilidades dentro de contêineres individuais. Para identificá-los, os pipelines DevOps usados para criar contêineres são complementados com scanners que procuram pacotes com possíveis vulnerabilidades em contêineres e alertam seus proprietários ou técnicos se forem encontrados. O Vulnerability Advisor on IBM Cloud é um exemplo de tal utilitário.
Outro aspecto da segurança é certificar-se de que o contêiner que está sendo iniciado é o que você deseja e que não foi modificado. Esse problema é solucionado com o uso de assinaturas digitais armazenadas no Notário, que protegerão os contêineres de quaisquer modificações.O Docker Notary é um exemplo de repositório público que armazena assinaturas de imagens. Usando o Notário, um cliente pode verificar a assinatura da imagem do contêiner para garantir que a imagem do contêiner não foi alterada desde que foi assinada com a chave do proprietário ou do técnico de serviço.
Outro problema de segurança potencial é o isolamento do contêiner. As tecnologias de segurança de tempo de execução do Linux, como namespaces, cgroups, recursos do Linux e perfis SELinux, AppArmor e Seccomp ajudam a limitar os processos de contêineres e isolar os contêineres uns dos outros no tempo de execução.
Este artigo aborda o problema de segurança empresarial ainda quente em relação à privacidade de dados e código em imagens de contêiner. O principal objetivo de segurança ao trabalhar com imagens de contêiner é permitir a criação e distribuição de imagens de contêiner criptografadas para que estejam disponíveis apenas para um conjunto específico de destinatários. Nesse caso, outras pessoas podem ter acesso a essas imagens, mas não serão capazes de executá-las ou ver os dados sensíveis dentro delas. A criptografia de contêiner é baseada na criptografia existente, como tecnologias de criptografia Rivest-Shamir-Adleman (RSA), curva elíptica e Advanced Encryption Standard (AES), também conhecido como Rijndael, um algoritmo de criptografia de bloco simétrico.
Introdutório
Para obter o máximo deste artigo, você deve estar familiarizado com os contêineres e imagens de contêiner Linux e ter uma compreensão dos fundamentos da segurança.
Trabalhos relacionados em criptografia e contêineres
Até onde sabemos, não há trabalho na área de criptografia de imagens de contêiner. No entanto, existem muitas implementações e produtos que oferecem suporte à privacidade de dados e criptografia anti-roubo por meio de sistema de arquivos, dispositivo de bloqueio ou criptografia de hardware. O último é implementado usando discos de autocriptografia. Também existem imagens de máquina virtual criptografadas.
Os sistemas de arquivos criptografados existem em muitos sistemas operacionais em empresas e podem suportar a montagem de partições e diretórios criptografados. Os sistemas de arquivos criptografados podem até mesmo suportar a inicialização a partir de uma unidade de inicialização criptografada. O Linux suporta criptografia de dispositivo de bloco usando o driver dm-encrypt; ecryptfs é um exemplo de sistema de arquivos criptografado. Outras soluções de criptografia de arquivos disponíveis para LinuxCódigo aberto. No Windows, a criptografia é compatível com o sistema de arquivos NTFS v3.0. Além disso, muitos fabricantes criam discos com autocriptografia. Para imagens de máquina virtual, existe uma solução semelhante aos discos criptografados. O emulador QEMU Machine (PC) de código aberto e os produtos de virtualização VMware oferecem suporte a imagens de máquina virtual criptografadas.
A criptografia de dados geralmente visa proteger contra roubo de dados enquanto o sistema está offline. Uma tecnologia relacionada é assinar a imagem do contêiner com uma chave fornecida pelo cliente e o servidor Docker Notary. O servidor Docker Notary funciona próximo ao registro de imagem do contêiner. Os usuários da ferramenta cliente Docker têm a opção de assinar a imagem do contêiner e fazer upload da assinatura em suas contas por meio do Docker Notário. Durante esse processo, a assinatura é vinculada à imagem do contêiner por meio do nome do caminho para a imagem e suas versões. A assinatura é criada usando uma função hash que é calculada com base na descrição de todo o conteúdo da imagem. Essa descrição é chamada de manifesto da imagem do contêiner.A tecnologia de assinatura de imagem do contêiner resolve o problema de proteger as imagens do contêiner contra acesso não autorizado e ajuda a determinar a origem da imagem do contêiner.
Estrutura
O ecossistema Docker evoluiu para padronizar os formatos de imagem de contêiner usando o grupo de padrões Open Container Initiative (OCI), que agora controla o formato de tempo de execução do contêiner (especificação do tempo de execução) e o formato da imagem do contêiner (especificação da imagem). Como o trabalho da equipe exigia uma extensão do formato de imagem de contêiner existente, identificamos uma extensão do padrão para oferecer suporte a imagens criptografadas. As seções a seguir descrevem a imagem do contêiner existente e o formato da extensão.
No nível superior, um contêiner pode consistir em um documento JavaScript Object Notation (JSON), que é uma lista de manifestos de imagem. Por exemplo, você pode usar esta lista de manifestos quando várias arquiteturas ou plataformas são usadas para a imagem do contêiner. A lista de manifestos contém links para manifestos de contêineres, um para cada combinação de arquitetura e sistema operacional. Por exemplo, as arquiteturas com suporte incluem amd64, arm e ppc64le, e os sistemas operacionais com suporte incluem Linux ou Windows. Um exemplo de uma lista de manifestos é mostrado na captura de tela abaixo:
O campo mediaType descreve o formato exato do documento especificado. Esta lista de manifestos permite expansão futura e seleção do analisador apropriado para o documento envolvido.
O nível abaixo da lista de manifestos é o manifesto. O manifesto também é um documento JSON e contém uma lista ordenada de referências a camadas de imagem. Esses links contêm mediaType que descreve o formato da camada. O formato pode descrever se a camada está compactada e, em caso afirmativo, como. Por exemplo, cada nível pode ser salvo como um arquivo .tar contendo arquivos que foram adicionados em um estágio específico da compilação ao fazer a compilação do docker em um Dockerfile. As camadas geralmente são compactadas usando arquivos .gzip compactados para melhorar a eficiência do armazenamento. Um exemplo de documento de manifesto é mostrado na seguinte captura de tela:
Conforme mostrado, os manifestos e as camadas são referenciados por meio de um "resumo", que geralmente é uma função hash sha256 em documentos JSON. Os manifestos e camadas geralmente são armazenados como arquivos no sistema de arquivos. Os nomes de arquivos geralmente são funções hash no topo do conteúdo, tornando-os mais fáceis de localizar e carregar. A conseqüência desse método hash é que uma pequena mudança no documento referenciado causa mudanças em todos os documentos que fazem referência a ele, até a lista de manifestos.
Como parte do projeto de nossa equipe, criamos a criptografia de imagens com base em um esquema de criptografia híbrida usando chaves públicas e simétricas. As chaves simétricas são usadas para criptografia de dados em massa (usadas para criptografia multinível) e as chaves públicas são usadas para empacotar chaves simétricas. Usamos três tecnologias diferentes de criptografia de chave pública: OpenPGP, JSON Web Encryption (JWE) e PKCS # 7.
OpenPGP
OpenPGP é uma tecnologia de criptografia e assinatura comumente usada para criptografar e assinar mensagens de e-mail. Comunidades de código aberto também costumam usá-lo para assinar commits (tags) de código-fonte em repositórios git. É um padrão da Internet definido pela IETF no RFC480 e pode ser visto como uma versão aberta da tecnologia PGP proprietária anterior.
O OpenPGP tem seu próprio formato para chaves RSA. As chaves são normalmente armazenadas em um arquivo de chaveiro e podem ser importadas de arquivos de chave OpenPGP simples. O aspecto mais conveniente do chaveiro OpenPGP é que as chaves públicas podem ser vinculadas aos endereços de e-mail de seus proprietários. Você pode trabalhar com vários destinatários de uma mensagem simplesmente selecionando uma lista de destinatários por seus endereços de e-mail, que então aparecem nas chaves públicas para esses destinatários. Além disso, uma rede de confiança foi construída em torno dessa tecnologia: você pode encontrar as chaves públicas de muitos usuários, classificadas por seus endereços de e-mail. Por exemplo, essas chaves são frequentemente usadas para assinar tags git.
Você pode usar o Formato de Mensagem Criptografada OpenPGP para criptografar uma mensagem em massa para vários destinatários. O cabeçalho da mensagem OpenPGP contém um bloco para cada destinatário. Cada bloco contém um identificador de chave de 64 bits que informa ao algoritmo de descriptografia onde tentar descriptografar a chave privada correspondente. Depois que o blob criptografado dentro do bloco é descriptografado, ele mostra uma chave simétrica que pode ser usada para descriptografar a mensagem em massa. O blob de chave pública criptografada de cada destinatário exibe a mesma chave simétrica.
Usamos o OpenPGP de forma semelhante, mas neste caso, a mensagem criptografada que ele transmite não é uma camada. Em vez disso, ele contém um documento JSON, que por sua vez contém uma chave simétrica usada para criptografar e descriptografar a camada e o vetor de inicialização. Chamamos essa chave de chave de criptografia de camada (LEK) e é uma forma de chave de criptografia de dados. A vantagem desse método é que precisamos apenas de um LEK. Com LEK, criptografamos a camada para um ou mais destinatários. Cada destinatário (imagem de contêiner) pode ter um tipo de chave diferente e não precisa ser uma chave OpenPGP. Por exemplo, pode ser uma chave RSA simples. Contanto que possamos usar essa chave RSA para criptografar o LEK, podemos trabalhar com vários destinatários com diferentes tipos de chave.
JSON Web Encryption (JWE)
JSON Web Encryption, também conhecido como JWE, é outro padrão IETF da Internet e é definido no RFC7516 . É um padrão de criptografia mais recente que o OpenPGP e, portanto, usa cifras de baixo nível mais recentes projetadas para atender a requisitos de criptografia mais rigorosos.
Em uma escala maior, o JWE funciona de maneira semelhante ao OpenPGP, pois também mantém uma lista de destinatários e envio em massa de uma mensagem criptografada com uma chave simétrica à qual todos os destinatários da mensagem têm acesso. Os destinatários de uma mensagem JWE podem ter diferentes tipos de chaves, como chaves RSA, tipos de chave de curva elíptica específica para criptografia e chaves simétricas. Como este é um padrão mais recente, ainda é possível estender o JWE para oferecer suporte a chaves em dispositivos de hardware, como TPMs ou módulos de segurança de hardware (HSMs), usando interfaces PKCS # 11 ou Key Management and Interoperability Protocol (KMIP). O JWE é usado de maneira semelhante ao OpenPGP se os destinatários tiverem chaves RSA ou curvas elípticas.No futuro, poderíamos estendê-lo para oferecer suporte a chaves simétricas como KMIP dentro do HSM.
PKCS # 7
PKCS # 7, também conhecido como Cryptographic Message Syntax (CMS), é definido no IEFT RFC5652 . De acordo com a Wikipedia sobre CMS, "Ele pode ser usado para assinar digitalmente, digerir, autenticar ou criptografar qualquer forma de dados digitais."
É semelhante às duas tecnologias descritas anteriormente, pois permite vários destinatários e criptografia de mensagens em massa. Portanto, usamos apenas como outras tecnologias, mas apenas para destinatários que fornecem certificados para chaves de criptografia.
Para oferecer suporte às tecnologias de criptografia descritas anteriormente, estendemos o documento de manifesto para incluir as seguintes informações:
- As mensagens OpenPGP, JWE e PKCS # 7 são armazenadas em um mapa de anotação, que faz parte do manifesto.
- Cada camada especificada contém um mapa. Um mapa de anotação é basicamente um dicionário com strings como chaves e strings como valores (pares chave-valor).
Para oferecer suporte à criptografia de imagem, definimos as seguintes chaves:
- org.opencontainers.image.enc.keys.openpgp
- org.opencontainers.image.enc.keys.jwe
- org.opencontainers.image.enc.keys.pkcs7
O valor referenciado por cada chave contém uma ou mais mensagens criptografadas para a tecnologia de criptografia correspondente. Como essas mensagens podem estar em formato binário, elas são codificadas em base64. Uma camada criptografada deve ter pelo menos uma dessas anotações, mas pode ter todas, se seu destinatário tiver um número suficiente de diferentes tipos de chaves.
Para determinar se a camada foi criptografada com LEK, estendemos os tipos de mídia existentes com o sufixo '+ criptografado', conforme mostrado nos exemplos a seguir:
- application / vnd.docker.image.rootfs.diff.tar + criptografado
- application / vnd.docker.image.rootfs.diff.tar.gzip + criptografado
Esses exemplos mostram que a camada é compactada em um arquivo .tar e criptografada - ou ambas compactadas em um arquivo .tar e compactadas em um arquivo .gzip e criptografadas. A captura de tela a seguir mostra um exemplo de um manifesto vinculado a camadas criptografadas. Ele também mostra um mapa de anotação contendo a mensagem JWE criptografada.
Criptografia em camadas usando chaves simétricas
Para criptografia simétrica com LEK, nossa equipe escolheu uma cifra que oferece suporte à criptografia autenticada e é baseada no padrão de criptografia AES com chaves de 128 e 256 bits.
Implementação de exemplo: containerd
Implementamos nossa variação em um novo projeto de tempo de execução de contêiner chamado containerd . Seu código-fonte no golang pode ser visualizado seguindo o link . O daemon Docker usa containerd para executar alguns de seus serviços, e o Kubernetes tem um plug-in para usar o containerd diretamente. Portanto, esperamos que nossas extensões para suportar imagens de contêiner criptografadas sejam úteis para ambos.
A implementação de criptografia multinível usando LEK está no nível arquitetônico mais baixo de extensões. Um dos requisitos de implementação foi acomodar camadas volumétricas de vários gigabytes, mantendo a quantidade de memória ocupada pelo processo que executa a operação criptográfica na camada com apenas alguns megabytes de tamanho.
O suporte para algoritmos de criptografia autenticados no Golang pega um array de bytes como entrada e executa todo o estágio de criptografar (selar) ou descriptografar (abrir), evitando a transferência e adição de arrays adicionais ao stream. Visto que essa cripto API exigia o carregamento de toda a camada na memória ou a invenção de algum esquema para alterar o vetor de inicialização (IV) para cada bloco, decidimos não usar a criptografia autenticada de golang com suporte a dados vinculados (AEAD). Em vez disso, usamos a biblioteca criptográfica golang, que suporta AEAD em streams(blocos) e implementa seu próprio esquema para alterar IV em cada bloco. Em nossa implementação, dividimos a camada em blocos de 1 MB, que transferimos um a um para criptografia. Essa abordagem permite reduzir a quantidade de memória ao usar uma cifra autenticada. Do lado da descriptografia, fazemos o oposto e prestamos atenção aos erros retornados pela função Open () para garantir que os blocos de criptografia não foram adulterados.
Acima da criptografia simétrica, os esquemas criptográficos assimétricos criptografam o nível LEK e o vetor de inicialização (IV). Para adicionar ou remover esquemas criptográficos, registramos cada implementação criptográfica assimétrica. Quando a API Asymmetric Cryptographic Code é chamada para criptografar a camada, chamamos os manipuladores criptográficos registrados um por um, passando as chaves públicas dos destinatários. Depois que todas as chaves do destinatário foram usadas para criptografia, retornamos ao mapa de anotação com os identificadores de criptoalgoritmo assimétricos como as chaves de mapeamento e os valores que contêm as mensagens codificadas em OpenPGP, JWE e PKCS # 7. Cada mensagem contém LEK e IV compactados. Os mapas de anotação são armazenados no documento de manifesto, conforme mostrado na captura de tela anterior.
Também podemos adicionar destinatários a uma imagem já criptografada. Os autores das imagens adicionam destinatários se eles estiverem na lista. A chave privada é usada para a lista de destinatários, que é necessária para desempacotar os níveis LEK e IV. Em seguida, empacotamos o LEK e o IV em uma nova mensagem usando a nova chave do destinatário e adicionamos essa mensagem ao mapa de anotação.
Usamos três tipos de esquemas de criptografia assimétrica para diferentes tipos de chaves. Usamos chaves OpenPGP para criptografar mensagens OpenPGP. O PKCS # 7 que estamos usando requer certificados x.509 para chaves de criptografia. O JWE lida com todos os outros tipos de chave, como chaves RSA simples, curvas elípticas e chaves simétricas. Fizemos um protótipo de uma extensão para JWE que permite operações criptográficas usando chaves gerenciadas pelo servidor KMIP.
O tempo de execução containerd inclui uma ferramenta cliente ctr para interagir com ele. Estendemos o ctr para permitir o teste de nossas alterações e fornecer acesso aos usuários do contêiner. ctr já implementa um subcomando que oferece suporte a operações de imagem, como interagir com o registro de imagem, buscando e enviando imagens.
Estendemos este subcomando adicionando funcionalidade para criptografar imagens e habilitar a criptografia de camadas específicas de arquiteturas específicas usando um conjunto específico de chaves. Essa abordagem permite aos usuários criptografar apenas as camadas que contêm dados confidenciais e deixar outras camadas sem criptografia. O último pode ser desduplicado, mas isso dificilmente é possível para camadas criptografadas.
Da mesma forma, podemos decifrar as camadas individuais de arquiteturas individuais. Adicionamos um subcomando layerinfo que mostra o status de criptografia de cada camada e exibe as tecnologias de criptografia usadas para ele. Para OpenPGP, também podemos exibir os IDs de chave necessários para descriptografar ou convertê-los para os endereços de e-mail de seus destinatários usando um chaveiro.
Além disso, você pode exportar e importar imagens de contêiner. Implementamos suporte para criptografia de camada na exportação e descriptografia na importação. Mesmo que descriptografemos as camadas para criar o sistema de arquivos rootfs do contêiner, as camadas criptografadas e os arquivos de metadados originais, como seus manifestos, são preservados. Essa abordagem permite que você exporte uma imagem criptografada e execute verificações de autorização quando os usuários desejam iniciar um contêiner com uma imagem criptografada.
Quando uma imagem simples (não criptografada) é recuperada do registro, ela é automaticamente descompactada e descompactada para que os contêineres possam ser criados a partir dela imediatamente. Para tornar mais fácil para imagens criptografadas, sugerimos que você passe a chave privada para a equipe de desempacotamento para que eles possam descriptografar as camadas antes de desempacotar. Se a imagem for criptografada com várias chaves, várias chaves podem ser passadas para o comando pull. Esta transferência também é suportada. Depois de extrair com sucesso a imagem criptografada do registro, qualquer pessoa com acesso ao containerd pode criar um contêiner a partir da imagem. Para confirmar se o usuário tem direitos para usar a imagem do contêiner, sugerimos que ele forneça as chaves privadas usadas para descriptografar o contêiner.Usamos chaves para verificar a autorização do usuário, se podem ser usadas para descriptografar o LEK de cada nível criptografado e, se isso for confirmado, permitimos que o contêiner seja iniciado.
Um guia passo a passo para criptografar usando containerd
Nesta seção, demonstraremos as etapas de criptografia que são aplicadas com o containderd usando ctr na linha de comando. Mostraremos como criptografar e descriptografar uma imagem de contêiner.
Em primeiro lugar, você precisa clonar o repositório git containerd / imgcrypt , que é um subprojeto e pode criptografar / descriptografar a imagem do contêiner. Então você precisa construir o containerd e executá-lo. Para concluir essas etapas, você precisa saber como o ambiente de desenvolvimento golang está configurado:
imgcrypt requer containerd versão 1.3 ou superior.
Crie e instale o imgcrypt:
# make
# sudo make install
Execute containerd com o arquivo de configuração visto no exemplo abaixo. Para evitar conflitos no containerd, use o diretório / tmp para os diretórios. Também compile a versão 1.3 do containerd a partir da fonte, mas não a instale.
# cat config.toml
disable_plugins = ["cri"]
root = "/tmp/var/lib/containerd"
state = "/tmp/run/containerd"
[grpc]
address = "/tmp/run/containerd/containerd.sock"
uid = 0
gid = 0
[stream_processors]
[stream_processors."io.containerd.ocicrypt.decoder.v1.tar.gzip"]
accepts = ["application/vnd.oci.image.layer.v1.tar+gzip+encrypted"]
returns = "application/vnd.oci.image.layer.v1.tar+gzip"
path = "/usr/local/bin/ctd-decoder"
[stream_processors."io.containerd.ocicrypt.decoder.v1.tar"]
accepts = ["application/vnd.oci.image.layer.v1.tar+encrypted"]
returns = "application/vnd.oci.image.layer.v1.tar"
path = "/usr/local/bin/ctd-decoder"
# sudo ~/src/github.com/containerd/containerd/bin/containerd -c config.toml
Crie um par de chaves RSA usando a ferramenta de linha de comando openssl e criptografe a imagem:
# openssl genrsa --out mykey.pem
Generating RSA private key, 2048 bit long modulus (2 primes)
...............................................+++++
............................+++++
e is 65537 (0x010001)
# openssl rsa -in mykey.pem -pubout -out mypubkey.pem
writing RSA key
# sudo chmod 0666 /tmp/run/containerd/containerd.sock
# CTR="/usr/local/bin/ctr-enc -a /tmp/run/containerd/containerd.sock"
# $CTR images pull --all-platforms docker.io/library/bash:latest
[...]
# $CTR images layerinfo --platform linux/amd64 docker.io/library/bash:latest
# DIGEST PLATFORM SIZE ENCRYPTION RECIPIENTS
0 sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609 linux/amd64 2789669
1 sha256:7dd01fd971d4ec7058c5636a505327b24e5fc8bd7f62816a9d518472bd9b15c0 linux/amd64 3174665
2 sha256:691cfbca522787898c8b37f063dd20e5524e7d103e1a3b298bd2e2b8da54faf5 linux/amd64 340
# $CTR images encrypt --recipient jwe:mypubkey.pem --platform linux/amd64 docker.io/library/bash:latest bash.enc:latest
Encrypting docker.io/library/bash:latest to bash.enc:latest
$ $CTR images layerinfo --platform linux/amd64 bash.enc:latest
# DIGEST PLATFORM SIZE ENCRYPTION RECIPIENTS
0 sha256:360be141b01f69b25427a9085b36ba8ad7d7a335449013fa6b32c1ecb894ab5b linux/amd64 2789669 jwe [jwe]
1 sha256:ac601e66cdd275ee0e10afead03a2722e153a60982122d2d369880ea54fe82f8 linux/amd64 3174665 jwe [jwe]
2 sha256:41e47064fd00424e328915ad2f7f716bd86ea2d0d8315edaf33ecaa6a2464530 linux/amd64 340 jwe [jwe]
Inicie seu registro de imagem local para que você possa carregar a imagem criptografada nele. Para receber imagens de contêiner criptografadas, você precisa das versões de registro mais recentes.
# docker pull registry:latest
# docker run -d -p 5000:5000 --restart=always --name registry registry
Faça upload da imagem criptografada para o registro local, extraia-a usando ctr-enc e execute a imagem:
# $CTR images tag bash.enc:latest localhost:5000/bash.enc:latest
# $CTR images push localhost:5000/bash.enc:latest
# $CTR images rm localhost:5000/bash.enc:latest bash.enc:latest
# $CTR images pull localhost:5000/bash.enc:latest
# sudo $CTR run --rm localhost:5000/bash.enc:latest test echo 'Hello World!'
ctr: you are not authorized to use this image: missing private key needed for decryption
# sudo $CTR run --rm --key mykey.pem localhost:5000/bash.enc:latest test echo 'Hello World!'
Hello World!
Conclusão
A criptografia de imagens de contêiner é uma boa adição à sua segurança, pois garante a confidencialidade dos dados e a integridade das imagens de contêiner no local de armazenamento. A tecnologia proposta é baseada nas tecnologias de criptografia RSA, curva elíptica e AES disponíveis publicamente. Ele aplica chaves a esquemas de criptografia de nível superior, como OpenPGP, JWE e PKCS # 7. Se você sabe como trabalhar com OpenPGP, pode criptografar imagens de contêiner para destinatários OpenPGP usando seus endereços de e-mail, enquanto chaves RSA simples e curvas elípticas são usadas para criptografia como JWE.