azure-sdk-for-cpp/sdk/attestation/azure-security-attestation/src/attestation_administration_client.cpp
Larry Osterman f757bb06e7
Clean up tracing logic - prepare for new tracing functionality. (#4263)
- Made Context immutable (including marking the static ApplicationContext as const
- Put back TelemetryPolicy in all pipeline cases and removed User-Agent generation from RequestActivityPolicy (note: This part of the change may end up being reverted). Also updated parameters for TelemetryPolicy to make it clearer that the parameter is a package name, not a service name.
- Changed Az.Namespace value from being the package name to being the service name.
- Change test SpanExporter to fully capture exported values rather than keeping references to the values.


Co-authored-by: Anton Kolesnyk <41349689+antkmsft@users.noreply.github.com>
2023-01-25 19:07:25 +00:00

572 lines
23 KiB
C++

// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/attestation/attestation_administration_client.hpp"
#include "azure/attestation/attestation_client.hpp"
#include "private/attestation_client_models_private.hpp"
#include "private/attestation_client_private.hpp"
#include "private/attestation_common_request.hpp"
#include "private/attestation_deserializers_private.hpp"
#include "private/package_version.hpp"
#include <azure/core/base64.hpp>
#include <azure/core/http/policies/policy.hpp>
#include <azure/core/internal/diagnostics/log.hpp>
#include <azure/core/internal/http/pipeline.hpp>
#include <shared_mutex>
#include <string>
// cspell: words jwks
using namespace Azure::Security::Attestation;
using namespace Azure::Security::Attestation::Models;
using namespace Azure::Security::Attestation::_detail;
using namespace Azure::Security::Attestation::Models::_detail;
using namespace Azure::Core::Tracing::_internal;
using namespace Azure::Core::Http;
using namespace Azure::Core::Http::Policies;
using namespace Azure::Core::Http::Policies::_internal;
using namespace Azure::Core::Http::_internal;
using namespace Azure::Core::Diagnostics::_internal;
using namespace Azure::Core::Diagnostics;
using namespace Azure::Core::_internal;
const Models::AttestationType AttestationType::SgxEnclave("SgxEnclave");
const Models::AttestationType AttestationType::OpenEnclave("OpenEnclave");
const Models::AttestationType AttestationType::Tpm("Tpm");
const Models::PolicyModification PolicyModification::Removed("Removed");
const Models::PolicyModification PolicyModification::Updated("Updated");
const Models::PolicyCertificateModification PolicyCertificateModification::IsAbsent("IsAbsent");
const Models::PolicyCertificateModification PolicyCertificateModification::IsPresent("IsPresent");
AttestationAdministrationClient::AttestationAdministrationClient(
std::string const& endpoint,
std::shared_ptr<Core::Credentials::TokenCredential const> credential,
AttestationAdministrationClientOptions const& options)
: m_endpoint{endpoint}, m_apiVersion{options.ApiVersion},
m_tokenValidationOptions{options.TokenValidationOptions},
m_tracingFactory{
options,
"Microsoft.Attestation",
"azure-security-attestation-cpp",
PackageVersion::ToString()}
{
std::vector<std::unique_ptr<HttpPolicy>> perRetrypolicies;
if (credential)
{
Azure::Core::Credentials::TokenRequestContext tokenContext;
tokenContext.Scopes = {"https://attest.azure.net/.default"};
perRetrypolicies.emplace_back(
std::make_unique<BearerTokenAuthenticationPolicy>(credential, tokenContext));
}
std::vector<std::unique_ptr<HttpPolicy>> perCallpolicies;
m_pipeline = std::make_shared<Azure::Core::Http::_internal::HttpPipeline>(
options,
"security.attestation",
PackageVersion::ToString(),
std::move(perRetrypolicies),
std::move(perCallpolicies));
}
AttestationAdministrationClient AttestationAdministrationClient::Create(
std::string const& endpoint,
std::shared_ptr<Core::Credentials::TokenCredential const> credential,
AttestationAdministrationClientOptions const& options,
Azure::Core::Context const& context)
{
AttestationAdministrationClient returnValue(endpoint, credential, options);
returnValue.RetrieveResponseValidationCollateral(context);
return returnValue;
}
namespace {
std::shared_timed_mutex SharedStateLock;
}
Azure::Response<Models::AttestationToken<std::string>>
AttestationAdministrationClient::GetAttestationPolicy(
AttestationType const& attestationType,
GetPolicyOptions const& options,
Azure::Core::Context const& context) const
{
auto tracingContext(m_tracingFactory.CreateTracingContext("GetAttestationPolicy", context));
try
{
auto request = AttestationCommonRequest::CreateRequest(
m_endpoint,
m_apiVersion,
HttpMethod::Get,
{"policies/" + attestationType.ToString()},
nullptr);
// Send the request to the service.
auto response = AttestationCommonRequest::SendRequest(*m_pipeline, request, context);
// Deserialize the Service response token and return the JSON web token returned by the
// service.
std::string responseToken = AttestationServiceTokenResponseSerializer::Deserialize(response);
// Parse the JWT returned by the attestation service.
const auto resultToken
= AttestationTokenInternal<Models::_detail::PolicyResult, PolicyResultSerializer>(
responseToken);
// Validate the token returned by the service. Use the cached attestation signers in the
// validation.
resultToken.ValidateToken(
options.TokenValidationOptionsOverride ? *options.TokenValidationOptionsOverride
: this->m_tokenValidationOptions,
m_attestationSigners);
// Extract the underlying policy token from the response.
std::string policyTokenValue
= *static_cast<AttestationToken<Models::_detail::PolicyResult>>(resultToken)
.Body.PolicyToken;
// TPM policies are empty by default, at least in our test instances, so handle the empty policy
// token case.
const auto policyTokenI
= AttestationTokenInternal<StoredAttestationPolicy, StoredAttestationPolicySerializer>(
policyTokenValue);
AttestationToken<StoredAttestationPolicy> policyToken(policyTokenI);
std::string returnPolicy;
if (policyToken.Body.AttestationPolicy)
{
std::vector<uint8_t> policyUtf8 = *policyToken.Body.AttestationPolicy;
returnPolicy = std::string(policyUtf8.begin(), policyUtf8.end());
}
// Construct a token whose body is the policy, but whose token is the response from the
// service.
const auto returnedToken = AttestationTokenInternal<std::string>(responseToken, &returnPolicy);
return Response<AttestationToken<std::string>>(returnedToken, std::move(response));
}
catch (std::runtime_error const& ex)
{
tracingContext.Span.AddEvent(ex);
throw;
}
}
Models::AttestationToken<void> AttestationAdministrationClient::CreateAttestationPolicyToken(
Azure::Nullable<std::string> const& newAttestationPolicy,
Azure::Nullable<AttestationSigningKey> const& signingKey) const
{
// Embed the encoded policy in the StoredAttestationPolicy.
Azure::Nullable<StoredAttestationPolicy> storedPolicy;
if (newAttestationPolicy)
{
storedPolicy = StoredAttestationPolicy{
std::vector<uint8_t>(newAttestationPolicy->begin(), newAttestationPolicy->end())};
}
const auto tokenToSet(
AttestationTokenInternal<StoredAttestationPolicy, StoredAttestationPolicySerializer>::
CreateToken(storedPolicy, signingKey));
const auto tokenToSend(static_cast<AttestationToken<StoredAttestationPolicy>>(tokenToSet));
// Strip the body type off the returned JWS - the caller of the function doesn't need it.
return AttestationTokenInternal<void>(tokenToSend.RawToken);
}
Azure::Response<Models::AttestationToken<Models::PolicyResult>>
AttestationAdministrationClient::SetAttestationPolicy(
AttestationType const& attestationType,
std::string const& newAttestationPolicy,
SetPolicyOptions const& options,
Azure::Core::Context const& context) const
{
auto tracingContext(m_tracingFactory.CreateTracingContext("SetAttestationPolicy", context));
try
{
// Calculate a signed (or unsigned) attestation policy token to send to the service.
Models::AttestationToken<void> const tokenToSend(
CreateAttestationPolicyToken(newAttestationPolicy, options.SigningKey));
Azure::Core::IO::MemoryBodyStream stream(
reinterpret_cast<uint8_t const*>(tokenToSend.RawToken.data()), tokenToSend.RawToken.size());
auto request = AttestationCommonRequest::CreateRequest(
m_endpoint,
m_apiVersion,
HttpMethod::Put,
{"policies/" + attestationType.ToString()},
&stream);
// Send the request to the service.
auto response = AttestationCommonRequest::SendRequest(*m_pipeline, request, context);
// Deserialize the Service response token and return the JSON web token returned by the
// service.
std::string responseToken = AttestationServiceTokenResponseSerializer::Deserialize(response);
// Parse the JWT returned by the attestation service.
auto resultToken
= AttestationTokenInternal<Models::_detail::PolicyResult, PolicyResultSerializer>(
responseToken);
// Validate the token returned by the service. Use the cached attestation signers in the
// validation.
resultToken.ValidateToken(
options.TokenValidationOptionsOverride ? *options.TokenValidationOptionsOverride
: this->m_tokenValidationOptions,
m_attestationSigners);
// Extract the underlying policy token from the response.
auto internalResult
= static_cast<AttestationToken<Models::_detail::PolicyResult>>(resultToken).Body;
Models::PolicyResult returnedResult;
if (internalResult.PolicyResolution)
{
returnedResult.PolicyResolution
= Models::PolicyModification(*internalResult.PolicyResolution);
}
if (internalResult.PolicySigner)
{
returnedResult.PolicySigner = AttestationSignerInternal(*internalResult.PolicySigner);
}
if (internalResult.PolicyTokenHash)
{
returnedResult.PolicyTokenHash = Base64Url::Base64UrlDecode(*internalResult.PolicyTokenHash);
}
// Construct a token whose body is the policy result, but whose token is the response from
// the service.
auto returnedToken
= AttestationTokenInternal<Models::PolicyResult>(responseToken, &returnedResult);
return Response<AttestationToken<Models::PolicyResult>>(returnedToken, std::move(response));
}
catch (std::runtime_error const& ex)
{
tracingContext.Span.AddEvent(ex);
throw;
}
}
Azure::Response<Models::AttestationToken<Models::PolicyResult>>
AttestationAdministrationClient::ResetAttestationPolicy(
AttestationType const& attestationType,
SetPolicyOptions const& options,
Azure::Core::Context const& context) const
{
auto tracingContext(m_tracingFactory.CreateTracingContext("ResetAttestationPolicy", context));
try
{
// Calculate a signed (or unsigned) attestation policy token to send to the service.
Models::AttestationToken<void> tokenToSend(
CreateAttestationPolicyToken(Azure::Nullable<std::string>(), options.SigningKey));
Azure::Core::IO::MemoryBodyStream stream(
reinterpret_cast<uint8_t const*>(tokenToSend.RawToken.data()), tokenToSend.RawToken.size());
auto request = AttestationCommonRequest::CreateRequest(
m_endpoint,
m_apiVersion,
HttpMethod::Post,
{"policies/" + attestationType.ToString() + ":reset"},
&stream);
// Send the request to the service.
auto response = AttestationCommonRequest::SendRequest(*m_pipeline, request, context);
// Deserialize the Service response token and return the JSON web token returned by the
// service.
std::string responseToken = AttestationServiceTokenResponseSerializer::Deserialize(response);
// Parse the JWT returned by the attestation service.
auto resultToken
= AttestationTokenInternal<Models::_detail::PolicyResult, PolicyResultSerializer>(
responseToken);
// Validate the token returned by the service. Use the cached attestation signers in the
// validation.
resultToken.ValidateToken(
options.TokenValidationOptionsOverride ? *options.TokenValidationOptionsOverride
: this->m_tokenValidationOptions,
m_attestationSigners);
// Extract the underlying policy token from the response.
auto internalResult
= static_cast<AttestationToken<Models::_detail::PolicyResult>>(resultToken).Body;
Models::PolicyResult returnedResult;
if (internalResult.PolicyResolution)
{
returnedResult.PolicyResolution
= Models::PolicyModification(*internalResult.PolicyResolution);
}
// Note that the attestation service currently never returns these values on Reset, even
// though they are meaningful. Commenting them out to improve code coverage numbers. At
// some point the attestation service may start returning these values, at which point
// they can be un-commented out.
// if (internalResult.PolicySigner)
// {
// returnedResult.PolicySigner =
// AttestationSignerInternal(*internalResult.PolicySigner);
// }
// if (internalResult.PolicyTokenHash)
// {
// returnedResult.PolicyTokenHash =
// Base64Url::Base64UrlDecode(*internalResult.PolicyTokenHash);
// }
// Construct a token whose body is the policy result, but whose token is the response from
// the service.
auto returnedToken
= AttestationTokenInternal<Models::PolicyResult>(responseToken, &returnedResult);
return Response<AttestationToken<Models::PolicyResult>>(returnedToken, std::move(response));
}
catch (std::runtime_error const& ex)
{
tracingContext.Span.AddEvent(ex);
throw;
}
}
Azure::Response<Models::AttestationToken<IsolatedModeCertificateListResult>>
AttestationAdministrationClient::GetIsolatedModeCertificates(
GetIsolatedModeCertificatesOptions const& options,
Azure::Core::Context const& context) const
{
auto tracingContext(
m_tracingFactory.CreateTracingContext("GetIsolatedModeCertificates", context));
try
{
auto request = AttestationCommonRequest::CreateRequest(
m_endpoint, m_apiVersion, HttpMethod::Get, {"certificates"}, nullptr);
// Send the request to the service.
auto response = AttestationCommonRequest::SendRequest(*m_pipeline, request, context);
// Deserialize the Service response token and return the JSON web token returned by the
// service.
std::string responseToken = AttestationServiceTokenResponseSerializer::Deserialize(response);
// Parse the JWT returned by the attestation service.
auto resultToken = AttestationTokenInternal<
Models::_detail::GetIsolatedModeCertificatesResult,
IsolatedModeCertificateGetResultSerializer>(responseToken);
// Validate the token returned by the service. Use the cached attestation signers in the
// validation.
resultToken.ValidateToken(
options.TokenValidationOptionsOverride ? *options.TokenValidationOptionsOverride
: this->m_tokenValidationOptions,
m_attestationSigners);
Models::_detail::JsonWebKeySet jwks(
*static_cast<AttestationToken<Models::_detail::GetIsolatedModeCertificatesResult>>(
resultToken)
.Body.PolicyCertificates);
Models::IsolatedModeCertificateListResult returnedResult;
for (const auto& certificate : jwks.Keys)
{
returnedResult.Certificates.push_back(AttestationSignerInternal(certificate));
}
// Construct a token whose body is the get policy certificates result, but whose token
// is the response from the service.
auto returnedToken = AttestationTokenInternal<Models::IsolatedModeCertificateListResult>(
responseToken, &returnedResult);
return Response<AttestationToken<Models::IsolatedModeCertificateListResult>>(
returnedToken, std::move(response));
}
catch (std::runtime_error const& ex)
{
tracingContext.Span.AddEvent(ex);
throw;
}
}
std::string AttestationAdministrationClient::CreateIsolatedModeModificationToken(
std::string const& pemEncodedX509CertificateToAdd,
AttestationSigningKey const& existingSigningKey) const
{
// Calculate a signed attestation policy token to send to the service.
// Embed the encoded policy in the StoredAttestationPolicy.
const auto x5cToAdd(Cryptography::ImportX509Certificate(pemEncodedX509CertificateToAdd));
// Create a JWK to add to the body.
JsonWebKey jwkToSend;
jwkToSend.Kty = x5cToAdd->GetKeyType();
jwkToSend.X5c = std::vector<std::string>{x5cToAdd->ExportAsBase64()};
IsolatedModeCertificateBody bodyToSend{jwkToSend};
auto const internalTokenToSend(
AttestationTokenInternal<IsolatedModeCertificateBody, IsolatedModeCertificateBodySerializer>::
CreateToken(bodyToSend, existingSigningKey));
auto const tokenToSend(
static_cast<AttestationToken<IsolatedModeCertificateBody>>(internalTokenToSend));
// JSON encode the string we're going to send.
return Azure::Core::Json::_internal::json(tokenToSend.RawToken).dump();
}
Models::AttestationToken<Models::IsolatedModeCertificateModificationResult>
AttestationAdministrationClient::ProcessIsolatedModeModificationResult(
std::unique_ptr<RawResponse> const& serverResponse,
AttestationTokenValidationOptions const& tokenValidationOptions) const
{
// Deserialize the Service response token and return the JSON web token returned by the
// service.
std::string responseToken
= AttestationServiceTokenResponseSerializer::Deserialize(serverResponse);
// Parse the JWT returned by the attestation service.
auto const resultToken = AttestationTokenInternal<
Models::_detail::ModifyIsolatedModeCertificatesResult,
ModifyIsolatedModeCertificatesResultSerializer>(responseToken);
// Validate the token returned by the service. Use the cached attestation signers in the
// validation.
resultToken.ValidateToken(tokenValidationOptions, m_attestationSigners);
// Extract the underlying policy token from the response.
auto internalResult
= static_cast<AttestationToken<Models::_detail::ModifyIsolatedModeCertificatesResult>>(
resultToken)
.Body;
Models::IsolatedModeCertificateModificationResult returnValue;
if (internalResult.CertificateResolution)
{
returnValue.CertificateModification
= Models::PolicyCertificateModification(*internalResult.CertificateResolution);
}
if (internalResult.CertificateThumbprint)
{
returnValue.CertificateThumbprint = (*internalResult.CertificateThumbprint);
}
// Construct a token whose body is the policy result, but whose token is the response
// from the service.
auto const returnedToken
= AttestationTokenInternal<Models::IsolatedModeCertificateModificationResult>(
responseToken, &returnValue);
return returnedToken;
}
Azure::Response<Models::AttestationToken<Models::IsolatedModeCertificateModificationResult>>
AttestationAdministrationClient::AddIsolatedModeCertificate(
std::string const& pemEncodedX509CertificateToAdd,
AttestationSigningKey const& existingSigningKey,
AddIsolatedModeCertificateOptions const& options,
Azure::Core::Context const& context) const
{
auto tracingContext(m_tracingFactory.CreateTracingContext("AddIsolatedModeCertificate", context));
try
{
auto const policyCertToken(
CreateIsolatedModeModificationToken(pemEncodedX509CertificateToAdd, existingSigningKey));
Azure::Core::IO::MemoryBodyStream stream(
reinterpret_cast<uint8_t const*>(policyCertToken.data()), policyCertToken.size());
auto request = AttestationCommonRequest::CreateRequest(
m_endpoint, m_apiVersion, HttpMethod::Post, {"certificates:add"}, &stream);
// Send the request to the service.
auto response = AttestationCommonRequest::SendRequest(*m_pipeline, request, context);
AttestationToken<IsolatedModeCertificateModificationResult> returnValue(
ProcessIsolatedModeModificationResult(
response,
options.TokenValidationOptionsOverride ? *options.TokenValidationOptionsOverride
: this->m_tokenValidationOptions));
return Response<AttestationToken<Models::IsolatedModeCertificateModificationResult>>(
returnValue, std::move(response));
}
catch (std::runtime_error const& ex)
{
tracingContext.Span.AddEvent(ex);
throw;
}
}
Azure::Response<Models::AttestationToken<Models::IsolatedModeCertificateModificationResult>>
AttestationAdministrationClient::RemoveIsolatedModeCertificate(
std::string const& pemEncodedX509CertificateToRemove,
AttestationSigningKey const& existingSigningKey,
RemoveIsolatedModeCertificateOptions const& options,
Azure::Core::Context const& context) const
{
auto tracingContext(
m_tracingFactory.CreateTracingContext("RemoveIsolatedModeCertificate", context));
try
{
// Calculate a signed (or unsigned) attestation policy token to send to the service.
// Embed the encoded policy in the StoredAttestationPolicy.
auto const policyCertToken(
CreateIsolatedModeModificationToken(pemEncodedX509CertificateToRemove, existingSigningKey));
Azure::Core::IO::MemoryBodyStream stream(
reinterpret_cast<uint8_t const*>(policyCertToken.data()), policyCertToken.size());
auto request = AttestationCommonRequest::CreateRequest(
m_endpoint, m_apiVersion, HttpMethod::Post, {"certificates:remove"}, &stream);
// Send the request to the service.
auto response = AttestationCommonRequest::SendRequest(*m_pipeline, request, context);
AttestationToken<IsolatedModeCertificateModificationResult> returnValue(
ProcessIsolatedModeModificationResult(
response,
options.TokenValidationOptionsOverride ? *options.TokenValidationOptionsOverride
: this->m_tokenValidationOptions));
return Response<AttestationToken<Models::IsolatedModeCertificateModificationResult>>(
returnValue, std::move(response));
}
catch (std::runtime_error const& ex)
{
tracingContext.Span.AddEvent(ex);
throw;
}
}
/**
* @brief Retrieves the information needed to validate the response returned from the
* attestation service.
*
* @details Validating the response returned by the attestation service requires a set of
* possible signers for the attestation token.
*
* @param context Client context for the request to the service.
*/
void AttestationAdministrationClient::RetrieveResponseValidationCollateral(
Azure::Core::Context const& context)
{
auto tracingContext(m_tracingFactory.CreateTracingContext("Create", context));
try
{
std::unique_lock<std::shared_timed_mutex> stateLock(SharedStateLock);
if (m_attestationSigners.empty())
{
stateLock.unlock();
auto request = AttestationCommonRequest::CreateRequest(
m_endpoint, HttpMethod::Get, {"certs"}, nullptr);
auto response = AttestationCommonRequest::SendRequest(*m_pipeline, request, context);
auto jsonWebKeySet(JsonWebKeySetSerializer::Deserialize(response));
TokenValidationCertificateResult returnValue;
std::vector<AttestationSigner> newValue;
for (const auto& jwk : jsonWebKeySet.Keys)
{
AttestationSignerInternal internalSigner(jwk);
newValue.push_back(internalSigner);
}
stateLock.lock();
if (m_attestationSigners.empty())
{
m_attestationSigners = newValue;
}
}
}
catch (std::runtime_error const& ex)
{
tracingContext.Span.AddEvent(ex);
throw;
}
}