From b57960e67b69040394c9dcb687af52305fd47ab8 Mon Sep 17 00:00:00 2001
From: Manuel Gaupp <m.gaupp@scanplus.de>
Date: Mon, 13 Jan 2014 09:45:09 +0000
Subject: [PATCH] CR-1602 (OPENDJ-883) Implement certificateExactMatch matching rule

---
 opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/asn1/GSERParserTestCase.java       |  380 ++++++++++
 opends/src/server/org/opends/server/schema/CertificateExactMatchingRuleFactory.java                      |   72 ++
 opends/src/server/org/opends/server/protocols/asn1/GSERParser.java                                       |  476 +++++++++++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/schema/CertificateExactMatchingRuleTest.java |  245 ++++++
 opends/src/server/org/opends/server/protocols/asn1/GSERException.java                                    |   77 ++
 opends/src/messages/messages/protocol.properties                                                         |   15 
 opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java                 |   59 +
 opends/src/messages/messages/schema.properties                                                           |   15 
 opends/resource/config/config.ldif                                                                       |   10 
 opends/src/server/org/opends/server/backends/jeb/AttributeIndex.java                                     |    7 
 opends/src/server/org/opends/server/types/SearchFilter.java                                              |   53 +
 opends/src/server/org/opends/server/schema/SchemaConstants.java                                          |   49 +
 opends/src/server/org/opends/server/schema/CertificateExactAssertionSyntax.java                          |  215 +++++
 opends/src/server/org/opends/server/schema/CertificateSyntax.java                                        |    5 
 opends/src/server/org/opends/server/schema/CertificateExactMatchingRule.java                             |  418 +++++++++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/controls/MatchedValuesControlTest.java       |   48 +
 opends/resource/schema/00-core.ldif                                                                      |    4 
 opends/src/server/org/opends/server/controls/MatchedValuesFilter.java                                    |   11 
 18 files changed, 2,138 insertions(+), 21 deletions(-)

diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index 5c7fe98..dd2d7a4 100644
--- a/opends/resource/config/config.ldif
+++ b/opends/resource/config/config.ldif
@@ -21,7 +21,7 @@
 #
 #      Copyright 2006-2010 Sun Microsystems, Inc.
 #      Portions Copyright 2010-2013 ForgeRock AS.
-#      Portions Copyright 2012-2013 Manuel Gaupp
+#      Portions Copyright 2012-2014 Manuel Gaupp
 #
 #
 # This file contains the primary Directory Server configuration.  It must not
@@ -1006,6 +1006,14 @@
 ds-cfg-java-class: org.opends.server.schema.CaseIgnoreListSubstringMatchingRuleFactory
 ds-cfg-enabled: true
 
+dn: cn=Certificate Exact Matching Rule,cn=Matching Rules,cn=config
+objectClass: top
+objectClass: ds-cfg-matching-rule
+objectClass: ds-cfg-equality-matching-rule
+cn: Certificate Exact Matching Rule
+ds-cfg-java-class: org.opends.server.schema.CertificateExactMatchingRuleFactory
+ds-cfg-enabled: true
+
 dn: cn=Collation Matching Rule,cn=Matching Rules,cn=config
 objectClass: top
 objectClass: ds-cfg-matching-rule
diff --git a/opends/resource/schema/00-core.ldif b/opends/resource/schema/00-core.ldif
index 3581ade..22b9777 100644
--- a/opends/resource/schema/00-core.ldif
+++ b/opends/resource/schema/00-core.ldif
@@ -22,7 +22,7 @@
 #
 #      Copyright 2006-2010 Sun Microsystems, Inc.
 #      Portions Copyright 2011-2012 ForgeRock AS
-#      Portions Copyright 2013 Manuel Gaupp
+#      Portions Copyright 2013-2014 Manuel Gaupp
 #
 #
 # This file contains a core set of attribute type and objectlass definitions
@@ -129,8 +129,10 @@
 attributeTypes: ( 2.5.4.35 NAME 'userPassword'
   SYNTAX 1.3.6.1.4.1.26027.1.3.1 X-ORIGIN 'RFC 4519' )
 attributeTypes: ( 2.5.4.36 NAME 'userCertificate'
+  EQUALITY certificateExactMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 X-ORIGIN 'RFC 4523' )
 attributeTypes: ( 2.5.4.37 NAME 'cACertificate'
+  EQUALITY certificateExactMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 X-ORIGIN 'RFC 4523' )
 attributeTypes: ( 2.5.4.38 NAME 'authorityRevocationList'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 X-ORIGIN 'RFC 4523' )
diff --git a/opends/src/messages/messages/protocol.properties b/opends/src/messages/messages/protocol.properties
index cc1fb4a..cc3b1b3 100644
--- a/opends/src/messages/messages/protocol.properties
+++ b/opends/src/messages/messages/protocol.properties
@@ -21,6 +21,7 @@
 #
 #      Copyright 2006-2009 Sun Microsystems, Inc.
 #      Portions copyright 2013 ForgeRock AS
+#      Portions copyright 2013-2014 Manuel Gaupp
 
 
 
@@ -892,3 +893,17 @@
 MILD_ERR_CONNHANDLER_CONFIG_CHANGES_REQUIRE_RESTART_1516=The server received \
  configuration changes that require a restart of the %s connection handler \
  to take effect
+MILD_ERR_GSER_PATTERN_NO_MATCH_1517=The GSER value does not contain a \
+ String matching the pattern %s at the current position: %s
+MILD_ERR_GSER_NO_VALID_SEPARATOR_1518=The GSER value does not contain a \
+ separator at the current position: %s
+MILD_ERR_GSER_NO_VALID_STRING_1519=The GSER value does not contain a valid \
+ String value at the current position: %s
+MILD_ERR_GSER_NO_VALID_INTEGER_1520=The GSER value does not contain a valid \
+ integer value at the current position: %s
+MILD_ERR_GSER_NO_VALID_IDENTIFIER_1521=The GSER value does not contain a \
+ valid identifier at the current position: %s
+MILD_ERR_GSER_SPACE_CHAR_EXPECTED_1522=The GSER value does not contain a \
+ whitespace character at the current position: %s
+MILD_ERR_GSER_NO_VALID_IDENTIFIEDCHOICE_1523=The GSER value does not \
+ contain a valid IdentifiedChoiceValue at the current position: %s
diff --git a/opends/src/messages/messages/schema.properties b/opends/src/messages/messages/schema.properties
index ff59978..e1ec32c 100644
--- a/opends/src/messages/messages/schema.properties
+++ b/opends/src/messages/messages/schema.properties
@@ -21,7 +21,7 @@
 #
 #      Copyright 2006-2010 Sun Microsystems, Inc.
 #      Portions Copyright 2011 ForgeRock AS
-#      Portions Copyright 2012 Manuel Gaupp
+#      Portions Copyright 2012-2014 Manuel Gaupp
 
 
 #
@@ -894,4 +894,15 @@
 MILD_ERR_ATTR_SYNTAX_COUNTRY_NO_VALID_ISO_CODE_333=The provided value "%s" \
  is not a valid ISO 3166 country code
 SEVERE_ERR_ATTR_SYNTAX_ILLEGAL_X_SCHEMA_FILE_334=The provided value "%s" is \
- not safe for X-SCHEMA-FILE
\ No newline at end of file
+ not safe for X-SCHEMA-FILE
+SEVERE_WARN_CERTIFICATE_MATCH_PARSE_ERROR_335=The value could not be parsed as \
+ an X.509 certificate: "%s"
+MILD_ERR_CERTIFICATE_MATCH_INVALID_DN_336=The provided value "%s" could \
+ not be parsed as a valid distinguished name because an error occurred while \
+ trying to parse the DN portion:  %s
+MILD_ERR_CERTIFICATE_MATCH_IDENTIFIER_NOT_FOUND_337=The identifier "%s" could \
+ not be found at the correct position
+MILD_ERR_CERTIFICATE_MATCH_EXPECTED_END_338=The GSER value contains additional \
+ characters at the end of the assertion
+MILD_ERR_CERTIFICATE_MATCH_GSER_INVALID_339=An error occured while parsing the \
+ the GSER String: "%s"
diff --git a/opends/src/server/org/opends/server/backends/jeb/AttributeIndex.java b/opends/src/server/org/opends/server/backends/jeb/AttributeIndex.java
index f63eb58..2b7b651 100644
--- a/opends/src/server/org/opends/server/backends/jeb/AttributeIndex.java
+++ b/opends/src/server/org/opends/server/backends/jeb/AttributeIndex.java
@@ -23,6 +23,7 @@
  *
  *      Copyright 2006-2010 Sun Microsystems, Inc.
  *      Portions Copyright 2011-2013 ForgeRock AS
+ *      Portions Copyright 2014 Manuel Gaupp
  */
 package org.opends.server.backends.jeb;
 import org.opends.messages.Message;
@@ -971,8 +972,10 @@
     try
     {
       // Make a key from the normalized assertion value.
-      byte[] keyBytes =
-          equalityFilter.getAssertionValue().getNormalizedValue().toByteArray();
+      EqualityMatchingRule equalityRule = equalityFilter.getAttributeType().
+        getEqualityMatchingRule();
+      byte[] keyBytes = equalityRule.normalizeAssertionValue(equalityFilter.
+        getAssertionValue().getValue()).toByteArray();
       DatabaseEntry key = new DatabaseEntry(keyBytes);
 
       if(debugBuffer != null)
diff --git a/opends/src/server/org/opends/server/controls/MatchedValuesFilter.java b/opends/src/server/org/opends/server/controls/MatchedValuesFilter.java
index 52af02a..8d49f56 100644
--- a/opends/src/server/org/opends/server/controls/MatchedValuesFilter.java
+++ b/opends/src/server/org/opends/server/controls/MatchedValuesFilter.java
@@ -22,6 +22,7 @@
  *
  *
  *      Copyright 2006-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2013-2014 Manuel Gaupp
  */
 package org.opends.server.controls;
 import org.opends.messages.Message;
@@ -1321,14 +1322,14 @@
     {
       case EQUALITY_MATCH_TYPE:
         if ((attributeType != null) && (type != null) &&
-            attributeType.equals(type) && (assertionValue != null) &&
+            attributeType.equals(type) && (rawAssertionValue != null) &&
             (value != null) && (equalityMatchingRule != null))
         {
           try
           {
             return equalityMatchingRule.areEqual(
-                        assertionValue.getNormalizedValue(),
-                        value.getNormalizedValue());
+              equalityMatchingRule.normalizeAssertionValue(rawAssertionValue),
+              value.getNormalizedValue());
           }
           catch (Exception e)
           {
@@ -1509,8 +1510,8 @@
           try
           {
             return equalityMatchingRule.areEqual(
-                        assertionValue.getNormalizedValue(),
-                        value.getNormalizedValue());
+              equalityMatchingRule.normalizeAssertionValue(rawAssertionValue),
+              value.getNormalizedValue());
           }
           catch (Exception e)
           {
diff --git a/opends/src/server/org/opends/server/protocols/asn1/GSERException.java b/opends/src/server/org/opends/server/protocols/asn1/GSERException.java
new file mode 100644
index 0000000..841cb67
--- /dev/null
+++ b/opends/src/server/org/opends/server/protocols/asn1/GSERException.java
@@ -0,0 +1,77 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2013-2014 Manuel Gaupp
+ */
+package org.opends.server.protocols.asn1;
+
+
+
+import org.opends.messages.Message;
+import org.opends.server.types.IdentifiedException;
+
+
+
+/**
+ * This class defines an exception that may be thrown if a problem occurs while
+ * interacting with a GSER String.
+ */
+public final class GSERException
+       extends IdentifiedException
+{
+  /**
+   * The serial version identifier required to satisfy the compiler because this
+   * class extends <CODE>java.lang.Exception</CODE>, which implements the
+   * <CODE>java.io.Serializable</CODE> interface.  This value was generated
+   * using the <CODE>serialver</CODE> command-line utility included with the
+   * Java SDK.
+   */
+  private static final long serialVersionUID = 3655637382448481369L;
+
+
+
+  /**
+   * Creates a new GSER exception with the provided message.
+   *
+   * @param  message    The message that explains the problem that occurred.
+   */
+  public GSERException(Message message)
+  {
+    super(message);
+  }
+
+
+
+  /**
+   * Creates a new GSER exception with the provided message and root
+   * cause.
+   *
+   * @param  message    The message that explains the problem that occurred.
+   * @param  cause      The exception that was caught to trigger this exception.
+   */
+  public GSERException(Message message, Throwable cause)
+  {
+    super(message, cause);
+  }
+}
+
diff --git a/opends/src/server/org/opends/server/protocols/asn1/GSERParser.java b/opends/src/server/org/opends/server/protocols/asn1/GSERParser.java
new file mode 100644
index 0000000..ac33e1a
--- /dev/null
+++ b/opends/src/server/org/opends/server/protocols/asn1/GSERParser.java
@@ -0,0 +1,476 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2013-2014 Manuel Gaupp
+ */
+package org.opends.server.protocols.asn1;
+
+
+
+import java.math.BigInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.opends.messages.Message;
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.server.util.Validator.*;
+
+
+
+/**
+ * This class implements a parser for strings which are encoded using the
+ * Generic String Encoding Rules (GSER) defined in RFC 3641.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc3641">RFC 3641 -
+ *      Generic String Encoding Rules (GSER) for ASN.1 Types
+ *      </a>
+ */
+public class GSERParser
+{
+
+  private String gserValue;
+
+  private int pos;
+
+  private int length;
+
+  /**
+   * Pattern to match an identifier defined in RFC 3641, section 3.4.
+   * <pre>
+   * An &lt;identifier&gt; conforms to the definition of an identifier in ASN.1
+   * notation (Clause 11.3 of X.680 [8]).  It begins with a lowercase
+   * letter and is followed by zero or more letters, digits, and hyphens.
+   * A hyphen is not permitted to be the last character, nor is it to be
+   * followed by another hyphen.  The case of letters in an identifier is
+   * always significant.
+   *
+   *    identifier    = lowercase *alphanumeric *(hyphen 1*alphanumeric)
+   *    alphanumeric  = uppercase / lowercase / decimal-digit
+   *    uppercase     = %x41-5A  ; "A" to "Z"
+   *    lowercase     = %x61-7A  ; "a" to "z"
+   *    decimal-digit = %x30-39  ; "0" to "9"
+   *    hyphen        = "-"
+   * </pre>
+   */
+  private static Pattern GSER_IDENTIFIER = Pattern
+          .compile("^([a-z]([A-Za-z0-9]|(-[A-Za-z0-9]))*)");
+
+  /**
+   * Pattern to match the identifier part (including the colon) of an
+   * IdentifiedChoiceValue defined in RFC 3641, section 3.12.
+   * <pre>
+   *    IdentifiedChoiceValue = identifier ":" Value
+   * </pre>
+   */
+  private static Pattern GSER_CHOICE_IDENTIFIER = Pattern
+          .compile("^([a-z]([A-Za-z0-9]|(-[A-Za-z0-9]))*:)");
+
+
+
+  /**
+   * Pattern to match "sp", containing zero, one or more space characters.
+   * <pre>
+   *    sp = *%x20  ; zero, one or more space characters
+   * </pre>
+   */
+  private static Pattern GSER_SP = Pattern.compile("^( *)");
+
+
+
+  /**
+   * Pattern to match "msp", containing at least one space character.
+   * <pre>
+   *    msp = 1*%x20  ; one or more space characters
+   * </pre>
+   */
+  private static Pattern GSER_MSP = Pattern.compile("^( +)");
+
+
+
+  /**
+   * Pattern to match an Integer value.
+   */
+  private static Pattern GSER_INTEGER = Pattern.compile("^(\\d+)");
+
+
+
+  /**
+   * Pattern to match a GSER StringValue, defined in RFC 3641, section 3.2:
+   * <pre>
+   * Any embedded double quotes in the resulting UTF-8 character string
+   * are escaped by repeating the double quote characters.
+   *
+   * [...]
+   *
+   *    StringValue       = dquote *SafeUTF8Character dquote
+   *    dquote            = %x22 ; &quot; (double quote)
+   * </pre>
+   */
+  private static Pattern GSER_STRING = Pattern
+          .compile("^(\"([^\"]|(\"\"))*\")");
+
+
+
+  /**
+   * Pattern to match the beginning of a GSER encoded Sequence.
+   * <pre>
+   *    SequenceValue = ComponentList
+   *    ComponentList = "{" [ sp NamedValue *( "," sp NamedValue) ] sp "}"
+   * </pre>
+   */
+  private static Pattern GSER_SEQUENCE_START = Pattern.compile("^(\\{)");
+
+
+
+  /**
+   * Pattern to match the end of a GSER encoded Sequence.
+   * <pre>
+   *    SequenceValue = ComponentList
+   *    ComponentList = "{" [ sp NamedValue *( "," sp NamedValue) ] sp "}"
+   * </pre>
+   */
+  private static Pattern GSER_SEQUENCE_END = Pattern.compile("^(\\})");
+
+
+
+  /**
+   * Pattern to match the separator used in GSER encoded sequences.
+   */
+  private static Pattern GSER_SEP = Pattern.compile("^(,)");
+
+
+
+  /**
+   * Creates a new GSER Parser.
+   *
+   * @param value the GSER encoded String value
+   */
+  public GSERParser(String value)
+  {
+    ensureNotNull(value);
+    this.gserValue = value;
+    this.pos = 0;
+    this.length = value.length();
+  }
+
+
+
+  /**
+   * Determines if the GSER String contains at least one character to be read.
+   *
+   * @return <code>true</code> if there is at least one remaining character or
+   *         <code>false</code> otherwise.
+   */
+  public boolean hasNext()
+  {
+    return (pos < length);
+  }
+
+
+
+  /**
+   * Determines if the remaining GSER String matches the provided pattern.
+   *
+   * @param pattern the pattern to search for
+   *
+   * @return <code>true</code> if the remaining string matches the pattern or
+   *         <code>false</code> otherwise.
+   */
+  private boolean hasNext(Pattern pattern)
+  {
+    if (!hasNext())
+    {
+      return false;
+    }
+
+    Matcher matcher = pattern.matcher(gserValue.substring(pos,length));
+
+    return matcher.find();
+  }
+
+
+
+  /**
+   * Returns the String matched by the first capturing group of the pattern.
+   * The parser advances past the input matched by the first capturing group.
+   *
+   * @param pattern the pattern to search for
+   *
+   * @return the String matched by the first capturing group of the pattern
+   *
+   * @throws GSERException
+   *           If no match could be found
+   */
+  private String next(Pattern pattern) throws GSERException
+  {
+    Matcher matcher = pattern.matcher(gserValue.substring(pos,length));
+    if (matcher.find() &&  matcher.groupCount() >= 1)
+    {
+      pos += matcher.end(1);
+      return matcher.group(1);
+    }
+    else
+    {
+      Message msg = ERR_GSER_PATTERN_NO_MATCH.get(pattern.pattern(),
+                      gserValue.substring(pos,length));
+      throw new GSERException(msg);
+    }
+  }
+
+
+
+  /**
+   * Skips the input matched by the first capturing group.
+   *
+   * @param pattern the pattern to search for
+   *
+   * @throws GSERException
+   *           If no match could be found
+   */
+  private void skip(Pattern pattern) throws GSERException
+  {
+    Matcher matcher = pattern.matcher(gserValue.substring(pos,length));
+
+    if (matcher.find() && matcher.groupCount() >= 1)
+    {
+      pos += matcher.end(1);
+    }
+    else
+    {
+      Message msg = ERR_GSER_PATTERN_NO_MATCH.get(pattern.pattern(),
+                      gserValue.substring(pos,length));
+      throw new GSERException(msg);
+    }
+  }
+
+
+
+  /**
+   * Skips the input matching zero, one or more space characters.
+   *
+   * @return reference to this GSERParser
+   *
+   * @throws GSERException
+   *           If no match could be found
+   */
+  public GSERParser skipSP() throws GSERException
+  {
+    skip(GSER_SP);
+    return this;
+  }
+
+
+
+  /**
+   * Skips the input matching one or more space characters.
+   *
+   * @return reference to this GSERParser
+   *
+   * @throws GSERException
+   *           If no match could be found
+   */
+  public GSERParser skipMSP() throws GSERException
+  {
+    skip(GSER_MSP);
+    return this;
+  }
+
+
+
+  /**
+   * Skips the input matching the start of a sequence and subsequent space
+   * characters.
+   *
+   * @return reference to this GSERParser
+   *
+   * @throws GSERException
+   *           If the input does not match the start of a sequence
+   */
+  public GSERParser readStartSequence() throws GSERException
+  {
+    next(GSER_SEQUENCE_START);
+    skip(GSER_SP);
+    return this;
+  }
+
+
+
+  /**
+   * Skips the input matching the end of a sequence and preceding space
+   * characters.
+   *
+   * @return reference to this GSERParser
+   *
+   * @throws GSERException
+   *           If the input does not match the end of a sequence
+   */
+  public GSERParser readEndSequence() throws GSERException
+  {
+    skip(GSER_SP);
+    next(GSER_SEQUENCE_END);
+    return this;
+  }
+
+
+  /**
+   * Skips the input matching the separator pattern (",") and subsequenct space
+   * characters.
+   *
+   * @return reference to this GSERParser
+   *
+   * @throws GSERException
+   *           If the input does not match the separator pattern.
+   */
+  public GSERParser skipSeparator() throws GSERException
+  {
+    if (!hasNext(GSER_SEP))
+    {
+      Message msg = ERR_GSER_NO_VALID_SEPARATOR.get(gserValue
+                      .substring(pos,length));
+      throw new GSERException(msg);
+    }
+    skip(GSER_SEP);
+    skip(GSER_SP);
+    return this;
+  }
+
+
+
+  /**
+   * Returns the next element as a String.
+   *
+   * @return the input matching the String pattern
+   *
+   * @throws GSERException
+   *           If the input does not match the string pattern.
+   */
+  public String nextString() throws GSERException
+  {
+    if (!hasNext(GSER_STRING))
+    {
+      Message msg = ERR_GSER_NO_VALID_STRING.get(gserValue
+                      .substring(pos,length));
+      throw new GSERException(msg);
+    }
+
+    String str = next(GSER_STRING);
+
+    // Strip leading and trailing dquotes; unescape double dquotes
+    return str.substring(1, str.length() - 1).replace("\"\"","\"");
+  }
+
+
+  /**
+   * Returns the next element as an Integer.
+   *
+   * @return the input matching the integer pattern
+   *
+   * @throws GSERException
+   *           If the input does not match the integer pattern
+   */
+  public int nextInteger() throws GSERException
+  {
+    if (!hasNext(GSER_INTEGER))
+    {
+      Message msg = ERR_GSER_NO_VALID_INTEGER.get(gserValue
+                      .substring(pos,length));
+      throw new GSERException(msg);
+    }
+    return Integer.valueOf(next(GSER_INTEGER)).intValue();
+  }
+
+
+
+  /**
+   * Returns the next element as a BigInteger.
+   *
+   * @return the input matching the integer pattern
+   *
+   * @throws GSERException
+   *           If the input does not match the integer pattern
+   */
+  public BigInteger nextBigInteger() throws GSERException
+  {
+    if (!hasNext(GSER_INTEGER))
+    {
+      Message msg = ERR_GSER_NO_VALID_INTEGER.get(gserValue
+                      .substring(pos,length));
+      throw new GSERException(msg);
+    }
+    return new BigInteger(next(GSER_INTEGER));
+  }
+
+
+  /**
+   * Returns the identifier of the next NamedValue element.
+   *
+   * @return the identifier of the NamedValue element
+   *
+   * @throws GSERException
+   *           If the input does not match the identifier pattern of a
+   *           NamedValue
+   */
+  public String nextNamedValueIdentifier() throws GSERException
+  {
+    if (!hasNext(GSER_IDENTIFIER))
+    {
+      Message msg = ERR_GSER_NO_VALID_IDENTIFIER.get(gserValue
+                      .substring(pos,length));
+      throw new GSERException(msg);
+    }
+    String identifier = next(GSER_IDENTIFIER);
+    if (!hasNext(GSER_MSP))
+    {
+      Message msg = ERR_GSER_SPACE_CHAR_EXPECTED.get(gserValue
+                      .substring(pos,length));
+      throw new GSERException(msg);
+    }
+    skipMSP();
+    return identifier;
+  }
+
+
+  /**
+   * Return the identifier of the next IdentifiedChoiceValue element.
+   *
+   * @return the identifier of the IdentifiedChoiceValue element
+   *
+   * @throws GSERException
+   *           If the input does not match the identifier pattern of an
+   *           IdentifiedChoiceValue
+   */
+  public String nextChoiceValueIdentifier() throws GSERException
+  {
+    if (!hasNext(GSER_CHOICE_IDENTIFIER))
+    {
+      Message msg = ERR_GSER_NO_VALID_IDENTIFIEDCHOICE.get(gserValue
+                      .substring(pos,length));
+      throw new GSERException(msg);
+    }
+    String identifier = next(GSER_CHOICE_IDENTIFIER);
+
+    // Remove the colon at the end of the identifier
+    return identifier.substring(0, identifier.length() - 1);
+  }
+
+
+}
diff --git a/opends/src/server/org/opends/server/schema/CertificateExactAssertionSyntax.java b/opends/src/server/org/opends/server/schema/CertificateExactAssertionSyntax.java
new file mode 100644
index 0000000..3cd393f
--- /dev/null
+++ b/opends/src/server/org/opends/server/schema/CertificateExactAssertionSyntax.java
@@ -0,0 +1,215 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2012 ForgeRock AS
+ *      Portions Copyright 2013-2014 Manuel Gaupp
+ */
+package org.opends.server.schema;
+
+
+
+import org.opends.server.admin.std.server.AttributeSyntaxCfg;
+import org.opends.server.api.ApproximateMatchingRule;
+import org.opends.server.api.AttributeSyntax;
+import org.opends.server.api.EqualityMatchingRule;
+import org.opends.server.api.OrderingMatchingRule;
+import org.opends.server.api.SubstringMatchingRule;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.ByteSequence;
+
+
+import static org.opends.server.loggers.ErrorLogger.*;
+import static org.opends.messages.SchemaMessages.*;
+import org.opends.messages.MessageBuilder;
+import static org.opends.server.schema.SchemaConstants.*;
+
+
+/**
+ * This class defines the Certificate Exact Assertion attribute syntax,
+ * which contains components for matching X.509 certificates.
+ */
+public class CertificateExactAssertionSyntax
+       extends AttributeSyntax<AttributeSyntaxCfg>
+{
+  // The default equality matching rule for this syntax.
+  private EqualityMatchingRule defaultEqualityMatchingRule;
+
+  // The default ordering matching rule for this syntax.
+  private OrderingMatchingRule defaultOrderingMatchingRule;
+
+  // The default substring matching rule for this syntax.
+  private SubstringMatchingRule defaultSubstringMatchingRule;
+
+
+
+  /**
+   * Creates a new instance of this syntax.  Note that the only thing that
+   * should be done here is to invoke the default constructor for the
+   * superclass.  All initialization should be performed in the
+   * <CODE>initializeSyntax</CODE> method.
+   */
+  public CertificateExactAssertionSyntax()
+  {
+    super();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void initializeSyntax(AttributeSyntaxCfg configuration)
+         throws ConfigException
+  {
+    defaultEqualityMatchingRule =
+         DirectoryServer.getEqualityMatchingRule(EMR_CASE_IGNORE_OID);
+    if (defaultEqualityMatchingRule == null)
+    {
+      logError(ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get(
+          EMR_CASE_IGNORE_OID, SYNTAX_CERTIFICATE_EXACT_ASSERTION_NAME));
+    }
+
+    defaultOrderingMatchingRule =
+         DirectoryServer.getOrderingMatchingRule(OMR_CASE_IGNORE_OID);
+    if (defaultOrderingMatchingRule == null)
+    {
+      logError(ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get(
+          OMR_CASE_IGNORE_OID, SYNTAX_CERTIFICATE_EXACT_ASSERTION_NAME));
+    }
+
+    defaultSubstringMatchingRule =
+         DirectoryServer.getSubstringMatchingRule(SMR_CASE_IGNORE_OID);
+    if (defaultSubstringMatchingRule == null)
+    {
+      logError(ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get(
+          SMR_CASE_IGNORE_OID, SYNTAX_CERTIFICATE_EXACT_ASSERTION_NAME));
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getSyntaxName()
+  {
+    return SYNTAX_CERTIFICATE_EXACT_ASSERTION_NAME;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getOID()
+  {
+    return SYNTAX_CERTIFICATE_EXACT_ASSERTION_OID;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getDescription()
+  {
+    return SYNTAX_CERTIFICATE_EXACT_ASSERTION_DESCRIPTION;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public EqualityMatchingRule getEqualityMatchingRule()
+  {
+    return defaultEqualityMatchingRule;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public OrderingMatchingRule getOrderingMatchingRule()
+  {
+    return defaultOrderingMatchingRule;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SubstringMatchingRule getSubstringMatchingRule()
+  {
+    return defaultSubstringMatchingRule;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ApproximateMatchingRule getApproximateMatchingRule()
+  {
+    // Approximate matching will not be allowed by default.
+    return null;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean valueIsAcceptable(ByteSequence value,
+                                   MessageBuilder invalidReason)
+  {
+    // This method will never be called because this syntax is only used
+    // within assertions.
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isBinary()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+}
+
diff --git a/opends/src/server/org/opends/server/schema/CertificateExactMatchingRule.java b/opends/src/server/org/opends/server/schema/CertificateExactMatchingRule.java
new file mode 100644
index 0000000..b3c450c
--- /dev/null
+++ b/opends/src/server/org/opends/server/schema/CertificateExactMatchingRule.java
@@ -0,0 +1,418 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2006-2009 Sun Microsystems, Inc.
+ *      Portions Copyright 2013-2014 Manuel Gaupp
+ */
+package org.opends.server.schema;
+
+
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import javax.security.auth.x500.X500Principal;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.server.schema.SchemaConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.opends.messages.Message;
+import org.opends.server.api.EqualityMatchingRule;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.loggers.ErrorLogger;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.GSERException;
+import org.opends.server.protocols.asn1.GSERParser;
+import org.opends.server.types.ByteSequence;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.ByteStringBuilder;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.ResultCode;
+import org.opends.server.util.StaticUtils;
+
+
+
+/**
+ * This class implements the certificateExactMatch matching rule defined
+ * in X.509 and referenced in RFC 4523.
+ */
+class CertificateExactMatchingRule
+       extends EqualityMatchingRule
+{
+  /**
+   * The GSER identifier for the serialNumber named value.
+   */
+  private static final String GSER_ID_SERIALNUMBER = "serialNumber";
+
+
+
+  /**
+   * The GSER identifier for the issuer named value.
+   */
+  private static final String GSER_ID_ISSUER = "issuer";
+
+
+
+  /**
+   * The GSER identifier for the rdnSequence IdentifiedChoiceValue.
+   */
+  private static final String GSER_ID_RDNSEQUENCE = "rdnSequence";
+
+
+
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+
+
+  /**
+   * Creates a new instance of this certificateExactMatch matching rule.
+   */
+  public CertificateExactMatchingRule()
+  {
+    super();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Collection<String> getAllNames()
+  {
+    return Collections.singleton(getName());
+  }
+
+
+
+  /**
+   * Retrieves the common name for this matching rule.
+   *
+   * @return  The common name for this matching rule, or <CODE>null</CODE> if
+   * it does not have a name.
+   */
+  public String getName()
+  {
+    return EMR_CERTIFICATE_EXACT_NAME;
+  }
+
+
+
+  /**
+   * Retrieves the OID for this matching rule.
+   *
+   * @return  The OID for this matching rule.
+   */
+  public String getOID()
+  {
+    return EMR_CERTIFICATE_EXACT_OID;
+  }
+
+
+
+  /**
+   * Retrieves the description for this matching rule.
+   *
+   * @return  The description for this matching rule, or <CODE>null</CODE> if
+   *          there is none.
+   */
+  public String getDescription()
+  {
+    return EMR_CERTIFICATE_EXACT_DESCRIPTION;
+  }
+
+
+
+  /**
+   * Retrieves the OID of the syntax with which this matching rule is
+   * associated.
+   *
+   * @return  The OID of the syntax with which this matching rule is associated.
+   */
+  public String getSyntaxOID()
+  {
+    return SYNTAX_CERTIFICATE_EXACT_ASSERTION_OID;
+  }
+
+
+
+  /**
+   * Retrieves the normalized form of the provided value, which is best suited
+   * for efficiently performing matching operations on that value.
+   *
+   * @param  value  The value to be normalized.
+   *
+   * @return  The normalized version of the provided value.
+   *
+   * @throws  DirectoryException  If the provided value is invalid according to
+   *                              the associated attribute syntax.
+   */
+  public ByteString normalizeValue(ByteSequence value)
+         throws DirectoryException
+  {
+    // The normalized form of this value is the GSER encoded ....
+    final BigInteger serialNumber;
+    final String dnstring;
+    String certificateIssuer;
+
+    // Read the X.509 Certificate and extract serialNumber and issuerDN
+    try
+    {
+      CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+      InputStream inputStream = new ByteArrayInputStream(value.toByteArray());
+      X509Certificate certValue = (X509Certificate) certFactory
+              .generateCertificate(inputStream);
+
+      serialNumber = certValue.getSerialNumber();
+      X500Principal issuer = certValue.getIssuerX500Principal();
+      dnstring = issuer.getName(X500Principal.RFC2253);
+    }
+    catch (CertificateException ce)
+    {
+      // There seems to be a problem while parsing the certificate.
+      Message message = WARN_CERTIFICATE_MATCH_PARSE_ERROR.get(ce.getMessage());
+      if (debugEnabled())
+      {
+         TRACER.debugWarning(message.toString());
+      }
+
+      // return the raw bytes as a fall back
+      return value.toByteString();
+    }
+
+    // Normalize the DN
+    try
+    {
+      DN dn = DN.decode(dnstring);
+      certificateIssuer = dn.toNormalizedString();
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      // We couldn't normalize the DN for some reason.  If we're supposed to use
+      // strict syntax enforcement, then throw an exception.  Otherwise, log a
+      // message and just try our best.
+      Message message = ERR_CERTIFICATE_MATCH_INVALID_DN.get(
+              dnstring, getExceptionMessage(e));
+
+      switch (DirectoryServer.getSyntaxEnforcementPolicy())
+      {
+        case REJECT:
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message);
+        case WARN:
+          ErrorLogger.logError(message);
+
+          certificateIssuer= toLowerCase(dnstring);
+          break;
+
+        default:
+          certificateIssuer= toLowerCase(dnstring);
+          break;
+      }
+    }
+
+    // Create the encoded value
+    return createEncodedValue(serialNumber,certificateIssuer);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString normalizeAssertionValue(ByteSequence value)
+         throws DirectoryException
+  {
+    // validate and normalize the GSER structure
+    // according to the definitions from RFC 4523, Appendix A.1
+    final BigInteger serialNumber;
+    final String dnstring;
+    String certificateIssuer;
+
+    final GSERParser parser;
+    String identifier;
+
+    parser = new GSERParser(value.toString());
+
+    try
+    {
+      // the String starts with a sequence
+      parser.readStartSequence();
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+      // Assume the assertion value is a certificate and parse issuer and serial
+      // number. If the value is not even a certificate then the raw bytes will
+      // be returned.
+      return normalizeValue(value);
+    }
+
+    try
+    {
+      // the first namedValue is serialNumber
+      identifier = parser.nextNamedValueIdentifier();
+      if (!identifier.equals(GSER_ID_SERIALNUMBER))
+      {
+        Message message = ERR_CERTIFICATE_MATCH_IDENTIFIER_NOT_FOUND
+                            .get(GSER_ID_SERIALNUMBER);
+        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message);
+      }
+
+      // The value for the serialNumber
+      serialNumber = parser.nextBigInteger();
+
+      // separator
+      parser.skipSeparator();
+
+      // the next namedValue is issuer
+      identifier = parser.nextNamedValueIdentifier();
+      if (!identifier.equals(GSER_ID_ISSUER))
+      {
+        Message message = ERR_CERTIFICATE_MATCH_IDENTIFIER_NOT_FOUND
+                            .get(GSER_ID_ISSUER);
+        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message);
+      }
+
+      // expecting "rdnSequence:"
+      identifier = parser.nextChoiceValueIdentifier();
+      if (!identifier.equals(GSER_ID_RDNSEQUENCE))
+      {
+        Message message = ERR_CERTIFICATE_MATCH_IDENTIFIER_NOT_FOUND
+                            .get(GSER_ID_RDNSEQUENCE);
+        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message);
+      }
+
+      // now the issuer dn
+      dnstring = parser.nextString();
+
+      // Closing the Sequence
+      parser.readEndSequence();
+
+      // There should not be additional characters
+      if (parser.hasNext())
+      {
+        Message message = ERR_CERTIFICATE_MATCH_EXPECTED_END.get();
+        switch (DirectoryServer.getSyntaxEnforcementPolicy())
+        {
+          case REJECT:
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message);
+          case WARN:
+            ErrorLogger.logError(message);
+            break;
+        }
+      }
+    }
+    catch (GSERException e)
+    {
+      Message message = ERR_CERTIFICATE_MATCH_GSER_INVALID.get(
+                          getExceptionMessage(e));
+      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message);
+    }
+
+    // Normalize the DN
+    try
+    {
+      DN dn = DN.decode(dnstring);
+      certificateIssuer = dn.toNormalizedString();
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      // We couldn't normalize the DN for some reason.  If we're supposed to use
+      // strict syntax enforcement, then throw an exception.  Otherwise, log a
+      // message and just try our best.
+      Message message = ERR_CERTIFICATE_MATCH_INVALID_DN.get(
+              dnstring, getExceptionMessage(e));
+
+      switch (DirectoryServer.getSyntaxEnforcementPolicy())
+      {
+        case REJECT:
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message);
+        case WARN:
+          ErrorLogger.logError(message);
+
+          certificateIssuer= toLowerCase(dnstring);
+          break;
+
+        default:
+          certificateIssuer= toLowerCase(dnstring);
+          break;
+      }
+    }
+
+    // Create the encoded value
+    return createEncodedValue(serialNumber,certificateIssuer);
+  }
+
+
+
+  /**
+   * Creates the value containing serialNumber and issuer DN.
+   *
+   * @param serial the serialNumber
+   * @param issuerDN the issuer DN String
+   *
+   * @return the encoded ByteString
+   */
+  private static ByteString createEncodedValue(BigInteger serial,
+                                               String issuerDN)
+  {
+    ByteStringBuilder builder = new ByteStringBuilder();
+    builder.append(StaticUtils.getBytes(issuerDN));
+    builder.append((byte) 0); // Separator
+    builder.append(serial.toByteArray());
+    return builder.toByteString();
+  }
+
+}
+
diff --git a/opends/src/server/org/opends/server/schema/CertificateExactMatchingRuleFactory.java b/opends/src/server/org/opends/server/schema/CertificateExactMatchingRuleFactory.java
new file mode 100644
index 0000000..c05740e
--- /dev/null
+++ b/opends/src/server/org/opends/server/schema/CertificateExactMatchingRuleFactory.java
@@ -0,0 +1,72 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2013-2014 Manuel Gaupp
+ */
+
+
+package org.opends.server.schema;
+
+import java.util.Collection;
+import java.util.Collections;
+import org.opends.server.api.MatchingRuleFactory;
+import org.opends.server.admin.std.server.MatchingRuleCfg;
+import org.opends.server.api.MatchingRule;
+import org.opends.server.config.ConfigException;
+import org.opends.server.types.InitializationException;
+
+/**
+ * This class is a factory class for
+ * {@link CertificateExactMatchingRule}.
+ */
+public final class CertificateExactMatchingRuleFactory
+        extends MatchingRuleFactory<MatchingRuleCfg>
+{
+  //Associated Matching Rule.
+  private MatchingRule matchingRule;
+
+
+
+
+ /**
+  * {@inheritDoc}
+  */
+ @Override
+ public final void initializeMatchingRule(MatchingRuleCfg configuration)
+         throws ConfigException, InitializationException
+ {
+   matchingRule = new CertificateExactMatchingRule();
+ }
+
+
+
+ /**
+  * {@inheritDoc}
+  */
+ @Override
+ public final Collection<MatchingRule> getMatchingRules()
+ {
+    return Collections.singleton(matchingRule);
+ }
+}
diff --git a/opends/src/server/org/opends/server/schema/CertificateSyntax.java b/opends/src/server/org/opends/server/schema/CertificateSyntax.java
index 93285aa..0d6dc0b 100644
--- a/opends/src/server/org/opends/server/schema/CertificateSyntax.java
+++ b/opends/src/server/org/opends/server/schema/CertificateSyntax.java
@@ -23,6 +23,7 @@
  *
  *      Copyright 2006-2008 Sun Microsystems, Inc.
  *      Portions Copyright 2012 Forgerock AS
+ *      Portions Copyright 2013-2014 Manuel Gaupp
  */
 package org.opends.server.schema;
 
@@ -99,11 +100,11 @@
          throws ConfigException
   {
     defaultEqualityMatchingRule =
-         DirectoryServer.getEqualityMatchingRule(EMR_OCTET_STRING_OID);
+         DirectoryServer.getEqualityMatchingRule(EMR_CERTIFICATE_EXACT_OID);
     if (defaultEqualityMatchingRule == null)
     {
       logError(ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get(
-          EMR_OCTET_STRING_OID, SYNTAX_CERTIFICATE_NAME));
+          EMR_CERTIFICATE_EXACT_OID, SYNTAX_CERTIFICATE_NAME));
     }
 
     defaultOrderingMatchingRule =
diff --git a/opends/src/server/org/opends/server/schema/SchemaConstants.java b/opends/src/server/org/opends/server/schema/SchemaConstants.java
index d582d4d..9dcf78f 100644
--- a/opends/src/server/org/opends/server/schema/SchemaConstants.java
+++ b/opends/src/server/org/opends/server/schema/SchemaConstants.java
@@ -23,6 +23,7 @@
  *
  *      Copyright 2006-2010 Sun Microsystems, Inc.
  *      Portions copyright 2011-2013 ForgeRock AS
+ *      Portions copyright 2013-2014 Manuel Gaupp
  */
 package org.opends.server.schema;
 
@@ -318,6 +319,29 @@
 
 
   /**
+   * The description for the certificateExactMatch equality matching rule.
+   */
+  public static final String EMR_CERTIFICATE_EXACT_DESCRIPTION =
+       "X.509 Certificate Exact Match";
+
+
+
+  /**
+   * The name for the certificateExactMatch equality matching rule.
+   */
+  public static final String EMR_CERTIFICATE_EXACT_NAME =
+       "certificateExactMatch";
+
+
+
+  /**
+   * The OID for the certificateExactMatch equality matching rule.
+   */
+  public static final String EMR_CERTIFICATE_EXACT_OID = "2.5.13.34";
+
+
+
+  /**
    * The name for the directoryStringFirstComponentMatch equality matching rule.
    */
   public static final String EMR_DIRECTORY_STRING_FIRST_COMPONENT_NAME =
@@ -966,6 +990,31 @@
 
 
   /**
+   * The description for the certificate exact assertion attribute syntax.
+   */
+  public static final String SYNTAX_CERTIFICATE_EXACT_ASSERTION_DESCRIPTION =
+       "X.509 Certificate Exact Assertion";
+
+
+
+  /**
+   * The name for the certificate exact assertion attribute syntax.
+   */
+  public static final String SYNTAX_CERTIFICATE_EXACT_ASSERTION_NAME =
+       "CertificateExactAssertion";
+
+
+
+  /**
+   * The OID for the Certificate Exact Assertion syntax used for assertion
+   * values in extensible match filters.
+   */
+  public static final String SYNTAX_CERTIFICATE_EXACT_ASSERTION_OID =
+       "1.3.6.1.1.15.1";
+
+
+
+  /**
    * The description for the certificate attribute syntax.
    */
   public static final String SYNTAX_CERTIFICATE_DESCRIPTION = "Certificate";
diff --git a/opends/src/server/org/opends/server/types/SearchFilter.java b/opends/src/server/org/opends/server/types/SearchFilter.java
index ba6c269..5bc9b5e 100644
--- a/opends/src/server/org/opends/server/types/SearchFilter.java
+++ b/opends/src/server/org/opends/server/types/SearchFilter.java
@@ -22,6 +22,7 @@
  *
  *
  *      Copyright 2006-2009 Sun Microsystems, Inc.
+ *      Portions Copyright 2013-2014 Manuel Gaupp
  */
 package org.opends.server.types;
 import org.opends.messages.Message;
@@ -2736,20 +2737,56 @@
       return ConditionResult.FALSE;
     }
 
-    // Iterate through all the attributes and see if we can find a
-    // match.
+    // Get the equality matching rule for the given attribute type
+    MatchingRule matchingRule = attributeType.getEqualityMatchingRule();
+    if (matchingRule == null)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugInfo(
+         "Attribute type %s does not have an equality matching " +
+         "rule -- returning undefined.",
+         attributeType.getNameOrOID());
+      }
+      return ConditionResult.UNDEFINED;
+    }
+
+    // Normalize the assertion value
+    ByteString value = assertionValue.getValue();
+    ByteString normalizedValue;
+    try
+    {
+        normalizedValue = matchingRule.normalizeAssertionValue(value);
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      // We can't normalize the assertion value, so the result must be
+      // undefined.
+      return ConditionResult.UNDEFINED;
+    }
+
+    ConditionResult result = ConditionResult.FALSE;
+
+    // Iterate through all the attributes and see if we can find a match.
+    AttributeValue dummyAttributeValue = AttributeValues.create(value,
+                                           normalizedValue);
+
     for (Attribute a : attrs)
     {
-      if (a.contains(assertionValue))
+      if (a.contains(dummyAttributeValue))
       {
         if (debugEnabled())
         {
           TRACER.debugVerbose(
-              "Returning TRUE for equality component %s in " +
-              "filter %s for entry %s",
-                       this, completeFilter, entry.getDN());
-        }
-        return ConditionResult.TRUE;
+            "Returning TRUE for equality component %s in " +
+            "filter %s for entry %s", this, completeFilter, entry.getDN());
+         }
+         return ConditionResult.TRUE;
       }
     }
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/MatchedValuesControlTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/MatchedValuesControlTest.java
index f90ae5a..0ddc0cd 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/MatchedValuesControlTest.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/MatchedValuesControlTest.java
@@ -22,9 +22,11 @@
  *
  *
  *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2013-2014 Manuel Gaupp
  */
 package org.opends.server.controls;
 
+import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -37,6 +39,7 @@
 import org.opends.server.types.*;
 import org.opends.server.protocols.asn1.ASN1Writer;
 import org.opends.server.protocols.asn1.ASN1;
+import org.opends.server.util.Base64;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
@@ -964,4 +967,49 @@
     }
 
   }
+
+  /**
+   *
+   */
+  @DataProvider(name = "differentNormalization")
+  public Object[][] differentNormalizationData() throws ParseException
+  {
+    final String BASE64_CERT_VALUE =
+      "MIICpTCCAg6gAwIBAgIJALeoA6I3ZC/cMA0GCSqGSIb3DQEBBQUAMFYxCzAJBgNV" +
+      "BAYTAlVTMRMwEQYDVQQHEwpDdXBlcnRpb25lMRwwGgYDVQQLExNQcm9kdWN0IERl" +
+      "dmVsb3BtZW50MRQwEgYDVQQDEwtCYWJzIEplbnNlbjAeFw0xMjA1MDIxNjM0MzVa" +
+      "Fw0xMjEyMjExNjM0MzVaMFYxCzAJBgNVBAYTAlVTMRMwEQYDVQQHEwpDdXBlcnRp" +
+      "b25lMRwwGgYDVQQLExNQcm9kdWN0IERldmVsb3BtZW50MRQwEgYDVQQDEwtCYWJz" +
+      "IEplbnNlbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApysa0c9qc8FB8gIJ" +
+      "8zAb1pbJ4HzC7iRlVGhRJjFORkGhyvU4P5o2wL0iz/uko6rL9/pFhIlIMbwbV8sm" +
+      "mKeNUPitwiKOjoFDmtimcZ4bx5UTAYLbbHMpEdwSpMC5iF2UioM7qdiwpAfZBd6Z" +
+      "69vqNxuUJ6tP+hxtr/aSgMH2i8ECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB" +
+      "hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE" +
+      "FLlZD3aKDa8jdhzoByOFMAJDs2osMB8GA1UdIwQYMBaAFLlZD3aKDa8jdhzoByOF" +
+      "MAJDs2osMA0GCSqGSIb3DQEBBQUAA4GBAE5vccY8Ydd7by2bbwiDKgQqVyoKrkUg" +
+      "6CD0WRmc2pBeYX2z94/PWO5L3Fx+eIZh2wTxScF+FdRWJzLbUaBuClrxuy0Y5ifj" +
+      "axuJ8LFNbZtsp1ldW3i84+F5+SYT+xI67ZcoAtwx/VFVI9s5I/Gkmu9f9nxjPpK7" +
+      "1AIUXiE3Qcck";
+    final String CERT_EXACT_ASSERTION =
+      "{ serialNumber 13233831500277100508, issuer rdnSequence:\""+
+      "CN=Babs Jensen,OU=Product Development,L=Cupertione,C=US\" }";
+    return new Object[][]{
+      {"userCertificate", ByteString.wrap(Base64.decode(BASE64_CERT_VALUE)),
+        CERT_EXACT_ASSERTION}};
+  }
+
+  /**
+   *
+   */
+  @Test(dataProvider = "differentNormalization")
+  public void testDifferentNormalization(String type, ByteString value,
+                                         String assertion)
+  {
+    MatchedValuesFilter mvf;
+    AttributeType attrType = DirectoryServer.getAttributeType("usercertificate");
+    AttributeValue attrValue = AttributeValues.create(attrType, value);
+
+    mvf = MatchedValuesFilter.createEqualityFilter(type, ByteString.valueOf(assertion));
+    assertTrue(mvf.valueMatches(attrType, attrValue));
+  }
 }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/asn1/GSERParserTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/asn1/GSERParserTestCase.java
new file mode 100644
index 0000000..41b1fbb
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/asn1/GSERParserTestCase.java
@@ -0,0 +1,380 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2013-2014 Manuel Gaupp
+ */
+package org.opends.server.protocols.asn1;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+import org.opends.server.protocols.asn1.GSERParser;
+import org.opends.server.protocols.asn1.GSERException;
+import org.opends.server.DirectoryServerTestCase;
+
+
+
+/**
+ * This class tests the GSERParser.
+ */
+public class GSERParserTestCase extends DirectoryServerTestCase
+{
+
+  /**
+   * Try to create a GSER Parser with <CODE>null</CODE> as parameter.
+   */
+  @Test(expectedExceptions = { AssertionError.class })
+  public void testGSERParserInitWithNull () throws Exception
+  {
+    GSERParser parser = new GSERParser(null);
+  }
+
+
+
+  /**
+   * Test the <CODE>hasNext</CODE> method.
+   */
+  @Test()
+  public void testHasNext() throws Exception
+  {
+    GSERParser parser = new GSERParser("0");
+    assertTrue(parser.hasNext());
+    assertEquals(parser.nextInteger(),0);
+    assertFalse(parser.hasNext());
+  }
+
+
+
+  /**
+   * Test the <CODE>skipSP</CODE> method.
+   */
+  @Test()
+  public void testSkipSP() throws Exception
+  {
+    String[] values = {" 42","  42","42"};
+    for (String value : values)
+    {
+      GSERParser parser = new GSERParser(value);
+      assertEquals(parser.skipSP().nextInteger(),42);
+      assertFalse(parser.hasNext());
+    }
+  }
+
+
+
+  /**
+   * Test the <CODE>skipMSP</CODE> method.
+   */
+  @Test()
+  public void testSkipMSP() throws Exception
+  {
+    String[] values = {" 42","  42","           42"};
+    for (String value : values)
+    {
+      GSERParser parser = new GSERParser(value);
+      assertEquals(parser.skipMSP().nextInteger(),42);
+      assertFalse(parser.hasNext());
+    }
+  }
+
+
+
+  /**
+   * Verify that <CODE>skipMSP</CODE> requires at least one space.
+   */
+  @Test(expectedExceptions = { GSERException.class })
+  public void testSkipMSPwithZeroSpaces() throws Exception
+  {
+    GSERParser parser = new GSERParser("42");
+    parser.skipMSP();
+  }
+
+
+
+  /**
+   * Create data for the <CODE>testSequence</CODE> test case.
+   */
+  @DataProvider(name="sequenceValues")
+  public Object[][] createSequenceValues()
+  {
+    return new Object[][] {
+      {"{123,122}", true},
+      {"{ 123,1}", true },
+      {"{ 123   ,   1   }", true },
+      {"{0123,}", false},
+      {"{0123 42 }", false},
+      {"{123  , 11 ", false},
+      {" {123  , 11 ", false},
+      {" 123  , 11}", false}
+    };
+  }
+
+
+
+  /**
+   * Test sequence parsing.
+   */
+  @Test(dataProvider="sequenceValues")
+  public void testSequence(String value, boolean expectedResult) throws Exception
+  {
+    GSERParser parser = new GSERParser(value);
+    boolean result = true;
+    try 
+    {
+      parser.readStartSequence();
+      parser.nextInteger();
+      parser.skipSP().skipSeparator();
+      parser.nextInteger();
+      parser.readEndSequence();
+      if (parser.hasNext())
+      {
+        result = false;
+      }
+    }
+    catch (GSERException e)
+    {
+      result = false;
+    }
+    assertEquals(expectedResult,result);
+  }
+
+
+
+  /**
+   * Create data for the <CODE>testString</CODE> test case.
+   */
+  @DataProvider(name="stringValues")
+  public Object[][] createStringValues()
+  {
+    return new Object[][] {
+      {"\"\"", true},
+      {"\"escaped\"\"dquotes\"", true },
+      {"\"valid Unicode \u00D6\u00C4\"", true },
+      {"\"only one \" \"", false},
+      {"invalid without dquotes", false},
+      {"\"missing end", false},
+      {"\"valid string\" with extra trailing characters", false}
+    };
+  }
+
+
+
+  /**
+   * Test the parsing of String values.
+   */
+  @Test(dataProvider="stringValues")
+  public void testString(String value, boolean expectedResult) throws Exception
+  {
+    GSERParser parser = new GSERParser(value);
+    boolean result = true;
+    try 
+    {
+      assertNotNull(parser.nextString());
+      if (parser.hasNext())
+      {
+        result = false;
+      }
+    }
+    catch (GSERException e)
+    {
+      result = false;
+    }
+    assertEquals(expectedResult,result);
+  }
+
+
+
+  /**
+   * Create data for the <CODE>testInteger</CODE> test case.
+   */
+  @DataProvider(name="integerValues")
+  public Object[][] createIntegerValues()
+  {
+    return new Object[][] {
+      {"0123456", true},
+      {"42", true},
+      {"0", true },
+      {"", false},
+      {"0xFF", false},
+      {"NULL", false},
+      {"Not a Number", false}
+    };
+  }
+
+
+
+  /**
+   * Create data for the <CODE>testBigInteger</CODE> test case.
+   */
+  @DataProvider(name="bigIntegerValues")
+  public Object[][] createBigIntegerValues()
+  {
+    return new Object[][] {
+      {"0123456", true},
+      {"42", true},
+      {"0", true },
+      {"", false},
+      {"0xFF", false},
+      {"NULL", false},
+      {"Not a Number", false},
+      {"2147483648",true}
+    };
+  }
+
+
+
+  /**
+   * Test the parsing of Integer values.
+   */
+  @Test(dataProvider="integerValues")
+  public void testInteger(String value, boolean expectedResult) throws Exception
+  {
+    GSERParser parser = new GSERParser(value);
+    boolean result = true;
+    try 
+    {
+      parser.nextInteger();
+      if (parser.hasNext())
+      {
+        result = false;
+      }
+    }
+    catch (GSERException e)
+    {
+      result = false;
+    }
+    assertEquals(expectedResult,result);
+  }
+
+
+
+  /**
+   * Test the parsing of BigInteger values.
+   */
+  @Test(dataProvider="bigIntegerValues")
+  public void testBigInteger(String value, boolean expectedResult) throws Exception
+  {
+    GSERParser parser = new GSERParser(value);
+    boolean result = true;
+    try 
+    {
+      parser.nextBigInteger();
+      if (parser.hasNext())
+      {
+        result = false;
+      }
+    }
+    catch (GSERException e)
+    {
+      result = false;
+    }
+    assertEquals(expectedResult,result);
+  }
+
+
+
+  /**
+   * Create data for the <CODE>testNamedValueIdentifier</CODE> test case.
+   */
+  @DataProvider(name="namedValueIdentifierValues")
+  public Object[][] createNamedValueIdentifierValues()
+  {
+    return new Object[][] {
+      {"serialNumber ", true},
+      {"issuer ", true},
+      {"Serialnumber ", false},
+      {"0serialnumber ", false},
+      {"serial Number ", false},
+      {"missingSpace",false}
+    };
+  }
+
+
+
+  /**
+   * Test the parsing of NamedValue identifiers.
+   */
+  @Test(dataProvider="namedValueIdentifierValues")
+  public void testNamedValueIdentifier(String value, boolean expectedResult) throws Exception
+  {
+    GSERParser parser = new GSERParser(value);
+    boolean result = true;
+    try 
+    {
+      assertNotNull(parser.nextNamedValueIdentifier());
+      if (parser.hasNext())
+      {
+        result = false;
+      }
+    }
+    catch (GSERException e)
+    {
+      result = false;
+    }
+    assertEquals(expectedResult,result);
+  }
+
+
+
+  /**
+   * Create data for the <CODE>testIdentifiedChoiceIdentifier</CODE> test case.
+   */
+  @DataProvider(name="identifiedChoicdeIdentifierValues")
+  public Object[][] createIdentifiedChoicdeIdentifierValues()
+  {
+    return new Object[][] {
+      {"serialNumber:", true},
+      {"issuer1:", true},
+      {"Serialnumber:", false},
+      {"0serialnumber:", false},
+      {"serial Number:", false},
+      {"missingColon",false}
+    };
+  }
+
+
+
+  /**
+   * Test the parsing of IdentifiedChoice identifiers.
+   */
+  @Test(dataProvider="identifiedChoicdeIdentifierValues")
+  public void testIdentifiedChoicdeIdentifier(String value, boolean expectedResult) throws Exception
+  {
+    GSERParser parser = new GSERParser(value);
+    boolean result = true;
+    try 
+    {
+      assertNotNull(parser.nextChoiceValueIdentifier());
+      if (parser.hasNext())
+      {
+        result = false;
+      }
+    }
+    catch (GSERException e)
+    {
+      result = false;
+    }
+    assertEquals(expectedResult,result);
+  }
+}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/CertificateExactMatchingRuleTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/CertificateExactMatchingRuleTest.java
new file mode 100644
index 0000000..6acae1c
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/CertificateExactMatchingRuleTest.java
@@ -0,0 +1,245 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2013-2014 Manuel Gaupp
+ */
+package org.opends.server.schema;
+
+import org.opends.server.types.ByteString;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.util.Base64;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+
+
+/**
+ * This class tests the certificateExactMatch matching rule.
+ */
+public class CertificateExactMatchingRuleTest extends SchemaTestCase
+{
+  /**
+   * Generate data for the certificateExactMatch matching rule test.
+   */
+  @DataProvider(name="certificateExactMatchingRules")
+  public Object[][] createCertificateExactMatchingRuleTest()
+    throws Exception
+  {
+    String validcert1 =
+      "MIICpTCCAg6gAwIBAgIJALeoA6I3ZC/cMA0GCSqGSIb3DQEBBQUAMFYxCzAJBgNV" +
+      "BAYTAlVTMRMwEQYDVQQHEwpDdXBlcnRpb25lMRwwGgYDVQQLExNQcm9kdWN0IERl" +
+      "dmVsb3BtZW50MRQwEgYDVQQDEwtCYWJzIEplbnNlbjAeFw0xMjA1MDIxNjM0MzVa" +
+      "Fw0xMjEyMjExNjM0MzVaMFYxCzAJBgNVBAYTAlVTMRMwEQYDVQQHEwpDdXBlcnRp" +
+      "b25lMRwwGgYDVQQLExNQcm9kdWN0IERldmVsb3BtZW50MRQwEgYDVQQDEwtCYWJz" +
+      "IEplbnNlbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApysa0c9qc8FB8gIJ" +
+      "8zAb1pbJ4HzC7iRlVGhRJjFORkGhyvU4P5o2wL0iz/uko6rL9/pFhIlIMbwbV8sm" +
+      "mKeNUPitwiKOjoFDmtimcZ4bx5UTAYLbbHMpEdwSpMC5iF2UioM7qdiwpAfZBd6Z" +
+      "69vqNxuUJ6tP+hxtr/aSgMH2i8ECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB" +
+      "hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE" +
+      "FLlZD3aKDa8jdhzoByOFMAJDs2osMB8GA1UdIwQYMBaAFLlZD3aKDa8jdhzoByOF" +
+      "MAJDs2osMA0GCSqGSIb3DQEBBQUAA4GBAE5vccY8Ydd7by2bbwiDKgQqVyoKrkUg" +
+      "6CD0WRmc2pBeYX2z94/PWO5L3Fx+eIZh2wTxScF+FdRWJzLbUaBuClrxuy0Y5ifj" +
+      "axuJ8LFNbZtsp1ldW3i84+F5+SYT+xI67ZcoAtwx/VFVI9s5I/Gkmu9f9nxjPpK7" +
+      "1AIUXiE3Qcck";
+
+    return new Object[][] {
+      {ByteString.wrap(Base64.decode(validcert1)),ByteString.valueOf(
+        "{ serialNumber 13233831500277100508, issuer rdnSequence:\""+
+        "CN=Babs Jensen,OU=Product Development,L=Cupertione,C=US\" }"), true },
+      {ByteString.wrap(Base64.decode(validcert1)),ByteString.valueOf(
+        "{    serialNumber     13233831500277100508,  issuer  rdnSequence:\""+
+        "CN=Babs Jensen,OU=Product Development, L=Cupertione,C=US\" }"), true },
+      {ByteString.wrap(Base64.decode(validcert1)),ByteString.valueOf(
+        "{ serialNumber 13233831500277100508, issuer rdnSequence:\""+
+        "cn=BABS Jensen,ou=Product Development,L=Cupertione,c=#5553\" }"), true },
+      {ByteString.wrap(Base64.decode(validcert1)),ByteString.valueOf(
+        "{ serialNumber 13233831511277100508, issuer rdnSequence:\""+
+        "CN=Babs Jensen,OU=Product Development,L=Cupertione,C=US\" }"), false },
+      {ByteString.wrap(Base64.decode(
+        "MIICpTCCAg6gAwIBAgIJALeoA6I3ZC/cMA0GCSqGSIb3DQEBBQUAMFYxCzAJBgNV")),
+       ByteString.wrap(Base64.decode(
+        "MIICpTCCAg6gAwIBAgIJALeoA6I3ZC/cMA0GCSqGSIb3DQEBBQUAMFYxCzAJBgNV")), true}
+    };
+  }
+
+
+
+  /**
+   * Generate valid assertion values for the certificateExactMatch matching
+   * rule test.
+   */
+  @DataProvider(name="certificateExactMatchValidAssertionValues")
+  public Object[][] createCertificateExactMatchingRuleValidAssertionValues()
+  {
+    return new Object[][] {
+      {"{serialNumber 123,issuer rdnSequence:\"c=DE\"}"},
+      {"{serialNumber 123,issuer rdnSequence:\"\"}"},
+      {"{serialNumber 0123,issuer rdnSequence:\"cn=issuer\"}"},
+      {"{  serialNumber  123,  issuer  rdnSequence:\"c=DE\"  }"},
+      {"{serialNumber 123,issuer rdnSequence:\"cn=escaped\"\"dquotes\"}"},
+      {"{serialNumber 123,issuer rdnSequence:\"cn=\u00D6\u00C4\"}"}
+    };
+  }
+
+
+
+  /**
+   * Generate invalid assertion values for the certificateExactMatch matching
+   * rule test.
+   */
+  @DataProvider(name="certificateExactMatchInvalidAssertionValues")
+  public Object[][] createCertificateExactMatchingRuleInvalidAssertionValues()
+  {
+    return new Object[][] {
+      {"{serialnumber 123,issuer rdnSequence:\"c=DE\"}"},
+      {"{serialNumber 123,issuer rdnSequence:\"invalid\"}"},
+      {"{serialNumber 0123,issuer rdnSequence: \"cn=issuer\"}"},
+      {"{  serialNumber  123  ,  issuer  rdnSequence:\"c=DE\"  }  trailing"}
+    };
+  }
+
+
+
+  /**
+   * Generate invalid atribute values for the certificateExactMatch matching
+   * rule test.
+   */
+  @DataProvider(name="certificateExactMatchInvalidAttributeValues")
+  public Object[][] createCertificateExactMatchingRuleInvalidAttributeValues()
+    throws Exception
+  {
+    String invalidcert1 =
+      "MIICpTCCAg6gAwIBBQIJALeoA6I3ZC/cMA0GCSqGSIb3DQEBBQUAMFYxCzAJBgNV" +
+      "BAYTAlVTMRMwEQYDVQQHEwpDdXBlcnRpb25lMRwwGgYDVQQLExNQcm9kdWN0IERl" +
+      "dmVsb3BtZW50MRQwEgYDVQQDEwtCYWJzIEplbnNlbjAeFw0xMjA1MDIxNjM0MzVa" +
+      "Fw0xMjEyMjExNjM0MzVaMFYxCzAJBgNVBAYTAlVTMRMwEQYDVQQHEwpDdXBlcnRp" +
+      "b25lMRwwGgYDVQQLExNQcm9kdWN0IERldmVsb3BtZW50MRQwEgYDVQQDEwtCYWJz" +
+      "IEplbnNlbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApysa0c9qc8FB8gIJ" +
+      "8zAb1pbJ4HzC7iRlVGhRJjFORkGhyvU4P5o2wL0iz/uko6rL9/pFhIlIMbwbV8sm" +
+      "mKeNUPitwiKOjoFDmtimcZ4bx5UTAYLbbHMpEdwSpMC5iF2UioM7qdiwpAfZBd6Z" +
+      "69vqNxuUJ6tP+hxtr/aSgMH2i8ECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB" +
+      "hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE" +
+      "FLlZD3aKDa8jdhzoByOFMAJDs2osMB8GA1UdIwQYMBaAFLlZD3aKDa8jdhzoByOF" +
+      "MAJDs2osMA0GCSqGSIb3DQEBBQUAA4GBAE5vccY8Ydd7by2bbwiDKgQqVyoKrkUg" +
+      "6CD0WRmc2pBeYX2z94/PWO5L3Fx+eIZh2wTxScF+FdRWJzLbUaBuClrxuy0Y5ifj" +
+      "axuJ8LFNbZtsp1ldW3i84+F5+SYT+xI67ZcoAtwx/VFVI9s5I/Gkmu9f9nxjPpK7" +
+      "1AIUXiE3Qcck";
+
+    String brokencert1 =
+      "MIICpTCCAg6gAwIBAgIJALeoA6I3ZC/cMA0GCSqGSIb3DQEBBQUAMFYxCzAJBgNV";
+
+    return new Object[][] {
+        {ByteString.wrap(Base64.decode(invalidcert1))},
+        {ByteString.wrap(Base64.decode(brokencert1))}
+     };
+  }
+
+
+
+  /**
+   * Get an instance of the matching rule.
+   *
+   * @return An instance of the matching rule to test.
+   */
+  protected CertificateExactMatchingRule getRule()
+  {
+    return new CertificateExactMatchingRule();
+  }
+
+
+
+  /**
+   * Test the normalization and the comparison of valid values.
+   */
+  @Test(dataProvider= "certificateExactMatchingRules")
+  public void certificateExactMatchingRules(ByteString attributeValue,
+          ByteString assertionValue, Boolean result) throws Exception
+  {
+    CertificateExactMatchingRule rule = getRule();
+
+    // normalize the 2 provided values and check that they are equals
+    ByteString normalizedAttributeValue =
+      rule.normalizeValue(attributeValue);
+    ByteString normalizedAssertionValue =
+      rule.normalizeAssertionValue(assertionValue);
+
+    Boolean liveResult = rule.areEqual(normalizedAttributeValue,
+                                       normalizedAssertionValue);
+    assertEquals(result, liveResult);
+  }
+
+
+
+  /**
+   * Test that valid assertion values are accepted.
+   */
+  @Test(dataProvider= "certificateExactMatchValidAssertionValues")
+  public void certificateExactMatchingRuleValidAssertionValues(String value)
+              throws Exception
+    {
+    // Get the instance of the rule to be tested.
+    CertificateExactMatchingRule rule = getRule();
+
+    // normalize the provided assertion values
+    rule.normalizeAssertionValue(ByteString.valueOf(value));
+  }
+
+
+
+  /**
+   * Test that invalid assertion values are rejected.
+   */
+  @Test(dataProvider= "certificateExactMatchInvalidAssertionValues",
+        expectedExceptions={ DirectoryException.class })
+  public void certificateExactMatchingRuleInvalidAssertionValues(String value)
+              throws Exception
+  {
+    // Get the instance of the rule to be tested.
+    CertificateExactMatchingRule rule = getRule();
+
+    // normalize the provided assertion value
+    rule.normalizeAssertionValue(ByteString.valueOf(value));
+  }
+
+
+
+  /**
+   * Test that invalid attribute values are returned with the original ByteString.
+   */
+  @Test(dataProvider= "certificateExactMatchInvalidAttributeValues")
+  public void certificateExactMatchingRuleInvalidAttributeValues(ByteString value)
+              throws Exception
+    {
+    // Get the instance of the rule to be tested.
+    CertificateExactMatchingRule rule = getRule();
+
+    // normalize the provided assertion value
+    ByteString normalizedValue = rule.normalizeAssertionValue(value);
+    assertEquals(value,normalizedValue);
+  }
+
+}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java
index aa40f22..c1c6ee4 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java
@@ -22,13 +22,16 @@
  *
  *
  *      Copyright 2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2013-2014 Manuel Gaupp
  */
 package org.opends.server.types;
 
 import org.opends.server.DirectoryServerTestCase;
 import org.opends.server.TestCaseUtils;
 import org.opends.server.util.StaticUtils;
+import org.opends.server.util.Base64;
 import org.opends.server.types.DirectoryException;
+import org.opends.server.types.RawFilter;
 import org.opends.server.core.DirectoryServer;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
@@ -39,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashSet;
+import java.text.ParseException;
 
 import static java.util.Arrays.asList;
 import static org.opends.server.util.StaticUtils.*;
@@ -1142,5 +1146,60 @@
     assertEquals(actualEquals, expectStringEquals,
                  "Expected " + filter1 + (expectStringEquals ? " == " : " != ") + filter2);
   }
+
+
+  /**
+   * Dataprovider for testing different normalization for value and assertion
+   */
+  @DataProvider(name = "differentNormalization")
+  public Object[][] differentNormalizationData() throws ParseException
+  {
+    final String BASE64_CERT_VALUE = 
+      "MIICpTCCAg6gAwIBAgIJALeoA6I3ZC/cMA0GCSqGSIb3DQEBBQUAMFYxCzAJBgNV" +
+      "BAYTAlVTMRMwEQYDVQQHEwpDdXBlcnRpb25lMRwwGgYDVQQLExNQcm9kdWN0IERl" +
+      "dmVsb3BtZW50MRQwEgYDVQQDEwtCYWJzIEplbnNlbjAeFw0xMjA1MDIxNjM0MzVa" +
+      "Fw0xMjEyMjExNjM0MzVaMFYxCzAJBgNVBAYTAlVTMRMwEQYDVQQHEwpDdXBlcnRp" +
+      "b25lMRwwGgYDVQQLExNQcm9kdWN0IERldmVsb3BtZW50MRQwEgYDVQQDEwtCYWJz" +
+      "IEplbnNlbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApysa0c9qc8FB8gIJ" +
+      "8zAb1pbJ4HzC7iRlVGhRJjFORkGhyvU4P5o2wL0iz/uko6rL9/pFhIlIMbwbV8sm" +
+      "mKeNUPitwiKOjoFDmtimcZ4bx5UTAYLbbHMpEdwSpMC5iF2UioM7qdiwpAfZBd6Z" +
+      "69vqNxuUJ6tP+hxtr/aSgMH2i8ECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB" +
+      "hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE" +
+      "FLlZD3aKDa8jdhzoByOFMAJDs2osMB8GA1UdIwQYMBaAFLlZD3aKDa8jdhzoByOF" +
+      "MAJDs2osMA0GCSqGSIb3DQEBBQUAA4GBAE5vccY8Ydd7by2bbwiDKgQqVyoKrkUg" +
+      "6CD0WRmc2pBeYX2z94/PWO5L3Fx+eIZh2wTxScF+FdRWJzLbUaBuClrxuy0Y5ifj" +
+      "axuJ8LFNbZtsp1ldW3i84+F5+SYT+xI67ZcoAtwx/VFVI9s5I/Gkmu9f9nxjPpK7" +
+      "1AIUXiE3Qcck";
+    final String CERT_EXACT_ASSERTION = 
+      "{ serialNumber 13233831500277100508, issuer rdnSequence:\""+
+      "CN=Babs Jensen,OU=Product Development,L=Cupertione,C=US\" }";
+    final String CERTIFICATE_LDIF = TestCaseUtils.makeLdif(
+          "dn: cn=John Smith,dc=example,dc=com",
+          "objectclass: inetorgperson",
+          "cn: John Smith",
+          "sn: Smith",
+          "userCertificate;binary:: "+BASE64_CERT_VALUE
+          );
+    StringBuilder builder = new StringBuilder();
+    RawFilter.valueToFilterString(builder,ByteString.wrap(Base64.decode(BASE64_CERT_VALUE)));
+    final String CERTIFICATE_ENCODED = builder.toString();
+
+    return new Object[][]{
+            {CERTIFICATE_LDIF, "userCertificate="+CERT_EXACT_ASSERTION, true},
+            {CERTIFICATE_LDIF, "userCertificate="+CERTIFICATE_ENCODED, true}};
+  }
+
+  @Test(dataProvider = "differentNormalization")
+  public void testdifferentNormalization(String ldifEntry, String filterStr,
+                                         boolean expectMatch) throws Exception
+  {
+    Entry entry = TestCaseUtils.entryFromLdifString(ldifEntry);
+    boolean matches = SearchFilter.createFilterFromString(filterStr).matchesEntry(entry);
+    Assert.assertEquals(matches, expectMatch, "Filter=" + filterStr + "\nEntry=" + entry);
+  }
+
+
+
+
 }
 

--
Gitblit v1.10.0