セキュリティ的に重要なデータを管理するためには、データの暗号化が重要な意味を持ちます。 これにより、そのデータは鍵を知るユーザー以外に複合化できなくなります。 しかし、このように暗号化されたデータがどのユーザーからでもアクセスできる場所に保存されていれば、 複合化はできないものの、データの中身を変更することができますし、 データそのものが削除されてしまうことも考えられます。 こうしたことから、重要なデータは暗号化されると共に、安全な場所に保存されるべきといえます。 LSAシークレットとは、システムとAdministratorsグループのみがアクセス可能な場所であり、 データが暗号化されて保存されることから、いくつかの重要なデータが格納されています。 LSAシークレットは、次のレジストリキーで表されます。
HKEY_LOCAL_MACHINE\SECURITY\Policy\SecretsSECURITYキーは既定でAdministratorsにアクセスを許可していないため、 レジストリエディタなどで上記キーを参照する場合は、 SECURITYキーのセキュリティ記述子を変更することになります。 上記キーには主に下記のようなサブキーが存在し、 これらがLSAシークレットに格納されるデータになります。
| データを識別するキー | 意味 |
|---|---|
| L$xxx | ローカルコンピュータ上から取得可能なデータ。 リモートから取得しようとした場合は、アクセスが拒否される。 |
| G$xxx | ドメインの全てのドメインコントローラ上で存在するデータ。 |
| M$ | システムのみアクセスできるデータ。 |
| NL$xxx | キャッシュドメインログオンで使用するユーザーアカウントやパスワード。 |
| $MACHINE.ACC | コンピュータアカウントのパスワード。 |
| DefaultPassword | 自動ログオン時に使用されるデフォルトのパスワード。 |
| _SC_xxx | サービスが特定のユーザーとしてログオンする場合の、そのユーザーの認証情報。システムのみ取得可。 |
| RasDialParamsxxx | ダイヤルアップ接続時に使用するパスワード。 |
xxxには任意の値が入りますが、キーによってはこの値が想像できる場合もあります。 たとえば、_SC_xxxのxxxにはサービスの名前が入り、 RasDialParamsxxxにはユーザーのSIDが入ります。 LSAシークレットに格納されたデータは、 Administratorsのメンバのユーザーとして動作していれば、 どのプロセスからでも取得することができるため、この点は注意してください。
LSAシークレットにデータを格納するには、LsaStorePrivateDataを呼び出します。
NTSTATUS LsaStorePrivateData( LSA_HANDLE PolicyHandle, PLSA_UNICODE_STRING KeyName, PLSA_UNICODE_STRING PrivateData );
PolicyHandleは、ポリシーオブジェクトのハンドルを指定します。 このハンドルには、POLICY_CREATE_SECRETアクセス権が割り当てられている必要があります。 KeyNameは、格納するデータを識別するためのキーを指定します。 PrivateDataは、LSAシークレットに格納したいデータを指定します。 NULLを指定した場合は、KeyNameで識別されるデータが削除されます。
LSAシークレットに格納されたデータを取得するには、LsaRetrievePrivateDataを呼び出します。
NTSTATUS LsaRetrievePrivateData( LSA_HANDLE PolicyHandle, PLSA_UNICODE_STRING KeyName, PLSA_UNICODE_STRING *PrivateData );
PolicyHandleは、ポリシーオブジェクトのハンドルを指定します。 このハンドルには、POLICY_GET_PRIVATE_INFORMATIONアクセス権が割り当てられている必要があります。 KeyNameは、取得したいデータを表すキーを指定します。 PrivateDataは、取得したデータを受け取る変数のアドレスを指定します。
今回のプログラムは、独自のデータをLSAシークレットに格納します。
#include <windows.h>
#include <ntsecapi.h>
BOOL StorePrivateData(LSA_HANDLE hPolicy, LPWSTR lpszKeyName, LPWSTR lpszPrivateData);
BOOL RetrievePrivateData(LSA_HANDLE hPolicy, LPWSTR lpszKeyName, LPWSTR lpszPrivateData);
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
WCHAR szKeyName[] = L"L$MySecret";
WCHAR szPrivateData[] = L"sample-data";
WCHAR szRetriveData[1024];
NTSTATUS ns;
LSA_HANDLE hPolicy;
LSA_OBJECT_ATTRIBUTES objectAttributes;
BOOL bStorePrivateData = TRUE;
ZeroMemory(&objectAttributes, sizeof(LSA_OBJECT_ATTRIBUTES));
objectAttributes.Length = sizeof(LSA_OBJECT_ATTRIBUTES);
ns = LsaOpenPolicy(NULL, &objectAttributes, POLICY_CREATE_SECRET | POLICY_GET_PRIVATE_INFORMATION, &hPolicy);
if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS) {
MessageBox(NULL, TEXT("ポリシーオブジェクトのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
return 0;
}
if (bStorePrivateData) {
if (StorePrivateData(hPolicy, szKeyName, szPrivateData))
MessageBox(NULL, TEXT("LSAシークレットにデータを格納しました。"), TEXT("OK"), MB_OK);
else
MessageBox(NULL, TEXT("データの格納に失敗しました。"), NULL, MB_ICONWARNING);
}
else {
if (RetrievePrivateData(hPolicy, szKeyName, szRetriveData))
MessageBox(NULL, szRetriveData, TEXT("OK"), MB_OK);
else
MessageBox(NULL, TEXT("データの取得に失敗しました。"), NULL, MB_ICONWARNING);
}
LsaClose(hPolicy);
return 0;
}
BOOL StorePrivateData(LSA_HANDLE hPolicy, LPWSTR lpszKeyName, LPWSTR lpszPrivateData)
{
NTSTATUS ns;
LSA_UNICODE_STRING lsaName;
LSA_UNICODE_STRING lsaData;
lsaName.Length = (USHORT)(lstrlenW(lpszKeyName) * sizeof(WCHAR));
lsaName.MaximumLength = lsaName.Length + sizeof(WCHAR);
lsaName.Buffer = lpszKeyName;
if (lpszPrivateData != NULL) {
lsaData.Length = (USHORT)(lstrlenW(lpszPrivateData) * sizeof(WCHAR));
lsaData.MaximumLength = lsaData.Length + sizeof(WCHAR);
lsaData.Buffer = lpszPrivateData;
ns = LsaStorePrivateData(hPolicy, &lsaName, &lsaData);
}
else
ns = LsaStorePrivateData(hPolicy, &lsaName, NULL);
return LsaNtStatusToWinError(ns) == ERROR_SUCCESS;
}
BOOL RetrievePrivateData(LSA_HANDLE hPolicy, LPWSTR lpszKeyName, LPWSTR lpszPrivateData)
{
NTSTATUS ns;
LSA_UNICODE_STRING lsaName;
PLSA_UNICODE_STRING plsaData;
lsaName.Length = (USHORT)(lstrlenW(lpszKeyName) * sizeof(WCHAR));
lsaName.MaximumLength = lsaName.Length + sizeof(WCHAR);
lsaName.Buffer = lpszKeyName;
ns = LsaRetrievePrivateData(hPolicy, &lsaName, &plsaData);
if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS)
return FALSE;
lstrcpynW(lpszPrivateData, plsaData->Buffer, (plsaData->Length / sizeof(WCHAR)) + 1);
LsaFreeMemory(plsaData);
return TRUE;
}
szNameがデータを識別する名前を表し、szDataが格納されるデータを表しています。 よって、今回のプログラムを実行すると、 L$MySecretというキーがSecretsキーの下に作成され、そこにszDataのデータが暗号化されて格納されます。 キーの先頭にL$が付加されているため、データはローカルコンピュータからのみアクセスすることができます。
実際のところ、現在のWindowsではLSAシークレットの利用は推奨されていません。 重要なデータはCryptProtectDataなどのDPAPIで暗号化し、 アプリケーション自身でデータを適切に管理することが望まれています。 この1つの原因は、LSAシークレットに格納できるデータの数が4096個と少なく、 さらにこのうちの2048個がシステムによって予約されているためであると思われます。