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            InputStream keystoreIS = new FileInputStream(rootKeystoreFile);
092            ks = java.security.KeyStore.getInstance(KEYSTORE_TYPE);
093            ks.load(keystoreIS, password.toCharArray());
094        } catch (KeyStoreException | IOException e) {
095            throw new SecurityException(e);
096        } catch (NoSuchAlgorithmException e) {
097            throw new SecurityException(e);
098        } catch (CertificateException e) {
099            throw new SecurityException(e);
100        }
101        return ks;
102    }
103
104    @Override
105    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
106        config = null;
107        setup();
108    }
109
110    @Override
111    public Credential getCredential(String keyName) {
112        try {
113            CriteriaSet cs = new CriteriaSet();
114            EntityIDCriteria criteria = new EntityIDCriteria(keyName);
115            cs.add(criteria);
116            return resolveSingle(cs);
117        } catch (org.opensaml.xml.security.SecurityException e) {
118            throw new SAMLRuntimeException("Can't obtain SP signing key", e);
119        }
120    }
121
122    @Override
123    public Set<String> getAvailableCredentials() {
124        if (availableCredentials != null) {
125            return availableCredentials;
126        }
127        try {
128            availableCredentials = new HashSet<>();
129            Enumeration<String> aliases = keyStore.aliases();
130            while (aliases.hasMoreElements()) {
131                availableCredentials.add(aliases.nextElement());
132            }
133            return availableCredentials;
134        } catch (KeyStoreException e) {
135            throw new RuntimeException("Unable to load aliases from keyStore", e);
136        }
137    }
138
139    public X509Certificate getCertificate(String alias) {
140        if (alias == null || alias.length() == 0) {
141            return null;
142        }
143        try {
144            return (X509Certificate) keyStore.getCertificate(alias);
145        } catch (KeyStoreException e) {
146            log.error("Error loading certificate", e);
147        }
148        return null;
149    }
150
151    @Override
152    public Credential getSigningCredential() {
153        if (!hasCredentials() || config.getSigningKey() == null) {
154            return null;
155        }
156        return getCredential(config.getSigningKey());
157    }
158
159    @Override
160    public Credential getEncryptionCredential() {
161        if (!hasCredentials() || config.getEncryptionKey() == null) {
162            return null;
163        }
164        return getCredential(config.getEncryptionKey());
165    }
166
167    @Override
168    public Credential getTlsCredential() {
169        if (!hasCredentials() || config.getTlsKey() == null) {
170            return null;
171        }
172        return getCredential(config.getTlsKey());
173    }
174
175    @Override
176    public Iterable<Credential> resolve(CriteriaSet criteria) throws org.opensaml.xml.security.SecurityException {
177        return credentialResolver.resolve(criteria);
178    }
179
180    @Override
181    public Credential resolveSingle(CriteriaSet criteria) throws SecurityException {
182        return credentialResolver.resolveSingle(criteria);
183    }
184
185    private boolean hasCredentials() {
186        return config != null && credentialResolver != null;
187    }
188}