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