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