/* * 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(); } }