Skip to content

Commit 8929d86

Browse files
committed
add command
1 parent 2c379a6 commit 8929d86

File tree

3 files changed

+180
-34
lines changed

3 files changed

+180
-34
lines changed

dotnet-ansible-vault-decoder/AnsibleVaultCodec.cs

+13-5
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ namespace dotnet_ansible_vault_decoder
1111
{
1212
public class AnsibleVaultCodec
1313
{
14-
public void Decode(string vaultFilePath, byte[] password, Stream output)
14+
public void Decode(TextReader vaultFileReader, byte[] password, Stream output)
1515
{
16-
var vaultFile = AnsibleVaultFile.Load(vaultFilePath);
16+
var vaultFile = AnsibleVaultFile.Load(vaultFileReader);
1717
var cipher = CipherUtilities.GetCipher("AES/CTR/PKCS7Padding");
1818
var pbkdf2 = new Rfc2898DeriveBytes(password, vaultFile.Salt, 10000, HashAlgorithmName.SHA256);
1919
var derived = pbkdf2.GetBytes(32 + 32 + 16);
@@ -22,7 +22,7 @@ public void Decode(string vaultFilePath, byte[] password, Stream output)
2222
var iv = derived.AsSpan(64, 16).ToArray();
2323
var hmac256 = new HMACSHA256(hmacKey);
2424
var actualhmac = hmac256.ComputeHash(vaultFile.EncryptedBytes);
25-
if(!actualhmac.AsSpan().SequenceEqual(vaultFile.ExpectedHMac))
25+
if (!actualhmac.AsSpan().SequenceEqual(vaultFile.ExpectedHMac))
2626
{
2727
throw new InvalidKeyException("HMAC check error: invalid password");
2828
}
@@ -31,7 +31,15 @@ public void Decode(string vaultFilePath, byte[] password, Stream output)
3131
var decrypted = cipher.DoFinal(vaultFile.EncryptedBytes);
3232
output.Write(decrypted.AsSpan());
3333
}
34-
public void Encode(byte[] data, byte[] password, byte[] salt, TextWriter output, int width)
34+
public void Decode(string vaultFilePath, byte[] password, Stream output)
35+
{
36+
using(var stm = File.OpenRead(vaultFilePath))
37+
using(var sr = new StreamReader(stm, Encoding.UTF8))
38+
{
39+
Decode(sr, password, output);
40+
}
41+
}
42+
public void Encode(byte[] data, byte[] password, byte[] salt, TextWriter output, string label, int width)
3543
{
3644
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256);
3745
var derived = pbkdf2.GetBytes(32 + 32 + 16);
@@ -49,7 +57,7 @@ public void Encode(byte[] data, byte[] password, byte[] salt, TextWriter output,
4957
var hmac256 = new HMACSHA256(hmacKey);
5058
vaultFile.EncryptedBytes = encrypted;
5159
vaultFile.ExpectedHMac = hmac256.ComputeHash(encrypted);
52-
AnsibleVaultFile.Save(vaultFile, output, width);
60+
AnsibleVaultFile.Save(vaultFile, output, label, width);
5361
}
5462
}
5563
}

dotnet-ansible-vault-decoder/AnsibleVaultFile.cs

+42-26
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,59 @@ class AnsibleVaultFile
1010
const string AnsibleVaultSignature = "$ANSIBLE_VAULT";
1111
public string Version { get; set; }
1212
public string Algorithm { get; set; }
13+
public string Label { get; set; }
1314
public byte[] Salt { get; set; }
1415
public byte[] ExpectedHMac { get; set; }
1516
public byte[] EncryptedBytes { get; set; }
16-
public static AnsibleVaultFile Load(string filePath)
17+
public static AnsibleVaultFile Load(TextReader sr)
1718
{
1819
var ret = new AnsibleVaultFile();
19-
using (var stm = File.OpenRead(filePath))
20-
using (var sr = new StreamReader(stm, Encoding.UTF8))
20+
var headerString = sr.ReadLine();
21+
var header = headerString.Split(';');
22+
if (header[0] != AnsibleVaultSignature)
2123
{
22-
var headerString = sr.ReadLine();
23-
var header = headerString.Split(';');
24-
if (header[0] != AnsibleVaultSignature)
25-
{
26-
throw new ArgumentException($"invalid ansible vault header:{headerString}");
27-
}
28-
ret.Version = header[1];
29-
ret.Algorithm = header[2];
30-
var bodyBytes = new List<byte>();
31-
while (true)
24+
throw new ArgumentException($"invalid ansible vault header:{headerString}");
25+
}
26+
ret.Version = header[1];
27+
ret.Algorithm = header[2];
28+
if(header.Length > 3)
29+
{
30+
ret.Label = header[3];
31+
}
32+
var bodyBytes = new List<byte>();
33+
while (true)
34+
{
35+
var line = sr.ReadLine();
36+
if (line == null)
3237
{
33-
var line = sr.ReadLine();
34-
if (line == null)
35-
{
36-
break;
37-
}
38-
ByteUtil.ConvertToBytes(line, bodyBytes);
38+
break;
3939
}
40-
var (salt, expectedhmac, encrypted_bytes) = DecodeBody(bodyBytes.ToArray());
41-
ret.Salt = salt;
42-
ret.ExpectedHMac = expectedhmac;
43-
ret.EncryptedBytes = encrypted_bytes;
40+
ByteUtil.ConvertToBytes(line, bodyBytes);
4441
}
42+
var (salt, expectedhmac, encrypted_bytes) = DecodeBody(bodyBytes.ToArray());
43+
ret.Salt = salt;
44+
ret.ExpectedHMac = expectedhmac;
45+
ret.EncryptedBytes = encrypted_bytes;
4546
return ret;
4647
}
47-
public static void Save(AnsibleVaultFile f, TextWriter output, int width = 80)
48+
public static AnsibleVaultFile Load(string filePath)
4849
{
49-
output.WriteLine($"{AnsibleVaultSignature};{f.Version};{f.Algorithm}");
50+
using (var stm = File.OpenRead(filePath))
51+
using (var sr = new StreamReader(stm, Encoding.UTF8))
52+
{
53+
return Load(sr);
54+
}
55+
}
56+
public static void Save(AnsibleVaultFile f, TextWriter output, string label, int width = 80)
57+
{
58+
if(string.IsNullOrEmpty(f.Label))
59+
{
60+
output.WriteLine($"{AnsibleVaultSignature};{f.Version};{f.Algorithm}");
61+
}
62+
else
63+
{
64+
output.WriteLine($"{AnsibleVaultSignature};{f.Version};{f.Algorithm};{f.Label}");
65+
}
5066
var data = new byte[f.Salt.Length + f.ExpectedHMac.Length + f.EncryptedBytes.Length + 2];
5167
var dataSpan = data.AsSpan();
5268
f.Salt.AsSpan().CopyTo(dataSpan);
@@ -55,7 +71,7 @@ public static void Save(AnsibleVaultFile f, TextWriter output, int width = 80)
5571
dataSpan[f.Salt.Length + 1 + f.ExpectedHMac.Length] = 0x0a;
5672
f.EncryptedBytes.AsSpan().CopyTo(dataSpan.Slice(f.Salt.Length + f.ExpectedHMac.Length + 2));
5773
Span<char> buffer = stackalloc char[width];
58-
while(!dataSpan.IsEmpty)
74+
while (!dataSpan.IsEmpty)
5975
{
6076
var len = Math.Min(40, dataSpan.Length);
6177
ByteUtil.ConvertToHexChars(dataSpan.Slice(0, len), buffer);
+125-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,138 @@
11
using System;
2+
using System.IO;
3+
using System.Text;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Security.Cryptography;
7+
using System.Threading.Tasks;
8+
using MicroBatchFramework;
29

310
namespace dotnet_ansible_vault_decoder
411
{
512
class Program
613
{
7-
static void Main(string[] args)
14+
static async Task Main(string[] args)
815
{
9-
Console.WriteLine("Hello World!");
16+
await BatchHost.CreateDefaultBuilder().RunBatchEngineAsync(args);
1017
}
1118
}
1219

13-
class EncodingBatch : MicroBatchFramework.BatchBase
20+
class EncodeVault : BatchBase
1421
{
22+
Stream GetInputStream(string filePath)
23+
{
24+
if (!string.IsNullOrEmpty(filePath))
25+
{
26+
return File.OpenRead(filePath);
27+
}
28+
else
29+
{
30+
return Console.OpenStandardInput();
31+
}
32+
}
33+
Stream GetOutputStream(string filePath, bool truncate)
34+
{
35+
if (!string.IsNullOrEmpty(filePath))
36+
{
37+
if (truncate)
38+
{
39+
return File.Create(filePath);
40+
}
41+
else
42+
{
43+
var ret = File.OpenWrite(filePath);
44+
ret.Seek(0, SeekOrigin.End);
45+
return ret;
46+
}
47+
}
48+
else
49+
{
50+
return Console.OpenStandardOutput();
51+
}
52+
}
53+
byte[] GetPassword(string password)
54+
{
55+
if (!string.IsNullOrEmpty(password))
56+
{
57+
return Encoding.UTF8.GetBytes(password);
58+
}
59+
else
60+
{
61+
password = Environment.GetEnvironmentVariable("ANSIBLE_VAULT_PASS");
62+
return Encoding.UTF8.GetBytes(password);
63+
}
64+
}
65+
public void Decode(string filePath = null, string password = null, string output = null)
66+
{
67+
var codec = new AnsibleVaultCodec();
68+
using (var stm = GetInputStream(filePath))
69+
using (var ostm = GetOutputStream(output, true))
70+
{
71+
using (var sr = new StreamReader(stm, Encoding.UTF8))
72+
{
73+
codec.Decode(sr, GetPassword(password), ostm);
74+
}
75+
}
76+
}
77+
string EolOptionToEolString(string eol)
78+
{
79+
if (string.IsNullOrEmpty(eol))
80+
{
81+
return null;
82+
}
83+
if (eol.Equals("crlf", StringComparison.OrdinalIgnoreCase))
84+
{
85+
return "\r\n";
86+
}
87+
else if (eol.Equals("lf", StringComparison.OrdinalIgnoreCase))
88+
{
89+
return "\n";
90+
}
91+
else if (eol.Equals("cr", StringComparison.OrdinalIgnoreCase))
92+
{
93+
return "\r";
94+
}
95+
else
96+
{
97+
return null;
98+
}
99+
}
100+
byte[] CreateSalt()
101+
{
102+
using (var rng = RandomNumberGenerator.Create())
103+
{
104+
var data = new byte[32];
105+
rng.GetBytes(data);
106+
return data;
107+
}
108+
}
109+
public void Encode(string filePath = null, string password = null, string output = null, string eol = null, string label = null)
110+
{
111+
var codec = new AnsibleVaultCodec();
112+
using (var stm = GetInputStream(filePath))
113+
using (var ostm = GetOutputStream(output, true))
114+
{
115+
using (var sw = new StreamWriter(ostm, new UTF8Encoding(false)))
116+
{
117+
var lst = new List<byte>();
118+
var buf = new byte[4096];
119+
while (true)
120+
{
121+
var bytesread = stm.Read(buf, 0, 4096);
122+
if (bytesread <= 0)
123+
{
124+
break;
125+
}
126+
lst.AddRange(buf.Take(bytesread));
127+
}
128+
var eolString = EolOptionToEolString(eol);
129+
if (eolString != null)
130+
{
131+
sw.NewLine = eolString;
132+
}
133+
codec.Encode(lst.ToArray(), GetPassword(password), CreateSalt(), sw, label, 80);
134+
}
135+
}
136+
}
15137
}
16138
}

0 commit comments

Comments
 (0)