/*
* Copyright (c) 2011 ICM Uniwersytet Warszawski All rights reserved.
* See LICENCE file for licensing information.
*/
package eu.emi.security.authn.x509.helpers.ns;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import eu.emi.security.authn.x509.helpers.trust.OpensslTruststoreHelper;
import eu.emi.security.authn.x509.impl.OpensslNameUtils;
/**
* Parses a single .signing_policy file and returns {@link NamespacePolicy} object.
* Only the simplified parsing of the EACL format is implemented, in a similar way is in case
* of a native Globus implementation. However there are differences. First of all the format
* of this file is defined in a very imprecise way.
* <p>
* The parsing is done in the following way:
* <ul>
* <li> as a whitespace the space and tab characters are used; a separator may
* contain an arbitrary number of those, below only one space was used for clarity.
* <li> all empty lines, whitespace only lines and lines beginning with '#' are ignored
* <li> the first line like this is searched:
* access_id_CA X509 'ANY_STRING'
* other are ignored. Line with the access_id_CA prefix and other ending causes an error.
* <li> after this line it is expected that the next significant line is :
* pos_rights globus CA:sign
* <li> next the line in the format:
* cond_subjects globus '"ANY_STRING" ["ANY_STRING"]'
* is expected. The trailing string need not to be enclosed in '' and in "", but if it is
* then the order of quotation must be preserved.
* <li> go to step 3.
* </ul>
* @author K. Benedyczak
*/
public class GlobusNamespacesParser implements NamespacesParser
{
public static String ACCESS_ID_CA = "access_id_CA";
public static String DEF_AUTH_X509 = "X509";
public static String DEF_AUTH_GLOBUS = "globus";
public static String POS_RIGHTS = "pos_rights";
public static String CONDITION_SUBJECT = "cond_subjects";
public static String VALUE_CA_SIGN = "CA:sign";
public static final String NS_REGEXP = "^([0-9a-fA-F]{8})\\.signing_policy$";
private String filePath;
private String hash;
private String issuer;
private List<NamespacePolicy> ret;
public GlobusNamespacesParser(String filePath)
{
this.filePath = filePath;
}
public List<NamespacePolicy> parse() throws IOException
{
hash = OpensslTruststoreHelper.getFileHash(filePath, NS_REGEXP);
if (hash == null)
throw new IOException("Policy file name " + filePath +
" is incorrect: it must be formed from 8 charater subject hash and " +
"'.signing_policy' extension.");
BufferedReader reader = new BufferedReader(new FileReader(filePath));
try
{
String line;
ret = new ArrayList<NamespacePolicy>();
while ((line = reader.readLine()) != null)
{
line = line.trim();
if (!isValid(line))
continue;
if (!line.startsWith(ACCESS_ID_CA))
continue;
handleCABlock(line, reader);
}
return ret;
} finally
{
reader.close();
}
}
private void handleCABlock(String line, BufferedReader reader) throws IOException
{
char[] caChars = line.toCharArray();
int i = ACCESS_ID_CA.length();
i += eatSpaces(caChars, i, true);
i += ParserUtils.checkToken(DEF_AUTH_X509, caChars, i, true);
i += eatSpaces(caChars, i, true);
StringBuilder issuerBuf = new StringBuilder();
i += getQuoted(caChars, i, '\'', issuerBuf);
issuer = issuerBuf.toString();
ParserUtils.checkEndOfLine(caChars, i);
while ((line = reader.readLine()) != null)
{
line = line.trim();
if (!isValid(line))
continue;
handleAuthEntry(line, reader);
break;
}
}
private void handleAuthEntry(String line, BufferedReader reader) throws IOException
{
char[] chars = line.toCharArray();
int j=0;
j += ParserUtils.checkToken(POS_RIGHTS, chars, j, true);
j += eatSpaces(chars, j, true);
j += ParserUtils.checkToken(DEF_AUTH_GLOBUS, chars, j, true);
j += eatSpaces(chars, j, true);
j += ParserUtils.checkToken(VALUE_CA_SIGN, chars, j, true);
ParserUtils.checkEndOfLine(chars, j);
while ((line = reader.readLine()) != null)
{
line = line.trim();
if (!isValid(line))
continue;
handlePermitEntry(line, reader);
break;
}
}
private void handlePermitEntry(String line, BufferedReader reader) throws IOException
{
char[] chars = line.toCharArray();
int j=0;
j += ParserUtils.checkToken(CONDITION_SUBJECT, chars, j, true);
j += eatSpaces(chars, j, true);
j += ParserUtils.checkToken(DEF_AUTH_GLOBUS, chars, j, true);
j += eatSpaces(chars, j, true);
StringBuilder subject = new StringBuilder();
j += getQuoted(chars, j, '\'', subject);
ParserUtils.checkEndOfLine(chars, j);
addPermitted(subject.toString());
}
private void addPermitted(String permitted) throws IOException
{
char []subjectWildcards = permitted.toCharArray();
int i=0;
do
{
int spaces = eatSpaces(subjectWildcards, i, false);
i += spaces;
if (i==0) //first element->spaces not needed.
spaces++;
StringBuilder permittedBuf = new StringBuilder();
i += getQuoted(subjectWildcards, i, '"', permittedBuf);
permitted = permittedBuf.toString().trim();
if (permitted.length() == 0)
break;
if (spaces == 0)
throw new IOException("Syntax problem, space character(s) missing in: " +
new String(subjectWildcards, 0, subjectWildcards.length));
// List<String> permittedList = normalize(permitted);
// for (String p: permittedList)
// {
// NamespacePolicy policy = new NamespacePolicy(
// ParserUtils.normalize(issuer),
// p, true, filePath);
// ret.add(policy);
// }
String permittedNormal = normalize(permitted);
NamespacePolicy policy = new OpensslNamespacePolicyImpl(
OpensslNameUtils.normalize(issuer), permittedNormal, hash, true, filePath);
ret.add(policy);
} while (true);
}
private int getQuoted(char[] string, int offset, char quoteChar, StringBuilder ret) throws IOException
{
int count = string.length-offset;
int all = count;
if (count <= 0)
return 0;
if (string[offset] == quoteChar)
{
if (count < 2)
throw new IOException("Syntax problem, quoted string is not properly ended: '"
+ new String(string, offset, string.length-offset));
offset++;
int finish = offset + eatUntil(string, offset, quoteChar);
count = finish-offset;
all = count+2;
}
ret.append(string, offset, count);
return all;
}
private boolean isValid(String line)
{
if (line.equals("") || line.startsWith("#"))
return false;
return true;
}
private int eatSpaces(char[] string, int offset, boolean atLeastOne) throws IOException
{
int i=0;
while (i+offset < string.length && (string[i+offset] == ' '
|| string[i+offset] == '\t'))
i++;
if (atLeastOne && i==0)
throw new IOException("Syntax problem, expected space character(s) here: " +
new String(string, offset, string.length-offset));
return i;
}
private int eatUntil(char[] string, int offset, char delimiter) throws IOException
{
int i=0;
while (i+offset < string.length && (string[i+offset] != delimiter))
i++;
if (i+offset == string.length)
throw new IOException("Syntax problem, quoted string is not properly ended: '"
+ new String(string, offset, string.length-offset));
return i;
}
public static String normalize(String dn)
{
dn = OpensslNameUtils.normalize(dn);
return makeRegexpClassicWildcard(dn);
}
/**
* Converts wildcard string to Java regexp, ensuring that
* literal sequences are correctly escaped.
* @param pattern input wildcard
* @return Java regular expression
*/
public static String makeRegexpClassicWildcard(String pattern)
{
String wPattern = pattern;
String REP_STAR = ".*";
String REP_QUESTION = ".";
StringBuilder patternB = new StringBuilder();
int pos = 0;
while (wPattern.startsWith("*") || wPattern.startsWith("?"))
{
if (wPattern.startsWith("*"))
patternB.append(REP_STAR);
else
patternB.append(REP_QUESTION);
wPattern = wPattern.substring(1);
}
int endingSize = 0;
while (wPattern.endsWith("*") || wPattern.endsWith("?"))
{
wPattern = wPattern.substring(0, wPattern.length()-1);
endingSize++;
}
String[] rPNames = wPattern.split("\\*|\\?");
for (int i=0; i<rPNames.length; i++)
{
if (rPNames[i].length() > 0)
{
patternB.append(Pattern.quote(rPNames[i]));
pos += rPNames[i].length();
}
if (i+1<rPNames.length)
{
char orig = wPattern.charAt(pos);
if (orig == '?')
patternB.append(REP_QUESTION);
else if (orig == '*')
patternB.append(REP_STAR);
else
throw new RuntimeException("Bug: should get ? or * on the split position");
pos++;
}
}
char []patternC = pattern.toCharArray();
for (int i=patternC.length-endingSize; i<patternC.length; i++)
{
if (patternC[i] == '*')
patternB.append(REP_STAR);
else
patternB.append(REP_QUESTION);
}
return patternB.toString();
}
}