Salta al contenuto

Piero Bosio Social Web Site Personale Logo Fediverso

Social Forum federato con il resto del mondo. Non contano le istanze, contano le persone
  • 0 Votazioni
    2 Post
    0 Visualizzazioni
    madduci@mastodon.social
    @ricardo looks interesting, thanks for sharing
  • 1 Votazioni
    1 Post
    0 Visualizzazioni
    blog@shkspr.mobi
    A reasonably practical guide to validating RFC 9421 HTTP Signatures for ActivityPub in PHP https://shkspr.mobi/blog/2026/09/a-reasonably-practical-guide-to-validating-rfc-9421-http-signatures-for-activitypub-in-php/ If you're reading this, you've probably been hitting your head against a brick wall trying to parse and decipher the new HTTP Signatures sent by Mastodon and other Fediverse servers.This is a basic and somewhat incomplete guide to accepting these signatures. I'm sure there are various gotchas, but it works with the signatures I've seen in the wild.Shut Up And Show Me The Code!OK, wow, no need to be a dick about it! Here's how I validated a real signature that my server received. PHP$verified = openssl_verify( data: '"@method": POST"@target-uri": https://example.viii.fi/inbox"content-digest": sha-256=:tFdB/ENGczHMlZMDb66pXoUi2d0OqH2iBHdnN/WV1mc=: "@signature-params": ("@method" "@target-uri" "content-digest");created=1787780262;keyid="https://mastodon.social/users/Edent#main-key"', signature: base64_decode( "sIfmNsM/Q8iG6AJlne1IkZVjQSVFDEYIPsnoSOXQY+W3Eb4+SOn9o4J5SQmFOP+Jecjf3ioFwUdsrFjAGkUUOHPvSbNWkGKtNuGm+C6r3aI3JBCFGPqX3ITgZYV76CF7JJJ5hPGaG8YH/XdmxVIeFfD3M39FQCncMyyq7xJJvwKKP1mzS5s1vNQie8hbQ9owRjtqvoWcmM9GEYCUHNcMPLjZc+CBrj8sfBbNTYgIFI4UtirOaRJvYymxXjmXuzeVYxQujMjAjgobxQ8QFv0zlYsHk+gS5EYyafpJG9zmfCFSoF9+ZwqKNADmuADbISD9LZIH/bmkPoNXhxaeFPqYog==" ), public_key: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsYMEs4waqk/6gaS+xn1T\nYygElTtNIFNkBcEdEBMaeoGVhyZiVKtSjJCS4z+X+394PKvcfSTcFILIt2GI2jOB\nHD0M2fFgxc8mmdSdCQkgEh9jF3bFI3kopDvzYf726iioYKlHXKpfPKvFt7EJgKH7\naCtS25NQkek3YUd6y3VBcT3R6Xhze9P3QNoZMIsFXklgXDKj+EllfbUqLf1vxt3s\nmD9ETxy2bJi9FheE0uY2WhARn49XAvwczM5Wzt+zqxVEtgpi5v2+ZZAVKhDnJkiC\nCCuI6hrSnKNIx/5mSlX0a0S5h5d03djrCkYsqmwelu01rhOXP2grsz4BXp0y2wrO\n3QIDAQAB\n-----END PUBLIC KEY-----\n", algorithm: "sha256" ); echo $verified; Copy and paste that into PHP and you should see that $verified is true.NOW EXPLAIN THE CODESay please.PLEASE!!!Along with the message sent to your server, you will have received HTTP headers like this: content-digest: sha-256=:tFdB/ENGczHMlZMDb66pXoUi2d0OqH2iBHdnN/WV1mc=: signature: sig1=:sIfmNsM/Q8iG6AJlne1IkZVjQSVFDEYIPsnoSOXQY+W3Eb4+SOn9o4J5SQmFOP+Jecjf3ioFwUdsrFjAGkUUOHPvSbNWkGKtNuGm+C6r3aI3JBCFGPqX3ITgZYV76CF7JJJ5hPGaG8YH/XdmxVIeFfD3M39FQCncMyyq7xJJvwKKP1mzS5s1vNQie8hbQ9owRjtqvoWcmM9GEYCUHNcMPLjZc+CBrj8sfBbNTYgIFI4UtirOaRJvYymxXjmXuzeVYxQujMjAjgobxQ8QFv0zlYsHk+gS5EYyafpJG9zmfCFSoF9+ZwqKNADmuADbISD9LZIH/bmkPoNXhxaeFPqYog==: signature-input: sig1=("@method" "@target-uri" "content-digest");created=1787780262;keyid="https://mastodon.social/users/Edent#main-key" The signature-input tells you how to construct a "Signature Base". You have to build a text string which places the various components in the order specified and separated with a newline: "@method": POST "@target-uri": https://example.viii.fi/inbox "content-digest": sha-256=:tFdB/ENGczHMlZMDb66pXoUi2d0OqH2iBHdnN/WV1mc=: "@signature-params": ("@method" "@target-uri" "content-digest");created=1787780262;keyid="https://mastodon.social/users/Edent#main-key" Where @method is the HTTP method used to send data to your server (usually GET or POST), and @target-uri is the URl the message was sent to (usually your inbox).The publicKey is slightly trickier. As you can see, the signature-input ends with keyid="https://mastodon.social/users/Edent#main-keyIf you make a signed request to that URl, you'll get back an ActivityPub Actor document. It will look something like this: JSON{ "@context": [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", ], "id": "https://mastodon.social/users/Edent", "webfinger": "Edent@mastodon.social", "type": "Person", "name": "Terence Eden", "publicKey": { "id": "https://mastodon.social/users/Edent#main-key", "owner": "https://mastodon.social/users/Edent", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsYMEs4waqk/6gaS+xn1T\nYygElTtNIFNkBcEdEBMaeoGVhyZiVKtSjJCS4z+X+394PKvcfSTcFILIt2GI2jOB\nHD0M2fFgxc8mmdSdCQkgEh9jF3bFI3kopDvzYf726iioYKlHXKpfPKvFt7EJgKH7\naCtS25NQkek3YUd6y3VBcT3R6Xhze9P3QNoZMIsFXklgXDKj+EllfbUqLf1vxt3s\nmD9ETxy2bJi9FheE0uY2WhARn49XAvwczM5Wzt+zqxVEtgpi5v2+ZZAVKhDnJkiC\nCCuI6hrSnKNIx/5mSlX0a0S5h5d03djrCkYsqmwelu01rhOXP2grsz4BXp0y2wrO\n3QIDAQAB\n-----END PUBLIC KEY-----\n" }, The publicKeyPem is the string you need. There's no need to convert the \n to literal newlines.Is that it?Not quite! All we've done so far is verify the headers. It is possible that these are genuine headers but attached to a fraudulent body.This takes us back to the header "content-digest": sha-256=:tFdB/ENGczHMlZMDb66pXoUi2d0OqH2iBHdnN/WV1mc=:That says that the body of the message sent has a Base64 encoded SHA256 hash of tFdB/ENGczHMlZMDb66pXoUi2d0OqH2iBHdnN/WV1mc=.To calculate your own content digest in PHP: PHP$digestCalculated = base64_encode( hash( algo: "sha256", data: $body, binary: true ) ); Does your digest match the one sent along with the headers? If not, something dodgy is going on.Putting it all togetherThe steps are:Get the headers.Get the body.From the headers' content-digest extract the algorithm and hash.Using the body, calculate your own hash using the algorithm from content-digest.Does your hash match the sent hash? If not, stop. If so, proceed.From the headers' signature extract the base64 encoded signature.From the headers' signature-input extract the signature-input string.From the signature-input string extract the order of the Signature Base.Construct the Signature Base.From the signature-input string extract the keyid.Get the Public Key from the keyid.Use openssl_verify() to verify the Signature Base and the base64 decoded signature, against the Public Key using SHA256.Note, Mastodon only uses SHA256. I think it should explicitly say which algorithm it is using and have raised the issue.In Code FormThis is how you do it in PHP. Please read this carefully as there are some hard-coded assumptions. PHP<?php// Validate the Digest.// It is the hash of the raw input string, in binary, encoded as base64.// The format is content-digest => <algorithm>=:<base64 encoded hash>:$digestString = $headers["content-digest"]; // The Base64 encoding may have multiple `=` at the end. So split this at the first `=`.$digestData = explode( separator: "=", string: $digestString, limit: 2 ); // Hashes are in lowercase, but have a `-` in their name.// This is not what hash_algos() expects.$digestAlgorithm = str_replace( search: "-", replace: "", subject: $digestData[0] ); // The hash is surrounded by `:` characters.$digestHash = str_replace( search: ":", replace: "", subject: $digestData[1] ); // Check if the hash algorithm is one known about to PHP.// If not, reject and record an error.if ( !in_array( needle:$digestAlgorithm, haystack: hash_algos() ) ) { return false; } // Manually calculate the digest based on the data sent.$digestCalculated = base64_encode( hash( algo: $digestAlgorithm, data: $input, binary: true ) ); // Does our calculation match what was sent?if ( !( $digestCalculated == $digestHash ) ) { return false; } // The signature format is signature => <signature name>=:<base64 encoded hash>:$signatureString = $headers["signature"]; // The Base64 encoding may have multiple `=` at the end. So split this at the first `=`.$signatureData = explode( separator: "=", string: $signatureString, limit: 2 ); $signatureName = $signatureData[0]; // The signature is surrounded by `:` characters.$signatureB64 = str_replace( search: ":", replace: "", subject: $signatureData[1] ); // The signature-input format is complicated!$signatureInputString = $headers["signature-input"]; // Get the parameters. Assume there is only one signature.$signatureParamsString = explode( separator: "=", string: $signatureInputString, limit: 2 )[1]; // Get the different elements of the signature.$signatureInputData = explode( separator: ";", string: $signatureInputString ); // Construct the data.$signatureInput = []; foreach( $signatureInputData as $signatureInputParts ) { $partsData = explode( separator: "=", string: $signatureInputParts ); // Strip quotes from keyid and parentheses from sig1. if ( "keyid" == $partsData[0] ) { $partsData[1] = str_replace( search: "\"", replace: "", subject: $partsData[1] ); } if ( $signatureName == $partsData[0] ) { $partsData[1] = str_replace( search: ["(", ")"], replace: "", subject: $partsData[1] ); } $signatureInput[ $partsData[0] ] = $partsData[1] ; } $signatureStructure = $signatureInput[$signatureName]; $signatureKeyID = $signatureInput["keyid"]; // Remove quotes.$signatureStructure = str_replace( search: "\"", replace: "", subject: $signatureStructure ); $signatureStructureData = explode( separator: " ", string: $signatureStructure ); // https://www.rfc-editor.org/info/rfc9421/#section-2.5$signatureBase = ""; foreach ( $signatureStructureData as $signatureStructureParts ) { if ( "@method" == $signatureStructureParts ) { // https://www.rfc-editor.org/info/rfc9421/#name-method $signatureBase .= "\"@method\": " . strtolower( $_SERVER["REQUEST_METHOD"] . "\n" ); } if ( "@target-uri" == $signatureStructureParts ) { // https://www.rfc-editor.org/info/rfc9421/#section-2.2.2 // Change the domain name to your own. $signatureBase .= "\"@target-uri\": https://EXAMPLE.COM" . $_SERVER["REQUEST_URI"] . "\n"; } if ( "content-digest" == $signatureStructureParts ) { $signatureBase .= "\"content-digest\": $digestString\n"; } } // https://victoronsoftware.com/posts/http-message-signatures/#how-the-signature-is-created$signatureBase .= "\"@signature-params\": $signatureParamsString"; // Get the signing user's public key.// This is usually in the form `https://example.com/user/username#main-key`// This is to differentiate if the user has multiple keys.// This may need to be a signed request. You will need to write your own getDataFromURl() function to get the sending user's key.$userData = getDataFromURl( $signatureKeyID ); $publicKey = $userData["publicKey"]["publicKeyPem"]; // Verify the request$verified = openssl_verify( data: $signatureBase, signature: base64_decode( $signatureB64 ), public_key: $publicKey, algorithm: $digestAlgorithm ); // Convert the result to boolean.if ( $verified === 1 ) { $verified = true; } elseif ( $verified === 0 ) { $verified = false; } else { $verified = null; } return $verified; Further ReadingRFC 9421 HTTP Message SignaturesUnderstanding HTTP message signatures: A developer's guideSign and verify HTTP messages (RFC 9421)Verification of HTTP Message SignaturesHTTP-Message-Signer in PHPThanks to NLnetThis blog post was funded in part by the work I'm doing for my NLnet NGI0 grant. Thanks! #ActivityBot #ActivityPub #mastodon #php #webdev
  • Starting a #WebDev from (kinda) scratch is always a pain.

    Mondo webdev
    2
    0 Votazioni
    2 Post
    0 Visualizzazioni
    crittero@mastodon.social
    Anyway, shout out to @fedify for having a Nuxt module <3
  • 0 Votazioni
    3 Post
    0 Visualizzazioni
    weirdwriter@caneandable.social
    @dmoonfire Right! I mean, you could link to the audiobook on Libro FM and that website is fantastic for consumers, but for developers, I imagine it’s a nightmare because they don’t have an official API. I think their website is some kind of CMS? I’m not really sure
  • CSS-DOS -- A computer made of CSS

    Mondo css webdev creativecoding
    1
    0 Votazioni
    1 Post
    0 Visualizzazioni
    lobsters@robot.villas
    CSS-DOS -- A computer made of CSShttps://css-dos.ahmedamer.co.uk/#CSS #WebDev #CreativeCoding
  • 0 Votazioni
    12 Post
    0 Visualizzazioni
    adjb@hcommons.social
    @splendorr $40 is outrageous.
  • Facciamo HTML Day in Italia!

    Mondo web html htmlday htmlenergy webdev evento italia
    3
    0 Votazioni
    3 Post
    0 Visualizzazioni
    layan2002@mastodon.social
    @tommi @informatica Vi auguro una splendida giornata, ma purtroppo qui a Gaza non sappiamo più nemmeno come essere felici.
  • 0 Votazioni
    1 Post
    28 Visualizzazioni
    symfonystation@drupal.community
    Ben Werdmuller shares: Building with love, and paying for it. https://werd.io/building-with-love-and-paying-for-it/ #OpenSource #fediverse #WebDevPlease donate to any instances you are on, apps you use, etc. If you are on a website platform, get the pro plan.
  • Does anyone have good resources for debugging memory leaks in website?

    Mondo webdev
    2
    0 Votazioni
    2 Post
    0 Visualizzazioni
    gubi@sociale.network
    @paulhebert Here is a tutorial to use devtools for performance analysis:https://developer.chrome.com/docs/devtools/performance
  • 0 Votazioni
    1 Post
    0 Visualizzazioni
    amber@front-end.social
    Using CSS #animations as state machines to remember focus and hover states with #CSS only"I wanted to do this without #JavaScript, just with CSS, because I was going to use it in a demo about the new focusgroup web platform feature. Focusgroup handles a lot of keyboard navigation logic for you, for free, with only an #HTML attribute."https://patrickbrosset.com/articles/2026-03-09-using-css-animations-as-state-machines-to-remember-focus-and-hover-states-with-css-only/#frontend #webdev #a11y #accessibility
  • 0 Votazioni
    1 Post
    0 Visualizzazioni
    amber@front-end.social
    More Reasons to Delete Your #Javascript Series: Invoker Commands & Popovers APIsIn the past decade, #CSS and #HTML has made fantastic strides into JS’s territory with multiple new APIs and features, like scroll-driven animations and dialog elements. Last week, I got to experiment with extending my dialog elements to opening without any #JS whatsoever...https://amberweinberg.com/more-reasons-to-delete-your-javascript-series-invoker-commands-popovers-apis/#frontEnd #webdevelopment #webdev #a11y #accessibility
  • 0 Votazioni
    1 Post
    0 Visualizzazioni
    tommi@pan.rent
    @manetta introduced us to #Flask during @xpub Prototyping class today! 🤩We were all waiting for this to happen. So many interesting notions! 👾I am keeping sparse public notes updated here.#webDev #XPUB #SI30 #prototyping
  • NEW MASTODON BROWSER PLUGIN

    Mondo mastodon plugin firefox chrome webdev
    24
    1
    0 Votazioni
    24 Post
    0 Visualizzazioni
    stefan@stefanbohacek.online
    @stefan@gardenstate.social @evanHa, just remembered https://fedidevs.com/s/MjI1/.@monnier @StefanThinks
  • #TIL

    Mondo til webdev coding
    1
    0 Votazioni
    1 Post
    0 Visualizzazioni
    riffraff@mastodon.social
    #TIL https://allorigins.win/a service to access web pages via js without CORS issues.#webdev #coding
  • 0 Votazioni
    1 Post
    0 Visualizzazioni
    riccardo1979@mastodon.uno
    Voglio condividere questo articolo: Guida all’ottimizzazione per dispositivi mobili. #SEO #MobileFirst #WebDev #UX #Performance https://www.doctor-web.it/guida-ottimizzazione-mobile-sito-mobile-friendly/
  • 0 Votazioni
    3 Post
    0 Visualizzazioni
    thomasfricke@23.social
    @pmj @kubikpixel Typosquatting or packages with name similarities infected Python and Ruby some years ago. Ugly, but fixable.NPM is controlled by Microslop via Gitslop.
  • Holy cow how did I miss invoker commands?

    Mondo html webdev developer
    3
    0 Votazioni
    3 Post
    0 Visualizzazioni
    amber@front-end.social
    @mattwilcox turns out I only ripped out two or three lines because the rest of it is dealing with auto playing videos and image dimensions, but it’s still pretty nice. It’ll come, especially in handy for things like staff bio pop-ups.
  • About to start a solo web dev project.

    Mondo webdev kanban php javascript
    6
    0 Votazioni
    6 Post
    0 Visualizzazioni
    aamfp@fosstodon.org
    @sephsterPen and paper? 😇https://marcoxbresciani.codeberg.page/kaizen/kanban-journal.html
  • The Web Development Glossary 4K:

    Mondo webdevglossary glossary webdev books
    1
    0 Votazioni
    1 Post
    0 Visualizzazioni
    j9t@mas.to
    The Web Development Glossary 4K:Introducing the latest edition of “The Web Development Glossary,” featuring updated definitions and nearly 1,000 additional terms. A must for anyone looking to navigate the web ecosystem and fill in gaps they never knew existed.https://meiert.com/blog/the-web-development-glossary-4k/#books #webdev #glossary #webdevglossary