@@ -206,16 +206,41 @@ func (key *MasterKey) SetEncryptedDataKey(enc []byte) {
206206 key .EncryptedKey = string (enc )
207207}
208208
209+ func formatError (msg string , err error , errs errSet , unusedLocations []string ) error {
210+ var loadSuffix string
211+ if len (errs ) > 0 {
212+ loadSuffix = fmt .Sprintf (". Errors while loading age identities: %s" , errs .Error ())
213+ }
214+ var unusedSuffix string
215+ if len (unusedLocations ) > 0 {
216+ count := len (unusedLocations )
217+ if count == 1 {
218+ unusedSuffix = fmt .Sprintf (" '%s'" , unusedLocations [0 ])
219+ } else if count == 2 {
220+ unusedSuffix = fmt .Sprintf ("s '%s' and '%s'" , unusedLocations [0 ], unusedLocations [1 ])
221+ } else {
222+ unusedSuffix = fmt .Sprintf ("s '%s', and '%s'" , strings .Join (unusedLocations [:count - 1 ], "', '" ), unusedLocations [count - 1 ])
223+ }
224+ unusedSuffix = fmt .Sprintf (". Did not find keys in location%s." , unusedSuffix )
225+ }
226+ if err != nil {
227+ return fmt .Errorf ("%s: %w%s%s" , msg , err , loadSuffix , unusedSuffix )
228+ } else {
229+ return fmt .Errorf ("%s%s%s" , msg , loadSuffix , unusedSuffix )
230+ }
231+ }
232+
209233// Decrypt decrypts the EncryptedKey with the parsed or loaded identities, and
210234// returns the result.
211235func (key * MasterKey ) Decrypt () ([]byte , error ) {
212236 var errs errSet
237+ var unusedLocations []string
213238 if len (key .parsedIdentities ) == 0 {
214239 var ids ParsedIdentities
215- ids , errs = key .loadIdentities ()
240+ ids , unusedLocations , errs = key .loadIdentities ()
216241 if len (ids ) == 0 {
217242 log .Info ("Decryption failed" )
218- return nil , fmt . Errorf ("failed to load age identities: %w " , errs )
243+ return nil , formatError ("failed to load age identities" , nil , errs , unusedLocations )
219244 }
220245 ids .ApplyToMasterKey (key )
221246 }
@@ -225,11 +250,7 @@ func (key *MasterKey) Decrypt() ([]byte, error) {
225250 r , err := age .Decrypt (ar , key .parsedIdentities ... )
226251 if err != nil {
227252 log .Info ("Decryption failed" )
228- var loadErrors string
229- if len (errs ) > 0 {
230- loadErrors = fmt .Sprintf (". Errors while loading age identities: %s" , errs .Error ())
231- }
232- return nil , fmt .Errorf ("failed to create reader for decrypting sops data key with age: %w%s" , err , loadErrors )
253+ return nil , formatError ("failed to create reader for decrypting sops data key with age" , err , errs , unusedLocations )
233254 }
234255
235256 var b bytes.Buffer
@@ -269,29 +290,55 @@ func (key *MasterKey) TypeToIdentifier() string {
269290// private key from the SopsAgeSshPrivateKeyFileEnv environment variable. If the
270291// environment variable is not present, it will fall back to `~/.ssh/id_ed25519`
271292// or `~/.ssh/id_rsa`. If no age SSH identity is found, it will return nil.
272- func loadAgeSSHIdentity () (age.Identity , error ) {
293+ func loadAgeSSHIdentities () ([]age.Identity , []string , errSet ) {
294+ var identities []age.Identity
295+ var unusedLocations []string
296+ var errs errSet
297+
273298 sshKeyFilePath , ok := os .LookupEnv (SopsAgeSshPrivateKeyFileEnv )
274299 if ok {
275- return parseSSHIdentityFromPrivateKeyFile (sshKeyFilePath )
300+ identity , err := parseSSHIdentityFromPrivateKeyFile (sshKeyFilePath )
301+ if err != nil {
302+ errs = append (errs , err )
303+ } else {
304+ identities = append (identities , identity )
305+ }
306+ } else {
307+ unusedLocations = append (unusedLocations , SopsAgeSshPrivateKeyFileEnv )
276308 }
277309
278310 userHomeDir , err := os .UserHomeDir ()
279- if err != nil || userHomeDir == "" {
311+ if err != nil {
312+ errs = append (errs , err )
313+ } else if userHomeDir == "" {
280314 log .Warnf ("could not determine the user home directory: %v" , err )
281- return nil , nil
282- }
283-
284- sshEd25519PrivateKeyPath := filepath .Join (userHomeDir , ".ssh" , "id_ed25519" )
285- if _ , err := os .Stat (sshEd25519PrivateKeyPath ); err == nil {
286- return parseSSHIdentityFromPrivateKeyFile (sshEd25519PrivateKeyPath )
287- }
315+ } else {
316+ sshEd25519PrivateKeyPath := filepath .Join (userHomeDir , ".ssh" , "id_ed25519" )
317+ if _ , err := os .Stat (sshEd25519PrivateKeyPath ); err == nil {
318+ identity , err := parseSSHIdentityFromPrivateKeyFile (sshEd25519PrivateKeyPath )
319+ if err != nil {
320+ errs = append (errs , err )
321+ } else {
322+ identities = append (identities , identity )
323+ }
324+ } else {
325+ unusedLocations = append (unusedLocations , sshEd25519PrivateKeyPath )
326+ }
288327
289- sshRsaPrivateKeyPath := filepath .Join (userHomeDir , ".ssh" , "id_rsa" )
290- if _ , err := os .Stat (sshRsaPrivateKeyPath ); err == nil {
291- return parseSSHIdentityFromPrivateKeyFile (sshRsaPrivateKeyPath )
328+ sshRsaPrivateKeyPath := filepath .Join (userHomeDir , ".ssh" , "id_rsa" )
329+ if _ , err := os .Stat (sshRsaPrivateKeyPath ); err == nil {
330+ identity , err := parseSSHIdentityFromPrivateKeyFile (sshRsaPrivateKeyPath )
331+ if err != nil {
332+ errs = append (errs , err )
333+ } else {
334+ identities = append (identities , identity )
335+ }
336+ } else {
337+ unusedLocations = append (unusedLocations , sshRsaPrivateKeyPath )
338+ }
292339 }
293340
294- return nil , nil
341+ return identities , unusedLocations , errs
295342}
296343
297344func getUserConfigDir () (string , error ) {
@@ -307,22 +354,15 @@ func getUserConfigDir() (string, error) {
307354// environment configurations (e.g. SopsAgeKeyEnv, SopsAgeKeyFileEnv,
308355// SopsAgeSshPrivateKeyFileEnv, SopsAgeKeyUserConfigPath). It will load all
309356// found references, and expects at least one configuration to be present.
310- func (key * MasterKey ) loadIdentities () (ParsedIdentities , errSet ) {
311- var identities ParsedIdentities
312-
313- var errs errSet
314-
315- sshIdentity , err := loadAgeSSHIdentity ()
316- if err != nil {
317- errs = append (errs , fmt .Errorf ("failed to get SSH identity: %w" , err ))
318- } else if sshIdentity != nil {
319- identities = append (identities , sshIdentity )
320- }
357+ func (key * MasterKey ) loadIdentities () (ParsedIdentities , []string , errSet ) {
358+ identities , unusedLocations , errs := loadAgeSSHIdentities ()
321359
322360 var readers = make (map [string ]io.Reader , 0 )
323361
324362 if ageKey , ok := os .LookupEnv (SopsAgeKeyEnv ); ok {
325363 readers [SopsAgeKeyEnv ] = strings .NewReader (ageKey )
364+ } else {
365+ unusedLocations = append (unusedLocations , SopsAgeKeyEnv )
326366 }
327367
328368 if ageKeyFile , ok := os .LookupEnv (SopsAgeKeyFileEnv ); ok {
@@ -333,6 +373,8 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, errSet) {
333373 defer f .Close ()
334374 readers [SopsAgeKeyFileEnv ] = f
335375 }
376+ } else {
377+ unusedLocations = append (unusedLocations , SopsAgeKeyFileEnv )
336378 }
337379
338380 if ageKeyCmd , ok := os .LookupEnv (SopsAgeKeyCmdEnv ); ok {
@@ -347,6 +389,8 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, errSet) {
347389 readers [SopsAgeKeyCmdEnv ] = bytes .NewReader (out )
348390 }
349391 }
392+ } else {
393+ unusedLocations = append (unusedLocations , SopsAgeKeyCmdEnv )
350394 }
351395
352396 userConfigDir , err := getUserConfigDir ()
@@ -358,23 +402,25 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, errSet) {
358402 if err != nil && ! errors .Is (err , os .ErrNotExist ) {
359403 errs = append (errs , fmt .Errorf ("failed to open file: %w" , err ))
360404 } else if errors .Is (err , os .ErrNotExist ) && len (readers ) == 0 && len (identities ) == 0 {
361- // If we have no other readers, presence of the file is required.
362- errs = append (errs , fmt .Errorf ("failed to open file: %w" , err ))
405+ unusedLocations = append (unusedLocations , ageKeyFilePath )
363406 } else if err == nil {
364407 defer f .Close ()
365408 readers [ageKeyFilePath ] = f
366409 }
367410 }
368411
369- for n , r := range readers {
370- ids , err := unwrapIdentities (n , r )
412+ for location , r := range readers {
413+ ids , err := unwrapIdentities (location , r )
371414 if err != nil {
372415 errs = append (errs , err )
373416 } else {
374417 identities = append (identities , ids ... )
418+ if len (ids ) == 0 {
419+ unusedLocations = append (unusedLocations , location )
420+ }
375421 }
376422 }
377- return identities , errs
423+ return identities , unusedLocations , errs
378424}
379425
380426// parseRecipient attempts to parse a string containing an encoded age public
0 commit comments