All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zeromq.ZCertStore Maven / Gradle / Ivy

There is a newer version: 0.6.0
Show newest version
package org.zeromq;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.zeromq.util.ZDigest;
import org.zeromq.util.ZMetadata;

/**
 *
    To authenticate new clients using the ZeroMQ CURVE security mechanism,
    we have to check that the client's public key matches a key we know and
    accept. There are numerous ways to store accepted client public keys.
    The mechanism CZMQ implements is "certificates" (plain text files) held
    in a "certificate store" (a disk directory). This class works with such
    certificate stores, and lets you easily load them from disk, and check
    if a given client public key is known or not. The {@link org.zeromq.ZCert} class does the
    work of managing a single certificate.

* Those files need to be in ZMP-Format which is created by {@link org.zeromq.ZConfig} */ public class ZCertStore { public interface Fingerprinter { byte[] print(File path); } public static final class Timestamper implements Fingerprinter { private final byte[] buf = new byte[Long.SIZE / Byte.SIZE]; @Override public byte[] print(File path) { final long value = path.lastModified(); buf[0] = (byte) ((value >>> 56) & 0xff); buf[0] = (byte) ((value >>> 48) & 0xff); buf[0] = (byte) ((value >>> 40) & 0xff); buf[0] = (byte) ((value >>> 32) & 0xff); buf[0] = (byte) ((value >>> 24) & 0xff); buf[0] = (byte) ((value >>> 16) & 0xff); buf[0] = (byte) ((value >>> 8) & 0xff); buf[0] = (byte) ((value) & 0xff); return buf; } } public static final class Hasher implements Fingerprinter { // temporary buffer used for digest. Instance member for performance reasons. private final byte[] buffer = new byte[8192]; @Override public byte[] print(File path) { InputStream input = stream(path); if (input != null) { try { return new ZDigest(buffer).update(input).data(); } catch (IOException e) { return null; } finally { try { input.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; } private InputStream stream(File path) { if (path.isFile()) { try { return new FileInputStream(path); } catch (FileNotFoundException e) { return null; } } else if (path.isDirectory()) { List list = Arrays.asList(path.list()); Collections.sort(list); return new ByteArrayInputStream(list.toString().getBytes(ZMQ.CHARSET)); } return null; } } private interface IFileVisitor { /** * Visits a file. * @param file the file to visit. * @return true to stop the traversal, false to continue. */ boolean visitFile(File file); /** * Visits a directory. * @param dir the directory to visit. * @return true to stop the traversal, false to continue. */ boolean visitDir(File dir); } // Directory location private final File location; // the scanned files (and directories) along with their fingerprint private final Map fingerprints = new HashMap<>(); // collected public keys private final Map publicKeys = new HashMap<>(); private final Fingerprinter finger; /** * Create a Certificate Store at that file system folder location * @param location */ public ZCertStore(String location) { this(location, new Timestamper()); } public ZCertStore(String location, Fingerprinter fingerprinter) { this.finger = fingerprinter; this.location = new File(location); loadFiles(); } private boolean traverseDirectory(File root, IFileVisitor visitor) { assert (root.exists()); assert (root.isDirectory()); if (visitor.visitDir(root)) { return true; } for (File file : root.listFiles()) { if (file.isFile()) { if (visitor.visitFile(file)) { return true; } } else if (file.isDirectory()) { return traverseDirectory(file, visitor); } else { System.out.printf( "WARNING: %s is neither file nor directory? This shouldn't happen....SKIPPING%n", file.getAbsolutePath()); } } return false; } /** * Check if a public key is in the certificate store. * @param publicKey needs to be a 32 byte array representing the public key */ public boolean containsPublicKey(byte[] publicKey) { if (publicKey.length != 32) { throw new RuntimeException("publickey needs to have a size of 32 bytes. got only " + publicKey.length); } return containsPublicKey(ZMQ.Curve.z85Encode(publicKey)); } /** * check if a z85-based public key is in the certificate store. * This method will scan the folder for changes on every call */ public boolean containsPublicKey(String publicKey) { if (publicKey.length() != 40) { throw new RuntimeException("z85 publickeys should have a length of 40 bytes but got " + publicKey.length()); } reloadIfNecessary(); return publicKeys.containsKey(publicKey); } public ZMetadata getMetadata(String publicKey) { reloadIfNecessary(); return publicKeys.get(publicKey); } private void loadFiles() { final Map keys = new HashMap<>(); if (!location.exists()) { location.mkdirs(); } final Map collected = new HashMap<>(); traverseDirectory(location, new IFileVisitor() { @Override public boolean visitFile(File file) { try { ZConfig zconf = ZConfig.load(file.getAbsolutePath()); String publicKey = zconf.getValue("curve/public-key"); if (publicKey == null) { System.out.printf( "Warning!! File %s has no curve/public-key-element. SKIPPING!%n", file.getAbsolutePath()); return false; } if (publicKey.length() == 32) { // we want to store the public-key as Z85-String publicKey = ZMQ.Curve.z85Encode(publicKey.getBytes(ZMQ.CHARSET)); } assert (publicKey.length() == 40); keys.put(publicKey, ZMetadata.read(zconf)); collected.put(file, finger.print(file)); } catch (IOException e) { e.printStackTrace(); } return false; } @Override public boolean visitDir(File dir) { collected.put(dir, finger.print(dir)); return false; } }); publicKeys.clear(); publicKeys.putAll(keys); fingerprints.clear(); fingerprints.putAll(collected); } int getCertificatesCount() { reloadIfNecessary(); return publicKeys.size(); } boolean reloadIfNecessary() { if (checkForChanges()) { loadFiles(); return true; } return false; } /** * Check if files in the certificate folders have been added or removed. */ boolean checkForChanges() { // initialize with last checked files final Map presents = new HashMap<>(fingerprints); boolean modified = traverseDirectory(location, new IFileVisitor() { @Override public boolean visitFile(File file) { return modified(presents.remove(file), file); } @Override public boolean visitDir(File dir) { return modified(presents.remove(dir), dir); } }); // if some files remain, that means they have been deleted since last scan return modified || !presents.isEmpty(); } private boolean modified(byte[] fingerprint, File path) { if (!path.exists()) { // run load-files if one file is not present return true; } if (fingerprint == null) { // file was not scanned before, it has been added return true; } // true if file has been modified. return !Arrays.equals(fingerprint, finger.print(path)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy