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 <nelson.silva@inevo.pt>
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    @Override
141    public X509Certificate getCertificate(String alias) {
142        if (alias == null || alias.length() == 0) {
143            return null;
144        }
145        try {
146            return (X509Certificate) keyStore.getCertificate(alias);
147        } catch (KeyStoreException e) {
148            log.error("Error loading certificate", e);
149        }
150        return null;
151    }
152
153    @Override
154    public Credential getSigningCredential() {
155        if (!hasCredentials() || config.getSigningKey() == null) {
156            return null;
157        }
158        return getCredential(config.getSigningKey());
159    }
160
161    @Override
162    public Credential getEncryptionCredential() {
163        if (!hasCredentials() || config.getEncryptionKey() == null) {
164            return null;
165        }
166        return getCredential(config.getEncryptionKey());
167    }
168
169    @Override
170    public Credential getTlsCredential() {
171        if (!hasCredentials() || config.getTlsKey() == null) {
172            return null;
173        }
174        return getCredential(config.getTlsKey());
175    }
176
177    @Override
178    public Iterable<Credential> resolve(CriteriaSet criteria) throws org.opensaml.xml.security.SecurityException {
179        return credentialResolver.resolve(criteria);
180    }
181
182    @Override
183    public Credential resolveSingle(CriteriaSet criteria) throws SecurityException {
184        return credentialResolver.resolveSingle(criteria);
185    }
186
187    private boolean hasCredentials() {
188        return config != null && credentialResolver != null;
189    }
190}