.net 如何从C#代码安全地调用Java keytool

fcwjkofz  于 2023-11-20  发布在  .NET
关注(0)|答案(1)|浏览(135)

我想用C#创建一个GUI,用于在后台在cmd.exe上运行keytool,以创建密钥库,包括密钥和证书数据。
输入数据则需要

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

不幸的是,人们可以在密码中键入特殊字符,并且在证书信息中允许空格。
总的来说,我担心有人可能会在某个地方输入一些信息,一旦调用这些信息,就会导致灾难性的命令在后台运行(如rm -rf *)。
有没有一种方法可以将包含输入信息的Java属性文件传递给keytool,或者有没有一种方法可以安全地转义作为字符串参数传递给keytool的所有数据?
我找不到任何类型的文件,keytool可以采取,即使在单独的步骤,这将消除这个问题。
下面是不安全的代码(警告:它不安全!):

  1. using System;
  2. using System.IO;
  3. using System.Diagnostics;
  4. using System.Collections.Generic;
  5. using System.Text.RegularExpressions;
  6. public class AndroidKeystoreCertificateData
  7. {
  8. public string FirstAndLastName;
  9. public string OrganizationalUnit;
  10. public string OrganizationName;
  11. public string CityOrLocality;
  12. public string StateOrProvince;
  13. public string CountryCode;
  14. }
  15. public class AndroidKeystoreData : AndroidKeystoreCertificateData
  16. {
  17. public string KeystorePath;
  18. public string Password;
  19. public string KeyAlias;
  20. public string KeyPassword;
  21. public int ValidityInYears;
  22. }
  23. internal class AndroidUtils
  24. {
  25. private static bool RunCommand(string command, string working_dir, bool show_window = true)
  26. {
  27. using (Process proc = new Process
  28. {
  29. StartInfo =
  30. {
  31. UseShellExecute = false,
  32. FileName = "cmd.exe",
  33. Arguments = command,
  34. CreateNoWindow = !show_window,
  35. WorkingDirectory = working_dir
  36. }
  37. })
  38. {
  39. try
  40. {
  41. proc.Start();
  42. proc.WaitForExit();
  43. return true;
  44. }
  45. catch
  46. {
  47. return false;
  48. }
  49. }
  50. return false;
  51. }
  52. private static string FilterString(string st)
  53. {
  54. return Regex.Replace(st, @"[^\w\d _]", "").Trim();
  55. }
  56. public static string GetKeystoreCertificateInputString(AndroidKeystoreCertificateData data)
  57. {
  58. string strCN = FilterString(data.FirstAndLastName);
  59. string strOU = FilterString(data.OrganizationalUnit);
  60. string strO = FilterString(data.OrganizationName);
  61. string strL = FilterString(data.CityOrLocality);
  62. string cnST = FilterString(data.StateOrProvince);
  63. string cnC = FilterString(data.CountryCode);
  64. string cert = "\"";
  65. if (!string.IsNullOrEmpty(strCN)) cert += "cn=" + strCN + ", ";
  66. if (!string.IsNullOrEmpty(strOU)) cert += "ou=" + strOU + ", ";
  67. if (!string.IsNullOrEmpty(strO)) cert += "o=" + strO + ", ";
  68. if (!string.IsNullOrEmpty(strL)) cert += "l=" + strL + ", ";
  69. if (!string.IsNullOrEmpty(cnST)) cert += "st=" + cnST + ", ";
  70. if (!string.IsNullOrEmpty(cnC)) cert += "c=" + cnC + "\"";
  71. if (cert.Length > 2) return cert;
  72. return string.Empty;
  73. }
  74. private static string GetKeytoolPath()
  75. {
  76. string javaHome = Environment.GetEnvironmentVariable("JAVA_HOME", EnvironmentVariableTarget.User);
  77. return Path.Combine(javaHome, "bin\\keytool");
  78. }
  79. private static string GetKeystoreGenerationCommand(AndroidKeystoreData d)
  80. {
  81. string cert = GetKeystoreCertificateInputString(d);
  82. string keytool = GetKeytoolPath();
  83. string days = (d.ValidityInYears * 365).ToString();
  84. string dname = "-dname \"cn=" + d.KeyAlias + "\"";
  85. if (!string.IsNullOrEmpty(cert)) dname = "-dname " + cert;
  86. string cmd = "echo y | " + keytool + " -genkeypair " + dname +
  87. " -alias " + d.KeyAlias + " -keypass " + d.KeyPassword +
  88. " -keystore " + d.KeystorePath + " -storepass " + d.Password + " -validity " + days;
  89. return cmd;
  90. }
  91. public static bool RunGenerateKeystore(AndroidKeystoreData d)
  92. {
  93. string cmd = GetKeystoreGenerationCommand(d);
  94. string wdir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
  95. return RunCommand(cmd, wdir, false);
  96. }
  97. }

字符串
示例用法如下:

  1. using System;
  2. class MainClass
  3. {
  4. static void Main(string[] args)
  5. {
  6. AndroidKeystoreData d = new AndroidKeystoreData();
  7. d.KeystorePath = "keystorepath";
  8. d.Password = "pass";
  9. d.KeyAlias = "key0";
  10. d.KeyPassword = "pass";
  11. d.ValidityInYears = 25*365;
  12. d.FirstAndLastName = "self";
  13. d.OrganizationalUnit = "my ou";
  14. d.OrganizationName = "my o";
  15. d.CityOrLocality = "my city";
  16. d.StateOrProvince = "my state";
  17. d.CountryCode = "cc";
  18. AndroidUtils.RunGenerateKeystore(d);
  19. }
  20. }


repository的|zip file
关于我尝试的事情的其他信息:

现在我有一个非常严格的正则表达式,但我不知道这是否有问题:名字中有非A-Za-z 0 -9字符的人,如果有人想在密码中使用特殊字符等。理想情况下,如果有一种方法可以安全地通过文件传递参数,我会更喜欢。或者,一种在纯C#中生成Android兼容密钥库的方法,而不依赖于Java keytool。
从上面的MSBuild中直接窃取代码,我设法删除了一些东西,并提出了下面的东西,这似乎是正确的,至少有一个类似的足够有用的功能。

  1. using System;
  2. using System.Text;
  3. using System.Text.RegularExpressions;
  4. namespace AndroidSignTool
  5. {
  6. public class CommandArgumentsBuilder
  7. {
  8. private StringBuilder Cmd { get; } = new StringBuilder();
  9. private readonly Regex DefinitelyNeedQuotes = new Regex(@"^[a-z\\/:0-9\._\-+=]*$", RegexOptions.None);
  10. private readonly Regex AllowedUnquoted = new Regex(@"[|><\s,;""]+", RegexOptions.IgnoreCase);
  11. private bool IsQuotingRequired(string parameter)
  12. {
  13. bool isQuotingRequired = false;
  14. if (parameter != null)
  15. {
  16. bool hasAllUnquotedCharacters = AllowedUnquoted.IsMatch(parameter);
  17. bool hasSomeQuotedCharacters = DefinitelyNeedQuotes.IsMatch(parameter);
  18. isQuotingRequired = !hasAllUnquotedCharacters;
  19. isQuotingRequired = isQuotingRequired || hasSomeQuotedCharacters;
  20. }
  21. return isQuotingRequired;
  22. }
  23. private void AppendTextWithQuoting(string unquotedTextToAppend)
  24. {
  25. if (string.IsNullOrEmpty(unquotedTextToAppend))
  26. return;
  27. bool addQuotes = IsQuotingRequired(unquotedTextToAppend);
  28. if (addQuotes)
  29. {
  30. Cmd.Append('"');
  31. }
  32. // Count the number of quotes
  33. int literalQuotes = 0;
  34. for (int i = 0; i < unquotedTextToAppend.Length; i++)
  35. {
  36. if (unquotedTextToAppend[i] == '"')
  37. {
  38. literalQuotes++;
  39. }
  40. }
  41. if (literalQuotes > 0)
  42. {
  43. // Replace any \" sequences with \\"
  44. unquotedTextToAppend = unquotedTextToAppend.Replace("\\\"", "\\\\\"");
  45. // Now replace any " with \"
  46. unquotedTextToAppend = unquotedTextToAppend.Replace("\"", "\\\"");
  47. }
  48. Cmd.Append(unquotedTextToAppend);
  49. // Be careful any trailing slash doesn't escape the quote we're about to add
  50. if (addQuotes && unquotedTextToAppend.EndsWith("\\", StringComparison.Ordinal))
  51. {
  52. Cmd.Append('\\');
  53. }
  54. if (addQuotes)
  55. {
  56. Cmd.Append('"');
  57. }
  58. }
  59. public CommandArgumentsBuilder()
  60. {
  61. }
  62. public void AppendSwitch(string switchName)
  63. {
  64. if (string.IsNullOrEmpty(switchName))
  65. return;
  66. if (Cmd.Length != 0 && Cmd[Cmd.Length - 1] != ' ')
  67. {
  68. Cmd.Append(' ');
  69. }
  70. Cmd.Append(switchName);
  71. }
  72. public void AppendSwitchIfNotNull(string switchName, string parameter)
  73. {
  74. if (string.IsNullOrEmpty(switchName) || string.IsNullOrEmpty(parameter))
  75. return;
  76. AppendSwitch(switchName);
  77. AppendTextWithQuoting(parameter);
  78. }
  79. public override string ToString() => Cmd.ToString();
  80. }
  81. }


则重写的GetKeystoreGenerationCommand变为

  1. public static string GetKeystoreGenerationCommand(AndroidKeystoreData d)
  2. {
  3. string cert = GetKeystoreCertificateInputString(d);
  4. string keytool = "%JAVA_HOME%\\bin\\keytool" ;// GetKeytoolPath();
  5. string days = (d.ValidityInYears * 365).ToString();
  6. if (!string.IsNullOrEmpty(cert)) cert = d.KeyAlias;
  7. var cmd = new CommandArgumentsBuilder();
  8. cmd.AppendSwitch("echo y | " + keytool);
  9. cmd.AppendSwitch("-genkeypair");
  10. cmd.AppendSwitchIfNotNull("-dname", cert);
  11. cmd.AppendSwitchIfNotNull("-alias", d.KeyAlias);
  12. cmd.AppendSwitchIfNotNull("-keypass", d.KeyPassword);
  13. cmd.AppendSwitchIfNotNull("-storepass", d.Password);
  14. cmd.AppendSwitchIfNotNull("-keystore", d.KeystorePath);
  15. cmd.AppendSwitchIfNotNull("-validity", days);
  16. return cmd.ToString();
  17. }

92vpleto

92vpleto1#

我相信,如果您不想让用户注入shell命令,那么直接调用keytool二进制文件而不是cmd.exe就可以了。

相关问题