/*
 * Copyright 2014 Andreas Schildbach
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.bitcoinj.crypto;

import javax.annotation.Nonnull;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;

/**
 * An implementation of TrustStoreLoader handles fetching a KeyStore from the operating system, a file, etc. It's
 * necessary because the Java {@link KeyStore} abstraction is not completely seamless and for example
 * we sometimes need slightly different techniques to load the key store on different versions of Android, MacOS,
 * Windows, etc.
 */
public interface TrustStoreLoader {
    KeyStore getKeyStore() throws FileNotFoundException, KeyStoreException;

    String DEFAULT_KEYSTORE_TYPE = KeyStore.getDefaultType();
    String DEFAULT_KEYSTORE_PASSWORD = "changeit";

    class DefaultTrustStoreLoader implements TrustStoreLoader {
        @Override
        public KeyStore getKeyStore() throws FileNotFoundException, KeyStoreException {

            String keystorePath = null;
            String keystoreType = DEFAULT_KEYSTORE_TYPE;
            try {
                // Check if we are on Android.
                Class<?> version = Class.forName("android.os.Build$VERSION");
                // Build.VERSION_CODES.ICE_CREAM_SANDWICH is 14.
                if (version.getDeclaredField("SDK_INT").getInt(version) >= 14) {
                    return loadIcsKeyStore();
                } else {
                    keystoreType = "BKS";
                    keystorePath = System.getProperty("java.home")
                            + "/etc/security/cacerts.bks".replace('/', File.separatorChar);
                }
            } catch (ClassNotFoundException e) {
                // NOP. android.os.Build is not present, so we are not on Android. Fall through.
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new RuntimeException(e); // Should never happen.
            }
            if (keystorePath == null) {
                keystorePath = System.getProperty("javax.net.ssl.trustStore");
            }
            if (keystorePath == null) {
                return loadFallbackStore();
            }
            try {
                return X509Utils.loadKeyStore(keystoreType, DEFAULT_KEYSTORE_PASSWORD,
                        new FileInputStream(keystorePath));
            } catch (FileNotFoundException e) {
                // If we failed to find a system trust store, load our own fallback trust store. This can fail on
                // Android but we should never reach it there.
                return loadFallbackStore();
            }
        }

        private KeyStore loadIcsKeyStore() throws KeyStoreException {
            try {
                // After ICS, Android provided this nice method for loading the keystore,
                // so we don't have to specify the location explicitly.
                KeyStore keystore = KeyStore.getInstance("AndroidCAStore");
                keystore.load(null, null);
                return keystore;
            } catch (IOException | GeneralSecurityException x) {
                throw new KeyStoreException(x);
            }
        }

        private KeyStore loadFallbackStore() throws FileNotFoundException, KeyStoreException {
            return X509Utils.loadKeyStore("JKS", DEFAULT_KEYSTORE_PASSWORD, getClass().getResourceAsStream("cacerts"));
        }
    }

    class FileTrustStoreLoader implements TrustStoreLoader {
        private final File path;

        public FileTrustStoreLoader(@Nonnull File path) throws FileNotFoundException {
            if (!path.exists())
                throw new FileNotFoundException(path.toString());
            this.path = path;
        }

        @Override
        public KeyStore getKeyStore() throws FileNotFoundException, KeyStoreException {
            return X509Utils.loadKeyStore(DEFAULT_KEYSTORE_TYPE, DEFAULT_KEYSTORE_PASSWORD, new FileInputStream(path));
        }
    }
}
