mirror of https://github.com/perkeep/perkeep.git
Merge "client: help (wrt to the gpg key) initialize the config"
This commit is contained in:
commit
f557a7235b
|
@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO(mpl): Shouldn't we move this subcommand to camtool? e.g as 'camtool configinit' ?
|
||||
// It does not feel like a good fit in camput since this does not send anything
|
||||
// to a server.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -28,26 +32,27 @@ import (
|
|||
"path"
|
||||
|
||||
"camlistore.org/pkg/blobref"
|
||||
"camlistore.org/pkg/client"
|
||||
"camlistore.org/pkg/cmdmain"
|
||||
"camlistore.org/pkg/jsonsign"
|
||||
"camlistore.org/pkg/osutil"
|
||||
)
|
||||
|
||||
type initCmd struct {
|
||||
newKey bool
|
||||
gpgkey string
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdmain.RegisterCommand("init", func(flags *flag.FlagSet) cmdmain.CommandRunner {
|
||||
cmd := new(initCmd)
|
||||
flags.BoolVar(&cmd.newKey, "newkey", false, "Automatically generate a new identity in a new secret ring.")
|
||||
flags.StringVar(&cmd.gpgkey, "gpgkey", "", "GPG key to use for signing (overrides $GPGKEY environment)")
|
||||
return cmd
|
||||
})
|
||||
}
|
||||
|
||||
func (c *initCmd) Describe() string {
|
||||
return "Initialize the camput configuration file."
|
||||
return "Initialize the camput configuration file. With no option, it tries to use the GPG key found in the default identity secret ring."
|
||||
}
|
||||
|
||||
func (c *initCmd) Usage() {
|
||||
|
@ -58,10 +63,14 @@ func (c *initCmd) Examples() []string {
|
|||
return []string{
|
||||
"",
|
||||
"--gpgkey=XXXXX",
|
||||
"--newkey Creates a new identity",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *initCmd) keyId() (string, error) {
|
||||
// keyId returns the current keyId. It checks, in this order,
|
||||
// the --gpgkey flag, the GPGKEY env var, and the default
|
||||
// identity secret ring.
|
||||
func (c *initCmd) keyId(secRing string) (string, error) {
|
||||
if k := c.gpgkey; k != "" {
|
||||
return k, nil
|
||||
}
|
||||
|
@ -69,13 +78,19 @@ func (c *initCmd) keyId() (string, error) {
|
|||
return k, nil
|
||||
}
|
||||
|
||||
// TODO: move camlistored.go's keyIdFromRing into
|
||||
// pkg/jsonsign/keys.go and use that (which looks for an
|
||||
// identify file with exactly one identity)
|
||||
k, err := jsonsign.KeyIdFromRing(secRing)
|
||||
if err != nil {
|
||||
log.Printf("No suitable gpg key was found in %v: %v", secRing, err)
|
||||
} else {
|
||||
if k != "" {
|
||||
log.Printf("Re-using identity with keyId %q found in file %s", k, secRing)
|
||||
return k, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: run and parse gpg --list-secret-keys and see if there's just one and suggest that? Or show
|
||||
// a list of them?
|
||||
return "", errors.New("Initialization requires your public GPG key. Set --gpgkey=<pubid> or set $GPGKEY in your environment. Run gpg --list-secret-keys to find their key IDs.")
|
||||
return "", errors.New("Initialization requires your public GPG key.\nYou can set --gpgkey=<pubid> or set $GPGKEY in your environment. Run gpg --list-secret-keys to find their key IDs.\nOr you can create a new secret ring and key with 'camput init --newkey'.")
|
||||
}
|
||||
|
||||
func (c *initCmd) getPublicKeyArmoredFromFile(secretRingFileName, keyId string) (b []byte, err error) {
|
||||
|
@ -112,13 +127,27 @@ func (c *initCmd) RunCommand(args []string) error {
|
|||
return cmdmain.ErrUsage
|
||||
}
|
||||
|
||||
if c.newKey && c.gpgkey != "" {
|
||||
log.Fatal("--newkey and --gpgkey are mutually exclusive")
|
||||
}
|
||||
|
||||
blobDir := path.Join(osutil.CamliConfigDir(), "keyblobs")
|
||||
os.Mkdir(osutil.CamliConfigDir(), 0700)
|
||||
os.Mkdir(blobDir, 0700)
|
||||
|
||||
keyId, err := c.keyId()
|
||||
if err != nil {
|
||||
return err
|
||||
var keyId string
|
||||
var err error
|
||||
secRing := osutil.IdentitySecretRing()
|
||||
if c.newKey {
|
||||
keyId, err = jsonsign.GenerateNewSecRing(secRing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
keyId, err = c.keyId(secRing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if os.Getenv("GPG_AGENT_INFO") == "" {
|
||||
|
@ -143,12 +172,13 @@ func (c *initCmd) RunCommand(args []string) error {
|
|||
|
||||
log.Printf("Your Camlistore identity (your GPG public key's blobref) is: %s", bref.String())
|
||||
|
||||
_, err = os.Stat(client.ConfigFilePath())
|
||||
configFilePath := osutil.UserClientConfigPath()
|
||||
_, err = os.Stat(configFilePath)
|
||||
if err == nil {
|
||||
log.Fatalf("Config file %q already exists; quitting without touching it.", client.ConfigFilePath())
|
||||
log.Fatalf("Config file %q already exists; quitting without touching it.", configFilePath)
|
||||
}
|
||||
|
||||
if f, err := os.OpenFile(client.ConfigFilePath(), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600); err == nil {
|
||||
if f, err := os.OpenFile(configFilePath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600); err == nil {
|
||||
defer f.Close()
|
||||
m := make(map[string]interface{})
|
||||
m["keyId"] = keyId // TODO(bradfitz): make this 'identity' to match server config?
|
||||
|
@ -163,9 +193,9 @@ func (c *initCmd) RunCommand(args []string) error {
|
|||
}
|
||||
_, err = f.Write(jsonBytes)
|
||||
if err != nil {
|
||||
log.Fatalf("Error writing to %q: %v", client.ConfigFilePath(), err)
|
||||
log.Fatalf("Error writing to %q: %v", configFilePath, err)
|
||||
}
|
||||
log.Printf("Wrote %q; modify as necessary.", client.ConfigFilePath())
|
||||
log.Printf("Wrote %q; modify as necessary.", configFilePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package client
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
@ -42,7 +43,7 @@ var (
|
|||
)
|
||||
|
||||
func AddFlags() {
|
||||
defaultPath := ConfigFilePath()
|
||||
defaultPath := osutil.UserClientConfigPath()
|
||||
flag.StringVar(&flagServer, "server", "", "Camlistore server prefix. If blank, the default from the \"server\" field of "+defaultPath+" is used. Acceptable forms: https://you.example.com, example.com:1345 (https assumed), or http://you.example.com/alt-root")
|
||||
flag.StringVar(&flagSecretRing, "secret-keyring", "", "GnuPG secret keyring file to use.")
|
||||
}
|
||||
|
@ -52,19 +53,40 @@ func ExplicitServer() string {
|
|||
return flagServer
|
||||
}
|
||||
|
||||
func ConfigFilePath() string {
|
||||
return filepath.Join(osutil.CamliConfigDir(), "config")
|
||||
}
|
||||
|
||||
var configOnce sync.Once
|
||||
var config = make(map[string]interface{})
|
||||
var parseConfigErr error
|
||||
|
||||
// serverGPGKey returns the public gpg key id ("identity" field)
|
||||
// from the user's server config , if any.
|
||||
// It returns the empty string otherwise.
|
||||
func serverKeyId() string {
|
||||
serverConfigFile := osutil.UserServerConfigPath()
|
||||
if _, err := os.Stat(serverConfigFile); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return ""
|
||||
}
|
||||
log.Fatalf("Could not stat %v: %v", serverConfigFile, err)
|
||||
}
|
||||
obj, err := jsonconfig.ReadFile(serverConfigFile)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
keyId, ok := obj["identity"].(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return keyId
|
||||
}
|
||||
|
||||
func parseConfig() {
|
||||
configPath := ConfigFilePath()
|
||||
configPath := osutil.UserClientConfigPath()
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
parseConfigErr = os.ErrNotExist
|
||||
return
|
||||
errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'camput init' to generate it.", configPath)
|
||||
if keyId := serverKeyId(); keyId != "" {
|
||||
hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'camput init -gpgkey %v'", keyId, osutil.UserServerConfigPath(), keyId)
|
||||
errMsg += hint
|
||||
}
|
||||
log.Fatal(errMsg)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -98,7 +120,7 @@ func serverOrDie() string {
|
|||
}
|
||||
server = cleanServer(server)
|
||||
if !ok || server == "" {
|
||||
log.Fatalf("Missing or invalid \"server\" in %q", ConfigFilePath())
|
||||
log.Fatalf("Missing or invalid \"server\" in %q", osutil.UserClientConfigPath())
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
@ -181,7 +203,7 @@ func getSignerPublicKeyBlobref() *blobref.BlobRef {
|
|||
key := "keyId"
|
||||
keyId, ok := config[key].(string)
|
||||
if !ok {
|
||||
log.Printf("No key %q in JSON configuration file %q; have you run \"camput init\"?", key, ConfigFilePath())
|
||||
log.Printf("No key %q in JSON configuration file %q; have you run \"camput init\"?", key, osutil.UserClientConfigPath())
|
||||
return nil
|
||||
}
|
||||
keyRing, hasKeyRing := config["secretRing"].(string)
|
||||
|
@ -208,7 +230,7 @@ func getSignerPublicKeyBlobref() *blobref.BlobRef {
|
|||
|
||||
selfPubKeyDir, ok := config["selfPubKeyDir"].(string)
|
||||
if !ok {
|
||||
log.Printf("No 'selfPubKeyDir' defined in %q", ConfigFilePath())
|
||||
log.Printf("No 'selfPubKeyDir' defined in %q", osutil.UserClientConfigPath())
|
||||
return nil
|
||||
}
|
||||
fi, err := os.Stat(selfPubKeyDir)
|
||||
|
|
|
@ -152,3 +152,46 @@ func WriteKeyRing(w io.Writer, el openpgp.EntityList) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyIdFromRing returns the public keyId contained in the secret
|
||||
// ring file secRing. It expects only one keyId in this secret ring
|
||||
// and returns an error otherwise.
|
||||
func KeyIdFromRing(secRing string) (keyId string, err error) {
|
||||
f, err := os.Open(secRing)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Could not open secret ring file %v: %v", secRing, err)
|
||||
}
|
||||
defer f.Close()
|
||||
el, err := openpgp.ReadKeyRing(f)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Could not read secret ring file %s: %v", secRing, err)
|
||||
}
|
||||
if len(el) != 1 {
|
||||
return "", fmt.Errorf("Secret ring file %v contained %d identities; expected 1", secRing, len(el))
|
||||
}
|
||||
ent := el[0]
|
||||
return ent.PrimaryKey.KeyIdShortString(), nil
|
||||
}
|
||||
|
||||
// GenerateNewSecRing creates a new secret ring file secRing, with
|
||||
// a new GPG identity. It returns the public keyId of that identity.
|
||||
// It returns an error if the file already exists.
|
||||
func GenerateNewSecRing(secRing string) (keyId string, err error) {
|
||||
ent, err := NewEntity()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("generating new identity: %v", err)
|
||||
}
|
||||
f, err := os.OpenFile(secRing, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = WriteKeyRing(f, openpgp.EntityList([]*openpgp.Entity{ent}))
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return "", fmt.Errorf("Could not write new key ring to %s: %v", secRing, err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return "", fmt.Errorf("Could not close %v: %v", secRing, err)
|
||||
}
|
||||
return ent.PrimaryKey.KeyIdShortString(), nil
|
||||
}
|
||||
|
|
|
@ -83,11 +83,15 @@ func UserServerConfigPath() string {
|
|||
return filepath.Join(CamliConfigDir(), "server-config.json")
|
||||
}
|
||||
|
||||
func UserClientConfigPath() string {
|
||||
return filepath.Join(CamliConfigDir(), "config")
|
||||
}
|
||||
|
||||
func IdentitySecretRing() string {
|
||||
return filepath.Join(CamliConfigDir(), "identity-secring.gpg")
|
||||
}
|
||||
|
||||
// Find the correct absolute path corresponding to a relative path,
|
||||
// Find the correct absolute path corresponding to a relative path,
|
||||
// searching the following sequence of directories:
|
||||
// 1. Working Directory
|
||||
// 2. CAMLI_CONFIG_DIR (deprecated, will complain if this is on env)
|
||||
|
|
|
@ -63,8 +63,6 @@ import (
|
|||
// Handlers:
|
||||
_ "camlistore.org/pkg/search"
|
||||
_ "camlistore.org/pkg/server" // UI, publish, etc
|
||||
|
||||
"camlistore.org/third_party/code.google.com/p/go.crypto/openpgp"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -198,40 +196,6 @@ func findConfigFile(file string) (absPath string, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func keyIdFromRing(filename string) (keyId string, err error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading identity secret ring file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
el, err := openpgp.ReadKeyRing(f)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading identity secret ring file %s: %v", filename, err)
|
||||
}
|
||||
if len(el) != 1 {
|
||||
return "", fmt.Errorf("identity secret ring file contained %d identities; expected 1", len(el))
|
||||
}
|
||||
ent := el[0]
|
||||
return ent.PrimaryKey.KeyIdShortString(), nil
|
||||
}
|
||||
|
||||
func generateNewSecRing(filename string) (keyId string, err error) {
|
||||
ent, err := jsonsign.NewEntity()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("generating new identity: %v", err)
|
||||
}
|
||||
f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
err = jsonsign.WriteKeyRing(f, openpgp.EntityList([]*openpgp.Entity{ent}))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("writing new key ring to %s: %v", filename, err)
|
||||
}
|
||||
return ent.PrimaryKey.KeyIdShortString(), nil
|
||||
}
|
||||
|
||||
type defaultConfigFile struct {
|
||||
Listen string `json:"listen"`
|
||||
HTTPS bool `json:"https"`
|
||||
|
@ -266,10 +230,10 @@ func newDefaultConfigFile(path string) error {
|
|||
_, err := os.Stat(secRing)
|
||||
switch {
|
||||
case err == nil:
|
||||
keyId, err = keyIdFromRing(secRing)
|
||||
keyId, err = jsonsign.KeyIdFromRing(secRing)
|
||||
log.Printf("Re-using identity with keyId %q found in file %s", keyId, secRing)
|
||||
case os.IsNotExist(err):
|
||||
keyId, err = generateNewSecRing(secRing)
|
||||
keyId, err = jsonsign.GenerateNewSecRing(secRing)
|
||||
log.Printf("Generated new identity with keyId %q in file %s", keyId, secRing)
|
||||
}
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue