Skip to content

Added base framework to create NDES Policy module#10

Merged
Crypt32 merged 18 commits into
masterfrom
NDES
May 1, 2025
Merged

Added base framework to create NDES Policy module#10
Crypt32 merged 18 commits into
masterfrom
NDES

Conversation

@Crypt32
Copy link
Copy Markdown
Contributor

@Crypt32 Crypt32 commented Apr 23, 2025

This pull request adds base implementation for custom NDES policy module implementation using .NET.

Overview

AD CS NDES service provides default NDES behavior customization such as:

  • custom challenge password generation
  • custom SCEP request validation logic
  • custom SCEP transaction auditing/logging logic

The following article describes NDES components, their relationships and NDES transaction flow: Use a policy module with Network Device Enrollment Service

SDK details

NDES Policy module can be implemented by deriving from NdesPolicyBase abstract class. All types here are defined in ADCS.CertMod.Managed.NDES namespace.

This class implements INDESPolicy COM interface and expose virtual/abstract methods to implementers where you can define your policy's business logic. The following methods can be overridden to implement your business logic:

  • void NdesPolicyBase.OnInitialize() - optional. Invoked by server engine on policy module initialization.
  • void NdesPolicyBase.OnUninitialize() - optional. Invoked by server engine on policy module uninitialization.
  • String OnGenerateChallenge(String, String) - optional. Invoked by server engine when new challenge password is requested by invoking https://server/certsrv/mscep_admin?operation=NDESGenerateChallenge&keyUsage=$KeyUsage&params= URL. More about default implementation below.
  • Boolean OnVerifyRequest(Byte[]?, Byte[]?, String, String) - mandatory. Invoked by server engine when NDES receives request from SCEP client. If it is a renewal request, signing certificate can be passed to this method by server engine. This is where you should perform challenge password validation if you opted to use them.
  • void OnNotify(String, String, SCEPDisposition, Int32, X509Certificate2? issuedCertificate) - optional. Invoked by server engine when SCEP transaction is completed. Transaction completion doesn't mean that a certificate is issued. Certificate may not be issued, may be placed in pending state, etc. Transaction result is stored in disposition parameter. If challenge passwords are in use, this method MUST be used to remove challenge password from storage.

NdesPolicyBase requires an object that implements ISCEPChallengeStore interface. This interface defines a SCEP challenge password storage. This interface exposes the following members:

  • String GetNextChallenge(String, String?) - this is where challenge password is generated and placed into implementation-specific internal storage (runtime dictionary, memory cache, persistent database, etc.). This method SHALL be invoked in NdesPolicyBase.OnGenerateChallenge method implementation. Default NdesPolicyBase.OnGenerateChallenge implementation performs this call automatically.
  • void ReleaseChallenge(String) - this method is used to remove the challenge password from internal storage when transaction is completed. This method SHALL be invoked in NdesPolicyBase.OnNotify method implementation. Default NdesPolicyBase.OnNotify implementation in performs this call automatically.
  • Boolean TryGetChallenge(String, out SCEPChallengeStoreEntry?) - attempts to retrieve specified challenge password from internal storage. This method SHALL be used in NdesPolicyBase.OnVerifyRequest to verify if challenge password supplied by SCEP client was generated by calling GetNextChallenge method to avoid replay attacks, for example.

You can use DefaultSCEPChallengeStore class which already implements ISCEPChallengeStore interface and represents a thread-safe in-memory storage. This storage does not survive SCEP application pool recycle/restart. Challenge password generation is delegated to ISCEPChallengeGenerator which requires to implement only one method:

  • String GenerateChallenge()

You can use DefaultSCEPChallengeGenerator class which already implements ISCEPChallengeGenerator. Default challenge password generator function generates a cryptographically random challenge of specified size.

If default implementations of challenge password generation function and storage satisfy your needs, then your NDES policy module constructor can look like this:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("MyNdesModule.ProgID")] // replace with your ProgID
[Guid("15f3bd50-20ea-41d6-abbe-9818d44ea1a6")] // replace with your GUID
public class MyNdesPolicyModule : NdesPolicyBase {
    public MyNdesPolicyModule() : base(
        new LogWriter("MyModule"),
        new DefaultSCEPChallengeStore(new DefaultSCEPChallengeGenerator())) {
        // my other implementation-specific code if needed
    }
// <...> the rest of implementation is omitted for brevity
}

If default implementations of challenge password generation function and storage do not satisfy your needs, you will have to provide an implementation of these interfaces and pass them to base class constructor.

Register custom policy module with NDES service

The following article provides information on registry changes required to register your policy module with NDES: Install a policy module with the Network Device Enrollment Service

Note

You MUST register your assembly as COM object in Windows Registry first.

Caution

Only one NDES policy module can be loaded by NDES service. This means that if you already use custom policy module (for example, Intune NDES policy module), then you will loose Intune implementation when registering another custom policy module.

Caution

NDES challenge password retrieval web portal will stop working after installing custom NDES policy module. This is by design, not a bug. Challenge passwords must be retrieved by explicitly calling password retrieval URL. It looks like this:

https://server/certsrv/mscep_admin?operation=NDESGenerateChallenge&keyUsage=$KeyUsage&params=

where $KeyUsage must be a hex value for desired template:

  • EncryptionTemplate - 0x20
  • SignatureTemplate - 0x80
  • GeneralTemplate - any other value

params URL parameter MUST be presented and be empty string if not used.

Additional references:

@Crypt32 Crypt32 linked an issue Apr 23, 2025 that may be closed by this pull request
@Crypt32 Crypt32 merged commit aafe8b5 into master May 1, 2025
2 checks passed
@Crypt32 Crypt32 deleted the NDES branch May 1, 2025 10:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add base implementation for NDES policy module

1 participant