Microsoft Software KSP: exact CNG signing ACE masks and protected DACL enforcement

Goce Terziev 0 Reputation points
2026-10-30T16:41:51.5+00:00

We need provider-specific technical clarification for the Microsoft Software Key Storage Provider (Software KSP) regarding access control on persisted CNG signing keys.

Scenario

We intend to provision two separate LocalMachine ECDSA P-256 signing keys, with non-exportable private key material and explicit protected DACLs.

Each key is assigned to a different Windows virtual service account. Each service must be able to sign using only its assigned key, without being able to sign using the other service's key or modify either key's DACL.

Relevant APIs include:

  • NCryptOpenStorageProvider
  • NCryptCreatePersistedKey
  • NCryptSetProperty
  • NCryptFinalizeKey
  • NCryptOpenKey
  • NCryptSignHash
  • NCryptGetProperty (NCRYPT_SECURITY_DESCR_PROPERTY)

Questions

  1. Which exact ACE access-mask bits are evaluated by Microsoft Software KSP when authorizing NCryptSignHash? Please identify the constants, numeric values and applicable generic-rights mapping.
  2. Which exact rights govern reading and modifying a persisted key's security descriptor, particularly READ_CONTROL, WRITE_DAC and the GENERIC_* rights?
  3. For a separate existing CurrentUser key, NCryptGetProperty returned a protected DACL containing an explicit Allow ACE with AccessMask 0xD01F01FF. How does Microsoft Software KSP interpret this value? Does it include signing or DACL modification rights?
  4. What is the supported sequence for assigning an explicit protected DACL to a LocalMachine persisted key? Should the descriptor be applied before or after NCryptFinalizeKey, and which security-information flags are required?
  5. Does reading back the security descriptor through NCryptGetProperty prove the permissions actually enforced during NCryptSignHash? If not, what additional verification is supported?
  6. How can we verify that two distinct service SIDs have isolated signing access, including denial of cross-service signing and DACL modification?

We specifically need Microsoft Software KSP behavior, not only generic Windows ACL documentation.

An authoritative Microsoft reference, documented rights mapping or engineering-supported explanation with a reproducible verification procedure would be greatly appreciated.

We are not requesting a workaround or relaxation of access-control requirements.

Windows development | Windows API - Win32
0 comments No comments

2 answers

Sort by: Oldest
  1. AI answer

    2026-10-30T16:44:02.9+00:00

    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, and GENERIC_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_INFORMATION also requires WRITE_DAC and 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_DAC or WRITE_OWNER.
    • Neither service SID should be the key owner. Windows owners have owner-derived authority affecting DACL administration, so omission of WRITE_DAC from an ACE alone is insufficient.
    • Provision under a separate administrative identity and retain an administrative recovery ACE.

    Provisioning lifecycle

    The documented lifecycle is:

    1. Open MS_KEY_STORAGE_PROVIDER.
    2. Call NCryptCreatePersistedKey with NCRYPT_MACHINE_KEY_FLAG; without it, the key is CurrentUser-scoped.^6^
    3. Set generation-time properties, including the intended non-export policy.
    4. Set NCRYPT_SECURITY_DESCR_PROPERTY before finalization using NCryptSetProperty.
    5. Call NCryptFinalizeKey; the key is unusable before finalization.^7^
    6. 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_INFORMATION with SE_DACL_PROTECTED set 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

    1. ACCESS_MASK
    2. Generic access rights
    3. NCryptSignHash function (ncrypt.h)
    4. SECURITY_INFORMATION
    5. SECURITY_DESCRIPTOR_CONTROL
    6. NCryptCreatePersistedKey function (ncrypt.h)
    7. NCryptFinalizeKey function (ncrypt.h)
    8. NCryptSetProperty function (ncrypt.h)
    9. Key Storage Property Identifiers
    10. NCryptGetProperty function (ncrypt.h)
    AI-generated content may be incorrect. Read our transparency notes for more information.

    Was this answer helpful?

  2. Zack Nguyen (WICLOUD CORPORATION) 405 Reputation points Microsoft External Staff Moderator
    2026-11-02T03:21:12.2733333+00:00

    Hi @Goce Terziev ,
    Thank you for the clear write up and questions. After reviewing the available Microsoft documentations, here are my answers:

    1. Which exact ACE bits authorize NCryptSignHash? The Microsoft references reviewed do not specify Software KSP’s signing ACE mask or its GENERIC_MAPPING. Neither NCryptSignHash nor NCryptOpenKey identifies the access bits checked.

    • NCRYPT_ALLOW_SIGNING_FLAG = 0x00000002 is a key-usage flag, not an ACE right. See Key Storage Property Identifiers.
    • Generic rights are GENERIC_READ = 0x80000000, GENERIC_WRITE = 0x40000000, GENERIC_EXECUTE = 0x20000000, and GENERIC_ALL = 0x10000000. Their meaning requires an object-specific mapping; a file mapping is not a documented Software KSP signing contract. See Generic Access Rights.

    2. Which rights govern security-descriptor access? The standard Windows rights are:

    • Read owner/group/DACL: READ_CONTROL = 0x00020000.
    • Modify DACL: WRITE_DAC = 0x00040000.
    • Change owner: WRITE_OWNER = 0x00080000.

    See ACCESSMASK and SECURITYINFORMATION. These definitions do not establish Software KSP’s complete provider-specific authorization checks. NCryptGetProperty and NCryptSetProperty document the security-information selectors, but not a complete rights mapping.

    3. What does 0xD01F01FF mean? It contains:

    • GENERIC_READ | GENERIC_WRITE | GENERIC_ALL.
    • STANDARD_RIGHTS_ALL, including READ_CONTROL, WRITE_DAC, and WRITE_OWNER.
    • Object-specific bits 0x01FF.

    It explicitly contains DACL-modification rights, so it is unsuitable as a least-privilege candidate for your requirement. Its signing meaning cannot be confirmed without the provider-specific mapping. See ACCESS_MASK and Generic Access Rights.

    4. Should the protected DACL be applied before or after finalization?

    The reviewed documentation does not establish the exact protected-DACL sequence requested.

    • The general lifecycle is Create → Set properties → Finalize, with NCRYPT_MACHINE_KEY_FLAG selecting machine scope. See NCryptCreatePersistedKey and NCryptFinalizeKey.
    • NCryptSetProperty documents DACL_SECURITY_INFORMATION, but does not explicitly establish protected-DACL persistence or exclusive before/after-finalization ordering.
    • SE_DACL_PROTECTED prevents inherited ACE changes. It does not prevent authorized DACL modification. The separate PROTECTED_DACL_SECURITY_INFORMATION flag is not listed among NCrypt’s documented selectors. See Security Descriptor Control.

    5. Does descriptor read-back prove signing enforcement?

    No. NCryptGetProperty retrieves the descriptor; its contract does not establish which permissions NCryptSignHash enforces.

    Actual signing tests can provide operational evidence. Security event 5061 additionally documents Software KSP operation details, including identity, key, operation and return code.

    6. How should service isolation be verified?

    The following acceptance cases are suggested for testing on disposable keys, using fresh processes under the actual service identities:

    • Service A: Sign key A successfully; deny signing key B; deny modification of either DACL.
    • Service B: Sign key B successfully; deny signing key A; deny modification of either DACL.

    Log opening and signing separately, generate actual signatures rather than only querying buffer size, and test DACL changes using valid replacement descriptors. This is a proposed validation procedure, not an engineering-confirmed mapping.

    The APIs are documented in NCryptOpenKey, NCryptSignHash, NCryptVerifySignature, and NCryptSetProperty.

    For the missing authoritative mapping and protected-DACL lifecycle contract, I recommend opening a support request with Microsoft through Support for Business. If you do not have a business support plan, you can contact Microsoft Support instead.


    I hope this helps answering your questions. If you find my response helpful, I would greatly appreciate it if you could follow the guide here so others experiencing similar behavior or issue can benefit from it as well.  

    Was this answer helpful?


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.