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}