Secrets and DPAPI
The webhook's secrets live in a single file, connectors.secrets.json, which the application encrypts with DPAPI, the Windows data protection mechanism.
File structure
Start from connectors.secrets.template.json, provided with the project:
{
"WebHookEncryptionKey": "<webhook key, in Base64>",
"WebHookEncryptionIV": "<webhook IV, in Base64>",
"Connectors": {
"zoho-crm": { "Secrets": { "ClientId": "", "ClientSecret": "", "RefreshToken": "" } },
"sharepoint": { "Secrets": { "TenantId": "", "ClientId": "", "ClientSecret": "" } },
"dynamics": { "Secrets": { "TenantId": "", "ClientId": "", "ClientSecret": "" } }
},
"Handlers": {
"my-handler": { "Secrets": { "ApiKey": "" } }
}
}
| Entry | Role |
|---|---|
WebHookEncryptionKey, WebHookEncryptionIV |
The webhook key and IV, copied exactly from the SESAR configuration (WebHookKey and WebHookIv). The HashKey is computed on the Base64 string itself: one extra space is enough to make authentication fail. |
Connectors.{id}.Secrets |
The secrets of a built-in connector: see its page. |
Handlers.{id}.Secrets |
The secrets of a handler, received in its Settings. |
PrivateAESKey |
Key of the files that SESAR encrypts, in the <key>_<IV> format. No connector provided reads it at this location: the SharePoint connector reads its own under Connectors.sharepoint.Secrets. |
Entry names are case-sensitive. Save the file in UTF-8.
Location
The file is read at the location given by ConnectorsSecretsPath, otherwise in the application folder.
- Place it outside the project folder and any Git repository: the project's
.jsonfiles are copied in plain text to the publish output. - Drop it directly on the server. Do not carry it in a deployment artifact.
- Give the application's account the right to read and modify it, and nobody else.
Encryption at startup
- At startup, the application reads the file.
- If it starts with
{, after an optional UTF-8 byte order mark, it considers it plain text: it reads it, encrypts it with DPAPI (ProtectedData.Protect) and writes it back to the same location. - On later startups, it decrypts it (
ProtectedData.Unprotect).
Warning
If encryption or the rewrite fails, for example for lack of write permission, the application continues with the values read and the file stays in plain text, with no blocking error. After the first startup, check that the file no longer starts with {.
If decryption fails (another account, another machine, another entropy), the application starts, but processes no webhook: each request receives a 500 error. The reason is given by Configuration.InitializationError in GET /api/health/status.
An empty file, or one saved in UTF-16, is taken for an encrypted file, and its decryption fails.
Scope and entropy
| Setting | Effect |
|---|---|
DataProtectionScope set to CurrentUser (default) |
Only the Windows account that runs the application can decrypt the file, and only on that machine. On IIS, turn on loading the user profile on the application pool. |
DataProtectionScope set to LocalMachine |
Any process on the machine can decrypt the file. Avoid this scope. |
FileEntropy |
Base64 value added to the encryption. The same value is required to decrypt. An invalid value is ignored. It is stored in plain text in appsettings.json: it is an extra protection, not a secret. |
Note
The first startup encrypts the file for the account that runs the application. A test with dotnet run under your own account therefore makes the file unreadable for the service account. Drop a new plain-text version before starting the service.
Change a secret
No route reloads the secrets, and the encrypted file can only be read by the service account. To change a secret:
- Take all the values from your secret store.
- Replace the encrypted file with a complete, plain-text version.
- Restart the application, then check that the file was encrypted.
Who can read the secrets
Any handler loaded by the application can read all the secrets, including those of the other connectors and the webhook key. Only drop in the handlers folder DLLs whose code you know.