Condividi tramite


Proteggere e raccogliere dati utente

Se un cliente immette informazioni nelle pagine di registrazione OEM, i file seguenti vengono creati al termine dell'OOBE:

  • Userdata.BLOB. File XML crittografato che contiene tutti i valori in tutti gli elementi configurabili dall'utente nelle pagine di registrazione, inclusi i campi delle informazioni dei clienti e gli stati della casella di controllo.
  • SessionKey.BLOB. Generato durante la crittografia di Userdata.BLOB. Contiene una chiave di sessione necessaria per il processo di decrittografia.
  • Userchoices.xml. File XML non crittografato che contiene le etichette e i valori della casella di controllo per tutte le caselle di controllo incluse nelle pagine di registrazione.

Nota

Se un cliente fa clic Skip sulla prima pagina di registrazione, nessun dato viene scritto o archiviato in questi file, non anche gli stati predefiniti della casella di controllo.

Il timestamp dell'esperienza predefinita dell'utente viene aggiunto anche al Registro di sistema di Windows in questa chiave:

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

Questo valore del Registro di sistema viene creato indipendentemente dal fatto che le pagine di registrazione siano incluse in OOBE. Il timestamp viene scritto in formato UTC (Coordinated Universal Time); in particolare, è un SYSTEMTIME valore scritto come BLOB serializzato di dati nel Registro di sistema.

Per accedere e usare le informazioni sui clienti, seguire questa procedura:

  1. Generare una coppia di chiavi pubblica/privata e posizionare la chiave pubblica nella %systemroot%\system32\Oobe\Info cartella dell'immagine.
  2. Raccogliere i dati del cliente crittografati usando un'app o un servizio che viene eseguito circa 30 minuti dopo il completamento del primo accesso.
  3. Inviare i dati al server per la decrittografia usando SSL. È quindi possibile decrittografare la chiave di sessione per decrittografare i dati del cliente.

Generare una coppia di chiavi pubblica/privata

Per proteggere i dati dei clienti, è necessario generare una coppia di chiavi pubblica/privata e la chiave pubblica deve essere inserita nella %systemroot%\system32\Oobe\Info cartella. Se si distribuiscono immagini in più aree o in più lingue, è necessario inserire la chiave pubblica direttamente in sottodirectory specifiche della lingua e dell'area, seguendo le stesse regole desiderate per i file Oobe.xml specifici dell'area o della lingua, come descritto in How Oobe.xml funziona.

Importante

Non è mai necessario inserire la chiave privata nel PC del cliente. Deve invece essere archiviato in modo sicuro nei server in modo che i dati possano essere decrittografati dopo il caricamento. Se un cliente fa clic su Avanti nelle pagine Registrazione, Windows usa la chiave pubblica per creare Sessionkey.BLOB nella %systemroot%\system32\Oobe\Info cartella. Il servizio o l'app di Microsoft Store devono caricare i dati nel server usando SSL. È quindi necessario decrittografare la chiave di sessione per decrittografare i dati del cliente.

Se nella cartella non è presente alcuna chiave %systemroot%\system32\Oobe\Info pubblica, le pagine di registrazione non vengono visualizzate.

Generare chiavi pubbliche e private

Eseguire questa sequenza di chiamate per generare le chiavi pubbliche e private.

  1. Acquisire il contesto di crittografia usando l'API CryptAcquireContext. Specificare questi valori:

    • pszProvider è MS_ENH_RSA_AES_PROV
    • dwProvType è PROV_RSA_AES
  2. Generare la chiave di crittografia RSA usando l'API CryptGenKey. Specificare questi valori:

    • Algid è CALG_RSA_KEYX
    • dwFlags è CRYPT_EXPORTABLE
  3. Serializzare la parte pubblica della chiave pubblica della chiave di crittografia dal passaggio 2 usando l'API CryptExportKey. Specificare questo valore:

    • dwBlobType è PUBLICKEYBLOB
  4. Scrivere i byte di chiave pubblica serializzati dal passaggio 3 al file Pubkey.BLOB usando le funzioni standard di Gestione file di Windows.

  5. Serializzare la parte privata della chiave privata della chiave di crittografia dal passaggio 2 usando l'API CryptExportKey. Specificare questo valore

    • dwBlobType è PRIVATEKEYBLOB
  6. Scrivere i byte di chiave privata serializzati dal passaggio 5 al file Prvkey.BLOB usando l'API file di Windows standard.

Questo frammento di codice illustra come generare le chiavi:

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;
}

Raccogliere i dati dei clienti crittografati

Creare e preinstallare un'app di Microsoft Store o scrivere un servizio da eseguire dopo la prima accesso a:

  1. Raccogliere i dati del cliente crittografati, inclusi il nome utente dello spazio dei nomi Windows.System.User, nonché il timestamp locale del primo accesso.
  2. Caricare i dati impostati nel server per la decrittografia e l'uso.

Per usare un'app di Microsoft Store per raccogliere i dati, assegnare il relativo ID modello utente applicazione (AUMID) al programma di installazione di Microsoft-Windows-Shell | OOBE | Impostazione OEMAppId Unattend. Windows passerà il timestamp, i dati utente, la chiave di sessione e i dati dello stato della casella di controllo alla cartella dati dell'applicazione per l'app OEM, associata al primo utente a cui accedere al dispositivo. Ad esempio, %localappdata%\packages\[OEM app package family name]\LocalState per tale utente.

Se si crea ed esegue un servizio per caricare i dati, è necessario impostare il servizio per eseguire almeno 30 minuti dopo che l'utente viene visualizzato nella schermata Start ed eseguire il servizio una sola volta. L'impostazione del servizio da eseguire in questo momento garantisce che il servizio non utilizzerà le risorse di sistema in background mentre gli utenti ottengono la prima possibilità di esplorare la schermata Start e le proprie app. Il servizio deve raccogliere i dati dalla directory OOBE, nonché il timestamp e il nome utente, come applicabile. Il servizio deve anche determinare quali azioni eseguire in risposta alle scelte dell'utente. Ad esempio, se l'utente ha consenso esplicito su una versione di valutazione dell'app antimalware, il servizio deve avviare la versione di valutazione anziché basarsi sull'app antimalware per decidere se deve essere eseguita. In alternativa, come un altro esempio, se l'utente ha scelto di inviare messaggi di posta elettronica dalla società o dalle aziende partner, il servizio deve comunicare le informazioni a chi gestisce i messaggi di posta elettronica di marketing.

Per altre informazioni su come scrivere un servizio, vedere Sviluppo di applicazioni di servizio Windows.

Inviare dati al server per la decrittografia

Il servizio o l'app di Microsoft Store devono caricare i dati nel server usando SSL. È quindi necessario decrittografare la chiave di sessione per decrittografare i dati del cliente.

Decrittografare i dati

Eseguire questa sequenza di chiamate per decrittografare i dati:

  1. Acquisire il contesto di crittografia usando l'API CryptAcquireContext. Specificare questi valori:

    • pszProvider è MS_ENH_RSA_AES_PROV
    • dwProvType è PROV_RSA_AES
  2. Leggere il file di chiave privata OEM (Prvkey.BLOB) dal disco usando l'API file di Windows standard.

  3. Convertire i byte di chiave privata in una chiave di crittografia usando l'API CryptImportKey.

  4. Leggere il file di chiave di sessione generato da OOBE (Sessionkey.BLOB) dal disco usando l'API file di Windows standard.

  5. Usare la chiave privata dal passaggio 3 per convertire i byte della chiave di sessione in una chiave di crittografia usando l'API CryptImportKey.

  6. La chiave di esportazione (hPubKey) è la chiave privata importata nel passaggio 3.

  7. Leggere i dati utente crittografati scritti da OOBE (Userdata.BLOB) dal disco usando l'API file di Windows standard.

  8. Usare la chiave di sessione (dal passaggio 5) per decrittografare i dati utente usando CryptDecrypt.

Questo frammento di codice illustra come decrittografare i dati:

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;
}