1// SPDX-License-Identifier: ISC
2// Copyright (c) 2014-2020 Bitmark Inc.
3// Use of this source code is governed by an ISC
4// license that can be found in the LICENSE file.
5
6package configuration
7
8import (
9	"encoding/json"
10	"os"
11	"path/filepath"
12
13	"github.com/bitmark-inc/bitmarkd/account"
14	"github.com/bitmark-inc/bitmarkd/fault"
15)
16
17// Configuration - configuration file data format
18type Configuration struct {
19	DefaultIdentity string              `json:"default_identity"`
20	TestNet         bool                `json:"testnet"`
21	Connections     []string            `json:"connections"`
22	Identities      map[string]Identity `json:"identities"`
23}
24
25// Identity - mix of plain and encrypted data
26type Identity struct {
27	Description string `json:"description"`
28	Account     string `json:"account"`
29	Data        string `json:"data"`
30	Salt        string `json:"salt"`
31}
32
33// Load - read the configuration
34func Load(filename string) (*Configuration, error) {
35
36	options := &Configuration{}
37
38	err := readConfiguration(filename, options)
39	if nil != err {
40		return nil, err
41	}
42	return options, nil
43}
44
45// generic JSON decoder
46func readConfiguration(filename string, options interface{}) error {
47
48	filename, err := filepath.Abs(filepath.Clean(filename))
49	if nil != err {
50		return err
51	}
52
53	f, err := os.Open(filename)
54	if nil != err {
55		return err
56	}
57	defer f.Close()
58
59	dec := json.NewDecoder(f)
60	err = dec.Decode(options)
61	if nil != err {
62		return err
63	}
64
65	return nil
66}
67
68// Identity - find identity for a given name
69func (config *Configuration) Identity(name string) (*Identity, error) {
70
71	// account names cannot be identities to prevent confusion
72	_, err := account.AccountFromBase58(name)
73	if nil == err {
74		return nil, fault.InvalidIdentityName
75	}
76
77	id, ok := config.Identities[name]
78	if !ok {
79		return nil, fault.IdentityNameNotFound
80	}
81
82	return &id, nil
83}
84
85// Account - find identity for a given name and convert to an account
86func (config *Configuration) Account(name string) (*account.Account, error) {
87	// check if valid account in Base58 first
88	// to prevent identifiers masquerading as accounts
89	acc, err := account.AccountFromBase58(name)
90	if nil == err {
91		return acc, nil
92	}
93
94	// otherwise lookup as an identifier
95	id, err := config.Identity(name)
96	if nil != err {
97		return nil, err
98	}
99
100	acc, err = account.AccountFromBase58(id.Account)
101
102	return acc, err
103}
104
105// Private - find identity decrypt all data for a given name
106func (config *Configuration) Private(password string, name string) (*Private, error) {
107	id, err := config.Identity(name)
108	if nil != err {
109		return nil, err
110	}
111
112	return decryptIdentity(password, id)
113}
114
115// AddIdentity - store encrypted identity
116func (config *Configuration) AddIdentity(name string, description string, seed string, password string) error {
117
118	if _, ok := config.Identities[name]; ok {
119		return fault.IdentityNameAlreadyExists
120	}
121
122	salt, secretKey, err := hashPassword(password)
123	if nil != err {
124		return err
125	}
126
127	encrypted, err := encryptData(seed, secretKey)
128	if nil != err {
129		return err
130	}
131
132	private, err := account.PrivateKeyFromBase58Seed(seed)
133	if nil != err {
134		return err
135	}
136
137	config.Identities[name] = Identity{
138		Description: description,
139		Account:     private.Account().String(),
140		Data:        encrypted,
141		Salt:        salt.String(),
142	}
143
144	return nil
145}
146
147// AddReceiveOnlyIdentity - store public-only identity
148func (config *Configuration) AddReceiveOnlyIdentity(name string, description string, acc string) error {
149
150	if _, ok := config.Identities[name]; ok {
151		return fault.IdentityNameAlreadyExists
152	}
153
154	_, err := account.AccountFromBase58(acc)
155	if nil != err {
156		return err
157	}
158
159	config.Identities[name] = Identity{
160		Description: description,
161		Account:     acc,
162		Data:        "",
163		Salt:        "",
164	}
165
166	return nil
167}
168