@@ -4,9 +4,9 @@ package wincred
44
55import (
66 "bytes"
7- "encoding/binary"
7+ "errors"
8+ "fmt"
89 "net/url"
9- "unicode/utf16"
1010
1111 winc "github.com/danieljoos/wincred"
1212 "golang.org/x/text/encoding/unicode"
@@ -25,7 +25,10 @@ func (h Wincred) Add(creds *credentials.Credentials) error {
2525 g := winc .NewGenericCredential (creds .ServerURL )
2626 g .UserName = creds .Username
2727 g .Persist = winc .PersistLocalMachine
28- g .Attributes = []winc.CredentialAttribute {{Keyword : "label" , Value : credsLabels }}
28+ g .Attributes = []winc.CredentialAttribute {
29+ {Keyword : "label" , Value : credsLabels },
30+ {Keyword : "encoding" , Value : []byte ("utf16le" )},
31+ }
2932
3033 encoder := unicode .UTF16 (unicode .LittleEndian , unicode .IgnoreBOM ).NewEncoder ()
3134 blob , _ , err := transform .Bytes (encoder , []byte (creds .Secret ))
@@ -66,21 +69,23 @@ func (h Wincred) Get(serverURL string) (string, string, error) {
6669
6770 for _ , attr := range g .Attributes {
6871 if attr .Keyword == "label" && bytes .Equal (attr .Value , []byte (credentials .CredsLabel )) {
69- creds := string (g .CredentialBlob )
70-
7172 // Older versions of the wincred credential-helper stored the password blob
7273 // as raw string bytes. Newer versions store it as UTF-16LE.
7374 //
74- // The credential blob does not include encoding metadata, so we cannot
75- // reliably distinguish legacy entries from UTF-16LE entries in all cases.
76- // For backward compatibility, keep the legacy raw-byte form by default and
77- // only use the UTF-16LE-decoded form when it round-trips cleanly.
78- //
7975 // See https://github.com/docker/docker-credential-helpers/pull/335
80- if p , ok := tryDecodeUTF16LE (g .CredentialBlob ); ok {
81- creds = p
76+ switch enc := credentialEncoding (g .Attributes ); enc {
77+ case "utf16le" :
78+ decoder := unicode .UTF16 (unicode .LittleEndian , unicode .IgnoreBOM ).NewDecoder ()
79+ creds , _ , err := transform .Bytes (decoder , g .CredentialBlob )
80+ if err != nil {
81+ return "" , "" , fmt .Errorf ("decoding credentials: %w" , err )
82+ }
83+ return g .UserName , string (creds ), nil
84+ case "" :
85+ return g .UserName , string (g .CredentialBlob ), nil
86+ default :
87+ return "" , "" , errors .New ("unsupported credential encoding: " + enc )
8288 }
83- return g .UserName , creds , nil
8489 }
8590 }
8691 return "" , "" , credentials .NewErrCredentialsNotFound ()
@@ -171,30 +176,11 @@ func (h Wincred) List() (map[string]string, error) {
171176 return resp , nil
172177}
173178
174- func tryDecodeUTF16LE (blob []byte ) (string , bool ) {
175- if len (blob )% 2 != 0 {
176- return "" , false
177- }
178-
179- decoder := unicode .UTF16 (unicode .LittleEndian , unicode .IgnoreBOM ).NewDecoder ()
180- s , _ , err := transform .String (decoder , string (blob ))
181- if err != nil {
182- return "" , false
183- }
184-
185- // roundtrip the value to check if it was indeed valid UTF-16LE.
186- if string (encodeUTF16LE (s )) != string (blob ) {
187- return "" , false
188- }
189-
190- return s , true
191- }
192-
193- func encodeUTF16LE (s string ) []byte {
194- u16 := utf16 .Encode ([]rune (s ))
195- buf := make ([]byte , len (u16 )* 2 )
196- for i , r := range u16 {
197- binary .LittleEndian .PutUint16 (buf [i * 2 :], r )
179+ func credentialEncoding (attrs []winc.CredentialAttribute ) string {
180+ for _ , attr := range attrs {
181+ if attr .Keyword == "encoding" {
182+ return string (attr .Value )
183+ }
198184 }
199- return buf
185+ return ""
200186}
0 commit comments