aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java
blob: 5b1b0b1ab437079ff5e6f7aa95c9e842a85628cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package de.cowtipper.cowlection.config;

import de.cowtipper.cowlection.Cowlection;
import de.cowtipper.cowlection.util.ApiUtils;
import de.cowtipper.cowlection.util.Utils;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.MathHelper;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
import org.apache.http.conn.ssl.SSLContexts;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Key and secret holder in its own file to avoid people leaking their keys accidentally.
 */
public class CredentialStorage {
    public static String moo;
    public static boolean isMooValid;
    public static SSLContext sslContext;
    private Property propMoo;
    private Property propIsMooValid;
    private final Configuration cfg;

    public CredentialStorage(Configuration configuration) {
        cfg = configuration;
        initConfig();
        verifyNewerHttpsSupport();
    }

    private void initConfig() {
        cfg.load();
        propMoo = cfg.get(Configuration.CATEGORY_CLIENT,
                        "moo", "", "Don't share this with anybody! Do not edit this entry manually either!", Utils.VALID_UUID_PATTERN)
                .setShowInGui(false);
        propMoo.setLanguageKey(Cowlection.MODID + ".config." + propMoo.getName());

        propIsMooValid = cfg.get(Configuration.CATEGORY_CLIENT,
                        "isMooValid", false, "Is the value valid?")
                .setShowInGui(false);
        moo = propMoo.getString();
        isMooValid = propIsMooValid.getBoolean();
        if (cfg.hasChanged()) {
            cfg.save();
        }
    }

    private void verifyNewerHttpsSupport() {
        String javaVersion = System.getProperty("java.version", "unknown java version");
        Pattern javaVersionPattern = Pattern.compile("1\\.8\\.0_(\\d+)"); // e.g. 1.8.0_51
        Matcher javaVersionMatcher = javaVersionPattern.matcher(javaVersion);
        if (!javaVersionMatcher.matches()
                || MathHelper.parseIntWithDefault(javaVersionMatcher.group(1), 1337) >= 101) {
            // newer Java version (>=8u101): *should* already have support by default
            return;
        }

        // running Java <8u101
        if (testNewHttps()) {
            // uhm... looks like someone added the certs to the default JKS already
            return;
        }
        Cowlection.getInstance().getLogger().info("Injecting Let's Encrypt support due to ancient Java version...");
        try {
            KeyStore originalKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            originalKeyStore.load(Files.newInputStream(Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts")), "changeit".toCharArray());

            KeyStore mooKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            mooKeyStore.load(getClass().getResourceAsStream("/https-for-ancient-java.jks"), "mooveit".toCharArray());

            KeyStore tempKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            tempKeyStore.load(null, "moovedit".toCharArray());

            for (Enumeration<String> aliases = originalKeyStore.aliases(); aliases.hasMoreElements(); ) {
                String alias = aliases.nextElement();
                tempKeyStore.setCertificateEntry(alias, originalKeyStore.getCertificate(alias));
            }
            for (Enumeration<String> aliases = mooKeyStore.aliases(); aliases.hasMoreElements(); ) {
                String alias = aliases.nextElement();
                tempKeyStore.setCertificateEntry(alias, mooKeyStore.getCertificate(alias));
            }
            sslContext = SSLContexts.custom()
                    .loadKeyMaterial(tempKeyStore, "moovedit".toCharArray())
                    .loadTrustMaterial(tempKeyStore, null)
                    .build();

            if (!testNewHttps()) {
                System.err.println("Error while trying to add Let's Encrypt support: Could not contact site after running setup");
            }
        } catch (GeneralSecurityException | IOException e) {
            System.err.println("Error while trying to add Let's Encrypt support:");
            e.printStackTrace();
        }
    }

    /**
     * Tests accessing a site that uses Let's Encrypt
     *
     * @return true, if connection was successful
     */
    private boolean testNewHttps() {
        try {
            URLConnection connection = new URL("https://helloworld.letsencrypt.org/").openConnection();
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(8000);
            connection.setDoInput(false);
            connection.setDoOutput(false);
            connection.addRequestProperty("User-Agent", "Forge Mod " + Cowlection.MODNAME + "/" + Cowlection.VERSION + " (" + Cowlection.GITURL + ")");
            if (CredentialStorage.sslContext != null && connection instanceof HttpsURLConnection) {
                ((HttpsURLConnection) connection).setSSLSocketFactory(CredentialStorage.sslContext.getSocketFactory());
            }

            connection.connect();

            // seems to be working since there was no IOException!
            return true;
        } catch (IOException ignored) {
            // most likely a newer https related issue, so setup might fix things
        }
        return false;
    }

    public void setMooIfValid(String moo, boolean commandTriggered) {
        ApiUtils.fetchApiKeyInfo(moo, hyApiKey -> {
            if (hyApiKey != null && hyApiKey.isSuccess()) {
                // api key is valid!
                Cowlection.getInstance().getMoo().setMoo(moo);
                if (commandTriggered) {
                    Cowlection.getInstance().getChatHelper().sendMessage(EnumChatFormatting.GREEN, "[" + Cowlection.MODNAME + "] Successfully verified API key ✔");
                }
            } else if (commandTriggered) {
                // api key is invalid
                String cause = hyApiKey != null ? hyApiKey.getCause() : null;
                Cowlection.getInstance().getChatHelper().sendMessage(EnumChatFormatting.RED, "[" + Cowlection.MODNAME + "] Failed to verify API key: " + (cause != null ? cause : "unknown cause :c"));
            }
        });
    }

    private void setMoo(String moo) {
        CredentialStorage.moo = moo;
        propMoo.set(moo);
        setMooValidity(true);
    }

    public void setMooValidity(boolean isMooValid) {
        CredentialStorage.isMooValid = isMooValid;
        propIsMooValid.set(isMooValid);
        cfg.save();
    }

    public Property getPropIsMooValid() {
        return propIsMooValid;
    }
}