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}