blob: 95724d5f4ef644f6ad313f2c4e27449203bd346f [file] [log] [blame]
/* Software-based Trusted Platform Module (TPM) Emulator
* Copyright (C) 2004-2010 Mario Strasser <mast@gmx.net>
*
* This module is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* This module is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* $Id: tpm_transport.c 367 2010-02-13 15:52:18Z mast $
*/
/*
* Thanks go to Edison Su (<sudison@gmail.com>) for providing
* the initial Transport Session patch.
*/
#include "tpm_emulator.h"
#include "tpm_commands.h"
#include "tpm_handles.h"
#include "tpm_marshalling.h"
#include "tpm_data.h"
#include "crypto/rsa.h"
#include "crypto/sha1.h"
/*
* Transport Sessions ([TPM_Part3], Section 24)
*/
static void debug_buf(const char *str, uint8_t *buf, size_t buf_len)
{
static char map[] = "0123456789abcdef";
char hex[buf_len * 3];
size_t i;
for (i = 0; i < buf_len; i++) {
hex[i*3 + 0] = map[buf[i] >> 4];
hex[i*3 + 1] = map[buf[i] & 0x0f];
hex[i*3 + 2] = ' ';
}
hex[sizeof(hex) - 1] = 0;
debug("%s%s", str, hex);
}
static int decrypt_transport_auth(TPM_KEY_DATA *key, BYTE *enc, UINT32 enc_size,
TPM_TRANSPORT_AUTH *trans_auth)
{
BYTE *buf;
size_t buf_size;
int scheme;
switch (key->encScheme) {
case TPM_ES_RSAESOAEP_SHA1_MGF1: scheme = RSA_ES_OAEP_SHA1; break;
case TPM_ES_RSAESPKCSv15: scheme = RSA_ES_PKCSV15; break;
default: return -1;
}
buf = tpm_malloc(key->key.size);
if (buf == NULL
|| tpm_rsa_decrypt(&key->key, scheme, enc, enc_size, buf, &buf_size)
|| buf_size != sizeof_TPM_TRANSPORT_AUTH(x)
|| (((UINT16)buf[0] << 8) | buf[1]) != TPM_TAG_TRANSPORT_AUTH) {
tpm_free(buf);
return -1;
}
trans_auth->tag = TPM_TAG_TRANSPORT_AUTH;
memcpy(trans_auth->authData, &buf[2], sizeof(TPM_AUTHDATA));
tpm_free(buf);
return 0;
}
static void transport_log_in(BYTE *params, BYTE *pubKeyHash,
TPM_DIGEST *transDigest)
{
BYTE *ptr, buf[sizeof_TPM_TRANSPORT_LOG_IN(x)];
UINT32 len;
tpm_sha1_ctx_t sha1;
ptr = buf; len = sizeof(buf);
tpm_marshal_TPM_TAG(&ptr, &len, TPM_TAG_TRANSPORT_LOG_IN);
tpm_marshal_BLOB(&ptr, &len, params, SHA1_DIGEST_LENGTH);
tpm_marshal_BLOB(&ptr, &len, pubKeyHash, SHA1_DIGEST_LENGTH);
tpm_sha1_init(&sha1);
tpm_sha1_update(&sha1, transDigest->digest, sizeof(transDigest->digest));
tpm_sha1_update(&sha1, buf, sizeof(buf));
tpm_sha1_final(&sha1, transDigest->digest);
debug_buf("LogIn: transDigest: ", transDigest->digest, sizeof(transDigest->digest));
}
static void transport_log_out(BYTE *params, TPM_DIGEST *transDigest)
{
BYTE *ptr, buf[sizeof_TPM_TRANSPORT_LOG_OUT(x)];
UINT32 len;
tpm_sha1_ctx_t sha1;
ptr = buf; len = sizeof(buf);
tpm_marshal_TPM_TAG(&ptr, &len, TPM_TAG_TRANSPORT_LOG_OUT);
tpm_marshal_TPM_CURRENT_TICKS(&ptr, &len, &tpmData.stany.data.currentTicks);
tpm_marshal_BLOB(&ptr, &len, params, SHA1_DIGEST_LENGTH);
tpm_marshal_TPM_MODIFIER_INDICATOR(&ptr, &len, tpmData.stany.flags.localityModifier);
tpm_sha1_init(&sha1);
tpm_sha1_update(&sha1, transDigest->digest, sizeof(transDigest->digest));
tpm_sha1_update(&sha1, buf, sizeof(buf));
tpm_sha1_final(&sha1, transDigest->digest);
debug_buf("LogOut: transDigest: ", transDigest->digest, sizeof(transDigest->digest));
}
TPM_RESULT TPM_EstablishTransport(TPM_KEY_HANDLE encHandle,
TPM_TRANSPORT_PUBLIC *transPublic,
UINT32 secretSize, BYTE *secret,
TPM_AUTH *auth1,
TPM_TRANSHANDLE *transHandle,
TPM_MODIFIER_INDICATOR *locality,
TPM_CURRENT_TICKS *currentTicks,
TPM_NONCE *transNonceEven)
{
TPM_RESULT res;
TPM_KEY_DATA *key;
TPM_TRANSPORT_AUTH trans_auth;
TPM_SESSION_DATA *session;
info("TPM_EstablishTransport()");
/* setup authorization data */
if (encHandle == TPM_KH_TRANSPORT) {
if (auth1->authHandle != TPM_INVALID_HANDLE) return TPM_BADTAG;
if (transPublic->transAttributes & TPM_TRANSPORT_ENCRYPT) return TPM_BAD_SCHEME;
if (secretSize != 20) return TPM_BAD_PARAM_SIZE;
memcpy(trans_auth.authData, secret, 20);
} else {
/* get key and verify its usage */
key = tpm_get_key(encHandle);
if (key == NULL) return TPM_INVALID_KEYHANDLE;
if (key->keyUsage != TPM_KEY_STORAGE && key->keyUsage != TPM_KEY_LEGACY)
return TPM_INVALID_KEYUSAGE;
/* verify authorization */
if (key->authDataUsage != TPM_AUTH_NEVER) {
res = tpm_verify_auth(auth1, key->usageAuth, encHandle);
if (res != TPM_SUCCESS) return res;
if (decrypt_transport_auth(key, secret, secretSize, &trans_auth))
return TPM_DECRYPT_ERROR;
}
}
/* check whether the transport has to be encrypted */
if (transPublic->transAttributes & TPM_TRANSPORT_ENCRYPT) {
if (tpmData.permanent.flags.FIPS
&& transPublic->algID == TPM_ALG_MGF1) return TPM_INAPPROPRIATE_ENC;
/* until now, only MGF1 is supported */
if (transPublic->algID != TPM_ALG_MGF1) return TPM_BAD_KEY_PROPERTY;
}
/* initialize transport session */
tpm_get_random_bytes(transNonceEven->nonce, sizeof(transNonceEven->nonce));
*transHandle = tpm_get_free_session(TPM_ST_TRANSPORT);
session = tpm_get_transport(*transHandle);
if (session == NULL) return TPM_RESOURCES;
session->transInternal.transHandle = *transHandle;
memset(&session->transInternal.transDigest, 0, sizeof(TPM_DIGEST));
memcpy(&session->transInternal.transPublic, transPublic,
sizeof_TPM_TRANSPORT_PUBLIC((*transPublic)));
memcpy(&session->transInternal.transNonceEven, transNonceEven, sizeof(TPM_NONCE));
memcpy(&session->nonceEven, transNonceEven, sizeof(TPM_NONCE));
memcpy(&session->transInternal.authData, trans_auth.authData, sizeof(TPM_AUTHDATA));
*locality = tpmData.stany.flags.localityModifier;
memcpy(currentTicks, &tpmData.stany.data.currentTicks, sizeof(TPM_CURRENT_TICKS));
/* perform transport logging */
if (transPublic->transAttributes & TPM_TRANSPORT_LOG) {
tpm_sha1_ctx_t sha1;
BYTE *ptr, buf[4 + 4 + 4 + sizeof_TPM_CURRENT_TICKS(x) + 20];
UINT32 len;
/* log input */
memset(buf, 0, sizeof(buf));
transport_log_in(auth1->digest, buf, &session->transInternal.transDigest);
/* compute digest of output parameters and log output */
ptr = buf; len = sizeof(buf);
tpm_marshal_UINT32(&ptr, &len, TPM_SUCCESS);
tpm_marshal_TPM_COMMAND_CODE(&ptr, &len, TPM_ORD_EstablishTransport);
tpm_marshal_TPM_MODIFIER_INDICATOR(&ptr, &len, *locality);
tpm_marshal_TPM_CURRENT_TICKS(&ptr, &len, currentTicks);
tpm_marshal_TPM_NONCE(&ptr, &len, transNonceEven);
tpm_sha1_init(&sha1);
tpm_sha1_update(&sha1, buf, sizeof(buf));
tpm_sha1_final(&sha1, buf);
transport_log_out(buf, &session->transInternal.transDigest);
}
/* check whether this is a exclusive transport session */
if (transPublic->transAttributes & TPM_TRANSPORT_EXCLUSIVE) {
tpmData.stany.flags.transportExclusive = TRUE;
tpmData.stany.data.transExclusive = *transHandle;
}
auth1->continueAuthSession = FALSE;
return TPM_SUCCESS;
}
extern UINT32 tpm_get_in_param_offset(TPM_COMMAND_CODE ordinal);
extern UINT32 tpm_get_out_param_offset(TPM_COMMAND_CODE ordinal);
extern void tpm_compute_in_param_digest(TPM_REQUEST *req);
extern void tpm_execute_command(TPM_REQUEST *req, TPM_RESPONSE *rsp);
extern void tpm_compute_out_param_digest(TPM_COMMAND_CODE ordinal, TPM_RESPONSE *rsp);
static void decrypt_wrapped_command(BYTE *buf, UINT32 buf_len, TPM_AUTH *auth,
TPM_SESSION_DATA *session)
{
UINT32 i, j;
BYTE mask[SHA1_DIGEST_LENGTH];
tpm_sha1_ctx_t sha1;
for (i = 0; buf_len > 0; i++) {
tpm_sha1_init(&sha1);
tpm_sha1_update(&sha1, session->nonceEven.nonce, sizeof(session->nonceEven.nonce));
tpm_sha1_update(&sha1, auth->nonceOdd.nonce, sizeof(auth->nonceOdd.nonce));
tpm_sha1_update(&sha1, (uint8_t*)"in", 2);
tpm_sha1_update(&sha1, session->transInternal.authData, sizeof(TPM_SECRET));
tpm_sha1_update_be32(&sha1, i);
tpm_sha1_final(&sha1, mask);
for (j = 0; j < sizeof(mask) && buf_len > 0; j++) {
*buf++ ^= mask[j];
buf_len--;
}
}
}
static void encrypt_wrapped_command(BYTE *buf, UINT32 buf_len, TPM_AUTH *auth,
TPM_SESSION_DATA *session)
{
UINT32 i, j;
BYTE mask[SHA1_DIGEST_LENGTH];
tpm_sha1_ctx_t sha1;
for (i = 0; buf_len > 0; i++) {
tpm_sha1_init(&sha1);
tpm_sha1_update(&sha1, session->nonceEven.nonce, sizeof(session->nonceEven.nonce));
tpm_sha1_update(&sha1, auth->nonceOdd.nonce, sizeof(auth->nonceOdd.nonce));
tpm_sha1_update(&sha1, (uint8_t*)"out", 3);
tpm_sha1_update(&sha1, session->transInternal.authData, sizeof(TPM_SECRET));
tpm_sha1_update_be32(&sha1, i);
tpm_sha1_final(&sha1, mask);
for (j = 0; j < sizeof(mask) && buf_len > 0; j++) {
*buf++ ^= mask[j];
buf_len--;
}
}
}
static void compute_key_digest(TPM_REQUEST *req, TPM_DIGEST *digest)
{
tpm_sha1_ctx_t ctx;
TPM_HANDLE h1, h2;
TPM_KEY_DATA *k1, *k2;
BYTE *ptr;
UINT32 len, offset = tpm_get_in_param_offset(req->ordinal);
/* handle some exceptions */
if (req->ordinal == TPM_ORD_FlushSpecific) offset = 0;
else if (req->ordinal == TPM_ORD_OwnerReadInternalPub) offset = 4;
/* compute public key digests */
if (offset == 0) {
debug("no handles");
memset(digest, 0, sizeof(TPM_DIGEST));
} else if (offset == 4) {
debug("one handle");
ptr = req->param; len = 4;
tpm_unmarshal_TPM_HANDLE(&ptr, &len, &h1);
k1 = tpm_get_key(h1);
if (k1 != NULL && tpm_compute_key_data_digest(k1, digest) == 0) {
debug("key found");
/* compute outer hash */
tpm_sha1_init(&ctx);
tpm_sha1_update(&ctx, digest->digest, sizeof(digest->digest));
tpm_sha1_final(&ctx, digest->digest);
} else {
memset(digest, 0, sizeof(TPM_DIGEST));
}
} else if (offset == 8) {
TPM_DIGEST digest2;
debug("two handles");
ptr = req->param; len = 8;
tpm_unmarshal_TPM_HANDLE(&ptr, &len, &h1);
tpm_unmarshal_TPM_HANDLE(&ptr, &len, &h2);
k1 = tpm_get_key(h1);
k2 = tpm_get_key(h2);
if (k1 != NULL && tpm_compute_key_data_digest(k1, digest) == 0
&& k2 != NULL && tpm_compute_key_data_digest(k2, &digest2) == 0) {
debug("two keys found");
/* compute outer hash */
tpm_sha1_init(&ctx);
tpm_sha1_update(&ctx, digest->digest, sizeof(digest->digest));
tpm_sha1_update(&ctx, digest2.digest, sizeof(digest2.digest));
tpm_sha1_final(&ctx, digest->digest);
} else {
memset(digest, 0, sizeof(TPM_DIGEST));
}
} else {
memset(digest, 0, sizeof(TPM_DIGEST));
}
}
TPM_RESULT TPM_ExecuteTransport(UINT32 inWrappedCmdSize, BYTE *inWrappedCmd,
TPM_AUTH *auth1, UINT64 *currentTicks,
TPM_MODIFIER_INDICATOR *locality,
UINT32 *outWrappedCmdSize, BYTE **outWrappedCmd)
{
TPM_RESULT res;
TPM_SESSION_DATA *session;
TPM_REQUEST req;
TPM_RESPONSE rsp;
BYTE *ptr, buf[4 * 4 + 8 + 20];
UINT32 len, offset;
tpm_sha1_ctx_t sha1;
info("TPM_ExecuteTransport()");
/* get transport session */
session = tpm_get_transport(auth1->authHandle);
if (session == NULL) return TPM_BAD_PARAMETER;
/* unmarshal wrapped command */
len = inWrappedCmdSize;
ptr = inWrappedCmd;
if (tpm_unmarshal_TPM_REQUEST(&ptr, &len, &req)) return TPM_FAIL;
/* decrypt wrapped command if needed */
ptr = tpm_malloc(req.paramSize);
if (ptr == NULL) return TPM_FAIL;
memcpy(ptr, req.param, req.paramSize);
if (session->transInternal.transPublic.transAttributes & TPM_TRANSPORT_ENCRYPT) {
if (req.ordinal == TPM_ORD_OIAP || req.ordinal == TPM_ORD_OSAP) {
offset = req.paramSize;
} else if (req.ordinal == TPM_ORD_DSAP) {
offset = 30;
} else {
offset = tpm_get_in_param_offset(req.ordinal);
}
debug("decrypting %d bytes, starting at pos %d", req.paramSize - offset, offset);
decrypt_wrapped_command(ptr + offset, req.paramSize - offset, auth1, session);
}
req.param = ptr;
/* verify authorization */
tpm_compute_in_param_digest(&req);
tpm_sha1_init(&sha1);
tpm_sha1_update_be32(&sha1, TPM_ORD_ExecuteTransport);
tpm_sha1_update_be32(&sha1, inWrappedCmdSize);
tpm_sha1_update(&sha1, req.auth1.digest, sizeof(req.auth1.digest));
tpm_sha1_final(&sha1, auth1->digest);
res = tpm_verify_auth(auth1, session->transInternal.authData, TPM_INVALID_HANDLE);
if (res != TPM_SUCCESS) {
tpm_free(req.param);
return res;
}
/* nested transport sessions are not allowed */
if (req.ordinal == TPM_ORD_EstablishTransport
|| req.ordinal == TPM_ORD_ExecuteTransport
|| req.ordinal == TPM_ORD_ReleaseTransportSigned) {
tpm_free(req.param);
return TPM_NO_WRAP_TRANSPORT;
}
/* log input parameters */
if (session->transInternal.transPublic.transAttributes & TPM_TRANSPORT_LOG) {
TPM_DIGEST keyDigest;
compute_key_digest(&req, &keyDigest);
transport_log_in(req.auth1.digest, keyDigest.digest,
&session->transInternal.transDigest);
}
/* execute and audit command*/
tpm_audit_request(req.ordinal, &req);
tpm_execute_command(&req, &rsp);
tpm_audit_response(req.ordinal, &rsp);
tpm_free(req.param);
/* get locality and ticks */
*locality = tpmData.stany.flags.localityModifier;
*currentTicks = tpmData.stany.data.currentTicks.currentTicks;
/* if required, compute digest of internal output parameters */
debug("result = %d", rsp.result);
if (rsp.result == TPM_SUCCESS) {
if (rsp.tag == TPM_TAG_RSP_COMMAND) {
rsp.auth1 = &req.auth1;
tpm_compute_out_param_digest(req.ordinal, &rsp);
}
/* encrypt parameters */
if (session->transInternal.transPublic.transAttributes & TPM_TRANSPORT_ENCRYPT) {
if (req.ordinal == TPM_ORD_OIAP || req.ordinal == TPM_ORD_OSAP) {
offset = rsp.paramSize;
} else if (req.ordinal == TPM_ORD_DSAP) {
offset = rsp.paramSize;
} else {
offset = tpm_get_out_param_offset(req.ordinal);
}
debug("encrypting %d bytes, starting at pos %d", rsp.paramSize - offset, offset);
encrypt_wrapped_command(rsp.param + offset, rsp.paramSize - offset, auth1, session);
}
} else {
rsp.auth1 = &req.auth1;
memset(rsp.auth1->digest, 0, sizeof(*rsp.auth1->digest));
}
/* marshal response */
*outWrappedCmdSize = len = rsp.size;
*outWrappedCmd = ptr = tpm_malloc(len);
if (ptr == NULL) {
tpm_free(rsp.param);
return TPM_FAIL;
}
tpm_marshal_TPM_RESPONSE(&ptr, &len, &rsp);
debug("marshalling done.");
/* log output parameters */
if (session->transInternal.transPublic.transAttributes & TPM_TRANSPORT_LOG) {
transport_log_out(rsp.auth1->digest, &session->transInternal.transDigest);
}
tpm_free(rsp.param);
/* compute digest of output parameters */
ptr = buf; len = sizeof(buf);
tpm_marshal_UINT32(&ptr, &len, TPM_SUCCESS);
tpm_marshal_TPM_COMMAND_CODE(&ptr, &len, TPM_ORD_ExecuteTransport);
tpm_marshal_UINT64(&ptr, &len, *currentTicks);
tpm_marshal_TPM_MODIFIER_INDICATOR(&ptr, &len, *locality);
tpm_marshal_UINT32(&ptr, &len, *outWrappedCmdSize);
memcpy(ptr, rsp.auth1->digest, sizeof(rsp.auth1->digest));
tpm_sha1_init(&sha1);
tpm_sha1_update(&sha1, buf, sizeof(buf));
tpm_sha1_final(&sha1, auth1->digest);
return TPM_SUCCESS;
}
TPM_RESULT TPM_ReleaseTransportSigned(TPM_KEY_HANDLE keyHandle,
TPM_NONCE *antiReplay,
TPM_AUTH *auth1, TPM_AUTH *auth2,
TPM_MODIFIER_INDICATOR *locality,
TPM_CURRENT_TICKS *currentTicks,
UINT32 *sigSize, BYTE **sig)
{
TPM_RESULT res;
TPM_KEY_DATA *key;
TPM_SESSION_DATA *session;
BYTE buf[30 + 20];
info("TPM_ReleaseTransportSigned()");
/* get key */
key = tpm_get_key(keyHandle);
if (key == NULL) return TPM_INVALID_KEYHANDLE;
/* verify authorization */
if (auth2->authHandle != TPM_INVALID_HANDLE
|| key->authDataUsage != TPM_AUTH_NEVER) {
res = tpm_verify_auth(auth1, key->usageAuth, keyHandle);
if (res != TPM_SUCCESS) return res;
session = tpm_get_transport(auth2->authHandle);
if (session == NULL) return TPM_INVALID_AUTHHANDLE;
res = tpm_verify_auth(auth2, session->transInternal.authData, TPM_INVALID_HANDLE);
if (res != TPM_SUCCESS) return (res == TPM_AUTHFAIL) ? TPM_AUTH2FAIL : res;
} else {
session = tpm_get_transport(auth1->authHandle);
if (session == NULL) return TPM_INVALID_AUTHHANDLE;
res = tpm_verify_auth(auth1, session->transInternal.authData, TPM_INVALID_HANDLE);
if (res != TPM_SUCCESS) return res;
}
/* invalidate transport session */
auth1->continueAuthSession = FALSE;
/* logging must be enabled */
if (!(session->transInternal.transPublic.transAttributes & TPM_TRANSPORT_LOG))
return TPM_BAD_MODE;
*locality = tpmData.stany.flags.localityModifier;
memcpy(currentTicks, &tpmData.stany.data.currentTicks, sizeof(TPM_CURRENT_TICKS));
transport_log_out(auth1->digest, &session->transInternal.transDigest);
/* setup a TPM_SIGN_INFO structure */
memcpy(&buf[0], (uint8_t*)"\x00\x05TRAN", 6);
memcpy(&buf[6], antiReplay->nonce, 20);
memcpy(&buf[26], (uint8_t*)"\x00\x00\x00\x14", 4);
memcpy(&buf[30], session->transInternal.transDigest.digest, 20);
/* sign info structure */
if (key->sigScheme == TPM_SS_RSASSAPKCS1v15_SHA1) {
tpm_sha1_ctx_t ctx;
debug("TPM_SS_RSASSAPKCS1v15_SHA1");
tpm_sha1_init(&ctx);
tpm_sha1_update(&ctx, buf, sizeof(buf));
tpm_sha1_final(&ctx, buf);
res = tpm_sign(key, auth1, FALSE, buf, SHA1_DIGEST_LENGTH, sig, sigSize);
} else if (key->sigScheme == TPM_SS_RSASSAPKCS1v15_INFO) {
debug("TPM_SS_RSASSAPKCS1v15_INFO");
res = tpm_sign(key, auth1, TRUE, buf, sizeof(buf), sig, sigSize);
} else {
debug("unsupported signature scheme: %02x", key->sigScheme);
res = TPM_INVALID_KEYUSAGE;
}
return res;
}