azure-sdk-for-cpp/sdk/attestation/azure-security-attestation/test/ut/token_test.cpp
Larry Osterman fb240a4b25
Define a standardized header ordering for Azure SDK for C++ (#4632)
* Standardized header ordering for C++ repo

* Reordered categories to move private headers above public headers; added space between categories to enable future flexibility
2023-05-26 14:32:45 -07:00

803 lines
28 KiB
C++

// Copyright(c) Microsoft Corporation.All rights reserved.
// SPDX-License-Identifier: MIT
#include "../../src/private/attestation_client_models_private.hpp"
#include "../../src/private/attestation_client_private.hpp"
#include "../../src/private/attestation_deserializers_private.hpp"
#include "../../src/private/crypto/inc/crypto.hpp"
#include "azure/attestation/attestation_client.hpp"
#include <azure/core/datetime.hpp>
#include <azure/core/internal/json/json.hpp>
#include <azure/core/internal/json/json_optional.hpp>
#include <azure/core/test/test_base.hpp>
#include <gtest/gtest.h>
// cspell:words jwk jwks
namespace Azure { namespace Security { namespace Attestation { namespace Test {
using namespace Azure::Core::Json::_internal;
using namespace Azure::Core::Http;
using namespace Azure::Security::Attestation;
using namespace Azure::Security::Attestation::_detail;
using namespace Azure::Security::Attestation::Models;
using namespace Azure::Security::Attestation::Models::_detail;
using namespace Azure::Core::_internal;
TEST(SerializationTests, TestDeserializePrimitivesJsonObject)
{
// Present JSON field.
{
Azure::Nullable<std::string> val;
JsonHelpers::SetIfExistsJson(
val, json::parse(R"({ "jsonObjectValue": {"stringField": "SF2"}})"), "jsonObjectValue");
EXPECT_TRUE(val);
EXPECT_EQ(R"({"stringField":"SF2"})", *val);
}
// Not present field.
{
Azure::Nullable<std::string> val;
JsonHelpers::SetIfExistsJson(
val, json::parse("{ \"objectValue\":{\"String Field\": 27}}"), "intValue");
EXPECT_FALSE(val);
}
}
TEST(SerializationTests, TestDeserializePrimitivesBase64Url)
{
std::string testData("Test Data");
std::string encodedData = Azure::Core::_internal::Base64Url::Base64UrlEncode(
std::vector<uint8_t>(testData.begin(), testData.end()));
// Present JSON field.
{
Azure::Nullable<std::vector<uint8_t>> val;
JsonOptional::SetIfExists<std::string, std::vector<uint8_t>>(
val,
json::parse("{ \"base64Urlfield\": \"" + encodedData + "\"}"),
"base64Urlfield",
Base64Url::Base64UrlDecode);
EXPECT_EQ(9ul, val->size());
EXPECT_EQ(testData, std::string(val->begin(), val->end()));
}
// Not present field.
{
Azure::Nullable<std::vector<uint8_t>> val;
JsonOptional::SetIfExists<std::string, std::vector<uint8_t>>(
val,
json::parse("{ \"base64Urlfield\": \"" + encodedData + "\"}"),
"intValue",
Base64Url::Base64UrlDecode);
EXPECT_FALSE(val);
}
}
TEST(SerializationTests, TestHexString)
{
{
auto bin(JsonHelpers::HexStringToBinary("010203AABBccddee"));
EXPECT_EQ(bin[0], 0x01);
EXPECT_EQ(bin[1], 0x02);
EXPECT_EQ(bin[2], 0x03);
EXPECT_EQ(bin[3], 0xaa);
EXPECT_EQ(bin[4], 0xbb);
EXPECT_EQ(bin[5], 0xcc);
EXPECT_EQ(bin[6], 0xdd);
EXPECT_EQ(bin[7], 0xee);
}
{
// cspell: disable
EXPECT_THROW(JsonHelpers::HexStringToBinary("ABCEQWERTY"), std::invalid_argument);
// cspell: enable
}
{
// cspell: disable
EXPECT_THROW(JsonHelpers::HexStringToBinary("ABC"), std::invalid_argument);
// cspell: enable
}
}
TEST(SerializationTests, TestDeserializeJWK)
{
{
EXPECT_THROW(
JsonWebKeySerializer::Deserialize(json::parse(R"({"Alg": "none"})")), std::runtime_error);
}
// cspell:disable
{
auto val(JsonWebKeySerializer::Deserialize(json::parse(R"(
{"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
"use":"enc",
"kid":"1"})")));
EXPECT_EQ("EC", *val.Kty);
EXPECT_EQ("P-256", *val.Crv);
EXPECT_EQ("MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", *val.X);
EXPECT_EQ("4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", *val.Y);
EXPECT_EQ("enc", *val.Use);
EXPECT_EQ("1", *val.Kid);
}
{
auto val(JsonWebKeySerializer::Deserialize(json::parse(R"({"kty":"RSA",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e":"AQAB",
"alg":"RS256",
"kid":"2011-04-29"})")));
EXPECT_EQ("RS256", *val.Alg);
EXPECT_EQ(
"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_"
"BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_"
"FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-"
"bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
*val.N);
EXPECT_EQ("AQAB", *val.E);
EXPECT_EQ("2011-04-29", *val.Kid);
// Now serialize the JWK back to JSON.
auto serializedKey(JsonWebKeySerializer::Serialize(val));
}
// cspell:enable
}
TEST(SerializationTests, TestPolicyCertificateManagementBody)
{
IsolatedModeCertificateBody body;
// cspell: disable
body.policyCertificate = JsonWebKeySerializer::Deserialize(json::parse(R"({"kty":"RSA",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e":"AQAB",
"alg":"RS256",
"kid":"2011-04-29"})"));
// cspell: enable
std::string serializedBody = IsolatedModeCertificateBodySerializer::Serialize(body);
auto deserializedBody
= IsolatedModeCertificateBodySerializer::Deserialize(json::parse(serializedBody));
EXPECT_EQ(body.policyCertificate.N.Value(), deserializedBody.policyCertificate.N.Value());
}
TEST(SerializationTests, TestDeserializeJWKS)
{
{
EXPECT_THROW(
JsonWebKeySetSerializer::Deserialize(json::parse(R"({"keys": [{"Alg": "none"}]})")),
std::runtime_error);
}
// cspell:disable
{
auto val(JsonWebKeySetSerializer::Deserialize(json::parse(R"(
{"keys": [
{"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
"use":"enc",
"kid":"1"},
{"kty":"RSA",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e":"AQAB",
"alg":"RS256",
"kid":"2011-04-29"}
]})")));
EXPECT_EQ(2ul, val.Keys.size());
EXPECT_EQ("EC", *val.Keys[0].Kty);
EXPECT_EQ("P-256", *val.Keys[0].Crv);
EXPECT_EQ("MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", *val.Keys[0].X);
EXPECT_EQ("4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", *val.Keys[0].Y);
EXPECT_EQ("enc", *val.Keys[0].Use);
EXPECT_EQ("1", *val.Keys[0].Kid);
EXPECT_EQ("RS256", *val.Keys[1].Alg);
EXPECT_EQ(
"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_"
"BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_"
"FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-"
"bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
*val.Keys[1].N);
EXPECT_EQ("AQAB", *val.Keys[1].E);
EXPECT_EQ("2011-04-29", *val.Keys[1].Kid);
}
{
EXPECT_THROW(
JsonWebKeySetSerializer::Deserialize(json::parse(R"({"xxx": [{"Alg": "none"}]})")),
std::runtime_error);
}
{
EXPECT_THROW(
JsonWebKeySetSerializer::Deserialize(json::parse(R"({"keys": {"Alg": "none"}})")),
std::runtime_error);
}
// cspell:enable
}
TEST(SerializationTests, TestSerializeAttestOpenEnclaveRequest)
{
{
Models::_detail::AttestOpenEnclaveRequest request;
request.Report = {1, 2, 3, 4};
request.RunTimeData = AttestationData{{4, 5, 7, 8}, AttestationDataType::Binary};
request.InitTimeData = AttestationData{{1, 2, 3, 4}, AttestationDataType::Json};
request.DraftPolicyForAttestation = "Draft";
request.Nonce = "My Nonce";
std::string val = AttestOpenEnclaveRequestSerializer::Serialize(request);
json parsedRequest = json::parse(val);
EXPECT_TRUE(parsedRequest.is_object());
EXPECT_TRUE(parsedRequest["report"].is_string());
EXPECT_TRUE(parsedRequest["inittimeData"].is_object());
EXPECT_TRUE(parsedRequest["runtimeData"].is_object());
EXPECT_TRUE(parsedRequest["draftPolicyForAttestation"].is_string());
EXPECT_TRUE(parsedRequest["nonce"].is_string());
}
}
TEST(SerializationTests, TestDeserializeTokenResponse)
{
{
auto val(AttestationServiceTokenResponseSerializer::Deserialize(
json::parse(R"({"token": "ABCDEFG.123.456"} )")));
EXPECT_EQ("ABCDEFG.123.456", val);
}
{
EXPECT_THROW(
AttestationServiceTokenResponseSerializer::Deserialize(
json::parse(R"({"fred": "ABCDEFG.123.456"} )")),
std::runtime_error);
}
}
TEST(SerializationTests, TestDeserializeSignerToJson)
{
auto asymmetricKey = Cryptography::CreateRsaKey(2048);
auto cert
= Cryptography::CreateX509CertificateForPrivateKey(asymmetricKey, "CN=TestSubject, C=US");
AttestationSigner signer{
std::string{"ABCDEFG"}, std::vector<std::string>{cert->ExportAsBase64()}};
std::string serializedSigner = AttestationSignerInternal::SerializeToJson(signer);
auto jsonSigner(json::parse(serializedSigner));
EXPECT_TRUE(jsonSigner["Kid"].is_string());
auto kidJson(jsonSigner["Kid"]);
auto Kid = kidJson.get<std::string>();
EXPECT_EQ(signer.KeyId.Value(), Kid);
EXPECT_TRUE(jsonSigner["X5c"].is_array());
auto X5c = jsonSigner["X5c"].get<std::vector<json>>();
EXPECT_TRUE(X5c[0].is_string());
auto x5c0(X5c[0]);
std::string x5cval(x5c0.get<std::string>());
EXPECT_EQ(x5cval, (*signer.CertificateChain)[0]);
}
template <typename T> bool CompareNullable(Nullable<T> const& me, Nullable<T> const& them)
{
if (me.HasValue() != them.HasValue())
{
return false;
}
if (me)
{
return (*me == *them);
}
return true;
}
template <>
bool CompareNullable(Nullable<Azure::DateTime> const& me, Nullable<Azure::DateTime> const& them)
{
if (me.HasValue() != them.HasValue())
{
return false;
}
if (me)
{
Azure::DateTime meTime(Azure::Core::_internal::PosixTimeConverter::PosixTimeToDateTime(
PosixTimeConverter::DateTimeToPosixTime(*me)));
Azure::DateTime themTime(Azure::Core::_internal::PosixTimeConverter::PosixTimeToDateTime(
PosixTimeConverter::DateTimeToPosixTime(*them)));
return (meTime == themTime);
}
return true;
}
struct TestObject
{
Azure::Nullable<std::string> Algorithm;
Azure::Nullable<int> Integer;
Azure::Nullable<Azure::DateTime> ExpiresAt;
Azure::Nullable<Azure::DateTime> IssuedOn;
Azure::Nullable<Azure::DateTime> NotBefore;
Azure::Nullable<std::vector<int>> IntegerArray;
Azure::Nullable<std::string> Issuer;
bool operator==(TestObject const& that) const
{
if (!CompareNullable(Algorithm, that.Algorithm))
{
return false;
}
if (!CompareNullable(Integer, that.Integer))
{
return false;
}
if (!CompareNullable(IntegerArray, that.IntegerArray))
{
return false;
}
if (!CompareNullable(ExpiresAt, that.ExpiresAt))
{
return false;
}
if (!CompareNullable(IssuedOn, that.IssuedOn))
{
return false;
}
if (!CompareNullable(NotBefore, that.NotBefore))
{
return false;
}
if (!CompareNullable(Issuer, that.Issuer))
{
return false;
}
return true;
}
bool operator!=(TestObject const& that) { return !(*this == that); }
};
struct TestObjectSerializer
{
static TestObject Deserialize(json const& serialized)
{
TestObject returnValue;
JsonOptional::SetIfExists(returnValue.Algorithm, serialized, "Alg");
JsonOptional::SetIfExists<int64_t, Azure::DateTime>(
returnValue.ExpiresAt,
serialized,
"exp",
Azure::Core::_internal::PosixTimeConverter::PosixTimeToDateTime);
JsonOptional::SetIfExists<int64_t, Azure::DateTime>(
returnValue.IssuedOn,
serialized,
"iat",
Azure::Core::_internal::PosixTimeConverter::PosixTimeToDateTime);
JsonOptional::SetIfExists<int64_t, Azure::DateTime>(
returnValue.NotBefore,
serialized,
"nbf",
Azure::Core::_internal::PosixTimeConverter::PosixTimeToDateTime);
JsonOptional::SetIfExists(returnValue.IntegerArray, serialized, "intArray");
JsonOptional::SetIfExists(returnValue.Issuer, serialized, "iss");
JsonOptional::SetIfExists(returnValue.Integer, serialized, "int");
return returnValue;
}
static std::string Serialize(TestObject const& testObject)
{
json returnValue;
JsonOptional::SetFromNullable(testObject.Algorithm, returnValue, "Alg");
JsonOptional::SetFromNullable(testObject.Integer, returnValue, "int");
JsonOptional::SetFromNullable(testObject.IntegerArray, returnValue, "intArray");
JsonOptional::SetFromNullable<Azure::DateTime, int64_t>(
testObject.ExpiresAt,
returnValue,
"exp",
Azure::Core::_internal::PosixTimeConverter::DateTimeToPosixTime);
JsonOptional::SetFromNullable(testObject.Issuer, returnValue, "iss");
JsonOptional::SetFromNullable<Azure::DateTime, int64_t>(
testObject.IssuedOn,
returnValue,
"iat",
Azure::Core::_internal::PosixTimeConverter::DateTimeToPosixTime);
JsonOptional::SetFromNullable<Azure::DateTime, int64_t>(
testObject.NotBefore,
returnValue,
"nbf",
Azure::Core::_internal::PosixTimeConverter::DateTimeToPosixTime);
return returnValue.dump();
}
};
TEST(SerializationTests, SerializeDeserializeTestObject)
{
{
TestObject testObject;
testObject.Algorithm = "RSA";
testObject.Integer = 314;
testObject.ExpiresAt = std::chrono::system_clock::now();
testObject.IssuedOn = std::chrono::system_clock::now();
testObject.NotBefore = std::chrono::system_clock::now();
testObject.IntegerArray = {1, 2, 99, 32};
testObject.Issuer = "George";
auto serializedObject = TestObjectSerializer::Serialize(testObject);
auto targetObject = TestObjectSerializer::Deserialize(json::parse(serializedObject));
EXPECT_EQ(testObject, targetObject);
}
{
TestObject testObject;
testObject.Algorithm = "RSA";
testObject.ExpiresAt = std::chrono::system_clock::now();
testObject.IssuedOn = std::chrono::system_clock::now();
testObject.IntegerArray = {1, 2, 99, 32};
auto serializedObject = TestObjectSerializer::Serialize(testObject);
auto targetObject = TestObjectSerializer::Deserialize(json::parse(serializedObject));
EXPECT_EQ(testObject, targetObject);
}
}
TEST(AttestationTokenTests, CreateUnsecuredTokenFromObject)
{
{
TestObject testObject;
testObject.Algorithm = "RSA";
testObject.Integer = 314;
testObject.ExpiresAt = std::chrono::system_clock::now() + std::chrono::seconds(30);
testObject.IssuedOn = std::chrono::system_clock::now();
testObject.NotBefore = std::chrono::system_clock::now();
testObject.IntegerArray = {1, 2, 99, 32};
testObject.Issuer = "George";
auto testToken
= AttestationTokenInternal<TestObject, TestObjectSerializer>::CreateToken(testObject);
EXPECT_NO_THROW(testToken.ValidateToken({}));
AttestationToken<TestObject> token = testToken;
EXPECT_EQ(testObject, token.Body);
EXPECT_TRUE(token.ExpiresOn);
EXPECT_TRUE(token.IssuedOn);
EXPECT_TRUE(token.NotBefore);
EXPECT_TRUE(token.Issuer);
EXPECT_EQ(*token.Issuer, "George");
EXPECT_TRUE(token.Header.Algorithm);
EXPECT_EQ("none", *token.Header.Algorithm);
}
}
TEST(AttestationTokenTests, TestUnsecuredTokenValidation)
{
// Test expired tokens.
{
// Capture the current time, needed for future validation.
auto now = std::chrono::system_clock::now() - std::chrono::seconds(30);
TestObject testObject;
testObject.Algorithm = "RSA";
testObject.Integer = 314;
testObject.IntegerArray = {1, 2, 99, 32};
testObject.Issuer = "George";
// This token was issued 40 seconds ago, and is valid for 15 seconds.
testObject.ExpiresAt = now + std::chrono::seconds(15);
testObject.IssuedOn = now;
testObject.NotBefore = now;
auto testToken
= AttestationTokenInternal<TestObject, TestObjectSerializer>::CreateToken(testObject);
// Simple valdation should throw an exception.
EXPECT_THROW(testToken.ValidateToken({}), std::runtime_error);
// Validate the token asking to ignore token validation.
{
AttestationTokenValidationOptions tokenOptions{};
tokenOptions.ValidateToken = false;
EXPECT_NO_THROW(testToken.ValidateToken(tokenOptions));
}
// Validate the token asking to ignore token expiration time.
{
AttestationTokenValidationOptions tokenOptions{};
tokenOptions.ValidateExpirationTime = false;
EXPECT_NO_THROW(testToken.ValidateToken(tokenOptions));
}
}
// Test tokens which are not yet ready.
{
// Capture the current time, 30 seconds in the future.
auto now = std::chrono::system_clock::now() + std::chrono::seconds(30);
TestObject testObject;
testObject.Algorithm = "RSA";
testObject.Integer = 314;
testObject.IntegerArray = {1, 2, 99, 32};
testObject.Issuer = "George";
// This token will be issued 30 seconds from now, and is valid for 15 seconds.
testObject.ExpiresAt = now + std::chrono::seconds(15);
testObject.IssuedOn = now;
testObject.NotBefore = now;
auto testToken
= AttestationTokenInternal<TestObject, TestObjectSerializer>::CreateToken(testObject);
// Simple valdation should throw an exception.
EXPECT_THROW(testToken.ValidateToken({}), std::runtime_error);
// Validate the token asking to ignore token validation.
{
AttestationTokenValidationOptions tokenOptions{};
tokenOptions.ValidateToken = false;
EXPECT_NO_THROW(testToken.ValidateToken(tokenOptions));
}
// Validate the token asking to ignore token expiration time.
{
AttestationTokenValidationOptions tokenOptions{};
tokenOptions.ValidateNotBeforeTime = false;
EXPECT_NO_THROW(testToken.ValidateToken(tokenOptions));
}
}
}
void CreateSecuredToken(
std::unique_ptr<Cryptography::AsymmetricKey> const& key,
std::unique_ptr<Cryptography::X509Certificate> const& cert)
{
TestObject testObject;
testObject.Algorithm = "UnknownAlgorithm";
testObject.Integer = 314;
// Capture the current time, needed for future validation.
auto now = std::chrono::system_clock::now();
// This token was issued now, and is valid for 30 seconds.
testObject.ExpiresAt = now + std::chrono::seconds(30);
testObject.IssuedOn = now;
testObject.NotBefore = now;
testObject.IntegerArray = {1, 2, 99, 32};
testObject.Issuer = "George";
AttestationSigningKey signingKey{key->ExportPrivateKey(), cert->ExportAsPEM()};
// Create a secured attestation token wrapped around the TestObject.
auto testToken = AttestationTokenInternal<TestObject, TestObjectSerializer>::CreateToken(
testObject, signingKey);
// Validate this token - it should not throw.
EXPECT_NO_THROW(testToken.ValidateToken({}));
{
AttestationTokenValidationOptions validationOptions;
validationOptions.ValidateIssuer = true;
validationOptions.ExpectedIssuer = "George";
EXPECT_NO_THROW(testToken.ValidateToken(validationOptions));
}
AttestationToken<TestObject> token = testToken;
EXPECT_EQ(testObject, token.Body);
EXPECT_TRUE(token.ExpiresOn);
EXPECT_TRUE(token.IssuedOn);
EXPECT_TRUE(token.NotBefore);
EXPECT_TRUE(token.Issuer);
EXPECT_EQ(*token.Issuer, "George");
}
TEST(AttestationTokenTests, CreateSecuredTokenFromObject)
{
{
// Create an RSA public/private key pair.
auto asymmetricKey = Cryptography::CreateRsaKey(2048);
auto cert
= Cryptography::CreateX509CertificateForPrivateKey(asymmetricKey, "CN=TestSubject, C=US");
CreateSecuredToken(asymmetricKey, cert);
}
{
// Create an RSA public/private key pair.
auto asymmetricKey = Cryptography::CreateEcdsaKey();
auto cert
= Cryptography::CreateX509CertificateForPrivateKey(asymmetricKey, "CN=TestSubject, C=US");
CreateSecuredToken(asymmetricKey, cert);
}
}
TEST(AttestationTokenTests, TestSecuredTokenValidation)
{
// Create an RSA public/private key pair. Use these for the subsequent tests.
auto asymmetricKey = Cryptography::CreateRsaKey(2048);
auto cert
= Cryptography::CreateX509CertificateForPrivateKey(asymmetricKey, "CN=TestSubject, C=US");
AttestationSigningKey signingKey{asymmetricKey->ExportPrivateKey(), cert->ExportAsPEM()};
// Test expired tokens.
{
// Capture the current time, needed for future validation.
auto now = std::chrono::system_clock::now() - std::chrono::seconds(30);
TestObject testObject;
testObject.Algorithm = "RSA";
testObject.Integer = 314;
testObject.IntegerArray = {1, 2, 99, 32};
testObject.Issuer = "George";
// This token was issued 40 seconds ago, and is valid for 15 seconds.
testObject.ExpiresAt = now + std::chrono::seconds(15);
testObject.IssuedOn = now;
testObject.NotBefore = now;
auto testToken = AttestationTokenInternal<TestObject, TestObjectSerializer>::CreateToken(
testObject, signingKey);
// Simple valdation should throw an exception.
EXPECT_THROW(testToken.ValidateToken({}), std::runtime_error);
// Validate the token asking to ignore token validation.
{
AttestationTokenValidationOptions tokenOptions{};
tokenOptions.ValidateToken = false;
EXPECT_NO_THROW(testToken.ValidateToken(tokenOptions));
}
// Validate the token asking to ignore token expiration time.
{
AttestationTokenValidationOptions tokenOptions{};
tokenOptions.ValidateExpirationTime = false;
EXPECT_NO_THROW(testToken.ValidateToken(tokenOptions));
}
}
// Test tokens which are not yet ready.
{
// Capture the current time, 30 seconds in the future.
auto now = std::chrono::system_clock::now() + std::chrono::seconds(30);
TestObject testObject;
testObject.Algorithm = "RSA";
testObject.Integer = 314;
testObject.IntegerArray = {1, 2, 99, 32};
testObject.Issuer = "George";
// This token will be issued 30 seconds from now, and is valid for 15 seconds.
testObject.ExpiresAt = now + std::chrono::seconds(15);
testObject.IssuedOn = now;
testObject.NotBefore = now;
auto testToken = AttestationTokenInternal<TestObject, TestObjectSerializer>::CreateToken(
testObject, signingKey);
// Simple valdation should throw an exception.
EXPECT_THROW(testToken.ValidateToken({}), std::runtime_error);
// Validate the token asking to ignore token validation.
{
AttestationTokenValidationOptions tokenOptions{};
tokenOptions.ValidateToken = false;
EXPECT_NO_THROW(testToken.ValidateToken(tokenOptions));
}
// Validate the token asking to ignore token expiration time.
{
AttestationTokenValidationOptions tokenOptions{};
tokenOptions.ValidateNotBeforeTime = false;
EXPECT_NO_THROW(testToken.ValidateToken(tokenOptions));
}
}
// Test signature corruptions...
{
// Capture the current time.
auto now = std::chrono::system_clock::now();
TestObject testObject;
testObject.Algorithm = "RSA";
testObject.Integer = 314;
testObject.IntegerArray = {1, 2, 99, 32};
testObject.Issuer = "George";
// This token will be issued 30 seconds from now, and is valid for 15 seconds.
testObject.ExpiresAt = now + std::chrono::seconds(15);
testObject.IssuedOn = now;
testObject.NotBefore = now;
auto goodToken = AttestationTokenInternal<TestObject, TestObjectSerializer>::CreateToken(
testObject, signingKey);
auto signedToken = static_cast<AttestationToken<TestObject>>(goodToken).RawToken;
signedToken += "ABCDEFGH"; // Corrupt the signature on the signedToken.
auto badToken = AttestationTokenInternal<TestObject, TestObjectSerializer>(signedToken);
// Simple valdation should throw an exception - the signature of the token is invalid.
EXPECT_THROW(badToken.ValidateToken({}), std::runtime_error);
// Validate the token asking to ignore token validation.
{
AttestationTokenValidationOptions tokenOptions{};
tokenOptions.ValidateToken = false;
EXPECT_NO_THROW(badToken.ValidateToken(tokenOptions));
}
// Validate the token asking to ignore token signature validation.
{
AttestationTokenValidationOptions tokenOptions{};
tokenOptions.ValidateSigner = false;
EXPECT_NO_THROW(badToken.ValidateToken(tokenOptions));
}
}
// Test incorrect issuer...
{
// Capture the current time.
auto now = std::chrono::system_clock::now();
TestObject testObject;
testObject.Algorithm = "RSA";
testObject.Integer = 314;
testObject.IntegerArray = {1, 2, 99, 32};
testObject.Issuer = "George";
// This token will be issued 30 seconds from now, and is valid for 15 seconds.
testObject.ExpiresAt = now + std::chrono::seconds(15);
testObject.IssuedOn = now;
testObject.NotBefore = now;
auto testToken = AttestationTokenInternal<TestObject, TestObjectSerializer>::CreateToken(
testObject, signingKey);
AttestationTokenValidationOptions validationOptions;
validationOptions.ValidateIssuer = true;
validationOptions.ExpectedIssuer = "Fred";
// Simple valdation should throw an exception - the signature of the token is invalid.
EXPECT_THROW(testToken.ValidateToken(validationOptions), std::runtime_error);
// Validate the token asking to ignore token signature validation.
{
AttestationTokenValidationOptions tokenOptions{};
tokenOptions.ValidateIssuer = false;
tokenOptions.ExpectedIssuer = "Fred";
EXPECT_NO_THROW(testToken.ValidateToken(tokenOptions));
}
}
// Test no issuer but issuer validation requested.
{
// Capture the current time.
auto now = std::chrono::system_clock::now();
TestObject testObject;
testObject.Algorithm = "RSA";
testObject.Integer = 314;
testObject.IntegerArray = {1, 2, 99, 32};
// This token will be issued 30 seconds from now, and is valid for 15 seconds.
testObject.ExpiresAt = now + std::chrono::seconds(15);
testObject.IssuedOn = now;
testObject.NotBefore = now;
auto testToken = AttestationTokenInternal<TestObject, TestObjectSerializer>::CreateToken(
testObject, signingKey);
AttestationTokenValidationOptions validationOptions;
validationOptions.ValidateIssuer = true;
validationOptions.ExpectedIssuer = "Fred";
// Simple valdation should throw an exception - the signature of the token is invalid.
EXPECT_THROW(testToken.ValidateToken(validationOptions), std::runtime_error);
}
}
}}}} // namespace Azure::Security::Attestation::Test