You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hub renewal stores issued certificates in the certificate provider when the provider made the CSR. The DPS operational-cert callback always gets the chain and the store result. The managed provider accepts only chains for its own key and writes files atomically with owner-only permissions.
Connection client
send_csr(): the key owner owns the chain.
csr NULL: the client gets the CSR from the provider's get_csr() (subject = device id). On ISSUED it stores the chain with store_issued_certificate() before the callback; the result is in the new az_iot_csr_event.store_status. az_iot_csr_event gains _internal_size (SDK-stamped, like the state event) so fields can be appended safely. Needs a v2 provider with both hooks, else AZ_IOT_ERR_NOT_SUPPORTED before anything is published.
App csr: the chain is delivered, never stored (store_status = AZ_IOT_ERR_NOT_SUPPORTED).
The live session is kept. Subsequent connects use the stored chain; if the store fails, the previous credential stays in use.
az_iot_operational_cert_callback gains store_result. It fires with the chain even when the store fails; the DPS registration then fails, as before. Breaking: callers must add the parameter. Breaking:az_iot_csr_event gains _internal_size as its first member, which shifts its layout; callers must rebuild.
DPS enrollment (dps.request_operational_certificate) requires a provider with get_csr, release_csr and store_issued_certificate; open() returns AZ_IOT_ERR_NOT_SUPPORTED otherwise. The issued chain is always stored in the provider, never kept by the client. Breaking: providers without store_issued_certificate can no longer be used for DPS enrollment.
The provider contract now says store_issued_certificate() must be all-or-nothing.
The provider vtable has no version field (and no AZ_IOT_CERTIFICATE_PROVIDER_VTABLE_VERSION); optional hooks are gated on NULL only. Breaking: custom providers must drop .version.
Managed provider
A store is refused unless every certificate in the chain parses (count must match) and the leaf certifies the operational key. At init, a stored chain that fails either check is ignored.
The key and chain are written to a uniquely named, exclusively created file next to the destination (0600; owner-only DACL on Windows), flushed, then renamed over the destination. A failed write leaves the old file untouched. A symlink at the destination is replaced, not followed.
The mode of an existing key file is not changed.
Tests: unit tests for each behavior above. The permission and symlink tests are POSIX only. On Windows, a test checks the key and chain DACLs.
Validation (Linux, gcc): unit suites pass, also under ASan/UBSan. Samples and the e2e CSR test compile with Paho. The Windows code path was syntax-checked with MinGW. clang-format 18 and the repo lint scripts pass on changed files. The hub-renewal e2e test has not been run.
- Add opts.use_issued_certificates (default true via options_default()):
on AZ_IOT_CSR_ISSUED the client stores the chain in the certificate
provider before the CSR callback, reports it in evt->store_status, and
keeps the live session. A failed store keeps the previous credential.
- Operational-cert callback (DPS) now always fires with the chain plus
the store result, including when the store fails.
- Managed provider stores atomically (temp file, validate, replace), so a
failed store keeps the previous certificate.
- Provider contract: store_issued_certificate() is all-or-nothing.
- Update samples, e2e test, docs; add unit tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- store_issued_certificate() refuses a chain whose leaf does not certify
the operational key; init ignores a stored chain that does not.
- Key and chain are written to a uniquely named, exclusively created file
next to the destination (0600 / owner-only DACL), flushed, then renamed
over it. A link at the destination is replaced, not followed.
- Tests for each; POSIX-only for permissions and links.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace opts.use_issued_certificates with ownership from the CSR source:
- send_csr(csr = NULL): the client takes the CSR from the provider's
get_csr() (subject = device id) and stores the issued chain with
store_issued_certificate(). Requires a v2 provider with both hooks,
else AZ_IOT_ERR_NOT_SUPPORTED before anything is published.
- send_csr(csr): the application owns the key; the chain is delivered,
never stored (store_status AZ_IOT_ERR_NOT_SUPPORTED).
hub_renew sample uses send_csr(NULL). Tests and docs updated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Managed provider: a stored or persisted chain is used only if every
certificate parses (not just the leaf) and, on store, the parsed count
matches the issued count.
- az_iot_csr_event gains _internal_size, stamped by the SDK, so fields
like store_status can be appended safely.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This new mandatory contract is not met by the repository's complete sample provider: c/samples/common/sample_cert_provider.c:135-160 opens the destination with "wb" before writing, so a write or close failure returns an error after destroying the previous credential. Update that provider to use atomic replacement too; otherwise the public example teaches an implementation that violates this contract and can strand the next connection.
The new owner-only Windows DACL is security-critical behavior, but the added permission tests are excluded under _WIN32; the coverage table also explicitly says this path is untested. Add a Windows unit test that creates both files and inspects their resulting DACLs, verifying access is limited to the owner and SYSTEM rather than relying only on a MinGW syntax check.
Align proposed API sketch with documented event fields
c/docs/eng/certificate-management.md:360
This prose now references evt->store_status, but the immediately preceding “Proposed C surface” still defines az_iot_csr_event without either store_status or _internal_size. Update that API sketch so the design document is internally consistent with the public header.
- sample_cert_provider writes the chain to "<path>.tmp", flushes it, then
replaces the destination, so a failed store keeps the previous chain.
- certificate-management.md: az_iot_csr_event and send_csr() match the
header (_internal_size, store_status, csr NULL).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- open() with dps.request_operational_certificate now requires a
provider with get_csr, release_csr and store_issued_certificate. The
issued chain is always stored in the provider (failure fails the
registration); the callback-only path is removed.
- send_csr() docs list the exact hooks behind AZ_IOT_ERR_NOT_SUPPORTED.
- Sample provider writes through a uniquely named, exclusively created
temp file (0600 on POSIX) instead of a fixed "<path>.tmp".
- Windows unit test checks the key and chain DACLs (protected; Owner
Rights and SYSTEM only).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🟡 Changes recommended
Persisted-chain validation can consume unbounded memory during initialization, and the documented existing-key permission behavior lacks regression coverage.
- Remove az_iot_certificate_provider_vtable::version and
AZ_IOT_CERTIFICATE_PROVIDER_VTABLE_VERSION; the client gates optional
hooks on NULL only. Providers, samples, tests and docs updated; the
v1-provider tests are removed.
- Windows DACL test: rename a local that shadowed system() (MSVC C4459
under /W4 /WX) and initialize the ACL size info.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Managed provider: a persisted chain over 64 KiB is refused at init
rather than buffered.
- Sample provider: an empty issued entry fails the store, like the
managed provider, so the previous chain is kept.
- Tests for both, and for init keeping an existing key file's mode.
- Restore AZ_IOT_HUB_PROFILE_INIT's formatting to main's.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A truncated, unbalanced or trailing-text credentials response (hub) or
ASSIGNED payload (DPS) no longer reaches the provider: the hub renewal
fails with AZ_IOT_ERR_PROTOCOL and the DPS registration fails. The chain
array must also close.
Superseded by smaller PRs: #410 (managed provider storage) and #411 (renewal CSR from the provider, chains always stored). The rest follows in separate PRs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hub renewal stores issued certificates in the certificate provider when the provider made the CSR. The DPS operational-cert callback always gets the chain and the store result. The managed provider accepts only chains for its own key and writes files atomically with owner-only permissions.
Connection client
send_csr(): the key owner owns the chain.csrNULL: the client gets the CSR from the provider'sget_csr()(subject = device id). On ISSUED it stores the chain withstore_issued_certificate()before the callback; the result is in the newaz_iot_csr_event.store_status.az_iot_csr_eventgains_internal_size(SDK-stamped, like the state event) so fields can be appended safely. Needs a v2 provider with both hooks, elseAZ_IOT_ERR_NOT_SUPPORTEDbefore anything is published.csr: the chain is delivered, never stored (store_status=AZ_IOT_ERR_NOT_SUPPORTED).az_iot_operational_cert_callbackgainsstore_result. It fires with the chain even when the store fails; the DPS registration then fails, as before. Breaking: callers must add the parameter. Breaking:az_iot_csr_eventgains_internal_sizeas its first member, which shifts its layout; callers must rebuild.dps.request_operational_certificate) requires a provider withget_csr,release_csrandstore_issued_certificate;open()returnsAZ_IOT_ERR_NOT_SUPPORTEDotherwise. The issued chain is always stored in the provider, never kept by the client. Breaking: providers withoutstore_issued_certificatecan no longer be used for DPS enrollment.store_issued_certificate()must be all-or-nothing.versionfield (and noAZ_IOT_CERTIFICATE_PROVIDER_VTABLE_VERSION); optional hooks are gated on NULL only. Breaking: custom providers must drop.version.Managed provider
Tests: unit tests for each behavior above. The permission and symlink tests are POSIX only. On Windows, a test checks the key and chain DACLs.
Validation (Linux, gcc): unit suites pass, also under ASan/UBSan. Samples and the e2e CSR test compile with Paho. The Windows code path was syntax-checked with MinGW. clang-format 18 and the repo lint scripts pass on changed files. The hub-renewal e2e test has not been run.