1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"crypto/sha256"
8	"encoding/hex"
9	"fmt"
10	"strings"
11
12	keybase1 "github.com/keybase/client/go/protocol/keybase1"
13	jsonw "github.com/keybase/go-jsonw"
14)
15
16func UIDFromHex(s string) (keybase1.UID, error) {
17	u, err := keybase1.UIDFromString(s)
18	if err != nil {
19		var nilUID keybase1.UID
20		return nilUID, err
21	}
22	return u, nil
23}
24
25func GetUID(w *jsonw.Wrapper) (keybase1.UID, error) {
26	s, err := w.GetString()
27	var nilUID keybase1.UID
28	if err != nil {
29		return nilUID, err
30	}
31	return UIDFromHex(s)
32}
33
34func GetUIDVoid(w *jsonw.Wrapper, u *keybase1.UID, e *error) {
35	uid, err := GetUID(w)
36	if err == nil {
37		*u = uid
38	} else if e != nil && *e == nil {
39		*e = err
40	}
41}
42
43func UIDWrapper(uid keybase1.UID) *jsonw.Wrapper {
44	return jsonw.NewString(uid.String())
45}
46
47func UIDArg(uid keybase1.UID) HTTPValue {
48	return S{Val: uid.String()}
49}
50
51// GetUIDByNormalizedUsername returns UID for normalized username. Works
52// offline for all usernames.
53func GetUIDByNormalizedUsername(g *GlobalContext, username NormalizedUsername) keybase1.UID {
54	uid := g.UIDMapper.MapHardcodedUsernameToUID(username)
55	if uid.Exists() {
56		return uid
57	}
58	return usernameToUIDPreserveCase(username.String())
59}
60
61// GetUIDByUsername returns UID for username strings with potentially
62// mixed letter casing. Works offline for all usernames.
63func GetUIDByUsername(g *GlobalContext, username string) keybase1.UID {
64	return GetUIDByNormalizedUsername(g, NewNormalizedUsername(username))
65}
66
67func AssertUsernameMatchesUID(g *GlobalContext, uid keybase1.UID, username string) error {
68	u2 := GetUIDByUsername(g, username)
69	if uid.NotEqual(u2) {
70		return UIDMismatchError{fmt.Sprintf("%s != %s (via %s)", uid, u2, username)}
71	}
72	return nil
73}
74
75// NOTE: Use the high level API above instead of any of the following. The
76// hilvl API handles both UIDS for old, potentially incorrectly hashed
77// usernames, as well as new, correct UIDs.
78//
79// tldr: you probably want to use GetUID* functions, instead of UsernameToUID*.
80
81// UsernameToUID works for users created after "Fri Feb  6 19:33:08 EST 2015",
82// with some exceptions, since we didn't ToLower() for all UIDs
83func UsernameToUID(s string) keybase1.UID {
84	return usernameToUIDPreserveCase(strings.ToLower(s))
85}
86
87func CheckUIDAgainstUsername(uid keybase1.UID, username string) (err error) {
88	// Note: does not handle pre-Feb-2015 UIDs. You might want to use
89	// `AssertUsernameMatchesUID` instead.
90	u2 := UsernameToUID(username)
91	if uid.NotEqual(u2) {
92		err = UIDMismatchError{fmt.Sprintf("%s != %s (via %s)", uid, u2, username)}
93	}
94	return
95}
96
97// UsernameToUID works for users created after "Fri Feb  6 19:33:08 EST 2015".  Some of
98// them had buggy Username -> UID conversions, in which case we need to hash the
99// original case to recover their UID.
100func usernameToUIDPreserveCase(s string) keybase1.UID {
101	h := sha256.Sum256([]byte(s))
102	var uid [keybase1.UID_LEN]byte
103	copy(uid[:], h[0:keybase1.UID_LEN-1])
104	uid[keybase1.UID_LEN-1] = keybase1.UID_SUFFIX_2
105	ret, _ := keybase1.UIDFromString(hex.EncodeToString(uid[:]))
106	return ret
107}
108
109// checkUIDAgainstCasedUsername takes the input string, does not convert toLower,
110// and then hashes it to recover a UID. This is a workaround for some
111// users whose UIDs were computed incorrectly.
112func checkUIDAgainstCasedUsername(uid keybase1.UID, username string) (err error) {
113	u2 := usernameToUIDPreserveCase(username)
114	if uid.NotEqual(u2) {
115		err = UIDMismatchError{fmt.Sprintf("%s != %s (via %s)", uid, u2, username)}
116	}
117	return
118}
119