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    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}