Skip to content

Commit d44f445

Browse files
committed
fix encoding bug
1 parent 8929d86 commit d44f445

11 files changed

+375
-129
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Linq;
3+
using Xunit;
4+
using dotnet_ansible_vault_decoder;
5+
using System.Text;
6+
using System.IO;
7+
8+
namespace DotNet.Ansible.Vault.Decoder.Test
9+
{
10+
public class CodecTest
11+
{
12+
[Fact]
13+
public void EncodeTest()
14+
{
15+
var codec = new AnsibleVaultCodec();
16+
var data = new byte[128];
17+
var salt = Enumerable.Range(0, 32).Select(i => (byte)((i * 2) & 0xff)).ToArray();
18+
var pass = Enumerable.Range(0, 16).Select(i => (byte)(i & 0xff)).ToArray();
19+
var sb = new StringBuilder();
20+
using(var sw = new StringWriter(sb))
21+
{
22+
codec.Encode(data, pass, salt, sw, "moge", 80);
23+
}
24+
using(var sr = new StringReader(sb.ToString()))
25+
using(var mstm = new MemoryStream())
26+
{
27+
codec.Decode(sr, pass, mstm);
28+
Assert.Equal(data, mstm.ToArray());
29+
}
30+
}
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp2.1</TargetFramework>
5+
<LangVersion>7.3</LangVersion>
6+
<IsPackable>false</IsPackable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
11+
<PackageReference Include="xunit" Version="2.4.0" />
12+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
13+
<ProjectReference Include="../dotnet-ansible-vault-decoder/dotnet-ansible-vault-decoder.csproj" />
14+
<PackageReference Include="Portable.BouncyCastle" Version="1.8.5" />
15+
</ItemGroup>
16+
17+
</Project>

LICENSE.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
3+
MIT License
4+
5+
Copyright (c) 2019 itn3000
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
9+
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
10+
11+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

dotnet-ansible-vault-decoder/AnsibleVaultFile.cs

+8-6
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,15 @@ public static void Save(AnsibleVaultFile f, TextWriter output, string label, int
6363
{
6464
output.WriteLine($"{AnsibleVaultSignature};{f.Version};{f.Algorithm};{f.Label}");
6565
}
66-
var data = new byte[f.Salt.Length + f.ExpectedHMac.Length + f.EncryptedBytes.Length + 2];
66+
var data = new byte[f.Salt.Length * 2 + f.ExpectedHMac.Length * 2 + f.EncryptedBytes.Length * 2 + 2];
6767
var dataSpan = data.AsSpan();
68-
f.Salt.AsSpan().CopyTo(dataSpan);
69-
dataSpan[f.Salt.Length] = 0x0a;
70-
f.ExpectedHMac.AsSpan().CopyTo(dataSpan.Slice(f.Salt.Length + 1));
71-
dataSpan[f.Salt.Length + 1 + f.ExpectedHMac.Length] = 0x0a;
72-
f.EncryptedBytes.AsSpan().CopyTo(dataSpan.Slice(f.Salt.Length + f.ExpectedHMac.Length + 2));
68+
Encoding.ASCII.GetBytes(ByteUtil.ConvertToHexString(f.Salt)).AsSpan().CopyTo(dataSpan);
69+
dataSpan[f.Salt.Length * 2] = 0x0a;
70+
71+
Encoding.ASCII.GetBytes(ByteUtil.ConvertToHexString(f.ExpectedHMac)).AsSpan().CopyTo(dataSpan.Slice(f.Salt.Length * 2 + 1));
72+
dataSpan[f.Salt.Length * 2 + 1 + f.ExpectedHMac.Length * 2] = 0x0a;
73+
74+
Encoding.ASCII.GetBytes(ByteUtil.ConvertToHexString(f.EncryptedBytes)).AsSpan().CopyTo(dataSpan.Slice(f.Salt.Length * 2 + f.ExpectedHMac.Length * 2 + 2));
7375
Span<char> buffer = stackalloc char[width];
7476
while (!dataSpan.IsEmpty)
7577
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using System.Text;
3+
using System.IO;
4+
5+
namespace dotnet_ansible_vault_decoder
6+
{
7+
static class ArgumentUtil
8+
{
9+
public static Stream GetInputStream(string filePath)
10+
{
11+
if (!string.IsNullOrEmpty(filePath))
12+
{
13+
return File.OpenRead(filePath);
14+
}
15+
else
16+
{
17+
return Console.OpenStandardInput();
18+
}
19+
}
20+
public static Stream GetOutputStream(string filePath, bool truncate)
21+
{
22+
if (!string.IsNullOrEmpty(filePath))
23+
{
24+
if (truncate)
25+
{
26+
return File.Create(filePath);
27+
}
28+
else
29+
{
30+
var ret = File.OpenWrite(filePath);
31+
ret.Seek(0, SeekOrigin.End);
32+
return ret;
33+
}
34+
}
35+
else
36+
{
37+
return Console.OpenStandardOutput();
38+
}
39+
}
40+
public static byte[] GetPassword(string password, string passfile, string passenv)
41+
{
42+
if (!string.IsNullOrEmpty(password))
43+
{
44+
return Encoding.UTF8.GetBytes(password);
45+
}
46+
else if(!string.IsNullOrEmpty(passenv))
47+
{
48+
return Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable(passenv));
49+
}
50+
else if(!string.IsNullOrEmpty(passfile))
51+
{
52+
using(var sr = File.OpenText(passfile))
53+
{
54+
var l = sr.ReadLine();
55+
return Encoding.UTF8.GetBytes(l.Trim());
56+
}
57+
}
58+
else
59+
{
60+
throw new ArgumentException("you must set either password, passfile, passenv");
61+
}
62+
}
63+
public static string EolOptionToEolString(string eol)
64+
{
65+
if (string.IsNullOrEmpty(eol))
66+
{
67+
return null;
68+
}
69+
if (eol.Equals("crlf", StringComparison.OrdinalIgnoreCase))
70+
{
71+
return "\r\n";
72+
}
73+
else if (eol.Equals("lf", StringComparison.OrdinalIgnoreCase))
74+
{
75+
return "\n";
76+
}
77+
else if (eol.Equals("cr", StringComparison.OrdinalIgnoreCase))
78+
{
79+
return "\r";
80+
}
81+
else
82+
{
83+
return null;
84+
}
85+
}
86+
}
87+
}

dotnet-ansible-vault-decoder/ByteUtil.cs

+13-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public static byte ConvertToByte(char sp)
2121
}
2222
else
2323
{
24-
throw new Exception($"invalid value:{sp}");
24+
throw new Exception($"invalid value:{sp}({(int)sp})");
2525
}
2626
}
2727
public static byte[] ConvertToBytes(ArraySegment<string> lines)
@@ -63,7 +63,7 @@ static char ByteToHexChar(byte b)
6363
}
6464
else
6565
{
66-
throw new ArgumentOutOfRangeException($"invalid byte value:{b}");
66+
throw new ArgumentOutOfRangeException($"invalid byte value:{(int)b}");
6767
}
6868
}
6969
public static void ConvertToHexChars(ReadOnlySpan<byte> data, Span<char> dest)
@@ -78,5 +78,16 @@ public static void ConvertToHexChars(ReadOnlySpan<byte> data, Span<char> dest)
7878
dest[i * 2 + 1] = ByteToHexChar((byte)(data[i] & 0xf));
7979
}
8080
}
81+
public static string ConvertToHexString(ReadOnlySpan<byte> data)
82+
{
83+
return string.Create(data.Length * 2, data.ToArray(), (c, state) =>
84+
{
85+
for(int i = 0;i<state.Length;i++)
86+
{
87+
c[i * 2] = ByteToHexChar((byte)(state[i] >> 4));
88+
c[i * 2 + 1] = ByteToHexChar((byte)(state[i] & 0xf));
89+
}
90+
});
91+
}
8192
}
8293
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.IO;
3+
using System.Collections.Generic;
4+
using McMaster.Extensions.CommandLineUtils;
5+
using System.Security.Cryptography;
6+
using System.Text;
7+
using System.Linq;
8+
9+
namespace dotnet_ansible_vault_decoder
10+
{
11+
[Command("decode", "decoding ansible vault data")]
12+
class DecodeCommand
13+
{
14+
[Option("-i|--input", "input data file(default stdin)", CommandOptionType.SingleValue)]
15+
public string FilePath { get; set; }
16+
[Option("-o|--output", "output file path(default stdout)", CommandOptionType.SingleValue)]
17+
public string OutputPath { get; set; }
18+
[Option("-p|--password", "encryption password, if not set, input via prompt", CommandOptionType.SingleValue)]
19+
public string Password { get; set; }
20+
[Option("--passfile", "password file(using first line, trailing whitespace will be trimmed", CommandOptionType.SingleValue)]
21+
public string Passfile { get; set; }
22+
[Option("--passenv", "get password from environment variable", CommandOptionType.SingleValue)]
23+
public string PassEnv { get; set; }
24+
public int OnExecute()
25+
{
26+
try
27+
{
28+
var codec = new AnsibleVaultCodec();
29+
using (var stm = ArgumentUtil.GetInputStream(FilePath))
30+
using (var ostm = ArgumentUtil.GetOutputStream(OutputPath, true))
31+
{
32+
using (var sr = new StreamReader(stm, Encoding.UTF8))
33+
{
34+
codec.Decode(sr, ArgumentUtil.GetPassword(Password, Passfile, PassEnv), ostm);
35+
}
36+
}
37+
}
38+
catch (Exception e)
39+
{
40+
Console.Error.WriteLine($"decoding error: {e}");
41+
return 1;
42+
}
43+
return 0;
44+
}
45+
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.IO;
3+
using System.Collections.Generic;
4+
using McMaster.Extensions.CommandLineUtils;
5+
using System.Security.Cryptography;
6+
using System.Text;
7+
using System.Linq;
8+
9+
namespace dotnet_ansible_vault_decoder
10+
{
11+
[Command("encode", "encoding to encrypted ansible vault")]
12+
class EncodeCommand
13+
{
14+
[Option("-i|--input", "input data file(default stdin)", CommandOptionType.SingleValue)]
15+
public string FilePath { get; set; }
16+
[Option("-o|--output", "output file path(default stdout)", CommandOptionType.SingleValue)]
17+
public string OutputPath { get; set; }
18+
[Option("-e|--eol", "output end of line(cr, crlf, lf, if not set, using system default value)", CommandOptionType.SingleValue)]
19+
public string EndOfLine { get; set; }
20+
[Option("-p|--password", "encryption password, if not set, input via prompt", CommandOptionType.SingleValue)]
21+
public string Password { get; set; }
22+
[Option("--passfile", "password file(using first line, trailing whitespace will be trimmed", CommandOptionType.SingleValue)]
23+
public string Passfile { get; set; }
24+
[Option("--passenv", "get password from environment variable", CommandOptionType.SingleValue)]
25+
public string PassEnv { get; set; }
26+
[Option("-l|--label", "vault label", CommandOptionType.SingleValue)]
27+
public string Label { get; set; }
28+
public int OnExecute()
29+
{
30+
try
31+
{
32+
var codec = new AnsibleVaultCodec();
33+
using (var stm = ArgumentUtil.GetInputStream(FilePath))
34+
using (var ostm = ArgumentUtil.GetOutputStream(OutputPath, true))
35+
{
36+
using (var sw = new StreamWriter(ostm, new UTF8Encoding(false)))
37+
{
38+
var lst = new List<byte>();
39+
var buf = new byte[4096];
40+
while (true)
41+
{
42+
var bytesread = stm.Read(buf, 0, 4096);
43+
if (bytesread <= 0)
44+
{
45+
break;
46+
}
47+
lst.AddRange(buf.Take(bytesread));
48+
}
49+
var eolString = ArgumentUtil.EolOptionToEolString(EndOfLine);
50+
if (eolString != null)
51+
{
52+
sw.NewLine = eolString;
53+
}
54+
codec.Encode(lst.ToArray(), ArgumentUtil.GetPassword(Password, Passfile, PassEnv), CreateSalt(), sw, Label, 80);
55+
}
56+
}
57+
}
58+
catch (Exception e)
59+
{
60+
Console.Error.WriteLine($"error in encoding: {e}");
61+
return 1;
62+
}
63+
return 0;
64+
}
65+
byte[] CreateSalt()
66+
{
67+
using (var rng = RandomNumberGenerator.Create())
68+
{
69+
var data = new byte[32];
70+
rng.GetBytes(data);
71+
return data;
72+
}
73+
}
74+
}
75+
76+
}

0 commit comments

Comments
 (0)