package config import ( "encoding/base64" "encoding/json" "errors" "fmt" "os" crypto "github.com/libp2p/go-libp2p/core/crypto" peer "github.com/libp2p/go-libp2p/core/peer" "github.com/kelseyhightower/envconfig" ) const configKey = "cluster" // Identity defaults const ( DefaultConfigCrypto = crypto.Ed25519 DefaultConfigKeyLength = -1 ) // Identity represents identity of a cluster peer for communication, // including the Consensus component. type Identity struct { ID peer.ID PrivateKey crypto.PrivKey } // identityJSON represents a Cluster peer identity as it will look when it is // saved using JSON. type identityJSON struct { ID string `json:"id"` PrivateKey string `json:"private_key"` } // NewIdentity returns a new random identity. func NewIdentity() (*Identity, error) { ident := &Identity{} err := ident.Default() return ident, err } // Default generates a random keypair for this identity. func (ident *Identity) Default() error { // pid and private key generation priv, pub, err := crypto.GenerateKeyPair( DefaultConfigCrypto, DefaultConfigKeyLength, ) if err != nil { return err } pid, err := peer.IDFromPublicKey(pub) if err != nil { return err } ident.ID = pid ident.PrivateKey = priv return nil } // ConfigKey returns a human-readable string to identify // a cluster Identity. func (ident *Identity) ConfigKey() string { return configKey } // SaveJSON saves the JSON representation of the Identity to // the given path. func (ident *Identity) SaveJSON(path string) error { logger.Info("Saving identity") bs, err := ident.ToJSON() if err != nil { return err } return os.WriteFile(path, bs, 0600) } // ToJSON generates a human-friendly version of Identity. func (ident *Identity) ToJSON() (raw []byte, err error) { jID, err := ident.toIdentityJSON() if err != nil { return } raw, err = json.MarshalIndent(jID, "", " ") return } func (ident *Identity) toIdentityJSON() (jID *identityJSON, err error) { jID = &identityJSON{} // Private Key pkeyBytes, err := crypto.MarshalPrivateKey(ident.PrivateKey) if err != nil { return } pKey := base64.StdEncoding.EncodeToString(pkeyBytes) // Set all identity fields jID.ID = ident.ID.Pretty() jID.PrivateKey = pKey return } // LoadJSON receives a raw json-formatted identity and // sets the Config fields from it. Note that it should be JSON // as generated by ToJSON(). func (ident *Identity) LoadJSON(raw []byte) error { jID := &identityJSON{} err := json.Unmarshal(raw, jID) if err != nil { logger.Error("Error unmarshaling cluster config") return err } return ident.applyIdentityJSON(jID) } func (ident *Identity) applyIdentityJSON(jID *identityJSON) error { pid, err := peer.Decode(jID.ID) if err != nil { err = fmt.Errorf("error decoding cluster ID: %s", err) return err } ident.ID = pid pkb, err := base64.StdEncoding.DecodeString(jID.PrivateKey) if err != nil { err = fmt.Errorf("error decoding private_key: %s", err) return err } pKey, err := crypto.UnmarshalPrivateKey(pkb) if err != nil { err = fmt.Errorf("error parsing private_key ID: %s", err) return err } ident.PrivateKey = pKey return ident.Validate() } // Validate will check that the values of this identity // seem to be working ones. func (ident *Identity) Validate() error { if ident.ID == "" { return errors.New("identity ID not set") } if ident.PrivateKey == nil { return errors.New("no identity private_key set") } if !ident.ID.MatchesPrivateKey(ident.PrivateKey) { return errors.New("identity ID does not match the private_key") } return nil } // LoadJSONFromFile reads an Identity file from disk and parses // it and return Identity. func (ident *Identity) LoadJSONFromFile(path string) error { file, err := os.ReadFile(path) if err != nil { logger.Error("error reading the configuration file: ", err) return err } return ident.LoadJSON(file) } // ApplyEnvVars fills in any Config fields found // as environment variables. func (ident *Identity) ApplyEnvVars() error { jID, err := ident.toIdentityJSON() if err != nil { return err } err = envconfig.Process(ident.ConfigKey(), jID) if err != nil { return err } return ident.applyIdentityJSON(jID) } // Equals returns true if equal to provided identity. func (ident *Identity) Equals(i *Identity) bool { return ident.ID == i.ID && ident.PrivateKey.Equals(i.PrivateKey) }