/*
* 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 eu.emi.security.authn.x509.helpers.trust.OpensslTruststoreHelper;
import eu.emi.security.authn.x509.impl.OpensslNameUtils;
import eu.emi.security.authn.x509.impl.X500NameUtils;
/**
* Parses a single EUGridPMA namespaces file and returns {@link NamespacePolicy} object.
* The syntax is defined in the document (available from the EUGridPMA website):
* eugridpma-namespaces-format-spec-20060113-0-1-4.doc, Mon, 16 January 2006.
* This class is not thread safe.
* @author K. Benedyczak
*/
public class EuGridPmaNamespacesParser implements NamespacesParser
{
private static final String VERSION_KEY = "#NAMESPACES-VERSION: ";
public static final String NS_REGEXP = "^([0-9a-fA-F]{8})\\.namespaces$";
private static final String SUPPORTED_VERSION = "1.0";
private String filePath;
private String hash;
private String issuer;
private String subject;
private boolean permit;
private boolean openssl1Mode;
public EuGridPmaNamespacesParser(String filePath, boolean openssl1Mode)
{
this.filePath = filePath;
this.openssl1Mode = openssl1Mode;
}
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 " +
"'.namespaces' extension.");
BufferedReader reader = new BufferedReader(new FileReader(filePath));
try
{
String line;
StringBuilder fullLine = new StringBuilder();
int entryNumber = 1;
List<NamespacePolicy> ret = new ArrayList<NamespacePolicy>();
while ((line = reader.readLine()) != null)
{
line = stripComments(line);
if (line.endsWith("\\") && !line.endsWith("\\\\"))
{
fullLine.append(line.substring(0, line.length() - 1));
continue;
}
fullLine.append(line);
String entry = fullLine.toString().trim();
if (entry.length() == 0)
continue;
handleEntry(entry);
if (issuer.contains("="))
{
@SuppressWarnings("deprecation")
String rfcDN = OpensslNameUtils.opensslToRfc2253(issuer);
String issuerHash = OpensslTruststoreHelper.getOpenSSLCAHash(
X500NameUtils.getX500Principal(rfcDN), openssl1Mode);
if (issuerHash.equals(hash))
issuer = hash;
else
issuer = OpensslNameUtils.normalize(issuer);
}
String subject = OpensslNameUtils.normalize(this.subject);
ret.add(new OpensslNamespacePolicyImpl(issuer,
subject, hash,
permit, filePath + ":" + entryNumber));
fullLine = new StringBuilder();
entryNumber++;
}
return ret;
} finally
{
reader.close();
}
}
protected String stripComments(String from) throws IOException
{
if (from.startsWith(VERSION_KEY))
{
String version = from.substring(VERSION_KEY.length());
if (!version.equals(SUPPORTED_VERSION))
throw new IOException("Namespaces policy version " +
version + " is unsupported");
return "";
}
char[] chars = from.toCharArray();
for (int i=0; i<chars.length; i++)
{
boolean escaped = false;
if (chars[i] == '\\' && i<chars.length-1)
{
i++;
escaped = true;
}
if (chars[i] == '#' && !escaped)
return from.substring(0, i);
}
return from;
}
protected void handleEntry(String line) throws IOException
{
char[] chars = line.toCharArray();
int i=0;
i += ParserUtils.checkToken("to", chars, 0, false);
i += eatSpaces(chars, i, true);
i += ParserUtils.checkToken("issuer", chars, i, false);
i += eatSpaces(chars, i, true);
if (chars[i] == '"')
{
StringBuilder sb = new StringBuilder();
i += consumeQuoted(chars, i, sb);
issuer = sb.toString();
} else
{
int r = ParserUtils.checkTokenSoft("self", chars, i, false);
if (r < 0)
throw new IOException("Syntax problem, expected either a quoted issuer DN or the SELF token. Got: " +
new String(chars, i, chars.length-i));
i += r;
issuer = hash;
}
i += eatSpaces(chars, i, true);
int r = ParserUtils.checkTokenSoft("permit", chars, i, false);
permit = true;
if (r < 0)
{
r = ParserUtils.checkTokenSoft("deny", chars, i, false);
permit = false;
}
if (r < 0)
throw new IOException("Syntax problem, expected PERMIT or DENY token. Got: " +
new String(chars, i, chars.length-i));
i += r;
i += eatSpaces(chars, i, true);
i += ParserUtils.checkToken("subject", chars, i, false);
i += eatSpaces(chars, i, true);
StringBuilder sb = new StringBuilder();
i += consumeQuoted(chars, i, sb);
ParserUtils.checkEndOfLine(chars, i);
subject = sb.toString();
}
protected int consumeQuoted(char[] chars, int offset, StringBuilder ret) throws IOException
{
if (chars[offset] != '"' || chars.length < offset+2)
throw new IOException("Syntax problem, expected a quoted string but got: " +
new String(chars, offset, chars.length-offset));
for (int i=1+offset; i<chars.length; i++)
{
boolean escaped = false;
if (chars[i] == '\\' && i<chars.length-1)
{
i++;
escaped = true;
}
if (chars[i] == '"' && !escaped)
{
ret.append(chars, offset+1, i-offset-1);
return ret.length() + 2;
}
}
throw new IOException("Syntax problem, quoted string has no closing double qote: " +
new String(chars, offset, chars.length-offset));
}
private int eatSpaces(char[] string, int offset, boolean atLeastOne) throws IOException
{
int i=0;
while (i+offset < string.length && string[i+offset] == ' ')
i++;
if (atLeastOne && i==0)
throw new IOException("Syntax problem, expected space character(s) here: " +
new String(string, offset, string.length-offset));
return i;
}
}