/**
* DSS - Digital Signature Services
* Copyright (C) 2015 European Commission, provided under the CEF programme
*
* This file is part of the "DSS - Digital Signature Services" project.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package eu.europa.esig.dss.validation;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.x500.X500Name;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class to check if two principals are equal
*
*
*/
public class X500PrincipalMatcher {
private static final Logger LOG = LoggerFactory.getLogger(X500PrincipalMatcher.class);
/**
* flag to configure on VM level unescaping of \xNN encoded literals<br/>
* to enable this, use <code>-Ddss.dn.unescapemultibyteutf8literal=true</code><br/>
* you can set this flag also programmatically via <code>System.getProperty(...)</code> and then calling {@link
* #resolvePatchConfiguration()}.
*/
public static final String DSS_DN_UNESCAPEMULTIBYTEUTF8LITERAL = "dss.dn.unescapemultibyteutf8literal";
private static boolean APPLY_UNESCAPEMULTIBYTEUTF8LITERAL;
static {
resolvePatchConfiguration();
}
private X500PrincipalMatcher() {
// hidden utility constructor
}
/**
* checks if the two principals are equal in a somewhat relaxed way.
*
* @param p1 not null
* @param p2 not null
* @return true, if either {@link #viaEquals(javax.security.auth.x500.X500Principal, javax.security.auth.x500.X500Principal)} or {@link
* #viaName(javax.security.auth.x500.X500Principal, javax.security.auth.x500.X500Principal)}
*/
public static boolean viaAny(final X500Principal p1, final X500Principal p2) {
return viaEquals(p1, p2) || viaName(p1, p2);
}
/**
* checks if the two principals are equal via the equals-method
*
* @param p1 not null
* @param p2 nullable
* @return true if {@link javax.security.auth.x500.X500Principal#equals(Object)}
*/
public static boolean viaEquals(final X500Principal p1, final X500Principal p2) {
return p1.equals(p2);
}
/**
* checks if the two principals are equal via the canonical names<br/>
* note that this also unescapes python style utf literals ("\xc3\xa9" to "é")
*
* @param p1 not null
* @param p2 not null
* @return true if {@link String#equals(Object)}
*/
public static boolean viaName(final X500Principal p1, final X500Principal p2) {
final String cn1 = getCanonicalName(p1);
final String n1 = maybePatchDN(cn1);
final String cn2 = getCanonicalName(p2);
final String n2 = maybePatchDN(cn2);
return n1.equals(n2);
}
private static String getCanonicalName(final X500Principal p1) {
return p1.getName(X500Principal.CANONICAL);
}
// ------------------------------------------------------------------------
/**
* checks if the two principals are equal in a somewhat relaxed way.
*
* @param p1 not null
* @param p2 not null
* @return true, if either {@link #viaEquals(org.bouncycastle.asn1.x500.X500Name, org.bouncycastle.asn1.x500.X500Name)} or {@link
* #viaName(org.bouncycastle.asn1.x500.X500Name, org.bouncycastle.asn1.x500.X500Name)}
*/
public static boolean viaAny(final X500Name p1, final X500Name p2) {
return viaEquals(p1, p2) || viaName(p1, p2);
}
/**
* checks if the two names are equal via the equals-method
*
* @param p1 not null
* @param p2 nullable
* @return true if {@link javax.security.auth.x500.X500Principal#equals(Object)}
*/
public static boolean viaEquals(final X500Name p1, final X500Name p2) {
return p1.equals(p2);
}
/**
* checks if the two principals are equal via the canonical names<br/>
* note that this also unescapes python style utf literals ("\xc3\xa9" to "é")
*
* @param p1 not null
* @param p2 not null
* @return true if {@link String#equals(Object)}
*/
public static boolean viaName(final X500Name p1, final X500Name p2) {
final String cn1 = getCanonicalName(p1);
final String n1 = maybePatchDN(cn1);
final String cn2 = getCanonicalName(p2);
final String n2 = maybePatchDN(cn2);
return n1.equals(n2);
}
private static String getCanonicalName(final X500Name p1) {
return p1.toString();
}
// ------------------------------------------------------------------------
/**
* this checks for {@link #DSS_DN_UNESCAPEMULTIBYTEUTF8LITERAL} that configures {@link #maybePatchDN(String)}.
*/
public static void resolvePatchConfiguration() {
APPLY_UNESCAPEMULTIBYTEUTF8LITERAL = "true".equalsIgnoreCase(System.getProperty(DSS_DN_UNESCAPEMULTIBYTEUTF8LITERAL, "false"));
}
/**
* sets the value for {@link #DSS_DN_UNESCAPEMULTIBYTEUTF8LITERAL} so that {@link #maybePatchDN(String)} is executed in that respect.
*/
public static void enablePatchDN() {
System.setProperty(DSS_DN_UNESCAPEMULTIBYTEUTF8LITERAL, "true");
resolvePatchConfiguration();
}
/**
* depending on the VM configuration, the distinguished name is patched<br/>
* e.g.: via {@link X500PrincipalMatcher#DSS_DN_UNESCAPEMULTIBYTEUTF8LITERAL} "\xc3\xa9" to "é"<br/>
* this has been specifically introduced for french signatures that have invalid XML escapes (see
* http://www.jira.e-codex.eu/browse/ECDX-59)
*
* @param distinguishedName the text
* @return either the original one or the patched one
*/
public static String maybePatchDN(String distinguishedName) {
if (APPLY_UNESCAPEMULTIBYTEUTF8LITERAL) {
distinguishedName = unescapeMultiByteUtf8Literals(distinguishedName);
}
return distinguishedName;
}
/**
* replaces e.g. "\xc3\xa9" with "é"
*
* @param s the input
* @return the output
*/
private static String unescapeMultiByteUtf8Literals(final String s) {
try {
final String q = new String(unescapePython(s.getBytes("UTF-8")), "UTF-8");
if (!q.equals(s)) {
LOG.error("multi byte utf literal found:\n" +
" orig = " + s + "\n" +
" escp = " + q);
}
return q;
} catch (Exception e) {
LOG.error("Could not unescape multi byte utf literal - will use original input: " + s, e);
return s;
}
}
private static byte[] unescapePython(final byte[] escaped) throws Exception {
// simple state machine iterates over the escaped bytes and converts
final byte[] unescaped = new byte[escaped.length];
int posTarget = 0;
for (int posSource = 0; posSource < escaped.length; posSource++) {
// if its not special then just move on
if (escaped[posSource] != '\\') {
unescaped[posTarget] = escaped[posSource];
posTarget++;
continue;
}
// if there is no next byte, throw incorrect encoding error
if (posSource + 1 >= escaped.length) {
throw new Exception("String incorrectly escaped, ends with escape character.");
}
// deal with hex first
if (escaped[posSource + 1] == 'x') {
// if there's no next byte, throw incorrect encoding error
if (posSource + 3 >= escaped.length) {
throw new Exception("String incorrectly escaped, ends early with incorrect hex encoding.");
}
unescaped[posTarget] = (byte) ((Character.digit(escaped[posSource + 2], 16) << 4) + Character.digit(escaped[posSource + 3], 16));
posTarget++;
posSource += 3;
}
// deal with n, then t, then r
else if (escaped[posSource + 1] == 'n') {
unescaped[posTarget] = '\n';
posTarget++;
posSource++;
} else if (escaped[posSource + 1] == 't') {
unescaped[posTarget] = '\t';
posTarget++;
posSource++;
} else if (escaped[posSource + 1] == 'r') {
unescaped[posTarget] = '\r';
posTarget++;
posSource++;
} else if (escaped[posSource + 1] == '\\') {
unescaped[posTarget] = escaped[posSource + 1];
posTarget++;
posSource++;
} else if (escaped[posSource + 1] == '\'') {
unescaped[posTarget] = escaped[posSource + 1];
posTarget++;
posSource++;
} else {
// invalid character
throw new Exception("String incorrectly escaped, invalid escaped character");
}
}
final byte[] result = new byte[posTarget];
System.arraycopy(unescaped, 0, result, 0, posTarget);
// return byte array, not string. Callers can convert to string.
return result;
}
}