@@ -28,6 +28,23 @@ EylloI7MNGbadPGb
2828-----END AGE ENCRYPTED FILE-----`
2929 // mockEncryptedKeyPlain is the plain value of mockEncryptedKey.
3030 mockEncryptedKeyPlain string = "data"
31+ // mockSshRecipient is a mock age ssh recipient, it matches mockSshIdentity
32+ mockSshRecipient string = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID+Wi8WZw2bXfBpcs/WECttCzP39OkenS6pHWHWGFJvN Test"
33+ // mockSshIdentity is a mock age identity based on an OpenSSH private key (ed25519)
34+ mockSshIdentity string = `-----BEGIN OPENSSH PRIVATE KEY-----
35+ b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
36+ QyNTUxOQAAACA/lovFmcNm13waXLP1hArbQsz9/TpHp0uqR1h1hhSbzQAAAIgCXDMIAlwz
37+ CAAAAAtzc2gtZWQyNTUxOQAAACA/lovFmcNm13waXLP1hArbQsz9/TpHp0uqR1h1hhSbzQ
38+ AAAEBJdWTJ8dC0OnMcwy4gQ96sp6KG8GE9EiyhFGhKldKiST+Wi8WZw2bXfBpcs/WECttC
39+ zP39OkenS6pHWHWGFJvNAAAABFRlc3QB
40+ -----END OPENSSH PRIVATE KEY-----`
41+ mockEncryptedSshKey string = `-----BEGIN AGE ENCRYPTED FILE-----
42+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IDJjd0R4dyB2R3Ns
43+ VUNHaXBiTEJaNU5BMFFQZUpCYWJqODFyTTZ4WWZoRVpUd2M2aTBFCkduUFJHb1U2
44+ K3RqWVQrLzE4anZKZ3h2T3c2MFpZTHlGaHprcElXenByWTAKLS0tIG56MHFSZERl
45+ em9PWmRMMTY4aytYTnVZN04yeER5Z2E3TWxWT3JTZWR2ekUKp/HZLy4MzQqoszGk
46+ +P0hSPPNhOhvFwv4AqCw1+A+WyeHGQPq
47+ -----END AGE ENCRYPTED FILE-----`
3148)
3249
3350func TestMasterKeysFromRecipients (t * testing.T ) {
@@ -41,22 +58,32 @@ func TestMasterKeysFromRecipients(t *testing.T) {
4158 assert .Equal (t , got [0 ].Recipient , mockRecipient )
4259 })
4360
61+ t .Run ("recipient-ssh" , func (t * testing.T ) {
62+ got , err := MasterKeysFromRecipients (mockSshRecipient )
63+ assert .NoError (t , err )
64+
65+ assert .Len (t , got , 1 )
66+ assert .Equal (t , got [0 ].Recipient , mockSshRecipient )
67+ })
68+
4469 t .Run ("recipients" , func (t * testing.T ) {
45- got , err := MasterKeysFromRecipients (mockRecipient + "," + otherRecipient )
70+ got , err := MasterKeysFromRecipients (mockRecipient + "," + otherRecipient + "," + mockSshRecipient )
4671 assert .NoError (t , err )
4772
48- assert .Len (t , got , 2 )
73+ assert .Len (t , got , 3 )
4974 assert .Equal (t , got [0 ].Recipient , mockRecipient )
5075 assert .Equal (t , got [1 ].Recipient , otherRecipient )
76+ assert .Equal (t , got [2 ].Recipient , mockSshRecipient )
5177 })
5278
5379 t .Run ("leading and trailing spaces" , func (t * testing.T ) {
54- got , err := MasterKeysFromRecipients (" " + mockRecipient + " , " + otherRecipient + " " )
80+ got , err := MasterKeysFromRecipients (" " + mockRecipient + " , " + otherRecipient + " , " + mockSshRecipient + " " )
5581 assert .NoError (t , err )
5682
57- assert .Len (t , got , 2 )
83+ assert .Len (t , got , 3 )
5884 assert .Equal (t , got [0 ].Recipient , mockRecipient )
5985 assert .Equal (t , got [1 ].Recipient , otherRecipient )
86+ assert .Equal (t , got [2 ].Recipient , mockSshRecipient )
6087 })
6188
6289 t .Run ("empty" , func (t * testing.T ) {
@@ -75,6 +102,14 @@ func TestMasterKeyFromRecipient(t *testing.T) {
75102 assert .Nil (t , got .parsedIdentities )
76103 })
77104
105+ t .Run ("recipient-ssh" , func (t * testing.T ) {
106+ got , err := MasterKeyFromRecipient (mockSshRecipient )
107+ assert .NoError (t , err )
108+ assert .EqualValues (t , mockSshRecipient , got .Recipient )
109+ assert .NotNil (t , got .parsedRecipient )
110+ assert .Nil (t , got .parsedIdentities )
111+ })
112+
78113 t .Run ("leading and trailing spaces" , func (t * testing.T ) {
79114 got , err := MasterKeyFromRecipient (" " + mockRecipient + " " )
80115 assert .NoError (t , err )
@@ -83,6 +118,14 @@ func TestMasterKeyFromRecipient(t *testing.T) {
83118 assert .Nil (t , got .parsedIdentities )
84119 })
85120
121+ t .Run ("leading and trailing spaces - ssh" , func (t * testing.T ) {
122+ got , err := MasterKeyFromRecipient (" " + mockSshRecipient + " " )
123+ assert .NoError (t , err )
124+ assert .EqualValues (t , mockSshRecipient , got .Recipient )
125+ assert .NotNil (t , got .parsedRecipient )
126+ assert .Nil (t , got .parsedIdentities )
127+ })
128+
86129 t .Run ("invalid recipient" , func (t * testing.T ) {
87130 got , err := MasterKeyFromRecipient ("invalid" )
88131 assert .Error (t , err )
@@ -111,6 +154,8 @@ func TestParsedIdentities_ApplyToMasterKey(t *testing.T) {
111154func TestMasterKey_Encrypt (t * testing.T ) {
112155 mockParsedRecipient , err := parseRecipient (mockRecipient )
113156 assert .NoError (t , err )
157+ mockSshParsedRecipient , err := parseRecipient (mockSshRecipient )
158+ assert .NoError (t , err )
114159
115160 t .Run ("recipient" , func (t * testing.T ) {
116161 key := & MasterKey {
@@ -120,6 +165,14 @@ func TestMasterKey_Encrypt(t *testing.T) {
120165 assert .NotEmpty (t , key .EncryptedKey )
121166 })
122167
168+ t .Run ("recipient ssh" , func (t * testing.T ) {
169+ key := & MasterKey {
170+ Recipient : mockSshRecipient ,
171+ }
172+ assert .NoError (t , key .Encrypt ([]byte (mockEncryptedKeyPlain )))
173+ assert .NotEmpty (t , key .EncryptedKey )
174+ })
175+
123176 t .Run ("parsed recipient" , func (t * testing.T ) {
124177 key := & MasterKey {
125178 parsedRecipient : mockParsedRecipient ,
@@ -128,13 +181,21 @@ func TestMasterKey_Encrypt(t *testing.T) {
128181 assert .NotEmpty (t , key .EncryptedKey )
129182 })
130183
184+ t .Run ("parsed recipient ssh" , func (t * testing.T ) {
185+ key := & MasterKey {
186+ parsedRecipient : mockSshParsedRecipient ,
187+ }
188+ assert .NoError (t , key .Encrypt ([]byte (mockEncryptedKeyPlain )))
189+ assert .NotEmpty (t , key .EncryptedKey )
190+ })
191+
131192 t .Run ("invalid recipient" , func (t * testing.T ) {
132193 key := & MasterKey {
133194 Recipient : "invalid" ,
134195 }
135196 err := key .Encrypt ([]byte (mockEncryptedKeyPlain ))
136197 assert .Error (t , err )
137- assert .ErrorContains (t , err , "failed to parse input as Bech32-encoded age public key " )
198+ assert .ErrorContains (t , err , "failed to parse input, unknown recipient type: " )
138199 assert .Empty (t , key .EncryptedKey )
139200 })
140201
@@ -188,6 +249,25 @@ func TestMasterKey_Decrypt(t *testing.T) {
188249 assert .EqualValues (t , mockEncryptedKeyPlain , got )
189250 })
190251
252+ t .Run ("loaded identities ssh" , func (t * testing.T ) {
253+ key := & MasterKey {EncryptedKey : mockEncryptedSshKey }
254+ tmp := t .TempDir ()
255+ overwriteUserConfigDir (t , tmp )
256+
257+ homeDir , err := os .UserHomeDir ()
258+ assert .NoError (t , err )
259+ keyPath := filepath .Join (homeDir , ".ssh/id_25519" )
260+ assert .True (t , strings .HasPrefix (keyPath , homeDir ))
261+
262+ assert .NoError (t , os .MkdirAll (filepath .Dir (keyPath ), 0o700 ))
263+ assert .NoError (t , os .WriteFile (keyPath , []byte (mockSshIdentity ), 0o644 ))
264+ t .Setenv (SopsAgeSshPrivateKeyFileEnv , keyPath )
265+
266+ got , err := key .Decrypt ()
267+ assert .NoError (t , err )
268+ assert .EqualValues (t , mockEncryptedKeyPlain , got )
269+ })
270+
191271 t .Run ("no identities" , func (t * testing.T ) {
192272 tmpDir := t .TempDir ()
193273 overwriteUserConfigDir (t , tmpDir )
@@ -327,6 +407,25 @@ func TestMasterKey_loadIdentities(t *testing.T) {
327407 assert .Len (t , got , 1 )
328408 })
329409
410+ t .Run (SopsAgeSshPrivateKeyFileEnv , func (t * testing.T ) {
411+ tmpDir := t .TempDir ()
412+ overwriteUserConfigDir (t , tmpDir )
413+
414+ homeDir , err := os .UserHomeDir ()
415+ assert .NoError (t , err )
416+ keyPath := filepath .Join (homeDir , ".ssh/id_25519" )
417+ assert .True (t , strings .HasPrefix (keyPath , homeDir ))
418+
419+ assert .NoError (t , os .MkdirAll (filepath .Dir (keyPath ), 0o700 ))
420+ assert .NoError (t , os .WriteFile (keyPath , []byte (mockSshIdentity ), 0o644 ))
421+ t .Setenv (SopsAgeSshPrivateKeyFileEnv , keyPath )
422+
423+ key := & MasterKey {}
424+ got , err := key .loadIdentities ()
425+ assert .NoError (t , err )
426+ assert .Len (t , got , 1 )
427+ })
428+
330429 t .Run ("no identity" , func (t * testing.T ) {
331430 tmpDir := t .TempDir ()
332431 overwriteUserConfigDir (t , tmpDir )
@@ -374,8 +473,8 @@ func TestMasterKey_loadIdentities(t *testing.T) {
374473 })
375474}
376475
377- // overwriteUserConfigDir sets the user config directory based on the
378- // os.UserConfigDir logic.
476+ // overwriteUserConfigDir sets the user config directory and the user home directory
477+ // based on the os.UserConfigDir logic.
379478func overwriteUserConfigDir (t * testing.T , path string ) {
380479 switch runtime .GOOS {
381480 case "windows" :
@@ -384,6 +483,7 @@ func overwriteUserConfigDir(t *testing.T, path string) {
384483 t .Setenv ("home" , path )
385484 default : // Unix
386485 t .Setenv ("XDG_CONFIG_HOME" , path )
486+ t .Setenv ("HOME" , path )
387487 }
388488}
389489
0 commit comments