Proteger e coletar dados do usuário
Se um cliente inserir informações nas páginas de registro do OEM, os seguintes arquivos serão criados quando concluirem o OOBE:
- Userdata.blob. Um arquivo XML criptografado que contém todos os valores em todos os elementos configuráveis pelo usuário nas páginas de registro, incluindo campos de informações do cliente e estados de caixa de seleção.
- SessionKey.blob. Gerado durante a criptografia de Userdata.blob. Contém uma chave de sessão necessária para o processo de descriptografia.
- Userchoices.xml. Um arquivo XML não criptografado que contém os rótulos e valores da caixa de seleção para todas as caixas de seleção incluídas nas páginas de registro.
Observação
Se um cliente clicar Skip
na primeira página de registro, nenhum dado será gravado ou armazenado nesses arquivos, nem mesmo os estados padrão da caixa de seleção.
O carimbo de data/hora da experiência pronta para uso do usuário também é adicionado ao Registro do Windows sob esta chave:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE\Stats [EndTimeStamp]
Esse valor do Registro é criado independentemente de as páginas de registro serem incluídas no OOBE. O carimbo de data/hora é escrito no formato UTC (Tempo Universal Coordenado) ; especificamente, é um SYSTEMTIME
valor gravado como um blob serializado de dados no Registro.
Para acessar e usar as informações do cliente, execute as seguintes etapas:
-
Gere um par de chaves pública/privada e coloque a chave pública na
%systemroot%\system32\Oobe\Info
pasta da imagem. - Colete os dados do cliente criptografados usando um aplicativo ou um serviço que é executado cerca de 30 minutos após a conclusão do primeiro logon.
- Envie os dados para o servidor para descriptografia usando SSL. Em seguida, você pode descriptografar a chave de sessão para descriptografar os dados do cliente.
Gerar um par de chaves pública/privada
Para proteger os dados do cliente, você deve gerar um par de chaves pública/privada e a chave pública deve ser colocada na %systemroot%\system32\Oobe\Info
pasta. Se você estiver implantando imagens em várias regiões ou em vários idiomas, deverá colocar a chave pública diretamente em subdiretórios específicos da região e do idioma, seguindo as mesmas regras que faria para arquivos de Oobe.xml específicos da região ou do idioma, conforme descrito em Como Oobe.xml funciona.
Importante
Você nunca deve colocar a chave privada no computador do cliente. Em vez disso, ele deve ser armazenado com segurança em seus servidores para que os dados possam ser descriptografados depois de carregados. Se um cliente clicar em Avançar nas páginas Registro, o Windows usará a chave pública para criar Sessionkey.blob na %systemroot%\system32\Oobe\Info
pasta . Seu serviço ou aplicativo da Microsoft Store deve carregar os dados no servidor usando SSL. Em seguida, você precisa descriptografar a chave de sessão para descriptografar os dados do cliente.
Se não houver nenhuma chave pública na %systemroot%\system32\Oobe\Info
pasta, as páginas de registro não serão mostradas.
Gerar chaves públicas e privadas
Faça essa sequência de chamadas para gerar as chaves públicas e privadas.
Adquira o contexto de cripta usando a API CryptAcquireContext. Forneça esses valores:
-
pszProvider
éMS_ENH_RSA_AES_PROV
-
dwProvType
éPROV_RSA_AES
-
Gere a chave de cripta RSA usando a API CryptGenKey. Forneça esses valores:
-
Algid
éCALG_RSA_KEYX
-
dwFlags
éCRYPT_EXPORTABLE
-
Serialize a parte de chave pública da chave cripta da Etapa 2 usando a API CryptExportKey. Forneça este valor:
-
dwBlobType
éPUBLICKEYBLOB
-
Escreva os bytes de chave pública serializados da Etapa 3 para o arquivo Pubkey.blob usando as funções padrão de Gerenciamento de Arquivos do Windows.
Serialize a parte de chave privada da chave cripta da Etapa 2 usando a API CryptExportKey. Forneça esse valor
-
dwBlobType
éPRIVATEKEYBLOB
-
Escreva os bytes de chave privada serializados da etapa 5 para o arquivo Prvkey.blob usando a API de Arquivo do Windows padrão.
Este snippet de código mostra como gerar as chaves:
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;
}
Coletar dados criptografados do cliente
Crie e pré-instale um aplicativo da Microsoft Store ou escreva um serviço para ser executado após a primeira entrada em:
- Colete os dados criptografados do cliente, incluindo o nome de usuário do namespace Windows.System.User, bem como o carimbo de data/hora local da primeira entrada.
- Carregue esse conjunto de dados no servidor para descriptografia e uso.
Para usar um aplicativo da Microsoft Store para coletar os dados, atribua sua AUMID (ID do Modelo de Usuário do Aplicativo) à Instalação do Microsoft-Windows-Shell | OOBE | Configuração não atenuada de OEMAppId . O Windows passará o carimbo de data/hora, os dados do usuário, a chave de sessão e os dados de estado da caixa de seleção para a pasta de dados do aplicativo para o aplicativo OEM, que está associada ao primeiro usuário a fazer logon no dispositivo. Por exemplo, %localappdata%\packages\[OEM app package family name]\LocalState
para esse usuário.
Se você criar e executar um serviço para carregar os dados, deverá definir o serviço para ser executado pelo menos 30 minutos depois que o usuário chegar à tela Inicial e executar o serviço apenas uma vez. Definir seu serviço para ser executado neste momento garante que seu serviço não consumirá recursos do sistema em segundo plano enquanto os usuários tiverem a primeira chance de explorar a tela Inicial e seus aplicativos. O serviço deve coletar os dados de dentro do diretório OOBE, bem como o carimbo de data/hora e o nome de usuário, conforme aplicável. O serviço também deve determinar quais ações tomar em resposta às escolhas do usuário. Por exemplo, se o usuário optou por uma avaliação de aplicativo antimalware, seu serviço deve iniciar a avaliação em vez de contar com o aplicativo antimalware para decidir se ele deve ser executado. Ou, como outro exemplo, se o usuário optou por emails de sua empresa ou empresas parceiras, seu serviço deverá comunicar essas informações a quem lidar com seus emails de marketing.
Para obter mais informações sobre como escrever um serviço, consulte Desenvolvendo aplicativos de serviço windows.
Enviar dados para o servidor para descriptografia
Seu serviço ou aplicativo da Microsoft Store deve carregar os dados no servidor usando SSL. Em seguida, você precisa descriptografar a chave de sessão para descriptografar os dados do cliente.
Descriptografar os dados
Faça essa sequência de chamadas para descriptografar os dados:
Adquira o contexto de cripta usando a API CryptAcquireContext. Forneça esses valores:
-
pszProvider
éMS_ENH_RSA_AES_PROV
-
dwProvType
éPROV_RSA_AES
-
Leia o arquivo de chave privada OEM (Prvkey.blob) do disco usando a API de Arquivo do Windows padrão.
Converta os bytes de chave privada em uma chave cripta usando a API CryptImportKey.
Leia o arquivo de chave de sessão gerado por OOBE (Sessionkey.blob) do disco usando a API de Arquivo do Windows padrão.
Use a chave privada da Etapa 3 para converter os bytes da chave de sessão em uma chave cripta usando a API CryptImportKey.
A chave de exportação (hPubKey) é a chave privada importada na Etapa 3.
Leia dados de usuário criptografados escritos por OOBE (Userdata.blob) do disco usando a API de Arquivo do Windows padrão.
Use a chave de sessão (da Etapa 5) para descriptografar os dados do usuário usando CryptDecrypt.
Este snippet de código mostra como descriptografar os dados:
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;
}