App Specific Hardware ID (ASHWID) cloud component
The App Specific Hardware ID (ASHWID) component of a back-end cloud service verifies that the output of the HardwareIdentification.GetPackageSpecificToken method is genuine. The cloud service, in conjunction with the ASHWID, provides a mechanism to limit content distribution to a fixed number of devices.
Use either this C# or C++ code for your ASHWID cloud component.
Here is the C# ASHWID cloud code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.Sample.Ashwid
{
public static class CloudVerification
{
#region publickey
static readonly byte[] gRootPublicKey = new byte[] {
0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xa8, 0xef, 0xce, 0xef, 0xec, 0x12, 0x8b,
0x92, 0x94, 0xed, 0xcf, 0xaa, 0xa5, 0x81, 0x8d, 0x4f, 0xa4, 0xad, 0x4a, 0xec, 0xa5, 0xf0, 0xda,
0xa8, 0x3d, 0xb6, 0xe5, 0x61, 0x01, 0x99, 0xce, 0x3a, 0x23, 0x73, 0x5a, 0x58, 0x67, 0x9f, 0xf5,
0xb6, 0x5b, 0xf5, 0x4f, 0xf9, 0xa0, 0x9b, 0x75, 0x1e, 0xcc, 0x53, 0x62, 0x10, 0x3c, 0xa7, 0xa5,
0x3a, 0x3b, 0xe6, 0x24, 0x22, 0xf4, 0x18, 0x96, 0x2e, 0xf2, 0xfc, 0xd9, 0xa5, 0x88, 0xc6, 0xfd,
0x51, 0xf0, 0x31, 0xc3, 0xbd, 0x01, 0xdc, 0x45, 0xb6, 0xf6, 0x40, 0x2b, 0xb7, 0x45, 0x7b, 0x45,
0x4f, 0xed, 0xc0, 0xb4, 0x7c, 0x58, 0x44, 0xf9, 0x89, 0xfb, 0x6a, 0x75, 0x3b, 0x6d, 0xf1, 0x2e,
0xac, 0x35, 0xa1, 0x5f, 0x7a, 0x94, 0xcd, 0x3a, 0x6d, 0x98, 0xb8, 0xb8, 0x29, 0xe6, 0x33, 0x98,
0x2e, 0x33, 0x83, 0x7a, 0x86, 0xb7, 0xa8, 0x0a, 0x10, 0xf2, 0x07, 0x32, 0x63, 0xe4, 0x32, 0xed,
0x4d, 0xab, 0x05, 0x0c, 0xa1, 0xd7, 0x72, 0x49, 0xac, 0x35, 0x2c, 0x2e, 0x70, 0xed, 0xee, 0x12,
0xfc, 0x23, 0xb1, 0xdc, 0x5a, 0xdf, 0x61, 0xe9, 0x2c, 0x44, 0xcd, 0xae, 0xdb, 0x06, 0x54, 0x8f,
0x4f, 0xc1, 0xd6, 0x15, 0x72, 0xae, 0x50, 0x89, 0x39, 0x89, 0xf5, 0x95, 0x82, 0xdc, 0xff, 0x41,
0xeb, 0x89, 0x6f, 0xbc, 0xe0, 0x9f, 0x79, 0x5d, 0x24, 0x16, 0xf7, 0x1d, 0x38, 0xaa, 0xde, 0xd8,
0x24, 0x97, 0xf6, 0x97, 0x47, 0x74, 0x5b, 0x23, 0x38, 0xc8, 0x9d, 0x2e, 0xaa, 0xd1, 0x1f, 0xce,
0x09, 0x5c, 0xf1, 0xb9, 0x9f, 0x92, 0x38, 0xd2, 0x11, 0x68, 0x3e, 0xcc, 0x5d, 0x4e, 0xcf, 0x94,
0x9f, 0xd2, 0x42, 0xbd, 0xe2, 0xf1, 0x4b, 0xf1, 0xa7, 0xa9, 0x5c, 0x79, 0x05, 0xfb, 0x25, 0xf7,
0xc1, 0x53, 0xf7, 0xd9, 0xc4, 0x4d, 0x79, 0x0f, 0x8a, 0x4d, 0xb4, 0x30, 0x71, 0xa6, 0xe9, 0x51,
0xe5, 0x8e, 0xe0, 0xc8, 0x83, 0xc7, 0x31, 0xfc, 0x98, 0x46, 0xf6, 0xa2, 0x76, 0xfc, 0xa6, 0x81,
0x6d, 0x76, 0x90, 0x8d, 0x32, 0x21, 0x1f, 0x2d, 0x3e, 0x69, 0x2b, 0x4f, 0xaa, 0xec, 0x7b, 0xd3,
0xb9, 0x64, 0xc1, 0xd6, 0xbb, 0x5f, 0xfa, 0x38, 0xc4, 0x41, 0xa6, 0x6d, 0x5a, 0xc3, 0x11, 0x87,
0xfb, 0xbc, 0x33, 0x70, 0x4a, 0x26, 0x8b, 0xe6, 0x44, 0xdd, 0xcb, 0xb8, 0x30, 0xd3, 0x9b, 0x7b,
0x1a, 0x0e, 0x03, 0xb4, 0x51, 0xe0, 0xca, 0xbf, 0x7b, 0x3c, 0x57, 0x9a, 0xa0, 0xd8, 0x4b, 0xfe,
0x7e, 0x36, 0xd8, 0x81, 0xfa, 0x25, 0xbd, 0x7e, 0x03, 0xf5, 0x59, 0x2c, 0xf6, 0xd7, 0xa7, 0x6d,
0xdd, 0x10, 0x77, 0x77, 0x09, 0xae, 0x76, 0xe2, 0x85, 0x33, 0xa6, 0x7d, 0x71, 0x20, 0xf8, 0x3a,
0x4f, 0x2a, 0xb6, 0xea, 0x42, 0x29, 0xd0, 0xd3, 0xc6, 0x29, 0x4b, 0x05, 0x2c, 0xe7, 0xb8, 0x4a,
0xcf, 0xd2, 0xbb, 0x82, 0x20, 0x30, 0x9b, 0xa2, 0x4d, 0x1f, 0x78, 0x2c, 0xd9, 0x54, 0x13, 0xd8,
0x2a, 0x28, 0x68, 0x51, 0x56, 0xa5, 0xf7, 0xdb, 0xae, 0x59, 0x0e, 0xb9, 0xd1, 0x30, 0x97, 0x82,
0x04, 0x66, 0xa5, 0x02, 0x3c, 0x25, 0xfa, 0xdd, 0xed, 0x09, 0xc2, 0x60, 0xbc, 0x17, 0x6c, 0xa1,
0x5a, 0xb6, 0x97, 0xcc, 0x8a, 0x13, 0x56, 0xf6, 0xb4, 0xae, 0xdf, 0xcf, 0x7e, 0x40, 0x2f, 0x49,
0x41, 0xe0, 0x63, 0x8e, 0x58, 0x20, 0xcc, 0xa3, 0x4f, 0x33, 0x3b, 0x9b, 0xcf, 0x3c, 0x72, 0x7e,
0x48, 0x41, 0x42, 0x3d, 0x63, 0xe3, 0x5e, 0xe7, 0x75, 0x6c, 0x7f, 0xef, 0x6d, 0x80, 0x09, 0xa4,
0x2b, 0xa4, 0x3e, 0xde, 0xe4, 0x2b, 0x2c, 0x2b, 0xa9, 0x44, 0x56, 0x83, 0xbe, 0xb6, 0x6e, 0x60,
0xb9, 0x16, 0x1a, 0xe1, 0x62, 0xe9, 0x54, 0x9d, 0xbf, 0x02, 0x03, 0x01, 0x00, 0x01};
#endregion
/// <summary>
/// Enumeration type that defines the different hardware types in a device.
/// </summary>
enum HardwareIdType
{
Invalid = 0,
Processor = 1,
Memory = 2,
DiskDevice = 3,
NetworkAdapter = 4,
DockingStation = 6,
MobileBroadband = 7,
Bluetooth = 8,
SmBios = 9
};
/// <summary>
/// Defines Id for an individual hardware in the device. type is one of the enumeration values
/// of HardwareIdType. value is the corresponding id value for the hardware.
/// </summary>
struct HardwareId
{
public UInt16 type;
public UInt16 value;
};
[StructLayout(LayoutKind.Sequential)]
internal struct BCRYPT_PSS_PADDING_INFO
{
[MarshalAs(UnmanagedType.LPWStr)]
internal string pszAlgId;
internal int cbSalt;
}
[StructLayout(LayoutKind.Sequential)]
internal struct BCRYPT_RSAKEY_BLOB
{
internal int Magic;
internal int BitLength;
internal int cbPublicExp;
internal int cbModulos;
internal int cbPrime1;
internal int cbPrime2;
}
internal static class UnsafeNativeMethods
{
[DllImport("ncrypt.dll")]
internal static extern int NCryptVerifySignature(
SafeNCryptKeyHandle hKey,
[In] ref BCRYPT_PSS_PADDING_INFO pPaddingInfo,
[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbHashValue,
int cbHashValue,
[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbSignature,
int cbSignature,
uint dwFlags);
}
/// <summary>
/// This function validates that the hardwareId is genuine by using nonce,
/// signature and certificate.
/// </summary>
/// <param name="nonce">The nonce that was sent to the client.</param>
/// <param name="id">Hardware id of the client device that was sent from the client app.</param>
/// <param name="signature">Signature for the nonce and hardwareId sent by the client app.</param>
/// <param name="certificate">Full certificate chain that was sent by the client app that was used to
/// sign signature data. This certificate chain is used to verify that the hardware id
/// data is generated by Windows OS on the client system.</param>
[PermissionSetAttribute(SecurityAction.Demand, Unrestricted = true)]
public static void ValidateData(byte[] nonce, byte[] id, byte[] signature, byte[] certificate)
{
// Convert the Certificate Chain which is in a serialized format to SignedCms object.
SignedCms cms = new SignedCms();
cms.Decode(certificate);
// Looping through all certificates to find the leaf certificate.
X509Certificate2 leafCertificate = null;
foreach (X509Certificate2 x509 in cms.Certificates)
{
bool basicConstraintExtensionExists = false;
foreach (X509Extension extension in x509.Extensions)
{
if (extension.Oid.FriendlyName == "Basic Constraints")
{
basicConstraintExtensionExists = true;
X509BasicConstraintsExtension ext = (X509BasicConstraintsExtension)extension;
if (!ext.CertificateAuthority)
{
leafCertificate = x509;
break;
}
}
}
if (leafCertificate != null)
{
break;
}
if (!basicConstraintExtensionExists)
{
if (x509.Issuer != x509.Subject)
{
leafCertificate = x509;
break;
}
}
}
if (leafCertificate == null)
{
throw new ArgumentException("Leaf certificate could not be found");
}
// Validating the certificate chain. Ignore the errors due to online revocation check not
// being available. Also we are not failing validation due to expired certificates. Microsoft
// will be revoking the certificates that were exploided.
X509Chain chain = new X509Chain();
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreNotTimeValid |
X509VerificationFlags.IgnoreCtlNotTimeValid |
X509VerificationFlags.IgnoreCertificateAuthorityRevocationUnknown |
X509VerificationFlags.IgnoreEndRevocationUnknown |
X509VerificationFlags.IgnoreCtlSignerRevocationUnknown;
chain.ChainPolicy.ApplicationPolicy.Add(new Oid("1.3.6.1.4.1.311.10.5.40"));
bool result = chain.Build(leafCertificate);
if (!result)
{
foreach (X509ChainStatus status in chain.ChainStatus)
{
switch (status.Status)
{
case X509ChainStatusFlags.NoError:
case X509ChainStatusFlags.NotTimeValid:
case X509ChainStatusFlags.NotTimeNested:
case X509ChainStatusFlags.CtlNotTimeValid:
case X509ChainStatusFlags.RevocationStatusUnknown:
case X509ChainStatusFlags.OfflineRevocation:
break;
default:
throw new ArgumentException("Chain verification failed with status " + status.Status);
}
}
}
// gRootPublicKey is the hard coded public key for the root certificate.
// Compare the public key on the root certificate with the hard coded one.
// They must match.
X509Certificate2 rootCertificate = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
if (!rootCertificate.PublicKey.EncodedKeyValue.RawData.SequenceEqual(gRootPublicKey))
{
throw new ArgumentException("Public key of the root certificate is not as expected.");
}
// Signature contains both nonce and hardwareId. So creating the combined data;
byte[] blob;
if (nonce == null)
{
blob = id;
}
else
{
blob = nonce.Concat(id).ToArray();
}
// Using the leaf Certificate we verify the signature of blob. The RSACryptoServiceProvider does not
// provide a way to pass in different padding mode. So we use Win32 NCryptVerifySignature API instead.
RSACryptoServiceProvider rsaCsp = leafCertificate.PublicKey.Key as RSACryptoServiceProvider;
RSAParameters parameters = rsaCsp.ExportParameters(false);
SHA1Managed sha1 = new SHA1Managed();
byte[] blobHash = sha1.ComputeHash(blob);
CngKey cngKey = CngKey.Import(GetPublicKey(parameters), CngKeyBlobFormat.GenericPublicBlob);
BCRYPT_PSS_PADDING_INFO paddingInfo = new BCRYPT_PSS_PADDING_INFO
{
pszAlgId = CngAlgorithm.Sha1.Algorithm,
cbSalt = 0
};
int result2 = UnsafeNativeMethods.NCryptVerifySignature(
cngKey.Handle,
ref paddingInfo,
blobHash,
blobHash.Length,
signature,
signature.Length,
8); // NCRYPT_PAD_PSS_FLAG
if (result2 != 0) // 0 means ERROR_SUCCESS
{
throw new ArgumentException("Verification failed with " + result2);
}
}
/// <summary>
/// In this method you should implement your business logic based on hardware ID.
/// You should call this method after ValidateData to make sure id is trustable.
/// </summary>
/// <param name="id">Hardware id of the client device that was sent from the client app.</param>
public static void ProcessData(byte[] id)
{
// Convert serialized hardwareId to well formed HardwareId structures so that
// it can be easily consumed.
if (id.Length % 4 != 0)
{
throw new ArgumentException("Invalid hardware id");
}
HardwareId[] hardwareIds = new HardwareId[id.Length / 4];
for (int index = 0; index < hardwareIds.Length; index++)
{
hardwareIds[index].type = BitConverter.ToUInt16(id, index * 4);
hardwareIds[index].value = BitConverter.ToUInt16(id, index * 4 + 2);
switch ((HardwareIdType)hardwareIds[index].type)
{
case HardwareIdType.Processor:
// implement your business logic based on hardwareIds[index].value
break;
case HardwareIdType.Memory:
// implement your business logic based on hardwareIds[index].value
break;
case HardwareIdType.NetworkAdapter:
// implement your business logic based on hardwareIds[index].value
break;
// Add other case statements for the other Hardware types here.
}
}
}
[System.Security.SecuritySafeCritical]
private static byte[] GetPublicKey(RSAParameters parameters)
{
int blobSize = Marshal.SizeOf(typeof(BCRYPT_RSAKEY_BLOB)) +
parameters.Exponent.Length +
parameters.Modulus.Length;
byte[] rsaBlob = new byte[blobSize];
unsafe
{
fixed (byte* pRsaBlob = rsaBlob)
{
BCRYPT_RSAKEY_BLOB* pBcryptBlob;
pBcryptBlob = (BCRYPT_RSAKEY_BLOB*)pRsaBlob;
pBcryptBlob->Magic = 0x31415352; // RsaPublic
pBcryptBlob->BitLength = parameters.Modulus.Length * 8;
pBcryptBlob->cbPublicExp = parameters.Exponent.Length;
pBcryptBlob->cbModulos = parameters.Modulus.Length;
int offset = Marshal.SizeOf(typeof(BCRYPT_RSAKEY_BLOB));
System.Buffer.BlockCopy(parameters.Exponent, 0, rsaBlob, offset, parameters.Exponent.Length);
offset += parameters.Exponent.Length;
System.Buffer.BlockCopy(parameters.Modulus, 0, rsaBlob, offset, parameters.Modulus.Length);
}
}
return rsaBlob;
}
}
}
Here is the C++ ASHWID cloud code:
/********************************************************
* *
* Copyright (C) Microsoft. All rights reserved. *
* *
********************************************************/
#include <Windows.h>
#include <wincrypt.h>
#include <intsafe.h>
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define STATUS_SUCCESS 0
BYTE gRootPublicKey[] = {
0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xa8, 0xef, 0xce, 0xef, 0xec, 0x12, 0x8b,
0x92, 0x94, 0xed, 0xcf, 0xaa, 0xa5, 0x81, 0x8d, 0x4f, 0xa4, 0xad, 0x4a, 0xec, 0xa5, 0xf0, 0xda,
0xa8, 0x3d, 0xb6, 0xe5, 0x61, 0x01, 0x99, 0xce, 0x3a, 0x23, 0x73, 0x5a, 0x58, 0x67, 0x9f, 0xf5,
0xb6, 0x5b, 0xf5, 0x4f, 0xf9, 0xa0, 0x9b, 0x75, 0x1e, 0xcc, 0x53, 0x62, 0x10, 0x3c, 0xa7, 0xa5,
0x3a, 0x3b, 0xe6, 0x24, 0x22, 0xf4, 0x18, 0x96, 0x2e, 0xf2, 0xfc, 0xd9, 0xa5, 0x88, 0xc6, 0xfd,
0x51, 0xf0, 0x31, 0xc3, 0xbd, 0x01, 0xdc, 0x45, 0xb6, 0xf6, 0x40, 0x2b, 0xb7, 0x45, 0x7b, 0x45,
0x4f, 0xed, 0xc0, 0xb4, 0x7c, 0x58, 0x44, 0xf9, 0x89, 0xfb, 0x6a, 0x75, 0x3b, 0x6d, 0xf1, 0x2e,
0xac, 0x35, 0xa1, 0x5f, 0x7a, 0x94, 0xcd, 0x3a, 0x6d, 0x98, 0xb8, 0xb8, 0x29, 0xe6, 0x33, 0x98,
0x2e, 0x33, 0x83, 0x7a, 0x86, 0xb7, 0xa8, 0x0a, 0x10, 0xf2, 0x07, 0x32, 0x63, 0xe4, 0x32, 0xed,
0x4d, 0xab, 0x05, 0x0c, 0xa1, 0xd7, 0x72, 0x49, 0xac, 0x35, 0x2c, 0x2e, 0x70, 0xed, 0xee, 0x12,
0xfc, 0x23, 0xb1, 0xdc, 0x5a, 0xdf, 0x61, 0xe9, 0x2c, 0x44, 0xcd, 0xae, 0xdb, 0x06, 0x54, 0x8f,
0x4f, 0xc1, 0xd6, 0x15, 0x72, 0xae, 0x50, 0x89, 0x39, 0x89, 0xf5, 0x95, 0x82, 0xdc, 0xff, 0x41,
0xeb, 0x89, 0x6f, 0xbc, 0xe0, 0x9f, 0x79, 0x5d, 0x24, 0x16, 0xf7, 0x1d, 0x38, 0xaa, 0xde, 0xd8,
0x24, 0x97, 0xf6, 0x97, 0x47, 0x74, 0x5b, 0x23, 0x38, 0xc8, 0x9d, 0x2e, 0xaa, 0xd1, 0x1f, 0xce,
0x09, 0x5c, 0xf1, 0xb9, 0x9f, 0x92, 0x38, 0xd2, 0x11, 0x68, 0x3e, 0xcc, 0x5d, 0x4e, 0xcf, 0x94,
0x9f, 0xd2, 0x42, 0xbd, 0xe2, 0xf1, 0x4b, 0xf1, 0xa7, 0xa9, 0x5c, 0x79, 0x05, 0xfb, 0x25, 0xf7,
0xc1, 0x53, 0xf7, 0xd9, 0xc4, 0x4d, 0x79, 0x0f, 0x8a, 0x4d, 0xb4, 0x30, 0x71, 0xa6, 0xe9, 0x51,
0xe5, 0x8e, 0xe0, 0xc8, 0x83, 0xc7, 0x31, 0xfc, 0x98, 0x46, 0xf6, 0xa2, 0x76, 0xfc, 0xa6, 0x81,
0x6d, 0x76, 0x90, 0x8d, 0x32, 0x21, 0x1f, 0x2d, 0x3e, 0x69, 0x2b, 0x4f, 0xaa, 0xec, 0x7b, 0xd3,
0xb9, 0x64, 0xc1, 0xd6, 0xbb, 0x5f, 0xfa, 0x38, 0xc4, 0x41, 0xa6, 0x6d, 0x5a, 0xc3, 0x11, 0x87,
0xfb, 0xbc, 0x33, 0x70, 0x4a, 0x26, 0x8b, 0xe6, 0x44, 0xdd, 0xcb, 0xb8, 0x30, 0xd3, 0x9b, 0x7b,
0x1a, 0x0e, 0x03, 0xb4, 0x51, 0xe0, 0xca, 0xbf, 0x7b, 0x3c, 0x57, 0x9a, 0xa0, 0xd8, 0x4b, 0xfe,
0x7e, 0x36, 0xd8, 0x81, 0xfa, 0x25, 0xbd, 0x7e, 0x03, 0xf5, 0x59, 0x2c, 0xf6, 0xd7, 0xa7, 0x6d,
0xdd, 0x10, 0x77, 0x77, 0x09, 0xae, 0x76, 0xe2, 0x85, 0x33, 0xa6, 0x7d, 0x71, 0x20, 0xf8, 0x3a,
0x4f, 0x2a, 0xb6, 0xea, 0x42, 0x29, 0xd0, 0xd3, 0xc6, 0x29, 0x4b, 0x05, 0x2c, 0xe7, 0xb8, 0x4a,
0xcf, 0xd2, 0xbb, 0x82, 0x20, 0x30, 0x9b, 0xa2, 0x4d, 0x1f, 0x78, 0x2c, 0xd9, 0x54, 0x13, 0xd8,
0x2a, 0x28, 0x68, 0x51, 0x56, 0xa5, 0xf7, 0xdb, 0xae, 0x59, 0x0e, 0xb9, 0xd1, 0x30, 0x97, 0x82,
0x04, 0x66, 0xa5, 0x02, 0x3c, 0x25, 0xfa, 0xdd, 0xed, 0x09, 0xc2, 0x60, 0xbc, 0x17, 0x6c, 0xa1,
0x5a, 0xb6, 0x97, 0xcc, 0x8a, 0x13, 0x56, 0xf6, 0xb4, 0xae, 0xdf, 0xcf, 0x7e, 0x40, 0x2f, 0x49,
0x41, 0xe0, 0x63, 0x8e, 0x58, 0x20, 0xcc, 0xa3, 0x4f, 0x33, 0x3b, 0x9b, 0xcf, 0x3c, 0x72, 0x7e,
0x48, 0x41, 0x42, 0x3d, 0x63, 0xe3, 0x5e, 0xe7, 0x75, 0x6c, 0x7f, 0xef, 0x6d, 0x80, 0x09, 0xa4,
0x2b, 0xa4, 0x3e, 0xde, 0xe4, 0x2b, 0x2c, 0x2b, 0xa9, 0x44, 0x56, 0x83, 0xbe, 0xb6, 0x6e, 0x60,
0xb9, 0x16, 0x1a, 0xe1, 0x62, 0xe9, 0x54, 0x9d, 0xbf, 0x02, 0x03, 0x01, 0x00, 0x01
};
// Enumeration type that defines the different hardware types in a device.
typedef enum HARDWARE_ID_TYPE
{
Invalid = 0,
Processor = 1,
Memory = 2,
DiskDevice = 3,
NetworkAdapter = 4,
DockingStation = 6,
MobileBroadband = 7,
Bluetooth = 8,
SmBios = 9
};
// Defines Id for an individual hardware in the device. type is one of the enumeration values
// of HARDWARE_ID_TYPE. value is the corresponding id value for the hardware.
typedef struct HARDWARE_ID
{
UINT16 type;
UINT16 value;
} HARDWARE_ID;
// Forward Declaration
HRESULT ValidateSignature(
PCERT_PUBLIC_KEY_INFO pKeyInfo,
BYTE *pbSignature,
DWORD cbSignature,
BYTE *pbData,
DWORD cbData);
// This function validates that the hardwareId is genuine by using nonce,
// signature and certificate.
//
// pbNonce - The nonce that was sent to the client.
// cbNonce - Length of pbNonce in bytes.
// pbHardwareId - Hardware id of the client device that was sent from the client app.
// cbHardwareId - Length of pbHardwareId in bytes.
// pbSignature - Signature for the nonce and hardwareId sent by the client app.
// cbSignature - Length of pbSignature in bytes.
// pbCert - Full certificate chain that was sent by the client app that was used to
// sign signature data. This certificate chain is used to verify that the hardware id
// data is generated by Windows OS on the client system.
// cbCert - Length of certificate chain in bytes.
HRESULT ValidateData(
BYTE *pbNonce,
DWORD cbNonce,
BYTE *pbHardwareId,
DWORD cbHardwareId,
BYTE *pbSignature,
DWORD cbSignature,
BYTE *pbCert,
DWORD cbCert)
{
HRESULT hr = S_OK;
PCCERT_CHAIN_CONTEXT pChainContext = nullptr;
PCERT_INFO pRootInfo = nullptr;
CERT_BLOB blob = { cbCert, (BYTE*)pbCert };
HCERTSTORE tempStore = 0;
PCCERT_CONTEXT pCert = nullptr;
PCCERT_CONTEXT pPreviousCert = nullptr;
PCERT_EXTENSION pCertExtension = nullptr;
DWORD cCerts = 0;
CERT_CHAIN_POLICY_STATUS chainPolicyStatus = {0};
CERT_ENHKEY_USAGE enhKeyUsage = {0};
CERT_CHAIN_PARA chainPara = {0};
CERT_CHAIN_POLICY_PARA chainPolicyPara = {0};
CERT_USAGE_MATCH usageMatch;
LPSTR ekuList[] = { "1.3.6.1.4.1.311.10.5.40" };
PCRYPT_BIT_BLOB pPublicKey;
BYTE* pbData = nullptr;
DWORD cbData = 0;
// Extract the objects from the signed message into the temporary store.
if (!CryptQueryObject(
CERT_QUERY_OBJECT_BLOB,
&blob,
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
0,
0,
0,
&tempStore,
0,
0))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
// Looping through all certificates to find the leaf certificate.
while((pCert = CertEnumCertificatesInStore(tempStore, pPreviousCert)) != nullptr)
{
BOOL bResult = FALSE;
PCERT_BASIC_CONSTRAINTS2_INFO pInfo = nullptr;
DWORD cbInfo = 0;
pPreviousCert = pCert;
pCertExtension = CertFindExtension(
szOID_BASIC_CONSTRAINTS2,
pCert->pCertInfo->cExtension,
pCert->pCertInfo->rgExtension);
if (pCertExtension != nullptr)
{
bResult = CryptDecodeObjectEx(
X509_ASN_ENCODING,
X509_BASIC_CONSTRAINTS2,
pCertExtension->Value.pbData,
pCertExtension->Value.cbData,
CRYPT_DECODE_ALLOC_FLAG,
NULL,
&pInfo,
&cbInfo);
if (bResult && (pInfo != nullptr))
{
BOOL leafCert = !pInfo->fCA;
LocalFree(pInfo);
if (leafCert)
{
break;
}
else
{
continue;
}
}
}
if (!(CertCompareCertificateName(
X509_ASN_ENCODING,
&pCert->pCertInfo->Issuer,
&pCert->pCertInfo->Subject)))
{
break;
}
};
// At this point pCert is pointing to the leaf certificate. If it is null
// it means we could not find the leaf certificate.
if (pCert == nullptr)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
// Get the certificate chain
enhKeyUsage.cUsageIdentifier = 1;
enhKeyUsage.rgpszUsageIdentifier = ekuList;
usageMatch.dwType = USAGE_MATCH_TYPE_AND;
usageMatch.Usage = enhKeyUsage;
chainPara.cbSize = sizeof(chainPara);
chainPara.RequestedUsage = usageMatch;
if (!CertGetCertificateChain(
nullptr,
pCert,
nullptr,
tempStore,
&chainPara,
CERT_CHAIN_REVOCATION_CHECK_CHAIN,
nullptr,
&pChainContext))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
// Validating the certificate chain. Ignore the errors due to online revocation check not
// being available. Also we are not failing validation due to expired certificates. Microsoft
// will be revoking the certificates that were exploided.
chainPolicyPara.cbSize = sizeof(CERT_CHAIN_POLICY_PARA);
chainPolicyPara.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_NOT_TIME_VALID_FLAGS |
CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS;
if (!CertVerifyCertificateChainPolicy(
CERT_CHAIN_POLICY_BASE,
pChainContext,
&chainPolicyPara,
&chainPolicyStatus))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
if (chainPolicyStatus.dwError != 0)
{
hr = HRESULT_FROM_WIN32(chainPolicyStatus.dwError);
goto Exit;
}
// Get the root certificates public key.
cCerts = pChainContext->rgpChain[0]->cElement;
pRootInfo = pChainContext->rgpChain[0]->rgpElement[cCerts - 1]->pCertContext->pCertInfo;
pPublicKey = &pRootInfo->SubjectPublicKeyInfo.PublicKey;
// gRootPublicKey is the hard coded public key for the root certificate.
// Compare the public key on the root certificate with the hard coded one.
// They must match.
if ((pPublicKey->cbData != sizeof(gRootPublicKey)) ||
(memcmp(gRootPublicKey, pPublicKey->pbData, sizeof(gRootPublicKey)) != 0))
{
hr = CERT_E_UNTRUSTEDROOT;
goto Exit;
}
// Signature contains both nonce and hardwareId. So creating the combined data;
hr = DWordAdd(cbNonce, cbHardwareId, &cbData);
if (FAILED(hr))
{
goto Exit;
}
pbData = new BYTE[cbData];
if (pbData == nullptr)
{
hr = E_OUTOFMEMORY;
goto Exit;
}
CopyMemory(pbData, pbNonce, cbNonce);
CopyMemory(pbData + cbNonce, pbHardwareId, cbHardwareId);
// pbData now contains nonce+hardwareId. Using the public key of the leaf certificate
// verify that signature is correct.
hr = ValidateSignature(
&pChainContext->rgpChain[0]->rgpElement[0]->pCertContext->pCertInfo->SubjectPublicKeyInfo,
pbSignature,
cbSignature,
pbData,
cbData);
if (FAILED(hr))
{
goto Exit;
}
// At this point we verified that the data can be trusted.
hr = S_OK;
Exit:
if (pbData != nullptr)
{
delete[] pbData;
}
if (pChainContext != nullptr)
{
CertFreeCertificateChain(pChainContext);
}
if (pCert != nullptr)
{
CertFreeCertificateContext(pCert);
}
if (tempStore != nullptr)
{
CertCloseStore(tempStore, CERT_CLOSE_STORE_FORCE_FLAG);
}
return hr;
}
// This function validates that the signature is actually the signature of
// the passed in data,
//
// pKeyInfo - Public key of the leaf certificate that is used to sign the data.
HRESULT ValidateSignature(
PCERT_PUBLIC_KEY_INFO pKeyInfo,
BYTE *pbSignature,
DWORD cbSignature,
BYTE *pbData,
DWORD cbData)
{
HRESULT hr = S_OK;
NTSTATUS status = STATUS_SUCCESS;
BCRYPT_ALG_HANDLE hAlg = nullptr;
BCRYPT_HASH_HANDLE hHash = nullptr;
BCRYPT_PSS_PADDING_INFO pad = {BCRYPT_SHA1_ALGORITHM, 0};
BCRYPT_KEY_HANDLE hKey = nullptr;
PBYTE pbHashObject = nullptr;
DWORD cbHashObject = 0;
PBYTE pbHash = nullptr;
DWORD cbHash = 0;
DWORD cbOutLength = 0;
status = BCryptOpenAlgorithmProvider(
&hAlg,
BCRYPT_SHA1_ALGORITHM,
NULL,
0);
if (!NT_SUCCESS(status))
{
hr = HRESULT_FROM_NT(status);
goto Exit;
}
status = BCryptGetProperty(
hAlg,
BCRYPT_OBJECT_LENGTH,
reinterpret_cast<PBYTE>(&cbHashObject),
sizeof(DWORD),
&cbOutLength,
0);
if (!NT_SUCCESS(status))
{
hr = HRESULT_FROM_NT(status);
goto Exit;
}
pbHashObject = static_cast<PBYTE>(HeapAlloc(GetProcessHeap(), 0, cbHashObject));
if (pbHashObject == nullptr)
{
hr = E_OUTOFMEMORY;
goto Exit;
}
status = BCryptGetProperty(
hAlg,
BCRYPT_HASH_LENGTH,
reinterpret_cast<PBYTE>(&cbHash),
sizeof(DWORD),
&cbOutLength,
0);
if (!NT_SUCCESS(status))
{
hr = HRESULT_FROM_NT(status);
goto Exit;
}
pbHash = static_cast<PBYTE>(HeapAlloc(GetProcessHeap(), 0, cbHash));
if(pbHash == nullptr)
{
hr = E_OUTOFMEMORY;
goto Exit;
}
status = BCryptCreateHash(
hAlg,
&hHash,
pbHashObject,
cbHashObject,
NULL,
0,
0);
if (!NT_SUCCESS(status))
{
hr = HRESULT_FROM_NT(status);
goto Exit;
}
status = BCryptHashData(
hHash,
static_cast<PBYTE>(pbData),
cbData,
0);
if (!NT_SUCCESS(status))
{
hr = HRESULT_FROM_NT(status);
goto Exit;
}
status = BCryptFinishHash(hHash, pbHash, cbHash, 0);
if (!NT_SUCCESS(status))
{
hr = HRESULT_FROM_NT(status);
goto Exit;
}
if (!CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, pKeyInfo, 0, nullptr, &hKey))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
status = BCryptVerifySignature(
hKey,
&pad,
pbHash,
cbHash,
static_cast<PUCHAR>(pbSignature),
cbSignature,
BCRYPT_PAD_PSS);
if (!NT_SUCCESS(status))
{
hr = HRESULT_FROM_NT(status);
goto Exit;
}
hr = S_OK;
Exit:
if (pbHash != nullptr)
{
HeapFree(GetProcessHeap(), 0, pbHash);
}
if (pbHashObject != nullptr)
{
HeapFree(GetProcessHeap(), 0, pbHashObject);
}
if(hHash != nullptr)
{
BCryptDestroyHash(hHash);
}
if(hKey != nullptr)
{
BCryptDestroyKey(hKey);
}
if(hAlg != nullptr)
{
BCryptCloseAlgorithmProvider(hAlg, 0);
}
return hr;
}
// In this method you should implement your business logic based on hardware ID.
// You should call this method after ValidateData to make sure hardware id is trustable.
//
// pbHardwareId - Hardware id of the client device that was sent from the client app.
// cbHardwareId - Length of pbHardwareId in bytes.
HRESULT ProcessData(
BYTE *pbHardwareId,
DWORD cbHardwareId)
{
HRESULT hr = S_OK;
DWORD dwHardwareIdsLength = 0;
HARDWARE_ID* pHardwareIds = nullptr;
// hardwareId is serialized form of HardwareId structures. So its length
// has to be dividible by the size of HARDWARE_ID structure. Since the data
// is already verified to be trusted this if statement should never be false,
// but still doing a sanity check.
if (cbHardwareId % sizeof(HARDWARE_ID) != 0)
{
hr = E_UNEXPECTED;
goto Exit;
}
// Now convert serialized hardwareId to well formed HARDWARE_ID structures so that
// it can be easily consumed.
pHardwareIds = reinterpret_cast<HARDWARE_ID*>(pbHardwareId);
dwHardwareIdsLength = cbHardwareId / sizeof(HARDWARE_ID);
for (DWORD index = 0; index < dwHardwareIdsLength; index++)
{
switch (pHardwareIds[index].type)
{
case Processor:
// implement your business logic based on pHardwareIds[index].value
break;
case Memory:
// implement your business logic based on pHardwareIds[index].value
break;
case NetworkAdapter:
// implement your business logic based on pHardwareIds[index].value
break;
// Add other case statements for the other Hardware types here.
}
}
hr = S_OK;
Exit:
return hr;
}
Related topics
Guidance on using the App Specific Hardware ID (ASHWID) to implement per-device app logic