我想用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);
}
}
型
repository的|zip file的
关于我尝试的事情的其他信息:
- 我在.NET 4.6.2中,我知道
CommandLineBuilderExtension
,但它的文档开始说不要使用它:这个API支持产品基础结构,不打算直接从代码中使用。 - Xamarin相关codebase seems to rely on whatever commandlinebuilderextension does
- 查看
CommandLineBuilder.cs
源代码,我无法判断它的转义效果如何(它包含了对代码注入的注解),也无法判断仅用于上述代码的最低版本是什么样子。
现在我有一个非常严格的正则表达式,但我不知道这是否有问题:名字中有非A-Za-z 0 -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();
}
型
1条答案
按热度按时间92vpleto1#
我相信,如果您不想让用户注入shell命令,那么直接调用
keytool
二进制文件而不是cmd.exe
就可以了。