001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nelson Silva <[email protected]> 018 */ 019package org.nuxeo.ecm.platform.auth.saml.key; 020 021import org.apache.commons.logging.Log; 022import org.apache.commons.logging.LogFactory; 023import org.nuxeo.runtime.model.ComponentInstance; 024import org.nuxeo.runtime.model.DefaultComponent; 025import org.opensaml.common.SAMLRuntimeException; 026import org.opensaml.xml.security.CriteriaSet; 027import org.opensaml.xml.security.SecurityException; 028import org.opensaml.xml.security.credential.Credential; 029import org.opensaml.xml.security.credential.KeyStoreCredentialResolver; 030import org.opensaml.xml.security.criteria.EntityIDCriteria; 031 032import java.io.File; 033import java.io.FileInputStream; 034import java.io.IOException; 035import java.io.InputStream; 036import java.security.KeyStore; 037import java.security.KeyStoreException; 038import java.security.NoSuchAlgorithmException; 039import java.security.cert.CertificateException; 040import java.security.cert.X509Certificate; 041import java.util.Enumeration; 042import java.util.HashSet; 043import java.util.Set; 044 045/** 046 * An implementation of {@link KeyManager} that uses a JKS key store. 047 */ 048public class KeyManagerImpl extends DefaultComponent implements KeyManager { 049 050 private static final Log log = LogFactory.getLog(KeyManagerImpl.class); 051 052 private static final String KEYSTORE_TYPE = "JKS"; 053 054 KeyDescriptor config; 055 056 private KeyStore keyStore; 057 058 private KeyStoreCredentialResolver credentialResolver; 059 060 private Set<String> availableCredentials; 061 062 @Override 063 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 064 config = (KeyDescriptor) contribution; 065 setup(); 066 } 067 068 private void setup() { 069 if (config != null) { 070 try { 071 keyStore = getKeyStore(config.getKeystoreFilePath(), config.getKeystorePassword()); 072 } catch (SecurityException e) { 073 throw new RuntimeException(e); 074 } 075 credentialResolver = new KeyStoreCredentialResolver(keyStore, config.getPasswords()); 076 } else { 077 keyStore = null; 078 credentialResolver = null; 079 availableCredentials = null; 080 } 081 } 082 083 private KeyStore getKeyStore(String path, String password) throws SecurityException { 084 KeyStore ks; 085 try { 086 File rootKeystoreFile = new File(path); 087 if (!rootKeystoreFile.exists()) { 088 throw new SecurityException("Unable to find keyStore at " + new File(".").getAbsolutePath() 089 + File.separator + path); 090 } 091 try (InputStream keystoreIS = new FileInputStream(rootKeystoreFile)) { 092 ks = java.security.KeyStore.getInstance(KEYSTORE_TYPE); 093 ks.load(keystoreIS, password.toCharArray()); 094 } 095 } catch (KeyStoreException | IOException e) { 096 throw new SecurityException(e); 097 } catch (NoSuchAlgorithmException e) { 098 throw new SecurityException(e); 099 } catch (CertificateException e) { 100 throw new SecurityException(e); 101 } 102 return ks; 103 } 104 105 @Override 106 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 107 config = null; 108 setup(); 109 } 110 111 @Override 112 public Credential getCredential(String keyName) { 113 try { 114 CriteriaSet cs = new CriteriaSet(); 115 EntityIDCriteria criteria = new EntityIDCriteria(keyName); 116 cs.add(criteria); 117 return resolveSingle(cs); 118 } catch (org.opensaml.xml.security.SecurityException e) { 119 throw new SAMLRuntimeException("Can't obtain SP signing key", e); 120 } 121 } 122 123 @Override 124 public Set<String> getAvailableCredentials() { 125 if (availableCredentials != null) { 126 return availableCredentials; 127 } 128 try { 129 availableCredentials = new HashSet<>(); 130 Enumeration<String> aliases = keyStore.aliases(); 131 while (aliases.hasMoreElements()) { 132 availableCredentials.add(aliases.nextElement()); 133 } 134 return availableCredentials; 135 } catch (KeyStoreException e) { 136 throw new RuntimeException("Unable to load aliases from keyStore", e); 137 } 138 } 139 140 public X509Certificate getCertificate(String alias) { 141 if (alias == null || alias.length() == 0) { 142 return null; 143 } 144 try { 145 return (X509Certificate) keyStore.getCertificate(alias); 146 } catch (KeyStoreException e) { 147 log.error("Error loading certificate", e); 148 } 149 return null; 150 } 151 152 @Override 153 public Credential getSigningCredential() { 154 if (!hasCredentials() || config.getSigningKey() == null) { 155 return null; 156 } 157 return getCredential(config.getSigningKey()); 158 } 159 160 @Override 161 public Credential getEncryptionCredential() { 162 if (!hasCredentials() || config.getEncryptionKey() == null) { 163 return null; 164 } 165 return getCredential(config.getEncryptionKey()); 166 } 167 168 @Override 169 public Credential getTlsCredential() { 170 if (!hasCredentials() || config.getTlsKey() == null) { 171 return null; 172 } 173 return getCredential(config.getTlsKey()); 174 } 175 176 @Override 177 public Iterable<Credential> resolve(CriteriaSet criteria) throws org.opensaml.xml.security.SecurityException { 178 return credentialResolver.resolve(criteria); 179 } 180 181 @Override 182 public Credential resolveSingle(CriteriaSet criteria) throws SecurityException { 183 return credentialResolver.resolveSingle(criteria); 184 } 185 186 private boolean hasCredentials() { 187 return config != null && credentialResolver != null; 188 } 189}