001/*
002 * (C) Copyright 2011 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.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 *    Wojciech Sulejman
016 */
017package org.nuxeo.ecm.platform.signature.core.user;
018
019import java.io.ByteArrayInputStream;
020import java.io.ByteArrayOutputStream;
021import java.security.KeyStore;
022import java.security.cert.X509Certificate;
023import java.util.HashMap;
024import java.util.Map;
025
026import javax.security.auth.login.LoginContext;
027import javax.security.auth.login.LoginException;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.nuxeo.common.utils.Base64;
032import org.nuxeo.ecm.core.api.DocumentModel;
033import org.nuxeo.ecm.core.api.NuxeoException;
034import org.nuxeo.ecm.directory.DirectoryException;
035import org.nuxeo.ecm.directory.Session;
036import org.nuxeo.ecm.directory.api.DirectoryService;
037import org.nuxeo.ecm.platform.signature.api.exception.CertException;
038import org.nuxeo.ecm.platform.signature.api.pki.CertService;
039import org.nuxeo.ecm.platform.signature.api.pki.RootService;
040import org.nuxeo.ecm.platform.signature.api.user.AliasType;
041import org.nuxeo.ecm.platform.signature.api.user.AliasWrapper;
042import org.nuxeo.ecm.platform.signature.api.user.CNField;
043import org.nuxeo.ecm.platform.signature.api.user.CUserService;
044import org.nuxeo.ecm.platform.signature.api.user.UserInfo;
045import org.nuxeo.runtime.api.Framework;
046import org.nuxeo.runtime.model.ComponentInstance;
047import org.nuxeo.runtime.model.DefaultComponent;
048
049/**
050 * Base implementation of the user certificate service.
051 *
052 * @author <a href="mailto:ws@nuxeo.com">Wojciech Sulejman</a>
053 */
054public class CUserServiceImpl extends DefaultComponent implements CUserService {
055
056    private static final Log LOG = LogFactory.getLog(CUserServiceImpl.class);
057
058    private static final String CERTIFICATE_DIRECTORY_NAME = "certificate";
059
060    protected RootService rootService;
061
062    protected CertService certService;
063
064    /**
065     * Configurable country code
066     */
067    protected String countryCode;
068
069    /**
070     * Configurable organization name
071     */
072    protected String organization;
073
074    /**
075     * Configurable organizational unit name
076     */
077    protected String organizationalUnit;
078
079    @Override
080    public UserInfo getUserInfo(DocumentModel userModel) throws CertException {
081        UserInfo userInfo = null;
082        String userID = (String) userModel.getPropertyValue("user:username");
083        String firstName = (String) userModel.getPropertyValue("user:firstName");
084        String lastName = (String) userModel.getPropertyValue("user:lastName");
085        String email = (String) userModel.getPropertyValue("user:email");
086
087        Map<CNField, String> userFields = new HashMap<CNField, String>();
088
089        userFields.put(CNField.C, countryCode);
090        userFields.put(CNField.O, organization);
091        userFields.put(CNField.OU, organizationalUnit);
092
093        userFields.put(CNField.CN, firstName + " " + lastName);
094        userFields.put(CNField.Email, email);
095        userFields.put(CNField.UserID, userID);
096        userInfo = new UserInfo(userFields);
097        return userInfo;
098    }
099
100    @Override
101    public KeyStore getUserKeystore(String userID, String userKeystorePassword) throws CertException {
102        // Log in as system user
103        LoginContext lc;
104        try {
105            lc = Framework.login();
106        } catch (LoginException e) {
107            throw new NuxeoException("Cannot log in as system user", e);
108        }
109        try {
110            // Open directory session
111            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
112                KeyStore keystore = null;
113                DocumentModel entry = session.getEntry(userID);
114                if (entry != null) {
115                    String keystore64Encoded = (String) entry.getPropertyValue("cert:keystore");
116                    byte[] keystoreBytes = Base64.decode(keystore64Encoded);
117                    ByteArrayInputStream byteIS = new ByteArrayInputStream(keystoreBytes);
118                    keystore = getCertService().getKeyStore(byteIS, userKeystorePassword);
119                } else {
120                    throw new CertException("No directory entry for " + userID);
121                }
122                return keystore;
123            }
124        } finally {
125            try {
126                // Login context may be null in tests
127                if (lc != null) {
128                    lc.logout();
129                }
130            } catch (LoginException e) {
131                throw new NuxeoException("Cannot log out system user", e);
132            }
133        }
134    }
135
136    @Override
137    public DocumentModel createCertificate(DocumentModel user, String userKeyPassword) throws CertException {
138        // Log in as system user
139        LoginContext lc;
140        try {
141            lc = Framework.login();
142        } catch (LoginException e) {
143            throw new NuxeoException("Cannot log in as system user", e);
144        }
145        try {
146            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
147                String userKeystorePassword = userKeyPassword;
148                DocumentModel certificate = null;
149
150                // create an entry in the directory
151                String userID = (String) user.getPropertyValue("user:username");
152
153                // make sure that no certificates are associated with the
154                // current userid
155                boolean certificateExists = session.hasEntry(userID);
156                if (certificateExists) {
157                    throw new CertException(userID + " already has a certificate");
158                }
159
160                LOG.info("Starting certificate generation for: " + userID);
161                Map<String, Object> map = new HashMap<String, Object>();
162                map.put("userid", userID);
163
164                // add a keystore to a directory entry
165                KeyStore keystore = getCertService().initializeUser(getUserInfo(user), userKeyPassword);
166                ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
167                getCertService().storeCertificate(keystore, byteOS, userKeystorePassword);
168                String keystore64Encoded = Base64.encodeBytes(byteOS.toByteArray());
169                map.put("keystore", keystore64Encoded);
170                map.put("certificate", getUserCertInfo(keystore, user));
171                map.put("keypassword", userKeyPassword);
172                certificate = session.createEntry(map);
173                return certificate;
174            } catch (DirectoryException e) {
175                LOG.error(e);
176                throw new CertException(e);
177            }
178        } finally {
179            try {
180                // Login context may be null in tests
181                if (lc != null) {
182                    lc.logout();
183                }
184            } catch (LoginException e) {
185                throw new NuxeoException("Cannot log out system user", e);
186            }
187        }
188    }
189
190    protected static DirectoryService getDirectoryService() {
191        return Framework.getService(DirectoryService.class);
192    }
193
194    @Override
195    public String getUserCertInfo(DocumentModel user, String userKeyPassword) throws CertException {
196        String userKeystorePassword = userKeyPassword;
197        String userID = (String) user.getPropertyValue("user:username");
198        KeyStore keystore = getUserKeystore(userID, userKeystorePassword);
199        return getUserCertInfo(keystore, user);
200    }
201
202    private String getUserCertInfo(KeyStore keystore, DocumentModel user) throws CertException {
203        String userCertInfo = null;
204        if (null != keystore) {
205            String userID = (String) user.getPropertyValue("user:username");
206            AliasWrapper alias = new AliasWrapper(userID);
207            X509Certificate certificate = getCertService().getCertificate(keystore, alias.getId(AliasType.CERT));
208            userCertInfo = certificate.getSubjectDN() + " valid till: " + certificate.getNotAfter();
209        }
210        return userCertInfo;
211    }
212
213    @Override
214    public DocumentModel getCertificate(String userID) {
215        // Log in as system user
216        LoginContext lc;
217        try {
218            lc = Framework.login();
219        } catch (LoginException e) {
220            throw new NuxeoException("Cannot log in as system user", e);
221        }
222        try {
223            // Open directory session
224            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
225                DocumentModel certificate = session.getEntry(userID);
226                return certificate;
227            }
228        } finally {
229            try {
230                // Login context may be null in tests
231                if (lc != null) {
232                    lc.logout();
233                }
234            } catch (LoginException e) {
235                throw new NuxeoException("Cannot log out system user", e);
236            }
237        }
238    }
239
240    @Override
241    public byte[] getRootCertificateData() {
242        byte[] certificateData = getRootService().getRootPublicCertificate();
243        return certificateData;
244    }
245
246    @Override
247    public boolean hasCertificate(String userID) throws CertException {
248        // Log in as system user
249        LoginContext lc;
250        try {
251            lc = Framework.login();
252        } catch (LoginException e) {
253            throw new NuxeoException("Cannot log in as system user", e);
254        }
255        try {
256            // Open directory session
257            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
258                return session.getEntry(userID) != null;
259            }
260        } finally {
261            try {
262                // Login context may be null in tests
263                if (lc != null) {
264                    lc.logout();
265                }
266            } catch (LoginException e) {
267                throw new NuxeoException("Cannot log out system user", e);
268            }
269        }
270    }
271
272    @Override
273    public void deleteCertificate(String userID) throws CertException {
274        // Log in as system user
275        LoginContext lc;
276        try {
277            lc = Framework.login();
278        } catch (LoginException e) {
279            throw new NuxeoException("Cannot log in as system user", e);
280        }
281        try {
282            // Open directory session
283            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
284                DocumentModel certEntry = session.getEntry(userID);
285                session.deleteEntry(certEntry);
286                assert (null == session.getEntry(userID));
287            }
288        } finally {
289            try {
290                // Login context may be null in tests
291                if (lc != null) {
292                    lc.logout();
293                }
294            } catch (LoginException e) {
295                throw new NuxeoException("Cannot log out system user", e);
296            }
297        }
298    }
299
300    @Override
301    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
302        if (contribution instanceof CUserDescriptor) {
303            CUserDescriptor desc = (CUserDescriptor) contribution;
304            countryCode = desc.getCountryCode();
305            organization = desc.getOrganization();
306            organizationalUnit = desc.getOrganizationalUnit();
307        }
308    }
309
310    protected CertService getCertService() {
311        if (certService == null) {
312            certService = Framework.getService(CertService.class);
313        }
314        return certService;
315    }
316
317    protected RootService getRootService() {
318        if (rootService == null) {
319            rootService = Framework.getService(RootService.class);
320        }
321        return rootService;
322    }
323}