Lors de la signature numérique d'un document avec itext v5.5.11, les documents PDF / A-2b sont corrompus, ce qui signifie qu'ils ne sont plus valides en tant que documents PDF / A. La règle suivante est violée: https: //github.com/veraPDF/veraPDF-validation-profiles/wiki/PDFA-Parts-2-and-3-rules#rule-643-1

Dans le lien ci-dessus, il est spécifié que le condensé est invalide, donc je vous donne également un segment de code qui traite du calcul du condensé lors de la signature d'un document pdf avec iText:

        // Make the digest
        InputStream data;
        try {

            data = signatureAppearance.getRangeStream();
        } catch (IOException e) {
            String message = "MessageDigest error for signature input, type: IOException";
            signLogger.logError(message, e);
            throw new CustomException(message, e);
        }
        MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance("SHA1");

        } catch (NoSuchAlgorithmException ex) {
            String message = "MessageDigest error for signature input, type: NoSuchAlgorithmException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }
        byte[] buf = new byte[8192];
        int n;
        try {
            while ((n = data.read(buf)) > 0) {
                messageDigest.update(buf, 0, n);
            }
        } catch (IOException ex) {
            String message = "MessageDigest update error for signature input, type: IOException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }
        byte[] hash = messageDigest.digest();
        // If we add a time stamp:
        // Create the signature
        PdfPKCS7 sgn;
        try {

            sgn = new PdfPKCS7(key, chain, configuration.getSignCertificate().getSignatureHashAlgorithm().value() , null, new BouncyCastleDigest(), false);
        } catch (InvalidKeyException ex) {
            String message = "Certificate PDF sign error for signature input, type: InvalidKeyException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        } catch (NoSuchProviderException ex) {
            String message = "Certificate PDF sign error for signature input, type: NoSuchProviderException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        } catch (NoSuchAlgorithmException ex) {
            String message = "Certificate PDF sign error for signature input, type: NoSuchAlgorithmException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }catch (Exception ex) {
            String message = "Certificate PDF sign error for signature input, type: Exception";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }
        byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null,null, MakeSignature.CryptoStandard.CMS);
        try {
            sgn.update(sh, 0, sh.length);
        } catch (java.security.SignatureException ex) {
            String message = "Certificate PDF sign error for signature input, type: SignatureException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }
        byte[] encodedSig = sgn.getEncodedPKCS7(hash);
        if (contentEstimated + 2 < encodedSig.length) {
            String message = "The estimated size for the signature is smaller than the required one. Terminating request..";
            signLogger.log("ERROR", message);
            throw new CustomException(message);
        }
        byte[] paddedSig = new byte[contentEstimated];
        System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
        // Replace the contents
        PdfDictionary dic2 = new PdfDictionary();
        dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
        try {
            signatureAppearance.close(dic2);
        } catch (IOException ex) {
            String message = "PdfSignatureAppearance close error for signature input, type: IOException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        } catch (DocumentException ex) {
            String message = "PdfSignatureAppearance close error for signature input, type: DocumentException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }

Pour la validation PDF / A, j'utilise la bibliothèque VeraPDF.

Il peut également être utile de mentionner que si la bibliothèque VeraPDF signale une bibliothèque PDF / A corrompue, les outils de validation d'Adobe Reader signalent que le document PDF / A n'est pas corrompu.

Toute aide serait très appréciée.

2
Peter Veselinović 20 avril 2017 à 17:29

3 réponses

Meilleure réponse

Lors de la signature numérique d'un document avec itext v5.5.11, les documents PDF / A-2b sont corrompus, ce qui signifie qu'ils ne sont plus valides en tant que documents PDF / A. La règle suivante n'est pas respectée: https://github.com/veraPDF/veraPDF-validation-profiles/wiki/PDFA-Parts-2-and-3-rules#rule-643-1

Bien que ce soit effectivement ce que prétend veraPDF, c'est faux; iText crée des signatures couvrant toute leur révision moins l'espace réservé au conteneur de signature.

La raison de cette détection de violation incorrecte est une erreur dans veraPDF.

Comment veraPDF détermine si les plages d'octets signées sont valides

La version veryPDF (celle basée sur l'analyseur greenfield et celle basée sur PDFBox) tentent de déterminer une valeur nominale de plages d'octets et de la comparer à la valeur réelle. Voici comment il détermine la valeur nominale:

public long[] getByteRangeBySignatureOffset(long signatureOffset) throws IOException {
    pdfSource.seek(signatureOffset);
    skipID();
    byteRange[0] = 0;
    parseDictionary();
    byteRange[3] = getOffsetOfNextEOF(byteRange[2]) - byteRange[2];
    return byteRange;
}

private long getOffsetOfNextEOF(long currentOffset) throws IOException {
    byte[] buffer = new byte[EOF_STRING.length];
    pdfSource.seek(currentOffset + document.getHeaderOffset());
    readWholeBuffer(pdfSource, buffer);
    pdfSource.rewind(buffer.length - 1);
    while (!Arrays.equals(buffer, EOF_STRING)) {    //TODO: does it need to be optimized?
        readWholeBuffer(pdfSource, buffer);
        if (pdfSource.isEOF()) {
            pdfSource.seek(currentOffset + document.getHeaderOffset());
            return pdfSource.length();
        }
        pdfSource.rewind(buffer.length - 1);
    }
    long result = pdfSource.getPosition() + buffer.length - 1;  // offset of byte after 'F'
    pdfSource.seek(currentOffset + document.getHeaderOffset());
    return result - 1;
}

(classe basée sur PDFBox SignatureParser)

public long[] getByteRangeBySignatureOffset(long signatureOffset) throws IOException {
    source.seek(signatureOffset);
    skipID();
    byteRange[0] = 0;
    parseDictionary();
    byteRange[3] = getOffsetOfNextEOF(byteRange[2]) - byteRange[2];
    return byteRange;
}

private long getOffsetOfNextEOF(long currentOffset) throws IOException {
    byte[] buffer = new byte[EOF_STRING.length];
    source.seek(currentOffset + document.getHeader().getHeaderOffset());
    source.read(buffer);
    source.unread(buffer.length - 1);
    while (!Arrays.equals(buffer, EOF_STRING)) {    //TODO: does it need to be optimized?
        source.read(buffer);
        if (source.isEOF()) {
            source.seek(currentOffset + document.getHeader().getHeaderOffset());
            return source.getStreamLength();
        }
        source.unread(buffer.length - 1);
    }
    long result = source.getOffset() - 1 + buffer.length;   // byte right after 'F'
    source.seek(currentOffset + document.getHeader().getHeaderOffset());
    return result - 1;
}

(basé sur un analyseur greenfield SignatureParser)

Essentiellement, les deux implémentations font la même chose ici, en commençant à la signature, elles recherchent la prochaine occurrence du marqueur de fin de fichier %%EOF et tentent de compléter la valeur nominale des plages d'octets de sorte que la deuxième plage se termine par ce marqueur.

Pourquoi c'est faux

Il y a plusieurs raisons pour lesquelles cette façon de déterminer la valeur nominale des plages d'octets signés est incorrecte:

  1. Selon les spécifications PDF / A,

    Aucune donnée ne peut suivre le dernier marqueur de fin de fichier à l'exception d'un seul marqueur de fin de ligne facultatif comme décrit dans ISO 32000-1: 2008, 7.5.5.

    Ainsi, l'offset directement après le prochain marqueur de fin de fichier %%EOF n'est pas nécessairement déjà la fin de la révision signée , l'offset correct peut être celui après une fin de -ligne marqueur! Et comme un marqueur de fin de ligne PDF peut être soit un CR unique, soit un seul LF ou une combinaison CRLF, cela signifie que veraPDF choisit l'un des trois décalages possibles et prétend qu'il s'agit de la fin nominale de la révision. et, par conséquent, la fin nominale des plages d'octets signés .

  2. Il est possible (même si rarement vu) qu'une valeur de signature soit préparée dans une révision (se terminant par un marqueur de fin de fichier), puis certaines données sont ajoutées dans une mise à jour incrémentielle donnant lieu à une nouvelle révision (se terminant par une autre marqueur de fin de fichier), puis la valeur de signature est remplie avec les valeurs signant le document incluant cette nouvelle révision.

    Comme veraPDF utilise le marqueur de fin de fichier suivant après le dictionnaire de signatures , dans cette situation, veraPDF choisit en fait le mauvais marqueur de fin de fichier .

  3. Le marqueur de fin de fichier %%EOF syntaxiquement est en fait simplement un commentaire avec une signification spéciale à la fin d'un PDF / révision, et les commentaires sont autorisés presque partout dans un PDF en dehors des chaînes PDF, des données de flux PDF et du PDF tableaux de références croisées. Ainsi, la séquence d'octets %%EOF peut se produire comme un commentaire normal ou comme un contenu sans commentaire d'une chaîne ou d'un flux autant de fois que vous le souhaitez entre le dictionnaire de valeurs de signature et la fin réelle de la révision signée.

    S'il y a une telle occurrence, veraPDF choisit une séquence d'octets comme marqueur de fin de fichier qui n'a jamais été conçu comme une fin de quelque chose .

De plus, à moins que la fin de fichier réelle ne soit atteinte dans la boucle (et que pdfSource.length() / source.getStreamLength() soit renvoyé), le résultat semble être décalé, le - 1 dans return result - 1 ne correspond pas à l'utilisation du résultat.

Versions de veraPDF

J'ai vérifié par rapport aux versions 1.5.0-SNAPSHOT actuelles de veraPDF qui sont marquées:

  • veraPDF-pdfbox-validation 1.5.4
  • veraPDF-validation 1.5.2
  • veraPDF-parser 1.5.1

Le document d'exemple du PO

L'exemple de document fourni par l'OP a un LF après le marqueur de fin de fichier. En raison de cela et du problème de décalage mentionné ci-dessus, veraPDF détermine une fin de plage d'octets signée nominale qui est courte de deux octets.

3
mkl 21 avril 2017 à 08:53

Je suis d'accord avec l'analyse de la façon dont veraPDF vérifie la ByteRange pour le moment. En effet, cela suppose que le fichier se termine exactement au marqueur% EOF qui suit immédiatement le champ de signature.

La raison est plutot simple. Le document peut être signé séquentiellement par plusieurs personnes et peut toujours être un document PDF / A-2B valide. Lorsque la deuxième signature est générée, il met à jour de manière incrémentielle le fichier contenant la première signature.

Donc, si nous interprétons littéralement le terme sentir dans les exigences PDF / A-2B:

Lors du calcul du condensé du fichier, il doit être calculé sur l'ensemble du fichier , y compris le dictionnaire de signatures, mais à l'exclusion de la signature PDF elle-même. Cette plage est ensuite indiquée par l'entrée ByteRange du dictionnaire de signatures.

Nous ne serons jamais en mesure de créer un fichier PDF / A valide avec plusieurs signatures. Ce n'était clairement pas une intention de la norme PDF / A-2.

Le fichier PDF est généralement compris comme une plage d'octets entre le% PDF de début et le% EOF de fin pour permettre, par exemple, que les fichiers PDF fassent partie d'un flux d'octets plus grand (par exemple, les pièces jointes aux e-mails). C'est sur quoi repose l'implémentation de veraPDF.

Je conviens cependant que cette approche ne prend pas en compte la séquence de fin de ligne optionnelle après% EOF. J'ai créé le problème correspondant pour veraPDF: https://github.com/veraPDF/ veraPDF-validation / issues / 166

Cela laisse cependant une question intéressante: qu'est-ce qu'une plage d'octets valide de la première signature au cas où le document aurait plus de signatures? Je crois, dans tous les cas:

  • ByteRange couvre le fichier jusqu'au prochain marqueur% EOF suivant
  • ByteRange couvre le fichier jusqu'au prochain marqueur% EOF suivant + un seul caractère CR
  • ByteRange couvre le fichier jusqu'au marqueur% EOF suivant + un seul caractère LF
  • ByteRange couvre le fichier jusqu'au marqueur% EOF suivant + une séquence CR + LF de deux octets

Devrait être permis.

0
Boris Doubrov 21 avril 2017 à 19:51

Comme indiqué ci-dessus, nous venons de publier le correctif pour veraPDF 1.4 qui résout les problèmes de cette discussion. La nouvelle version est disponible au téléchargement: http: // téléchargements. verapdf.org/rel/1.4/verapdf-1.4.5-installer.zip

En particulier, les documents PDF / A-2 signés iText semblent très bien passer la validation veraPDF.

2
Boris Doubrov 28 avril 2017 à 07:31