Como criptografar bytes usando o TPM (Trusted Platform Module)

110

Como posso criptografar bytes usando o módulo TPM de uma máquina?

CryptProtectData

O Windows fornece uma API (relativamente) simples para criptografar um blob usando a CryptProtectDataAPI, que pode envolver uma função fácil de usar:

public Byte[] ProtectBytes(Byte[] plaintext)
{
   //...
}

Os detalhes de ProtectBytessão menos importantes do que a ideia de que você pode usá-lo com bastante facilidade:

  • aqui estão os bytes que quero criptografados por uma chave secreta mantida no System
  • me devolva o blob criptografado

O blob retornado é uma documentação não documentada estrutura de que contém tudo o que é necessário para descriptografar e retornar os dados originais (algoritmo de hash, algoritmo de cifra, salt, assinatura HMAC, etc).

Para completar, aqui está o exemplo de implementação de pseudocódigo ProtectBytesque usa o Crypt APIpara proteger os bytes:

public Byte[] ProtectBytes(Byte[] plaintext)
{
   //Setup our n-byte plaintext blob
   DATA_BLOB dataIn;
   dataIn.cbData = plaintext.Length;
   dataIn.pbData = Addr(plaintext[0]);

   DATA_BLOB dataOut;

   //dataOut = EncryptedFormOf(dataIn)
   BOOL bRes = CryptProtectData(
         dataIn,
         null,     //data description (optional PWideChar)
         null,     //optional entropy (PDATA_BLOB)
         null,     //reserved
         null,     //prompt struct
         CRYPTPROTECT_UI_FORBIDDEN || CRYPTPROTECT_LOCAL_MACHINE,
         ref dataOut);
   if (!bRes) then
   {
      DWORD le = GetLastError();
      throw new Win32Error(le, "Error calling CryptProtectData");
   }

   //Copy ciphertext from dataOut blob into an actual array
   bytes[] result;
   SetLength(result, dataOut.cbData);
   CopyMemory(dataOut.pbData, Addr(result[0]), dataOut.cbData);

   //When you have finished using the DATA_BLOB structure, free its pbData member by calling the LocalFree function
   LocalFree(HANDLE(dataOut.pbData)); //LocalFree takes a handle, not a pointer. But that's what the SDK says.
}

Como fazer o mesmo com o TPM?

O código acima é útil para criptografar dados apenas para a máquina local. Os dados são criptografados usando a Systemconta como gerador de chave (os detalhes, embora interessantes, não são importantes ). O resultado final é que posso criptografar dados (por exemplo, uma chave mestra de criptografia de disco rígido) que só podem ser descriptografados pela máquina local.

Agora é hora de dar um passo adiante. Desejo criptografar alguns dados (por exemplo, uma chave mestra de criptografia do disco rígido) que só podem ser descriptografados pelo TPM local. Em outras palavras, desejo substituir o Qualcomm Trusted Execution Environment ( TEE ) no diagrama de blocos abaixo para Android, pelo TPM no Windows:

insira a descrição da imagem aqui

Nota : Eu percebo que o TPM não faz assinatura de dados (ou se faz, não garante que assinar os mesmos dados dará sempre a mesma saída binária). É por isso que eu estaria disposto a substituir "assinatura RSA" por "criptografar um blob de 256 bits com uma chave associada ao hardware" .

Onde está o código?

O problema é que a programação TPM não está documentada no MSDN . Não há API disponível para realizar qualquer operação. Em vez disso, você precisa encontrar uma cópia da pilha de software do Trusted Computing Group (também conhecida como TSS) , descobrir quais comandos enviar para o TPM, com cargas úteis, em que ordem, e chamar a função Tbsip_Submit_Command do Windows para enviar comandos diretamente:

TBS_RESULT Tbsip_Submit_Command(
  _In_     TBS_HCONTEXT hContext,
  _In_     TBS_COMMAND_LOCALITY Locality,
  _In_     TBS_COMMAND_PRIORITY Priority,
  _In_     const PCBYTE *pabCommand,
  _In_     UINT32 cbCommand,
  _Out_    PBYTE *pabResult,
  _Inout_  UINT32 *pcbOutput
);

O Windows não tem API de nível superior para realizar ações.

É o equivalente moral de tentar criar um arquivo de texto emitindo comandos SATA I / O para seu disco rígido .

Por que não usar apenas calças

O Trusted Computing Group (TCG) definiu sua própria API: TCB Software Stack (TSS) . Uma implementação dessa API foi criada por algumas pessoas e é chamada de TrouSerS . Um cara então portou esse projeto para o Windows .

O problema com esse código é que não é portátil para o mundo do Windows. Por exemplo, você não pode usá-lo do Delphi, você não pode usá-lo do C #. Isso requer:

  • OpenSSL
  • pThread

Só quero que o código criptografe algo com meu TPM.

O acima CryptProtectDatarequer nada além do que está no corpo da função.

Qual é o código equivalente para criptografar dados usando o TPM? Como outros notaram, você provavelmente terá que consultar os três manuais do TPM e construir você mesmo os blobs . Provavelmente envolve o TPM_sealcomando. Embora eu ache que não quero lacrar dados, acho que quero vinculá- los:

Vinculação - criptografa os dados usando a chave de vinculação TPM, uma chave RSA exclusiva descendente de uma chave de armazenamento. Selagem - criptografa os dados de maneira semelhante à vinculação, mas, além disso, especifica um estado em que o TPM deve estar para que os dados sejam descriptografados (sem lacre)

Tento ler os três volumes necessários para encontrar as 20 linhas de código de que preciso:

Mas eu não tenho idéia do que estou lendo. Se houvesse algum tipo de tutorial ou exemplo, eu poderia tentar. Mas estou completamente perdido.

Então pedimos Stackoverflow

Da mesma forma, fui capaz de fornecer:

Byte[] ProtectBytes_Crypt(Byte[] plaintext)
{
   //...
   CryptProtectData(...); 
   //...
}

alguém pode fornecer o equivalente correspondente:

Byte[] ProtectBytes_TPM(Byte[] plaintext)
{
   //...
   Tbsip_Submit_Command(...);
   Tbsip_Submit_Command(...);
   Tbsip_Submit_Command(...);
   //...snip...
   Tbsip_Submit_Command(...);
   //...
}

que faz a mesma coisa, exceto ao invés de uma chave trancada em System LSA, é trancada no TPM?

Início da Pesquisa

Não sei exatamente o que significa ligar . Mas olhando para TPM Main - Parte 3 Comandos - Especificação Versão 1.2, há uma menção de bind :

10.3 TPM_UnBind

TPM_UnBind pega o blob de dados que é o resultado de um comando Tspi_Data_Bind e o descriptografa para exportação para o usuário. O chamador deve autorizar o uso da chave que descriptografará o blob de entrada. TPM_UnBind opera bloco a bloco e não tem noção de qualquer relação entre um bloco e outro.

O que está confundindo está lá é nenhum Tspi_Data_Bindcomando.

Esforço de Pesquisa

É horrível como ninguém jamais se preocupou em documentar o TPM ou sua operação. É como se eles passassem o tempo todo inventando uma coisa legal para brincar, mas não quisessem lidar com a dolorosa etapa de torná-la utilizável para alguma coisa.

Começando com o livro (agora) gratuito A Practical Guide to TPM 2.0: Using the Trusted Platform Module in the New Age of Security :

Capítulo 3 - Tutorial rápido sobre TPM 2.0

O TPM tem acesso a uma chave privada gerada automaticamente, portanto, ele pode criptografar chaves com uma chave pública e, em seguida, armazenar o blob resultante no disco rígido. Dessa forma, o TPM pode manter um número virtualmente ilimitado de chaves disponíveis para uso, mas não desperdiça armazenamento interno valioso. As chaves armazenadas no disco rígido podem ser apagadas, mas também podem ser feitas cópias de segurança, o que pareceu aos projetistas uma troca aceitável.

Como posso criptografar uma chave com a chave pública do TPM?

Capítulo 4 - Aplicativos existentes que usam TPMs

Aplicativos que devem usar o TPM, mas não

Nos últimos anos, o número de aplicativos baseados na web aumentou. Entre eles estão backup e armazenamento baseados na web. Atualmente, um grande número de empresas oferece esses serviços, mas, pelo que sabemos, nenhum dos clientes desses serviços permite que o usuário bloqueie a chave do serviço de backup para um TPM. Se isso fosse feito, certamente seria bom se a própria chave TPM fosse copiada, duplicando-a em várias máquinas. Esta parece ser uma oportunidade para desenvolvedores.

Como um desenvolvedor bloqueia uma chave para o TPM?

Capítulo 9 - Heirarquias

CASO DE USO: ARMAZENAMENTO DE SENHAS DE LOGIN

Um arquivo de senha típico armazena hashes salgados de senhas. A verificação consiste em fazer o sal e hash de uma senha fornecida e compará-la com o valor armazenado. Como o cálculo não inclui um segredo, ele está sujeito a um ataque offline ao arquivo de senha.

Este caso de uso usa uma chave HMAC gerada por TPM. O arquivo de senha armazena um HMAC da senha com salt. A verificação consiste em salting e HMAC a senha fornecida e compará-la com o valor armazenado. Como um invasor offline não tem a chave HMAC, o invasor não pode montar um ataque executando o cálculo.

Isso pode funcionar. Se o TPM tiver uma chave HMAC secreta e apenas meu TPM souber a chave HMAC, eu poderia substituir "Assinar (também conhecido como criptografar TPM com sua chave privada)" por "HMAC". Mas então na linha seguinte ele se inverte completamente:

TPM2_Create, especificando uma chave HMAC

Não é um segredo do TPM se eu tiver que especificar a chave HMAC. O fato de a chave HMAC não ser secreta faz sentido quando você percebe que este é o capítulo sobre utilitários criptográficos que o TPM fornece. Em vez de escrever SHA2, AES, HMAC ou RSA por conta própria, você pode reutilizar o que o TPM já tem disponível.

Capítulo 10 - Chaves

Como um dispositivo de segurança, a capacidade de um aplicativo de usar chaves enquanto as mantém seguras em um dispositivo de hardware é a maior força do TPM. O TPM pode gerar e importar chaves geradas externamente. Ele oferece suporte a chaves assimétricas e simétricas.

Excelente! Como você faz isso!?

Gerador de Chaves

Indiscutivelmente, a maior força do TPM é sua capacidade de gerar uma chave criptográfica e proteger seu segredo dentro de um limite de hardware. O gerador de chaves é baseado no próprio gerador de números aleatórios do TPM e não depende de fontes externas de aleatoriedade. Assim, elimina fraquezas com base em softwares de software fracos com uma fonte insuficiente de entropia.

O TPM tem a capacidade de gerar chaves criptográficas e proteger seus segredos dentro de um limite de hardware? É assim como?

Capítulo 12 - Registros de configuração da plataforma

PCRs para autorização

CASO DE USO: VEDANDO UMA CHAVE DE ENCRIPTAÇÃO DE DISCO RÍGIDO PARA O ESTADO DA PLATAFORMA

Os aplicativos de criptografia de disco completo são muito mais seguros se um TPM proteger a chave de criptografia do que se ela estiver armazenada no mesmo disco, protegido apenas por uma senha. Primeiro, o hardware TPM tem proteção anti-hammering (consulte o Capítulo 8 para uma descrição detalhada da proteção contra ataques de dicionário do TPM), tornando um ataque de força bruta à senha impraticável. Uma chave protegida apenas por software é muito mais vulnerável a uma senha fraca. Em segundo lugar, uma chave de software armazenada em disco é muito mais fácil de roubar. Pegue o disco (ou um backup do disco) e você terá a chave. Quando um TPM detém a chave, toda a plataforma, ou pelo menos o disco e a placa-mãe, deve ser roubada.

O lacre permite que a chave seja protegida não apenas por uma senha, mas por uma política. Uma política típica bloqueia a chave para os valores PCR (o estado do software) atuais no momento da selagem. Isso pressupõe que o estado na primeira inicialização não está comprometido. Qualquer malware pré-instalado presente na primeira inicialização seria medido nos PCRs e, portanto, a chave seria selada para um estado de software comprometido. Uma empresa menos confiável pode ter uma imagem de disco padrão e selo para PCRs que representam essa imagem. Esses valores de PCR seriam pré-calculados em uma plataforma presumivelmente mais confiável. Uma empresa ainda mais sofisticada usaria TPM2_PolicyAuthorize e forneceria vários tíquetes autorizando um conjunto de valores PCR confiáveis. Consulte o Capítulo 14 para obter uma descrição detalhada da política de autorização e sua aplicação para resolver o problema de fragilidade PCR.

Embora uma senha também possa proteger a chave, há um ganho de segurança mesmo sem uma senha de chave TPM. Um invasor pode inicializar a plataforma sem fornecer uma senha TPMkey, mas não pode fazer login sem o nome de usuário e a senha do sistema operacional. O OSsecurity protege os dados. O invasor pode inicializar um sistema operacional alternativo, digamos a partir de um DVD ao vivo ou pen drive, em vez do disco rígido, para contornar a segurança de login do sistema operacional. No entanto, essa configuração de inicialização e software diferentes mudariam os valores PCR. Como esses novos PCRs não corresponderiam aos valores lacrados, o TPM não liberaria a chave de descriptografia e o disco rígido não poderia ser descriptografado.

Excelente! Este é exatamente o caso de uso que desejo. É também o caso de uso para o qual a Microsoft usa o TPM. Como eu faço isso!?

Portanto, li o livro inteiro e não trouxe nada de útil. O que é bastante impressionante porque tem 375 páginas. Você se pergunta o que o livro continha - e, olhando para trás, não tenho ideia.

Portanto, desistimos do guia definitivo para programar o TPM e, em vez disso, recorremos a alguma documentação da Microsoft:

Do Microsoft TPM Platform Crypto-Provider Toolkit . Ele menciona exatamente o que eu quero fazer:

A chave de endosso ou EK

O EK foi projetado para fornecer um identificador criptográfico confiável para a plataforma. Uma empresa pode manter um banco de dados das Chaves de Endosso pertencentes aos TPMs de todos os PCs em sua empresa, ou um controlador de malha do data center pode ter um banco de dados dos TPMs em todos os blades. No Windows, você pode usar o provedor NCrypt descrito na seção “Provedor de criptografia de plataforma no Windows 8” para ler a parte pública do EK.

Em algum lugar dentro do TPM está uma chave privada RSA. Essa chave está trancada lá - para nunca ser vista pelo mundo exterior. Quero que o TPM assine algo com sua chave privada (ou seja, criptografe-o com sua chave privada).

Então, eu quero a operação mais básica que pode existir:

insira a descrição da imagem aqui

Criptografe algo com sua chave privada. Não estou nem (ainda) pedindo as coisas mais complicadas:

  • "selando" com base no estado de PCR
  • criar uma chave e armazená-la em uma memória volátil ou não volátil
  • criar uma chave simétrica e tentar carregá-la no TPM

Estou pedindo a operação mais básica que um TPM pode fazer. Por que é impossível obter informações sobre como fazer isso?

Posso obter dados aleatórios

Acho que estava sendo simplista quando disse que a assinatura do RSA era a coisa mais básica que o TPM pode fazer. A coisa mais básica que o TPM pode ser solicitado a fazer é me dar bytes aleatórios. Que descobri como fazer:

public Byte[] GetRandomBytesTPM(int desiredBytes)
{
   //The maximum random number size is limited to 4,096 bytes per call
   Byte[] result = new Byte[desiredBytes];

   BCRYPT_ALG_HANDLE hAlgorithm;

   BCryptOpenAlgorithmProvider(
         out hAlgorithm,
         BCRYPT_RNG_ALGORITHM, //AlgorithmID: "RNG"
         MS_PLATFORM_CRYPTO_PROVIDER, //Implementation: "Microsoft Platform Crypto Provider" i.e. the TPM
         0 //Flags
   );
   try
   {                
      BCryptGenRandom(hAlgorithm, @result[0], desiredBytes, 0);
   }
   finally
   {
      BCryptCloseAlgorithmProvider(hAlgorithm);
   }

   return result;
}

The Fancy Thing

Percebo que o volume de pessoas que usam o TPM é muito baixo. É por isso que ninguém no Stackoverflow tem uma resposta. Portanto, não posso ficar muito ganancioso em encontrar uma solução para meu problema comum. Mas o que eu realmente quero fazer é "lacrar" alguns dados:

insira a descrição da imagem aqui

  • apresentar ao TPM alguns dados (por exemplo, 32 bytes de material chave)
  • fazer com que o TPM criptografe os dados, retornando alguma estrutura de blob opaca
  • mais tarde, peça ao TPM para descriptografar o blob
  • a descriptografia só funcionará se os registros PCR do TPM forem os mesmos que eram durante a criptografia.

Em outras palavras:

Byte[] ProtectBytes_TPM(Byte[] plaintext, Boolean sealToPcr)
{
   //...
}

Byte[] UnprotectBytes_TPM(Byte[] protectedBlob)
{
   //...
}

Cryptography Next Gen (Cng, aka BCrypt) suporta TPM

A API de criptografia original no Windows era conhecida como API de criptografia.

A partir do Windows Vista, a Crypto API foi substituída por Cryptography API: Next Generation (internamente conhecido como BestCrypt , abreviado como BCrypt , não deve ser confundido com o algoritmo de hash de senha ).

O Windows é fornecido com dois provedores BCrypt :

O provedor Platform Crypto não está documentado no MSDN, mas possui a documentação de um site de pesquisa da Microsoft 2012:

TPM Platform Crypto-Provider Toolkit

O provedor de criptografia da plataforma TPM e o kit de ferramentas contém código de amostra, utilitários e documentação para usar a funcionalidade relacionada ao TPM no Windows 8. Os subsistemas descritos incluem o provedor de criptografia de plataforma Crypto-Next-Gen (CNG) apoiado por TPM e como provedores de serviço de atestado pode usar os novos recursos do Windows. Ambos os sistemas baseados em TPM1.2 e TPM2.0 são suportados.

Parece que a intenção da Microsoft é trazer à tona a funcionalidade de criptografia TPM com o Microsoft Platform Crypto Provider da Cryptography NG API.

Criptografia de chave pública usando Microsoft BCrypt

Dado que:

um caminho a seguir poderia ser a de descobrir como fazer a assinatura digital usando a API Microsoft Cryptography Next Gen .

Meu próximo passo será chegar ao código para fazer a criptografia no BCrypt, com uma chave pública RSA, usando o provedor padrão ( MS_PRIMITIVE_PROVIDER). Por exemplo:

  • modulus: 0xDC 67 FA F4 9E F2 72 1D 45 2C B4 80 79 06 A0 94 27 50 8209 DD 67 CE 57 B8 6C 4A 4F 40 9F D2 D1 69 FB 995D 85 0C 07 A1 F9 47 1B 56 16 6E F6 7F B9 CF 2A 58 36 37 99 29 AA 4F A8 12 E8 4F C7 82 2B 9D 72 2A 9C DE 6F C2 EE 12 6D CF F0 F2 B8 C4 DD 7C 5C 1A C8 17 51 A9 AC DF 08 22 04 9D 2B D7 F9 4B 09 DE 9A EB 5C 51 1A D8 F8 F9 56 9E F8 FB 37 9B 3F D3 74 65 24 0D FF 34 75 57 A4 F5 BF 55
  • publicExponent: 65537

Com esse código funcionando, posso passar a usar o Provedor TPM ( MS_PLATFORM_CRYPTO_PROVIDER).

22/02/2016: E com a Apple sendo obrigada a ajudar a descriptografar os dados do usuário, há um interesse renovado em como fazer o TPM executar a tarefa mais simples para a qual foi inventado - criptografar algo.

É mais ou menos equivalente a todo mundo que possui um carro, mas ninguém sabe como dar partida em um. Ele pode fazer coisas realmente úteis e interessantes, se ao menos pudéssemos passar da Etapa 1 .

Leitura de bônus

Ian Boyd
fonte
Para vinculação (criptografia), não há função explícita disponível e também não é necessária. Basta criar uma chave de ligação no TPM e usar sua parte pública para criptografar uma chave de criptografia simétrica sek com a função de criptografia rsa do sistema ("RSA / ECB / OAEPWithSHA1AndMGF1Padding") e salvá-la na estrutura correta ("TcTssConstants.TSS_ENCDATA_BIND"). Para desvincular (descriptografar) o sek, basta usar a função de desvincular dos TPMs e usar o sek em qualquer função de criptografia simétrica que desejar. Eu tenho uma base de código em vez de idade para que eu fiz há algum tempo, talvez ele ajuda: goo.gl/jV1Ouw
Evildead
Da Wikipedia, Binding - criptografa dados usando a chave de ligação TPM, uma chave RSA exclusiva descendente de uma chave de armazenamento. en.wikipedia.org/wiki/Trusted_Platform_Module Parece que este par de comandos (TSpi_Data_Bind / TPM_UnBind) deve ser suficiente para suas necessidades ...
Alex Mazzariol
1
Não acho que você precise usar o TPM diretamente. É compatível com as APIs CNG / NCryptXXX padrão e o "Microsoft Platform Crypto Provider" (para plataformas Windows OS recentes e se o hardware estiver ok e habilitado, é claro). Talvez você possa dar uma olhada no "TPM Platform Crypto-Provider Toolkit aqui: research.microsoft.com/en-us/downloads/… também verifique: tiw2013.cse.psu.edu/slides/…
Simon Mourier
CryptProtectData não usa necessariamente o TPM. Por outro lado, se você puder obter um identificador CNG ou CSP válido para o TPM, poderá usá-lo em funções criptográficas.
Michael Chourdakis
1
@ b3nj1 Não, não era; ninguém foi capaz de responder à pergunta.
Ian Boyd

Respostas:

7

Primer

Tudo o que se segue é sobre TPM 1.2. Lembre-se de que a Microsoft requer um TPM 2.0 para todas as versões futuras do Windows. A geração 2.0 é fundamentalmente diferente da 1.2

Não existe uma solução de uma linha devido aos princípios de design do TPM. Pense no TPM como um microcontrolador com recursos limitados. Seu principal objetivo de design era ser barato, mas ainda seguro. Portanto, o TPM foi retirado de toda a lógica que não era necessária para uma operação segura. Portanto, um TPM só funciona quando você tem pelo menos um software mais ou menos gordo , emitindo muitos comandos na ordem correta. E essas sequências de comandos podem ficar muito complexas. É por isso que TCG especificou o TSS com uma API bem definida. Se você gostaria de seguir o caminho do Java, existe até uma API Java de alto nível . Não conheço um projeto semelhante para C # / .net

Desenvolvimento

No seu caso, sugiro que você dê uma olhada no software TPM da IBM.

No pacote você encontrará 3 componentes muito úteis:

  • um emulador de software TPM
  • um tpm lib leve
  • alguns utilitários básicos de linha de comando

Você não precisa necessariamente do emulador de software TPM, você também pode se conectar ao HW TPM da máquina. No entanto, você pode interceptar os comandos emitidos e ver as respostas, aprendendo assim como eles são montados e como correspondem à especificação do comando.

Alto nível

Pré-requisitos:

  1. TPM está ativado
  2. O driver TPM está carregado
  3. você assumiu a propriedade do TPM

Para selar um blob, você precisa fazer o seguinte:

  1. crie uma chave
  2. armazene o key-blob em algum lugar
  3. certifique-se de que a chave está carregada no TPM
  4. selar a bolha

Para retirar o lacre, você precisa:

  1. obter o key-blob
  2. carregue a chave para o TPM
  3. desmarque a bolha selada

Você pode armazenar o key-blob na estrutura de dados que usa para armazenar os bytes protegidos.

A maioria dos comandos TPM de que você precisa são autorizados. Portanto, você precisa estabelecer sessões de autorização quando necessário. AFAIR, essas são principalmente sessões OSAP.

Comandos TPM

Atualmente não consigo executar uma versão de depuração, portanto, não posso fornecer a sequência exata. Portanto, considere esta uma lista não ordenada de comandos que você terá que usar:

  • TPM_OSAP
  • TPM_CreateWrapKey
  • TPM_LoadKey2
  • TPM_Seal

Se você quiser ler os valores PCR atuais também:

  • TPM_PCRRead
Scolytus
fonte
A Microsoft tem sua biblioteca gerenciada C # .NET para usar o TPM . Eles também têm um emulador TPM , ao qual a biblioteca gerenciada pode se conectar como uma alternativa de depuração se um TPM real não estiver presente. Eles também têm o TPM Platform Provider Toolkit , que contém documentação e código de amostra para usar o TPM. Agora, se alguém pudesse descobrir como usar o TPM para criptografar bytes.
Ian Boyd
Seus primeiros dois links são apenas TPM 2.0. Se você quiser usar isso, não posso ajudar.
Scolytus
4

Chaves confiáveis ​​e criptografadas

Chaves confiáveis ​​e criptografadas são dois novos tipos de chave adicionados ao serviço de anel de chaves do kernel existente. Ambos os novos tipos são chaves simétricas de comprimento variável e, em ambos os casos, todas as chaves são criadas no kernel e o espaço do usuário vê, armazena e carrega apenas blobs criptografados. As chaves confiáveis ​​exigem a disponibilidade de um chip Trusted Platform Module (TPM) para maior segurança, enquanto as chaves criptografadas podem ser usadas em qualquer sistema. Todos os blobs no nível do usuário são exibidos e carregados em hex ascii por conveniência e têm sua integridade verificada.

Chaves confiáveis ​​usam um TPM para gerar e lacrar as chaves. As chaves são lacradas com uma chave RSA de 2048 bits no TPM e, opcionalmente, seladas para valores de PCR (medição de integridade) especificados e apenas sem lacre pelo TPM, se as verificações de integridade de blob e PCR forem correspondentes. Uma chave confiável carregada pode ser atualizada com novos (futuros) valores PCR, de forma que as chaves sejam facilmente migradas para novos valores pcr, como quando o kernel e o initramfs são atualizados. A mesma chave pode ter muitos blobs salvos em diferentes valores de PCR, portanto, várias inicializações são facilmente suportadas.

Por padrão, as chaves confiáveis ​​são seladas sob a SRK, que tem o valor de autorização padrão (20 zeros). Isso pode ser definida em tempo takeownership com a utilidade do calças: tpm_takeownership -u -z.

Usage:
    keyctl add trusted name "new keylen [options]" ring
    keyctl add trusted name "load hex_blob [pcrlock=pcrnum]" ring
    keyctl update key "update [options]"
    keyctl print keyid

    options:
    keyhandle= ascii hex value of sealing key default 0x40000000 (SRK)
    keyauth=   ascii hex auth for sealing key default 0x00...i
        (40 ascii zeros)
    blobauth=  ascii hex auth for sealed data default 0x00...
        (40 ascii zeros)
    blobauth=  ascii hex auth for sealed data default 0x00...
        (40 ascii zeros)
    pcrinfo=   ascii hex of PCR_INFO or PCR_INFO_LONG (no default)
    pcrlock=   pcr number to be extended to "lock" blob
    migratable= 0|1 indicating permission to reseal to new PCR values,
                default 1 (resealing allowed)

keyctl printretorna uma cópia hexadecimal ascii da chave lacrada, que está no formato TPM_STORED_DATA padrão. O comprimento da chave para novas chaves é sempre em bytes. Chaves confiáveis ​​podem ter 32 - 128 bytes (256 - 1024 bits), o limite superior é para caber no comprimento de chave SRK (RSA) de 2048 bits, com toda a estrutura / preenchimento necessários.

As chaves criptografadas não dependem de um TPM e são mais rápidas, pois usam AES para criptografar / descriptografar. Novas chaves são criadas a partir de números aleatórios gerados pelo kernel e são criptografadas / descriptografadas usando uma chave 'mestra' especificada. A chave 'mestra' pode ser uma chave confiável ou um tipo de chave de usuário. A principal desvantagem das chaves criptografadas é que, se não estiverem enraizadas em uma chave confiável, serão tão seguras quanto a chave do usuário que as criptografou. A chave mestra do usuário deve, portanto, ser carregada da maneira mais segura possível, de preferência no início da inicialização.

A parte descriptografada das chaves criptografadas pode conter uma chave simétrica simples ou uma estrutura mais complexa. O formato da estrutura mais complexa é específico do aplicativo, que é identificado por 'formato'.

Usage:
    keyctl add encrypted name "new [format] key-type:master-key-name keylen"
        ring
    keyctl add encrypted name "load hex_blob" ring
    keyctl update keyid "update key-type:master-key-name"

format:= 'default | ecryptfs'
key-type:= 'trusted' | 'user'

Exemplos de uso de chave confiável e criptografada

Crie e salve uma chave confiável chamada "kmk" de 32 bytes de comprimento:

$ keyctl add trusted kmk "new 32" @u
440502848

$ keyctl show
Session Keyring
       -3 --alswrv    500   500  keyring: _ses
 97833714 --alswrv    500    -1   \_ keyring: _uid.500
440502848 --alswrv    500   500       \_ trusted: kmk

$ keyctl print 440502848
0101000000000000000001005d01b7e3f4a6be5709930f3b70a743cbb42e0cc95e18e915
3f60da455bbf1144ad12e4f92b452f966929f6105fd29ca28e4d4d5a031d068478bacb0b
27351119f822911b0a11ba3d3498ba6a32e50dac7f32894dd890eb9ad578e4e292c83722
a52e56a097e6a68b3f56f7a52ece0cdccba1eb62cad7d817f6dc58898b3ac15f36026fec
d568bd4a706cb60bb37be6d8f1240661199d640b66fb0fe3b079f97f450b9ef9c22c6d5d
dd379f0facd1cd020281dfa3c70ba21a3fa6fc2471dc6d13ecf8298b946f65345faa5ef0
f1f8fff03ad0acb083725535636addb08d73dedb9832da198081e5deae84bfaf0409c22b
e4a8aea2b607ec96931e6f4d4fe563ba

$ keyctl pipe 440502848 > kmk.blob

Carregue uma chave confiável do blob salvo:

$ keyctl add trusted kmk "load `cat kmk.blob`" @u
268728824

$ keyctl print 268728824
0101000000000000000001005d01b7e3f4a6be5709930f3b70a743cbb42e0cc95e18e915
3f60da455bbf1144ad12e4f92b452f966929f6105fd29ca28e4d4d5a031d068478bacb0b
27351119f822911b0a11ba3d3498ba6a32e50dac7f32894dd890eb9ad578e4e292c83722
a52e56a097e6a68b3f56f7a52ece0cdccba1eb62cad7d817f6dc58898b3ac15f36026fec
d568bd4a706cb60bb37be6d8f1240661199d640b66fb0fe3b079f97f450b9ef9c22c6d5d
dd379f0facd1cd020281dfa3c70ba21a3fa6fc2471dc6d13ecf8298b946f65345faa5ef0
f1f8fff03ad0acb083725535636addb08d73dedb9832da198081e5deae84bfaf0409c22b
e4a8aea2b607ec96931e6f4d4fe563ba

Sele novamente uma chave confiável com novos valores pcr:

$ keyctl update 268728824 "update pcrinfo=`cat pcr.blob`"
$ keyctl print 268728824
010100000000002c0002800093c35a09b70fff26e7a98ae786c641e678ec6ffb6b46d805
77c8a6377aed9d3219c6dfec4b23ffe3000001005d37d472ac8a44023fbb3d18583a4f73
d3a076c0858f6f1dcaa39ea0f119911ff03f5406df4f7f27f41da8d7194f45c9f4e00f2e
df449f266253aa3f52e55c53de147773e00f0f9aca86c64d94c95382265968c354c5eab4
9638c5ae99c89de1e0997242edfb0b501744e11ff9762dfd951cffd93227cc513384e7e6
e782c29435c7ec2edafaa2f4c1fe6e7a781b59549ff5296371b42133777dcc5b8b971610
94bc67ede19e43ddb9dc2baacad374a36feaf0314d700af0a65c164b7082401740e489c9
7ef6a24defe4846104209bf0c3eced7fa1a672ed5b125fc9d8cd88b476a658a4434644ef
df8ae9a178e9f83ba9f08d10fa47e4226b98b0702f06b3b8

O consumidor inicial de chaves confiáveis ​​é o EVM, que no momento da inicialização precisa de uma chave simétrica de alta qualidade para proteção HMAC de metadados de arquivo. O uso de uma chave confiável fornece fortes garantias de que a chave EVM não foi comprometida por um problema no nível do usuário e, quando selada para valores específicos de PCR de inicialização, protege contra ataques de inicialização e offline. Crie e salve uma chave criptografada "evm" usando a chave confiável "kmk" acima:

opção 1: omitindo 'formato'

$ keyctl add encrypted evm "new trusted:kmk 32" @u
159771175

opção 2: definir explicitamente 'formato' como 'padrão'

$ keyctl add encrypted evm "new default trusted:kmk 32" @u
159771175

$ keyctl print 159771175
default trusted:kmk 32 2375725ad57798846a9bbd240de8906f006e66c03af53b1b3
82dbbc55be2a44616e4959430436dc4f2a7a9659aa60bb4652aeb2120f149ed197c564e0
24717c64 5972dcb82ab2dde83376d82b2e3c09ffc

$ keyctl pipe 159771175 > evm.blob

Carregue uma chave criptografada "evm" do blob salvo:

$ keyctl add encrypted evm "load `cat evm.blob`" @u
831684262

$ keyctl print 831684262
default trusted:kmk 32 2375725ad57798846a9bbd240de8906f006e66c03af53b1b3
82dbbc55be2a44616e4959430436dc4f2a7a9659aa60bb4652aeb2120f149ed197c564e0
24717c64 5972dcb82ab2dde83376d82b2e3c09ffc

Outros usos para chaves confiáveis ​​e criptografadas, como para criptografia de disco e arquivo, são previstos. Em particular, o novo formato 'ecryptfs' foi definido para usar chaves criptografadas para montar um sistema de arquivos eCryptfs. Mais detalhes sobre o uso podem ser encontrados no arquivo 'Documentation / security / keys-ecryptfs.txt'.

Chandana
fonte
Você tem alguma ideia de quando esses dois novos tipos de chaves foram adicionados? Em qual versão quero dizer. No momento, estou usando 1.2 (pacotes da empresa) e esse não oferece suporte para eles. Talvez em 1.5+?
Acapulco
1
Qual é a fonte desta postagem? O final se refere a um documentoDocumentation/security/keys-ecryptfs.tx
goodguys_activate
Todas parecem ser chamadas para um programa de linha de comando. Não vejo nenhum código sobre como usar o TPM.
Ian Boyd
3

Como posso criptografar bytes usando o módulo TPM de uma máquina?

Depende da sua intenção e circunstâncias:

  • Que tipo de TPM você tem (1 família ou 2 famílias)?
  • Em que estado está o TPM? Foi possuído? Foi provisionado?
  • Qual é a sua linguagem de programação?
  • Você quer criptografar ou assinar? (isso é vago para o resto da pergunta)
  • Qual é o tamanho dos dados que você deseja criptografar?
  • Você deseja usar uma chave simétrica ou uma chave assimétrica?
  • Você deseja usar uma chave que já existe no TPM ou deseja que ele crie a chave primeiro?
  • Por "criptografar" você quer dizer "embrulhar uma chave"?
  • Você deseja bloquear os dados criptografados para a configuração do sistema, de modo que só possam ser descriptografados quando o sistema estiver novamente na mesma configuração?
  • Você deseja solicitar autorização para descriptografar?
  • Talvez você nem precise criptografar, mas sim armazenar os dados no TPM?
  • Se você estiver armazenando os dados no TPM, deseja solicitar autorização ou que o sistema esteja em uma configuração específica para recuperação?

Cada um desses casos de uso (e há mais) - ou uma combinação deles - apresenta um caminho de implementação diferente. Pense no TPM como um canivete suíço de dispositivos criptográficos: não há muito que você não possa fazer com ele, mas a facilidade de uso é prejudicada por sua versatilidade. A pergunta continua oscilando entre criptografar, assinar e bloquear a configuração do sistema, mas a parte principal desta resposta considerará o comando Seal para cobrir a maioria das necessidades descritas na pergunta.

Agora é hora de dar um passo adiante. Desejo criptografar alguns dados (por exemplo, uma chave mestra de criptografia do disco rígido) que só podem ser descriptografados pelo TPM local.

É para isso que serve o comando Bind (substituído pelo comando Criar do TPM 2). Você carrega uma chave que deriva de uma chave associada ao TPM e criptografa com ela (ou diretamente com uma chave associada ao hardware). Dessa forma, os dados só podem ser descriptografados com acesso ao mesmo TPM.

Em outras palavras, desejo substituir o Qualcomm Trusted Execution Environment (TEE) no diagrama de blocos abaixo para Android, pelo TPM no Windows:

Não tenho certeza se replicar todo esse processo é uma boa ideia. Por um lado, não há necessidade de usar uma operação de assinatura em qualquer parte do processo. Parece que, no momento em que o Android 5 estava sendo desenvolvido, a API Keystore estava limitada a operações de assinatura e verificação. Meu melhor palpite é que a equipe de criptografia de disco fez o melhor para trabalhar com o que tinha e desenvolveu um algoritmo em que uma das chaves intermediárias era derivada com uma operação de assinatura , usando uma chave TEE armazenada, amarrando assim todo o processo a um hardware. chave vinculada disponível apenas na plataforma - já que a assinatura era a única maneira de fazer isso no momento. No entanto, não há necessidade de se restringir dessa forma se você tiver acesso a um TPM, que oferece mais recursos do que você sabia que precisava!

Eu percebo que o TPM não faz assinatura de dados

Isso é falso, ambas as versões de TPM suportam assinatura.

(ou se tiver, não garante que assinar os mesmos dados dará a mesma saída binária todas as vezes)

Isso não faz sentido. Assinando os mesmos dados com a mesma chave vai produzir a mesma assinatura. Você pode estar confundindo a operação de assinatura com a operação de cotação, que se misturam em um nonce.

É por isso que eu estaria disposto a substituir "assinatura RSA" por "criptografar um blob de 256 bits com uma chave associada ao hardware".

Esta deve ser a opção preferida, embora ambas sejam possíveis com um TPM. Veja acima.

O problema é que a programação TPM não está documentada no MSDN. Não há API disponível para realizar qualquer operação.

Infelizmente, não há muito para documentar. A API do Win é limitada a algumas funções do TBS que são removidas de um nível do driver.

Em vez disso, você precisa encontrar uma cópia da pilha de software do Trusted Computing Group (também conhecida como TSS), descobrir quais comandos enviar para o TPM, com cargas úteis, em que ordem, e chamar a função Tbsip_Submit_Command do Windows para enviar comandos diretamente:

Na verdade, não, se você tivesse um TSS, não teria que usar Tbsip_submit_Command(). Esse é o objetivo de ter um TSS - os detalhes de baixo nível são abstraídos.

O Windows não tem API de nível superior para realizar ações.

Ainda verdadeiro para o TPM 1, mas para o TPM 2 há TSS.MSR .

É o equivalente moral de tentar criar um arquivo de texto emitindo comandos SATA I / O para seu disco rígido.

Corrigir.

Por que não usar apenas calças ... O problema com esse código é que ele não é portátil para o mundo do Windows. Por exemplo, você não pode usá-lo do Delphi, você não pode usá-lo do C #. Requer: OpenSSL, pThread

Não está claro se este é um desafio intransponível. Acessar TrouSerS por meio de uma interoperabilidade deve ser preferível a reescrever todo o código de estruturação de dados. Além disso, havia doTSSno momento de escrever a pergunta.

Qual é o código equivalente para criptografar dados usando o TPM? Provavelmente envolve o comando TPM_seal. Embora eu ache que não quero lacrar dados, acho que quero vinculá-los:

A pergunta contém uma citação que descreve a diferença entre os dois comandos, portanto, não deve haver muita confusão. A vedação é semelhante à ligação, com a restrição adicional de que o estado do sistema deve ser o mesmo para que os dados sejam removidos.

Da mesma forma, fui capaz de fornecer:

Byte[] ProtectBytes_Crypt(Byte[] plaintext)
{
   //...
   CryptProtectData(...); 
   //...
}

alguém pode fornecer o equivalente correspondente:

Byte[] ProtectBytes_TPM(Byte[] plaintext)
{
   //...
   Tbsip_Submit_Command(...);
   Tbsip_Submit_Command(...);
   Tbsip_Submit_Command(...);
   //...snip...
   Tbsip_Submit_Command(...);
   //...
}

que faz a mesma coisa, exceto que, em vez de uma chave bloqueada no System LSA, é bloqueada no TPM?

Em primeiro lugar, vale ressaltar que existem duas versões principais do TPM, que são totalmente incompatíveis entre si. Portanto, praticamente nenhum código que você escreveu para o TPM 1 funcionará para o TPM 2. A API do TBS é o único código comum entre os dois e, para ser justo com a Microsoft, esse pode ter sido um dos motivos pelos quais essa API nunca cresceu. A parte principal da resposta apresentará o código para TPM 1 por dois motivos:

  • A questão está carregada de conceitos específicos do TPM 1, então as pessoas que usam o TPM 1 são mais propensas a chegar aqui procurando por eles
  • Existe uma implementação da Microsoft do TSS para TPM 2.

Em segundo lugar, vamos tornar a pergunta mais específica. Estou reinterpretando da seguinte maneira:

How do I write code in C#, using only the TBS API, to interface with
an already owned and provisioned TPM to, without user interaction,
encrypt no more than 128 bytes of arbitrary data with an asymmetric
key already resident in the TPM and bound to it, but not protected
with a password, so that in order to decrypt the data the system may
need to be in the same state it was in at encryption time based on an
easily configurable variable?

O comando Seal é mais adequado para isso, pois executa a mesma função que o comando Bind quando o tamanho de seleção de PCR é definido como zero, mas a seleção de PCR pode ser facilmente alterada para incluir quaisquer PCRs que você desejar. Isso faz com que se pergunte por que o comando Bind foi incluído na especificação e, como observado, ele foi removido na especificação TPM 2 e os dois foram combinados em um comando Create.

Aqui está o código C # para usar o comando TPM 1.2 Seal para criptografar dados apenas com funções TBS (nota: este código não foi testado e provavelmente não funcionará sem depuração) :

[DllImport ("tbs.dll")]
unsafe static extern UInt32 Tbsi_Context_Create (UInt32 * version, IntPtr * hContext);

[DllImport ("tbs.dll")]
unsafe static extern UInt32 Tbsip_Context_Close (IntPtr hContext);

[DllImport ("tbs.dll")]
unsafe static extern UInt32 Tbsip_Submit_Command (
    IntPtr hContext, UInt32 Locality, 
    UInt32 Priority, 
    byte * pCommandBuf, 
    UInt32 CommandBufLen, 
    byte * pResultBuf, 
    UInt32 * pResultBufLen);

byte[] ProtectBytes_TPM (byte[] plaintext) {

    void AddUInt32Reversed (byte[] a, System.UInt32 o, ref int i) {
        byte[] bytes = System.BitConverter.GetBytes (o);
        Array.Reverse (bytes);
        Array.Copy (bytes, 0, a, i, bytes.Length);
        i += bytes.Length;
    }
    void AddUInt16Reversed (byte[] a, System.UInt16 o, ref int i) {
        byte[] bytes = System.BitConverter.GetBytes (o);
        Array.Reverse (bytes);
        Array.Copy (bytes, 0, a, i, bytes.Length);
        i += bytes.Length;
    }
    void AddBool (byte[] a, byte b, ref int i) {
        a[i] = b;
        i += 1;
    }
    void AddBlob (byte[] a, byte[] b, ref int i) {
        Array.Copy (b, 0, a, i, b.Length);
        i += b.Length;
    }
    byte[] Xor (byte[] text, byte[] key) {
        byte[] xor = new byte[text.Length];
        for (int i = 0; i < text.Length; i++) {
            xor[i] = (byte) (text[i] ^ key[i % key.Length]);
        }
        return xor;
    }

    int offset;

    Random rnd = new Random ();

    IntPtr hContext = IntPtr.Zero;
    unsafe {
        UInt32 version = 1;
        IntPtr handle = hContext;
        UInt32 result = Tbsi_Context_Create ( & version, & handle);

        if (result == 0) {
            hContext = handle;
        }
    }

    byte[] cmdBuf = new byte[768];

    //OSAP
    System.UInt32 outSize;

    byte[] oddOsap = new byte[20];
    byte[] evenOsap = new byte[20];
    byte[] nonceEven = new byte[20];
    byte[] nonceOdd = new byte[20];
    System.UInt32 hAuth = 0;

    offset = 0;
    AddUInt16Reversed (cmdBuf, 0x00C1, ref offset);
    offset = 6;
    AddUInt32Reversed (cmdBuf, 0x0000000B, ref offset);

    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for command code

    AddUInt16Reversed (cmdBuf, 0x0004, ref offset); //Entity Type SRK = 0x0004
    AddUInt32Reversed (cmdBuf, 0x40000000, ref offset); //Entity Value SRK = 0x40000000
    rnd.NextBytes (oddOsap);
    AddBlob (cmdBuf, oddOsap, ref offset);
    uint cmdSize = (System.UInt32) offset;
    offset = 2;
    AddUInt32Reversed (cmdBuf, cmdSize, ref offset);

    outSize = (System.UInt32) (Marshal.SizeOf (hAuth) + nonceEven.Length + evenOsap.Length);

    byte[] response = new byte[outSize];
    unsafe {
        UInt32 result = 0;

        //uint cmdSize = (uint)offset;
        uint resSize = outSize;
        fixed (byte * pCmd = cmdBuf, pRes = response) {
            result = Tbsip_Submit_Command (hContext, 0, 200, pCmd, cmdSize, pRes, & resSize);
        }
    }

    byte contSession = 0;
    System.UInt32 hKey = 0x40000000; //TPM_KH_SRK;
    System.UInt32 pcrInfoSize = 0;
    byte[] srkAuthdata = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    uint inDataSize = (uint) plaintext.Length;

    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for return code
    byte[] hauthbytes = new byte[Marshal.SizeOf (hAuth)];
    Array.Copy (response, offset, hauthbytes, 0, hauthbytes.Length);
    Array.Reverse (hauthbytes);
    hAuth = System.BitConverter.ToUInt32 (hauthbytes, 0);
    offset += Marshal.SizeOf (hAuth);
    Array.Copy (response, offset, nonceEven, 0, nonceEven.Length);
    offset += nonceEven.Length;
    Array.Copy (response, offset, evenOsap, 0, evenOsap.Length);

    //shared-secret = HMAC(srk_auth, even_osap || odd_osap)
    byte[] sharedSecretBuf = new byte[evenOsap.Length + oddOsap.Length];
    Array.Copy (evenOsap, 0, sharedSecretBuf, 0, evenOsap.Length);
    Array.Copy (oddOsap, 0, sharedSecretBuf, evenOsap.Length, oddOsap.Length);
    System.Security.Cryptography.HMACSHA1 sharedSecretHmac = new System.Security.Cryptography.HMACSHA1 (srkAuthdata);
    byte[] sharedSecret = sharedSecretHmac.ComputeHash (sharedSecretBuf);

    byte[] authSha1InBuf = new byte[sharedSecret.Length + nonceEven.Length];
    Array.Copy (sharedSecret, 0, authSha1InBuf, 0, sharedSecret.Length);
    Array.Copy (nonceEven, 0, authSha1InBuf, sharedSecret.Length, nonceEven.Length);
    System.Security.Cryptography.SHA1Managed sha1 = new System.Security.Cryptography.SHA1Managed ();
    byte[] authSha1 = sha1.ComputeHash (authSha1InBuf);
    byte[] encAuth = Xor (srkAuthdata, authSha1);

    //inParamDigest = sha1(1S ~ 6S) 
    int paramInDigestInBufSize =
        sizeof (System.UInt32) + 
        encAuth.Length +
        Marshal.SizeOf (pcrInfoSize) +
        Marshal.SizeOf (inDataSize) +
        (int) inDataSize;
    byte[] paramInDigestInBuf = new byte[paramInDigestInBufSize];
    offset = 0;
    AddUInt32Reversed (paramInDigestInBuf, 0x00000017, ref offset);
    AddBlob (paramInDigestInBuf, encAuth, ref offset);
    AddUInt32Reversed (paramInDigestInBuf, 0x0, ref offset); //PCR info size
    AddUInt32Reversed (paramInDigestInBuf, inDataSize, ref offset);
    AddBlob (paramInDigestInBuf, plaintext, ref offset);

    byte[] paramInDigest = sha1.ComputeHash (paramInDigestInBuf);

    int pubAuthInBufSize = paramInDigest.Length + nonceEven.Length + nonceOdd.Length + Marshal.SizeOf (contSession);
    byte[] pubAuthInBuf = new byte[pubAuthInBufSize];

    offset = 0;
    AddBlob (pubAuthInBuf, paramInDigest, ref offset);
    AddBlob (pubAuthInBuf, nonceEven, ref offset);
    AddBlob (pubAuthInBuf, nonceOdd, ref offset);
    AddBool (pubAuthInBuf, contSession, ref offset);
    System.Security.Cryptography.HMACSHA1 pubAuthHmac = new System.Security.Cryptography.HMACSHA1 (sharedSecret);
    byte[] pubAuth = pubAuthHmac.ComputeHash (pubAuthInBuf);

    //Seal
    offset = 0;
    AddUInt16Reversed (cmdBuf, 0x00C2, ref offset); // TPM_TAG_RQU_AUTH1_COMMAND;
    offset = 6;
    AddUInt32Reversed (cmdBuf, 0x00000017, ref offset); // TPM_ORD_SEAL;
    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for command code

    AddUInt32Reversed (cmdBuf, hKey, ref offset);
    AddBlob (cmdBuf, encAuth, ref offset);
    AddUInt32Reversed (cmdBuf, pcrInfoSize, ref offset);
    AddUInt32Reversed (cmdBuf, inDataSize, ref offset);
    AddBlob (cmdBuf, plaintext, ref offset);

    AddUInt32Reversed (cmdBuf, hAuth, ref offset);
    AddBlob (cmdBuf, nonceOdd, ref offset);
    AddBool (cmdBuf, contSession, ref offset);
    AddBlob (cmdBuf, pubAuth, ref offset);
    cmdSize = (System.UInt32) offset;
    offset = 2;
    AddUInt32Reversed (cmdBuf, cmdSize, ref offset);

    outSize = 768;
    uint responseSize = 0;

    response = new byte[outSize];
    unsafe {
        UInt32 result = 0;

        uint resSize = outSize;
        fixed (byte * pCmd = cmdBuf, pRes = response) {
            result = Tbsip_Submit_Command (hContext, 0, 200, pCmd, cmdSize, pRes, & resSize);
        }
        responseSize = resSize;
    }

    byte[] retBuffer = new byte[responseSize - 10];
    Array.Copy (response, 10, retBuffer, 0, retBuffer.Length);
    Tbsip_Context_Close (hContext);
    return retBuffer;

}

Análise de código:

[DllImport ("tbs.dll")]
...

Estas são algumas das poucas funções disponíveis em Tbs.h e as únicas que usaremos aqui. Eles basicamente permitem que você abra um identificador para o dispositivo e se comunique com ele enviando e recebendo bytes brutos.

    void AddUInt32Reversed (byte[] a, System.UInt32 o, ref int i) { ... }
    void AddUInt16Reversed (byte[] a, System.UInt16 o, ref int i) { ... }
    void AddBool (byte[] a, byte b, ref int i) { ... }
    void AddBlob (byte[] a, byte[] b, ref int i) { ... }

TPM é big endian, o Windows é little endian. Portanto, a ordem dos bytes terá que ser revertida para todos os dados que estamos enviando. Precisamos apenas nos preocupar em reverter os ints não assinados de 32 bits e 16 bits aqui.

    ...
    UInt32 result = Tbsi_Context_Create ( & version, & handle);
    ...

Aqui, usamos Tbsi_Context_Create () para abrir um identificador para falar com o TPM. O TBS_CONTEXT_PARAMSparâmetro é apenas uma estrutura C com um campo int de 32 bits sem sinal que deve ser definido como 1 para se comunicar com uma instância do TPM 1.2, e é isso que definimos.

    byte[] cmdBuf = new byte[768];

Isso é especificado como o tamanho mínimo do buffer em TPM PC Client Spec . Será mais do que suficiente para nossas necessidades aqui.

TPM 1.2 Spec Parte 3 diz o seguinte:

TPM_Seal requires the encryption of one parameter (“Secret”). For the
sake of uniformity with other commands that require the encryption of
more than one parameter, the string used for XOR encryption is
generated by concatenating a nonce (created during the OSAP session)
with the session shared secret and then hashing the result.

Precisamos criptografar com XOR esse parâmetro "secreto" usando um nonce gerado durante uma sessão OSAP. Um dos identificadores de entrada do comando Seal também é um identificador OSAP:

The authorization session handle used for keyHandle authorization.
Must be an OSAP session for this command.

Portanto, precisamos primeiro estabelecer esta sessão OSAP. OSAP é descrito no TPM 1.2 Spec Parte 1, . OSAP, ou protocolo de autorização específico de objeto, foi inventado para lidar com o caso de uso em que você deseja usar um objeto TPM que requer autorização várias vezes, mas não deseja fornecer autorização a cada vez: uma sessão OSAP é usada em vez disso, que depende sobre o conceito de "segredo compartilhado", que é um HMACque mistura os dados de autorização do objeto com nonces gerados em cada lado para evitar ataques de resposta. Portanto, o "segredo compartilhado" só é conhecido pelos dois lados desta sessão: o lado que iniciou a sessão (usuário) e o lado que o aceitou (TPM); além disso, ambos os lados devem ter os mesmos dados de autorização de objeto para que o "segredo compartilhado" seja o mesmo; além disso, o "segredo compartilhado" usado em uma sessão será inválido em outra. Este diagrama da especificação descreve o processo:

OSAP

Não usaremos várias sessões neste caso específico (na verdade, esse parâmetro é ignorado com o comando Seal!) E a chave que usaremos não requer autorização, mas infelizmente ainda estamos vinculados à especificação para estabelecer um OSAP sessão.

    offset = 0;
    AddUInt16Reversed (cmdBuf, 0x00C1, ref offset);
    offset = 6;
    AddUInt32Reversed (cmdBuf, 0x0000000B, ref offset);

    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for command code

    AddUInt16Reversed (cmdBuf, 0x0004, ref offset); //Entity Type SRK = 0x0004
    AddUInt32Reversed (cmdBuf, 0x40000000, ref offset); //Entity Value SRK = 0x40000000
    rnd.NextBytes (oddOsap);
    AddBlob (cmdBuf, oddOsap, ref offset);
    uint cmdSize = (System.UInt32) offset;

Os operandos do comando TPM_OSAP são:

Operandos TPM_OSAP

Cada comando do TPM 1.2 é apresentado assim:

  2 bytes       4 bytes             4 bytes
+---------+------------------+------------------+---------------------------
|   Tag   |       Size       |   Command code   |    Command body    ....
+---------+------------------+------------------+---------------------------

A tag é um valor de dois bytes que indica se o que se segue é entrada ou saída e se há algum valor de dados de autenticação seguindo os parâmetros de comando. Para TPM_OSAP, a tag deve ser TPM_TAG_RQU_COMMAND (0x00C1) conforme a especificação, o que significa "um comando sem autorização".

Tamanho é um valor de quatro bytes que especifica o tamanho do comando em bytes, incluindo a tag e o próprio tamanho. Vamos definir esse valor mais tarde, uma vez que o tenhamos calculado.

O código de comando é um valor de quatro bytes que serve como um ID de comando: ele informa ao TPM como interpretar o restante do comando. Nosso código de comando aqui é TPM_OSAP (0x0000000B).

As próximas duas coisas a definir são o tipo de entidade e o valor da entidade. Uma vez que queremos usar uma chave que já existe no TPM, usaremos o tipo de entidade "SRK" (0x0004) e, uma vez que estamos trabalhando sob a suposição de que o TPM já pertenceu, é seguro assumir que ele foi um SRK carregado sob o identificador permanente 0x40000000 de acordo com as especificações, portanto, usaremos esse valor de identificador permanente para nosso valor de entidade. (SRK significa "Storage Root Key" e é a chave raiz da qual derivam a maioria das outras chaves de propriedade do TPM)

    result = Tbsip_Submit_Command (hContext, 0, 200, pCmd, cmdSize, pRes, & resSize);

Por fim, calculamos o tamanho do comando, definimos e enviamos o comando.

    offset = 2 + 4 + 4; //2 for tag, 4 for size and 4 for return code
    byte[] hauthbytes = new byte[Marshal.SizeOf (hAuth)];
    Array.Copy (response, offset, hauthbytes, 0, hauthbytes.Length);
    Array.Reverse (hauthbytes);
    hAuth = System.BitConverter.ToUInt32 (hauthbytes, 0);
    offset += Marshal.SizeOf (hAuth);
    Array.Copy (response, offset, nonceEven, 0, nonceEven.Length);
    offset += nonceEven.Length;
    Array.Copy (response, offset, evenOsap, 0, evenOsap.Length);

Os dados que devemos obter do TPM no TPM_OSAP são:

Resposta TPM_OSAP

Então, voltamos:

  • O identificador de autorização para usar com nosso comando principal (Selo)
  • nonceEven: o nonce gerado pelo TPM para usar com o comando principal
  • nonceEvenOSAP: o OSAP nonce que é o contraponto ao nonce que geramos em nosso lado antes de enviar o comando TPM_OSAP. Esses dois nonces serão usados ​​na geração do "segredo compartilhado".

Extraímos esses valores e os armazenamos em variáveis.

    byte[] sharedSecretBuf = new byte[evenOsap.Length + oddOsap.Length];
    Array.Copy (evenOsap, 0, sharedSecretBuf, 0, evenOsap.Length);
    Array.Copy (oddOsap, 0, sharedSecretBuf, evenOsap.Length, oddOsap.Length);
    System.Security.Cryptography.HMACSHA1 sharedSecretHmac = new System.Security.Cryptography.HMACSHA1 (srkAuthdata);
    byte[] sharedSecret = sharedSecretHmac.ComputeHash (sharedSecretBuf);

Em seguida, calculamos o "segredo compartilhado". De acordo com as especificações, os valores que entram no cálculo são os dois nonces OSAP (um gerado pelo usuário e outro gerado pelo TPM) e o valor de autorização para a chave que queremos usar - o SRK. Por convenção, o valor de autenticação SRK é a "autenticação bem conhecida": um buffer de 20 bytes zerado. Tecnicamente, pode-se alterar esse valor para outra coisa ao assumir a propriedade do TPM, mas isso não é feito na prática, portanto, podemos presumir com segurança que o valor "autenticação bem conhecida" é bom.

A seguir, vamos dar uma olhada no que se passa no comando TPM_Seal:

TPM_Seal

Muitos desses parâmetros são fáceis de construir, exceto por dois deles: encAuthe pubAuth. Vamos examiná-los um por um.

encAuthé "O AuthData criptografado para os dados lacrados." Nosso AuthData aqui é o "auth bem conhecido" de antes, mas ainda temos que criptografá-lo. Como estamos usando uma sessão OSAP, ela é criptografada de acordo com ADIP, ou Authorization-Data Insertion Protocol. Da especificação: "O ADIP permite a criação de novas entidades e a inserção segura da nova entidade AuthData. A transmissão do novo AuthData usa criptografia com a chave baseada no segredo compartilhado de uma sessão OSAP." Além disso: "Para o algoritmo de criptografia XOR obrigatório, o criador constrói uma chave de criptografia usando um hash SHA-1 do segredo compartilhado OSAP e um nonce de sessão. O criador XOR criptografa o novo AuthData usando a chave de criptografia como um teclado único e envia esses dados criptografados junto com a solicitação de criação para o TPM. "

O diagrama a seguir explica como o ADIP opera:

UM MERGULHO

pubAuthé "O resumo da sessão de autorização para entradas e keyHandle." A parte 1 da especificação, em "Declarações de parâmetro para exemplos OIAP e OSAP", explica como interpretar a tabela de parâmetros TPM_Seal acima: "A coluna HMAC # detalha os parâmetros usados ​​no cálculo HMAC. Os parâmetros 1S, 2S, etc. são concatenados e hash para inParamDigest ou outParamDigest, implicitamente denominado 1H1 e possivelmente 1H2 se houver duas sessões de autorização. Para a primeira sessão, 1H1, 2H1, 3H1 e 4H1 são concatenados e HMAC'ed. Para a segunda sessão, 1H2, 2H2, 3H2, e 4H2 são concatenados e HMAC'ed. " Então, teremos que fazer o hash do texto simples, seu tamanho, tamanho da informação de PCR, encAuthde cima e do ordinal TPM_Seal, e então HMAC que com os dois nonces e o booleano "continue session" usando o OSAP "

Juntando tudo em um diagrama:

computação pubAuth

Observe como definimos "PCR info size" como zero neste código, pois queremos apenas criptografar os dados sem bloqueá-los no estado do sistema. No entanto, é trivial fornecer uma estrutura de "informação de PCR" se necessário.

    offset = 0;
    AddUInt16Reversed (cmdBuf, 0x00C2, ref offset); 
    offset = 6;
    AddUInt32Reversed (cmdBuf, 0x00000017, ref offset); // TPM_ORD_SEAL;
    ...
    result = Tbsip_Submit_Command (hContext, 0, 200, pCmd, cmdSize, pRes, & resSize);

Finalmente, construímos o comando e o enviamos.

    byte[] retBuffer = new byte[responseSize - 10];
    Array.Copy (response, 10, retBuffer, 0, retBuffer.Length);
    Tbsip_Context_Close (hContext);
    return retBuffer;

Usamos a função Tbsip_Context_Close () para fechar nosso identificador de comunicação.

Retornamos a resposta como está aqui. O ideal é reverter os bytes novamente e validá-los recalculando o resAuthvalor para evitar ataques man-in-the-middle.


O que é confuso é que não há comando Tspi_Data_Bind.

Isso ocorre porque Tspi_Data_Bind é um comando TSS, não um comando TPM. A razão é porque não requer segredos (apenas a chave pública é usada), portanto, pode ser feito sem envolver o TPM. Isso causou confusão, no entanto, e até mesmo os comandos que não exigem segredos agora estão incluídos na especificação do TPM 2.

Como posso criptografar uma chave com a chave pública do TPM?

Depende da versão do TPM. Com o comando TPM_CreateWrapKey para TPM 1.2. Com o comando TPM2_Create para TPM 2.

Como um desenvolvedor bloqueia uma chave para o TPM?

Crie-o no TPM ou envolva-o ou use qualquer outro método disponível.

TPM2_Create, especificando uma chave HMAC

O texto do livro é confuso. Você não especifica a chave HMAC , você especifica que deseja uma chave HMAC .

O fato de a chave HMAC não ser secreta faz sentido

Não, não faz sentido. A chave é secreta.

... use as chaves enquanto as mantém seguras em um dispositivo de hardware ... Excelente! Como você faz isso!?

Existem comandos para criar chaves ou importá-las para ambas as versões do TPM. Para o TPM 1, havia apenas uma chave raiz - a SRK - a partir da qual você poderia estabelecer uma hierarquia de chaves criando chaves agrupadas. Com o TPM 2, você pode ter várias chaves primárias ou raiz.

O TPM tem a capacidade de gerar chaves criptográficas e proteger seus segredos dentro de um limite de hardware? É assim como?

Veja acima.

Excelente! Este é exatamente o caso de uso que desejo. É também o caso de uso para o qual a Microsoft usa o TPM. Como eu faço isso!?

Provavelmente depende do tipo de unidade. No caso de unidades não SED, a chave de criptografia da unidade provavelmente está agrupada com uma chave TPM. No caso de unidades SED, a senha Admin1 (ou semelhante) é selada com o TPM.

A chave de endosso ou EK ... Em algum lugar dentro do TPM está uma chave privada RSA. Essa chave está trancada lá - para nunca ser vista pelo mundo exterior. Quero que o TPM assine algo com sua chave privada (ou seja, criptografe-o com sua chave privada).

O EK não é uma chave de assinatura - é uma chave de criptografia. No entanto, não é uma chave de criptografia de uso geral: ela só pode ser usada em determinados contextos .

Mas o que eu realmente quero fazer é "selar" alguns dados

Veja acima.

mnístico
fonte
2

Quando diz

especificando a chave HMAC

NÃO significa fornecer a chave HMAC - significa "apontar para a chave HMAC que deseja usar" .

Os TPMs podem usar um número virtualmente ilimitado de chaves HMAC, como é indicado no livro. Você tem que dizer ao TPM qual usar.

DCC
fonte
Portanto, há talvez um exemplo de código mostrando como especificar (apontar para) a chave HMAC a ser usada em C # ou em outro idioma?
Chad