001/*
002 * Copyright 2024 Emmanuel Bourg
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
017package net.jsign.jca;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.security.AuthProvider;
022import java.security.Key;
023import java.security.KeyStoreSpi;
024import java.security.NoSuchAlgorithmException;
025import java.security.Provider;
026import java.security.UnrecoverableKeyException;
027import java.security.cert.Certificate;
028import java.security.cert.CertificateException;
029import java.util.Enumeration;
030import javax.security.auth.callback.Callback;
031import javax.security.auth.callback.PasswordCallback;
032import javax.security.auth.login.LoginException;
033
034/**
035 * Security provider calling automatically the <code>login()</code> method of the underlying provider password before
036 * signing. It is designed to avoid the <code>CKR_USER_NOT_LOGGED_IN</code> error when signing multiple times with the
037 * ykcs11 PKCS#11 module for the Yubikey.
038 *
039 * @since 7.0
040 */
041public class AutoLoginProvider extends Provider {
042
043    private final AuthProvider provider;
044
045    private char[] storepass;
046
047    public AutoLoginProvider(AuthProvider provider) {
048        super(provider.getName(), provider.getVersion(), provider.getInfo() + " with auto login");
049        this.provider = provider;
050    }
051
052    @Override
053    public Service getService(String type, String algorithm) {
054        if ("KeyStore".equals(type)) {
055            return new PasswordInterceptorService(provider.getService(type, algorithm));
056        } else if ("Signature".equals(type) && storepass != null) {
057            login();
058        }
059
060        return provider.getService(type, algorithm);
061    }
062
063    private void login() {
064        // logout and login again to avoid the CKR_USER_NOT_LOGGED_IN error with the Yubikey PKCS#11 provider
065        try {
066            provider.logout();
067            provider.login(null, callbacks -> {
068                for (Callback callback : callbacks) {
069                    if (callback instanceof PasswordCallback) {
070                        ((PasswordCallback) callback).setPassword(storepass);
071                    }
072                }
073            });
074        } catch (LoginException e) {
075            // ignore the CKR_USER_NOT_LOGGED_IN error thrown when the user isn't logged in
076        }
077    }
078
079    class PasswordInterceptorService extends Service {
080        private final Service service;
081
082        public PasswordInterceptorService(Service service) {
083            super(AutoLoginProvider.this, service.getType(), service.getAlgorithm(), service.getClassName(), null, null);
084            this.service = service;
085        }
086
087        @Override
088        public Object newInstance(Object constructorParameter) throws NoSuchAlgorithmException {
089            return new PasswordInterceptorKeyStoreSpi((KeyStoreSpi) service.newInstance(constructorParameter));
090        }
091    }
092
093    class PasswordInterceptorKeyStoreSpi extends AbstractKeyStoreSpi {
094        private final KeyStoreSpi instance;
095
096        public PasswordInterceptorKeyStoreSpi(KeyStoreSpi instance) {
097            this.instance = instance;
098        }
099
100        @Override
101        public void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException {
102            storepass = password;
103            instance.engineLoad(stream, password);
104        }
105
106        @Override
107        public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException {
108            return instance.engineGetKey(alias, password);
109        }
110
111        @Override
112        public Certificate[] engineGetCertificateChain(String alias) {
113            return instance.engineGetCertificateChain(alias);
114        }
115
116        @Override
117        public Enumeration<String> engineAliases() {
118            return instance.engineAliases();
119        }
120    }
121}