Protéger et collecter des données utilisateur

Si un client entre des informations dans les pages d’inscription OEM, les fichiers suivants sont créés lorsqu’il termine OOBE :

  • Userdata.blob. Fichier XML chiffré qui contient toutes les valeurs de tous les éléments configurables par l’utilisateur sur les pages d’inscription, y compris les champs d’informations client et les états de case à cocher.
  • SessionKey.blob. Généré pendant le chiffrement de Userdata.blob. Contient une clé de session nécessaire pour le processus de déchiffrement.
  • Userchoices.xml. Fichier XML non chiffré qui contient les étiquettes et les valeurs de case à cocher de toutes les cases à cocher incluses dans les pages d’inscription.

Notes

Si un client clique Skip sur la première page d’inscription, aucune donnée n’est écrite ou stockée dans ces fichiers, pas même les états par défaut de la case à cocher.

L’horodatage de l’expérience prête à l’emploi de l’utilisateur est également ajouté au Registre Windows sous cette clé :

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE\Stats [EndTimeStamp]

Cette valeur de Registre est créée, que les pages d’inscription soient incluses ou non dans OOBE. L’horodatage est écrit au format UTC (Temps universel coordonné) ; plus précisément, il s’agit d’une SYSTEMTIME valeur écrite sous la forme d’un objet blob sérialisé de données dans le Registre.

Pour accéder aux informations client et les utiliser, procédez comme suit :

  1. Générez une paire de clés publique/privée et placez la clé publique dans le %systemroot%\system32\Oobe\Info dossier de l’image.
  2. Collectez les données client chiffrées à l’aide d’une application ou d’un service qui s’exécute environ 30 minutes après la première ouverture de session.
  3. Envoyez les données à votre serveur pour le déchiffrement à l’aide de SSL. Vous pouvez ensuite déchiffrer la clé de session pour déchiffrer les données client.

Générer une paire de clés publique/privée

Pour protéger les données client, vous devez générer une paire de clés publique/privée, et la clé publique doit être placée dans le %systemroot%\system32\Oobe\Info dossier. Si vous déployez des images dans plusieurs régions ou dans plusieurs langues, vous devez placer la clé publique directement sous les sous-répertoires spécifiques à la région et à la langue, en suivant les mêmes règles que pour les fichiers de Oobe.xml propres à une région ou à une langue, comme décrit dans Fonctionnement Oobe.xml.

Important

Vous ne devez jamais placer la clé privée sur le PC du client. Au lieu de cela, il doit être stocké en toute sécurité sur vos serveurs afin que les données puissent être déchiffrées après leur chargement. Si un client clique sur Suivant dans les pages Inscription, Windows utilise la clé publique pour créer Sessionkey.blob dans le %systemroot%\system32\Oobe\Info dossier. Votre service ou votre application du Microsoft Store doit charger les données sur votre serveur à l’aide de SSL. Vous devez ensuite déchiffrer la clé de session pour déchiffrer les données client.

S’il n’y a pas de clé publique dans le %systemroot%\system32\Oobe\Info dossier, les pages d’inscription ne s’affichent pas.

Générer des clés publiques et privées

Effectuez cette séquence d’appels pour générer les clés publiques et privées.

  1. Acquérir un contexte de chiffrement à l’aide de l’API CryptAcquireContext. Remplacez les valeurs suivantes :

    • pszProvider a la valeur MS_ENH_RSA_AES_PROV.
    • dwProvType a la valeur PROV_RSA_AES.
  2. Générez une clé de chiffrement RSA à l’aide de l’API CryptGenKey. Remplacez les valeurs suivantes :

    • Algid a la valeur CALG_RSA_KEYX.
    • dwFlags a la valeur CRYPT_EXPORTABLE.
  3. Sérialisez la partie clé publique de la clé de chiffrement à l’étape 2 à l’aide de l’API CryptExportKey. Fournissez cette valeur :

    • dwBlobType est PUBLICKEYBLOB
  4. Écrivez les octets de clé publique sérialisées de l’étape 3 dans le fichier Pubkey.blob à l’aide des fonctions de gestion de fichiers Windows standard.

  5. Sérialisez la partie clé privée de la clé de chiffrement à l’étape 2 à l’aide de l’API CryptExportKey. Fournissez cette valeur

    • dwBlobType est PRIVATEKEYBLOB
  6. Écrivez les octets de clé privée sérialisées de l’étape 5 dans le fichier Prvkey.blob à l’aide de l’API de fichier Windows standard.

Cet extrait de code montre comment générer les clés :

HRESULT CryptExportKeyHelper(_In_ HCRYPTKEY hKey, _In_opt_ HCRYPTKEY hExpKey, DWORD dwBlobType, _Outptr_result_bytebuffer_(*pcbBlob) BYTE **ppbBlob, _Out_ DWORD *pcbBlob);

HRESULT WriteByteArrayToFile(_In_ PCWSTR pszPath, _In_reads_bytes_(cbData) BYTE const *pbData, DWORD cbData);

// This method generates an OEM public and private key pair and writes it to Pubkey.blob and Prvkey.blob
HRESULT GenerateKeysToFiles()
{
    // Acquire crypt provider. Use provider MS_ENH_RSA_AES_PROV and provider type PROV_RSA_AES to decrypt the blob from OOBE.
    HCRYPTPROV hProv;
    HRESULT hr = CryptAcquireContext(&hProv, L"OEMDecryptContainer", MS_ENH_RSA_AES_PROV,
PROV_RSA_AES, CRYPT_NEWKEYSET) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    if (hr == NTE_EXISTS)
    {
        hr = CryptAcquireContext(&hProv, L"OEMDecryptContainer", MS_ENH_RSA_AES_PROV,
PROV_RSA_AES, 0) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    }

    if (SUCCEEDED(hr))
    {
        // Call CryptGenKey to generate the OEM public and private key pair. OOBE expects the algorithm to be CALG_RSA_KEYX.
        HCRYPTKEY hKey;
        hr = CryptGenKey(hProv, CALG_RSA_KEYX, CRYPT_EXPORTABLE, &hKey) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
        if (SUCCEEDED(hr))
        {
            // Call CryptExportKeyHelper to serialize the public key into bytes.
            BYTE *pbPubBlob;
            DWORD cbPubBlob;
            hr = CryptExportKeyHelper(hKey, NULL, PUBLICKEYBLOB, &pbPubBlob, &cbPubBlob);
            if (SUCCEEDED(hr))
            {
                // Call CryptExportKey again to serialize the private key into bytes.
                BYTE *pbPrvBlob;
                DWORD cbPrvBlob;
                hr = CryptExportKeyHelper(hKey, NULL, PRIVATEKEYBLOB, &pbPrvBlob, &cbPrvBlob);
                if (SUCCEEDED(hr))
                {
                    // Now write the public key bytes into the file pubkey.blob
                    hr = WriteByteArrayToFile(L"pubkey.blob", pbPubBlob, cbPubBlob);
                    if (SUCCEEDED(hr))
                    {
                        // And write the private key bytes into the file Prvkey.blob
                        hr = WriteByteArrayToFile(L"prvkey.blob", pbPrvBlob, cbPrvBlob);
                    }
                    HeapFree(GetProcessHeap(), 0, pbPrvBlob);
                }
                HeapFree(GetProcessHeap(), 0, pbPubBlob);
            }
            CryptDestroyKey(hKey);
        }
        CryptReleaseContext(hProv, 0);
    }
    return hr;
}

HRESULT CryptExportKeyHelper(_In_ HCRYPTKEY hKey, _In_opt_ HCRYPTKEY hExpKey, DWORD dwBlobType, _Outptr_result_bytebuffer_(*pcbBlob) BYTE **ppbBlob, _Out_ DWORD *pcbBlob)
{
    *ppbBlob = nullptr;
    *pcbBlob = 0;

    // Call CryptExportKey the first time to determine the size of the serialized key.
    DWORD cbBlob = 0;
    HRESULT hr = CryptExportKey(hKey, hExpKey, dwBlobType, 0, nullptr, &cbBlob) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    if (SUCCEEDED(hr))
    {
        // Allocate a buffer to hold the serialized key.
        BYTE *pbBlob = reinterpret_cast<BYTE *>(CoTaskMemAlloc(cbBlob));
        hr = (pbBlob != nullptr) ? S_OK : E_OUTOFMEMORY;
        if (SUCCEEDED(hr))
        {
            // Now export the key to the buffer.
            hr = CryptExportKey(hKey, hExpKey, dwBlobType, 0, pbBlob, &cbBlob) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
            if (SUCCEEDED(hr))
            {
                *ppbBlob = pbBlob;
                *pcbBlob = cbBlob;
                pbBlob = nullptr;
            }
            CoTaskMemFree(pbBlob);
        }
    }
    return hr;
}

HRESULT WriteByteArrayToFile(_In_ PCWSTR pszPath, _In_reads_bytes_(cbData) BYTE const *pbData, DWORD cbData)
{
    bool fDeleteFile = false;
    HANDLE hFile = CreateFile(pszPath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
    HRESULT hr = (hFile == INVALID_HANDLE_VALUE) ? HRESULT_FROM_WIN32(GetLastError()) : S_OK;
    if (SUCCEEDED(hr))
    {
        DWORD cbWritten;
        hr = WriteFile(hFile, pbData, cbData, &cbWritten, nullptr) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
        fDeleteFile = FAILED(hr);
        CloseHandle(hFile);
    }

    if (fDeleteFile)
    {
        DeleteFile(pszPath);
    }
    return hr;
}

Collecter des données client chiffrées

Créez et préinstallez une application du Microsoft Store ou écrivez un service à exécuter après la première connexion pour :

  1. Collectez les données client chiffrées, y compris le nom d’utilisateur de l’espace de noms Windows.System.User, ainsi que l’horodatage local de la première connexion.
  2. Chargez ce jeu de données sur votre serveur à des fins de déchiffrement et d’utilisation.

Pour utiliser une application du Microsoft Store afin de collecter les données, affectez son ID de modèle utilisateur d’application (AUMID) à Microsoft-Windows-Shell-Setup | OOBE | Paramètre OEMAppId Unattend. Windows transmet les données d’horodatage, de données utilisateur, de clé de session et d’état de case à cocher au dossier de données de l’application OEM, associé au premier utilisateur à se connecter à l’appareil. Par exemple, %localappdata%\packages\[OEM app package family name]\LocalState pour cet utilisateur.

Si vous créez et exécutez un service pour charger les données, vous devez définir le service pour qu’il s’exécute au moins 30 minutes après que l’utilisateur accède à l’écran d’accueil et n’exécute le service qu’une seule fois. La définition de l’exécution de votre service à ce stade garantit que votre service ne consommera pas de ressources système en arrière-plan pendant que les utilisateurs ont la première chance d’explorer l’écran d’accueil et leurs applications. Le service doit collecter les données à partir de l’annuaire OOBE, ainsi que l’horodatage et le nom d’utilisateur, le cas échéant. Le service doit également déterminer les actions à entreprendre en réponse aux choix de l’utilisateur. Par exemple, si l’utilisateur a choisi de participer à un essai d’application anti-programme malveillant, votre service doit commencer la version d’évaluation au lieu de compter sur l’application anti-programme malveillant pour décider si elle doit s’exécuter. Ou, par exemple, si votre utilisateur a choisi d’envoyer des e-mails de votre entreprise ou de vos sociétés partenaires, votre service doit communiquer ces informations à quiconque gère vos e-mails marketing.

Pour plus d’informations sur l’écriture d’un service, consultez Développement d’applications de service Windows.

Envoyer des données à votre serveur pour le déchiffrement

Votre service ou votre application du Microsoft Store doit charger les données sur votre serveur à l’aide de SSL. Vous devez ensuite déchiffrer la clé de session pour déchiffrer les données client.

Déchiffrer les données

Effectuez cette séquence d’appels pour déchiffrer les données :

  1. Acquérir un contexte de chiffrement à l’aide de l’API CryptAcquireContext. Remplacez les valeurs suivantes :

    • pszProvider a la valeur MS_ENH_RSA_AES_PROV.
    • dwProvType a la valeur PROV_RSA_AES.
  2. Lisez le fichier de clé privée OEM (Prvkey.blob) à partir du disque à l’aide de l’API de fichier Windows standard.

  3. Convertissez les octets de clé privée en clé de chiffrement à l’aide de l’API CryptImportKey.

  4. Lisez le fichier de clé de session généré par OOBE (Sessionkey.blob) à partir du disque à l’aide de l’API de fichier Windows standard.

  5. Utilisez la clé privée de l’étape 3 pour convertir les octets de clé de session en clé de chiffrement, à l’aide de l’API CryptImportKey.

  6. La clé d’exportation (hPubKey) est la clé privée importée à l’étape 3.

  7. Lisez les données utilisateur chiffrées écrites en OOBE (Userdata.blob) à partir du disque à l’aide de l’API de fichier Windows standard.

  8. Utilisez la clé de session (de l’étape 5) pour déchiffrer les données utilisateur à l’aide de CryptDecrypt.

Cet extrait de code montre comment déchiffrer les données :

HRESULT DecryptHelper(_In_reads_bytes_(cbData) BYTE *pbData, DWORD cbData, _In_ HCRYPTKEY hPrvKey, _Outptr_result_bytebuffer_(*pcbPlain) BYTE **ppbPlain, _Out_ DWORD *pcbPlain);
HRESULT ReadFileToByteArray(_In_ PCWSTR pszPath, _Outptr_result_bytebuffer_(*pcbData) BYTE **ppbData, _Out_ DWORD *pcbData);

// This method uses the specified Userdata.blob (pszDataFilePath), Sessionkey.blob (pszSessionKeyPath), and Prvkey.blob (pszPrivateKeyPath)
// and writes the plaintext XML user data to Plaindata.xml
HRESULT UseSymmetricKeyFromFileToDecrypt(_In_ PCWSTR pszDataFilePath, _In_ PCWSTR pszSessionKeyPath, _In_ PCWSTR pszPrivateKeyPath)
{
    // Acquire crypt provider. Use provider MS_ENH_RSA_AES_PROV and provider type PROV_RSA_AES to decrypt the blob from OOBE.
    HCRYPTPROV hProv;
    HRESULT hr = CryptAcquireContext(&hProv, L"OEMDecryptContainer", MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_NEWKEYSET) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    if (hr == NTE_EXISTS)
    {
        hr = CryptAcquireContext (&hProv, L"OEMDecryptContainer", MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 0) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    }

    if (SUCCEEDED(hr))
    {
        // Read in the OEM private key file.
        BYTE *pbPrvBlob;
        DWORD cbPrvBlob;
        hr = ReadFileToByteArray(pszPrivateKeyPath, &pbPrvBlob, &cbPrvBlob);
        if (SUCCEEDED(hr))
        {
            // Convert the private key file bytes into an HCRYPTKEY.
            HCRYPTKEY hKey;
            hr = CryptImportKey(hProv, pbPrvBlob, cbPrvBlob, 0, 0, &hKey) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
            if (SUCCEEDED(hr))
            {
                // Read in the encrypted session key generated by OOBE.
                BYTE *pbSymBlob;
                DWORD cbSymBlob;
                hr = ReadFileToByteArray(pszSessionKeyPath, &pbSymBlob, &cbSymBlob);
                if (SUCCEEDED(hr))
                {
                    // Convert the encrypted session key file bytes into an HCRYPTKEY.
                    // This uses the OEM private key to decrypt the session key file bytes.
                    HCRYPTKEY hSymKey;
                    hr = CryptImportKey(hProv, pbSymBlob, cbSymBlob, hKey, 0, &hSymKey) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
                    if (SUCCEEDED(hr))
                    {
                        // Read in the encrypted user data written by OOBE.
                        BYTE *pbCipher;
                        DWORD dwCipher;
                        hr = ReadFileToByteArray(pszDataFilePath, &pbCipher, &dwCipher);
                        if (SUCCEEDED(hr))
                        {
                            // Use the session key to decrypt the encrypted user data.
                            BYTE *pbPlain;
                            DWORD dwPlain;
                            hr = DecryptHelper(pbCipher, dwCipher, hSymKey, &pbPlain, &dwPlain);
                            if (SUCCEEDED(hr))
                            {
                                hr = WriteByteArrayToFile(L"plaindata.xml", pbPlain, dwPlain);
                                HeapFree(GetProcessHeap(), 0, pbPlain);
                            }
                            HeapFree(GetProcessHeap(), 0, pbCipher);
                        }
                        CryptDestroyKey(hSymKey);
                    }
                    HeapFree(GetProcessHeap(), 0, pbSymBlob);
                }
                else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
                {
                    wcout << L"Couldn't find session key file [" << pszSessionKeyPath << L"]" << endl;
                }
                CryptDestroyKey(hKey);
            }
            HeapFree(GetProcessHeap(), 0, pbPrvBlob);
        }
        else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
        {
            wcout << L"Couldn't find private key file [" << pszPrivateKeyPath << L"]" << endl;
        }
        CryptReleaseContext(hProv, 0);
    }
    return hr;
}

HRESULT DecryptHelper(_In_reads_bytes_(cbData) BYTE *pbData, DWORD cbData, _In_ HCRYPTKEY hPrvKey, _Outptr_result_bytebuffer_(*pcbPlain) BYTE **ppbPlain, _Out_ DWORD *pcbPlain)
{
        BYTE *pbCipher = reinterpret_cast<BYTE *>(HeapAlloc(GetProcessHeap(), 0, cbData));
    HRESULT hr = (pbCipher != nullptr) ? S_OK : E_OUTOFMEMORY;
    if (SUCCEEDED(hr))
    {
        // CryptDecrypt will write the actual length of the plaintext to cbPlain.
        // Any block padding that was added during CryptEncrypt won't be counted in cbPlain.
        DWORD cbPlain = cbData;
        memcpy(pbCipher, pbData, cbData);
        hr = ResultFromWin32Bool(CryptDecrypt(hPrvKey,
                                              0,
                                              TRUE,
                                              0,
                                              pbCipher,
                                              &cbPlain));
        if (SUCCEEDED(hr))
        {
            *ppbPlain = pbCipher;
            *pcbPlain = cbPlain;
            pbCipher = nullptr;
        }
        HeapFree(GetProcessHeap(), 0, pbCipher);
    }    return hr;
}

HRESULT ReadFileToByteArray(_In_ PCWSTR pszPath, _Outptr_result_bytebuffer_(*pcbData) BYTE **ppbData, _Out_ DWORD *pcbData)
{
    *ppbData = nullptr;
    *pcbData = 0;
    HANDLE hFile = CreateFile(pszPath, GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
    HRESULT hr = (hFile == INVALID_HANDLE_VALUE) ? HRESULT_FROM_WIN32(GetLastError()) : S_OK;
    if (SUCCEEDED(hr))
    {
        DWORD cbSize = GetFileSize(hFile, nullptr);
        hr = (cbSize != INVALID_FILE_SIZE) ? S_OK : ResultFromKnownLastError();
        if (SUCCEEDED(hr))
        {
            BYTE *pbData = reinterpret_cast<BYTE *>(CoTaskMemAlloc(cbSize));
            hr = (pbData != nullptr) ? S_OK : E_OUTOFMEMORY;
            if (SUCCEEDED(hr))
            {
                DWORD cbRead;
                hr = ReadFile(hFile, pbData, cbSize, &cbRead, nullptr) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
                if (SUCCEEDED(hr))
                {
                    *ppbData = pbData;
                    *pcbData = cbSize;
                    pbData = nullptr;
                }
                CoTaskMemFree(pbData);
            }
        }
        CloseHandle(hFile);
    }
    return hr;
}