You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2019/06/01 17:48:27 UTC

svn commit: r1860486 - in /pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption: SaslPrep.java StandardSecurityHandler.java

Author: tilman
Date: Sat Jun  1 17:48:27 2019
New Revision: 1860486

URL: http://svn.apache.org/viewvc?rev=1860486&view=rev
Log:
PDFBOX-4155: use SASLprep algorithm implementation by Tom Bentley for revision 6, as suggested by Marc Kaufman

Added:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SaslPrep.java   (with props)
Modified:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java

Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SaslPrep.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SaslPrep.java?rev=1860486&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SaslPrep.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SaslPrep.java Sat Jun  1 17:48:27 2019
@@ -0,0 +1,339 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.pdmodel.encryption;
+
+import java.nio.CharBuffer;
+import java.text.Normalizer;
+
+/**
+ * Copied from https://github.com/tombentley/saslprep/blob/master/src/main/java/SaslPrep.java on
+ * 30.5.2019, commit 2e30daa.
+ *
+ * @author Tom Bentley
+ */
+class SaslPrep
+{
+
+    /**
+     * Return the {@code SASLPrep}-canonicalised version of the given {@code str} for use as a query
+     * string. This implements the {@code SASLPrep} algorithm defined in
+     * <a href="https://tools.ietf.org/html/rfc4013">RFC 4013</a>.
+     *
+     * @param str The string to canonicalise.
+     * @return The canonicalised string.
+     * @throws IllegalArgumentException if the string contained prohibited codepoints, or broke the
+     * requirements for bidirectional character handling.
+     * @see <a href="https://tools.ietf.org/html/rfc3454#section-7">RFC 3454, Section 7</a> for
+     * discussion of what a query string is.
+     */
+    static String saslPrepQuery(String str)
+    {
+        return saslPrep(str, true);
+    }
+
+    /**
+     * Return the {@code SASLPrep}-canonicalised version of the given
+     * @code str} for use as a stored string. This implements the {@code SASLPrep} algorithm defined
+     * in
+     * <a href="https://tools.ietf.org/html/rfc4013">RFC 4013</a>.
+     *
+     * @param str The string to canonicalise.
+     * @return The canonicalised string.
+     * @throws IllegalArgumentException if the string contained prohibited codepoints, or broke the
+     * requirements for bidirectional character handling.
+     * @see <a href="https://tools.ietf.org/html/rfc3454#section-7">RFC 3454, Section 7</a> for
+     * discussion of what a stored string is.
+     */
+    static String saslPrepStored(String str)
+    {
+        return saslPrep(str, false);
+    }
+
+    private static String saslPrep(String str, boolean allowUnassigned)
+    {
+        char[] chars = str.toCharArray();
+
+        // 1. Map
+        // non-ASCII space chars mapped to space
+        for (int i = 0, j = 0; i < str.length(); i++)
+        {
+            char ch = str.charAt(i);
+            if (nonAsciiSpace(ch))
+            {
+                chars[i] = ' ';
+            }
+        }
+
+        int length = 0;
+        for (int i = 0; i < str.length(); i++)
+        {
+            char ch = chars[i];
+            if (!mappedToNothing(ch))
+            {
+                chars[length++] = ch;
+            }
+        }
+
+        // 2. Normalize
+        String normalized = Normalizer.normalize(CharBuffer.wrap(chars, 0, length), Normalizer.Form.NFKC);
+
+        boolean containsRandALCat = false;
+        boolean containsLCat = false;
+        boolean initialRandALCat = false;
+        for (int i = 0; i < normalized.length();)
+        {
+            final int codepoint = normalized.codePointAt(i);
+            // 3. Prohibit
+            if (prohibited(codepoint))
+            {
+                throw new IllegalArgumentException("Prohibited character '" +
+                        Character.getName(codepoint) + "' at position " + i);
+            }
+
+            // 4. Check bidi
+            final byte directionality = Character.getDirectionality(codepoint);
+            final boolean isRandALcat = directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT
+                    || directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC;
+            containsRandALCat |= isRandALcat;
+            containsLCat |= directionality == Character.DIRECTIONALITY_LEFT_TO_RIGHT;
+
+            initialRandALCat |= i == 0 && isRandALcat;
+            if (!allowUnassigned && !Character.isDefined(codepoint))
+            {
+                throw new IllegalArgumentException("Character at position " + i + " is unassigned");
+            }
+
+            i += Character.charCount(codepoint);
+
+            if (initialRandALCat && i >= normalized.length() && !isRandALcat)
+            {
+                throw new IllegalArgumentException("First character is RandALCat, but last character is not");
+            }
+        }
+        if (containsRandALCat && containsLCat)
+        {
+            throw new IllegalArgumentException("Contains both RandALCat characters and LCat characters");
+        }
+        return normalized;
+    }
+
+    /**
+     * Return true if the given {@code codepoint} is a prohibited character
+     * as defined by
+     * <a href="https://tools.ietf.org/html/rfc4013#section-2.3">RFC 4013,
+     * Section 2.3</a>.
+     */
+    static boolean prohibited(int codepoint)
+    {
+        return nonAsciiSpace((char)codepoint)
+                || asciiControl((char)codepoint)
+                || nonAsciiControl(codepoint)
+                || privateUse(codepoint)
+                || nonCharacterCodePoint(codepoint)
+                || surrogateCodePoint(codepoint)
+                || inappropriateForPlainText(codepoint)
+                || inappropriateForCanonical(codepoint)
+                || changeDisplayProperties(codepoint)
+                || tagging(codepoint);
+    }
+
+    /**
+     * Return true if the given {@code codepoint} is a tagging character
+     * as defined by
+     * <a href="https://tools.ietf.org/html/rfc3454#appendix-C.9">RFC 3454,
+     * Appendix C.9</a>.
+     */
+    private static boolean tagging(int codepoint)
+    {
+        return codepoint == 0xE0001
+                || 0xE0020 <= codepoint && codepoint <= 0xE007F;
+    }
+
+    /**
+     * Return true if the given {@code codepoint} is change display properties
+     * or deprecated characters as defined by
+     * <a href="https://tools.ietf.org/html/rfc3454#appendix-C.8">RFC 3454,
+     * Appendix C.8</a>.
+     */
+    private static boolean changeDisplayProperties(int codepoint)
+    {
+        return codepoint == 0x0340
+                || codepoint == 0x0341
+                || codepoint == 0x200E
+                || codepoint == 0x200F
+                || codepoint == 0x202A
+                || codepoint == 0x202B
+                || codepoint == 0x202C
+                || codepoint == 0x202D
+                || codepoint == 0x202E
+                || codepoint == 0x206A
+                || codepoint == 0x206B
+                || codepoint == 0x206C
+                || codepoint == 0x206D
+                || codepoint == 0x206E
+                || codepoint == 0x206F
+                ;
+    }
+
+    /**
+     * Return true if the given {@code codepoint} is inappropriate for
+     * canonical representation characters as defined by
+     * <a href="https://tools.ietf.org/html/rfc3454#appendix-C.7">RFC 3454,
+     * Appendix C.7</a>.
+     */
+    private static boolean inappropriateForCanonical(int codepoint)
+    {
+        return 0x2FF0 <= codepoint && codepoint <= 0x2FFB;
+    }
+
+    /**
+     * Return true if the given {@code codepoint} is inappropriate for plain
+     * text characters as defined by
+     * <a href="https://tools.ietf.org/html/rfc3454#appendix-C.6">RFC 3454,
+     * Appendix C.6</a>.
+     */
+    private static boolean inappropriateForPlainText(int codepoint)
+    {
+        return codepoint == 0xFFF9
+                || codepoint == 0xFFFA
+                || codepoint == 0xFFFB
+                || codepoint == 0xFFFC
+                || codepoint == 0xFFFD
+                ;
+    }
+
+    /**
+     * Return true if the given {@code codepoint} is a surrogate
+     * code point as defined by
+     * <a href="https://tools.ietf.org/html/rfc3454#appendix-C.5">RFC 3454,
+     * Appendix C.5</a>.
+     */
+    private static boolean surrogateCodePoint(int codepoint)
+    {
+        return 0xD800 <= codepoint && codepoint <= 0xDFFF;
+    }
+
+    /**
+     * Return true if the given {@code codepoint} is a non-character
+     * code point as defined by
+     * <a href="https://tools.ietf.org/html/rfc3454#appendix-C.4">RFC 3454,
+     * Appendix C.4</a>.
+     */
+    private static boolean nonCharacterCodePoint(int codepoint)
+    {
+        return 0xFDD0 <= codepoint && codepoint <= 0xFDEF
+                || 0xFFFE <= codepoint && codepoint <= 0xFFFF
+                || 0x1FFFE <= codepoint && codepoint <= 0x1FFFF
+                || 0x2FFFE <= codepoint && codepoint <= 0x2FFFF
+                || 0x3FFFE <= codepoint && codepoint <= 0x3FFFF
+                || 0x4FFFE <= codepoint && codepoint <= 0x4FFFF
+                || 0x5FFFE <= codepoint && codepoint <= 0x5FFFF
+                || 0x6FFFE <= codepoint && codepoint <= 0x6FFFF
+                || 0x7FFFE <= codepoint && codepoint <= 0x7FFFF
+                || 0x8FFFE <= codepoint && codepoint <= 0x8FFFF
+                || 0x9FFFE <= codepoint && codepoint <= 0x9FFFF
+                || 0xAFFFE <= codepoint && codepoint <= 0xAFFFF
+                || 0xBFFFE <= codepoint && codepoint <= 0xBFFFF
+                || 0xCFFFE <= codepoint && codepoint <= 0xCFFFF
+                || 0xDFFFE <= codepoint && codepoint <= 0xDFFFF
+                || 0xEFFFE <= codepoint && codepoint <= 0xEFFFF
+                || 0xFFFFE <= codepoint && codepoint <= 0xFFFFF
+                || 0x10FFFE <= codepoint && codepoint <= 0x10FFFF
+                ;
+    }
+
+    /**
+     * Return true if the given {@code codepoint} is a private use character
+     * as defined by <a href="https://tools.ietf.org/html/rfc3454#appendix-C.3">RFC 3454,
+     * Appendix C.3</a>.
+     */
+    private static boolean privateUse(int codepoint)
+    {
+        return 0xE000 <= codepoint && codepoint <= 0xF8FF
+                || 0xF000 <= codepoint && codepoint <= 0xFFFFD
+                || 0x100000 <= codepoint && codepoint <= 0x10FFFD;
+    }
+
+    /**
+     * Return true if the given {@code ch} is a non-ASCII control character
+     * as defined by <a href="https://tools.ietf.org/html/rfc3454#appendix-C.2.2">RFC 3454,
+     * Appendix C.2.2</a>.
+     */
+    private static boolean nonAsciiControl(int codepoint)
+    {
+        return 0x0080 <= codepoint && codepoint <= 0x009F
+                || codepoint == 0x06DD
+                || codepoint == 0x070F
+                || codepoint == 0x180E
+                || codepoint == 0x200C
+                || codepoint == 0x200D
+                || codepoint == 0x2028
+                || codepoint == 0x2029
+                || codepoint == 0x2060
+                || codepoint == 0x2061
+                || codepoint == 0x2062
+                || codepoint == 0x2063
+                || 0x206A <= codepoint && codepoint <= 0x206F
+                || codepoint == 0xFEFF
+                || 0xFFF9 <= codepoint && codepoint <= 0xFFFC
+                || 0x1D173 <= codepoint && codepoint <= 0x1D17A;
+    }
+
+    /**
+     * Return true if the given {@code ch} is an ASCII control character
+     * as defined by <a href="https://tools.ietf.org/html/rfc3454#appendix-C.2.1">RFC 3454,
+     * Appendix C.2.1</a>.
+     */
+    private static boolean asciiControl(char ch)
+    {
+        return '\u0000' <= ch && ch <= '\u001F' || ch == '\u007F';
+    }
+
+    /**
+     * Return true if the given {@code ch} is a non-ASCII space character
+     * as defined by <a href="https://tools.ietf.org/html/rfc3454#appendix-C.1.2">RFC 3454,
+     * Appendix C.1.2</a>.
+     */
+    private static boolean nonAsciiSpace(char ch)
+    {
+        return ch == '\u00A0'
+                || ch == '\u1680'
+                || '\u2000' <= ch && ch <= '\u200B'
+                || ch == '\u202F'
+                || ch == '\u205F'
+                || ch == '\u3000';
+    }
+
+    /**
+     * Return true if the given {@code ch} is a "commonly mapped to nothing" character
+     * as defined by <a href="https://tools.ietf.org/html/rfc3454#appendix-B.1">RFC 3454,
+     * Appendix B.1</a>.
+     */
+    private static boolean mappedToNothing(char ch)
+    {
+        return ch == '\u00AD'
+                || ch == '\u034F'
+                || ch == '\u1806'
+                || ch == '\u180B'
+                || ch == '\u180C'
+                || ch == '\u180D'
+                || ch == '\u200B'
+                || ch == '\u200C'
+                || ch == '\u200D'
+                || ch == '\u2060'
+                || '\uFE00' <= ch && ch <= '\uFE0F'
+                || ch == '\uFEFF';
+    }
+}
\ No newline at end of file

Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SaslPrep.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java?rev=1860486&r1=1860485&r2=1860486&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java Sat Jun  1 17:48:27 2019
@@ -211,7 +211,12 @@ public final class StandardSecurityHandl
             ue = encryption.getUserEncryptionKey();
             oe = encryption.getOwnerEncryptionKey();
         }
-        
+
+        if (dicRevision == 6)
+        {
+            password = SaslPrep.saslPrepQuery(password); // PDFBOX-4155
+        }
+
         AccessPermission currentAccessPermission;
 
         if( isOwnerPassword(password.getBytes(passwordCharset), userKey, ownerKey,
@@ -396,6 +401,9 @@ public final class StandardSecurityHandl
 
         if (revision == 6)
         {
+            // PDFBOX-4155
+            ownerPassword = SaslPrep.saslPrepStored(ownerPassword);
+            userPassword = SaslPrep.saslPrepStored(userPassword);
             prepareEncryptionDictRev6(ownerPassword, userPassword, encryptionDictionary, permissionInt);
         }
         else