Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
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