X.509
nXPico M carries the same X.509 functions as nXPico W: an elliptic-curve key pair (secp256r1) and a self-signed certificate, generated on the device and stored in a reserved flash region, with the private key never leaving the chip.
What differs is where the certificate goes next. nXPico W hands it to its own TLS stack; nXPico M hands it to the nRF9151’s credential manager, because on this board TLS is terminated by the modem, not by the runtime.
The functions
Section titled “The functions”Identical names, signatures and return codes to nXPico W — see the full reference. The same nXPicoX509.compiled-library declares them here too:
| Function | Type | What it does |
|---|---|---|
x509GenerateDeviceCert | asynchronous | generates the device identity |
x509HasDeviceCert | immediate | is an identity present? |
x509GetGenerateStatus | immediate | generation status |
x509GetDeviceCertLen | immediate | certificate length (typically 583–595 bytes) |
x509DumpDeviceCert | asynchronous | prints the certificate on the serial console — not visible on this board |
x509GetDeviceCert | immediate | copies the certificate into a buffer |
x509GetDeviceCertChunk | immediate | reads the certificate piecewise |
x509GenerateCsr | immediate | generates a signing request (not validated) |
Install the library as on nXPico W — see Library. Text parameters are POINTER TO BYTE, never STRING.
Storing the certificate in the modem
Section titled “Storing the certificate in the modem”The nRF9151 keeps credentials in slots identified by a sec_tag, managed with AT%CMNG:
AT%CMNG=<opcode>,<sec_tag>,<type>,<content>| Field | Value used here |
|---|---|
| opcode | 0 = write, 1 = list, 2 = read, 3 = delete |
| sec_tag | any number you choose — 5432 in the example |
| type | 0 root CA, 1 client certificate, 2 client private key |
The command is assembled through the modem pipe: a 583-byte PEM makes a 602-byte command, ten times the 64-byte channel, so it goes in chunks with SEND = 2 and is closed with SEND = 1.
VAR sCert : STRING(700); nOffset : INT; nChunk : INT := 48; (* stay well under the 64-byte channel *) sPiece : STRING(63); nStep : INT; diRes : DINT;END_VARCASE nStep OF
(* 1. make sure the identity exists *)0: IF NOT x509HasDeviceCert() THEN diRes := x509GenerateDeviceCert(ADR('CN=nxpico-m-01'), 3650, FALSE); nStep := 1; ELSE nStep := 2; END_IF
1: IF x509HasDeviceCert() THEN nStep := 2; END_IF (* ~450 ms *)
(* 2. read it into the application *)2: diRes := x509GetDeviceCert(ADR(sCert), SIZEOF(sCert)); IF diRes = 0 THEN nStep := 3; END_IF
(* 3. opening of the command *)3: GVL.sOut := 'AT%CMNG=0,5432,1,"'; GVL.wSend := 2; (* append only *) nOffset := 0; nStep := 4;
(* 4. the PEM, 48 bytes at a time *)4: GVL.wSend := 0; IF nOffset < LEN(sCert) THEN sPiece := MID(sCert, nChunk, nOffset + 1); GVL.sOut := sPiece; GVL.wSend := 2; nOffset := nOffset + nChunk; ELSE nStep := 5; END_IF
(* 5. closing quote, and transmit the whole thing *)5: GVL.sOut := '"'; GVL.wSend := 1; (* append, add CR LF, send *) nStep := 6;
6: GVL.wSend := 0; IF GVL.wInLen > 0 THEN (* expect 'OK' *) nStep := 7; END_IFEND_CASEThe PEM’s line breaks inside the quotes are accepted by the AT interpreter — this was the last unknown of the design, and it holds.
Reading it back
Section titled “Reading it back”GVL.sOut := 'AT%CMNG=1'; (* list what is stored *)GVL.wSend := 1;The answer names the slot, the type and the SHA-256 fingerprint of the stored credential:
%CMNG: 5432,1,"945DA7A0..."OKUse AT%CMNG=2,5432,1 to read the certificate itself back, and walk MODEM_INPUT_OFFSET across the response — it is far longer than the 128-byte window.
What this is for
Section titled “What this is for”With its own certificate in the modem, the device can authenticate itself to a platform with mutual TLS, without any shared secret and without the private key ever existing outside the RP2350. The server certificate goes in the same way, as type 0 on its own sec_tag.