Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/release-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ jobs:
-r ${{ matrix.rid }}
--self-contained true
-p:PublishSingleFile=true
-p:DebugType=none
-p:Version=${{ env.VERSION }}
-o ./publish

Expand Down
1 change: 1 addition & 0 deletions src/CoderPatros.Jsf.Cli/CoderPatros.Jsf.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>jsf-cli</AssemblyName>
<RootNamespace>CoderPatros.Jsf.Cli</RootNamespace>
</PropertyGroup>

Expand Down
35 changes: 30 additions & 5 deletions src/CoderPatros.Jsf.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,22 @@
Description = "Output directory for key files (defaults to current directory)"
};

var genForceOption = new Option<bool>("--force", "-f")
{
Description = "Overwrite existing key files"
};

var generateKeyCommand = new Command("generate-key", "Generate a cryptographic key pair (or symmetric key for HMAC)");
generateKeyCommand.Options.Add(genAlgorithmOption);
generateKeyCommand.Options.Add(genOutputOption);
generateKeyCommand.Options.Add(genForceOption);

generateKeyCommand.SetAction(parseResult =>
{
var algorithm = parseResult.GetValue(genAlgorithmOption)!;
var outputDir = parseResult.GetValue(genOutputOption)
?? new DirectoryInfo(Directory.GetCurrentDirectory());
var force = parseResult.GetValue(genForceOption);

if (!validAlgorithms.Contains(algorithm))
{
Expand All @@ -66,17 +73,32 @@

if (JwkKeyHelper.IsSymmetricAlgorithm(algorithm))
{
var symmetricJwk = JwkKeyHelper.GenerateSymmetricKey(algorithm);
var symmetricPath = Path.Combine(outputDir.FullName, $"{algorithm}-symmetric.jwk");
WriteSecretFile(symmetricPath, symmetricJwk);
if (!force && File.Exists(symmetricPath))
{
Console.Error.WriteLine($"Key file already exists: {symmetricPath}");
Console.Error.WriteLine("Use --force to overwrite existing key files.");
return 1;
}
var symmetricJwk = JwkKeyHelper.GenerateSymmetricKey(algorithm);
WriteSecretFile(symmetricPath, symmetricJwk, force);
Console.WriteLine($"Symmetric key written to {symmetricPath}");
}
else
{
var (privateJwk, publicJwk) = JwkKeyHelper.GenerateAsymmetricKey(algorithm);
var privatePath = Path.Combine(outputDir.FullName, $"{algorithm}-private.jwk");
var publicPath = Path.Combine(outputDir.FullName, $"{algorithm}-public.jwk");
WriteSecretFile(privatePath, privateJwk);
if (!force && (File.Exists(privatePath) || File.Exists(publicPath)))
{
if (File.Exists(privatePath))
Console.Error.WriteLine($"Key file already exists: {privatePath}");
if (File.Exists(publicPath))
Console.Error.WriteLine($"Key file already exists: {publicPath}");
Console.Error.WriteLine("Use --force to overwrite existing key files.");
return 1;
}
var (privateJwk, publicJwk) = JwkKeyHelper.GenerateAsymmetricKey(algorithm);
WriteSecretFile(privatePath, privateJwk, force);
File.WriteAllText(publicPath, publicJwk);
Console.WriteLine($"Private key written to {privatePath}");
Console.WriteLine($"Public key written to {publicPath}");
Expand Down Expand Up @@ -293,10 +315,13 @@

// --- Helpers ---

static void WriteSecretFile(string filePath, string content)
static void WriteSecretFile(string filePath, string content, bool overwrite = false)
{
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
if (overwrite && File.Exists(filePath))
File.Delete(filePath);

// Atomically create with owner-only permissions to avoid TOCTOU race
var options = new FileStreamOptions
{
Expand Down