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