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 org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.nuxeo.common.utils.Base64;
031import org.nuxeo.ecm.core.api.DocumentModel;
032import org.nuxeo.ecm.directory.DirectoryException;
033import org.nuxeo.ecm.directory.Session;
034import org.nuxeo.ecm.directory.api.DirectoryService;
035import org.nuxeo.ecm.platform.signature.api.exception.CertException;
036import org.nuxeo.ecm.platform.signature.api.pki.CertService;
037import org.nuxeo.ecm.platform.signature.api.pki.RootService;
038import org.nuxeo.ecm.platform.signature.api.user.AliasType;
039import org.nuxeo.ecm.platform.signature.api.user.AliasWrapper;
040import org.nuxeo.ecm.platform.signature.api.user.CNField;
041import org.nuxeo.ecm.platform.signature.api.user.CUserService;
042import org.nuxeo.ecm.platform.signature.api.user.UserInfo;
043import org.nuxeo.runtime.api.Framework;
044import org.nuxeo.runtime.model.ComponentInstance;
045import org.nuxeo.runtime.model.DefaultComponent;
046
047/**
048 * Base implementation of the user certificate service.
049 *
050 * @author <a href="mailto:ws@nuxeo.com">Wojciech Sulejman</a>
051 */
052public class CUserServiceImpl extends DefaultComponent implements CUserService {
053
054    private static final Log LOG = LogFactory.getLog(CUserServiceImpl.class);
055
056    private static final String CERTIFICATE_DIRECTORY_NAME = "certificate";
057
058    protected RootService rootService;
059
060    protected CertService certService;
061
062    /**
063     * Configurable country code
064     */
065    protected String countryCode;
066
067    /**
068     * Configurable organization name
069     */
070    protected String organization;
071
072    /**
073     * Configurable organizational unit name
074     */
075    protected String organizationalUnit;
076
077    @Override
078    public UserInfo getUserInfo(DocumentModel userModel) throws CertException {
079        UserInfo userInfo = null;
080        String userID = (String) userModel.getPropertyValue("user:username");
081        String firstName = (String) userModel.getPropertyValue("user:firstName");
082        String lastName = (String) userModel.getPropertyValue("user:lastName");
083        String email = (String) userModel.getPropertyValue("user:email");
084
085        Map<CNField, String> userFields = new HashMap<CNField, String>();
086
087        userFields.put(CNField.C, countryCode);
088        userFields.put(CNField.O, organization);
089        userFields.put(CNField.OU, organizationalUnit);
090
091        userFields.put(CNField.CN, firstName + " " + lastName);
092        userFields.put(CNField.Email, email);
093        userFields.put(CNField.UserID, userID);
094        userInfo = new UserInfo(userFields);
095        return userInfo;
096    }
097
098    @Override
099    public KeyStore getUserKeystore(String userID, String userKeystorePassword) throws CertException {
100        String keystore64Encoded = Framework.doPrivileged(() -> {
101            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
102                DocumentModel entry = session.getEntry(userID);
103                if (entry != null) {
104                    return (String) entry.getPropertyValue("cert:keystore");
105                } else {
106                    throw new CertException("No directory entry for " + userID);
107                }
108            }
109        });
110        byte[] keystoreBytes = Base64.decode(keystore64Encoded);
111        ByteArrayInputStream byteIS = new ByteArrayInputStream(keystoreBytes);
112        return getCertService().getKeyStore(byteIS, userKeystorePassword);
113    }
114
115    @Override
116    public DocumentModel createCertificate(DocumentModel user, String userKeyPassword) throws CertException {
117        return Framework.doPrivileged(() -> {
118            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
119                String userKeystorePassword = userKeyPassword;
120                DocumentModel certificate = null;
121
122                // create an entry in the directory
123                String userID = (String) user.getPropertyValue("user:username");
124
125                // make sure that no certificates are associated with the
126                // current userid
127                boolean certificateExists = session.hasEntry(userID);
128                if (certificateExists) {
129                    throw new CertException(userID + " already has a certificate");
130                }
131
132                LOG.info("Starting certificate generation for: " + userID);
133                Map<String, Object> map = new HashMap<String, Object>();
134                map.put("userid", userID);
135
136                // add a keystore to a directory entry
137                KeyStore keystore = getCertService().initializeUser(getUserInfo(user), userKeyPassword);
138                ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
139                getCertService().storeCertificate(keystore, byteOS, userKeystorePassword);
140                String keystore64Encoded = Base64.encodeBytes(byteOS.toByteArray());
141                map.put("keystore", keystore64Encoded);
142                map.put("certificate", getUserCertInfo(keystore, user));
143                map.put("keypassword", userKeyPassword);
144                certificate = session.createEntry(map);
145                return certificate;
146            } catch (DirectoryException e) {
147                LOG.error(e);
148                throw new CertException(e);
149            }
150        });
151    }
152
153    protected static DirectoryService getDirectoryService() {
154        return Framework.getService(DirectoryService.class);
155    }
156
157    @Override
158    public String getUserCertInfo(DocumentModel user, String userKeyPassword) throws CertException {
159        String userKeystorePassword = userKeyPassword;
160        String userID = (String) user.getPropertyValue("user:username");
161        KeyStore keystore = getUserKeystore(userID, userKeystorePassword);
162        return getUserCertInfo(keystore, user);
163    }
164
165    private String getUserCertInfo(KeyStore keystore, DocumentModel user) throws CertException {
166        String userCertInfo = null;
167        if (null != keystore) {
168            String userID = (String) user.getPropertyValue("user:username");
169            AliasWrapper alias = new AliasWrapper(userID);
170            X509Certificate certificate = getCertService().getCertificate(keystore, alias.getId(AliasType.CERT));
171            userCertInfo = certificate.getSubjectDN() + " valid till: " + certificate.getNotAfter();
172        }
173        return userCertInfo;
174    }
175
176    @Override
177    public DocumentModel getCertificate(String userID) {
178        return Framework.doPrivileged(() -> {
179            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
180                DocumentModel certificate = session.getEntry(userID);
181                return certificate;
182            }
183        });
184    }
185
186    @Override
187    public byte[] getRootCertificateData() {
188        byte[] certificateData = getRootService().getRootPublicCertificate();
189        return certificateData;
190    }
191
192    @SuppressWarnings("boxing")
193    @Override
194    public boolean hasCertificate(String userID) throws CertException {
195        return Framework.doPrivileged(() -> {
196            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
197                return session.getEntry(userID) != null;
198            }
199        });
200    }
201
202    @Override
203    public void deleteCertificate(String userID) throws CertException {
204        Framework.doPrivileged(() -> {
205            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
206                DocumentModel certEntry = session.getEntry(userID);
207                session.deleteEntry(certEntry);
208                assert (null == session.getEntry(userID));
209            }
210        });
211    }
212
213    @Override
214    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
215        if (contribution instanceof CUserDescriptor) {
216            CUserDescriptor desc = (CUserDescriptor) contribution;
217            countryCode = desc.getCountryCode();
218            organization = desc.getOrganization();
219            organizationalUnit = desc.getOrganizationalUnit();
220        }
221    }
222
223    protected CertService getCertService() {
224        if (certService == null) {
225            certService = Framework.getService(CertService.class);
226        }
227        return certService;
228    }
229
230    protected RootService getRootService() {
231        if (rootService == null) {
232            rootService = Framework.getService(RootService.class);
233        }
234        return rootService;
235    }
236}