Cómo cifrar bytes usando el TPM (Módulo de plataforma segura)

110

¿Cómo puedo cifrar bytes usando el módulo TPM de una máquina?

CryptProtectData

Windows proporciona una API (relativamente) simple para cifrar un blob usando la CryptProtectDataAPI, que podemos envolver en una función fácil de usar:

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

Los detalles de ProtectBytesson menos importantes que la idea de que puedes usarlo con bastante facilidad:

  • aquí están los bytes que quiero encriptados por una clave secreta en el System
  • devuélveme la gota encriptada

El blob devuelto es una estructura de documentación no documentada que contiene todo lo necesario para descifrar y devolver los datos originales (algoritmo hash, algoritmo de cifrado, sal, firma HMAC, etc.).

Para completar, aquí está la implementación de pseudocódigo de muestra ProtectBytesque usa Crypt APIpara proteger 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.
}

¿Cómo hacer lo mismo con el TPM?

El código anterior es útil para cifrar datos solo para la máquina local. Los datos se cifran utilizando la Systemcuenta como generador de claves (los detalles, aunque interesantes, no son importantes ). El resultado final es que puedo cifrar datos (por ejemplo, una clave maestra de cifrado de disco duro) que solo puede descifrar la máquina local.

Ahora es el momento de dar un paso más. Quiero cifrar algunos datos (por ejemplo, una clave maestra de cifrado de disco duro) que solo puede descifrar el TPM local. En otras palabras, quiero reemplazar Qualcomm Trusted Execution Environment ( TEE ) en el diagrama de bloques a continuación para Android, con el TPM en Windows:

ingrese la descripción de la imagen aquí

Nota : Me doy cuenta de que el TPM no realiza la firma de datos (o si lo hace, no garantiza que la firma de los mismos datos dé la misma salida binaria cada vez). Es por eso que estaría dispuesto a reemplazar la "firma RSA" por "cifrar un blob de 256 bits con una clave vinculada al hardware" .

Entonces, ¿dónde está el código?

El problema es que la programación de TPM está completamente indocumentada en MSDN . No hay ninguna API disponible para realizar ninguna operación. En su lugar, debe buscar una copia de la pila de software de Trusted Computing Group (también conocida como TSS) , averiguar qué comandos enviar al TPM, con cargas útiles, en qué orden, y llamar a la función Tbsip_Submit_Command de Windows para enviar comandos directamente:

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
);

Windows no tiene una API de nivel superior para realizar acciones.

Es el equivalente moral de intentar crear un archivo de texto mediante la emisión de comandos de E / S SATA a su disco duro .

¿Por qué no usar pantalones?

Trusted Computing Group (TCG) definió su propia API: TCB Software Stack (TSS) . Algunas personas crearon una implementación de esta API y se llama TrouSerS . Luego, un chico transfirió ese proyecto a Windows .

El problema con ese código es que no es portátil en el mundo de Windows. Por ejemplo, no puede usarlo desde Delphi, no puede usarlo desde C #. Requiere:

  • OpenSSL
  • pThread

Solo quiero que el código cifre algo con mi TPM.

Lo anterior CryptProtectDatano requiere nada más que lo que está en el cuerpo de la función.

¿Cuál es el código equivalente para cifrar datos usando el TPM? Como han señalado otros, probablemente tenga que consultar los tres manuales de TPM y construir los blobs usted mismo . Probablemente implique el TPM_sealcomando. Aunque creo que no quiero sellar datos, creo que quiero vincularlos :

Enlace : cifra los datos mediante la clave de enlace TPM, una clave RSA única derivada de una clave de almacenamiento. Sellado : cifra los datos de manera similar a la vinculación, pero además especifica un estado en el que debe estar TPM para que los datos se descifren (desbloqueen)

Intento leer los tres volúmenes requeridos para encontrar las 20 líneas de código que necesito:

Pero no tengo ninguna idea de lo que estoy leyendo. Si hubiera algún tipo de tutorial o ejemplos, podría tener una oportunidad. Pero estoy completamente perdido.

Entonces le preguntamos a Stackoverflow

De la misma manera pude proporcionar:

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

¿Alguien puede proporcionar el equivalente correspondiente:

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

que hace lo mismo, excepto que en lugar de una llave guardada en SystemLSA, está guardada en el TPM?

Inicio de la investigación

No sé exactamente qué significa enlazar . Pero mirando TPM Main - Part 3 Commands - Specification Version 1.2, hay una mención de bind :

10.3 TPM_UnBind

TPM_UnBind toma el blob de datos que es el resultado de un comando Tspi_Data_Bind y lo descifra para exportarlo al usuario. La persona que llama debe autorizar el uso de la clave que descifrará el blob entrante. TPM_UnBind opera bloque por bloque y no tiene noción de ninguna relación entre un bloque y otro.

Lo que es confuso no es ningún Tspi_Data_Bindcomando.

Esfuerzo de investigación

Es espantoso cómo nadie se ha molestado nunca en documentar el TPM o su funcionamiento. Es como si hubieran pasado todo el tiempo ideando algo genial para jugar, pero no quisieran lidiar con el doloroso paso de hacerlo utilizable para algo.

Comenzando con el (ahora) libro 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

El TPM tiene acceso a una clave privada autogenerada, por lo que puede cifrar claves con una clave pública y luego almacenar el blob resultante en el disco duro. De esta manera, el TPM puede mantener un número virtualmente ilimitado de claves disponibles para su uso, pero sin desperdiciar un valioso almacenamiento interno. Las claves almacenadas en el disco duro se pueden borrar, pero también se pueden hacer copias de seguridad, lo que a los diseñadores les pareció una compensación aceptable.

¿Cómo puedo cifrar una clave con la clave pública de TPM?

Capítulo 4: Aplicaciones existentes que utilizan TPM

Aplicaciones que deberían usar el TPM pero no lo hacen

En los últimos años, ha aumentado el número de aplicaciones basadas en web. Entre ellos se encuentran el respaldo y el almacenamiento basados ​​en la web. Actualmente, un gran número de empresas ofrecen dichos servicios, pero hasta donde sabemos, ninguno de los clientes de estos servicios permite que el usuario bloquee la clave del servicio de respaldo en un TPM. Si se hiciera esto, sin duda sería bueno si se hiciera una copia de seguridad de la clave TPM duplicándola en varias máquinas. Esta parece ser una oportunidad para los desarrolladores.

¿Cómo bloquea un desarrollador una clave para el TPM?

Capítulo 9 - Jerarquías

CASO DE USO: ALMACENAMIENTO DE CONTRASEÑAS DE INICIO DE SESIÓN

Un archivo de contraseña típico almacena hashes de contraseñas. La verificación consiste en salar y aplicar hash a una contraseña proporcionada y compararla con el valor almacenado. Debido a que el cálculo no incluye un secreto, está sujeto a un ataque sin conexión al archivo de contraseña.

Este caso de uso utiliza una clave HMAC generada por TPM. El archivo de contraseña almacena un HMAC de la contraseña con sal. La verificación consiste en salar y HMACing la contraseña proporcionada y compararla con el valor almacenado. Debido a que un atacante fuera de línea no tiene la clave HMAC, el atacante no puede montar un ataque realizando el cálculo.

Esto podría funcionar. Si el TPM tiene una clave HMAC secreta, y solo mi TPM conoce la clave HMAC, entonces podría reemplazar "Firmar (también conocido como TPM encriptar con su clave privada)" por "HMAC". Pero luego, en la siguiente línea, se invierte completamente:

TPM2_Create, especificando una clave HMAC

No es un secreto de TPM si tengo que especificar la clave HMAC. El hecho de que la clave HMAC no sea secreta tiene sentido cuando se da cuenta de que este es el capítulo sobre las utilidades criptográficas que proporciona el TPM. En lugar de tener que escribir SHA2, AES, HMAC o RSA usted mismo, puede reutilizar lo que el TPM ya tiene por ahí.

Capítulo 10 - Llaves

Como dispositivo de seguridad, la capacidad de una aplicación para usar claves mientras las mantiene seguras en un dispositivo de hardware es la mayor fortaleza del TPM. El TPM puede generar e importar claves generadas externamente. Es compatible con claves asimétricas y simétricas.

¡Excelente! ¿¡Cómo lo haces!?

Generador de llaves

Podría decirse que la mayor fortaleza del TPM es su capacidad para generar una clave criptográfica y proteger su secreto dentro de un límite de hardware. El generador de claves se basa en el propio generador de números aleatorios del TPM y no depende de fuentes externas de aleatoriedad. De este modo, elimina las debilidades basadas en software débil con una fuente de entropía insuficiente.

¿ El TPM tiene la capacidad de generar claves criptográficas y proteger sus secretos dentro de un límite de hardware? Es así, ¿cómo?

Capítulo 12 - Registros de configuración de plataforma

ITP para autorización

CASO DE USO: SELLADO DE UNA CLAVE DE CIFRADO DE DISCO DURO A LA PLATAFORMA ESTADO

Las aplicaciones de cifrado de disco completo son mucho más seguras si un TPM protege la clave de cifrado que si está almacenada en el mismo disco, protegida solo por una contraseña. Primero, el hardware TPM tiene protección anti-martilleo (consulte el Capítulo 8 para obtener una descripción detallada de la protección contra ataques de diccionario TPM), lo que hace que un ataque de fuerza bruta a la contraseña sea poco práctico. Una clave protegida solo por software es mucho más vulnerable a una contraseña débil. En segundo lugar, una clave de software almacenada en el disco es mucho más fácil de robar. Tome el disco (o una copia de seguridad del disco) y obtendrá la clave. Cuando un TPM tiene la clave, se debe robar toda la plataforma, o al menos el disco y la placa base.

El sellado permite que la clave esté protegida no solo por una contraseña sino también por una política. Una política típica bloquea la clave de los valores de PCR (el estado del software) actuales en el momento del sellado. Esto supone que el estado en el primer arranque no está comprometido. Cualquier malware preinstalado presente en el primer arranque se mediría en los PCR y, por lo tanto, la clave se sellaría a un estado de software comprometido. Una empresa menos confiada podría tener una imagen de disco estándar y sellar los PCR que representan esa imagen. Estos valores de PCR se calcularían previamente en una plataforma presumiblemente más confiable. Una empresa aún más sofisticada usaría TPM2_PolicyAuthorize y proporcionaría varios tickets que autorizan un conjunto de valores de PCR confiables. Consulte el Capítulo 14 para obtener una descripción detallada de la autorización de la política y su aplicación para resolver el problema de la fragilidad de la PCR.

Aunque una contraseña también podría proteger la clave, existe una ganancia de seguridad incluso sin una contraseña de clave TPM. Un atacante podría iniciar la plataforma sin proporcionar una contraseña de TPMkey, pero no podría iniciar sesión sin el nombre de usuario y la contraseña del sistema operativo. OSsecurity protege los datos. El atacante podría iniciar un sistema operativo alternativo, digamos desde un DVD en vivo o una memoria USB en lugar de desde el disco duro, para evitar la seguridad de inicio de sesión del sistema operativo. Sin embargo, esta configuración de arranque y software diferentes cambiarían los valores de PCR. Debido a que estos nuevos PCR no coincidirían con los valores sellados, el TPM no liberaría la clave de descifrado y el disco duro no se podría descifrar.

¡Excelente! Este es exactamente el caso de uso que quiero. También es el caso de uso para el que Microsoft usa el TPM. ¿¡Cómo lo hago!?

Así que leí todo el libro y no proporcionó nada útil. Lo cual es bastante impresionante porque tiene 375 páginas. Te preguntas qué contenía el libro y, mirando hacia atrás, no tengo ni idea.

Así que renunciamos a la guía definitiva para programar el TPM y, en su lugar, recurrimos a alguna documentación de Microsoft:

Desde el kit de herramientas del proveedor de cifrado de la plataforma Microsoft TPM . Menciona exactamente lo que quiero hacer:

La clave de respaldo o EK

El EK está diseñado para proporcionar un identificador criptográfico confiable para la plataforma. Una empresa puede mantener una base de datos de las claves de aprobación que pertenecen a los TPM de todas las PC de su empresa, o un controlador de estructura del centro de datos puede tener una base de datos de los TPM en todos los blades. En Windows, puede utilizar el proveedor NCrypt descrito en la sección "Proveedor de cifrado de plataforma en Windows 8" para leer la parte pública del EK.

En algún lugar dentro del TPM hay una clave privada RSA. Esa llave está guardada allí, para que el mundo exterior nunca la vea. Quiero que el TPM firme algo con su clave privada (es decir, que lo cifre con su clave privada).

Entonces quiero la operación más básica que pueda existir:

ingrese la descripción de la imagen aquí

Cifre algo con su clave privada. Ni siquiera estoy pidiendo (todavía) las cosas más complicadas:

  • "sellarlo" según el estado de la PCR
  • crear una clave y almacenarla en memroy volátil o no volátil
  • creando una clave simétrica e intentando cargarla en el TPM

Estoy solicitando la operación más básica que puede hacer un TPM. ¿Por qué es imposible obtener información sobre cómo hacerlo?

Puedo obtener datos aleatorios

Supongo que estaba siendo simplista cuando dije que la firma de RSA era lo más básico que puede hacer el TPM. Lo más básico que se le puede pedir al TPM es que me dé bytes aleatorios. Que he descubierto cómo hacer:

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;
}

La cosa elegante

Me doy cuenta de que el volumen de personas que utilizan el TPM es muy bajo. Es por eso que nadie en Stackoverflow tiene una respuesta. Así que realmente no puedo ser demasiado codicioso para encontrar una solución a mi problema común. Pero lo que realmente me gustaría hacer es "sellar" algunos datos:

ingrese la descripción de la imagen aquí

  • presentar al TPM algunos datos (por ejemplo, 32 bytes de material clave)
  • hacer que el TPM encripte los datos, devolviendo una estructura de blob opaca
  • luego pida al TPM que descifre el blob
  • el descifrado solo funcionará si los registros de PCR del TPM son los mismos que durante el cifrado.

En otras palabras:

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

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

Cryptography Next Gen (Cng, también conocido como BCrypt) admite TPM

La API de criptografía original en Windows se conoce como la API de criptografía.

A partir de Windows Vista, la Crypto API ha sido reemplazada por Cryptography API: Next Generation (internamente conocida como BestCrypt , abreviada como BCrypt , que no debe confundirse con el algoritmo hash de contraseñas ).

Windows se envía con dos proveedores de BCrypt :

  • Proveedor primitivo de Microsoft ( MS_PRIMITIVE_PROVIDER) predeterminado : implementación de software predeterminada de todas las primitivas (hash, cifrado simétrico, firmas digitales, etc.)
  • Proveedor de cifrado de plataforma Microsoft ( MS_PLATFORM_CRYPTO_PROVIDER): proveedor que proporciona acceso a TPM

El proveedor de Platform Crypto no está documentado en MSDN, pero tiene documentación de un sitio de Microsoft Research de 2012:

Kit de herramientas para proveedores de cifrado de plataforma TPM

El proveedor de herramientas y el proveedor de cifrado de la plataforma TPM contiene código de muestra, utilidades y documentación para usar la funcionalidad relacionada con TPM en Windows 8. Los subsistemas descritos incluyen el proveedor de cifrado de la plataforma Crypto-Next-Gen (CNG) respaldado por TPM y cómo los proveedores de servicios de certificación puede utilizar las nuevas funciones de Windows. Se admiten los sistemas basados ​​en TPM1.2 y TPM2.0.

Parece que la intención de Microsoft es mostrar la funcionalidad de cifrado de TPM con el proveedor de cifrado de plataforma de Microsoft de la API de Cryptography NG .

Cifrado de clave pública con Microsoft BCrypt

Dado que:

un camino a seguir podría ser la de encontrar la manera de hacer la firma digital utilizando la API de criptografía de Microsoft de próxima generación .

Mi siguiente paso será crear el código para realizar el cifrado en BCrypt, con una clave pública RSA, utilizando el proveedor estándar ( MS_PRIMITIVE_PROVIDER). P.ej:

  • 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

Con ese código en funcionamiento, es posible que pueda cambiar al proveedor de TPM ( MS_PLATFORM_CRYPTO_PROVIDER).

22/02/2016: Y como Apple se ve obligada a ayudar a descifrar los datos del usuario, existe un interés renovado en cómo hacer que el TPM realice la tarea más simple para la que fue inventado: cifrar algo.

Es aproximadamente equivalente a que todo el mundo tenga un coche, pero nadie sabe cómo arrancar uno. Puede hacer cosas realmente útiles y geniales, si tan solo pudiéramos pasar del Paso 1 .

Lectura adicional

Ian Boyd
fuente
Para la vinculación (cifrado) no hay una función explícita disponible y tampoco es necesaria. Simplemente crea una clave de enlace en el TPM y usa su parte pública para cifrar una clave de cifrado simétrica sek con la función de cifrado rsa de los sistemas ("RSA / ECB / OAEPWithSHA1AndMGF1Padding") y guárdalo en la estructura correcta ("TcTssConstants.TSS_ENCDATA_BIND"). Para desvincular (descifrar) el sek, simplemente use la función de desvinculación de TPM y use el sek en cualquier función de cifrado simétrico que desee. Tengo un código base bastante antiguo para eso que hice hace algún tiempo, tal vez ayude: goo.gl/jV1Ouw
evildead
De wikipedia, Binding: cifra los datos mediante la clave de enlace TPM, una clave RSA única que desciende de una clave de almacenamiento. en.wikipedia.org/wiki/Trusted_Platform_Module Parece que este par de comandos (TSpi_Data_Bind / TPM_UnBind) debería ser suficiente para sus necesidades ...
Alex Mazzariol
1
No creo que tengas que usar el TPM directamente. Es compatible a través de las API estándar CNG / NCryptXXX y el "Proveedor de cifrado de plataforma Microsoft" (para plataformas recientes del sistema operativo Windows, y si el hardware está bien y habilitado, por supuesto). Tal vez pueda echar un vistazo al "Kit de herramientas para proveedores de cifrado de la plataforma TPM aquí: research.microsoft.com/en-us/downloads/… también verifique esto: tiw2013.cse.psu.edu/slides/…
Simon Mourier
CryptProtectData no usa necesariamente el TPM. Por otro lado, si puede obtener un identificador CNG o CSP válido para el TPM, puede usarlo en funciones criptográficas.
Michael Chourdakis
1
@ b3nj1 No, no lo estaba; nadie pudo responder la pregunta.
Ian Boyd

Respuestas:

7

Cebador

Todo lo que sigue es sobre TPM 1.2. Tenga en cuenta que Microsoft requiere un TPM 2.0 para todas las versiones futuras de Windows. La generación 2.0 es fundamentalmente diferente a la 1.2

No existe una solución de una línea debido a los principios de diseño de TPM. Piense en el TPM como un microcontrolador con recursos limitados. Su principal objetivo de diseño era ser barato y, al mismo tiempo, seguro. Así que el TPM fue despojado de toda lógica que no era necesaria para una operación segura. Por lo tanto, un TPM solo funciona cuando tiene al menos algún software más o menos pesado , emitiendo muchos comandos en el orden correcto. Y esas secuencias de comandos pueden volverse muy complejas. Es por eso que TCG especificó el TSS con una API bien definida. Si desea seguir el camino de Java, existe incluso una API de Java de alto nivel . No tengo conocimiento de un proyecto similar para C # / .net

Desarrollo

En su caso, le sugiero que consulte el software TPM de IBM.

En el paquete encontrará 3 componentes muy útiles:

  • un emulador de software TPM
  • una lib tpm ligera
  • algunas utilidades básicas de la línea de comandos

No necesita necesariamente el emulador de software TPM, también puede conectarse al HW TPM de la máquina. Sin embargo, puede interceptar los comandos emitidos y observar las respuestas, aprendiendo así cómo se ensamblan y cómo se corresponden con la especificación del comando.

Nivel alto

Prerrequisitos:

  1. TPM está activado
  2. El controlador TPM está cargado
  3. se ha hecho cargo del TPM

Para sellar una gota, debe hacer lo siguiente:

  1. crear una clave
  2. guardar el blob de claves en algún lugar
  3. asegúrese de que la clave esté cargada en el TPM
  4. sellar la mancha

Para abrir es necesario:

  1. obtener el blob de claves
  2. cargar la clave al TPM
  3. abrir la mancha sellada

Puede almacenar el blob de claves en su estructura de datos que usa para almacenar los bytes protegidos.

La mayoría de los comandos de TPM que necesita están autorizados. Por lo tanto, debe establecer sesiones de autorización donde sea necesario. AFAIR, esas son en su mayoría sesiones OSAP.

Comandos TPM

Actualmente no puedo ejecutar una versión de depuración, por lo que no puedo proporcionarles la secuencia exacta. Así que considere esto como una lista desordenada de comandos que tendrá que usar:

  • TPM_OSAP
  • TPM_CreateWrapKey
  • TPM_LoadKey2
  • TPM_Seal

Si también desea leer los valores actuales de PCR:

  • TPM_PCRRead
Escolito
fuente
Microsoft tiene su biblioteca administrada C # .NET para usar el TPM . También tienen un emulador de TPM , al que la biblioteca administrada puede conectarse como una alternativa de depuración si no hay un TPM real. También tienen el kit de herramientas del proveedor de la plataforma TPM , que contiene documentación y código de muestra para usar el TPM. Ahora, si tan solo alguien pudiera descubrir cómo usar el TPM para cifrar bytes.
Ian Boyd
Sus dos primeros enlaces son solo TPM 2.0. Si desea utilizar estos, me temo que no seré de ayuda.
Scolytus
4

Claves de confianza y cifradas

Las claves fiables y cifradas son dos nuevos tipos de claves añadidos al servicio de anillo de claves del kernel existente. Ambos tipos nuevos son claves simétricas de longitud variable y, en ambos casos, todas las claves se crean en el kernel y el espacio de usuario ve, almacena y carga solo blobs cifrados. Las claves de confianza requieren la disponibilidad de un chip de módulo de plataforma segura (TPM) para una mayor seguridad, mientras que las claves cifradas se pueden utilizar en cualquier sistema. Todos los blobs de nivel de usuario se muestran y cargan en ascii hexadecimal para mayor comodidad y se verifica su integridad.

Las claves de confianza utilizan un TPM tanto para generar como para sellar las claves. Las claves se sellan con una clave RSA de 2048 bits en el TPM y, opcionalmente, se sellan a valores especificados de PCR (medición de integridad) y solo se desbloquean mediante el TPM, si las PCR y las verificaciones de integridad de blob coinciden. Una clave de confianza cargada se puede actualizar con nuevos (futuros) valores de PCR, por lo que las claves se migran fácilmente a nuevos valores de pcr, como cuando se actualizan el kernel y initramfs. La misma clave puede tener muchos blobs guardados con diferentes valores de PCR, por lo que se admiten fácilmente varios arranques.

De forma predeterminada, las claves de confianza están selladas bajo SRK, que tiene el valor de autorización predeterminado (20 ceros). Esto se puede configurar en el momento takeownership con la utilidad del pantalón: 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 printdevuelve una copia hexadecimal ascii de la clave sellada, que está en formato estándar TPM_STORED_DATA. La longitud de la clave para las claves nuevas siempre está en bytes. Las claves de confianza pueden ser de 32 a 128 bytes (256 a 1024 bits), el límite superior debe ajustarse a la longitud de clave SRK (RSA) de 2048 bits, con toda la estructura / relleno necesarios.

Las claves cifradas no dependen de un TPM y son más rápidas, ya que utilizan AES para el cifrado / descifrado. Las nuevas claves se crean a partir de números aleatorios generados por el kernel y se cifran / descifran utilizando una clave 'maestra' especificada. La clave 'maestra' puede ser una clave confiable o un tipo de clave de usuario. La principal desventaja de las claves cifradas es que si no están enraizadas en una clave de confianza, son tan seguras como la clave de usuario que las cifra. Por lo tanto, la clave de usuario maestra debe cargarse de la manera más segura posible, preferiblemente al principio del inicio.

La parte descifrada de las claves cifradas puede contener una clave simétrica simple o una estructura más compleja. El formato de la estructura más compleja es específico de la aplicación, que se identifica 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'

Ejemplos de uso de claves cifradas y confiables

Cree y guarde una clave de confianza llamada "kmk" de 32 bytes de longitud:

$ 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

Cargue una clave de confianza desde el blob guardado:

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

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

Vuelva a sellar una clave de confianza con nuevos valores de pcr:

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

El consumidor inicial de claves de confianza es EVM, que en el momento del arranque necesita una clave simétrica de alta calidad para la protección HMAC de metadatos de archivos. El uso de una clave confiable proporciona sólidas garantías de que la clave EVM no se ha visto comprometida por un problema de nivel de usuario y, cuando está sellada a valores específicos de PCR de inicio, protege contra ataques de inicio y fuera de línea. Cree y guarde una clave cifrada "evm" utilizando la clave de confianza "kmk" anterior:

opción 1: omitir 'formato'

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

opción 2: definir explícitamente 'formato' como 'predeterminado'

$ 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

Cargue una clave cifrada "evm" del blob guardado:

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

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

Se anticipan otros usos de claves confiables y cifradas, como para cifrado de archivos y discos. En particular, se ha definido el nuevo formato 'ecryptfs' para utilizar claves cifradas para montar un sistema de archivos eCryptfs. Se pueden encontrar más detalles sobre el uso en el archivo 'Documentation / security / keys-ecryptfs.txt'.

chandana
fuente
¿Tiene alguna idea de cuándo se agregaron estos dos nuevos tipos de claves? A qué versión me refiero. Actualmente estoy usando 1.2 (paquetes de la empresa) y ese no los admite. ¿Quizás en 1.5+?
Acapulco
1
¿Cuál es la fuente de esta publicación? El final se refiere a un documentoDocumentation/security/keys-ecryptfs.tx
goodguys_activate
Parece que todas son llamadas a un programa de línea de comandos. No veo ningún código sobre cómo usar el TPM.
Ian Boyd
3

¿Cómo puedo cifrar bytes usando el módulo TPM de una máquina?

Depende de su intención y circunstancias:

  • ¿Qué tipo de TPM tiene (1 familia o 2 familias)?
  • ¿En qué estado se encuentra el TPM? ¿Ha sido propiedad? ¿Ha sido aprovisionado?
  • ¿Cuál es tu lenguaje de programación?
  • ¿Quieres encriptar o firmar? (eso es vago del resto de la pregunta)
  • ¿Qué tamaño tienen los datos que desea cifrar?
  • ¿Quiere utilizar una clave simétrica o asimétrica?
  • ¿Desea utilizar una clave que ya existe en el TPM o desea que primero cree la clave?
  • ¿Por "cifrar" se refiere quizás a "envolver una clave"?
  • ¿Desea bloquear los datos cifrados en la configuración del sistema, de modo que solo puedan descifrarse cuando el sistema vuelva a tener la misma configuración?
  • ¿Quiere solicitar autorización para desencriptar?
  • ¿Quizás no necesite cifrar en absoluto, sino almacenar los datos dentro del TPM?
  • Si está almacenando los datos dentro del TPM, ¿desea solicitar autorización o que el sistema tenga una configuración particular para su recuperación?

Cada uno de estos casos de uso (y hay más), o una combinación de los mismos, presenta una ruta de implementación diferente. Piense en el TPM como una navaja suiza de dispositivos criptográficos: no hay mucho que no pueda hacer con él, pero la facilidad de uso sufre por esa versatilidad. La pregunta sigue rebotando entre cifrar, firmar y bloquear la configuración del sistema, pero la parte principal de esta respuesta considerará que el comando Seal cubre la mayoría de las necesidades descritas en la pregunta.

Ahora es el momento de dar un paso más. Quiero cifrar algunos datos (por ejemplo, una clave maestra de cifrado de disco duro) que solo puede descifrar el TPM local.

Para esto es el comando Bind (reemplazado por el comando Create para TPM 2). Carga una clave que se deriva de una clave vinculada a TPM y la cifra con ella (o directamente con una clave vinculada al hardware). De esta forma, los datos solo se pueden descifrar con acceso al mismo TPM.

En otras palabras, quiero reemplazar Qualcomm Trusted Execution Environment (TEE) en el diagrama de bloques a continuación para Android, con el TPM en Windows:

No estoy seguro de si replicar todo este proceso es una buena idea. Por un lado, no es necesario utilizar una operación de firma en ningún lugar del proceso. Parece que, en el momento en que se estaba desarrollando Android 5, la API de Keystore se limitaba a las operaciones de firma y verificación. Mi mejor suposición es que el equipo de cifrado de disco hizo todo lo posible para trabajar con lo que tenía y diseñó un algoritmo mediante el cual una de las claves intermedias se derivaba con una operación de firma , utilizando una clave TEE almacenada, vinculando así todo el proceso a un hardware. La clave vinculada solo está disponible en la plataforma, ya que la firma era la única forma de hacerlo en ese momento. Sin embargo, no es necesario limitarse de esa manera si tiene acceso a un TPM, que le brinda más capacidades de las que sabía que necesitaba.

Me doy cuenta de que el TPM no firma datos

Esto es falso, ambas versiones de TPM admiten la firma.

(o si lo hace, no garantiza que firmar los mismos datos dará la misma salida binaria cada vez)

Esto no tiene sentido. La firma de los mismos datos con la misma clave se producirá la misma firma. Puede estar confundiendo la operación de firma con la operación de cotización, que se mezclará en un momento.

Es por eso que estaría dispuesto a reemplazar la "firma RSA" por "cifrar un blob de 256 bits con una clave vinculada al hardware".

Esta debería ser la opción preferida, aunque ambas son posibles con un TPM. Véase más arriba.

El problema es que la programación de TPM está completamente indocumentada en MSDN. No hay ninguna API disponible para realizar ninguna operación.

Desafortunadamente, no hay mucho que documentar. La API de Win está limitada a un par de funciones de TBS que se eliminan en un nivel del controlador.

En su lugar, debe buscar una copia de la pila de software de Trusted Computing Group (también conocida como TSS), averiguar qué comandos enviar al TPM, con cargas útiles, en qué orden, y llamar a la función Tbsip_Submit_Command de Windows para enviar comandos directamente:

En realidad, no, si tuvieras un TSS no tendrías que usarlo Tbsip_submit_Command(). Ese es el objetivo de tener un TSS: los detalles de bajo nivel se abstraen.

Windows no tiene una API de nivel superior para realizar acciones.

Sigue siendo cierto para TPM 1, pero para TPM 2 existe TSS.MSR .

Es el equivalente moral de intentar crear un archivo de texto mediante la emisión de comandos de E / S SATA a su disco duro.

Correcto.

¿Por qué no usar simplemente pantalones? El problema con ese código es que no es portátil en el mundo de Windows. Por ejemplo, no puede usarlo desde Delphi, no puede usarlo desde C #. Requiere: OpenSSL, pThread

No está claro que este sea un desafío insuperable. Acceder a TrouSerS a través de una interoperabilidad debería ser preferible a reescribir todo el código de estructuración de datos. Además, estaba doTSSen el momento de redactar la pregunta.

¿Cuál es el código equivalente para cifrar datos usando el TPM? Probablemente implique el comando TPM_seal. Aunque creo que no quiero sellar datos, creo que quiero vincularlos:

La pregunta contiene una cita que describe la diferencia entre los dos comandos, por lo que no debería haber mucha confusión. El sellado es similar al enlace, con la restricción adicional de que el estado del sistema debe ser el mismo para que los datos se desbloqueen.

De la misma manera pude proporcionar:

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

¿Alguien puede proporcionar el equivalente correspondiente:

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

que hace lo mismo, excepto que en lugar de una llave guardada en System LSA, está guardada en el TPM?

Primero, vale la pena señalar que hay dos versiones principales de TPM, que son totalmente incompatibles entre sí. De modo que prácticamente ningún código que haya escrito para TPM 1 funcionará para TPM 2. La API de TBS es el único código común entre los dos y, para ser justos con Microsoft, esta puede haber sido una de las razones por las que esa API nunca creció. La parte principal de la respuesta presentará el código para TPM 1 por dos razones:

  • La pregunta está cargada con conceptos específicos de TPM 1, por lo que las personas que usan TPM 1 tienen más probabilidades de aterrizar aquí buscándolos
  • Hay una implementación de Microsoft de TSS para TPM 2.

En segundo lugar, hagamos la pregunta más específica. Lo estoy reinterpretando de la siguiente manera:

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?

El comando Seal es el más adecuado para esto, ya que realiza la misma función que el comando Bind cuando el tamaño de selección de PCR se establece en cero, pero la selección de PCR se puede cambiar fácilmente para incluir cualquier PCR que desee. Hace que uno se pregunte por qué el comando Bind se incluyó en la especificación y, como se señaló, se eliminó en la especificación TPM 2 y los dos se combinaron en un comando Create.

Aquí está el código C # para usar el comando TPM 1.2 Seal para cifrar datos solo con funciones de TBS (nota: este código no está probado y no es probable que funcione sin depurar) :

[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álisis de código:

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

Estas son algunas de las pocas funciones disponibles en Tbs.h y las únicas que usaremos aquí. Básicamente, le permiten abrir un identificador al dispositivo y comunicarse con él enviando y recibiendo bytes sin procesar.

    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 es big endian, Windows es little endian. Por lo tanto, el orden de los bytes deberá invertirse para cualquier dato que enviemos. Solo tenemos que preocuparnos por revertir entradas sin firmar de 32 bits y 16 bits aquí.

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

Aquí usamos Tbsi_Context_Create () para abrir un identificador para hablar con el TPM. El TBS_CONTEXT_PARAMSparámetro es solo una estructura C con un campo int de 32 bits sin firmar que debe establecerse en 1 para comunicarse con una instancia de TPM 1.2, y eso es lo que lo configuramos.

    byte[] cmdBuf = new byte[768];

Esto se especifica como el tamaño de búfer mínimo en la especificación de cliente de TPM PC . Será más que suficiente para nuestras necesidades aquí.

TPM 1.2 Spec Part 3 dice lo siguiente:

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.

Necesitamos XOR-encriptar este parámetro "secreto" usando un nonce generado durante una sesión OSAP. Uno de los identificadores de entrada del comando Seal es también un identificador OSAP:

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

Entonces, primero debemos establecer esta sesión OSAP. OSAP se describe en TPM 1.2 Spec Part 1 . OSAP, o Protocolo de autorización específico de objeto, se inventó para manejar el caso de uso en el que desea usar un objeto TPM que requiere autorización varias veces, pero no desea proporcionar autorización cada vez: en su lugar, se usa una sesión OSAP, que depende sobre el concepto de "secreto compartido", que es un HMACque mezcla los datos de autorización del objeto con nonces generados en cada lado para evitar ataques de respuesta. Por lo tanto, el "secreto compartido" solo es conocido por las dos partes en esta sesión: la parte que inició la sesión (usuario) y la parte que la aceptó (TPM); además, ambas partes deben tener los mismos datos de autorización de objeto para que el "secreto compartido" sea el mismo; Además, el "secreto compartido" utilizado en una sesión no será válido en otra. Este diagrama de la especificación describe el proceso:

OSAP

No usaremos múltiples sesiones en este caso particular (de hecho, ¡ese parámetro se ignora con el comando Seal!) Y la clave que usaremos no requiere autorización, pero desafortunadamente todavía estamos obligados por la especificación a establecer un OSAP. sesión.

    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;

Los operandos del comando TPM_OSAP son:

Operandos TPM_OSAP

Cada comando de TPM 1.2 se presenta así:

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

La etiqueta es un valor de dos bytes que indica si lo que sigue es entrada o salida, y si hay valores de datos de autenticación después de los parámetros de comando. Para TPM_OSAP, la etiqueta debe ser TPM_TAG_RQU_COMMAND (0x00C1) según las especificaciones, lo que significa "un comando sin autorización".

El tamaño es un valor de cuatro bytes que especifica el tamaño del comando en bytes, incluida la etiqueta y el tamaño en sí. Estableceremos este valor más tarde, una vez que lo hayamos calculado.

El código de comando es un valor de cuatro bytes que los servidores como ID de comando: le dice al TPM cómo interpretar el resto del comando. Nuestro código de comando aquí es TPM_OSAP (0x0000000B).

Las siguientes dos cosas a establecer son el tipo de entidad y el valor de la entidad. Dado que queremos usar una clave que ya existe en el TPM, usaremos el tipo de entidad "SRK" (0x0004), y dado que estamos trabajando bajo el supuesto de que el TPM ya ha sido propiedad, es seguro asumir que tiene un SRK cargado bajo el identificador permanente 0x40000000 según la especificación, por lo que usaremos este valor de identificador permanente para nuestro valor de entidad. (SRK significa "Storage Root Key" y es la clave raíz de la que derivan la mayoría de las otras claves propiedad de TPM)

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

Finalmente calculamos el tamaño del comando, lo configuramos y enviamos el 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);

Los datos que se supone que debemos recuperar del TPM en TPM_OSAP son:

Respuesta de TPM_OSAP

Entonces volvemos:

  • El identificador de autorización para usar con nuestro comando principal (Sello)
  • nonceEven: el nonce generado por el TPM para usar con el comando principal
  • nonceEvenOSAP: el nonce OSAP que es el contra-nonce que generamos de nuestro lado antes de enviar el comando TPM_OSAP. Estos dos nonces se utilizarán para generar el "secreto compartido".

Extraemos esos valores y los almacenamos en variables.

    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);

Luego calculamos el "secreto compartido". Según la especificación, los valores que entran en el cálculo son los dos nonces OSAP (uno generado por el usuario y otro generado por el TPM) y el valor de autorización para la clave que queremos usar - el SRK. Por convención, el valor de autenticación SRK es la "autenticación conocida": un búfer de 20 bytes puesto a cero. Técnicamente, se podría cambiar este valor por otro al tomar posesión del TPM, pero esto no se hace en la práctica, por lo que podemos asumir con seguridad que el valor de "autenticación conocida" es bueno.

A continuación, echemos un vistazo a lo que incluye el comando TPM_Seal:

TPM_Seal

La mayoría de estos parámetros son triviales de construir, excepto dos de ellos: encAuthy pubAuth. Veámoslos uno por uno.

encAuthes "AuthData cifrado para los datos sellados". Nuestro AuthData aquí es el "auth conocido" de antes, pero sí, todavía tenemos que cifrarlo. Debido a que estamos usando una sesión OSAP, está encriptada siguiendo ADIP, o Protocolo de inserción de datos de autorización. De la especificación: "El ADIP permite la creación de nuevas entidades y la inserción segura de la nueva entidad AuthData. La transmisión de la nueva AuthData utiliza encriptación con la clave basada en el secreto compartido de una sesión OSAP". Además: "Para el algoritmo de cifrado XOR obligatorio, el creador crea una clave de cifrado utilizando un hash SHA-1 del secreto compartido OSAP y un nonce de sesión. El creador XOR cifra el nuevo AuthData utilizando la clave de cifrado como un pad de un solo uso y envía estos datos cifrados junto con la solicitud de creación al TPM ".

El siguiente diagrama explica cómo funciona ADIP:

UN CHAPUZÓN

pubAuthes "El resumen de la sesión de autorización para entradas y keyHandle". La parte 1 de la especificación, en "Declaraciones de parámetros para ejemplos de OIAP y OSAP", explica cómo interpretar la tabla de parámetros TPM_Seal anterior: "La columna HMAC # detalla los parámetros utilizados en el cálculo de HMAC. Los parámetros 1S, 2S, etc. están concatenados y hash a inParamDigest o outParamDigest, llamado implícitamente 1H1 y posiblemente 1H2 si hay dos sesiones de autorización. Para la primera sesión, 1H1, 2H1, 3H1 y 4H1 se concatenan y HMAC'ed. Para la segunda sesión, 1H2, 2H2, 3H2, y 4H2 se concatenan y HMAC '. " Así que tendremos que hacer un hash del texto plano, su tamaño, el tamaño de la información de PCR, encAuthdesde arriba y el ordinal TPM_Seal, y luego HMAC que con los dos nonces y el booleano "continuar sesión" usando el OSAP "

Poniéndolo todo junto en un diagrama:

cómputo pubAuth

Observe cómo establecemos el "tamaño de información de PCR" en cero en este código, ya que solo queremos cifrar los datos sin bloquearlos en un estado del sistema. Sin embargo, es trivial proporcionar una estructura de "información de PCR" si es necesario.

    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 construimos el comando y lo enviamos.

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

Usamos la función Tbsip_Context_Close () para cerrar nuestro identificador de comunicación.

Devolvemos la respuesta como está aquí. Lo ideal sería invertir los bytes nuevamente y validarlos volviendo a calcular el resAuthvalor para evitar ataques man-in-the-middle.


Lo que es confuso es que no hay un comando Tspi_Data_Bind.

Esto se debe a que Tspi_Data_Bind es un comando de TSS, no un comando de TPM. La razón es porque no requiere secretos (solo se usa la clave pública) por lo que se puede hacer sin involucrar al TPM. Sin embargo, esto causó confusión e incluso los comandos que no requieren secretos ahora se incluyen en la especificación TPM 2.

¿Cómo puedo cifrar una clave con la clave pública de TPM?

Depende de la versión de TPM. Con el comando TPM_CreateWrapKey para TPM 1.2. Con el comando TPM2_Create para TPM 2.

¿Cómo bloquea un desarrollador una clave para el TPM?

Créelo en el TPM, envuélvalo o utilice cualquier otro de los métodos disponibles.

TPM2_Create, especificando una clave HMAC

El texto del libro es confuso. No especifica la clave HMAC , especifica que desea una clave HMAC .

El hecho de que la clave HMAC no sea secreta tiene sentido

No, no tiene sentido. La clave es secreta.

... use las claves mientras las mantiene seguras en un dispositivo de hardware ... ¡Excelente! ¿¡Cómo lo haces!?

Hay comandos para crear claves o importarlas para ambas versiones de TPM. Para TPM 1, solo había una clave raíz, la SRK, a partir de la cual se podía establecer una jerarquía de claves creando claves envueltas. Con TPM 2 puede tener varias claves primarias o raíz.

¿El TPM tiene la capacidad de generar claves criptográficas y proteger sus secretos dentro de un límite de hardware? Es así, ¿cómo?

Véase más arriba.

¡Excelente! Este es exactamente el caso de uso que quiero. También es el caso de uso para el que Microsoft usa el TPM. ¿¡Cómo lo hago!?

Probablemente depende del tipo de unidad. En el caso de las unidades que no son SED, la clave de cifrado de la unidad probablemente esté envuelta con una clave TPM. En el caso de las unidades SED, la contraseña de Admin1 (o tal) está sellada con el TPM.

La clave de aprobación o EK ... En algún lugar dentro del TPM hay una clave privada RSA. Esa llave está guardada allí, para que el mundo exterior nunca la vea. Quiero que el TPM firme algo con su clave privada (es decir, lo cifre con su clave privada).

La EK no es una clave de firma, es una clave de cifrado. Sin embargo, no es una clave de cifrado de propósito general: solo se puede usar en ciertos contextos .

Pero lo que realmente me gustaría hacer es "sellar" algunos datos

Véase más arriba.

mnístico
fuente
2

Cuando dice

especificando la clave HMAC

NO significa proporcionar la clave HMAC; significa "señalar la clave HMAC que desea usar" .

Los TPM pueden utilizar una cantidad prácticamente ilimitada de claves HMAC, como se indica en el libro. Tienes que decirle al TPM cuál usar.

DCC
fuente
Entonces, ¿hay quizás un ejemplo de código que muestre cómo especificar (señalar) la clave HMAC que se usará en C # u otro idioma?
Chad