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}