There is no publicly documented Microsoft Software KSP access-mask constant or GENERIC_MAPPING that identifies a sign-only right for NCryptSignHash; therefore, a documented numeric “sign but cannot modify DACL” ACE cannot be derived from the available Windows SDK contract.
What is documented
ACCESS_MASK is divided into object-specific rights in bits 0–15, standard rights in bits 16–23, and generic rights in bits 28–31. The relevant numeric values are:
| Right | Value |
|---|---|
DELETE |
0x00010000 |
READ_CONTROL |
0x00020000 |
WRITE_DAC |
0x00040000 |
WRITE_OWNER |
0x00080000 |
SYNCHRONIZE |
0x00100000 |
GENERIC_ALL |
0x10000000 |
GENERIC_EXECUTE |
0x20000000 |
GENERIC_WRITE |
0x40000000 |
GENERIC_READ |
0x80000000 |
The low 16 bits have meaning only when the object type publishes its object-specific rights.^1^ Generic rights likewise require an object-type-specific mapping.^2^
Microsoft does not publish that object-specific rights set or generic mapping for persisted Microsoft Software KSP keys. In particular, the NCryptSignHash contract defines signing parameters and errors but does not identify an ACL right checked for signing.^3^
Do not substitute FILE_GENERIC_* or other file rights merely because Software KSP keys are file-backed. Those mappings describe file objects, not the KSP’s key-object authorization contract.
Interpretation of 0xD01F01FF
The value decomposes mechanically as:
0xD01F01FF
= 0x80000000 GENERIC_READ
| 0x40000000 GENERIC_WRITE
| 0x10000000 GENERIC_ALL
| 0x001F0000 STANDARD_RIGHTS_ALL
| 0x000001FF object-specific bits 0 through 8
Consequently, the ACE explicitly includes:
-
READ_CONTROL -
WRITE_DAC -
WRITE_OWNER -
DELETE -
SYNCHRONIZE -
GENERIC_READ,GENERIC_WRITE, andGENERIC_ALL
It therefore grants WRITE_DAC as a standard bit and must not be used for an identity that is prohibited from modifying the DACL. It probably permits signing in the environment where it was observed, but neither the low 0x01FF field nor the generic bits can be authoritatively mapped to NCryptSignHash from public documentation.
Security-descriptor access
For the general Windows security model:
- Querying a DACL requires
READ_CONTROL. - Setting a DACL requires
WRITE_DAC. - Setting an owner requires
WRITE_OWNER. -
PROTECTED_DACL_SECURITY_INFORMATIONalso requiresWRITE_DACand means that the DACL does not inherit ACEs.^4^
SE_DACL_PROTECTED (0x1000) prevents modification through inherited ACE processing; it does not stop an owner or a principal authorized with WRITE_DAC from replacing the DACL.^5^
For the requested isolation guarantee:
- Neither service SID should receive
WRITE_DACorWRITE_OWNER. - Neither service SID should be the key owner. Windows owners have owner-derived authority affecting DACL administration, so omission of
WRITE_DACfrom an ACE alone is insufficient. - Provision under a separate administrative identity and retain an administrative recovery ACE.
Provisioning lifecycle
The documented lifecycle is:
- Open
MS_KEY_STORAGE_PROVIDER. - Call
NCryptCreatePersistedKeywithNCRYPT_MACHINE_KEY_FLAG; without it, the key is CurrentUser-scoped.^6^ - Set generation-time properties, including the intended non-export policy.
- Set
NCRYPT_SECURITY_DESCR_PROPERTYbefore finalization usingNCryptSetProperty. - Call
NCryptFinalizeKey; the key is unusable before finalization.^7^ - Close all handles, reopen the machine key, and read back the descriptor.
For NCRYPT_SECURITY_DESCR_PROPERTY, NCryptSetProperty explicitly documents DACL_SECURITY_INFORMATION as selecting the DACL.^8^ The property applies only to persistent keys.^9^
However, the NCrypt documentation does not explicitly list PROTECTED_DACL_SECURITY_INFORMATION among its accepted selectors or specify whether Software KSP persists an embedded SE_DACL_PROTECTED control bit when only DACL_SECURITY_INFORMATION is supplied. Therefore, this part cannot be represented as a documented provider guarantee.
On every supported Windows build, test both candidate forms during provisioning:
-
DACL_SECURITY_INFORMATIONwithSE_DACL_PROTECTEDset in the descriptor. -
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION.
Reject provisioning if either NCryptSetProperty fails or, after finalize/close/reopen, the returned descriptor does not contain the exact explicit ACE set and SE_DACL_PROTECTED. Reading uses NCRYPT_SECURITY_DESCR_PROPERTY with DACL_SECURITY_INFORMATION; retrieving owner information additionally requires OWNER_SECURITY_INFORMATION.^10^
Enforcement verification
Descriptor read-back proves persistence, not which provider-specific right authorizes signing. Test actual operations under fresh, complete service tokens:
| Process identity | Own key | Other key | Replace own DACL | Replace other DACL |
|---|---|---|---|---|
| Service A SID | Sign succeeds | Fails | Fails | Fails |
| Service B SID | Sign succeeds | Fails | Fails | Fails |
Implement the harness as a temporary Windows service configured with each virtual service account. In each fresh process:
OpenMachineKey(ownName, &ownKey);
Log("own-open", status);
Log("own-sign", SignTestHash(ownKey));
OpenMachineKey(otherName, &otherKey);
Log("other-open", status);
if (status == ERROR_SUCCESS)
Log("other-sign", SignTestHash(otherKey));
Log("own-set-dacl", TryReplaceDacl(ownKey));
Record NCryptOpenKey and NCryptSignHash separately: denial during open and denial during signing are different enforcement points. Also attempt NCryptSetProperty(NCRYPT_SECURITY_DESCR_PROPERTY, …, DACL_SECURITY_INFORMATION) with a harmless replacement descriptor and require failure.
Because no documented sign-only mask exists, derive no production mask solely from these experiments. Use the tests to validate a candidate mask on each supported OS build, and obtain a binding provider-specific mapping through Microsoft Support/Windows cryptography engineering if the design requires a stable documented authorization contract.
References
- ACCESS_MASK
- Generic access rights
- NCryptSignHash function (ncrypt.h)
- SECURITY_INFORMATION
- SECURITY_DESCRIPTOR_CONTROL
- NCryptCreatePersistedKey function (ncrypt.h)
- NCryptFinalizeKey function (ncrypt.h)
- NCryptSetProperty function (ncrypt.h)
- Key Storage Property Identifiers
- NCryptGetProperty function (ncrypt.h)