如何从 C# 代码安全地调用 Java keytool [已关闭]

问题描述 投票:0回答:1

我想用 C# 创建一个 GUI,用于在幕后运行 cmd.exe 上的

keytool
以创建密钥库,包括密钥和证书数据。

输入数据然后需要

  • 密钥库路径
  • 密码
  • 密钥别名
  • 密钥密码
  • 有效性
  • 证书信息(cn、ou、o、l、st 和 c)

不幸的是,人们可能会在密码中输入特殊字符,并且证书信息中也允许有空格。

总的来说,我担心有人可能会在某处输入一些信息,一旦调用该信息,可能会导致在幕后运行灾难性的命令(如

rm -rf *
)。

有没有办法将带有输入信息的 Java 属性文件传递给 keytool,或者有没有办法可以安全地转义作为字符串参数传递给 keytool 的所有数据?

我找不到 keytool 可以获取的任何类型的文件,即使在单独的步骤中也无法消除此问题。

这是不安全的代码(警告:它不安全!):

using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text.RegularExpressions;

public class AndroidKeystoreCertificateData
{
  public string FirstAndLastName;
  public string OrganizationalUnit;
  public string OrganizationName;
  public string CityOrLocality;
  public string StateOrProvince;
  public string CountryCode;
}

public class AndroidKeystoreData : AndroidKeystoreCertificateData
{
  public string KeystorePath;
  public string Password;
  public string KeyAlias;
  public string KeyPassword;
  public int ValidityInYears;
}

internal class AndroidUtils
{

  private static bool RunCommand(string command, string working_dir, bool show_window = true)
  {
    using (Process proc = new Process
    {
      StartInfo =
      {
        UseShellExecute = false,
        FileName = "cmd.exe",
        Arguments = command,
        CreateNoWindow = !show_window,
        WorkingDirectory = working_dir
      }
    })
    {
      try
      {
        proc.Start();
        proc.WaitForExit();
        return true;
      }
      catch
      {
        return false;
      }
    }
    return false;
  }
  
  private static string FilterString(string st)
  {
    return Regex.Replace(st, @"[^\w\d _]", "").Trim();
  }

  public static string GetKeystoreCertificateInputString(AndroidKeystoreCertificateData data)
  {
    string strCN = FilterString(data.FirstAndLastName);
    string strOU = FilterString(data.OrganizationalUnit);
    string strO = FilterString(data.OrganizationName);
    string strL = FilterString(data.CityOrLocality);
    string cnST = FilterString(data.StateOrProvince);
    string cnC = FilterString(data.CountryCode);

    string cert = "\"";
    if (!string.IsNullOrEmpty(strCN)) cert += "cn=" + strCN + ", ";
    if (!string.IsNullOrEmpty(strOU)) cert += "ou=" + strOU + ", ";
    if (!string.IsNullOrEmpty(strO))  cert += "o=" + strO + ", ";
    if (!string.IsNullOrEmpty(strL))  cert += "l=" + strL + ", ";
    if (!string.IsNullOrEmpty(cnST))  cert += "st=" + cnST + ", ";
    if (!string.IsNullOrEmpty(cnC))   cert += "c=" + cnC + "\"";

    if (cert.Length > 2) return cert;

    return string.Empty;
  }
  
  private static string GetKeytoolPath()
  {
    string javaHome = Environment.GetEnvironmentVariable("JAVA_HOME", EnvironmentVariableTarget.User);
    return Path.Combine(javaHome, "bin\\keytool");
  }

  private static string GetKeystoreGenerationCommand(AndroidKeystoreData d)
  {
    string cert = GetKeystoreCertificateInputString(d);
    string keytool = GetKeytoolPath();
    string days = (d.ValidityInYears * 365).ToString();

    string dname = "-dname \"cn=" + d.KeyAlias + "\"";
    if (!string.IsNullOrEmpty(cert)) dname = "-dname " + cert;

    string cmd = "echo y | " + keytool + " -genkeypair " + dname + 
        " -alias " + d.KeyAlias + " -keypass " + d.KeyPassword +
        " -keystore " + d.KeystorePath + " -storepass " + d.Password + " -validity " + days;

    return cmd;
  }
  
  public static bool RunGenerateKeystore(AndroidKeystoreData d)
  {
    string cmd = GetKeystoreGenerationCommand(d);
    string wdir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);

    return RunCommand(cmd, wdir, false);
  }
}

一个示例用法是:

using System;

class MainClass
{
  static void Main(string[] args)
  {
    AndroidKeystoreData d = new AndroidKeystoreData();

    d.KeystorePath = "keystorepath";
    d.Password = "pass";
    d.KeyAlias = "key0";
    d.KeyPassword = "pass";

    d.ValidityInYears = 25*365;

    d.FirstAndLastName = "self";
    d.OrganizationalUnit = "my ou";
    d.OrganizationName = "my o";
    d.CityOrLocality = "my city";
    d.StateOrProvince = "my state";
    d.CountryCode = "cc";

    AndroidUtils.RunGenerateKeystore(d);
  }
}

存储库 | zip 文件


有关我尝试过的事情的其他信息:

现在我有一个非常严格的正则表达式,但我不知道这是否可能有问题:名字中含有非 A-Za-z0-9 字符的人,如果有人想在密码中使用特殊字符等等在。理想情况下,如果有一种方法可以通过文件安全地传递参数,我会更愿意。或者,可以通过某种方式在纯 C# 中生成 Android 兼容密钥库,而不依赖于 Java keytool。

从上面的 MSBuild 中直接窃取代码,我设法将一些内容删除并提出如下所示的内容,这似乎是最少的,具有足够相似的功能,非常有用。

using System;
using System.Text;
using System.Text.RegularExpressions;

namespace AndroidSignTool
{
    public class CommandArgumentsBuilder
    {
        private StringBuilder Cmd { get; } = new StringBuilder();

        private readonly Regex DefinitelyNeedQuotes = new Regex(@"^[a-z\\/:0-9\._\-+=]*$", RegexOptions.None);

        private readonly Regex AllowedUnquoted = new Regex(@"[|><\s,;""]+", RegexOptions.IgnoreCase);

        private bool IsQuotingRequired(string parameter)
        {
            bool isQuotingRequired = false;

            if (parameter != null)
            {
                bool hasAllUnquotedCharacters = AllowedUnquoted.IsMatch(parameter);
                bool hasSomeQuotedCharacters = DefinitelyNeedQuotes.IsMatch(parameter);

                isQuotingRequired = !hasAllUnquotedCharacters;
                isQuotingRequired = isQuotingRequired || hasSomeQuotedCharacters;
            }

            return isQuotingRequired;
        }

        private void AppendTextWithQuoting(string unquotedTextToAppend)
        {
            if (string.IsNullOrEmpty(unquotedTextToAppend))
                return;


            bool addQuotes = IsQuotingRequired(unquotedTextToAppend);

            if (addQuotes)
            {
                Cmd.Append('"');
            }

            // Count the number of quotes
            int literalQuotes = 0;
            for (int i = 0; i < unquotedTextToAppend.Length; i++)
            {
                if (unquotedTextToAppend[i] == '"')
                {
                    literalQuotes++;
                }
            }
            if (literalQuotes > 0)
            {
                // Replace any \" sequences with \\"
                unquotedTextToAppend = unquotedTextToAppend.Replace("\\\"", "\\\\\"");
                // Now replace any " with \"
                unquotedTextToAppend = unquotedTextToAppend.Replace("\"", "\\\"");
            }

            Cmd.Append(unquotedTextToAppend);

            // Be careful any trailing slash doesn't escape the quote we're about to add
            if (addQuotes && unquotedTextToAppend.EndsWith("\\", StringComparison.Ordinal))
            {
                Cmd.Append('\\');
            }

            if (addQuotes)
            {
                Cmd.Append('"');
            }
        }

        public CommandArgumentsBuilder()
        {
        }

        public void AppendSwitch(string switchName)
        {
            if (string.IsNullOrEmpty(switchName))
                return;

            if (Cmd.Length != 0 && Cmd[Cmd.Length - 1] != ' ')
            {
                Cmd.Append(' ');
            }

            Cmd.Append(switchName);
        }

        public void AppendSwitchIfNotNull(string switchName, string parameter)
        {
            if (string.IsNullOrEmpty(switchName) || string.IsNullOrEmpty(parameter))
                return;

            AppendSwitch(switchName);
            AppendTextWithQuoting(parameter);
        }

        public override string ToString() => Cmd.ToString();
    }
}

然后重写后的

GetKeystoreGenerationCommand
就变成这样了

public static string GetKeystoreGenerationCommand(AndroidKeystoreData d)
{
    string cert = GetKeystoreCertificateInputString(d);
    string keytool =  "%JAVA_HOME%\\bin\\keytool" ;// GetKeytoolPath();
    string days = (d.ValidityInYears * 365).ToString();

    if (!string.IsNullOrEmpty(cert)) cert = d.KeyAlias;

    var cmd = new CommandArgumentsBuilder();

    cmd.AppendSwitch("echo y | " + keytool);
    cmd.AppendSwitch("-genkeypair");

    cmd.AppendSwitchIfNotNull("-dname", cert);

    cmd.AppendSwitchIfNotNull("-alias", d.KeyAlias);
    cmd.AppendSwitchIfNotNull("-keypass", d.KeyPassword);
    cmd.AppendSwitchIfNotNull("-storepass", d.Password);
    cmd.AppendSwitchIfNotNull("-keystore", d.KeystorePath);
    cmd.AppendSwitchIfNotNull("-validity", days);

    return cmd.ToString();
}
c# .net cmd code-injection keytool
1个回答
1
投票

我相信,如果您不希望用户注入 shell 命令,直接调用

keytool
二进制文件而不是
cmd.exe
就可以解决问题。

© www.soinside.com 2019 - 2024. All rights reserved.