USP Per-Device Unique Credentials — Software Design Document

1. Overview

1.1. Purpose

This document describes the design and implementation of per-device unique BASIC-auth credentials (DEV-1799) for USP (TR-369) devices managed by the Friendly Technologies ACS.

It is the USP counterpart of TR-069 Per-Device Unique Credentials (DEV-1194). Both features store their credentials in the same cpe_credentials table and share the value generator and the profile-exclusion contract, but the steps between generation and enforcement differ: the two protocols authenticate at different moments and over different channels.

This document covers USP devices reached over MQTT or STOMP with BASIC authentication. USP devices using other MTPs (CoAP, WebSocket, UDS) or other security modes (PSK, certificates) have no BASIC credentials to make unique and are unaffected.

1.2. Problem Statement

A USP agent authenticates to the ACS with the login/password of its security configuration (iot_sec_conf_details table), presented on the MQTT CONNECT (or STOMP CONNECT) frame. That login/password is shared by every device of the configuration, so any device holding it can connect under another device’s endpoint name.

1.3. Solution Summary

When unique-credential generation is enabled on the security configuration a device authenticates against, the ACS:

  1. generates a unique username/password pair for that device and stores it, DES-encrypted, in the cpe_credentials table;

  2. writes it onto the device’s own MQTT client (Device.MQTT.Client.{i}.Username / .Password) or STOMP connection (Device.STOMP.Connection.{i}.Username / .Password) with a SetParameterValues task;

  3. marks the pair confirmed once the delivery is known; the pair is accepted at authentication whether or not it is confirmed;

  4. protects the pair from being overwritten by profile parameters;

  5. adopts a username, a password or both written onto that same object by anything other than its own push — a provision an operator made by hand, for example — as the new unique pair, so the stored copy never drifts from what the device holds;

  6. lets a device that was factory reset back in on the shared credentials provisionally, and disconnects it unless it then proves the reset — see Factory Reset Quarantine.

The feature is opt-in per security configuration (iot_sec_conf_details.generate_unique_credentials column).

1.4. How USP Differs from TR-069

TR-069/TR-181 USP

Credential pairs per device

Two — ManagementServer (device → ACS) and Connection Request (ACS → device)

One — the MQTT/STOMP BASIC pair, used device → ACS. There is no ACS-initiated dial-back, so no CR equivalent.

Where the credentials live on the device

Device.ManagementServer.Username / .Password

Device.MQTT.Client.{i}.Username / .Password or Device.STOMP.Connection.{i}.Username / .Password — the object the agent carries USP over, which has to be resolved first.

Opt-in flag

cpe_login.generate_management_server_credentials / …_connection_request_credentials

iot_sec_conf_details.generate_unique_credentials

When the ACS decides which credentials to accept

At HTTP request time, with the Inform body available — the event list (0 BOOTSTRAP) is known before the decision.

At MQTT/STOMP CONNECT, which carries nothing but the credentials. Anything the device has to say arrives later, on the already-accepted connection.

Factory reset handling

The 0 BOOTSTRAP event in the same request switches the ACS to the shared credentials for that request.

The connection is accepted provisionally and the device owes a Boot!(Reset) or an OnBoardRequest — see Factory Reset Quarantine.

Provisioning trigger

Every Inform (AOP on the Inform handler).

Every registration (UspCpeRegistrationService), which is once per connection rather than once per session cycle.

Confirmation

SetParameterValuesResponse intercepted by AOP.

Completed SetParameterValuesProvision task, plus a second route: a successful authentication with the pair latches the flag.

2. Architecture

2.1. Component Overview

usp-cpe-credentials-components

2.2. Key Classes

usp-cpe-credentials-classes

3. Data Model

3.1. Database Schema

USP credentials live in the same cpe_credentials table as TR-069/TR-181 ones — one row per device, whatever the protocol. The diagram below is shared by both design documents.

usp-cpe-credentials-schema

3.2. USP Columns of cpe_credentials

A USP credentials row is created on the first registration that finds unique-credential generation enabled. It is identified by serial + group_id when provisioning, and by endpoint_name on every authentication and factory-reset path.

Column Nullable Description

username / password

Yes

The device’s unique MQTT/STOMP BASIC credentials, DES-encrypted. Both set or both NULL. The same columns hold ManagementServer credentials for TR-069/TR-181 rows.

confirmed

No

Whether the ACS knows the device received the pair. Raised by the completed SetParameterValues task or by the device successfully authenticating with the pair; lowered only by a proved factory reset. It is a latch, not an admission gate — see Confirmation.

endpoint_name

Yes

The USP endpoint name (MTP client endpoint) the credentials were provisioned for, unique across the table. USP rows only; NULL for TR-069/TR-181. This is the key every authentication and factory-reset path uses, because it survives the device being deleted from the ACS (which nulls cpe_id).

original_sec_conf_details_id

Yes

The iot_sec_conf_details.id of the security configuration the credentials were generated from. USP rows only; NULL for TR-069/TR-181, which record their own origin in original_login. The referenced row cannot be deleted while this one exists — see Deleting a Security Configuration.

cpe_id

Yes

The device’s id, NULL after the device is deleted from the ACS. Relinked by the next registration.

serial, group_id, isp_id, cpe_deleted_at

—

As for TR-069/TR-181 — see the TR-069 table description.

connection_request_username, connection_request_password, cr_confirmed

—

TR-069/TR-181 only. USP never writes them: there is no ACS-initiated connection request in USP.

4. Configuration

4.1. Feature Toggle per Security Configuration

Column Default Description

iot_sec_conf_details.generate_unique_credentials

false

When true, every device authenticating against this security configuration gets its own MQTT/STOMP BASIC pair generated and pushed.

The flag is exposed to the outside by ACSWebService.saveOrUpdateSecurityConfiguration() and ACSWebService.getSecurityConfigurations(), as the generateUniqueCredentials field of SecurityConfigurationDetailsWs.

It is read at two different moments, off two different configuration rows:

  • when credentials are generated and pushed — off the configuration the device authenticated against on this connection;

  • when they are retired after a proved factory reset — off the configuration recorded in original_sec_conf_details_id.

4.2. Credential Format and Length

Values are produced by the shared UniqueCredentialValueGenerator, exactly as for TR-069:

  • format {OUI}-{random}-{serial}, where OUI comes from the device’s manufacturer record;

  • random part length between DMConfigurationParameter.credentialMinLength and credentialMaxLength (defaults 7 and 180);

  • stored DES-encrypted.

The cipher is DES/ECB/ISO10126Padding, whose padding bytes are random, so encrypting the same secret twice yields different ciphertexts. Stored credential values must therefore never be compared or matched in SQL — compare decrypted plaintext, and address rows by id or endpoint_name.

4.3. Factory Reset Proof Timeout

Parameter Default Description

usp.credentials.reset_proof_timeout

60000 ms

How long a device that authenticated with the shared credentials while holding a confirmed unique pair is given to prove a factory reset before it is disconnected. Available on Settings → Protocols → USP → Configuration page. See Factory Reset Quarantine.

Setting it to 0 or less would leave the wait for a proof without any expiry at all, so such a value is refused: the default 60 seconds is used instead, and a single error line reports it.

ERROR [c.f.u.c.s.c.FactoryResetQuarantineService] USP factory reset proof timeout is configured as 0, which would leave the waits for a proof without expiry - using 60000 ms instead: property=usp.credentials.reset_proof_timeout

4.4. Retention Policy

Retention of credential rows after a device is deleted is shared with TR-069 and unchanged — see Retention Policy.

5. Business Logic

5.1. Credential Provisioning Flow

Provisioning — provisionCredentials in the diagrams — is the last step of registration (UspCpeRegistrationService), reached on both registration routes: with an OnBoardRequest and without one. A failure here does not fail the registration: a device whose credentials could not be provisioned is still a registered device, and the unique credentials stay unconfirmed so the next registration re-sends them.

usp-cpe-credentials-provisioning

Anything that leaves the unique credentials unconfirmed ends in the same retry: a push that never reached the device, a response that never came back, or a pair unconfirmed by a proved factory reset.

5.2. Resolving the Device’s Own MQTT/STOMP Object

A device may have several MQTT clients or STOMP connections that have nothing to do with USP — the ACS itself creates an MQTT one when a BulkData profile points at a broker. Writing to the wrong object puts the secret where the device does not read it, while the ACS can still record the pair as delivered. The device is then refused on every connection — dropped in the factory-reset quarantine, or matching neither cpe_credentials nor the shared configuration if a foreign value was taken for its own.

UspCredentialParameterPathResolver therefore takes the object from what the agent states itself — Device.LocalAgent.MTP.{i}.MQTT.Reference or Device.LocalAgent.MTP.{i}.STOMP.Reference, a strong path reference to the MQTT client / STOMP connection the agent carries USP over.

Resolution ladder, strongest first:

  1. Live read of Device.LocalAgent.MTP. from the device. Used before every push, because a cached reference can predate a factory reset that renumbered the instances, a stale answer writes the credentials to an object the device does not use, and it can then no longer connect. The response also refreshes the parameter cache.

  2. Cached reference, from cpe_parameter. Used first on the read-only route (recognizing whether a completed task wrote to our object), where a round trip on every completed task is not worth it and the cache has just been refreshed by the push anyway.

  3. Lowest instance under the transport prefix — the pre-DEV-1799 behaviour, kept so devices that do not model the reference keep working. Logged at warn — Device states no MTP reference … falling back to the lowest instance — so such devices can be found in the logs.

  4. Instance 0 — when neither the cache nor a live GET finds any instance under the transport prefix at all. Logged at warn: No instance found under Device.MQTT.Client. on device cpeId=… (cache miss + GET miss); defaulting to 0. Instance 0 normally does not exist on a device, so a push addressed there faults and the credentials stay unconfirmed for the next registration to retry.

When more than one MTP of the same protocol carries a reference, an enabled one is preferred, then the lowest MTP instance; the ambiguity is logged at warn: Device has 2 MTPs referencing a Device.MQTT.Client. object and nothing tells which one this connection arrived over, preferring an enabled one. A reference that is empty, or that does not name a plain instance path (an alias-addressed one, for instance), is treated as absent and the ladder continues.

The synchronous counterpart, findAgentTransportObjectPrefixes(cpeId), works from the cache alone and returns every MQTT client and STOMP connection the agent references — with no connection to ask, which MTP the device uses is not known. Used where a device round trip is impossible — see Profile Overwrite Protection.

5.3. Credential Delivery

The pair is written with an ordinary SetParameterValues provisioning task carrying both parameters, in plaintext (the device expects the value, not the ciphertext). Nothing is sent when the object cannot be resolved: the unique credentials stay unconfirmed and the next registration retries.

5.4. Confirmation

cpe_credentials.confirmed records that the ACS knows the device holds the pair. It is a latch, not an admission gate: authentication accepts the pair whether or not the flag is up, because presenting the pair proves the device holds it, while a task response only reports that it was sent.

Two routes raise it.

The completed task (confirmCredentialsSent, called from UspPostHandlerTaskProcessor for every completed SetParameterValuesProvision task):

  • the task is recognized by parameter name, so it is not necessarily our own push: the ACS’s own BulkData feature writes Username/Password on its MQTT client too. Only what was written to the device’s own object counts, resolved as above;

  • the value that reached the device becomes the stored value. Confirming without adopting would leave cpe_credentials holding a secret the device no longer has, which locks the device out with no way to recover;

  • the flag is raised only by a task that carried both halves — a half task is always someone else’s, and the stored credentials would otherwise be left in a state the device never had.

A successful authentication (confirmAfterSuccessfulAuthentication): the device connected using its unique pair while the ACS still had it marked unconfirmed. Connecting with the pair proves the device has it, so the flag is raised.

The flag is raised only if it is still down, so a factory reset lowering it at that same moment is not overwritten. The event is logged at error: the ACS recovers by itself, but a confirmation that went missing is a fault on the delivery path. Look for USP device authenticated with unique credentials that were still unconfirmed … confirming them now.

Leaving the flag down would not lock the device out — authentication accepts its pair either way. It would cost the device the protection the flag provides: the factory-reset quarantine only holds devices whose pair is confirmed, so until the ACS knows the device has its pair, a client connecting with the shared credentials under that endpoint name is treated as a device still waiting for it, and the next registration pushes the pair to it. Every device is in that state until its first confirmation, and this route ends it on the device’s next connection rather than waiting for another push.

5.5. Authentication Flow

usp-cpe-credentials-auth

The unique-pair lookup runs first and is keyed by the endpoint the device connects as. It deliberately does not filter on cpe_credentials.confirmed. A unique-pair mismatch is not a rejection on its own: the shared credentials are what a factory reset leaves the device with. Matching them proves nothing by itself, so the connection is accepted provisionally and the proof is owed afterwards — see Factory Reset Quarantine.

If the stored pair cannot be decrypted, the device is disconnected outright: that is a corrupted stored pair on the ACS side, not a device that came back with its factory credentials.

5.6. Factory Reset Quarantine

A device that authenticates with the shared credentials while its unique credentials are still marked confirmed = 1 may have been factory reset, and is indistinguishable from any other client of the same configuration connecting under that endpoint name. It is held until it proves the reset.

5.6.1. The wait begins

Nothing is written to the database when the wait starts: the unique credentials stay confirmed, and that is what stops anything from sending them while the proof is owed. The wait itself is only a note in the cache — one per device, keyed by its endpoint name, saying that this device owes a proof. It is removed when the proof arrives or when the device is dropped, and it expires by itself if neither happens, so there is never anything to clean up by hand. The note is shared across the cluster because the connection and the proof may reach different nodes, while the countdown belongs to the node that accepted the connection.

usp-cpe-credentials-quarantine-wait

5.6.2. The device proves the reset

The proof is a Boot! notification that reports a factory reset — one whose Cause is FactoryReset, LocalFactoryReset or RemoteFactoryReset, which the ACS records as the event Boot!(Reset) — or an OnBoardRequest. These are the two things only a device starting from factory defaults sends. A Boot! reporting a plain reboot (LocalReboot, RemoteReboot) is not a proof: it is recorded as Boot!(Reboot) and counts as one of the events the device is dropped on.

An OnBoardRequest is a proof only when its serial_number is equal to cpe_credentials.serial of the unique credentials (DEV-3204). A factory reset does not change the serial number of a device. A different serial number means that a different device connected with the same endpoint name, for example a second device configured with this endpoint name by mistake. In that case:

  • confirmed stays 1, and the unique credentials are not sent.

  • The wait continues. If no valid proof arrives, the connection is dropped when the wait times out.

  • The registration started by the OnBoardRequest fails with Endpoint already exists, because the endpoint name is already registered for another serial number.

  • The log has the line USP device sent an OnBoardRequest with a serial number other than the one its endpoint’s unique credentials belong to, not accepted as a factory reset proof.

Who hands the pair back depends on whether registration has already run on this connection.

usp-cpe-credentials-quarantine-proof

An OnBoardRequest normally takes the second branch: it is settled before registration starts, on purpose.

5.6.3. The proof arrives first

A Boot! is handled the moment it arrives, whereas the connection is authenticated only when registration starts — REG_PAUSE (5 s by default) after the connection, or at once if the device opens with an OnBoardRequest. A device that reports its reset inside that window therefore proves it before anything is waiting for the proof. Such a boot is remembered for usp.credentials.reset_proof_timeout (60 s by default), and the authentication that follows takes it instead of starting a wait.

usp-cpe-credentials-quarantine-early

Only a Boot!(Reset) is kept this way, and an OnBoardRequest needs no such memory: onboarding authenticates the device itself before it settles the proof, so the wait it settles has just been started a step earlier.

5.6.4. The device fails to prove itself

usp-cpe-credentials-quarantine-drop

The pair is never handed over in either case, and the credentials stay confirmed. Nothing stops the device from connecting again, and if it still cannot prove the reset the same wait starts and ends the same way — see Recovering a Device That Cannot Prove Itself for the way out. The event a quarantined device is dropped on is still recorded in its device history (cpe_log), so the attempt is visible in the UI rather than silent — a drop on the timeout has no event to record and shows only in the log.

5.6.5. Key properties

These hold for the quarantine as a whole, whichever way it ends.

  • The credentials stay confirmed while the proof is pending. This is what the wait relies on: the provisioning at the end of every registration (Credential Provisioning Flow) only pushes credentials that are unconfirmed, so a client that never proves a reset never gets the device’s pair. While the wait is on, only a proof lowers the flag — and no route sends the pair meanwhile either.

  • The same pair is re-sent, never a fresh one. A device that did not actually reset still holds the old pair; rotating it would lock that device out permanently.

  • Never both sides send. A proof that arrives before registration only unconfirms the pair and lets registration push it; a proof that arrives after registration has no registration left on this connection, so it pushes the pair itself. The two are told apart by a "registration done" mark that registration leaves on the wait, and it is set after registration’s own push rather than before, deliberately: the worst case is then a pair left unconfirmed and re-sent on the device’s next connection, never one sent twice.

  • State is shared, timers are local. The waits live in Hazelcast, so a proof and the wait for it need not reach the same node. The deadline that normally ends a wait is local to the node that started it, and the shared entry is given twice the timeout, so a node dying mid-wait leaves an entry that expires by itself.

  • Disconnect is local-or-broadcast, the same pattern registration uses: disconnect here if the agent is connected here, otherwise broadcast the disconnect event.

5.7. Profile Overwrite Protection

A profile is declarative configuration the ACS owns, so it must never silently overwrite a device’s unique credentials. Two layers, mirroring TR-069:

Prevention. Credential parameters a profile carries for the object the agent uses as its USP transport are never sent to the device: they are dropped while the profile’s task for that device is being built, going by the cached Device.LocalAgent.MTP references. The profile itself is left as the operator configured it — only this device’s copy of the task loses them. A profile that configures the credentials of another MQTT client or STOMP connection — a bulk-data one, for example — is legitimate configuration and is still sent, and so is a profile for a device that has no unique credentials and is not due any. When the references are not known, the Username and Password of every MQTT client and STOMP connection the profile touches are dropped instead — nothing outside those objects is affected, and a device with no unique credentials is not filtered at all. Dropping too much only leaves a profile parameter unapplied, while letting one through can lock the device out. That is the one case reported at warn — ordinary exclusions are debug:

WARN [c.f.u.c.s.c.UspCpeCredentialsService] Cpe 42: excluding credential profile parameter Device.MQTT.Client.2.Username (profile 7) without knowing whether it belongs to the object the device carries USP over - its Device.LocalAgent.MTP references are not known.

Recovery. If a profile’s credential parameter reaches the device anyway (due to some bug in the code or another reason), the completed task is inspected: the ACS resolves the device’s own object off the device, as a push does, and only if the profile wrote there does it lower confirmed and immediately re-send the correct value. Waiting for the next registration instead would leave the device holding a value the ACS does not have. If the object cannot be resolved just then, the pair is only marked unconfirmed and the next registration re-sends it — reported at warn as Cannot tell whether a profile task overwrote the unique credentials of cpe …. Recovery runs only for a pair already recorded as delivered; an unconfirmed one is on the ordinary re-send path anyway.

A device that still owes a factory reset proof is left alone. Nothing is sent to it, and confirmed is not lowered either. Whoever is on the other end got in on the shared credentials and has proved nothing, and keeping the pair away from such a client is exactly what the wait is for. The flag stays up because the stored pair is still the one the genuine device holds. Waiting costs nothing: a device that proves the reset has the pair re-sent then, and one that never proves it is disconnected. Logged at debug: Profile task carried the credential parameters of a USP device that still owes a factory reset proof, leaving its unique credentials untouched.

5.8. Retirement When Generation Is Disabled

Generation is a per-configuration flag, iot_sec_conf_details.generate_unique_credentials. Turning it off stops new credentials: provisioning returns before it touches cpe_credentials, so no row is created for a device that has none, and an existing row is left exactly as it stands — not deleted, confirmed not lowered, nothing pushed, whatever that row’s confirmed says. Such a device also keeps the prevention layer of the profile protection: a row that still carries a username/password counts as unique-managed whatever the flag says, and that check never looks at confirmed either.

A device already running on its unique pair has nothing to fall back on — the ACS never sends the shared credentials to a device — so removing its credentials would reject it on its next CONNECT. Credentials are therefore retired only on a proved factory reset, the point at which the device’s state is known: the proof deletes them instead of unconfirming them when generation is disabled on the original_sec_conf_details_id configuration.

In practice:

The device comes back… …and its unique credentials

with its unique pair

stays. The device has just proved it holds the pair; deleting it would lock the device out.

with the shared credentials, and proves a reset

is deleted (generation disabled) or unconfirmed and re-sent (generation still enabled).

with the shared credentials and proves nothing

stays untouched; the connection is dropped when the proof times out.

There is no supported way to migrate a device that holds a unique pair back onto the shared credentials — the ACS never pushes shared credentials to a device. Disabling the flag means "stop generating", not "revert what was generated".

5.9. Deleting a Security Configuration

A security configuration’s details row cannot be deleted while any cpe_credentials row still points at it through original_sec_conf_details_id. The attempt is refused and nothing is deleted — whether the details row was deleted on its own, together with the whole security configuration, or simply left out of a save, which deletes it just the same.

Every referencing row counts, whatever its cpe_id, cpe_deleted_at or confirmed hold: a device deleted from the ACS keeps its pair and relinks on its next registration, and an unconfirmed pair may well be in the device’s hands with only the delivery confirmation lost.

The reason is the one behind Retirement When Generation Is Disabled: the details row is what the ACS reads to place such a device in its domain, to tell whether generation is still on for it, and to decide whether a proved reset re-sends its pair or retires it. With the row gone those lookups come back empty, which is indistinguishable from generate_unique_credentials having been turned off, and the shared credentials the device would fall back to are gone with the row too.

The refusal is reported to the caller and logged at error by com.friendly.security:

Refused to delete security configuration details unique per-device credentials were generated from: securityDetailsIds=…

Credential rows are removed by the paths that know the device’s state — a proved factory reset while generation is off, or the retention policy once the device has been deleted long enough — so the configuration becomes deletable once those have run.

5.10. Device Deletion and Re-registration

Deleting a device from the ACS touches two columns only: cpe_id is set to NULL and cpe_deleted_at is stamped. The pair itself, its confirmed flag and its endpoint_name stay exactly as they were, so a device that comes back without a factory reset still authenticates with its own credentials. This is why every authentication and factory-reset path is keyed on endpoint_name and never on cpe_id.

The next registration relinks cpe_id, isp_id and original_sec_conf_details_id; endpoint_name is never rewritten on credentials that already exist.

6. Support Guide

6.1. How to Enable Unique Credentials

  1. Set generate_unique_credentials = true on the security configuration (iot_sec_conf_details) the devices authenticate against — on the Settings → Protocols → USP → Configuration page, or through ACSWebService.saveOrUpdateSecurityConfiguration, which carries the flag as SecurityConfigurationDetailsWs.generateUniqueCredentials.

  2. The next registration of each device generates its pair and pushes it.

  3. The completed task marks the pair confirmed, and the device reconnects to the broker with it.

Nothing has to be done per device, and devices that are offline pick it up on their next connection.

6.2. Log Lines to Look For

All lines below come from com.friendly.usp.core… and name the device by endpointName= and/or cpeId=. Most of them are debug; the level is given below only where it is warn or error.

The debug ones are off by default, because conf/logback-spring.xml sets com.friendly to INFO. One entry alongside the other <logger> elements turns on all of them:

<logger name="com.friendly.usp.core.security" level="DEBUG"/>

That covers SecurityValidationVisitor and everything under …security.credentials (UspCpeCredentialsService, FactoryResetQuarantineService, UspCredentialParameterPathResolver) — name those four classes individually instead if a narrower switch is wanted. The one line from registration is an error and needs no change.

Line What it means

Generated unique USP credentials: cpeId=…

The pair has just been created — the device does not have it yet.

Pushing unique USP credentials: cpeId=… object=Device.MQTT.Client.2.

The task carrying the pair is being created; the object was resolved as shown.

[USP] Failed to provision unique credentials, the next registration will re-send them (error)

Generating or pushing the pair threw. The device stays registered and its pair stays unconfirmed, so its next connection retries.

Confirmed unique USP credentials: cpeId=…

The task completed and carried both halves.

USP device authenticated with unique credentials that were still unconfirmed… (error)

The confirmation of the push was lost; the ACS recovered from it via the authentication latch.

USP device presented BASIC credentials matching neither its own unique pair nor the shared configuration… (error)

The device is now unreachable — see Recovering a Device That Cannot Prove Itself.

USP device authenticated with default credentials while holding confirmed unique ones, awaiting Boot!(Reset)

The quarantine has started.

Disconnecting USP device that owed a Boot!(Reset) and sent another event instead (error)

The device was dropped; the event it sent is recorded in its device history.

Disconnecting USP device that owed a Boot!(Reset) and sent no event at all

The proof never arrived and the wait timed out. This drop leaves no device-history entry, so this line is the only trace of it.

Unique credentials generation disabled for endpointName=…, deleting the credentials it just proved it no longer holds

Retirement — the device proved a reset while generation was off.

Profile task carried the credential parameters of a USP device that still owes a factory reset proof…

A profile got past the prevention layer while the device was in quarantine; its credentials were deliberately left untouched.

USP device reported a factory reset nobody is waiting for yet…

An early proof was stored for one timeout period — the only trace of what the early-proof cache holds.

USP factory reset proof timeout is configured as … using 60000 ms instead (error)

usp.credentials.reset_proof_timeout is 0 or negative; fix the property. One line per misconfiguration, per node.

Device states no MTP reference … falling back to the lowest instance (warn)

The agent does not model Device.LocalAgent.MTP.{i}.MQTT.Reference (or its STOMP counterpart); credentials may be written to an object that is not the one carrying USP.

Failed to read Device.LocalAgent.MTP. off the device, falling back to what the ACS already knows about its transport objects (warn)

The live read before a push went unanswered; the cached reference is used instead, or the lowest instance if the cache is empty too.

No instance found under Device.MQTT.Client. … defaulting to 0 (warn)

Neither the cache nor a GET shows any instance; the push is addressed to instance 0 and will normally fault.

6.3. Recovering a Device That Cannot Prove Itself

A device can lose its unique pair without a factory reset — a restored configuration, a firmware downgrade, an MQTT client or STOMP connection re-created without it. It then has no proof to offer and is dropped on every connection, since only a proof lowers confirmed.

No code change is needed, just run the following SQL query against the database:

UPDATE cpe_credentials SET confirmed = 0 WHERE endpoint_name = ?;

Nothing caches that row, so it takes effect on the device’s next connection: the wait no longer starts, and registration re-sends the pair to a device now known not to hold it. A wait that has already started still expires on its own timeout first, so one more disconnect after the update is expected.

Use endpoint_name, never cpe_id — the latter is NULL once the device has been deleted from the ACS, which is one of the states this recovery is for.

6.4. Known Limitations

  • The factory-reset proof is not authenticated — the device asserts it, exactly as TR-069’s 0 BOOTSTRAP is asserted. The quarantine keeps the pair from a client that never claims a reset; it does not stop one that claims it deliberately.

  • Disabling generation does not revert existing devices — see Retirement When Generation Is Disabled.

  • A security configuration cannot be deleted while devices hold credentials generated from it — see Deleting a Security Configuration.

  • Devices that do not model their MTP reference fall back to the lowest MQTT client / STOMP connection instance, which is only correct when the device has one — and to instance 0, which normally does not exist, when no instance can be found at all.

  • A quarantine that ends in a timeout leaves no device-history entry, unlike one ended by a wrong event.

7. Database Migration Reference

Migration file: changelog-7.1.2.DEV-1799.xml — see the change-set list in the TR-069 document, where the shared table is described.

9. Document History

This section lists what was changed in this document, when and by whom, with links to the affected sections. The Ticket column is the JIRA issue the document change was committed under, so the matching code change and test scope can be looked up from it.

Date Author Ticket What changed in the document

2026-08-28

Eduard Fastovskyi

DEV-1799

Initial version of the document — the USP (TR-369) per-device unique credentials feature described in full: provisioning, delivery, confirmation, authentication, the factory-reset quarantine, profile overwrite protection, retirement, and the support guide.