Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core/commands/config: do not show private key on local network #2957

Merged
merged 9 commits into from
Jul 16, 2016
76 changes: 60 additions & 16 deletions core/commands/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io/ioutil"
"os"
"os/exec"
"strings"

cmds "github.com/ipfs/go-ipfs/commands"
repo "github.com/ipfs/go-ipfs/repo"
Expand Down Expand Up @@ -58,6 +59,14 @@ Set the value of the 'datastore.path' key:
args := req.Arguments()
key := args[0]

// This is a temporary fix until we move the private key out of the config file
switch strings.ToLower(key) {
case "identity", "identity.privkey":
res.SetError(fmt.Errorf("cannot show or change private key through API"), cmds.ErrNormal)
return
default:
}

r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
if err != nil {
res.SetError(err, cmds.ErrNormal)
Expand Down Expand Up @@ -134,18 +143,40 @@ included in the output of this command.
},

Run: func(req cmds.Request, res cmds.Response) {
filename, err := config.Filename(req.InvocContext().ConfigRoot)
fname, err := config.Filename(req.InvocContext().ConfigRoot)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

output, err := showConfig(filename)
data, err := ioutil.ReadFile(fname)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(output)

var cfg map[string]interface{}
err = json.Unmarshal(data, &cfg)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

idmap, ok := cfg["Identity"].(map[string]interface{})
if !ok {
res.SetError(fmt.Errorf("config has no identity"), cmds.ErrNormal)
return
}

delete(idmap, "PrivKey")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • this wont work if the map has privkey. this should be case insensitive.
  • i know this comes from our own structs, so it should be capitalized.
  • but all someone has to do is change the capitalization to Privkey there.
  • come to think of it, this should be either:
    • a constant there, in that file where the struct is
    • or derived from the struct using reflection, to be very damn sure that doesn't get changed around these checks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can also loop the map, compare keys to privkey case insensitive and remove that. SGTY?


output, err := config.HumanOutput(cfg)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

res.SetOutput(bytes.NewReader(output))
},
}

Expand Down Expand Up @@ -219,22 +250,20 @@ func getConfig(r repo.Repo, key string) (*ConfigField, error) {
}

func setConfig(r repo.Repo, key string, value interface{}) (*ConfigField, error) {
err := r.SetConfigKey(key, value)
keyF, err := getConfig(r, "Identity.PrivKey")
if err != nil {
return nil, fmt.Errorf("Failed to set config value: %s (maybe use --json?)", err)
return nil, errors.New("failed to get PrivKey")
}
return getConfig(r, key)
}

func showConfig(filename string) (io.Reader, error) {
// TODO maybe we should omit privkey so we don't accidentally leak it?

data, err := ioutil.ReadFile(filename)
privkey := keyF.Value
err = r.SetConfigKey(key, value)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to set config value: %s (maybe use --json?)", err)
}

return bytes.NewReader(data), nil
err = r.SetConfigKey("Identity.PrivKey", privkey)
Copy link
Member

@jbenet jbenet Aug 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • this code is trying to "set the privatekey again" with the original value, to be sure the user did not change it.
  • this is an incorrect way to do this
  • and is subject to data races.
  • and is subject to "triggered crash" problem (if i can cause a crash after the first write and before the second, i can set it to disk successfully. given i can trigger a crash, i can achieve this by doing it over and over again until i get it right.)
  • the right way to do this is to sanitize the input and not let it set Identity.PrivKey at all.
  • I know it is tricky because the value may be Identity.PrivKey, or the whole Identity block, or the whole config.
  • But the "setting it again" thing should not be relied upon for security.
  • I would probably "set the private key" in the incoming value.
  • Another option is removing Identity entirely (not just the private key), after all, the Identity is tied to the private key and shouldn't be mutable either...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should probably also error similarly to replaceConfig if the incoming value has a protected field.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use this opportunity to remove the private key from the config file altogether. Identity.PeerID can still remain as a readonly attribute for compat. The preferred way of getting one's own PeerID is :5001/api/v0/id and we should state that somewhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. maybe we can do another migration (4-5) that moves the key to $repo/keys/<hash-of-key> and symlinks id -> <hash-of-key>. That would stop this madness.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will address this issue here in case any other callers to setConfig come up, but we do guard against this entire scenario occuring by filtering the input keys in the config command itself.

if err != nil {
return nil, errors.New("failed to set PrivKey")
}
return getConfig(r, key)
}

func editConfig(filename string) error {
Expand All @@ -251,8 +280,23 @@ func editConfig(filename string) error {
func replaceConfig(r repo.Repo, file io.Reader) error {
var cfg config.Config
if err := json.NewDecoder(file).Decode(&cfg); err != nil {
return errors.New("Failed to decode file as config")
return errors.New("failed to decode file as config")
}
if len(cfg.Identity.PrivKey) != 0 {
return errors.New("setting private key with API is not supported")
}

keyF, err := getConfig(r, "Identity.PrivKey")
if err != nil {
return fmt.Errorf("Failed to get PrivKey")
}

pkstr, ok := keyF.Value.(string)
if !ok {
return fmt.Errorf("private key in config was not a string")
}

cfg.Identity.PrivKey = pkstr

return r.SetConfig(&cfg)
}
2 changes: 1 addition & 1 deletion repo/config/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
// Identity tracks the configuration of the local node's identity.
type Identity struct {
PeerID string
PrivKey string
PrivKey string `json:",omitempty"`
}

// DecodePrivateKey is a helper to decode the users PrivateKey
Expand Down
53 changes: 53 additions & 0 deletions test/sharness/t0021-config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,59 @@ test_config_cmd() {
grep "\"beep2\": false," actual &&
grep "\"beep3\": false," actual
'

test_expect_success "'ipfs config Identity' fails" '
test_expect_code 1 ipfs config Identity 2> ident_out
'

test_expect_success "output looks good" '
echo "Error: cannot show or change private key through API" > ident_exp &&
test_cmp ident_exp ident_out
'

# SECURITY
# Those tests are here to prevent exposing the PrivKey on the network
test_expect_success "'ipfs config Identity.PrivKey' fails" '
test_expect_code 1 ipfs config Identity.PrivKey 2> ident_out
'

test_expect_success "output looks good" '
test_cmp ident_exp ident_out
'

test_expect_success "lower cased PrivKey" '
sed -i -e '\''s/PrivKey/privkey/'\'' "$IPFS_PATH/config" &&
test_expect_code 1 ipfs config Identity.privkey 2> ident_out
'

test_expect_success "output looks good" '
test_cmp ident_exp ident_out
'

test_expect_success "fix it back" '
sed -i -e '\''s/privkey/PrivKey/'\'' "$IPFS_PATH/config"
'

test_expect_success "'ipfs config show' doesn't include privkey" '
ipfs config show > show_config &&
test_expect_code 1 grep PrivKey show_config
'

test_expect_success "'ipfs config replace' injects privkey back" '
Copy link
Member

@jbenet jbenet Jul 13, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a test where the keys are lowercased?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kubuxu where is it added? not seeing it

Copy link
Member Author

@Kubuxu Kubuxu Aug 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I added test case for lower case key in the config file, I will add test case for ipfs config replace with lower case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding in: #3141

ipfs config replace show_config &&
grep "\"PrivKey\":" "$IPFS_PATH/config" | grep -e ": \".\+\"" >/dev/null
'

test_expect_success "'ipfs config replace' with privkey erors out" '
cp "$IPFS_PATH/config" real_config &&
test_expect_code 1 ipfs config replace - < real_config 2> replace_out
'

test_expect_success "output looks good" '
echo "Error: setting private key with API is not supported" > replace_expected
test_cmp replace_out replace_expected
'

}

test_init_ipfs
Expand Down