001/* 002 * Copyright 2023 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.InputStream; 020import java.security.AccessController; 021import java.security.InvalidKeyException; 022import java.security.InvalidParameterException; 023import java.security.Key; 024import java.security.KeyStore; 025import java.security.KeyStoreException; 026import java.security.NoSuchAlgorithmException; 027import java.security.PrivateKey; 028import java.security.PrivilegedAction; 029import java.security.Provider; 030import java.security.Signature; 031import java.security.SignatureException; 032import java.security.UnrecoverableKeyException; 033import java.security.cert.Certificate; 034import java.util.Enumeration; 035 036import net.jsign.DigestAlgorithm; 037import net.jsign.KeyStoreBuilder; 038import net.jsign.KeyStoreType; 039 040/** 041 * JCA provider using a Jsign keystore and compatible with jarsigner and apksigner. 042 * 043 * <p>The provider must be configured with the keystore parameter (the value depends on the keystore type). 044 * The type of the keystore is one of the names from the {@link KeyStoreType} enum.</p> 045 * 046 * <p>Example:</p> 047 * <pre> 048 * Provider provider = new JsignJcaProvider(); 049 * provider.configure(vaultname) 050 * KeyStore keystore = KeyStore.getInstance(AZUREKEYVAULT.name(), provider); 051 * keystore.load(null, accessToken); 052 * 053 * PrivateKey key = (PrivateKey) keystore.getKey(alias, null); 054 * 055 * Signature signature = Signature.getInstance("SHA256withRSA", provider); 056 * signature.initSign(key); 057 * signature.update("Lorem ipsum dolor sit amet".getBytes()); 058 * signature.sign(); 059 * </pre> 060 * 061 * @since 6.0 062 */ 063public class JsignJcaProvider extends Provider { 064 065 private String keystore; 066 067 public JsignJcaProvider() { 068 super("Jsign", 1.0, "Jsign security provider"); 069 070 AccessController.doPrivileged((PrivilegedAction<Object>) () -> { 071 for (KeyStoreType type : KeyStoreType.values()) { 072 putService(new ProviderService(this, "KeyStore", type.name(), JsignJcaKeyStore.class.getName(), () -> new JsignJcaKeyStore(type, keystore))); 073 } 074 for (String alg : new String[]{"RSA", "ECDSA"}) { 075 for (DigestAlgorithm digest : DigestAlgorithm.values()) { 076 if (digest != DigestAlgorithm.MD5) { 077 String algorithm = digest.name() + "with" + alg; 078 putService(new ProviderService(this, "Signature", algorithm, JsignJcaSignature.class.getName(), () -> new JsignJcaSignature(algorithm))); 079 } 080 } 081 } 082 return null; 083 }); 084 } 085 086 public JsignJcaProvider(String configArg) { 087 this(); 088 configure(configArg); 089 } 090 091 public Provider configure(String configArg) throws InvalidParameterException { 092 this.keystore = configArg; 093 094 return this; 095 } 096 097 static class JsignJcaKeyStore extends AbstractKeyStoreSpi { 098 099 private KeyStoreBuilder builder = new KeyStoreBuilder(); 100 private KeyStore keystore; 101 102 public JsignJcaKeyStore(KeyStoreType type, String keystore) { 103 builder.storetype(type); 104 builder.keystore(keystore); 105 builder.certfile(""); 106 } 107 108 private KeyStore getKeyStore() throws KeyStoreException { 109 if (keystore == null) { 110 keystore = builder.build(); 111 } 112 113 return keystore; 114 } 115 116 @Override 117 public Key engineGetKey(String alias, char[] password) throws UnrecoverableKeyException { 118 if (password != null) { 119 builder.keypass(new String(password)); 120 } 121 try { 122 return new JsignJcaPrivateKey((PrivateKey) getKeyStore().getKey(alias, password), builder.provider()); 123 } catch (UnrecoverableKeyException e) { 124 e.printStackTrace(); // because jarsigner swallows the root cause and hides what's going on 125 throw e; 126 } catch (KeyStoreException | NoSuchAlgorithmException e) { 127 throw new RuntimeException(e); 128 } 129 } 130 131 @Override 132 public Certificate[] engineGetCertificateChain(String alias) { 133 try { 134 return getKeyStore().getCertificateChain(alias); 135 } catch (KeyStoreException e) { 136 return null; 137 } 138 } 139 140 @Override 141 public Enumeration<String> engineAliases() { 142 try { 143 return getKeyStore().aliases(); 144 } catch (KeyStoreException e) { 145 throw new RuntimeException(e); 146 } 147 } 148 149 @Override 150 public void engineLoad(InputStream stream, char[] password) { 151 if (password != null) { 152 builder.storepass(new String(password)); 153 } 154 } 155 } 156 157 static class JsignJcaPrivateKey implements PrivateKey { 158 159 private final PrivateKey privateKey; 160 private final Provider provider; 161 162 public JsignJcaPrivateKey(PrivateKey key, Provider provider) { 163 this.privateKey = key; 164 this.provider = provider; 165 } 166 167 @Override 168 public String getAlgorithm() { 169 return privateKey.getAlgorithm(); 170 } 171 172 @Override 173 public String getFormat() { 174 return privateKey.getFormat(); 175 } 176 177 @Override 178 public byte[] getEncoded() { 179 return privateKey.getEncoded(); 180 } 181 182 public PrivateKey getPrivateKey() { 183 return privateKey; 184 } 185 186 public Provider getProvider() { 187 return provider; 188 } 189 } 190 191 static class JsignJcaSignature extends AbstractSignatureSpi { 192 193 private Signature signature; 194 195 public JsignJcaSignature(String signingAlgorithm) { 196 super(signingAlgorithm); 197 } 198 199 @Override 200 protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { 201 JsignJcaPrivateKey key = (JsignJcaPrivateKey) privateKey; 202 203 try { 204 signature = Signature.getInstance(signingAlgorithm, key.getProvider()); 205 } catch (NoSuchAlgorithmException e) { 206 throw new RuntimeException(e); 207 } 208 signature.initSign(key.getPrivateKey()); 209 } 210 211 @Override 212 protected void engineUpdate(byte b) throws SignatureException { 213 signature.update(b); 214 } 215 216 @Override 217 protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { 218 signature.update(b, off, len); 219 } 220 221 @Override 222 protected byte[] engineSign() throws SignatureException { 223 return signature.sign(); 224 } 225 } 226}