-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Expand file tree
/
Copy pathGpgPassCredentialStore.cs
More file actions
109 lines (90 loc) · 4.04 KB
/
Copy pathGpgPassCredentialStore.cs
File metadata and controls
109 lines (90 loc) · 4.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace GitCredentialManager.Interop.Posix
{
public class GpgPassCredentialStore : PlaintextCredentialStore
{
public const string PasswordStoreDirEnvar = "PASSWORD_STORE_DIR";
private readonly IGpg _gpg;
public GpgPassCredentialStore(IFileSystem fileSystem, IGpg gpg, string storeRoot, string @namespace = null)
: base(fileSystem, storeRoot, @namespace)
{
PlatformUtils.EnsurePosix();
EnsureArgument.NotNull(gpg, nameof(gpg));
_gpg = gpg;
}
protected override string CredentialFileExtension => ".gpg";
private string GetGpgId(string credentialFullPath)
{
// Walk up from the credential's directory to the store root, looking for a .gpg-id file.
// This mimics the behaviour of GNU Pass, which uses the nearest .gpg-id in the directory hierarchy.
string dir = Path.GetDirectoryName(credentialFullPath);
while (dir != null)
{
string gpgIdPath = Path.Combine(dir, ".gpg-id");
if (FileSystem.FileExists(gpgIdPath))
{
using (var stream = FileSystem.OpenFileStream(gpgIdPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var reader = new StreamReader(stream))
{
return reader.ReadLine();
}
}
// Stop after checking the store root
if (FileSystem.IsSamePath(dir, StoreRoot))
{
break;
}
dir = Path.GetDirectoryName(dir);
}
throw new Exception($"Cannot find GPG ID in password store at '{StoreRoot}'; run `pass init <gpg-id>` to initialize the store.");
}
protected override bool TryDeserializeCredential(string path, out FileCredential credential)
{
string text = _gpg.DecryptFile(path);
int line1Idx = text.IndexOf(Environment.NewLine, StringComparison.OrdinalIgnoreCase);
if (line1Idx > 0)
{
// Password is the first line
string password = text.Substring(0, line1Idx);
// All subsequent lines are metadata/attributes
string attrText = text.Substring(line1Idx + Environment.NewLine.Length);
using var attrReader = new StringReader(attrText);
IDictionary<string, string> attrs = attrReader.ReadDictionary(StringComparer.OrdinalIgnoreCase);
// Account is optional
attrs.TryGetValue("account", out string account);
// Service is required
if (attrs.TryGetValue("service", out string service))
{
credential = new FileCredential(path, service, account, password);
return true;
}
}
credential = null;
return false;
}
protected override void SerializeCredential(FileCredential credential)
{
string gpgId = GetGpgId(credential.FullPath);
var sb = new StringBuilder(credential.Password);
sb.AppendFormat("{1}service={0}{1}", credential.Service, Environment.NewLine);
sb.AppendFormat("account={0}{1}", credential.Account, Environment.NewLine);
string fileContents = sb.ToString();
// Ensure the parent directory exists
string parentDir = Path.GetDirectoryName(credential.FullPath);
if (!FileSystem.DirectoryExists(parentDir))
{
FileSystem.CreateDirectory(parentDir);
}
// Delete any existing file
if (FileSystem.FileExists(credential.FullPath))
{
FileSystem.DeleteFile(credential.FullPath);
}
// Encrypt!
_gpg.EncryptFile(credential.FullPath, gpgId, fileContents);
}
}
}