DRM FileOpen / PDF

FileOpen est la solution de DRM utilisée dans les fichiers PDF de l’AFNOR pour contourner l’obligation de publication des normes.

Le seul moyen pour ouvrir les fichiers est l’utilisation du lecteur Acrobat de Adobe avec un plugin FileOpen (disponible uniquement sous plateforme x86).

Une première analyse montre que le plugin reçoit une clé (paramètre http Code). Voila un exemple de requête reçu:

RetVal=1&ServId=btq_afnor&DocuId=<identifiant enlevé>&Ident3ID=NORBJ&Ident4ID=<identifiant enlevé>&Code=NORBJ&Perms=1

Ici, RetVal=1 indique que le serveur a accepté la requête, Perms renvoie les permissions acceptées (e.g impression, copie, lecture, etc.) et Code renvoie la clé de chiffrement.

La clé de chiffrement fonctionne exactement comme la clé de chiffrement de la protection standard de Adobe (la file key de 5 octets). Sauf qu’on n’a pas besoin d’algorithme compliqué pour la genérer, elle est obtenue directement depuis le serveur.

Il semble que DocuId, Ident3ID et Ident4ID soient générés à partir de valeurs contenus dans le document:

  • DocuId est dans la clé DUID du dictionnaire de chiffrement (/Filter/FOPN_foweb)
  • Ident4ID est la première partie de DocuID (jusqu’au '.')

Dans le cas de l’AFNOR, il semble que la clé soit toujours égale à 'NORBJ'.

Hack for poppler

Le patch suivant code en dur la clé utilisée par l’AFNOR afin de pouvoir lire les normes obligatoires avec les logiciels basés sur poppler:

diff --git a/poppler/Decrypt.cc b/poppler/Decrypt.cc
index ca294d3..2f6aff2 100644
--- a/poppler/Decrypt.cc
+++ b/poppler/Decrypt.cc
@@ -57,6 +57,8 @@ GBool Decrypt::makeFileKey(int encVersion, int encRevision, int keyLength,
                          GooString *ownerPassword, GooString *userPassword,
                          Guchar *fileKey, GBool encryptMetadata,
                          GBool *ownerPasswordOk) {
+
+#if 0
   Guchar test[32], test2[32];
   GooString *userPassword2;
   Guchar fState[256];
@@ -114,6 +116,8 @@ GBool Decrypt::makeFileKey(int encVersion, int encRevision, int keyLength,
   return makeFileKey2(encVersion, encRevision, keyLength, ownerKey, userKey,
                     permissions, fileID, userPassword, fileKey,
                     encryptMetadata);
+#endif
+  return gTrue;
 }

 GBool Decrypt::makeFileKey2(int encVersion, int encRevision, int keyLength,
diff --git a/poppler/SecurityHandler.cc b/poppler/SecurityHandler.cc
index ea91e21..bde22f6 100644
--- a/poppler/SecurityHandler.cc
+++ b/poppler/SecurityHandler.cc
@@ -39,7 +39,8 @@ SecurityHandler *SecurityHandler::make(PDFDoc *docA, Object *encryptDictA) {
 #endif

   encryptDictA->dictLookup("Filter", &filterObj);
-  if (filterObj.isName("Standard")) {
+  //if (filterObj.isName("Standard")) {
+  if (filterObj.isName("FOPN_foweb")) {
     secHdlr = new StandardSecurityHandler(docA, encryptDictA);
   } else if (filterObj.isName()) {
 #ifdef ENABLE_PLUGINS
@@ -127,6 +128,13 @@ StandardSecurityHandler::StandardSecurityHandler(PDFDoc *docA,
                                                Object *encryptDictA):
   SecurityHandler(docA)
 {
+  ok = gTrue;
+  fileID = NULL;
+  ownerKey = NULL;
+  userKey = NULL;
+  encAlgorithm = cryptRC4;
+  fileKeyLength = 5;
+#if 0
   Object versionObj, revisionObj, lengthObj;
   Object ownerKeyObj, userKeyObj, permObj, fileIDObj;
   Object fileIDObj1;
@@ -238,6 +246,8 @@ StandardSecurityHandler::StandardSecurityHandler(PDFDoc *docA,
   lengthObj.free();
   revisionObj.free();
   versionObj.free();
+#endif
+
 }

 StandardSecurityHandler::~StandardSecurityHandler() {
@@ -261,6 +271,7 @@ void *StandardSecurityHandler::makeAuthData(GooString *ownerPassword,
 }

 void *StandardSecurityHandler::getAuthData() {
+#if 0
 #if HAVE_XPDFCORE
   XPDFCore *core;
   GooString *password;
@@ -282,6 +293,8 @@ void *StandardSecurityHandler::getAuthData() {
 #else
   return NULL;
 #endif
+#endif
+  return NULL;
 }

 void StandardSecurityHandler::freeAuthData(void *authData) {
@@ -301,12 +314,17 @@ GBool StandardSecurityHandler::authorize(void *authData) {
     ownerPassword = NULL;
     userPassword = NULL;
   }
-  if (!Decrypt::makeFileKey(encVersion, encRevision, fileKeyLength,
-                          ownerKey, userKey, permFlags, fileID,
-                          ownerPassword, userPassword, fileKey,
-                          encryptMetadata, &ownerPasswordOk)) {
-    return gFalse;
-  }
+  fileKey[0] = 'N';
+  fileKey[1] = 'O';
+  fileKey[2] = 'R';
+  fileKey[3] = 'B';
+  fileKey[4] = 'J';
+  //if (!Decrypt::makeFileKey(encVersion, encRevision, fileKeyLength,
+  //                      ownerKey, userKey, permFlags, fileID,
+  //                      ownerPassword, userPassword, fileKey,
+  //                      encryptMetadata, &ownerPasswordOk)) {
+  //  return gFalse;
+  //}
   return gTrue;
 }

Voici un portage du patch fonctionnant avec mupdf, qui est la bibliothèque utilisé par sumatrapdf.

diff -rN -u old-mupdf/mupdf/pdf_crypt.c new-mupdf/mupdf/pdf_crypt.c
--- old-mupdf/mupdf/pdf_crypt.c        2009-12-09 16:17:18.000000000 +0100
+++ new-mupdf/mupdf/pdf_crypt.c        2009-12-09 16:17:18.000000000 +0100
@@ -27,7 +27,7 @@
               pdf_freecrypt(crypt);
               return fz_throw("unspecified encryption handler");
       }
-      if (strcmp(fz_toname(obj), "Standard") != 0)
+      if (strcmp(fz_toname(obj), "FOPN_foweb") != 0)
       {
               pdf_freecrypt(crypt);
               return fz_throw("unknown encryption handler: '%s'", fz_toname(obj));
@@ -37,13 +37,20 @@
       obj = fz_dictgets(dict, "V");
       if (fz_isint(obj))
               crypt->v = fz_toint(obj);
+#if 0
       if (crypt->v != 1 && crypt->v != 2 && crypt->v != 4)
       {
               pdf_freecrypt(crypt);
               return fz_throw("unknown encryption version");
       }
+#endif

       crypt->length = 40;
+      crypt->stmf.method = PDF_CRYPT_RC4;
+      crypt->stmf.length = crypt->length;
+      crypt->strf.method = PDF_CRYPT_RC4;
+      crypt->strf.length = crypt->length;
+#if 0
       if (crypt->v == 2 || crypt->v == 4)
       {
               obj = fz_dictgets(dict, "Length");
@@ -161,6 +168,7 @@
               pdf_freecrypt(crypt);
               return fz_throw("encryption dictionary missing permissions value");
       }
+#endif

       crypt->encryptmetadata = 1;
       obj = fz_dictgets(dict, "EncryptMetadata");
@@ -172,7 +180,7 @@
        */

       crypt->idlength = 0;
-
+#if 0
       if (fz_isarray(id) && fz_arraylen(id) == 2)
       {
               obj = fz_arrayget(id, 0);
@@ -187,6 +195,13 @@
       }
       else
               fz_warn("missing file identifier, may not be able to do decryption");
+#endif
+
+      crypt->key[0] = 'N';
+      crypt->key[1] = 'O';
+      crypt->key[2] = 'R';
+      crypt->key[3] = 'B';
+      crypt->key[4] = 'J';

 #if 0
       {
@@ -461,6 +476,7 @@
 int
 pdf_authenticatepassword(pdf_xref *xref, char *password)
 {
+#if 0
       if (xref->crypt)
       {
               if (pdf_authenticateuserpassword(xref->crypt, (unsigned char *)password, strlen(password)))
@@ -469,6 +485,7 @@
                       return 1;
               return 0;
       }
+#endif
       return 1;
 }

Table Of Contents

Previous topic

Le webring de la salade

This Page