Skip to content

Commit 6a5e3dd

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 0df32d6 commit 6a5e3dd

1 file changed

Lines changed: 43 additions & 4 deletions

File tree

wincred/wincred.go

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ package wincred
44

55
import (
66
"bytes"
7+
"encoding/binary"
78
"net/url"
9+
"unicode/utf16"
810

911
winc "github.com/danieljoos/wincred"
1012
"golang.org/x/text/encoding/unicode"
@@ -64,10 +66,19 @@ func (h Wincred) Get(serverURL string) (string, string, error) {
6466

6567
for _, attr := range g.Attributes {
6668
if attr.Keyword == "label" && bytes.Equal(attr.Value, []byte(credentials.CredsLabel)) {
67-
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewDecoder()
68-
creds, _, err := transform.String(encoder, string(g.CredentialBlob))
69-
if err != nil {
70-
return "", "", err
69+
creds := string(g.CredentialBlob)
70+
71+
// Older versions of the wincred credential-helper stored the password blob
72+
// as raw string bytes. Newer versions store it as UTF-16LE.
73+
//
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+
//
79+
// See https://github.com/docker/docker-credential-helpers/pull/335
80+
if p, ok := tryDecodeUTF16LE(g.CredentialBlob); ok {
81+
creds = p
7182
}
7283
return g.UserName, creds, nil
7384
}
@@ -159,3 +170,31 @@ func (h Wincred) List() (map[string]string, error) {
159170

160171
return resp, nil
161172
}
173+
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)
198+
}
199+
return buf
200+
}

0 commit comments

Comments
 (0)