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'.
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;
}