001/*
002 * (C) Copyright 2011-2016 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.codec.binary.Base64;
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
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;
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<>();
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.decodeBase64(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                DocumentModel certificate = null;
120
121                // create an entry in the directory
122                String userID = (String) user.getPropertyValue("user:username");
123
124                // make sure that no certificates are associated with the
125                // current userid
126                boolean certificateExists = session.hasEntry(userID);
127                if (certificateExists) {
128                    throw new CertException(userID + " already has a certificate");
129                }
130
131                LOG.info("Starting certificate generation for: " + userID);
132                Map<String, Object> map = new HashMap<>();
133                map.put("userid", userID);
134
135                // add a keystore to a directory entry
136                KeyStore keystore = getCertService().initializeUser(getUserInfo(user), userKeyPassword);
137                ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
138                getCertService().storeCertificate(keystore, byteOS, userKeyPassword);
139                String keystore64Encoded = Base64.encodeBase64String(byteOS.toByteArray());
140                map.put("keystore", keystore64Encoded);
141                map.put("certificate", getUserCertInfo(keystore, user));
142                map.put("keypassword", userKeyPassword);
143                certificate = session.createEntry(map);
144                return certificate;
145            } catch (DirectoryException e) {
146                LOG.error(e);
147                throw new CertException(e);
148            }
149        });
150    }
151
152    protected static DirectoryService getDirectoryService() {
153        return Framework.getService(DirectoryService.class);
154    }
155
156    @Override
157    public String getUserCertInfo(DocumentModel user, String userKeyPassword) throws CertException {
158        String userID = (String) user.getPropertyValue("user:username");
159        KeyStore keystore = getUserKeystore(userID, userKeyPassword);
160        return getUserCertInfo(keystore, user);
161    }
162
163    private String getUserCertInfo(KeyStore keystore, DocumentModel user) throws CertException {
164        String userCertInfo = null;
165        if (null != keystore) {
166            String userID = (String) user.getPropertyValue("user:username");
167            AliasWrapper alias = new AliasWrapper(userID);
168            X509Certificate certificate = getCertService().getCertificate(keystore, alias.getId(AliasType.CERT));
169            userCertInfo = certificate.getSubjectDN() + " valid till: " + certificate.getNotAfter();
170        }
171        return userCertInfo;
172    }
173
174    @Override
175    public DocumentModel getCertificate(String userID) {
176        return Framework.doPrivileged(() -> {
177            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
178                DocumentModel certificate = session.getEntry(userID);
179                return certificate;
180            }
181        });
182    }
183
184    @Override
185    public byte[] getRootCertificateData() {
186        byte[] certificateData = getRootService().getRootPublicCertificate();
187        return certificateData;
188    }
189
190    @SuppressWarnings("boxing")
191    @Override
192    public boolean hasCertificate(String userID) throws CertException {
193        return Framework.doPrivileged(() -> {
194            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
195                return session.getEntry(userID) != null;
196            }
197        });
198    }
199
200    @Override
201    public void deleteCertificate(String userID) throws CertException {
202        Framework.doPrivileged(() -> {
203            try (Session session = getDirectoryService().open(CERTIFICATE_DIRECTORY_NAME)) {
204                DocumentModel certEntry = session.getEntry(userID);
205                session.deleteEntry(certEntry);
206                assert (null == session.getEntry(userID));
207            }
208        });
209    }
210
211    @Override
212    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
213        if (contribution instanceof CUserDescriptor) {
214            CUserDescriptor desc = (CUserDescriptor) contribution;
215            countryCode = desc.getCountryCode();
216            organization = desc.getOrganization();
217            organizationalUnit = desc.getOrganizationalUnit();
218        }
219    }
220
221    protected CertService getCertService() {
222        if (certService == null) {
223            certService = Framework.getService(CertService.class);
224        }
225        return certService;
226    }
227
228    protected RootService getRootService() {
229        if (rootService == null) {
230            rootService = Framework.getService(RootService.class);
231        }
232        return rootService;
233    }
234}