001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nelson Silva <nelson.silva@inevo.pt> 016 */ 017package org.nuxeo.ecm.platform.auth.saml.key; 018 019import org.apache.commons.logging.Log; 020import org.apache.commons.logging.LogFactory; 021import org.nuxeo.runtime.model.ComponentInstance; 022import org.nuxeo.runtime.model.DefaultComponent; 023import org.opensaml.common.SAMLRuntimeException; 024import org.opensaml.xml.security.CriteriaSet; 025import org.opensaml.xml.security.SecurityException; 026import org.opensaml.xml.security.credential.Credential; 027import org.opensaml.xml.security.credential.KeyStoreCredentialResolver; 028import org.opensaml.xml.security.criteria.EntityIDCriteria; 029 030import java.io.File; 031import java.io.FileInputStream; 032import java.io.IOException; 033import java.io.InputStream; 034import java.security.KeyStore; 035import java.security.KeyStoreException; 036import java.security.NoSuchAlgorithmException; 037import java.security.cert.CertificateException; 038import java.security.cert.X509Certificate; 039import java.util.Enumeration; 040import java.util.HashSet; 041import java.util.Set; 042 043/** 044 * An implementation of {@link KeyManager} that uses a JKS key store. 045 */ 046public class KeyManagerImpl extends DefaultComponent implements KeyManager { 047 048 private static final Log log = LogFactory.getLog(KeyManagerImpl.class); 049 050 private static final String KEYSTORE_TYPE = "JKS"; 051 052 KeyDescriptor config; 053 054 private KeyStore keyStore; 055 056 private KeyStoreCredentialResolver credentialResolver; 057 058 private Set<String> availableCredentials; 059 060 @Override 061 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 062 config = (KeyDescriptor) contribution; 063 setup(); 064 } 065 066 private void setup() { 067 if (config != null) { 068 try { 069 keyStore = getKeyStore(config.getKeystoreFilePath(), config.getKeystorePassword()); 070 } catch (SecurityException e) { 071 throw new RuntimeException(e); 072 } 073 credentialResolver = new KeyStoreCredentialResolver(keyStore, config.getPasswords()); 074 } else { 075 keyStore = null; 076 credentialResolver = null; 077 availableCredentials = null; 078 } 079 } 080 081 private KeyStore getKeyStore(String path, String password) throws SecurityException { 082 KeyStore ks; 083 try { 084 File rootKeystoreFile = new File(path); 085 if (!rootKeystoreFile.exists()) { 086 throw new SecurityException("Unable to find keyStore at " + new File(".").getAbsolutePath() 087 + File.separator + path); 088 } 089 InputStream keystoreIS = new FileInputStream(rootKeystoreFile); 090 ks = java.security.KeyStore.getInstance(KEYSTORE_TYPE); 091 ks.load(keystoreIS, password.toCharArray()); 092 } catch (KeyStoreException | IOException e) { 093 throw new SecurityException(e); 094 } catch (NoSuchAlgorithmException e) { 095 throw new SecurityException(e); 096 } catch (CertificateException e) { 097 throw new SecurityException(e); 098 } 099 return ks; 100 } 101 102 @Override 103 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 104 config = null; 105 setup(); 106 } 107 108 @Override 109 public Credential getCredential(String keyName) { 110 try { 111 CriteriaSet cs = new CriteriaSet(); 112 EntityIDCriteria criteria = new EntityIDCriteria(keyName); 113 cs.add(criteria); 114 return resolveSingle(cs); 115 } catch (org.opensaml.xml.security.SecurityException e) { 116 throw new SAMLRuntimeException("Can't obtain SP signing key", e); 117 } 118 } 119 120 @Override 121 public Set<String> getAvailableCredentials() { 122 if (availableCredentials != null) { 123 return availableCredentials; 124 } 125 try { 126 availableCredentials = new HashSet<>(); 127 Enumeration<String> aliases = keyStore.aliases(); 128 while (aliases.hasMoreElements()) { 129 availableCredentials.add(aliases.nextElement()); 130 } 131 return availableCredentials; 132 } catch (KeyStoreException e) { 133 throw new RuntimeException("Unable to load aliases from keyStore", e); 134 } 135 } 136 137 public X509Certificate getCertificate(String alias) { 138 if (alias == null || alias.length() == 0) { 139 return null; 140 } 141 try { 142 return (X509Certificate) keyStore.getCertificate(alias); 143 } catch (KeyStoreException e) { 144 log.error("Error loading certificate", e); 145 } 146 return null; 147 } 148 149 @Override 150 public Credential getSigningCredential() { 151 if (!hasCredentials() || config.getSigningKey() == null) { 152 return null; 153 } 154 return getCredential(config.getSigningKey()); 155 } 156 157 @Override 158 public Credential getEncryptionCredential() { 159 if (!hasCredentials() || config.getEncryptionKey() == null) { 160 return null; 161 } 162 return getCredential(config.getEncryptionKey()); 163 } 164 165 @Override 166 public Credential getTlsCredential() { 167 if (!hasCredentials() || config.getTlsKey() == null) { 168 return null; 169 } 170 return getCredential(config.getTlsKey()); 171 } 172 173 @Override 174 public Iterable<Credential> resolve(CriteriaSet criteria) throws org.opensaml.xml.security.SecurityException { 175 return credentialResolver.resolve(criteria); 176 } 177 178 @Override 179 public Credential resolveSingle(CriteriaSet criteria) throws SecurityException { 180 return credentialResolver.resolveSingle(criteria); 181 } 182 183 private boolean hasCredentials() { 184 return config != null && credentialResolver != null; 185 } 186}