From 621d9e5c413e561293d7484b93882d985b3fe15f Mon Sep 17 00:00:00 2001 From: Endi Sukma Dewata Date: Sat, 24 Mar 2012 02:27:47 -0500 Subject: Removed unnecessary pki folder. Previously the source code was located inside a pki folder. This folder was created during svn migration and is no longer needed. This folder has now been removed and the contents have been moved up one level. Ticket #131 --- .../src/com/netscape/cmstools/AuditVerify.java | 334 +++++++++++++++++++++ 1 file changed, 334 insertions(+) create mode 100644 base/java-tools/src/com/netscape/cmstools/AuditVerify.java (limited to 'base/java-tools/src/com/netscape/cmstools/AuditVerify.java') diff --git a/base/java-tools/src/com/netscape/cmstools/AuditVerify.java b/base/java-tools/src/com/netscape/cmstools/AuditVerify.java new file mode 100644 index 000000000..fb23e89fd --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/AuditVerify.java @@ -0,0 +1,334 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.cmstools; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FilenameFilter; +import java.io.IOException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.util.StringTokenizer; +import java.util.Vector; + +import netscape.security.x509.X509CertImpl; + +import org.mozilla.jss.CryptoManager; +import org.mozilla.jss.crypto.ObjectNotFoundException; +import org.mozilla.jss.crypto.X509Certificate; + +import com.netscape.cmsutil.util.Utils; + +/** + * Tool for verifying signed audit logs + * + * @version $Revision$, $Date$ + */ +public class AuditVerify { + + private static void usage() { + System.out + .println("Usage: AuditVerify -d -n -a [-P ] [-v]"); + System.exit(1); + } + + public static final String CRYPTO_PROVIDER = "Mozilla-JSS"; + + public static byte[] base64decode(String input) throws Exception { + return Utils.base64decode(input); + } + + // We always sign 0x0a as the line separator, regardless of what + // line separator characters are used in the log file. This helps + // signature verification be platform-independent. + private static final byte LINE_SEP_BYTE = 0x0a; + + private static void output(int linenum, String mesg) throws IOException { + System.out.println("Line " + linenum + ": " + mesg); + } + + private static void writeFile(String curfileName) { + System.out.println("======\nFile: " + curfileName + "\n======"); + } + + private static void writeSigStatus(int linenum, String sigStartFile, + int sigStartLine, String sigStopFile, int sigStopLine, String mesg) + throws IOException { + output(linenum, mesg + ": signature of " + sigStartFile + ":" + + sigStartLine + " to " + sigStopFile + ":" + sigStopLine); + } + + private static class PrefixFilter implements FilenameFilter { + private String prefix; + + public PrefixFilter(String prefix) { + this.prefix = prefix; + } + + public boolean accept(File dir, String name) { + // look for cert* in this directory + return (name.indexOf(prefix + "cert") != -1); + } + } + + public static boolean validPrefix(String configDir, String prefix) + throws IOException { + File dir = new File(configDir); + if (!dir.isDirectory()) { + System.out.println("ERROR: \"" + dir + "\" is not a directory"); + usage(); + } + + String matchingFiles[] = dir.list(new PrefixFilter(prefix)); + + // prefix may be valid if at least one file matched the pattern + return (matchingFiles.length > 0); + } + + public static boolean isSigningCert(X509CertImpl cert) { + boolean[] keyUsage = null; + + try { + keyUsage = cert.getKeyUsage(); + } catch (Exception e) { + e.printStackTrace(); + } + return (keyUsage == null) ? false : keyUsage[0]; + } + + public static void main(String args[]) { + try { + + String dbdir = null; + String logListFile = null; + String signerNick = null; + String prefix = null; + boolean verbose = false; + + for (int i = 0; i < args.length; ++i) { + if (args[i].equals("-d")) { + if (++i >= args.length) + usage(); + dbdir = args[i]; + } else if (args[i].equals("-a")) { + if (++i >= args.length) + usage(); + logListFile = args[i]; + } else if (args[i].equals("-n")) { + if (++i >= args.length) + usage(); + signerNick = args[i]; + } else if (args[i].equals("-P")) { + if (++i >= args.length) + usage(); + prefix = args[i]; + } else if (args[i].equals("-v")) { + verbose = true; + } else { + System.out.println("Unrecognized argument(" + i + "): " + + args[i]); + usage(); + } + } + if (dbdir == null || logListFile == null || signerNick == null) { + System.out.println("Argument omitted"); + usage(); + } + + // get list of log files + Vector logFiles = new Vector(); + BufferedReader r = new BufferedReader(new FileReader(logListFile)); + String listLine; + while ((listLine = r.readLine()) != null) { + StringTokenizer tok = new StringTokenizer(listLine, ","); + while (tok.hasMoreElements()) { + logFiles.addElement(((String) tok.nextElement()).trim()); + } + } + if (logFiles.size() == 0) { + System.out.println("Error: no log files listed in " + logListFile); + System.exit(1); + } + + // initialize crypto stuff + if (prefix == null) { + if (!validPrefix(dbdir, "")) { + System.out.println("ERROR: \"" + dbdir + + "\" does not contain any security databases"); + usage(); + } + CryptoManager.initialize(dbdir); + } else { + if (!validPrefix(dbdir, prefix)) { + System.out.println("ERROR: \"" + prefix + + "\" is not a valid prefix"); + usage(); + } + CryptoManager.initialize( + new CryptoManager.InitializationValues(dbdir, prefix, prefix, + "secmod.db") + ); + } + CryptoManager cm = CryptoManager.getInstance(); + X509Certificate signerCert = cm.findCertByNickname(signerNick); + + X509CertImpl cert_i = null; + if (signerCert != null) { + byte[] signerCert_b = signerCert.getEncoded(); + cert_i = new X509CertImpl(signerCert_b); + } else { + System.out.println("ERROR: signing certificate not found"); + System.exit(1); + } + + // verify signer's certificate + // not checking validity because we want to allow verifying old logs + // + if (!isSigningCert(cert_i)) { + System.out.println("info: signing certificate is not a signing certificate"); + System.exit(1); + } + + PublicKey pubk = signerCert.getPublicKey(); + String sigAlgorithm = null; + if (pubk instanceof RSAPublicKey) { + sigAlgorithm = "SHA-256/RSA"; + } else if (pubk instanceof DSAPublicKey) { + sigAlgorithm = "SHA-256/DSA"; + } else { + System.out.println("Error: unknown key type: " + + pubk.getAlgorithm()); + System.exit(1); + } + Signature sig = Signature.getInstance(sigAlgorithm, CRYPTO_PROVIDER); + sig.initVerify(pubk); + + int goodSigCount = 0; + int badSigCount = 0; + + int lastFileWritten = -1; + + int sigStartLine = 1; + int sigStopLine = 1; + String sigStartFile = (String) logFiles.elementAt(0); + String sigStopFile = null; + int signedLines = 1; + + for (int curfile = 0; curfile < logFiles.size(); ++curfile) { + String curfileName = (String) logFiles.elementAt(curfile); + BufferedReader br = new BufferedReader(new FileReader(curfileName)); + + if (verbose) { + writeFile(curfileName); + lastFileWritten = curfile; + } + + String curLine; + int linenum = 0; + while ((curLine = br.readLine()) != null) { + ++linenum; + if (curLine.indexOf("AUDIT_LOG_SIGNING") != -1) { + if (curfile == 0 && linenum == 1) { + // Ignore the first signature of the first file, + // since it signs data we don't have access to. + if (verbose) { + output(linenum, + "Ignoring first signature of log series"); + } + } else { + int sigStart = curLine.indexOf("sig: ") + 5; + if (sigStart < 5) { + output(linenum, "INVALID SIGNATURE"); + ++badSigCount; + } else { + byte[] logSig = + base64decode(curLine.substring(sigStart)); + + // verify the signature + if (sig.verify(logSig)) { + // signature verifies correctly + if (verbose) { + writeSigStatus(linenum, sigStartFile, + sigStartLine, sigStopFile, sigStopLine, + "verification succeeded"); + } + ++goodSigCount; + } else { + if (lastFileWritten < curfile) { + writeFile(curfileName); + lastFileWritten = curfile; + } + writeSigStatus(linenum, sigStartFile, + sigStartLine, sigStopFile, sigStopLine, + "VERIFICATION FAILED"); + ++badSigCount; + } + } + sig.initVerify(pubk); + signedLines = 0; + sigStartLine = linenum; + sigStartFile = curfileName; + } + } + + byte[] lineBytes = curLine.getBytes("UTF-8"); + sig.update(lineBytes); + sig.update(LINE_SEP_BYTE); + ++signedLines; + sigStopLine = linenum; + sigStopFile = curfileName; + } + + } + + // Make sure there were no unsigned log entries at the end. + // The first signed line is the previous signature, but anything + // more than that is data. + if (signedLines > 1) { + System.out.println( + "ERROR: log entries after " + sigStartFile + + ":" + sigStartLine + " are UNSIGNED"); + badSigCount++; + } + + System.out.println("\nVerification process complete."); + System.out.println("Valid signatures: " + goodSigCount); + System.out.println("Invalid signatures: " + badSigCount); + + if (badSigCount > 0) { + System.exit(2); + } else { + System.exit(0); + } + + } catch (FileNotFoundException fnfe) { + System.out.println(fnfe); + } catch (ObjectNotFoundException onfe) { + System.out.println("ERROR: certificate not found"); + } catch (Exception e) { + e.printStackTrace(); + } + + System.out.println("Verification process FAILED."); + System.exit(1); + } +} -- cgit