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
- HardwareIdentification.GetPackageSpecificToken
Show: