Skip to content

Commit 2b9c650

Browse files
committed
wincred: add fallback for existing values
As older versions of the credential helper would store creds as raw bytes, we must expect the value to be a raw byte, not UTF-16LE encoded. Try decoding the value and check if it round-trips correctly to make sure we're actually dealing with UTF-16LE encoded creds. We should also consider setting a custom "encoding" attribute instead to detect what encoding was used to store the value. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent 8847163 commit 2b9c650

1 file changed

Lines changed: 44 additions & 5 deletions

File tree

wincred/wincred.go

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ package wincred
44

55
import (
66
"bytes"
7+
"encoding/binary"
78
"net/url"
89
"strings"
10+
"unicode/utf16"
911

1012
winc "github.com/danieljoos/wincred"
1113
"golang.org/x/text/encoding/unicode"
@@ -67,12 +69,21 @@ func (h Wincred) Get(serverURL string) (string, string, error) {
6769
if strings.Compare(attr.Keyword, "label") == 0 &&
6870
bytes.Compare(attr.Value, []byte(credentials.CredsLabel)) == 0 {
6971

70-
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewDecoder()
71-
passwd, _, err := transform.String(encoder, string(g.CredentialBlob))
72-
if err != nil {
73-
return "", "", err
72+
creds := string(g.CredentialBlob)
73+
74+
// Older versions of the wincred credential-helper stored the password blob
75+
// as raw string bytes. Newer versions store it as UTF-16LE.
76+
//
77+
// The credential blob does not include encoding metadata, so we cannot
78+
// reliably distinguish legacy entries from UTF-16LE entries in all cases.
79+
// For backward compatibility, keep the legacy raw-byte form by default and
80+
// only use the UTF-16LE-decoded form when it round-trips cleanly.
81+
//
82+
// See https://github.com/docker/docker-credential-helpers/pull/335
83+
if p, ok := tryDecodeUTF16LE(g.CredentialBlob); ok {
84+
creds = p
7485
}
75-
return g.UserName, passwd, nil
86+
return g.UserName, creds, nil
7687
}
7788
}
7889
return "", "", credentials.NewErrCredentialsNotFound()
@@ -165,3 +176,31 @@ func (h Wincred) List() (map[string]string, error) {
165176

166177
return resp, nil
167178
}
179+
180+
func tryDecodeUTF16LE(blob []byte) (string, bool) {
181+
if len(blob)%2 != 0 {
182+
return "", false
183+
}
184+
185+
decoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewDecoder()
186+
s, _, err := transform.String(decoder, string(blob))
187+
if err != nil {
188+
return "", false
189+
}
190+
191+
// roundtrip the value to check if it was indeed valid UTF-16LE.
192+
if string(encodeUTF16LE(s)) != string(blob) {
193+
return "", false
194+
}
195+
196+
return s, true
197+
}
198+
199+
func encodeUTF16LE(s string) []byte {
200+
u16 := utf16.Encode([]rune(s))
201+
buf := make([]byte, len(u16)*2)
202+
for i, r := range u16 {
203+
binary.LittleEndian.PutUint16(buf[i*2:], r)
204+
}
205+
return buf
206+
}

0 commit comments

Comments
 (0)