/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.cxf.xkms.x509.repo.file; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.net.URISyntaxException; import java.nio.file.Files; import java.security.cert.CRLException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; import org.apache.cxf.xkms.exception.XKMSConfigurationException; import org.apache.cxf.xkms.handlers.Applications; import org.apache.cxf.xkms.model.xkms.ResultMajorEnum; import org.apache.cxf.xkms.model.xkms.ResultMinorEnum; import org.apache.cxf.xkms.model.xkms.UseKeyWithType; import org.apache.cxf.xkms.x509.repo.CertificateRepo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class FileCertificateRepo implements CertificateRepo { private static final Logger LOG = LoggerFactory.getLogger(FileCertificateRepo.class); private static final String CN_PREFIX = "cn="; private static final String TRUSTED_CAS_PATH = "trusted_cas"; private static final String CRLS_PATH = "crls"; private static final String CAS_PATH = "cas"; private static final String SPLIT_REGEX = "\\s*,\\s*"; private final File storageDir; private final CertificateFactory certFactory; public FileCertificateRepo(String path) { storageDir = new File(path); try { this.certFactory = CertificateFactory.getInstance("X.509"); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } public void saveCertificate(X509Certificate cert, UseKeyWithType id) { saveCategorizedCertificate(cert, id, false, false); } public void saveTrustedCACertificate(X509Certificate cert, UseKeyWithType id) { saveCategorizedCertificate(cert, id, true, false); } public void saveCACertificate(X509Certificate cert, UseKeyWithType id) { saveCategorizedCertificate(cert, id, false, true); } public void saveCRL(X509CRL crl, UseKeyWithType id) { String name = crl.getIssuerX500Principal().getName(); try { String path = convertIdForFileSystem(name) + ".cer"; Pattern p = Pattern.compile("[a-zA-Z_0-9-_]"); if (!p.matcher(path).find()) { throw new URISyntaxException(path, "Input did not match [a-zA-Z_0-9-_]."); } File certFile = new File(storageDir + "/" + CRLS_PATH, path); certFile.getParentFile().mkdirs(); try (OutputStream os = Files.newOutputStream(certFile.toPath()); BufferedOutputStream bos = new BufferedOutputStream(os)) { bos.write(crl.getEncoded()); bos.close(); } } catch (Exception e) { throw new RuntimeException("Error saving CRL " + name + ": " + e.getMessage(), e); } } private boolean saveCategorizedCertificate(X509Certificate cert, UseKeyWithType id, boolean isTrustedCA, boolean isCA) { String category = ""; if (isTrustedCA) { category = TRUSTED_CAS_PATH; } if (isCA) { category = CAS_PATH; } try { File certFile = new File(storageDir + "/" + category, getCertPath(cert, id)); certFile.getParentFile().mkdirs(); try (OutputStream os = Files.newOutputStream(certFile.toPath()); BufferedOutputStream bos = new BufferedOutputStream(os)) { bos.write(cert.getEncoded()); bos.close(); } } catch (Exception e) { throw new RuntimeException("Error saving certificate " + cert.getSubjectDN() + ": " + e.getMessage(), e); } return true; } public String convertIdForFileSystem(String dn) { String result = dn.replace("=", "-"); result = result.replace(", ", "_"); result = result.replace(",", "_"); result = result.replace("/", "_"); result = result.replace("\\", "_"); result = result.replace("{", "_"); result = result.replace("}", "_"); result = result.replace(":", "_"); return result; } public String getCertPath(X509Certificate cert, UseKeyWithType id) throws URISyntaxException { Applications application = null; String path = null; if (id != null) { application = Applications.fromUri(id.getApplication()); } if (application == Applications.SERVICE_ENDPOINT) { path = id.getIdentifier(); } else { path = cert.getSubjectDN().getName(); } path = convertIdForFileSystem(path) + ".cer"; validateCertificatePath(path); return path; } private void validateCertificatePath(String path) throws URISyntaxException { Pattern p = Pattern.compile("[a-zA-Z_0-9-_]"); if (!p.matcher(path).find()) { throw new URISyntaxException(path, "Input did not match [a-zA-Z_0-9-_]."); } } private File[] getX509Files() { List<File> certificateFiles = new ArrayList<>(); try { certificateFiles.addAll(Arrays.asList(storageDir.listFiles())); certificateFiles.addAll(Arrays.asList(new File(storageDir + "/" + TRUSTED_CAS_PATH).listFiles())); certificateFiles.addAll(Arrays.asList(new File(storageDir + "/" + CAS_PATH).listFiles())); certificateFiles.addAll(Arrays.asList(new File(storageDir + "/" + CRLS_PATH).listFiles())); } catch (NullPointerException e) { // } if (certificateFiles.isEmpty()) { throw new XKMSConfigurationException(ResultMajorEnum.HTTP_WWW_W_3_ORG_2002_03_XKMS_RECEIVER, ResultMinorEnum.HTTP_WWW_W_3_ORG_2002_03_XKMS_FAILURE, "File base persistence storage is not found: " + storageDir.getPath()); } return certificateFiles.toArray(new File[certificateFiles.size()]); } public X509Certificate readCertificate(File certFile) throws CertificateException, FileNotFoundException, IOException { try (InputStream is = Files.newInputStream(certFile.toPath())) { return (X509Certificate)certFactory.generateCertificate(is); } } public X509CRL readCRL(File crlFile) throws FileNotFoundException, CRLException, IOException { try (InputStream is = Files.newInputStream(crlFile.toPath())) { return (X509CRL)certFactory.generateCRL(is); } } @Override public List<X509Certificate> getTrustedCaCerts() { List<X509Certificate> results = new ArrayList<>(); File[] list = getX509Files(); for (File certFile : list) { try { if (certFile.isDirectory()) { continue; } if (certFile.getParent().endsWith(TRUSTED_CAS_PATH)) { X509Certificate cert = readCertificate(certFile); results.add(cert); } } catch (Exception e) { LOG.warn(String.format("Cannot load certificate from file: %s. Error: %s", certFile, e.getMessage())); } } return results; } @Override public List<X509Certificate> getCaCerts() { List<X509Certificate> results = new ArrayList<>(); File[] list = getX509Files(); for (File certFile : list) { try { if (certFile.isDirectory()) { continue; } if (certFile.getParent().endsWith(CAS_PATH)) { X509Certificate cert = readCertificate(certFile); results.add(cert); } } catch (Exception e) { LOG.warn(String.format("Cannot load certificate from file: %s. Error: %s", certFile, e.getMessage())); } } return results; } @Override public List<X509CRL> getCRLs() { List<X509CRL> results = new ArrayList<>(); File[] list = getX509Files(); for (File crlFile : list) { try { if (crlFile.isDirectory()) { continue; } if (crlFile.getParent().endsWith(CRLS_PATH)) { X509CRL crl = readCRL(crlFile); results.add(crl); } } catch (Exception e) { LOG.warn(String.format("Cannot load CRL from file: %s. Error: %s", crlFile, e.getMessage())); } } return results; } @Override public X509Certificate findByServiceName(String serviceName) { return findBySubjectDn(CN_PREFIX + serviceName); } @Override public X509Certificate findByEndpoint(String endpoint) { try { String path = convertIdForFileSystem(endpoint) + ".cer"; validateCertificatePath(path); File certFile = new File(storageDir.getAbsolutePath() + "/" + path); if (!certFile.exists()) { LOG.warn(String.format("Certificate not found for endpoint %s, path %s", endpoint, certFile.getAbsolutePath())); return null; } InputStream input = Files.newInputStream(certFile.toPath()); return (X509Certificate)certFactory.generateCertificate(input); } catch (Exception e) { LOG.warn(String.format("Cannot load certificate by endpoint: %s. Error: %s", endpoint, e.getMessage()), e); return null; } } @Override public X509Certificate findBySubjectDn(String subjectDn) { List<X509Certificate> result = new ArrayList<>(); File[] list = getX509Files(); String[] sDnArray = subjectDn.split(SPLIT_REGEX); Arrays.sort(sDnArray); for (File certFile : list) { try { if (certFile.isDirectory()) { continue; } X509Certificate cert = readCertificate(certFile); LOG.debug("Searching for " + subjectDn + ". Checking cert " + cert.getSubjectDN().getName() + ", " + cert.getSubjectX500Principal().getName()); String[] csDnArray = cert.getSubjectDN().getName().split(SPLIT_REGEX); Arrays.sort(csDnArray); String[] csX500Array = cert.getSubjectX500Principal().getName().split(SPLIT_REGEX); Arrays.sort(csX500Array); if (arraysEqualsIgnoreCaseIgnoreWhiteSpace(sDnArray, csDnArray) || arraysEqualsIgnoreCaseIgnoreWhiteSpace(sDnArray, csX500Array)) { result.add(cert); } } catch (Exception e) { LOG.warn(String.format("Cannot load certificate from file: %s. Error: %s", certFile, e.getMessage())); } } if (!result.isEmpty()) { return result.get(0); } return null; } private boolean arraysEqualsIgnoreCaseIgnoreWhiteSpace(String[] s1, String[] s2) { if (s1 == null || s2 == null || s1.length != s2.length) { return false; } for (int i = 0; i < s1.length; i++) { if (!s1[i].trim().equalsIgnoreCase(s2[i].trim())) { return false; } } return true; } @Override public X509Certificate findByIssuerSerial(String issuer, String serial) { List<X509Certificate> result = new ArrayList<>(); File[] list = getX509Files(); for (File certFile : list) { try { if (certFile.isDirectory()) { continue; } X509Certificate cert = readCertificate(certFile); BigInteger cs = cert.getSerialNumber(); BigInteger ss = new BigInteger(serial, 16); if (issuer.equalsIgnoreCase(cert.getIssuerX500Principal().getName()) && cs.equals(ss)) { result.add(cert); } } catch (Exception e) { LOG.warn(String.format("Cannot load certificate from file: %s. Error: %s", certFile, e.getMessage())); } } if (!result.isEmpty()) { return result.get(0); } return null; } }