Catégorie : Développement informatique

  • Comment on fait de la crypto dans le navi­ga­teur ?

    Faire de la cryp­to­gra­phie dans le navi­ga­teur se révèle bien plus simple que prévu.

    Lais­sez tomber les portages de libso­dium & co. Quasi­ment tous les navi­ga­teurs supportent désor­mais une API native dédiée. Seul IE11 ne le fait pas tota­le­ment mais il a au moins le mini­mum qu’est la géné­ra­tion de nombres réel­le­ment aléa­toires. Ceux qui veulent vrai­ment pour­ront complé­ter l’im­plé­men­ta­tion d’IE11 avec un poly­fill sans risquer de problème de sécu­rité.

    Il y a plein de jolis exemples sur qnimate mais certaines choses datent un peu. J’ai tenté de résu­mer ici pour ceux que ça inté­resse. Ici pour du déchif­fre­ment à partir d’une clef secrète.

    Avant-propos

    Je ne suis pas un expert en chif­fre­ment et ce genre de choses est toujours à manier avec précau­tion. Si vous faites quelque chose de sérieux, ne vous conten­tez pas d’exemples et embau­chez quelqu’un qui sait.

    Rien que choi­sir l’al­go­rithme perti­nent demande de savoir ce qu’on fait. AES-CTR semble perti­nent pour mon cas d’usage où n’ai pas besoin de véri­fier l’au­then­ti­cité du message, où je n’ai pas envie de me colti­ner les ques­tions de padding, et où je serai heureux de profi­ter des multiples cœurs des smart­phones.

    Si l’al­go­rithme choisi ou autre chose vous dérange et que vous en savez plus que moi, n’hé­si­tez pas à commen­ter.

    Récu­pé­rer une clef

    Le plus simple est de récu­pé­rer direc­te­ment une clef au format JSON Web Key (en gros la clef en base64url plus un peu de méta­don­nées). Dans ce cas il suffit de passer par importKey :

    const crypto = window.crypto.subtle;
    
    const jwk = {
      "kty": "oct",
      "use": "enc",
      "k": "SDRSTgdOpUGfgn3iAwiC1cpzsevLM98r_6ehMXlK1Gk",
      "alg": "A256CTR"
    };
    const algo = {name: "AES-CTR"};
    const exportable = false;
    const usage = ["decrypt"];
    
    const key = await crypto.importKey("jwk", jwk, algo, exportable, usage);
    Déri­ver une clef

    Si on veut partir d’une phrase secrète mémo­ri­sable et non d’une clef complète, on commence par créer une clef tempo­raire et on utilise un algo­rithme de déri­va­tion comme PBKDF2.

    Malheu­reu­se­ment pour créer cette première clef il faut passer par un TextEn­co­der et ArrayBuf­fer. peut toujours déri­ver la clef à partir de là. TextEn­co­der n’existe pas sous Edge et IE11, il vous faudra utili­ser une fonc­tion comme uniba­bel qui fait ça pour vous.

    const crypto = window.crypto.subtle;
    const encoder = TextEncoder();
    
    const passphrase = "l'eau ça mouille, le feu ça brûle";
    const buffer = encoder.encode( passphrase );
    const d_algo =  {name: 'PBKDF2'};
    const d_exportable = false;
    const d_usage = ['deriveBits', 'deriveKey'];
    const d_key = await crypto.importKey('raw', buffer, d_algo, d_exportable, d_usage);
    
    const deriv = { 
      name: 'PBKDF2',
      salt: encoder.encode( "c'est le week-end !" ),
      iterations = 25,
      hash: 'SHA-256',
    };
    const algo = {name: "AES-CTR", length: 256}; 
    const exportable = false; 
    const usage = ["decrypt"];
    
    const key = await crypto.deriveKey(deriv, d_key, algo, exportable, usage);

    La sécu­rité de tout ça dépend de la longueur et de l’uni­cité de votre phrase secrète initiale. À vous de trou­ver le bon compro­mis entre la sécu­rité et la puis­sance des smart­phones qui risquent d’uti­li­ser votre code. Le 25 ici est pure­ment arbi­traire et le temps de calcul néces­saire est propor­tion­nel.

    Déchif­frer

    Déchif­frer n’est pas plus diffi­cile.

    Le vecteur d’ini­tia­li­sa­tion (iv) et la donnée chif­frée (encryp­ted) sont atten­dus sous forme d’Ar­rayBuf­fer. Il faudra de nouveau passer par uniba­bel ou une autre solu­tion si vous avez ça sous forme de chaîne binaire ou codé en base64.

    Le résul­tat de decrypt() vous est retourné sous la même forme. S’il est petit le plus simple est d’uti­li­ser TextDe­co­der ou uniba­bel. Si vous avez quelque chose de plus volu­mi­neux vous pouvez aussi passer par un Blob et un FileRea­der.

    const crypto = window.crypto.subtle;
    const decoder = TextDecoder
    
    const key = ...
    const iv = ...
    const encrypted = ...
    
    const algo = { name: 'AES-CTR', iv: iv }
    const buffer = await crypto.decrypt(alg, key, encryted);
  • Trou­ver un héber­geur pour des fichiers statiques

    Je conti­nue l’ex­plo­ra­tion pour la bascule de mon héber­ge­ment, et plus préci­sé­ment l’hé­ber­ge­ment de mes fichiers statiques.

    Idéa­le­ment j’ai besoin d’un quota de 1 ou 2 Go, de pouvoir y bran­cher 4 domaines diffé­rents avec du HTTPS, de pouvoir régler des entêtes de cache correctes sur les fichiers que je veux, et si possible défi­nir des règles de redi­rec­tion ou de réécri­ture. Pas de PHP, pas de base de données, pas de trucs qui consomment du CPU.

    On m’a proposé pas mal de choses, pour l’ins­tant je retiens, dans cet ordre :

    Netlify, qui est opti­misé pour ça et qui a un compte gratuit qui semble conve­nir. Un système qui ne prévoit pas de PHP et de choses consom­ma­trices ça me garan­tit que je ne vais pas me retrou­ver sur une machine à bout de souffle et que tout est dédié à mon usage.

    Lautre.net, asso­cia­tif héri­tage indi­rect de altern.org. On se retrouve sur les clas­siques pas cher qui permettent du PHP et tout plein de choses, et qui géné­ra­le­ment sont un peu à la peine, mais altern.org fut mon premier accès au web et rien que pour ça ça me ferait plai­sir de contri­buer à son survi­vant. La coti­sa­tion à 23€ est abor­dable et c’est pour une asso.

    Gandi simple hosting, dont l’offre small+ssl semble à priori conve­nir et les 36 € de la première année sont accep­tables. Pour les suivantes ça dépen­dra de ce que j’y trouve. J’ima­gi­nais pouvoir héber­ger de bêtes fichiers statiques pour moins cher que 70 €.

    OVH avec les offres d’hé­ber­ge­ment web mutua­lisé. L’offre «  perso » est à 43 € l’an­née mais je ne sais pas trop à quoi m’at­tendre côté qualité.

    Always­data, figure connue, je sais que ça le fera mais le prix commence à monter avec les 80 €.

    Online.net a une offre « perso » à 30 € l’an­née mais je ne sais pas si elle gère plusieurs domaines. Ce n’est pas clair et j’ai les mêmes réserves que les mutua­li­sés OVH.

    Sur le reste, si ça en inté­resse d’autres avec des besoins simi­laires, on m’a aussi pointé o2switch.fr, phpnet.org

    J’ai écarté tout ce qui est à plus de 100 € l’an­née et les « quasi­ment gratuit tout illi­mité » qui ne m’ins­pirent pas du tout confiance, et les offres VPS vu que mon but était juste­ment d’avoir du managé.

    Si vous avez d’autres noms, des recom­man­da­tions ou des commen­taires critiques sur l’un ou sur l’autre, c’est le moment.

  • Donnée confi­den­tielle dans une session de navi­ga­tion.

    Je partage, ça peut servir à d’autres. Je cher­chais à garder confi­den­tiel une infor­ma­tion confi­den­tielle le temps d’une session de navi­ga­tion. En gros je cher­chais un genre de cookie de session mais qui reste côté client sans jamais tran­si­ter sur le réseau.

    Le localS­to­rage est top mais il persiste au delà de la session de navi­ga­tion. Le sessionS­to­rage est top mais il n’est pas partagé entre les onglets.

    Visi­ble­ment certains se sont penchés sur la ques­tion il y a quelques années et sont reve­nus avec une solu­tion toute bête mais effi­cace : Faire dialo­guer les diffé­rents sessionS­to­rage en surveillant les écri­tures dans le localS­to­rage.

    C’est tout con, ça prend juste 20 lignes, mais il fallait y penser.

    Merci Rik

  • Un espace de publi­ca­tion chif­fré côté client

    Je ne veux plus gérer de serveur en ligne. Je me sens de moins en moins capable d’as­su­rer la sécu­rité d’un tel envi­ron­ne­ment 24/7 seul et sur mon temps person­nel. Je n’en ai pas la moti­va­tion, ne souhaite pas y inves­tir le temps néces­saire. Ne parlons même pas de la possi­bi­lité de prendre des congés deux semaines hors de France sans connexion Inter­net ni veille sécu­rité. Rien qu’a­voir ce blog sous word­press me gêne.

    Je vais dépla­cer mes services sur un envi­ron­ne­ment mutua­lisé, géré par des profes­sion­nels qui ont les moyens, le temps et les compé­tences. Je vais en profi­ter pour passer à peu près tout en fichiers HTML statiques. Publier des fichiers html, css et images sur un espace à 2€, ça limite pas mal la main­te­nance.

    * * *

    Mon problème c’est que j’ai aussi des parties de site à accès restreint, avec des docu­ments qui ne doivent pas sortir n’im­porte où.

    Je peux faci­le­ment trou­ver un héber­ge­ment mutua­lisé qui me permet de faire des accès restreints par authen­ti­fi­ca­tion HTTP ou avec un bout de PHP en façade, mais j’ai une confiance limi­tée dans la confi­den­tia­lité des fichiers que je peux poser sur un héber­geur mutua­lisé.

    Du coup j’ima­gine utili­ser du chif­fre­ment côté client, avec un croi­se­ment entre Jekyll/Peli­can et 0bin/cryp­to­pad. Je chiffre les conte­nus lors de la géné­ra­tion et je les envoie chif­frés sur l’hé­ber­ge­ment. Les conte­nus sont déchif­frés dans le navi­ga­teur du client avec un gros bout de JS, en utili­sant un dérivé de mot de passe ou une clef cachée dans l’URL.

    Le seul défaut que je vois c’est inter­dire l’ac­cès à ceux qui désac­tivent volon­tai­re­ment Javas­cript, et impo­ser un peu d’at­tente aux autres pour déver­rouiller les conte­nus : Pas idéal, pas perti­nent pour tous les usages, mais ici ça me semble accep­table.

    * * *

    Il y a 0bin et Cryp­to­pad (ainsi que d’autres) qui fonc­tionnent un peu sur ce prin­cipe, mais bran­cher ça dans Jekyll ou Peli­can me semble néces­si­ter un peu de travail, surtout si je veux avoir plus que du texte et que je veux présen­ter à l’uti­li­sa­teur unique­ment les liens auxquels il a accès.

    Si vous connais­sez un CMS à publi­ca­tion statique qui a envi­sagé quelque chose du genre, je suis preneur.

  • [Aide] Commu­ni­ca­tion entre une page et une exten­sion navi­ga­teur

    J’ai une page qui fait des trai­te­ments javas­cripts basés sur des appels XHR authen­ti­fiés vers son origine et sur des commu­ni­ca­tions en window.postMes­sage avec des <iframe>. Elle n’a besoin d’au­cune permis­sion privi­lé­giée, c’est juste une page web avec une origine normale.

    J’ai­me­rais pouvoir inter­ro­ger cette page depuis une exten­sion Fire­fox et qu’elle me commu­nique le résul­tat de ses trai­te­ments, mais sans que ça n’af­fiche la page à l’uti­li­sa­teur.

    Au départ j’ima­gi­nais que l’ex­ten­sion pouvait lancer une <iframe> cachée et discu­ter avec elle en postMes­sage. On me dit que ce n’est pas possible.

    Embarquer direc­te­ment le code de la page dans l’ex­ten­sion n’est pas envi­sa­geable pour des raisons de sécu­rité (et on ne ferait que repor­ter le problème vu que cette page lance elle-même des iframes pour commu­niquer avec elles).

    Dis, public, est-ce que tu aurais une sugges­tion ou une piste à explo­rer ?

  • La base de travail pour 2018

    Une progres­sive web app prévue d’abord pour mobile, fonc­tion­nant tota­le­ment hors ligne avec une synchro à la prochaine recon­nexion et des données chif­frées côté client.

    Oui, votre besoin a peut-être des usages ou des contraintes qui ne cadrent pas avec ce stéréo­type mais ça mérite proba­ble­ment d’y réflé­chir deux fois avant d’écar­ter un des éléments.

    Quand je vois nombre de projets sans chif­fre­ment des données ou quasi­ment inutiles une fois hors ligne, j’ai l’im­pres­sion de retrou­ver les projets d’il y a quelques années qui consi­dé­raient le mobile comme acces­soire.

  • J’ef­face mes traces

    Il y a désor­mais 5 ans que j’ef­face mes traces.

    J’avais fait un petit billet pour expliquer que j’ef­façais mes tweets après 48h. Je l’ai fait un temps mais via des scripts lancés à la main, pour garder le contrôle. Je l’ai fait de façon irré­gu­lière, puis plus du tout pendant un temps.

    La moti­va­tion n’a toute­fois pas chan­gée.

    Je m’y remets avec un script un peu plus évolué qui devrait à terme pouvoir être lancé en auto­ma­tique (quand j’au­rais un peu plus confiance). Désor­mais la plupart de mes tweets pour­ront être suppri­més au bout d’une dizaine de jours.

    Si j’ai le courage j’éten­drai ça à Masto­don et aux autres réseaux. Plus géné­ra­le­ment, tout ce qui n’est pas dans un espace que je contrôle person­nel­le­ment est poten­tiel­le­ment amené à dispa­raitre. Ce qui m’im­porte est géné­ra­le­ment retrans­crit sur le site que vous êtes en train de lire.

  • Et si on agençait des photos sur une page web mobile ?

    J’ai commencé à vouloir mettre en pratique mes études sur l’agen­ce­ment de photos dans une page web et… j’ai tout repris à zéro.

    Quand je donne un lien vers mes photos, il est quasi­ment toujours ouvert en premier sur mobile. Sauf à y faire des minia­tures illi­sibles, on peut y mettre une ou deux photos maxi­mum en largeur. La vue à la Flickr n’a aucun sens.

    Du coup je suis reparti d’une vue mobile, avec des agen­ce­ments prédé­ter­mi­nés. J’ai en trouvé quatre, même si bien évidem­ment certains peuvent s’in­ver­ser :

    Tech­nique­ment les calculs pour que les agen­ce­ments avec plusieurs images tombent « juste » sont les mêmes que dans la vue Flickr décrite au précé­dent billet.

    * * *

    La vraie ques­tion c’est le choix de l’agen­ce­ment en fonc­tion du format des diffé­rentes photos et de leur ordre d’ap­pa­ri­tion.

    La mauvaise idée de départ c’était tenter de faire des règles complexes pour choi­sir l’agen­ce­ment en fonc­tion des prochaines images et de leur format. L’ap­proche naïve était simple mais le résul­tat assez mauvais visuel­le­ment. Il aurait fallu faire plus complexe que verti­cal / carré / hori­zon­tal mais si je commence à distin­guer sept caté­go­ries, les combi­nai­sons explosent.

    Autant calcu­ler les quatre agen­ce­ments possibles et voir lequel est le plus perti­nent. J’ai pris deux axiomes :

    1– L’im­por­tant visuel­le­ment c’est la surface affi­chée de chaque image plutôt que sa taille en largeur ou hauteur.  Trop gros est aussi mauvais que trop petit. Je compare donc la surface de l’image à évaluer à celle d’une image de réfé­rence (une hori­zon­tale au format 3/2 affi­chée en pleine largeur).

    2– Il suffit d’une seule image illi­sible pour tout gâcher. Je calcule donc le score de chaque image indi­vi­duel­le­ment et le score d’un agen­ce­ment de plusieurs images corres­pond au plus mauvais score des images concer­nées.

    Le résul­tat est plutôt réussi. J’ai tenté de bidouiller et ajou­ter d’autres règles complexes mais ça n’a pas donné des amélio­ra­tions visuelles signi­fi­ca­tives.

    * * *

    Je calcule la gale­rie au fur et à mesure. Parfois il me reste une image verti­cale à la fin et je n’ai plus qu’à l’af­fi­cher en grand, même si c’est déme­suré. Une solu­tion pour­rait être de calcu­ler réel­le­ment toutes les combi­nai­sons pour toute la gale­rie avant de faire mon choix. Ça risque d’être un peu lourd pour ce seul défaut, surtout pour des gale­ries assez longues, donc pour l’ins­tant je ne suis pas allé sur ce chemin.

    Aujourd’­hui je garde l’ordre des photos. Je pour­rais aussi éven­tuel­le­ment iden­ti­fier les cas où tous les agen­ce­ments testés sont mauvais, et tenter de modi­fier un peu l’ordre des images avec celles juste après, pour voir si c’est mieux.

    Tant que je ne repère pas de cas vrai­ment moche, je vais toute­fois rester sur du simple.

    * * *

    Le choix assumé c’est de faire du mobile-first. Si j’ai plus d’es­pace hori­zon­tal je peux tenter de faire deux colonnes avec ce même algo­rithme.

    Si je trouve un point pivot et que je découpe la gale­rie en deux sections, je pour­rai choi­sir de les enchaî­ner verti­ca­le­ment ou hori­zon­ta­le­ment sans avoir besoin de calcu­ler quoi que ce soit en javas­cript.

    Si j’ai vrai­ment une grande surface en hauteur et en largeur, un mur d’images à la Flickr est peut-être plus perti­nent mais je ne m’in­ter­dis pas de simple­ment garder deux colonnes de grande largeur. Je vais attendre de voir ce que ça donne avant de faire mon choix.

     

  • Les meilleurs commen­taires ne s’écrivent pas

    Les meilleurs commen­taires sont ceux que l’on n’a pas besoin d’écrire.

    Lorsque l’on a besoin d’écrire un commen­taire, le plus souvent, c’est que notre code n’est pas aussi clair et propre qu’il devrait l’être.

    Je suis bien d’ac­cord avec la cita­tion du dessus, mais elle n’im­plique aucu­ne­ment ce qui suit.

    Damned, un déve­lop­peur faisant une telle faute logique ? On devrait savoir que les rela­tions de cause et consé­quence ne s’in­versent pas. Si les meilleurs commen­taires sont ceux qu’on n’a pas besoin d’écrire, personne n’a dit qu’on ne devrait pas écrire les commen­taires !

    function main() {  
      let imageName = 'test.png'
    
      // Get the extension off the image filename  
      let pieces = imageName.split('.')
      let extension = pieces.pop()
    }
    

    […] ça ressemble beau­coup trop à une excuse : « Mon code est moche / compliqué mais c’est pas grave je vais l’ex­pliquer dans un commen­taire » au lieu de le nettoyer.

    Donc oui, ce commen­taire est à peu près inutile. S’il le devient c’est que le code est proba­ble­ment inuti­le­ment complexe et pour­rait être amélio­rer avec l’uti­li­sa­tion de fonc­tions tierces bien nommées.

    Si effec­ti­ve­ment vous commen­tez ainsi (pas de honte, ça arrive à tous), ça ne sert à rien.

    Par contre, un commen­taire qui dit pourquoi vous avez besoin de l’ex­ten­sion ça ne serait pas forcé­ment du luxe. Peut-être que ce commen­taire manquant me permet­trait de savoir si le compor­te­ment face à un fichier « .test.png » est une anoma­lie ou est volon­taire. Là je suis bien à mal de savoir sans lire tout le code source en détail pour cher­cher l’in­ten­tion du déve­lop­peur.

    Bref, si vous croyez qu’un code source clair remplace les commen­taires, c’est que vous n’avez pas encore compris ce qu’il faut écrire dans les commen­taires.

    /**
     * Get the extension of the file
     * 
     * @param {string} filename - The filename
     * @return {string} the extension of the file  
     */
    function getFileExtension(filename) {  
      let pieces = imageName.split('.')
      return pieces.pop()
    }

    Dites moi qu’il y a une infor­ma­tion dans ce commen­taire que vous n’aviez pas en lisant le code !

    Non. Par contre dans un IDE évolué, ce type de code me permet d’avoir confir­ma­tion du rôle de la fonc­tion quand je la tape ou quand je la lis plutôt que de devoir m’in­ter­rompre pour aller ouvrir le fichier corres­pon­dant et lire tout le code source. D’au­tant que là ce sont deux lignes mais parfois, même si c’est clair, c’est quand même plus long à lire.

    C’est aussi avec les commen­taires de @ que l’IDE me donnera le rôle des diffé­rents para­mètres. Oui, le plus souvent ils devraient être évident mais est-ce toujours le cas ? Grâce à ce jsdoc je saurais sans ouvrir la fonc­tion que je dois y rensei­gner un nom de fichier et pas un chemin complet. Bien m’en a pris parce que « ./test.png » aurait provoqué de jolies erreurs à l’exé­cu­tion de mon programme. Je saurai aussi si j’ai ou pas un argu­ment option­nel et pourquoi.

    Toujours avec un outillage évolué, la mention du string permet­tra d’iden­ti­fier des erreurs de typage malen­con­treuses. Ça pour­rait être dans le proto­type de la fonc­tion avec flow ou ici dans le commen­taire, peu importe, seule la syntaxe diffère.

    Main­te­nant même ici, le problème n’est pas avec le commen­taire mais avec ce qu’il dit. Ça n’au­rait pas été du luxe que la fonc­tion décrive que ce qu’elle consi­dère être une exten­sion dans le cas d’un « test.tar.gz ».

    Là où le code est propre le commen­taires ne sera qu’une redon­dance sans grand inté­rêt que le cerveau appren­dra vite à igno­rer.

    Et si c’était le contenu des commen­taires qu’il fallait remettre en cause et pas leur présence ?

    Sinon oui, je règle mes outils pour que les commen­taires s’af­fichent en gris clair. Pas qu’ils soient sans inté­rêt mais parce que j’ai deux niveaux de lecture suivant que je travaille le corps de la fonc­tion (et là je veux igno­rer les commen­taires) ou que je l’étu­die la première fois.

    Le seul problème que je rencontre aujourd’­hui se produit dans le cadre de project avec une docu­men­ta­tion publique auto-géné­rée. Comment éviter la redon­dance tout en faisant en sorte que la docu­men­ta­tion géné­rée à partir de mes commen­taires soit complète.

    Si la docu­men­ta­tion géné­rée pour les déve­lop­peurs a besoin des commen­taires, c’est que peut-être l’as­ser­tion comme quoi le code est aussi simple et effi­cace à lire que les commen­tai­res… est peut-être fausse. Je dis ça je dis rien.

  • Attri­buer une cita­tion en HTML

    On parle de HTML 5, le web n’est qu’une multi­tude de liens, et on ne sait toujours pas attri­buer une cita­tion. Comment est-ce que je source le Au-revoir de Valéry Giscard d’Es­taing ?

    <blockquote>Au revoir.</blockquote>

    Il y a bien un attri­but cite dans <blockquote>. Je peux y mettre un lien mais ce lien ne sera ni affi­ché ni action­nable dans les navi­ga­teurs.

    Je peux aussi mettre un texte dans un attri­but data-source et l’af­fi­cher avec le lien via quelques geeke­ries CSS. C’est un peu mieux mais le lien n’est ni cliquable ni sélec­tion­nable pour un copier-coller : Plus frus­trant du meurs.

    Je ne parle même pas d’al­ler indiquer la date dans la cita­tion avec la balise <time>, là on entre­rait dans la science fiction.

    <style>
    blockquote::after { content: "-- " attr(data-source) ", " attr(cite);
    </style>

    La solu­tion du pauvre – celle que j’uti­lise sur ce blog – c’est de mettre la source direc­te­ment dans le <blockquote>. Il s’agit toute­fois d’un détour­ne­ment du sens des balises, parce que l’at­tri­bu­tion ne fait pas partie du bloc cité.

    D’autres proposent de mettre ça dans un <footer> du <blockquote>, éven­tuel­le­ment avec un <cite>. C’est un peu mieux – le <footer> est offi­ciel­le­ment fait pour être de la méta­don­née à propos du bloc parent – mais ça reste bancal : Le code ne permet pas de sépa­rer ce qu’on cite de l’at­tri­bu­tion elle-même.

    <blockquote>
      Au revoir.
      <footer>
        -- Valéry Giscard d'Estaing, 
        <cite>
          <a href="http://www.ina.fr/video/I08358793">
            dans son discours du 
            <time datetime="1981-05-19">le 19 mai 1981</time> 
            avant la passation de pouvoir à François Mitterrand
          </a>
        </cite>
      </footer>
    </blockquote>

    La balise <figure> peut venir à notre rescousse. On englobe le <blockquote> dans la <figure> et on ajoute un <figcaption> pour l’at­tri­bu­tion, avec éven­tuel­le­ment un <cite> correc­te­ment placé.

    <figure>
      <blockquote>Au revoir.</blockquote>
      <figcaption>
        -- Valéry Giscard d'Estaing, 
        <cite>
          <a href="http://www.ina.fr/video/I08358793">
            dans son discours du 
            <time datetime="1981-05-19">le 19 mai 1981</time> 
            avant la passation de pouvoir à François Mitterrand
          </a>
        </cite>
      </figcaption>
    </figure>

    Quelque part j’ai encore mal parce qu’un robot ne reliera pas l’at­tri­bu­tion et la cita­tion.

    Reste aussi que la défi­ni­tion de <figure> indique que l’em­pla­ce­ment de son contenu dans le texte envi­ron­nant n’a pas d’im­por­tance, ce qui ne me parait pas forcé­ment vrai pour une cita­tion.

    On sait expri­mer tout ça à desti­na­tion des moteurs de recherche en rdfa, en micro data ou en json-ld, le tout avec un voca­bu­laire normé, mais on n’a toujours rien pour les données visibles de l’uti­li­sa­teur. Que la spéci­fi­ca­tion HTML 5 ait été diri­gée par un éditeur de Google ne peut être qu’une coïn­ci­dence.