/** * @author ETSI / STF481 / Yann Garcia * @version $URL$ * $Id$ */ package org.etsi.certificates.io; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.etsi.adapter.TERFactory; import org.etsi.certificates.Helpers; import org.etsi.common.ByteHelper; import de.fraunhofer.sit.c2x.CryptoLib; public class CertificatesIO implements ICertificatesIO { /** * Extension file for certificate */ private static final String CERT_EXT = "crt"; /** * Extension file for digests */ private static final String DIGESTS_EXT = "dgs"; /** * Excluded files while building the lists of certificates/private keys */ private static final String[] EXCLUDED_PATTERNS = new String[] { /*for debug: */".svn", "._.DS_Store", ".DS_Store"}; /** * Full path to access certificate files */ private String _fullPathCerts; /** * Memory cache for the certificates */ private Map _cachedCertificates; private Map _cachedCertificatesDigest; private Map _cachedReverseCertificatesDigest; /** * Memory cache for the signing private keys */ private Map _cachedSigningPrivateKey; /** * Memory cache for the encrypt private keys */ private Map _cachedEncryptPrivateKey; /** * Set to true if loadCertificates was already called */ private boolean _areCertificatesLoaded = false; /** * Default constructor */ public CertificatesIO() { _cachedCertificates = new ConcurrentHashMap(); _cachedCertificatesDigest = new ConcurrentHashMap(); _cachedSigningPrivateKey = new ConcurrentHashMap(); _cachedEncryptPrivateKey = new ConcurrentHashMap(); _cachedReverseCertificatesDigest = new ConcurrentHashMap(); } // End of Constructor /** * @desc Load in memory cache the certificates available in the specified directory * @param rootDirectory Root directory to access to the certificates identified by the certificate ID * @param configId A configuration identifier * @return true on success, false otherwise */ @Override public boolean loadCertificates(final String p_rootDirectory, final String p_configId) { // E.g. , cfg01 TERFactory.getInstance().logDebug(">>> CertificatesIO.loadCertificates: '" + p_rootDirectory + "', '" + p_configId + "' - " + _areCertificatesLoaded); // Sanity check if (_areCertificatesLoaded) { return true; } // Build full path if ((p_rootDirectory == null) || (p_rootDirectory.length() == 0)) { _fullPathCerts = System.getProperty("user.dir").replace("\\", "/"); } else { _fullPathCerts = p_rootDirectory.replace("\\", "/"); } if (!_fullPathCerts.endsWith("/")) { _fullPathCerts += "/"; } File certsPath = new File(_fullPathCerts); if (!certsPath.exists()) { TERFactory.getInstance().logError("CertificatesIO.loadCertificates: path '" + _fullPathCerts + "' does not found"); return false; } _areCertificatesLoaded = loadMemoryCache(certsPath); // Load certificates and keys and return if ((p_configId != null) && (p_configId.length() != 0)) { String path = new String(_fullPathCerts + "/" + p_configId); certsPath = new File(path); if (!certsPath.exists()) { TERFactory.getInstance().logError("CertificatesIO.loadCertificates: path '" + path + "' does not found"); return false; } loadMemoryCache(certsPath); // Load certificates and keys and return } return _areCertificatesLoaded; } /** * @desc Unload from memory cache the certificates available * @return true on success, false otherwise */ @Override public boolean unloadCertificates() { _areCertificatesLoaded = false; _fullPathCerts = null; _cachedCertificates.clear(); _cachedCertificatesDigest.clear(); _cachedSigningPrivateKey.clear(); _cachedEncryptPrivateKey.clear(); _cachedReverseCertificatesDigest.clear(); return true; } /** * @desc Read the specified certificate * @param certificateId the certificate identifier * @param certificate the expected certificate * @return true on success, false otherwise */ @Override public boolean readCertificate(final String key, final ByteArrayOutputStream certificate) { // TERFactory.getInstance().logDebug(">>> CertificatesIO.readCertificate: " + key); String certKey; if (_cachedReverseCertificatesDigest.containsKey(key)) { certKey = _cachedReverseCertificatesDigest.get(key); }else{ certKey = key; } if (_cachedCertificates.containsKey(certKey)) { try { certificate.write(_cachedCertificates.get(certKey)); return true; } catch (IOException e) { e.printStackTrace(); } }else{ TERFactory.getInstance().logError("CertificatesIO.readCertificate: key '" + key + "' not found"); } return false; } @Override public boolean readCertificateDigest(final String certificateId, final ByteArrayOutputStream digest) { // TERFactory.getInstance().logDebug(">>> CertificatesIO.readCertificateDigest: " + certificateId); // Sanity check if (!_cachedCertificatesDigest.containsKey(certificateId)) { TERFactory.getInstance().logError("CertificatesIO.readCertificateDigest: key '" + certificateId + "' not found"); return false; } try { digest.write(_cachedCertificatesDigest.get(certificateId)); return true; } catch (IOException e) { e.printStackTrace(); } return false; } /** * @desc Read the signing private key for the specified certificate * @param keysId the keys identifier * @param key the signing private key * @return true on success, false otherwise */ @Override public boolean readSigningKey(final String keyName, final ByteArrayOutputStream key) { // TERFactory.getInstance().logDebug(">>> CertificatesIO.readSigningKey: " + keyName); try { String certKey; if (_cachedReverseCertificatesDigest.containsKey(keyName)) { certKey = _cachedReverseCertificatesDigest.get(keyName); }else{ certKey = keyName; } if (_cachedSigningPrivateKey.containsKey(certKey)) { key.write(_cachedSigningPrivateKey.get(certKey)); return true; } } catch (IOException e) { e.printStackTrace(); } return false; } /** * @desc Read the encrypting private key for the specified certificate * @param keysId the keys identifier * @param key the signing private key * @return true on success, false otherwise */ @Override public boolean readEncryptingKey(final String keyName, final ByteArrayOutputStream key) { String certKey; try { if (_cachedReverseCertificatesDigest.containsKey(keyName)) { certKey = _cachedReverseCertificatesDigest.get(keyName); }else{ certKey = keyName; } if (_cachedEncryptPrivateKey.containsKey(certKey)) { key.write(_cachedEncryptPrivateKey.get(certKey)); return true; } } catch (IOException e) { e.printStackTrace(); } return false; } /** * @desc Load certificates based on existing keys file, excluding xxx_at.bin files * @param p_keysPath path for private key files * @param p_certsPath Path for certificate files * @return true on success, false otherwise */ private boolean loadMemoryCache(final File p_keysPath) { // E.g. /keys, /certs // Retrieve the list of the files in the p_keysPath try { List files = Helpers.getInstance().getFileListing(p_keysPath, CERT_EXT, EXCLUDED_PATTERNS); // Create the memory cache for (File file : files) { try { addCertItem(file); }catch(FileNotFoundException e){} } // End of 'for' statement } catch (IOException e) { e.printStackTrace(); } // load digests try { List files = Helpers.getInstance().getFileListing(p_keysPath, DIGESTS_EXT, EXCLUDED_PATTERNS); // Create the memory cache for (File file : files) { try { addDigestItem(file); }catch(FileNotFoundException e){} } // End of 'for' statement } catch (IOException e) { e.printStackTrace(); } return true; } private void addDigestItem(final File p_file) throws FileNotFoundException, IOException { String filename = p_file.getName(); String certName = filename.substring(0, filename.lastIndexOf(".")).toUpperCase(); // Load certificate byte bytes[] = new byte[64]; FileInputStream fs = new FileInputStream(p_file); int n = fs.read(bytes); fs.close(); if(n == 32){ // take last 8 bytes bytes = Arrays.copyOfRange(bytes, 24, 32); }else if(n >= 64){ bytes = ByteHelper.hexStringToByteArray(new String(bytes).substring(48, 64)); }else if(n >= 16){ bytes = ByteHelper.hexStringToByteArray(new String(bytes).substring(0, 16)); }else if(n == 8){ bytes = Arrays.copyOfRange(bytes, 0, 8); }else{ TERFactory.getInstance().logError("CertificatesIO: " + filename + ": wrong digest file length\n"); return; } _cachedCertificatesDigest.put(certName, bytes); // TERFactory.getInstance().logDebug("CertificatesIO.addDigestItem: Store digest: " + ByteHelper.byteArrayToString(bytes) + " - " + certName); } private void addCertItem(final File p_certFile) throws FileNotFoundException, IOException { // TERFactory.getInstance().logDebug(">>> CertificatesIO.addItem: " + p_certFile); // Load the keys file name String filename = p_certFile.getName(); String certName = filename.substring(0, filename.lastIndexOf(".")).toUpperCase(); // Load certificate byte bytes[] = new byte[(int) p_certFile.length()]; FileInputStream fsKeys = new FileInputStream(p_certFile); fsKeys.read(bytes); fsKeys.close(); bytes = ByteHelper.hexStringToByteArray(new String(bytes)); _cachedCertificates.put(certName, bytes); // TERFactory.getInstance().logDebug("CertificatesIO.addItem: Store cert " + certName + " - " + ByteHelper.byteArrayToString(bytes)); // calculate digest bytes = calculateDigestFromCertificate(bytes); _cachedCertificatesDigest.put(certName, bytes); // TERFactory.getInstance().logDebug("CertificatesIO.addItem: Store digest: " + ByteHelper.byteArrayToString(bytes) + " - " + certName); _cachedReverseCertificatesDigest.put(ByteHelper.byteArrayToString(bytes), certName); // TERFactory.getInstance().logDebug("CertificatesIO.addItem: Store reverse digest " + ByteHelper.byteArrayToString(bytes) + " - " + certName); // Load Private Keys filename = p_certFile.getPath(); filename = filename.substring(0, filename.lastIndexOf(".")); try { File f = new File(filename+".vkey"); if(f.exists()){ long l = f.length(); if(l == 32 || l == 64){ bytes = new byte[64]; fsKeys = new FileInputStream(f); l = fsKeys.read(bytes); fsKeys.close(); if(l == 64){ bytes = ByteHelper.hexStringToByteArray(new String(bytes)); l = 32; } if(l != 32){ TERFactory.getInstance().logError("CertificatesIO: " + f.getName() + ": wrong data length[" + l + "\n"); } if (!_cachedSigningPrivateKey.containsKey(certName)) { _cachedSigningPrivateKey.put(certName, bytes); } }else{ TERFactory.getInstance().logError("CertificatesIO: " + f.getName() + ": wrong key file length\n"); } } }catch(FileNotFoundException e){} try { File f = new File(filename+".ekey"); if(f.exists()){ if(f.length() == 32 || f.length() == 64){ bytes = new byte[64]; fsKeys = new FileInputStream(f); fsKeys.read(bytes); fsKeys.close(); if(f.length() == 64){ bytes = ByteHelper.hexStringToByteArray(new String(bytes)); } if (!_cachedEncryptPrivateKey.containsKey(certName)) { _cachedEncryptPrivateKey.put(certName, bytes); } } } }catch(FileNotFoundException e){} } @Override public String getKeyIdFromHashedId8(byte[] p_hashedId8ToBeUsed) { String key = ByteHelper.byteArrayToString(p_hashedId8ToBeUsed); if (!_cachedReverseCertificatesDigest.containsKey(key)) { return null; } return _cachedReverseCertificatesDigest.get(key).substring(0, _cachedReverseCertificatesDigest.get(key).length() - 7/*.DIGEST*/); } private byte[] calculateDigestFromCertificate(final byte[] p_toBeHashedData) { byte[] hash = CryptoLib.hashWithSha256(p_toBeHashedData); return ByteHelper.extract(hash, hash.length - 8, 8); } } // End of class CertificatesIO