[{"data":1,"prerenderedAt":4216},["ShallowReactive",2],{"blog-posts-en":3,"blog-post-one-golden-certificate-for-the-whole-production-line":80},[4,17,28,34,44,53,64,72],{"path":5,"stem":6,"title":7,"description":8,"date":9,"tags":10,"readingTime":15,"lang":16},"/blog/what-cra-compliance-takes-for-a-small-hardware-team","blog/what-cra-compliance-takes-for-a-small-hardware-team","What CRA compliance actually takes for a small hardware team","I turned every Cyber Resilience Act obligation for device makers into one checklist. What it asks of a connected product, and what I would do first.","2026-10-04",[11,12,13,14],"cra","iot","security","compliance","8 min read","en",{"path":18,"stem":19,"title":20,"description":21,"date":22,"tags":23,"readingTime":27,"lang":16},"/blog/one-golden-certificate-for-the-whole-production-line","blog/one-golden-certificate-for-the-whole-production-line","One golden certificate for the whole production line","Give every device its own X.509 identity in AWS IoT Core without flashing certificates unit by unit, using a shared claim certificate and fleet provisioning.","2026-10-01",[12,24,13,25,26],"aws","provisioning","esp32","9 min read",{"path":29,"stem":30,"title":31,"description":32,"date":22,"tags":33,"readingTime":27,"lang":16},"/blog/the-cra-clock-is-already-running","blog/the-cra-clock-is-already-running","The CRA clock is already running on devices you shipped years ago","Since 11 September 2026 the CRA's 24-hour reporting duty covers every connected product on the EU market, old ones too. What it means for a small hardware team.",[11,12,13,14],{"path":35,"stem":36,"title":37,"description":38,"date":39,"tags":40,"readingTime":43,"lang":16},"/blog/updating-firmware-on-a-fleet-without-bricking-it","blog/updating-firmware-on-a-fleet-without-bricking-it","Updating firmware on fifty thousand ESP32s without bricking any","Over-the-air updates for a large ESP32 fleet: partition layout and rollback, signed images, staged rollouts and the cost of sending a megabyte to every device.","2026-09-30",[12,26,41,42],"firmware","ota","7 min read",{"path":45,"stem":46,"title":47,"description":48,"date":39,"tags":49,"readingTime":43,"lang":16},"/blog/where-the-battery-goes-on-an-lte-m-sensor","blog/where-the-battery-goes-on-an-lte-m-sensor","Where the battery goes on an LTE-M sensor","The power budget of a battery powered LTE-M sensor, why the radio dominates it, and the settings that decide whether a battery lasts one year or five.",[12,50,51,52],"embedded","lte-m","low-power",{"path":54,"stem":55,"title":56,"description":57,"date":58,"tags":59,"readingTime":63,"lang":16},"/blog/building-an-llm-trading-bot","blog/building-an-llm-trading-bot","So everyone is building LLM trading bots now","I kept hearing about language models that trade on their own, so I dug into how these bots work, where they fall apart and what I would build instead.","2026-09-29",[60,61,62],"ai","llm","trading","6 min read",{"path":65,"stem":66,"title":67,"description":68,"date":58,"tags":69,"readingTime":71,"lang":16},"/blog/jev-a-model-that-only-decides","blog/jev-a-model-that-only-decides","JEV: a model that only decides","TypeSafe AI's JEV returns typed answers with calibrated probabilities and never writes a word. What it is, where it fits, and what to be careful about.",[60,61,70],"jev","5 min read",{"path":73,"stem":74,"title":75,"description":76,"date":58,"tags":77,"readingTime":15,"lang":16},"/blog/storing-a-reading-a-minute-from-100k-devices","blog/storing-a-reading-a-minute-from-100k-devices","Storing a reading a minute from 100,000 devices","What it actually takes to ingest, deduplicate and keep a year of per-minute telemetry from a fleet of a hundred thousand devices, and where the costs hide.",[12,78,79],"data-engineering","timescaledb",{"original":81,"pl":2294},{"id":82,"title":20,"author":83,"body":84,"date":22,"description":21,"extension":2287,"lang":16,"meta":2288,"navigation":1000,"path":18,"readingTime":27,"seo":2289,"sitemap":2290,"stem":19,"tags":2291,"updated":2292,"__hash__":2293},"blog/blog/one-golden-certificate-for-the-whole-production-line.md","Bartosz Szczepanik",{"type":85,"value":86,"toc":2277},"minimark",[87,91,94,99,102,105,109,112,456,459,463,466,918,929,1086,1096,1100,1103,1110,1438,1441,1445,1448,1737,1740,1754,1791,1794,1870,1884,1944,1947,1992,2003,2007,2010,2250,2253,2257,2260,2263,2267,2270,2273],[88,89,90],"p",{},"Somewhere between the tenth prototype and the first production batch, every connected product runs into the same question. On the desk, each board got its certificate the easy way. Someone generated it in the console, copied the files into the project and flashed them together with the firmware. That works for ten boards and falls apart at a thousand, because the factory now needs a separate step for every unit, a secure way to hand it unique secrets, and a record of which certificate went onto which board. Most contract manufacturers are set up to flash one identical image onto every device, and injecting a unique private key into each unit adds cost, time and a secure handover of secrets that many of them would rather avoid. I saw exactly this question on Reddit recently from someone running ThingsBoard, and the answers were thin, so here is how I handle it.",[88,92,93],{},"The approach I reach for in this situation is often called the golden certificate. Every device leaves the factory with the same firmware and the same shared certificate, and that certificate is allowed to do exactly one thing, which is to ask for a real identity of its own the first time the device comes online. AWS IoT Core supports this directly under the name fleet provisioning by claim, and the rest of this post walks through how I set it up.",[95,96,98],"h2",{"id":97},"how-the-exchange-works","How the exchange works",[88,100,101],{},"The shared certificate is called the claim certificate. A device connects to AWS IoT Core with it, generates a fresh key pair locally, and sends a certificate signing request over MQTT. AWS signs it and returns a new certificate along with a short ownership token. The device then calls a provisioning template with that token and a few parameters, such as its serial number. The template creates a thing in the registry, activates the new certificate, attaches the right policy and hands back the thing name. From that moment the device disconnects, throws the claim certificate away for everyday use and reconnects with its own identity.",[88,103,104],{},"The private key for the device certificate is generated on the device and never travels anywhere. AWS also offers a variant where the cloud generates the key and sends it down. I avoid it, because the CSR route costs very little extra on an ESP32 and removes the one moment where a private key is in transit.",[95,106,108],{"id":107},"a-claim-certificate-that-can-do-almost-nothing","A claim certificate that can do almost nothing",[88,110,111],{},"The claim certificate will be on every unit you ship, so I assume from day one that someone will eventually extract it. Its policy should let it connect and use the provisioning topics, and nothing else at all.",[113,114,119],"pre",{"className":115,"code":116,"language":117,"meta":118,"style":118},"language-json shiki shiki-themes arch-amber arch-amber-light","{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:Connect\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\"iot:Publish\", \"iot:Receive\"],\n      \"Resource\": [\n        \"arn:aws:iot:eu-central-1:123456789012:topic/$aws/certificates/create-from-csr/*\",\n        \"arn:aws:iot:eu-central-1:123456789012:topic/$aws/provisioning-templates/factory-template/provision/*\"\n      ]\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:Subscribe\",\n      \"Resource\": [\n        \"arn:aws:iot:eu-central-1:123456789012:topicfilter/$aws/certificates/create-from-csr/*\",\n        \"arn:aws:iot:eu-central-1:123456789012:topicfilter/$aws/provisioning-templates/factory-template/provision/*\"\n      ]\n    }\n  ]\n}\n","json","",[120,121,122,131,159,174,180,202,223,243,249,254,273,307,320,333,343,349,354,359,378,398,411,423,433,438,444,450],"code",{"__ignoreMap":118},[123,124,127],"span",{"class":125,"line":126},"line",1,[123,128,130],{"class":129},"sfVkF","{\n",[123,132,134,137,141,144,147,150,154,156],{"class":125,"line":133},2,[123,135,136],{"class":129},"  \"",[123,138,140],{"class":139},"sDcrj","Version",[123,142,143],{"class":129},"\"",[123,145,146],{"class":129},":",[123,148,149],{"class":129}," \"",[123,151,153],{"class":152},"sP85D","2012-10-17",[123,155,143],{"class":129},[123,157,158],{"class":129},",\n",[123,160,162,164,167,169,171],{"class":125,"line":161},3,[123,163,136],{"class":129},[123,165,166],{"class":139},"Statement",[123,168,143],{"class":129},[123,170,146],{"class":129},[123,172,173],{"class":129}," [\n",[123,175,177],{"class":125,"line":176},4,[123,178,179],{"class":129},"    {\n",[123,181,183,186,189,191,193,195,198,200],{"class":125,"line":182},5,[123,184,185],{"class":129},"      \"",[123,187,188],{"class":139},"Effect",[123,190,143],{"class":129},[123,192,146],{"class":129},[123,194,149],{"class":129},[123,196,197],{"class":152},"Allow",[123,199,143],{"class":129},[123,201,158],{"class":129},[123,203,205,207,210,212,214,216,219,221],{"class":125,"line":204},6,[123,206,185],{"class":129},[123,208,209],{"class":139},"Action",[123,211,143],{"class":129},[123,213,146],{"class":129},[123,215,149],{"class":129},[123,217,218],{"class":152},"iot:Connect",[123,220,143],{"class":129},[123,222,158],{"class":129},[123,224,226,228,231,233,235,237,240],{"class":125,"line":225},7,[123,227,185],{"class":129},[123,229,230],{"class":139},"Resource",[123,232,143],{"class":129},[123,234,146],{"class":129},[123,236,149],{"class":129},[123,238,239],{"class":152},"*",[123,241,242],{"class":129},"\"\n",[123,244,246],{"class":125,"line":245},8,[123,247,248],{"class":129},"    },\n",[123,250,252],{"class":125,"line":251},9,[123,253,179],{"class":129},[123,255,257,259,261,263,265,267,269,271],{"class":125,"line":256},10,[123,258,185],{"class":129},[123,260,188],{"class":139},[123,262,143],{"class":129},[123,264,146],{"class":129},[123,266,149],{"class":129},[123,268,197],{"class":152},[123,270,143],{"class":129},[123,272,158],{"class":129},[123,274,276,278,280,282,284,287,289,292,294,297,299,302,304],{"class":125,"line":275},11,[123,277,185],{"class":129},[123,279,209],{"class":139},[123,281,143],{"class":129},[123,283,146],{"class":129},[123,285,286],{"class":129}," [",[123,288,143],{"class":129},[123,290,291],{"class":152},"iot:Publish",[123,293,143],{"class":129},[123,295,296],{"class":129},",",[123,298,149],{"class":129},[123,300,301],{"class":152},"iot:Receive",[123,303,143],{"class":129},[123,305,306],{"class":129},"],\n",[123,308,310,312,314,316,318],{"class":125,"line":309},12,[123,311,185],{"class":129},[123,313,230],{"class":139},[123,315,143],{"class":129},[123,317,146],{"class":129},[123,319,173],{"class":129},[123,321,323,326,329,331],{"class":125,"line":322},13,[123,324,325],{"class":129},"        \"",[123,327,328],{"class":152},"arn:aws:iot:eu-central-1:123456789012:topic/$aws/certificates/create-from-csr/*",[123,330,143],{"class":129},[123,332,158],{"class":129},[123,334,336,338,341],{"class":125,"line":335},14,[123,337,325],{"class":129},[123,339,340],{"class":152},"arn:aws:iot:eu-central-1:123456789012:topic/$aws/provisioning-templates/factory-template/provision/*",[123,342,242],{"class":129},[123,344,346],{"class":125,"line":345},15,[123,347,348],{"class":129},"      ]\n",[123,350,352],{"class":125,"line":351},16,[123,353,248],{"class":129},[123,355,357],{"class":125,"line":356},17,[123,358,179],{"class":129},[123,360,362,364,366,368,370,372,374,376],{"class":125,"line":361},18,[123,363,185],{"class":129},[123,365,188],{"class":139},[123,367,143],{"class":129},[123,369,146],{"class":129},[123,371,149],{"class":129},[123,373,197],{"class":152},[123,375,143],{"class":129},[123,377,158],{"class":129},[123,379,381,383,385,387,389,391,394,396],{"class":125,"line":380},19,[123,382,185],{"class":129},[123,384,209],{"class":139},[123,386,143],{"class":129},[123,388,146],{"class":129},[123,390,149],{"class":129},[123,392,393],{"class":152},"iot:Subscribe",[123,395,143],{"class":129},[123,397,158],{"class":129},[123,399,401,403,405,407,409],{"class":125,"line":400},20,[123,402,185],{"class":129},[123,404,230],{"class":139},[123,406,143],{"class":129},[123,408,146],{"class":129},[123,410,173],{"class":129},[123,412,414,416,419,421],{"class":125,"line":413},21,[123,415,325],{"class":129},[123,417,418],{"class":152},"arn:aws:iot:eu-central-1:123456789012:topicfilter/$aws/certificates/create-from-csr/*",[123,420,143],{"class":129},[123,422,158],{"class":129},[123,424,426,428,431],{"class":125,"line":425},22,[123,427,325],{"class":129},[123,429,430],{"class":152},"arn:aws:iot:eu-central-1:123456789012:topicfilter/$aws/provisioning-templates/factory-template/provision/*",[123,432,242],{"class":129},[123,434,436],{"class":125,"line":435},23,[123,437,348],{"class":129},[123,439,441],{"class":125,"line":440},24,[123,442,443],{"class":129},"    }\n",[123,445,447],{"class":125,"line":446},25,[123,448,449],{"class":129},"  ]\n",[123,451,453],{"class":125,"line":452},26,[123,454,455],{"class":129},"}\n",[88,457,458],{},"A device holding only this certificate cannot read or publish telemetry, cannot touch shadows and cannot see any other device. The worst it can do is ask for a new identity, and the next sections make sure that request only succeeds for serial numbers that actually came off your line.",[95,460,462],{"id":461},"the-provisioning-template","The provisioning template",[88,464,465],{},"The template describes what AWS should create for each new device. Mine usually takes the serial number as a parameter, names the thing after it and attaches a single shared device policy.",[113,467,469],{"className":115,"code":468,"language":117,"meta":118,"style":118},"{\n  \"Parameters\": {\n    \"SerialNumber\": { \"Type\": \"String\" },\n    \"AWS::IoT::Certificate::Id\": { \"Type\": \"String\" }\n  },\n  \"Resources\": {\n    \"thing\": {\n      \"Type\": \"AWS::IoT::Thing\",\n      \"Properties\": {\n        \"ThingName\": { \"Fn::Join\": [\"\", [\"sensor-\", { \"Ref\": \"SerialNumber\" }]] },\n        \"AttributePayload\": { \"serial\": { \"Ref\": \"SerialNumber\" } }\n      }\n    },\n    \"certificate\": {\n      \"Type\": \"AWS::IoT::Certificate\",\n      \"Properties\": {\n        \"CertificateId\": { \"Ref\": \"AWS::IoT::Certificate::Id\" },\n        \"Status\": \"ACTIVE\"\n      }\n    },\n    \"policy\": {\n      \"Type\": \"AWS::IoT::Policy\",\n      \"Properties\": { \"PolicyName\": \"fleet-device-policy\" }\n    }\n  },\n  \"DeviceConfiguration\": {}\n}\n",[120,470,471,475,489,523,553,558,571,584,603,616,678,721,726,730,743,762,774,803,821,825,829,842,861,891,895,899,913],{"__ignoreMap":118},[123,472,473],{"class":125,"line":126},[123,474,130],{"class":129},[123,476,477,479,482,484,486],{"class":125,"line":133},[123,478,136],{"class":129},[123,480,481],{"class":139},"Parameters",[123,483,143],{"class":129},[123,485,146],{"class":129},[123,487,488],{"class":129}," {\n",[123,490,491,494,497,499,501,504,506,509,511,513,515,518,520],{"class":125,"line":161},[123,492,493],{"class":129},"    \"",[123,495,496],{"class":139},"SerialNumber",[123,498,143],{"class":129},[123,500,146],{"class":129},[123,502,503],{"class":129}," {",[123,505,149],{"class":129},[123,507,508],{"class":139},"Type",[123,510,143],{"class":129},[123,512,146],{"class":129},[123,514,149],{"class":129},[123,516,517],{"class":152},"String",[123,519,143],{"class":129},[123,521,522],{"class":129}," },\n",[123,524,525,527,530,532,534,536,538,540,542,544,546,548,550],{"class":125,"line":176},[123,526,493],{"class":129},[123,528,529],{"class":139},"AWS::IoT::Certificate::Id",[123,531,143],{"class":129},[123,533,146],{"class":129},[123,535,503],{"class":129},[123,537,149],{"class":129},[123,539,508],{"class":139},[123,541,143],{"class":129},[123,543,146],{"class":129},[123,545,149],{"class":129},[123,547,517],{"class":152},[123,549,143],{"class":129},[123,551,552],{"class":129}," }\n",[123,554,555],{"class":125,"line":182},[123,556,557],{"class":129},"  },\n",[123,559,560,562,565,567,569],{"class":125,"line":204},[123,561,136],{"class":129},[123,563,564],{"class":139},"Resources",[123,566,143],{"class":129},[123,568,146],{"class":129},[123,570,488],{"class":129},[123,572,573,575,578,580,582],{"class":125,"line":225},[123,574,493],{"class":129},[123,576,577],{"class":139},"thing",[123,579,143],{"class":129},[123,581,146],{"class":129},[123,583,488],{"class":129},[123,585,586,588,590,592,594,596,599,601],{"class":125,"line":245},[123,587,185],{"class":129},[123,589,508],{"class":139},[123,591,143],{"class":129},[123,593,146],{"class":129},[123,595,149],{"class":129},[123,597,598],{"class":152},"AWS::IoT::Thing",[123,600,143],{"class":129},[123,602,158],{"class":129},[123,604,605,607,610,612,614],{"class":125,"line":251},[123,606,185],{"class":129},[123,608,609],{"class":139},"Properties",[123,611,143],{"class":129},[123,613,146],{"class":129},[123,615,488],{"class":129},[123,617,618,620,623,625,627,629,631,634,636,638,640,643,645,647,649,652,654,656,658,660,663,665,667,669,671,673,676],{"class":125,"line":256},[123,619,325],{"class":129},[123,621,622],{"class":139},"ThingName",[123,624,143],{"class":129},[123,626,146],{"class":129},[123,628,503],{"class":129},[123,630,149],{"class":129},[123,632,633],{"class":139},"Fn::Join",[123,635,143],{"class":129},[123,637,146],{"class":129},[123,639,286],{"class":129},[123,641,642],{"class":129},"\"\"",[123,644,296],{"class":129},[123,646,286],{"class":129},[123,648,143],{"class":129},[123,650,651],{"class":152},"sensor-",[123,653,143],{"class":129},[123,655,296],{"class":129},[123,657,503],{"class":129},[123,659,149],{"class":129},[123,661,662],{"class":139},"Ref",[123,664,143],{"class":129},[123,666,146],{"class":129},[123,668,149],{"class":129},[123,670,496],{"class":152},[123,672,143],{"class":129},[123,674,675],{"class":129}," }]]",[123,677,522],{"class":129},[123,679,680,682,685,687,689,691,693,696,698,700,702,704,706,708,710,712,714,716,719],{"class":125,"line":275},[123,681,325],{"class":129},[123,683,684],{"class":139},"AttributePayload",[123,686,143],{"class":129},[123,688,146],{"class":129},[123,690,503],{"class":129},[123,692,149],{"class":129},[123,694,695],{"class":139},"serial",[123,697,143],{"class":129},[123,699,146],{"class":129},[123,701,503],{"class":129},[123,703,149],{"class":129},[123,705,662],{"class":139},[123,707,143],{"class":129},[123,709,146],{"class":129},[123,711,149],{"class":129},[123,713,496],{"class":152},[123,715,143],{"class":129},[123,717,718],{"class":129}," }",[123,720,552],{"class":129},[123,722,723],{"class":125,"line":309},[123,724,725],{"class":129},"      }\n",[123,727,728],{"class":125,"line":322},[123,729,248],{"class":129},[123,731,732,734,737,739,741],{"class":125,"line":335},[123,733,493],{"class":129},[123,735,736],{"class":139},"certificate",[123,738,143],{"class":129},[123,740,146],{"class":129},[123,742,488],{"class":129},[123,744,745,747,749,751,753,755,758,760],{"class":125,"line":345},[123,746,185],{"class":129},[123,748,508],{"class":139},[123,750,143],{"class":129},[123,752,146],{"class":129},[123,754,149],{"class":129},[123,756,757],{"class":152},"AWS::IoT::Certificate",[123,759,143],{"class":129},[123,761,158],{"class":129},[123,763,764,766,768,770,772],{"class":125,"line":351},[123,765,185],{"class":129},[123,767,609],{"class":139},[123,769,143],{"class":129},[123,771,146],{"class":129},[123,773,488],{"class":129},[123,775,776,778,781,783,785,787,789,791,793,795,797,799,801],{"class":125,"line":356},[123,777,325],{"class":129},[123,779,780],{"class":139},"CertificateId",[123,782,143],{"class":129},[123,784,146],{"class":129},[123,786,503],{"class":129},[123,788,149],{"class":129},[123,790,662],{"class":139},[123,792,143],{"class":129},[123,794,146],{"class":129},[123,796,149],{"class":129},[123,798,529],{"class":152},[123,800,143],{"class":129},[123,802,522],{"class":129},[123,804,805,807,810,812,814,816,819],{"class":125,"line":361},[123,806,325],{"class":129},[123,808,809],{"class":139},"Status",[123,811,143],{"class":129},[123,813,146],{"class":129},[123,815,149],{"class":129},[123,817,818],{"class":152},"ACTIVE",[123,820,242],{"class":129},[123,822,823],{"class":125,"line":380},[123,824,725],{"class":129},[123,826,827],{"class":125,"line":400},[123,828,248],{"class":129},[123,830,831,833,836,838,840],{"class":125,"line":413},[123,832,493],{"class":129},[123,834,835],{"class":139},"policy",[123,837,143],{"class":129},[123,839,146],{"class":129},[123,841,488],{"class":129},[123,843,844,846,848,850,852,854,857,859],{"class":125,"line":425},[123,845,185],{"class":129},[123,847,508],{"class":139},[123,849,143],{"class":129},[123,851,146],{"class":129},[123,853,149],{"class":129},[123,855,856],{"class":152},"AWS::IoT::Policy",[123,858,143],{"class":129},[123,860,158],{"class":129},[123,862,863,865,867,869,871,873,875,878,880,882,884,887,889],{"class":125,"line":435},[123,864,185],{"class":129},[123,866,609],{"class":139},[123,868,143],{"class":129},[123,870,146],{"class":129},[123,872,503],{"class":129},[123,874,149],{"class":129},[123,876,877],{"class":139},"PolicyName",[123,879,143],{"class":129},[123,881,146],{"class":129},[123,883,149],{"class":129},[123,885,886],{"class":152},"fleet-device-policy",[123,888,143],{"class":129},[123,890,552],{"class":129},[123,892,893],{"class":125,"line":440},[123,894,443],{"class":129},[123,896,897],{"class":125,"line":446},[123,898,557],{"class":129},[123,900,901,903,906,908,910],{"class":125,"line":452},[123,902,136],{"class":129},[123,904,905],{"class":139},"DeviceConfiguration",[123,907,143],{"class":129},[123,909,146],{"class":129},[123,911,912],{"class":129}," {}\n",[123,914,916],{"class":125,"line":915},27,[123,917,455],{"class":129},[88,919,920,921,924,925,928],{},"The template needs an IAM role that AWS IoT can assume to create these resources. A role trusted by ",[120,922,923],{},"iot.amazonaws.com"," with the managed ",[120,926,927],{},"AWSIoTThingsRegistration"," policy attached covers it. Creating the template, the claim certificate and attaching the claim policy then takes three commands.",[113,930,934],{"className":931,"code":932,"language":933,"meta":118,"style":118},"language-bash shiki shiki-themes arch-amber arch-amber-light","aws iot create-provisioning-template \\\n  --template-name factory-template \\\n  --provisioning-role-arn arn:aws:iam::123456789012:role/IoTFleetProvisioningRole \\\n  --template-body file://template.json \\\n  --pre-provisioning-hook targetArn=arn:aws:lambda:eu-central-1:123456789012:function:check-serial \\\n  --enabled\n\naws iot create-keys-and-certificate --set-as-active \\\n  --certificate-pem-outfile claim.cert.pem \\\n  --private-key-outfile claim.private.key \\\n  --query certificateArn --output text\n\naws iot attach-policy --policy-name claim-policy --target \u003Ccertificate-arn>\n","bash",[120,935,936,951,961,971,981,991,996,1002,1016,1026,1036,1050,1054],{"__ignoreMap":118},[123,937,938,941,944,947],{"class":125,"line":126},[123,939,24],{"class":940},"swbo2",[123,942,943],{"class":152}," iot",[123,945,946],{"class":152}," create-provisioning-template",[123,948,950],{"class":949},"smnN6"," \\\n",[123,952,953,956,959],{"class":125,"line":133},[123,954,955],{"class":152},"  --template-name",[123,957,958],{"class":152}," factory-template",[123,960,950],{"class":949},[123,962,963,966,969],{"class":125,"line":161},[123,964,965],{"class":152},"  --provisioning-role-arn",[123,967,968],{"class":152}," arn:aws:iam::123456789012:role/IoTFleetProvisioningRole",[123,970,950],{"class":949},[123,972,973,976,979],{"class":125,"line":176},[123,974,975],{"class":152},"  --template-body",[123,977,978],{"class":152}," file://template.json",[123,980,950],{"class":949},[123,982,983,986,989],{"class":125,"line":182},[123,984,985],{"class":152},"  --pre-provisioning-hook",[123,987,988],{"class":152}," targetArn=arn:aws:lambda:eu-central-1:123456789012:function:check-serial",[123,990,950],{"class":949},[123,992,993],{"class":125,"line":204},[123,994,995],{"class":152},"  --enabled\n",[123,997,998],{"class":125,"line":225},[123,999,1001],{"emptyLinePlaceholder":1000},true,"\n",[123,1003,1004,1006,1008,1011,1014],{"class":125,"line":245},[123,1005,24],{"class":940},[123,1007,943],{"class":152},[123,1009,1010],{"class":152}," create-keys-and-certificate",[123,1012,1013],{"class":152}," --set-as-active",[123,1015,950],{"class":949},[123,1017,1018,1021,1024],{"class":125,"line":251},[123,1019,1020],{"class":152},"  --certificate-pem-outfile",[123,1022,1023],{"class":152}," claim.cert.pem",[123,1025,950],{"class":949},[123,1027,1028,1031,1034],{"class":125,"line":256},[123,1029,1030],{"class":152},"  --private-key-outfile",[123,1032,1033],{"class":152}," claim.private.key",[123,1035,950],{"class":949},[123,1037,1038,1041,1044,1047],{"class":125,"line":275},[123,1039,1040],{"class":152},"  --query",[123,1042,1043],{"class":152}," certificateArn",[123,1045,1046],{"class":152}," --output",[123,1048,1049],{"class":152}," text\n",[123,1051,1052],{"class":125,"line":309},[123,1053,1001],{"emptyLinePlaceholder":1000},[123,1055,1056,1058,1060,1063,1066,1069,1072,1076,1079,1083],{"class":125,"line":322},[123,1057,24],{"class":940},[123,1059,943],{"class":152},[123,1061,1062],{"class":152}," attach-policy",[123,1064,1065],{"class":152}," --policy-name",[123,1067,1068],{"class":152}," claim-policy",[123,1070,1071],{"class":152}," --target",[123,1073,1075],{"class":1074},"sxJ4f"," \u003C",[123,1077,1078],{"class":152},"certificate-ar",[123,1080,1082],{"class":1081},"sile1","n",[123,1084,1085],{"class":1074},">\n",[88,1087,1088,1089,1091,1092,1095],{},"The Lambda function in the hook also needs a resource policy that lets ",[120,1090,923],{}," invoke it, which ",[120,1093,1094],{},"aws lambda add-permission"," takes care of.",[95,1097,1099],{"id":1098},"only-serials-you-actually-built","Only serials you actually built",[88,1101,1102],{},"Without a check, anyone holding the claim certificate could invent serial numbers and provision as many fake devices as they like. The pre-provisioning hook closes that gap. AWS calls a Lambda function before creating anything, passes it the parameters the device sent, and only continues if the function says yes.",[88,1104,1105,1106,1109],{},"The factory side of this is pleasantly small. The end-of-line test already reads something unique from every board, and on an ESP32 the factory programmed MAC address in eFuse works well as a serial. The test station writes each serial into a DynamoDB table as it passes, with a ",[120,1107,1108],{},"provisioned"," flag set to false. The hook then accepts a serial only once, and only if it is in that table. AWS gives the hook five seconds to answer and refuses the request if it takes longer, which a single DynamoDB write fits into comfortably.",[113,1111,1115],{"className":1112,"code":1113,"language":1114,"meta":118,"style":118},"language-python shiki shiki-themes arch-amber arch-amber-light","import boto3\n\ntable = boto3.resource(\"dynamodb\").Table(\"factory-serials\")\n\n\ndef handler(event, context):\n    serial = event.get(\"parameters\", {}).get(\"SerialNumber\", \"\")\n    try:\n        table.update_item(\n            Key={\"serial\": serial},\n            UpdateExpression=\"SET provisioned = :yes\",\n            ConditionExpression=\"attribute_exists(serial) AND provisioned = :no\",\n            ExpressionAttributeValues={\":yes\": True, \":no\": False},\n        )\n    except table.meta.client.exceptions.ConditionalCheckFailedException:\n        return {\"allowProvisioning\": False}\n    return {\"allowProvisioning\": True}\n","python",[120,1116,1117,1126,1130,1175,1179,1183,1204,1250,1258,1271,1295,1311,1327,1364,1369,1399,1419],{"__ignoreMap":118},[123,1118,1119,1123],{"class":125,"line":126},[123,1120,1122],{"class":1121},"s-Rrv","import",[123,1124,1125],{"class":1081}," boto3\n",[123,1127,1128],{"class":125,"line":133},[123,1129,1001],{"emptyLinePlaceholder":1000},[123,1131,1132,1135,1138,1141,1144,1147,1150,1152,1155,1157,1160,1163,1165,1167,1170,1172],{"class":125,"line":161},[123,1133,1134],{"class":1081},"table ",[123,1136,1137],{"class":1074},"=",[123,1139,1140],{"class":1081}," boto3",[123,1142,1143],{"class":129},".",[123,1145,1146],{"class":940},"resource",[123,1148,1149],{"class":129},"(",[123,1151,143],{"class":129},[123,1153,1154],{"class":152},"dynamodb",[123,1156,143],{"class":129},[123,1158,1159],{"class":129},").",[123,1161,1162],{"class":940},"Table",[123,1164,1149],{"class":129},[123,1166,143],{"class":129},[123,1168,1169],{"class":152},"factory-serials",[123,1171,143],{"class":129},[123,1173,1174],{"class":129},")\n",[123,1176,1177],{"class":125,"line":176},[123,1178,1001],{"emptyLinePlaceholder":1000},[123,1180,1181],{"class":125,"line":182},[123,1182,1001],{"emptyLinePlaceholder":1000},[123,1184,1185,1188,1191,1193,1196,1198,1201],{"class":125,"line":204},[123,1186,1187],{"class":1121},"def",[123,1189,1190],{"class":940}," handler",[123,1192,1149],{"class":129},[123,1194,1195],{"class":139},"event",[123,1197,296],{"class":129},[123,1199,1200],{"class":139}," context",[123,1202,1203],{"class":129},"):\n",[123,1205,1206,1209,1211,1214,1216,1219,1221,1223,1226,1228,1230,1233,1235,1237,1239,1241,1243,1245,1248],{"class":125,"line":225},[123,1207,1208],{"class":1081},"    serial ",[123,1210,1137],{"class":1074},[123,1212,1213],{"class":1081}," event",[123,1215,1143],{"class":129},[123,1217,1218],{"class":940},"get",[123,1220,1149],{"class":129},[123,1222,143],{"class":129},[123,1224,1225],{"class":152},"parameters",[123,1227,143],{"class":129},[123,1229,296],{"class":129},[123,1231,1232],{"class":129}," {}).",[123,1234,1218],{"class":940},[123,1236,1149],{"class":129},[123,1238,143],{"class":129},[123,1240,496],{"class":152},[123,1242,143],{"class":129},[123,1244,296],{"class":129},[123,1246,1247],{"class":129}," \"\"",[123,1249,1174],{"class":129},[123,1251,1252,1255],{"class":125,"line":245},[123,1253,1254],{"class":1121},"    try",[123,1256,1257],{"class":129},":\n",[123,1259,1260,1263,1265,1268],{"class":125,"line":251},[123,1261,1262],{"class":1081},"        table",[123,1264,1143],{"class":129},[123,1266,1267],{"class":940},"update_item",[123,1269,1270],{"class":129},"(\n",[123,1272,1273,1276,1278,1281,1283,1285,1287,1289,1292],{"class":125,"line":256},[123,1274,1275],{"class":139},"            Key",[123,1277,1137],{"class":1074},[123,1279,1280],{"class":129},"{",[123,1282,143],{"class":129},[123,1284,695],{"class":152},[123,1286,143],{"class":129},[123,1288,146],{"class":129},[123,1290,1291],{"class":940}," serial",[123,1293,1294],{"class":129},"},\n",[123,1296,1297,1300,1302,1304,1307,1309],{"class":125,"line":275},[123,1298,1299],{"class":139},"            UpdateExpression",[123,1301,1137],{"class":1074},[123,1303,143],{"class":129},[123,1305,1306],{"class":152},"SET provisioned = :yes",[123,1308,143],{"class":129},[123,1310,158],{"class":129},[123,1312,1313,1316,1318,1320,1323,1325],{"class":125,"line":309},[123,1314,1315],{"class":139},"            ConditionExpression",[123,1317,1137],{"class":1074},[123,1319,143],{"class":129},[123,1321,1322],{"class":152},"attribute_exists(serial) AND provisioned = :no",[123,1324,143],{"class":129},[123,1326,158],{"class":129},[123,1328,1329,1332,1334,1336,1338,1341,1343,1345,1348,1350,1352,1355,1357,1359,1362],{"class":125,"line":322},[123,1330,1331],{"class":139},"            ExpressionAttributeValues",[123,1333,1137],{"class":1074},[123,1335,1280],{"class":129},[123,1337,143],{"class":129},[123,1339,1340],{"class":152},":yes",[123,1342,143],{"class":129},[123,1344,146],{"class":129},[123,1346,1347],{"class":949}," True",[123,1349,296],{"class":129},[123,1351,149],{"class":129},[123,1353,1354],{"class":152},":no",[123,1356,143],{"class":129},[123,1358,146],{"class":129},[123,1360,1361],{"class":949}," False",[123,1363,1294],{"class":129},[123,1365,1366],{"class":125,"line":335},[123,1367,1368],{"class":129},"        )\n",[123,1370,1371,1374,1377,1379,1382,1384,1387,1389,1392,1394,1397],{"class":125,"line":345},[123,1372,1373],{"class":1121},"    except",[123,1375,1376],{"class":1081}," table",[123,1378,1143],{"class":129},[123,1380,1381],{"class":1081},"meta",[123,1383,1143],{"class":129},[123,1385,1386],{"class":1081},"client",[123,1388,1143],{"class":129},[123,1390,1391],{"class":1081},"exceptions",[123,1393,1143],{"class":129},[123,1395,1396],{"class":1081},"ConditionalCheckFailedException",[123,1398,1257],{"class":129},[123,1400,1401,1404,1406,1408,1411,1413,1415,1417],{"class":125,"line":351},[123,1402,1403],{"class":1121},"        return",[123,1405,503],{"class":129},[123,1407,143],{"class":129},[123,1409,1410],{"class":152},"allowProvisioning",[123,1412,143],{"class":129},[123,1414,146],{"class":129},[123,1416,1361],{"class":949},[123,1418,455],{"class":129},[123,1420,1421,1424,1426,1428,1430,1432,1434,1436],{"class":125,"line":356},[123,1422,1423],{"class":1121},"    return",[123,1425,503],{"class":129},[123,1427,143],{"class":129},[123,1429,1410],{"class":152},[123,1431,143],{"class":129},[123,1433,146],{"class":129},[123,1435,1347],{"class":949},[123,1437,455],{"class":129},[88,1439,1440],{},"The one-time rule needs an escape hatch. A device that passes the hook and then loses power before saving its new certificate will be refused on the next attempt, so support needs a simple way to deactivate the orphaned certificate on that thing and reset the flag for its serial. Saving the private key before sending the CSR, and the certificate the moment it arrives, keeps that window small.",[95,1442,1444],{"id":1443},"what-the-device-does-on-first-boot","What the device does on first boot",[88,1446,1447],{},"On the device, the first step is generating a key pair and a CSR. The code below is for ESP-IDF 5.x, which ships mbedTLS 3.6, and assumes a CTR-DRBG random generator has already been seeded. ESP-IDF 6.0 moved to mbedTLS 4, where key generation goes through the PSA crypto API, so on that release these calls need porting. I use an EC key on the P-256 curve, which is smaller and faster than RSA on a microcontroller.",[113,1449,1453],{"className":1450,"code":1451,"language":1452,"meta":118,"style":118},"language-c shiki shiki-themes arch-amber arch-amber-light","mbedtls_pk_context key;\nmbedtls_x509write_csr csr;\nchar subject[64];\nunsigned char csr_pem[1024];\n\nsnprintf(subject, sizeof(subject), \"CN=sensor-%s\", serial);\n\nmbedtls_pk_init(&key);\nmbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY));\nmbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1, mbedtls_pk_ec(key),\n                    mbedtls_ctr_drbg_random, &ctr_drbg);\n\nmbedtls_x509write_csr_init(&csr);\nmbedtls_x509write_csr_set_key(&csr, &key);\nmbedtls_x509write_csr_set_md_alg(&csr, MBEDTLS_MD_SHA256);\nmbedtls_x509write_csr_set_subject_name(&csr, subject);\nmbedtls_x509write_csr_pem(&csr, csr_pem, sizeof(csr_pem),\n                          mbedtls_ctr_drbg_random, &ctr_drbg);\n","c",[120,1454,1455,1463,1470,1488,1506,1510,1546,1550,1565,1589,1611,1626,1630,1644,1663,1681,1698,1724],{"__ignoreMap":118},[123,1456,1457,1460],{"class":125,"line":126},[123,1458,1459],{"class":1081},"mbedtls_pk_context key",[123,1461,1462],{"class":129},";\n",[123,1464,1465,1468],{"class":125,"line":133},[123,1466,1467],{"class":1081},"mbedtls_x509write_csr csr",[123,1469,1462],{"class":129},[123,1471,1472,1476,1479,1482,1485],{"class":125,"line":161},[123,1473,1475],{"class":1474},"sSq2B","char",[123,1477,1478],{"class":1081}," subject",[123,1480,1481],{"class":129},"[",[123,1483,1484],{"class":949},"64",[123,1486,1487],{"class":129},"];\n",[123,1489,1490,1493,1496,1499,1501,1504],{"class":125,"line":176},[123,1491,1492],{"class":1474},"unsigned",[123,1494,1495],{"class":1474}," char",[123,1497,1498],{"class":1081}," csr_pem",[123,1500,1481],{"class":129},[123,1502,1503],{"class":949},"1024",[123,1505,1487],{"class":129},[123,1507,1508],{"class":125,"line":182},[123,1509,1001],{"emptyLinePlaceholder":1000},[123,1511,1512,1515,1517,1520,1522,1525,1527,1529,1532,1534,1537,1539,1541,1543],{"class":125,"line":204},[123,1513,1514],{"class":940},"snprintf",[123,1516,1149],{"class":129},[123,1518,1519],{"class":1081},"subject",[123,1521,296],{"class":129},[123,1523,1524],{"class":1074}," sizeof",[123,1526,1149],{"class":129},[123,1528,1519],{"class":1081},[123,1530,1531],{"class":129},"),",[123,1533,149],{"class":129},[123,1535,1536],{"class":152},"CN=sensor-%s",[123,1538,143],{"class":129},[123,1540,296],{"class":129},[123,1542,1291],{"class":1081},[123,1544,1545],{"class":129},");\n",[123,1547,1548],{"class":125,"line":225},[123,1549,1001],{"emptyLinePlaceholder":1000},[123,1551,1552,1555,1557,1560,1563],{"class":125,"line":245},[123,1553,1554],{"class":940},"mbedtls_pk_init",[123,1556,1149],{"class":129},[123,1558,1559],{"class":1074},"&",[123,1561,1562],{"class":139},"key",[123,1564,1545],{"class":129},[123,1566,1567,1570,1572,1574,1576,1578,1581,1583,1586],{"class":125,"line":251},[123,1568,1569],{"class":940},"mbedtls_pk_setup",[123,1571,1149],{"class":129},[123,1573,1559],{"class":1074},[123,1575,1562],{"class":139},[123,1577,296],{"class":129},[123,1579,1580],{"class":940}," mbedtls_pk_info_from_type",[123,1582,1149],{"class":129},[123,1584,1585],{"class":1081},"MBEDTLS_PK_ECKEY",[123,1587,1588],{"class":129},"));\n",[123,1590,1591,1594,1596,1599,1601,1604,1606,1608],{"class":125,"line":256},[123,1592,1593],{"class":940},"mbedtls_ecp_gen_key",[123,1595,1149],{"class":129},[123,1597,1598],{"class":1081},"MBEDTLS_ECP_DP_SECP256R1",[123,1600,296],{"class":129},[123,1602,1603],{"class":940}," mbedtls_pk_ec",[123,1605,1149],{"class":129},[123,1607,1562],{"class":1081},[123,1609,1610],{"class":129},"),\n",[123,1612,1613,1616,1618,1621,1624],{"class":125,"line":275},[123,1614,1615],{"class":1081},"                    mbedtls_ctr_drbg_random",[123,1617,296],{"class":129},[123,1619,1620],{"class":1074}," &",[123,1622,1623],{"class":139},"ctr_drbg",[123,1625,1545],{"class":129},[123,1627,1628],{"class":125,"line":309},[123,1629,1001],{"emptyLinePlaceholder":1000},[123,1631,1632,1635,1637,1639,1642],{"class":125,"line":322},[123,1633,1634],{"class":940},"mbedtls_x509write_csr_init",[123,1636,1149],{"class":129},[123,1638,1559],{"class":1074},[123,1640,1641],{"class":139},"csr",[123,1643,1545],{"class":129},[123,1645,1646,1649,1651,1653,1655,1657,1659,1661],{"class":125,"line":335},[123,1647,1648],{"class":940},"mbedtls_x509write_csr_set_key",[123,1650,1149],{"class":129},[123,1652,1559],{"class":1074},[123,1654,1641],{"class":139},[123,1656,296],{"class":129},[123,1658,1620],{"class":1074},[123,1660,1562],{"class":139},[123,1662,1545],{"class":129},[123,1664,1665,1668,1670,1672,1674,1676,1679],{"class":125,"line":345},[123,1666,1667],{"class":940},"mbedtls_x509write_csr_set_md_alg",[123,1669,1149],{"class":129},[123,1671,1559],{"class":1074},[123,1673,1641],{"class":139},[123,1675,296],{"class":129},[123,1677,1678],{"class":1081}," MBEDTLS_MD_SHA256",[123,1680,1545],{"class":129},[123,1682,1683,1686,1688,1690,1692,1694,1696],{"class":125,"line":351},[123,1684,1685],{"class":940},"mbedtls_x509write_csr_set_subject_name",[123,1687,1149],{"class":129},[123,1689,1559],{"class":1074},[123,1691,1641],{"class":139},[123,1693,296],{"class":129},[123,1695,1478],{"class":1081},[123,1697,1545],{"class":129},[123,1699,1700,1703,1705,1707,1709,1711,1713,1715,1717,1719,1722],{"class":125,"line":356},[123,1701,1702],{"class":940},"mbedtls_x509write_csr_pem",[123,1704,1149],{"class":129},[123,1706,1559],{"class":1074},[123,1708,1641],{"class":139},[123,1710,296],{"class":129},[123,1712,1498],{"class":1081},[123,1714,296],{"class":129},[123,1716,1524],{"class":1074},[123,1718,1149],{"class":129},[123,1720,1721],{"class":1081},"csr_pem",[123,1723,1610],{"class":129},[123,1725,1726,1729,1731,1733,1735],{"class":125,"line":361},[123,1727,1728],{"class":1081},"                          mbedtls_ctr_drbg_random",[123,1730,296],{"class":129},[123,1732,1620],{"class":1074},[123,1734,1623],{"class":139},[123,1736,1545],{"class":129},[88,1738,1739],{},"The private key goes straight into storage that is protected on the device, which on an ESP32 means encrypted NVS with flash encryption enabled, or the Digital Signature peripheral on chips that have it.",[88,1741,1742,1743,1746,1747,1750,1751,1143],{},"Then the device connects with the claim certificate and has a short conversation over MQTT. It subscribes to ",[120,1744,1745],{},"$aws/certificates/create-from-csr/json/accepted"," and the matching ",[120,1748,1749],{},"rejected"," topic before sending anything, then publishes the CSR to ",[120,1752,1753],{},"$aws/certificates/create-from-csr/json",[113,1755,1757],{"className":115,"code":1756,"language":117,"meta":118,"style":118},"{\n  \"certificateSigningRequest\": \"-----BEGIN CERTIFICATE REQUEST-----\\nMIIB...\"\n}\n",[120,1758,1759,1763,1787],{"__ignoreMap":118},[123,1760,1761],{"class":125,"line":126},[123,1762,130],{"class":129},[123,1764,1765,1767,1770,1772,1774,1776,1779,1782,1785],{"class":125,"line":133},[123,1766,136],{"class":129},[123,1768,1769],{"class":139},"certificateSigningRequest",[123,1771,143],{"class":129},[123,1773,146],{"class":129},[123,1775,149],{"class":129},[123,1777,1778],{"class":152},"-----BEGIN CERTIFICATE REQUEST-----",[123,1780,1781],{"class":949},"\\n",[123,1783,1784],{"class":152},"MIIB...",[123,1786,242],{"class":129},[123,1788,1789],{"class":125,"line":161},[123,1790,455],{"class":129},[88,1792,1793],{},"The accepted response carries the signed certificate and the ownership token. The token is valid for one hour, and AWS deletes the new certificate if it has not been registered by then, so the device should move straight on to the next step.",[113,1795,1797],{"className":115,"code":1796,"language":117,"meta":118,"style":118},"{\n  \"certificateId\": \"8f2c...\",\n  \"certificatePem\": \"-----BEGIN CERTIFICATE-----\\nMIIC...\",\n  \"certificateOwnershipToken\": \"eyJ2...\"\n}\n",[120,1798,1799,1803,1823,1848,1866],{"__ignoreMap":118},[123,1800,1801],{"class":125,"line":126},[123,1802,130],{"class":129},[123,1804,1805,1807,1810,1812,1814,1816,1819,1821],{"class":125,"line":133},[123,1806,136],{"class":129},[123,1808,1809],{"class":139},"certificateId",[123,1811,143],{"class":129},[123,1813,146],{"class":129},[123,1815,149],{"class":129},[123,1817,1818],{"class":152},"8f2c...",[123,1820,143],{"class":129},[123,1822,158],{"class":129},[123,1824,1825,1827,1830,1832,1834,1836,1839,1841,1844,1846],{"class":125,"line":161},[123,1826,136],{"class":129},[123,1828,1829],{"class":139},"certificatePem",[123,1831,143],{"class":129},[123,1833,146],{"class":129},[123,1835,149],{"class":129},[123,1837,1838],{"class":152},"-----BEGIN CERTIFICATE-----",[123,1840,1781],{"class":949},[123,1842,1843],{"class":152},"MIIC...",[123,1845,143],{"class":129},[123,1847,158],{"class":129},[123,1849,1850,1852,1855,1857,1859,1861,1864],{"class":125,"line":176},[123,1851,136],{"class":129},[123,1853,1854],{"class":139},"certificateOwnershipToken",[123,1856,143],{"class":129},[123,1858,146],{"class":129},[123,1860,149],{"class":129},[123,1862,1863],{"class":152},"eyJ2...",[123,1865,242],{"class":129},[123,1867,1868],{"class":125,"line":182},[123,1869,455],{"class":129},[88,1871,1872,1873,1876,1877,1879,1880,1883],{},"The device subscribes to the ",[120,1874,1875],{},"accepted"," and ",[120,1878,1749],{}," topics under ",[120,1881,1882],{},"$aws/provisioning-templates/factory-template/provision/json"," and publishes the token with its serial number to that same topic.",[113,1885,1887],{"className":115,"code":1886,"language":117,"meta":118,"style":118},"{\n  \"certificateOwnershipToken\": \"eyJ2...\",\n  \"parameters\": { \"SerialNumber\": \"a4cf12b39e04\" }\n}\n",[120,1888,1889,1893,1911,1940],{"__ignoreMap":118},[123,1890,1891],{"class":125,"line":126},[123,1892,130],{"class":129},[123,1894,1895,1897,1899,1901,1903,1905,1907,1909],{"class":125,"line":133},[123,1896,136],{"class":129},[123,1898,1854],{"class":139},[123,1900,143],{"class":129},[123,1902,146],{"class":129},[123,1904,149],{"class":129},[123,1906,1863],{"class":152},[123,1908,143],{"class":129},[123,1910,158],{"class":129},[123,1912,1913,1915,1917,1919,1921,1923,1925,1927,1929,1931,1933,1936,1938],{"class":125,"line":161},[123,1914,136],{"class":129},[123,1916,1225],{"class":139},[123,1918,143],{"class":129},[123,1920,146],{"class":129},[123,1922,503],{"class":129},[123,1924,149],{"class":129},[123,1926,496],{"class":139},[123,1928,143],{"class":129},[123,1930,146],{"class":129},[123,1932,149],{"class":129},[123,1934,1935],{"class":152},"a4cf12b39e04",[123,1937,143],{"class":129},[123,1939,552],{"class":129},[123,1941,1942],{"class":125,"line":176},[123,1943,455],{"class":129},[88,1945,1946],{},"If the hook approves the serial, the response names the new thing.",[113,1948,1950],{"className":115,"code":1949,"language":117,"meta":118,"style":118},"{\n  \"deviceConfiguration\": {},\n  \"thingName\": \"sensor-a4cf12b39e04\"\n}\n",[120,1951,1952,1956,1970,1988],{"__ignoreMap":118},[123,1953,1954],{"class":125,"line":126},[123,1955,130],{"class":129},[123,1957,1958,1960,1963,1965,1967],{"class":125,"line":133},[123,1959,136],{"class":129},[123,1961,1962],{"class":139},"deviceConfiguration",[123,1964,143],{"class":129},[123,1966,146],{"class":129},[123,1968,1969],{"class":129}," {},\n",[123,1971,1972,1974,1977,1979,1981,1983,1986],{"class":125,"line":161},[123,1973,136],{"class":129},[123,1975,1976],{"class":139},"thingName",[123,1978,143],{"class":129},[123,1980,146],{"class":129},[123,1982,149],{"class":129},[123,1984,1985],{"class":152},"sensor-a4cf12b39e04",[123,1987,242],{"class":129},[123,1989,1990],{"class":125,"line":176},[123,1991,455],{"class":129},[88,1993,1994,1995,1998,1999,2002],{},"At this point the device saves its certificate, disconnects and connects again with the new certificate, using the thing name as its MQTT client ID. Everything after that is normal operation. If you would rather not write the topic handling by hand, AWS publishes a small Fleet Provisioning library in C that builds and parses these exact topics and works with any MQTT client. Espressif's ",[120,1996,1997],{},"esp-aws-iot"," repository includes it together with a ",[120,2000,2001],{},"fleet_provisioning_with_csr"," example, which at the time of writing supports ESP-IDF 5.x and not yet 6.0.",[95,2004,2006],{"id":2005},"a-policy-that-fits-every-device","A policy that fits every device",[88,2008,2009],{},"Since the template attaches the same policy to the whole fleet, that policy uses variables so each device can only act as itself. Because the template also attaches the thing to the certificate, AWS can resolve the thing name at connection time.",[113,2011,2013],{"className":115,"code":2012,"language":117,"meta":118,"style":118},"{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:Connect\",\n      \"Resource\": \"arn:aws:iot:eu-central-1:123456789012:client/${iot:Connection.Thing.ThingName}\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\"iot:Publish\", \"iot:Receive\"],\n      \"Resource\": \"arn:aws:iot:eu-central-1:123456789012:topic/devices/${iot:Connection.Thing.ThingName}/*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:Subscribe\",\n      \"Resource\": \"arn:aws:iot:eu-central-1:123456789012:topicfilter/devices/${iot:Connection.Thing.ThingName}/*\"\n    }\n  ]\n}\n",[120,2014,2015,2019,2037,2049,2053,2071,2089,2106,2110,2114,2132,2160,2177,2181,2185,2203,2221,2238,2242,2246],{"__ignoreMap":118},[123,2016,2017],{"class":125,"line":126},[123,2018,130],{"class":129},[123,2020,2021,2023,2025,2027,2029,2031,2033,2035],{"class":125,"line":133},[123,2022,136],{"class":129},[123,2024,140],{"class":139},[123,2026,143],{"class":129},[123,2028,146],{"class":129},[123,2030,149],{"class":129},[123,2032,153],{"class":152},[123,2034,143],{"class":129},[123,2036,158],{"class":129},[123,2038,2039,2041,2043,2045,2047],{"class":125,"line":161},[123,2040,136],{"class":129},[123,2042,166],{"class":139},[123,2044,143],{"class":129},[123,2046,146],{"class":129},[123,2048,173],{"class":129},[123,2050,2051],{"class":125,"line":176},[123,2052,179],{"class":129},[123,2054,2055,2057,2059,2061,2063,2065,2067,2069],{"class":125,"line":182},[123,2056,185],{"class":129},[123,2058,188],{"class":139},[123,2060,143],{"class":129},[123,2062,146],{"class":129},[123,2064,149],{"class":129},[123,2066,197],{"class":152},[123,2068,143],{"class":129},[123,2070,158],{"class":129},[123,2072,2073,2075,2077,2079,2081,2083,2085,2087],{"class":125,"line":204},[123,2074,185],{"class":129},[123,2076,209],{"class":139},[123,2078,143],{"class":129},[123,2080,146],{"class":129},[123,2082,149],{"class":129},[123,2084,218],{"class":152},[123,2086,143],{"class":129},[123,2088,158],{"class":129},[123,2090,2091,2093,2095,2097,2099,2101,2104],{"class":125,"line":225},[123,2092,185],{"class":129},[123,2094,230],{"class":139},[123,2096,143],{"class":129},[123,2098,146],{"class":129},[123,2100,149],{"class":129},[123,2102,2103],{"class":152},"arn:aws:iot:eu-central-1:123456789012:client/${iot:Connection.Thing.ThingName}",[123,2105,242],{"class":129},[123,2107,2108],{"class":125,"line":245},[123,2109,248],{"class":129},[123,2111,2112],{"class":125,"line":251},[123,2113,179],{"class":129},[123,2115,2116,2118,2120,2122,2124,2126,2128,2130],{"class":125,"line":256},[123,2117,185],{"class":129},[123,2119,188],{"class":139},[123,2121,143],{"class":129},[123,2123,146],{"class":129},[123,2125,149],{"class":129},[123,2127,197],{"class":152},[123,2129,143],{"class":129},[123,2131,158],{"class":129},[123,2133,2134,2136,2138,2140,2142,2144,2146,2148,2150,2152,2154,2156,2158],{"class":125,"line":275},[123,2135,185],{"class":129},[123,2137,209],{"class":139},[123,2139,143],{"class":129},[123,2141,146],{"class":129},[123,2143,286],{"class":129},[123,2145,143],{"class":129},[123,2147,291],{"class":152},[123,2149,143],{"class":129},[123,2151,296],{"class":129},[123,2153,149],{"class":129},[123,2155,301],{"class":152},[123,2157,143],{"class":129},[123,2159,306],{"class":129},[123,2161,2162,2164,2166,2168,2170,2172,2175],{"class":125,"line":309},[123,2163,185],{"class":129},[123,2165,230],{"class":139},[123,2167,143],{"class":129},[123,2169,146],{"class":129},[123,2171,149],{"class":129},[123,2173,2174],{"class":152},"arn:aws:iot:eu-central-1:123456789012:topic/devices/${iot:Connection.Thing.ThingName}/*",[123,2176,242],{"class":129},[123,2178,2179],{"class":125,"line":322},[123,2180,248],{"class":129},[123,2182,2183],{"class":125,"line":335},[123,2184,179],{"class":129},[123,2186,2187,2189,2191,2193,2195,2197,2199,2201],{"class":125,"line":345},[123,2188,185],{"class":129},[123,2190,188],{"class":139},[123,2192,143],{"class":129},[123,2194,146],{"class":129},[123,2196,149],{"class":129},[123,2198,197],{"class":152},[123,2200,143],{"class":129},[123,2202,158],{"class":129},[123,2204,2205,2207,2209,2211,2213,2215,2217,2219],{"class":125,"line":351},[123,2206,185],{"class":129},[123,2208,209],{"class":139},[123,2210,143],{"class":129},[123,2212,146],{"class":129},[123,2214,149],{"class":129},[123,2216,393],{"class":152},[123,2218,143],{"class":129},[123,2220,158],{"class":129},[123,2222,2223,2225,2227,2229,2231,2233,2236],{"class":125,"line":356},[123,2224,185],{"class":129},[123,2226,230],{"class":139},[123,2228,143],{"class":129},[123,2230,146],{"class":129},[123,2232,149],{"class":129},[123,2234,2235],{"class":152},"arn:aws:iot:eu-central-1:123456789012:topicfilter/devices/${iot:Connection.Thing.ThingName}/*",[123,2237,242],{"class":129},[123,2239,2240],{"class":125,"line":361},[123,2241,443],{"class":129},[123,2243,2244],{"class":125,"line":380},[123,2245,449],{"class":129},[123,2247,2248],{"class":125,"line":400},[123,2249,455],{"class":129},[88,2251,2252],{},"AWS IoT works out the thing from the certificate the device presents, so a device can only ever act as the thing attached to its own certificate, and its client ID has to match that thing's name for the connection to be allowed.",[95,2254,2256],{"id":2255},"looking-after-the-golden-certificate","Looking after the golden certificate",[88,2258,2259],{},"The claim certificate is the most valuable secret in the whole setup, so I protect it the same way as the firmware itself. Secure Boot and flash encryption on the ESP32 make it much harder to read off a board. I also issue a new claim certificate for each production batch or each manufacturer, which keeps the damage small if one leaks and lets me deactivate an old one once every device from that batch has come online. Watching the provisioning numbers helps too. A sudden burst of requests at three in the morning, or a hook rejecting many unknown serials, is worth an alert.",[88,2261,2262],{},"One timing question is worth deciding early. Devices can provision during the end-of-line test, while they are still in the factory and on a known network, or later at the customer site. Provisioning in the factory gives you certainty that every shipped unit already has its identity, and lets you deactivate the claim certificate as soon as the batch is complete. Provisioning in the field keeps the factory process simpler and suits products that sit in a warehouse for months, at the cost of keeping the claim certificate active for longer.",[95,2264,2266],{"id":2265},"when-to-choose-something-else","When to choose something else",[88,2268,2269],{},"The golden certificate fits most small and medium production runs, and it is what I would suggest to the ThingsBoard user from the start of this post. ThingsBoard has the same idea built in, where a device sends the provisioning key and secret from its device profile on first connection and receives its own credentials back, so the pattern carries over outside AWS too. For larger volumes, or products where the security requirements are strict, secure elements such as Microchip's ATECC608B in its Trust&GO variant arrive from the chip vendor with a unique private key locked inside and a matching certificate, which removes the shared secret entirely. Microchip supplies a manifest of those certificates that can be registered with AWS IoT in bulk before the devices ship. Those options cost more per unit and add a supply chain dependency, so for many products the claim certificate with a careful hook is the sensible place to start.",[88,2271,2272],{},"If you are about to move a connected product from prototypes to its first production batch and want a second pair of eyes on the provisioning flow, I would be glad to help.",[2274,2275,2276],"style",{},"html pre.shiki code .sfVkF, html code.shiki .sfVkF{--shiki-default:#D6D3D1;--shiki-light:#57534E}html pre.shiki code .sDcrj, html code.shiki .sDcrj{--shiki-default:#F59E0B;--shiki-light:#B45309}html pre.shiki code .sP85D, html code.shiki .sP85D{--shiki-default:#FEF3C7;--shiki-light:#78350F}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .swbo2, html code.shiki .swbo2{--shiki-default:#FBBF24;--shiki-light:#A16207}html pre.shiki code .smnN6, html code.shiki .smnN6{--shiki-default:#FB923C;--shiki-light:#C2410C}html pre.shiki code .sxJ4f, html code.shiki .sxJ4f{--shiki-default:#D6D3D1;--shiki-default-font-weight:bold;--shiki-light:#57534E;--shiki-light-font-weight:bold}html pre.shiki code .sile1, html code.shiki .sile1{--shiki-default:#FCD34D;--shiki-light:#7C2D12}html pre.shiki code .s-Rrv, html code.shiki .s-Rrv{--shiki-default:#F59E0B;--shiki-default-font-weight:bold;--shiki-light:#B45309;--shiki-light-font-weight:bold}html pre.shiki code .sSq2B, html code.shiki .sSq2B{--shiki-default:#FDBA74;--shiki-default-font-weight:bold;--shiki-light:#9A3412;--shiki-light-font-weight:bold}",{"title":118,"searchDepth":133,"depth":133,"links":2278},[2279,2280,2281,2282,2283,2284,2285,2286],{"id":97,"depth":133,"text":98},{"id":107,"depth":133,"text":108},{"id":461,"depth":133,"text":462},{"id":1098,"depth":133,"text":1099},{"id":1443,"depth":133,"text":1444},{"id":2005,"depth":133,"text":2006},{"id":2255,"depth":133,"text":2256},{"id":2265,"depth":133,"text":2266},"md",{},{"title":20,"description":21},{"loc":18},[12,24,13,25,26],null,"s8ax0q7K_EqT4qi4XFe8wWOOgQFkqsVrO_zAMSYd2MM",{"id":2295,"title":2296,"author":83,"body":2297,"date":22,"description":4209,"extension":2287,"lang":4210,"meta":4211,"navigation":1000,"path":18,"readingTime":4212,"seo":4213,"stem":19,"tags":4214,"updated":2292,"__hash__":4215},"blogPl/blog/one-golden-certificate-for-the-whole-production-line.md","Jeden złoty certyfikat na całą linię produkcyjną",{"type":85,"value":2298,"toc":4199},[2299,2302,2305,2309,2312,2315,2319,2322,2594,2597,2601,2604,3018,3027,3145,3153,3157,3160,3166,3438,3441,3445,3448,3692,3695,3706,3738,3741,3809,3821,3879,3882,3922,3931,3935,3938,4174,4177,4181,4184,4187,4191,4194,4197],[88,2300,2301],{},"Gdzieś między dziesiątym prototypem a pierwszą partią produkcyjną każdy projekt urządzenia IoT natrafia na to samo pytanie. Na biurku certyfikat trafiał na każdą płytkę najprostszą drogą: ktoś wygenerował go w konsoli, skopiował pliki do projektu i wgrał je razem z firmware. Przy dziesięciu płytkach to działa, przy tysiącu się sypie, bo fabryka musi teraz wykonać osobny krok dla każdej sztuki, mieć bezpieczny sposób na przekazanie unikalnych sekretów i prowadzić ewidencję, który certyfikat trafił na którą płytkę. Większość producentów kontraktowych jest przygotowana na wgrywanie jednego, identycznego obrazu na wszystkie urządzenia. Wstrzyknięcie do każdej sztuki osobnego klucza prywatnego oznacza dodatkowy koszt, czas i bezpieczne przekazanie sekretów, a tego wielu z nich wolałoby uniknąć. Niedawno trafiłem na Reddicie na dokładnie takie pytanie od kogoś, kto korzysta z ThingsBoard, a odpowiedzi były dość mizerne, więc opiszę, jak ja to rozwiązuję.",[88,2303,2304],{},"W takiej sytuacji sięgam po podejście nazywane często „złotym certyfikatem”. Każde urządzenie wyjeżdża z fabryki z tym samym firmware i tym samym wspólnym certyfikatem, a ten certyfikat pozwala na dokładnie jedną rzecz: przy pierwszym połączeniu urządzenie może nim poprosić o własną, docelową tożsamość. AWS IoT Core obsługuje ten schemat wprost pod nazwą fleet provisioning by claim. W dalszej części wpisu pokazuję, jak to u siebie konfiguruję.",[95,2306,2308],{"id":2307},"jak-przebiega-wymiana","Jak przebiega wymiana",[88,2310,2311],{},"Wspólny certyfikat to tak zwany claim certificate, dalej nazywam go po prostu certyfikatem claim. Urządzenie łączy się przy jego użyciu z AWS IoT Core, lokalnie generuje nową parę kluczy i wysyła przez MQTT żądanie podpisania certyfikatu (CSR). AWS je podpisuje i odsyła nowy certyfikat razem z krótkim tokenem własności. Następnie urządzenie wywołuje provisioning template, przekazując ten token i kilka parametrów, na przykład swój numer seryjny. Szablon tworzy w rejestrze Thing, aktywuje nowy certyfikat, przypina do niego właściwą politykę i zwraca nazwę Thinga. Wtedy urządzenie się rozłącza, w codziennej pracy przestaje używać certyfikatu claim i łączy się ponownie, już z własną tożsamością.",[88,2313,2314],{},"Klucz prywatny do certyfikatu urządzenia powstaje na samym urządzeniu i nigdy go nie opuszcza. AWS oferuje też wariant, w którym klucz generuje chmura i wysyła go do urządzenia. Unikam go, bo na ESP32 ścieżka przez CSR kosztuje niewiele więcej, a znika dzięki niej jedyny moment, w którym klucz prywatny jest przesyłany.",[95,2316,2318],{"id":2317},"certyfikat-claim-który-prawie-nic-nie-może","Certyfikat claim, który prawie nic nie może",[88,2320,2321],{},"Certyfikat claim trafi na każde urządzenie, które wyślesz, więc od pierwszego dnia zakładam, że ktoś go w końcu wyciągnie. Jego polityka powinna pozwalać na połączenie i korzystanie z topików provisioningu, a poza tym na absolutnie nic.",[113,2323,2324],{"className":115,"code":116,"language":117,"meta":118,"style":118},[120,2325,2326,2330,2348,2360,2364,2382,2400,2416,2420,2424,2442,2470,2482,2492,2500,2504,2508,2512,2530,2548,2560,2570,2578,2582,2586,2590],{"__ignoreMap":118},[123,2327,2328],{"class":125,"line":126},[123,2329,130],{"class":129},[123,2331,2332,2334,2336,2338,2340,2342,2344,2346],{"class":125,"line":133},[123,2333,136],{"class":129},[123,2335,140],{"class":139},[123,2337,143],{"class":129},[123,2339,146],{"class":129},[123,2341,149],{"class":129},[123,2343,153],{"class":152},[123,2345,143],{"class":129},[123,2347,158],{"class":129},[123,2349,2350,2352,2354,2356,2358],{"class":125,"line":161},[123,2351,136],{"class":129},[123,2353,166],{"class":139},[123,2355,143],{"class":129},[123,2357,146],{"class":129},[123,2359,173],{"class":129},[123,2361,2362],{"class":125,"line":176},[123,2363,179],{"class":129},[123,2365,2366,2368,2370,2372,2374,2376,2378,2380],{"class":125,"line":182},[123,2367,185],{"class":129},[123,2369,188],{"class":139},[123,2371,143],{"class":129},[123,2373,146],{"class":129},[123,2375,149],{"class":129},[123,2377,197],{"class":152},[123,2379,143],{"class":129},[123,2381,158],{"class":129},[123,2383,2384,2386,2388,2390,2392,2394,2396,2398],{"class":125,"line":204},[123,2385,185],{"class":129},[123,2387,209],{"class":139},[123,2389,143],{"class":129},[123,2391,146],{"class":129},[123,2393,149],{"class":129},[123,2395,218],{"class":152},[123,2397,143],{"class":129},[123,2399,158],{"class":129},[123,2401,2402,2404,2406,2408,2410,2412,2414],{"class":125,"line":225},[123,2403,185],{"class":129},[123,2405,230],{"class":139},[123,2407,143],{"class":129},[123,2409,146],{"class":129},[123,2411,149],{"class":129},[123,2413,239],{"class":152},[123,2415,242],{"class":129},[123,2417,2418],{"class":125,"line":245},[123,2419,248],{"class":129},[123,2421,2422],{"class":125,"line":251},[123,2423,179],{"class":129},[123,2425,2426,2428,2430,2432,2434,2436,2438,2440],{"class":125,"line":256},[123,2427,185],{"class":129},[123,2429,188],{"class":139},[123,2431,143],{"class":129},[123,2433,146],{"class":129},[123,2435,149],{"class":129},[123,2437,197],{"class":152},[123,2439,143],{"class":129},[123,2441,158],{"class":129},[123,2443,2444,2446,2448,2450,2452,2454,2456,2458,2460,2462,2464,2466,2468],{"class":125,"line":275},[123,2445,185],{"class":129},[123,2447,209],{"class":139},[123,2449,143],{"class":129},[123,2451,146],{"class":129},[123,2453,286],{"class":129},[123,2455,143],{"class":129},[123,2457,291],{"class":152},[123,2459,143],{"class":129},[123,2461,296],{"class":129},[123,2463,149],{"class":129},[123,2465,301],{"class":152},[123,2467,143],{"class":129},[123,2469,306],{"class":129},[123,2471,2472,2474,2476,2478,2480],{"class":125,"line":309},[123,2473,185],{"class":129},[123,2475,230],{"class":139},[123,2477,143],{"class":129},[123,2479,146],{"class":129},[123,2481,173],{"class":129},[123,2483,2484,2486,2488,2490],{"class":125,"line":322},[123,2485,325],{"class":129},[123,2487,328],{"class":152},[123,2489,143],{"class":129},[123,2491,158],{"class":129},[123,2493,2494,2496,2498],{"class":125,"line":335},[123,2495,325],{"class":129},[123,2497,340],{"class":152},[123,2499,242],{"class":129},[123,2501,2502],{"class":125,"line":345},[123,2503,348],{"class":129},[123,2505,2506],{"class":125,"line":351},[123,2507,248],{"class":129},[123,2509,2510],{"class":125,"line":356},[123,2511,179],{"class":129},[123,2513,2514,2516,2518,2520,2522,2524,2526,2528],{"class":125,"line":361},[123,2515,185],{"class":129},[123,2517,188],{"class":139},[123,2519,143],{"class":129},[123,2521,146],{"class":129},[123,2523,149],{"class":129},[123,2525,197],{"class":152},[123,2527,143],{"class":129},[123,2529,158],{"class":129},[123,2531,2532,2534,2536,2538,2540,2542,2544,2546],{"class":125,"line":380},[123,2533,185],{"class":129},[123,2535,209],{"class":139},[123,2537,143],{"class":129},[123,2539,146],{"class":129},[123,2541,149],{"class":129},[123,2543,393],{"class":152},[123,2545,143],{"class":129},[123,2547,158],{"class":129},[123,2549,2550,2552,2554,2556,2558],{"class":125,"line":400},[123,2551,185],{"class":129},[123,2553,230],{"class":139},[123,2555,143],{"class":129},[123,2557,146],{"class":129},[123,2559,173],{"class":129},[123,2561,2562,2564,2566,2568],{"class":125,"line":413},[123,2563,325],{"class":129},[123,2565,418],{"class":152},[123,2567,143],{"class":129},[123,2569,158],{"class":129},[123,2571,2572,2574,2576],{"class":125,"line":425},[123,2573,325],{"class":129},[123,2575,430],{"class":152},[123,2577,242],{"class":129},[123,2579,2580],{"class":125,"line":435},[123,2581,348],{"class":129},[123,2583,2584],{"class":125,"line":440},[123,2585,443],{"class":129},[123,2587,2588],{"class":125,"line":446},[123,2589,449],{"class":129},[123,2591,2592],{"class":125,"line":452},[123,2593,455],{"class":129},[88,2595,2596],{},"Urządzenie, które ma tylko ten certyfikat, nie może odczytywać ani publikować telemetrii, nie ma dostępu do shadowów i nie widzi żadnych innych urządzeń. W najgorszym razie poprosi o nową tożsamość, a w kolejnych sekcjach pokazuję, jak sprawić, żeby takie żądanie przechodziło wyłącznie dla numerów seryjnych, które naprawdę zeszły z twojej linii.",[95,2598,2600],{"id":2599},"szablon-provisioningu","Szablon provisioningu",[88,2602,2603],{},"Szablon opisuje, co AWS ma utworzyć dla każdego nowego urządzenia. Mój zwykle przyjmuje jako parametr numer seryjny, od niego nazywa Thinga i przypina jedną wspólną politykę dla urządzeń.",[113,2605,2606],{"className":115,"code":468,"language":117,"meta":118,"style":118},[120,2607,2608,2612,2624,2652,2680,2684,2696,2708,2726,2738,2794,2834,2838,2842,2854,2872,2884,2912,2928,2932,2936,2948,2966,2994,2998,3002,3014],{"__ignoreMap":118},[123,2609,2610],{"class":125,"line":126},[123,2611,130],{"class":129},[123,2613,2614,2616,2618,2620,2622],{"class":125,"line":133},[123,2615,136],{"class":129},[123,2617,481],{"class":139},[123,2619,143],{"class":129},[123,2621,146],{"class":129},[123,2623,488],{"class":129},[123,2625,2626,2628,2630,2632,2634,2636,2638,2640,2642,2644,2646,2648,2650],{"class":125,"line":161},[123,2627,493],{"class":129},[123,2629,496],{"class":139},[123,2631,143],{"class":129},[123,2633,146],{"class":129},[123,2635,503],{"class":129},[123,2637,149],{"class":129},[123,2639,508],{"class":139},[123,2641,143],{"class":129},[123,2643,146],{"class":129},[123,2645,149],{"class":129},[123,2647,517],{"class":152},[123,2649,143],{"class":129},[123,2651,522],{"class":129},[123,2653,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2674,2676,2678],{"class":125,"line":176},[123,2655,493],{"class":129},[123,2657,529],{"class":139},[123,2659,143],{"class":129},[123,2661,146],{"class":129},[123,2663,503],{"class":129},[123,2665,149],{"class":129},[123,2667,508],{"class":139},[123,2669,143],{"class":129},[123,2671,146],{"class":129},[123,2673,149],{"class":129},[123,2675,517],{"class":152},[123,2677,143],{"class":129},[123,2679,552],{"class":129},[123,2681,2682],{"class":125,"line":182},[123,2683,557],{"class":129},[123,2685,2686,2688,2690,2692,2694],{"class":125,"line":204},[123,2687,136],{"class":129},[123,2689,564],{"class":139},[123,2691,143],{"class":129},[123,2693,146],{"class":129},[123,2695,488],{"class":129},[123,2697,2698,2700,2702,2704,2706],{"class":125,"line":225},[123,2699,493],{"class":129},[123,2701,577],{"class":139},[123,2703,143],{"class":129},[123,2705,146],{"class":129},[123,2707,488],{"class":129},[123,2709,2710,2712,2714,2716,2718,2720,2722,2724],{"class":125,"line":245},[123,2711,185],{"class":129},[123,2713,508],{"class":139},[123,2715,143],{"class":129},[123,2717,146],{"class":129},[123,2719,149],{"class":129},[123,2721,598],{"class":152},[123,2723,143],{"class":129},[123,2725,158],{"class":129},[123,2727,2728,2730,2732,2734,2736],{"class":125,"line":251},[123,2729,185],{"class":129},[123,2731,609],{"class":139},[123,2733,143],{"class":129},[123,2735,146],{"class":129},[123,2737,488],{"class":129},[123,2739,2740,2742,2744,2746,2748,2750,2752,2754,2756,2758,2760,2762,2764,2766,2768,2770,2772,2774,2776,2778,2780,2782,2784,2786,2788,2790,2792],{"class":125,"line":256},[123,2741,325],{"class":129},[123,2743,622],{"class":139},[123,2745,143],{"class":129},[123,2747,146],{"class":129},[123,2749,503],{"class":129},[123,2751,149],{"class":129},[123,2753,633],{"class":139},[123,2755,143],{"class":129},[123,2757,146],{"class":129},[123,2759,286],{"class":129},[123,2761,642],{"class":129},[123,2763,296],{"class":129},[123,2765,286],{"class":129},[123,2767,143],{"class":129},[123,2769,651],{"class":152},[123,2771,143],{"class":129},[123,2773,296],{"class":129},[123,2775,503],{"class":129},[123,2777,149],{"class":129},[123,2779,662],{"class":139},[123,2781,143],{"class":129},[123,2783,146],{"class":129},[123,2785,149],{"class":129},[123,2787,496],{"class":152},[123,2789,143],{"class":129},[123,2791,675],{"class":129},[123,2793,522],{"class":129},[123,2795,2796,2798,2800,2802,2804,2806,2808,2810,2812,2814,2816,2818,2820,2822,2824,2826,2828,2830,2832],{"class":125,"line":275},[123,2797,325],{"class":129},[123,2799,684],{"class":139},[123,2801,143],{"class":129},[123,2803,146],{"class":129},[123,2805,503],{"class":129},[123,2807,149],{"class":129},[123,2809,695],{"class":139},[123,2811,143],{"class":129},[123,2813,146],{"class":129},[123,2815,503],{"class":129},[123,2817,149],{"class":129},[123,2819,662],{"class":139},[123,2821,143],{"class":129},[123,2823,146],{"class":129},[123,2825,149],{"class":129},[123,2827,496],{"class":152},[123,2829,143],{"class":129},[123,2831,718],{"class":129},[123,2833,552],{"class":129},[123,2835,2836],{"class":125,"line":309},[123,2837,725],{"class":129},[123,2839,2840],{"class":125,"line":322},[123,2841,248],{"class":129},[123,2843,2844,2846,2848,2850,2852],{"class":125,"line":335},[123,2845,493],{"class":129},[123,2847,736],{"class":139},[123,2849,143],{"class":129},[123,2851,146],{"class":129},[123,2853,488],{"class":129},[123,2855,2856,2858,2860,2862,2864,2866,2868,2870],{"class":125,"line":345},[123,2857,185],{"class":129},[123,2859,508],{"class":139},[123,2861,143],{"class":129},[123,2863,146],{"class":129},[123,2865,149],{"class":129},[123,2867,757],{"class":152},[123,2869,143],{"class":129},[123,2871,158],{"class":129},[123,2873,2874,2876,2878,2880,2882],{"class":125,"line":351},[123,2875,185],{"class":129},[123,2877,609],{"class":139},[123,2879,143],{"class":129},[123,2881,146],{"class":129},[123,2883,488],{"class":129},[123,2885,2886,2888,2890,2892,2894,2896,2898,2900,2902,2904,2906,2908,2910],{"class":125,"line":356},[123,2887,325],{"class":129},[123,2889,780],{"class":139},[123,2891,143],{"class":129},[123,2893,146],{"class":129},[123,2895,503],{"class":129},[123,2897,149],{"class":129},[123,2899,662],{"class":139},[123,2901,143],{"class":129},[123,2903,146],{"class":129},[123,2905,149],{"class":129},[123,2907,529],{"class":152},[123,2909,143],{"class":129},[123,2911,522],{"class":129},[123,2913,2914,2916,2918,2920,2922,2924,2926],{"class":125,"line":361},[123,2915,325],{"class":129},[123,2917,809],{"class":139},[123,2919,143],{"class":129},[123,2921,146],{"class":129},[123,2923,149],{"class":129},[123,2925,818],{"class":152},[123,2927,242],{"class":129},[123,2929,2930],{"class":125,"line":380},[123,2931,725],{"class":129},[123,2933,2934],{"class":125,"line":400},[123,2935,248],{"class":129},[123,2937,2938,2940,2942,2944,2946],{"class":125,"line":413},[123,2939,493],{"class":129},[123,2941,835],{"class":139},[123,2943,143],{"class":129},[123,2945,146],{"class":129},[123,2947,488],{"class":129},[123,2949,2950,2952,2954,2956,2958,2960,2962,2964],{"class":125,"line":425},[123,2951,185],{"class":129},[123,2953,508],{"class":139},[123,2955,143],{"class":129},[123,2957,146],{"class":129},[123,2959,149],{"class":129},[123,2961,856],{"class":152},[123,2963,143],{"class":129},[123,2965,158],{"class":129},[123,2967,2968,2970,2972,2974,2976,2978,2980,2982,2984,2986,2988,2990,2992],{"class":125,"line":435},[123,2969,185],{"class":129},[123,2971,609],{"class":139},[123,2973,143],{"class":129},[123,2975,146],{"class":129},[123,2977,503],{"class":129},[123,2979,149],{"class":129},[123,2981,877],{"class":139},[123,2983,143],{"class":129},[123,2985,146],{"class":129},[123,2987,149],{"class":129},[123,2989,886],{"class":152},[123,2991,143],{"class":129},[123,2993,552],{"class":129},[123,2995,2996],{"class":125,"line":440},[123,2997,443],{"class":129},[123,2999,3000],{"class":125,"line":446},[123,3001,557],{"class":129},[123,3003,3004,3006,3008,3010,3012],{"class":125,"line":452},[123,3005,136],{"class":129},[123,3007,905],{"class":139},[123,3009,143],{"class":129},[123,3011,146],{"class":129},[123,3013,912],{"class":129},[123,3015,3016],{"class":125,"line":915},[123,3017,455],{"class":129},[88,3019,3020,3021,3023,3024,3026],{},"Do utworzenia tych zasobów szablon potrzebuje roli IAM, którą AWS IoT może przyjąć. Wystarczy rola z relacją zaufania do ",[120,3022,923],{}," i przypiętą zarządzaną polityką ",[120,3025,927],{},". Utworzenie szablonu i certyfikatu claim oraz przypięcie polityki claim to potem trzy polecenia.",[113,3028,3029],{"className":931,"code":932,"language":933,"meta":118,"style":118},[120,3030,3031,3041,3049,3057,3065,3073,3077,3081,3093,3101,3109,3119,3123],{"__ignoreMap":118},[123,3032,3033,3035,3037,3039],{"class":125,"line":126},[123,3034,24],{"class":940},[123,3036,943],{"class":152},[123,3038,946],{"class":152},[123,3040,950],{"class":949},[123,3042,3043,3045,3047],{"class":125,"line":133},[123,3044,955],{"class":152},[123,3046,958],{"class":152},[123,3048,950],{"class":949},[123,3050,3051,3053,3055],{"class":125,"line":161},[123,3052,965],{"class":152},[123,3054,968],{"class":152},[123,3056,950],{"class":949},[123,3058,3059,3061,3063],{"class":125,"line":176},[123,3060,975],{"class":152},[123,3062,978],{"class":152},[123,3064,950],{"class":949},[123,3066,3067,3069,3071],{"class":125,"line":182},[123,3068,985],{"class":152},[123,3070,988],{"class":152},[123,3072,950],{"class":949},[123,3074,3075],{"class":125,"line":204},[123,3076,995],{"class":152},[123,3078,3079],{"class":125,"line":225},[123,3080,1001],{"emptyLinePlaceholder":1000},[123,3082,3083,3085,3087,3089,3091],{"class":125,"line":245},[123,3084,24],{"class":940},[123,3086,943],{"class":152},[123,3088,1010],{"class":152},[123,3090,1013],{"class":152},[123,3092,950],{"class":949},[123,3094,3095,3097,3099],{"class":125,"line":251},[123,3096,1020],{"class":152},[123,3098,1023],{"class":152},[123,3100,950],{"class":949},[123,3102,3103,3105,3107],{"class":125,"line":256},[123,3104,1030],{"class":152},[123,3106,1033],{"class":152},[123,3108,950],{"class":949},[123,3110,3111,3113,3115,3117],{"class":125,"line":275},[123,3112,1040],{"class":152},[123,3114,1043],{"class":152},[123,3116,1046],{"class":152},[123,3118,1049],{"class":152},[123,3120,3121],{"class":125,"line":309},[123,3122,1001],{"emptyLinePlaceholder":1000},[123,3124,3125,3127,3129,3131,3133,3135,3137,3139,3141,3143],{"class":125,"line":322},[123,3126,24],{"class":940},[123,3128,943],{"class":152},[123,3130,1062],{"class":152},[123,3132,1065],{"class":152},[123,3134,1068],{"class":152},[123,3136,1071],{"class":152},[123,3138,1075],{"class":1074},[123,3140,1078],{"class":152},[123,3142,1082],{"class":1081},[123,3144,1085],{"class":1074},[88,3146,3147,3148,3150,3151,1143],{},"Funkcja Lambda podpięta jako hook potrzebuje jeszcze polityki zasobu, która pozwala ",[120,3149,923],{}," ją wywołać. Załatwia to ",[120,3152,1094],{},[95,3154,3156],{"id":3155},"tylko-te-numery-seryjne-które-naprawdę-wyprodukowałeś","Tylko te numery seryjne, które naprawdę wyprodukowałeś",[88,3158,3159],{},"Bez żadnej weryfikacji każdy, kto ma certyfikat claim, mógłby wymyślać numery seryjne i rejestrować dowolnie wiele fałszywych urządzeń. Tę lukę zamyka pre-provisioning hook. Zanim AWS cokolwiek utworzy, wywołuje funkcję Lambda z parametrami przesłanymi przez urządzenie i idzie dalej tylko wtedy, gdy funkcja się zgodzi.",[88,3161,3162,3163,3165],{},"Po stronie fabryki roboty jest na szczęście niewiele. Test końca linii i tak odczytuje z każdej płytki coś unikalnego, a na ESP32 jako numer seryjny świetnie sprawdza się adres MAC zapisany fabrycznie w eFuse. Stanowisko testowe wpisuje każdy numer seryjny do tabeli DynamoDB, gdy płytka przechodzi test, z flagą ",[120,3164,1108],{}," ustawioną na false. Hook akceptuje potem numer seryjny tylko raz i tylko wtedy, gdy jest on w tej tabeli. AWS daje hookowi pięć sekund na odpowiedź, a jeśli trwa to dłużej, odrzuca żądanie. Pojedynczy zapis do DynamoDB mieści się w tym limicie z dużym zapasem.",[113,3167,3168],{"className":1112,"code":1113,"language":1114,"meta":118,"style":118},[120,3169,3170,3176,3180,3214,3218,3222,3238,3278,3284,3294,3314,3328,3342,3374,3378,3402,3420],{"__ignoreMap":118},[123,3171,3172,3174],{"class":125,"line":126},[123,3173,1122],{"class":1121},[123,3175,1125],{"class":1081},[123,3177,3178],{"class":125,"line":133},[123,3179,1001],{"emptyLinePlaceholder":1000},[123,3181,3182,3184,3186,3188,3190,3192,3194,3196,3198,3200,3202,3204,3206,3208,3210,3212],{"class":125,"line":161},[123,3183,1134],{"class":1081},[123,3185,1137],{"class":1074},[123,3187,1140],{"class":1081},[123,3189,1143],{"class":129},[123,3191,1146],{"class":940},[123,3193,1149],{"class":129},[123,3195,143],{"class":129},[123,3197,1154],{"class":152},[123,3199,143],{"class":129},[123,3201,1159],{"class":129},[123,3203,1162],{"class":940},[123,3205,1149],{"class":129},[123,3207,143],{"class":129},[123,3209,1169],{"class":152},[123,3211,143],{"class":129},[123,3213,1174],{"class":129},[123,3215,3216],{"class":125,"line":176},[123,3217,1001],{"emptyLinePlaceholder":1000},[123,3219,3220],{"class":125,"line":182},[123,3221,1001],{"emptyLinePlaceholder":1000},[123,3223,3224,3226,3228,3230,3232,3234,3236],{"class":125,"line":204},[123,3225,1187],{"class":1121},[123,3227,1190],{"class":940},[123,3229,1149],{"class":129},[123,3231,1195],{"class":139},[123,3233,296],{"class":129},[123,3235,1200],{"class":139},[123,3237,1203],{"class":129},[123,3239,3240,3242,3244,3246,3248,3250,3252,3254,3256,3258,3260,3262,3264,3266,3268,3270,3272,3274,3276],{"class":125,"line":225},[123,3241,1208],{"class":1081},[123,3243,1137],{"class":1074},[123,3245,1213],{"class":1081},[123,3247,1143],{"class":129},[123,3249,1218],{"class":940},[123,3251,1149],{"class":129},[123,3253,143],{"class":129},[123,3255,1225],{"class":152},[123,3257,143],{"class":129},[123,3259,296],{"class":129},[123,3261,1232],{"class":129},[123,3263,1218],{"class":940},[123,3265,1149],{"class":129},[123,3267,143],{"class":129},[123,3269,496],{"class":152},[123,3271,143],{"class":129},[123,3273,296],{"class":129},[123,3275,1247],{"class":129},[123,3277,1174],{"class":129},[123,3279,3280,3282],{"class":125,"line":245},[123,3281,1254],{"class":1121},[123,3283,1257],{"class":129},[123,3285,3286,3288,3290,3292],{"class":125,"line":251},[123,3287,1262],{"class":1081},[123,3289,1143],{"class":129},[123,3291,1267],{"class":940},[123,3293,1270],{"class":129},[123,3295,3296,3298,3300,3302,3304,3306,3308,3310,3312],{"class":125,"line":256},[123,3297,1275],{"class":139},[123,3299,1137],{"class":1074},[123,3301,1280],{"class":129},[123,3303,143],{"class":129},[123,3305,695],{"class":152},[123,3307,143],{"class":129},[123,3309,146],{"class":129},[123,3311,1291],{"class":940},[123,3313,1294],{"class":129},[123,3315,3316,3318,3320,3322,3324,3326],{"class":125,"line":275},[123,3317,1299],{"class":139},[123,3319,1137],{"class":1074},[123,3321,143],{"class":129},[123,3323,1306],{"class":152},[123,3325,143],{"class":129},[123,3327,158],{"class":129},[123,3329,3330,3332,3334,3336,3338,3340],{"class":125,"line":309},[123,3331,1315],{"class":139},[123,3333,1137],{"class":1074},[123,3335,143],{"class":129},[123,3337,1322],{"class":152},[123,3339,143],{"class":129},[123,3341,158],{"class":129},[123,3343,3344,3346,3348,3350,3352,3354,3356,3358,3360,3362,3364,3366,3368,3370,3372],{"class":125,"line":322},[123,3345,1331],{"class":139},[123,3347,1137],{"class":1074},[123,3349,1280],{"class":129},[123,3351,143],{"class":129},[123,3353,1340],{"class":152},[123,3355,143],{"class":129},[123,3357,146],{"class":129},[123,3359,1347],{"class":949},[123,3361,296],{"class":129},[123,3363,149],{"class":129},[123,3365,1354],{"class":152},[123,3367,143],{"class":129},[123,3369,146],{"class":129},[123,3371,1361],{"class":949},[123,3373,1294],{"class":129},[123,3375,3376],{"class":125,"line":335},[123,3377,1368],{"class":129},[123,3379,3380,3382,3384,3386,3388,3390,3392,3394,3396,3398,3400],{"class":125,"line":345},[123,3381,1373],{"class":1121},[123,3383,1376],{"class":1081},[123,3385,1143],{"class":129},[123,3387,1381],{"class":1081},[123,3389,1143],{"class":129},[123,3391,1386],{"class":1081},[123,3393,1143],{"class":129},[123,3395,1391],{"class":1081},[123,3397,1143],{"class":129},[123,3399,1396],{"class":1081},[123,3401,1257],{"class":129},[123,3403,3404,3406,3408,3410,3412,3414,3416,3418],{"class":125,"line":351},[123,3405,1403],{"class":1121},[123,3407,503],{"class":129},[123,3409,143],{"class":129},[123,3411,1410],{"class":152},[123,3413,143],{"class":129},[123,3415,146],{"class":129},[123,3417,1361],{"class":949},[123,3419,455],{"class":129},[123,3421,3422,3424,3426,3428,3430,3432,3434,3436],{"class":125,"line":356},[123,3423,1423],{"class":1121},[123,3425,503],{"class":129},[123,3427,143],{"class":129},[123,3429,1410],{"class":152},[123,3431,143],{"class":129},[123,3433,146],{"class":129},[123,3435,1347],{"class":949},[123,3437,455],{"class":129},[88,3439,3440],{},"Jednorazowość wymaga furtki awaryjnej. Jeśli urządzenie przejdzie przez hook, a potem straci zasilanie, zanim zapisze nowy certyfikat, przy następnej próbie zostanie odrzucone. Support musi więc mieć prosty sposób, żeby dezaktywować osierocony certyfikat przypięty do tego Thinga i wyzerować flagę dla jego numeru seryjnego. To okno pozostaje małe, jeśli zapiszesz klucz prywatny jeszcze przed wysłaniem CSR, a certyfikat od razu, gdy tylko przyjdzie.",[95,3442,3444],{"id":3443},"co-robi-urządzenie-przy-pierwszym-uruchomieniu","Co robi urządzenie przy pierwszym uruchomieniu",[88,3446,3447],{},"Pierwszym krokiem na urządzeniu jest wygenerowanie pary kluczy i CSR. Poniższy kod jest napisany pod ESP-IDF 5.x, w którym jest mbedTLS 3.6, i zakłada, że generator liczb losowych CTR-DRBG został już zainicjowany. ESP-IDF 6.0 przeszedł na mbedTLS 4, gdzie klucze generuje się przez PSA crypto API, więc w tej wersji te wywołania trzeba przeportować. Używam klucza EC na krzywej P-256, który na mikrokontrolerze jest mniejszy i szybszy niż RSA.",[113,3449,3450],{"className":1450,"code":1451,"language":1452,"meta":118,"style":118},[120,3451,3452,3458,3464,3476,3490,3494,3524,3528,3540,3560,3578,3590,3594,3606,3624,3640,3656,3680],{"__ignoreMap":118},[123,3453,3454,3456],{"class":125,"line":126},[123,3455,1459],{"class":1081},[123,3457,1462],{"class":129},[123,3459,3460,3462],{"class":125,"line":133},[123,3461,1467],{"class":1081},[123,3463,1462],{"class":129},[123,3465,3466,3468,3470,3472,3474],{"class":125,"line":161},[123,3467,1475],{"class":1474},[123,3469,1478],{"class":1081},[123,3471,1481],{"class":129},[123,3473,1484],{"class":949},[123,3475,1487],{"class":129},[123,3477,3478,3480,3482,3484,3486,3488],{"class":125,"line":176},[123,3479,1492],{"class":1474},[123,3481,1495],{"class":1474},[123,3483,1498],{"class":1081},[123,3485,1481],{"class":129},[123,3487,1503],{"class":949},[123,3489,1487],{"class":129},[123,3491,3492],{"class":125,"line":182},[123,3493,1001],{"emptyLinePlaceholder":1000},[123,3495,3496,3498,3500,3502,3504,3506,3508,3510,3512,3514,3516,3518,3520,3522],{"class":125,"line":204},[123,3497,1514],{"class":940},[123,3499,1149],{"class":129},[123,3501,1519],{"class":1081},[123,3503,296],{"class":129},[123,3505,1524],{"class":1074},[123,3507,1149],{"class":129},[123,3509,1519],{"class":1081},[123,3511,1531],{"class":129},[123,3513,149],{"class":129},[123,3515,1536],{"class":152},[123,3517,143],{"class":129},[123,3519,296],{"class":129},[123,3521,1291],{"class":1081},[123,3523,1545],{"class":129},[123,3525,3526],{"class":125,"line":225},[123,3527,1001],{"emptyLinePlaceholder":1000},[123,3529,3530,3532,3534,3536,3538],{"class":125,"line":245},[123,3531,1554],{"class":940},[123,3533,1149],{"class":129},[123,3535,1559],{"class":1074},[123,3537,1562],{"class":139},[123,3539,1545],{"class":129},[123,3541,3542,3544,3546,3548,3550,3552,3554,3556,3558],{"class":125,"line":251},[123,3543,1569],{"class":940},[123,3545,1149],{"class":129},[123,3547,1559],{"class":1074},[123,3549,1562],{"class":139},[123,3551,296],{"class":129},[123,3553,1580],{"class":940},[123,3555,1149],{"class":129},[123,3557,1585],{"class":1081},[123,3559,1588],{"class":129},[123,3561,3562,3564,3566,3568,3570,3572,3574,3576],{"class":125,"line":256},[123,3563,1593],{"class":940},[123,3565,1149],{"class":129},[123,3567,1598],{"class":1081},[123,3569,296],{"class":129},[123,3571,1603],{"class":940},[123,3573,1149],{"class":129},[123,3575,1562],{"class":1081},[123,3577,1610],{"class":129},[123,3579,3580,3582,3584,3586,3588],{"class":125,"line":275},[123,3581,1615],{"class":1081},[123,3583,296],{"class":129},[123,3585,1620],{"class":1074},[123,3587,1623],{"class":139},[123,3589,1545],{"class":129},[123,3591,3592],{"class":125,"line":309},[123,3593,1001],{"emptyLinePlaceholder":1000},[123,3595,3596,3598,3600,3602,3604],{"class":125,"line":322},[123,3597,1634],{"class":940},[123,3599,1149],{"class":129},[123,3601,1559],{"class":1074},[123,3603,1641],{"class":139},[123,3605,1545],{"class":129},[123,3607,3608,3610,3612,3614,3616,3618,3620,3622],{"class":125,"line":335},[123,3609,1648],{"class":940},[123,3611,1149],{"class":129},[123,3613,1559],{"class":1074},[123,3615,1641],{"class":139},[123,3617,296],{"class":129},[123,3619,1620],{"class":1074},[123,3621,1562],{"class":139},[123,3623,1545],{"class":129},[123,3625,3626,3628,3630,3632,3634,3636,3638],{"class":125,"line":345},[123,3627,1667],{"class":940},[123,3629,1149],{"class":129},[123,3631,1559],{"class":1074},[123,3633,1641],{"class":139},[123,3635,296],{"class":129},[123,3637,1678],{"class":1081},[123,3639,1545],{"class":129},[123,3641,3642,3644,3646,3648,3650,3652,3654],{"class":125,"line":351},[123,3643,1685],{"class":940},[123,3645,1149],{"class":129},[123,3647,1559],{"class":1074},[123,3649,1641],{"class":139},[123,3651,296],{"class":129},[123,3653,1478],{"class":1081},[123,3655,1545],{"class":129},[123,3657,3658,3660,3662,3664,3666,3668,3670,3672,3674,3676,3678],{"class":125,"line":356},[123,3659,1702],{"class":940},[123,3661,1149],{"class":129},[123,3663,1559],{"class":1074},[123,3665,1641],{"class":139},[123,3667,296],{"class":129},[123,3669,1498],{"class":1081},[123,3671,296],{"class":129},[123,3673,1524],{"class":1074},[123,3675,1149],{"class":129},[123,3677,1721],{"class":1081},[123,3679,1610],{"class":129},[123,3681,3682,3684,3686,3688,3690],{"class":125,"line":361},[123,3683,1728],{"class":1081},[123,3685,296],{"class":129},[123,3687,1620],{"class":1074},[123,3689,1623],{"class":139},[123,3691,1545],{"class":129},[88,3693,3694],{},"Klucz prywatny trafia od razu do chronionej pamięci na urządzeniu. Na ESP32 oznacza to szyfrowany NVS przy włączonym flash encryption albo, w układach, które je mają, peryferium Digital Signature.",[88,3696,3697,3698,3700,3701,3703,3704,1143],{},"Następnie urządzenie łączy się, używając certyfikatu claim, i wymienia kilka wiadomości przez MQTT. Zanim cokolwiek wyśle, subskrybuje ",[120,3699,1745],{}," i odpowiadający mu topic ",[120,3702,1749],{},", a potem publikuje CSR na ",[120,3705,1753],{},[113,3707,3708],{"className":115,"code":1756,"language":117,"meta":118,"style":118},[120,3709,3710,3714,3734],{"__ignoreMap":118},[123,3711,3712],{"class":125,"line":126},[123,3713,130],{"class":129},[123,3715,3716,3718,3720,3722,3724,3726,3728,3730,3732],{"class":125,"line":133},[123,3717,136],{"class":129},[123,3719,1769],{"class":139},[123,3721,143],{"class":129},[123,3723,146],{"class":129},[123,3725,149],{"class":129},[123,3727,1778],{"class":152},[123,3729,1781],{"class":949},[123,3731,1784],{"class":152},[123,3733,242],{"class":129},[123,3735,3736],{"class":125,"line":161},[123,3737,455],{"class":129},[88,3739,3740],{},"Odpowiedź na topicu accepted zawiera podpisany certyfikat i token własności. Token jest ważny przez godzinę, a jeśli w tym czasie nowy certyfikat nie zostanie zarejestrowany, AWS go usuwa, więc urządzenie powinno od razu przejść do kolejnego kroku.",[113,3742,3743],{"className":115,"code":1796,"language":117,"meta":118,"style":118},[120,3744,3745,3749,3767,3789,3805],{"__ignoreMap":118},[123,3746,3747],{"class":125,"line":126},[123,3748,130],{"class":129},[123,3750,3751,3753,3755,3757,3759,3761,3763,3765],{"class":125,"line":133},[123,3752,136],{"class":129},[123,3754,1809],{"class":139},[123,3756,143],{"class":129},[123,3758,146],{"class":129},[123,3760,149],{"class":129},[123,3762,1818],{"class":152},[123,3764,143],{"class":129},[123,3766,158],{"class":129},[123,3768,3769,3771,3773,3775,3777,3779,3781,3783,3785,3787],{"class":125,"line":161},[123,3770,136],{"class":129},[123,3772,1829],{"class":139},[123,3774,143],{"class":129},[123,3776,146],{"class":129},[123,3778,149],{"class":129},[123,3780,1838],{"class":152},[123,3782,1781],{"class":949},[123,3784,1843],{"class":152},[123,3786,143],{"class":129},[123,3788,158],{"class":129},[123,3790,3791,3793,3795,3797,3799,3801,3803],{"class":125,"line":176},[123,3792,136],{"class":129},[123,3794,1854],{"class":139},[123,3796,143],{"class":129},[123,3798,146],{"class":129},[123,3800,149],{"class":129},[123,3802,1863],{"class":152},[123,3804,242],{"class":129},[123,3806,3807],{"class":125,"line":182},[123,3808,455],{"class":129},[88,3810,3811,3812,3814,3815,3817,3818,3820],{},"Urządzenie subskrybuje topiki ",[120,3813,1875],{}," i ",[120,3816,1749],{}," pod ",[120,3819,1882],{}," i publikuje na ten sam topic token razem ze swoim numerem seryjnym.",[113,3822,3823],{"className":115,"code":1886,"language":117,"meta":118,"style":118},[120,3824,3825,3829,3847,3875],{"__ignoreMap":118},[123,3826,3827],{"class":125,"line":126},[123,3828,130],{"class":129},[123,3830,3831,3833,3835,3837,3839,3841,3843,3845],{"class":125,"line":133},[123,3832,136],{"class":129},[123,3834,1854],{"class":139},[123,3836,143],{"class":129},[123,3838,146],{"class":129},[123,3840,149],{"class":129},[123,3842,1863],{"class":152},[123,3844,143],{"class":129},[123,3846,158],{"class":129},[123,3848,3849,3851,3853,3855,3857,3859,3861,3863,3865,3867,3869,3871,3873],{"class":125,"line":161},[123,3850,136],{"class":129},[123,3852,1225],{"class":139},[123,3854,143],{"class":129},[123,3856,146],{"class":129},[123,3858,503],{"class":129},[123,3860,149],{"class":129},[123,3862,496],{"class":139},[123,3864,143],{"class":129},[123,3866,146],{"class":129},[123,3868,149],{"class":129},[123,3870,1935],{"class":152},[123,3872,143],{"class":129},[123,3874,552],{"class":129},[123,3876,3877],{"class":125,"line":176},[123,3878,455],{"class":129},[88,3880,3881],{},"Jeśli hook zaakceptuje numer seryjny, w odpowiedzi przychodzi nazwa nowego Thinga.",[113,3883,3884],{"className":115,"code":1949,"language":117,"meta":118,"style":118},[120,3885,3886,3890,3902,3918],{"__ignoreMap":118},[123,3887,3888],{"class":125,"line":126},[123,3889,130],{"class":129},[123,3891,3892,3894,3896,3898,3900],{"class":125,"line":133},[123,3893,136],{"class":129},[123,3895,1962],{"class":139},[123,3897,143],{"class":129},[123,3899,146],{"class":129},[123,3901,1969],{"class":129},[123,3903,3904,3906,3908,3910,3912,3914,3916],{"class":125,"line":161},[123,3905,136],{"class":129},[123,3907,1976],{"class":139},[123,3909,143],{"class":129},[123,3911,146],{"class":129},[123,3913,149],{"class":129},[123,3915,1985],{"class":152},[123,3917,242],{"class":129},[123,3919,3920],{"class":125,"line":176},[123,3921,455],{"class":129},[88,3923,3924,3925,3927,3928,3930],{},"Teraz urządzenie zapisuje certyfikat, rozłącza się i łączy ponownie, już z nowym certyfikatem, a jako MQTT client ID podaje nazwę Thinga. Dalej to już zwykła praca. Jeśli nie chcesz ręcznie pisać obsługi topików, AWS udostępnia niewielką bibliotekę Fleet Provisioning w C, która buduje i parsuje dokładnie te topiki i współpracuje z dowolnym klientem MQTT. Repozytorium ",[120,3926,1997],{}," Espressifa zawiera ją razem z przykładem ",[120,3929,2001],{},", który w chwili pisania tego tekstu działa z ESP-IDF 5.x, ale jeszcze nie z 6.0.",[95,3932,3934],{"id":3933},"jedna-polityka-dla-wszystkich-urządzeń","Jedna polityka dla wszystkich urządzeń",[88,3936,3937],{},"Szablon przypina całej flocie tę samą politykę, dlatego korzysta ona ze zmiennych, żeby każde urządzenie mogło występować wyłącznie jako ono samo. Szablon przypina też Thinga do certyfikatu, więc AWS potrafi ustalić nazwę Thinga w chwili połączenia.",[113,3939,3940],{"className":115,"code":2012,"language":117,"meta":118,"style":118},[120,3941,3942,3946,3964,3976,3980,3998,4016,4032,4036,4040,4058,4086,4102,4106,4110,4128,4146,4162,4166,4170],{"__ignoreMap":118},[123,3943,3944],{"class":125,"line":126},[123,3945,130],{"class":129},[123,3947,3948,3950,3952,3954,3956,3958,3960,3962],{"class":125,"line":133},[123,3949,136],{"class":129},[123,3951,140],{"class":139},[123,3953,143],{"class":129},[123,3955,146],{"class":129},[123,3957,149],{"class":129},[123,3959,153],{"class":152},[123,3961,143],{"class":129},[123,3963,158],{"class":129},[123,3965,3966,3968,3970,3972,3974],{"class":125,"line":161},[123,3967,136],{"class":129},[123,3969,166],{"class":139},[123,3971,143],{"class":129},[123,3973,146],{"class":129},[123,3975,173],{"class":129},[123,3977,3978],{"class":125,"line":176},[123,3979,179],{"class":129},[123,3981,3982,3984,3986,3988,3990,3992,3994,3996],{"class":125,"line":182},[123,3983,185],{"class":129},[123,3985,188],{"class":139},[123,3987,143],{"class":129},[123,3989,146],{"class":129},[123,3991,149],{"class":129},[123,3993,197],{"class":152},[123,3995,143],{"class":129},[123,3997,158],{"class":129},[123,3999,4000,4002,4004,4006,4008,4010,4012,4014],{"class":125,"line":204},[123,4001,185],{"class":129},[123,4003,209],{"class":139},[123,4005,143],{"class":129},[123,4007,146],{"class":129},[123,4009,149],{"class":129},[123,4011,218],{"class":152},[123,4013,143],{"class":129},[123,4015,158],{"class":129},[123,4017,4018,4020,4022,4024,4026,4028,4030],{"class":125,"line":225},[123,4019,185],{"class":129},[123,4021,230],{"class":139},[123,4023,143],{"class":129},[123,4025,146],{"class":129},[123,4027,149],{"class":129},[123,4029,2103],{"class":152},[123,4031,242],{"class":129},[123,4033,4034],{"class":125,"line":245},[123,4035,248],{"class":129},[123,4037,4038],{"class":125,"line":251},[123,4039,179],{"class":129},[123,4041,4042,4044,4046,4048,4050,4052,4054,4056],{"class":125,"line":256},[123,4043,185],{"class":129},[123,4045,188],{"class":139},[123,4047,143],{"class":129},[123,4049,146],{"class":129},[123,4051,149],{"class":129},[123,4053,197],{"class":152},[123,4055,143],{"class":129},[123,4057,158],{"class":129},[123,4059,4060,4062,4064,4066,4068,4070,4072,4074,4076,4078,4080,4082,4084],{"class":125,"line":275},[123,4061,185],{"class":129},[123,4063,209],{"class":139},[123,4065,143],{"class":129},[123,4067,146],{"class":129},[123,4069,286],{"class":129},[123,4071,143],{"class":129},[123,4073,291],{"class":152},[123,4075,143],{"class":129},[123,4077,296],{"class":129},[123,4079,149],{"class":129},[123,4081,301],{"class":152},[123,4083,143],{"class":129},[123,4085,306],{"class":129},[123,4087,4088,4090,4092,4094,4096,4098,4100],{"class":125,"line":309},[123,4089,185],{"class":129},[123,4091,230],{"class":139},[123,4093,143],{"class":129},[123,4095,146],{"class":129},[123,4097,149],{"class":129},[123,4099,2174],{"class":152},[123,4101,242],{"class":129},[123,4103,4104],{"class":125,"line":322},[123,4105,248],{"class":129},[123,4107,4108],{"class":125,"line":335},[123,4109,179],{"class":129},[123,4111,4112,4114,4116,4118,4120,4122,4124,4126],{"class":125,"line":345},[123,4113,185],{"class":129},[123,4115,188],{"class":139},[123,4117,143],{"class":129},[123,4119,146],{"class":129},[123,4121,149],{"class":129},[123,4123,197],{"class":152},[123,4125,143],{"class":129},[123,4127,158],{"class":129},[123,4129,4130,4132,4134,4136,4138,4140,4142,4144],{"class":125,"line":351},[123,4131,185],{"class":129},[123,4133,209],{"class":139},[123,4135,143],{"class":129},[123,4137,146],{"class":129},[123,4139,149],{"class":129},[123,4141,393],{"class":152},[123,4143,143],{"class":129},[123,4145,158],{"class":129},[123,4147,4148,4150,4152,4154,4156,4158,4160],{"class":125,"line":356},[123,4149,185],{"class":129},[123,4151,230],{"class":139},[123,4153,143],{"class":129},[123,4155,146],{"class":129},[123,4157,149],{"class":129},[123,4159,2235],{"class":152},[123,4161,242],{"class":129},[123,4163,4164],{"class":125,"line":361},[123,4165,443],{"class":129},[123,4167,4168],{"class":125,"line":380},[123,4169,449],{"class":129},[123,4171,4172],{"class":125,"line":400},[123,4173,455],{"class":129},[88,4175,4176],{},"AWS IoT rozpoznaje Thinga po certyfikacie, którym przedstawia się urządzenie, więc urządzenie może występować tylko jako Thing przypięty do własnego certyfikatu, a połączenie zostanie przyjęte tylko wtedy, gdy jego client ID zgadza się z nazwą tego Thinga.",[95,4178,4180],{"id":4179},"jak-dbać-o-złoty-certyfikat","Jak dbać o złoty certyfikat",[88,4182,4183],{},"Certyfikat claim to najcenniejszy sekret w całym rozwiązaniu, więc chronię go tak samo jak sam firmware. Secure Boot i flash encryption na ESP32 znacznie utrudniają odczytanie go z płytki. Dla każdej partii produkcyjnej albo każdego producenta wystawiam też osobny certyfikat claim. Jeśli któryś wycieknie, szkody są ograniczone, a stary certyfikat mogę dezaktywować, gdy wszystkie urządzenia z danej partii już się połączyły. Przydaje się też śledzenie liczby provisioningów. Nagły wysyp żądań o trzeciej w nocy albo hook odrzucający wiele nieznanych numerów seryjnych to dobry powód do alertu.",[88,4185,4186],{},"Warto też wcześnie rozstrzygnąć, kiedy urządzenia mają przechodzić provisioning. Może się to odbywać podczas testu końca linii, gdy są jeszcze w fabryce i w znanej sieci, albo później, u klienta. Provisioning w fabryce daje pewność, że każda wysłana sztuka ma już swoją tożsamość, i pozwala dezaktywować certyfikat claim zaraz po zakończeniu partii. Provisioning w terenie upraszcza proces fabryczny i pasuje do produktów, które miesiącami leżą w magazynie, ale certyfikat claim musi wtedy dłużej pozostać aktywny.",[95,4188,4190],{"id":4189},"kiedy-wybrać-coś-innego","Kiedy wybrać coś innego",[88,4192,4193],{},"Złoty certyfikat sprawdza się w większości małych i średnich serii produkcyjnych i właśnie to zasugerowałbym użytkownikowi ThingsBoard, od którego zacząłem ten wpis. ThingsBoard ma zresztą ten sam mechanizm wbudowany: przy pierwszym połączeniu urządzenie wysyła klucz i sekret provisioningu ze swojego profilu urządzenia, a w zamian dostaje własne dane uwierzytelniające, więc ten wzorzec działa także poza AWS. Przy większych wolumenach albo w produktach z rygorystycznymi wymaganiami bezpieczeństwa są jeszcze układy typu secure element, na przykład ATECC608B Microchipa w wariancie Trust&GO. Przychodzą prosto od producenta układu z unikalnym kluczem prywatnym zamkniętym w środku i pasującym do niego certyfikatem, co całkowicie eliminuje wspólny sekret. Microchip dostarcza manifest tych certyfikatów, który można zbiorczo zarejestrować w AWS IoT jeszcze przed wysyłką urządzeń. Takie rozwiązania kosztują więcej na sztukę i dokładają zależność w łańcuchu dostaw, więc w wielu produktach rozsądnie jest zacząć od certyfikatu claim ze starannie napisanym hookiem.",[88,4195,4196],{},"Jeśli właśnie przenosisz urządzenie IoT z fazy prototypów do pierwszej partii produkcyjnej i chcesz, żeby ktoś jeszcze rzucił okiem na proces provisioningu, chętnie pomogę.",[2274,4198,2276],{},{"title":118,"searchDepth":133,"depth":133,"links":4200},[4201,4202,4203,4204,4205,4206,4207,4208],{"id":2307,"depth":133,"text":2308},{"id":2317,"depth":133,"text":2318},{"id":2599,"depth":133,"text":2600},{"id":3155,"depth":133,"text":3156},{"id":3443,"depth":133,"text":3444},{"id":3933,"depth":133,"text":3934},{"id":4179,"depth":133,"text":4180},{"id":4189,"depth":133,"text":4190},"Jak nadać każdemu urządzeniu własną tożsamość X.509 w AWS IoT Core bez wgrywania certyfikatów sztuka po sztuce: certyfikat claim i fleet provisioning.","pl",{},"9 min czytania",{"title":2296,"description":4209},[12,24,13,25,26],"h5_2wOEbu2o7f1gdJBgwNVqc_YHrKeBBLfKV4QItN8M",1792141329369]