forked from upspin/upspin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
keygen.go
182 lines (164 loc) · 5.57 KB
/
keygen.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// Copyright 2017 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package keygen provides functions for generating Upspin key pairs and
// writing them to files.
package keygen // import "upspin.io/key/keygen"
import (
"encoding/binary"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"upspin.io/errors"
"upspin.io/key/proquint"
"upspin.io/pack/ee"
)
// secret represents the secret seed for a key.
// It is the byte representation of a proquint string.
//
// TODO(ehg): Consider whether to use long seeds for P521.
type secret [16]byte
func (b secret) proquint() string {
proquints := make([]interface{}, len(b)/2)
for i := range proquints {
proquints[i] = proquint.Encode(binary.BigEndian.Uint16(b[2*i : 2*i+2]))
}
return fmt.Sprintf("%s-%s-%s-%s.%s-%s-%s-%s", proquints...)
}
func secretFromProquint(secretStr string) secret {
var b secret
pq := []byte(secretStr)
for i := 0; i < len(b)/2; i++ {
binary.BigEndian.PutUint16(b[2*i:2*i+2], proquint.Decode(pq[6*i:6*i+5]))
}
return b
}
// Generate generates a random key pair on the given curve.
func Generate(curveName string) (public, private, secretStr string, err error) {
// Pick secret 128 bits.
var b secret
ee.GenEntropy(b[:])
return FromSecret(curveName, b.proquint())
}
// FromSecret generates a key pair with the given curve and secret seed.
func FromSecret(curveName, secret string) (public, private, secretStr string, err error) {
const op errors.Op = "keygen.FromSecret"
secretStr = secret
if !ValidSecretSeed(secretStr) {
err := errors.Errorf("expected secret like\n"+
"\tlusab-babad-gutih-tugad.gutuk-bisog-mudof-sakat\n"+
"got\n\t%q", secretStr)
return "", "", "", errors.E(op, errors.Invalid, err)
}
b := secretFromProquint(secretStr)
pub, priv, err := ee.CreateKeys(curveName, b[:])
if err != nil {
return "", "", "", err
}
return string(pub), priv, secretStr, nil
}
// ValidSecretSeed reports whether a seed conforms to the proquint format.
func ValidSecretSeed(seed string) bool {
// TODO: this could be more strict.
return len(seed) == 47 && seed[5] == '-'
}
// writeKeyFile writes a single key to its file, removing the file
// beforehand if necessary due to permission errors.
// If the file's parent directory does not exist, writeKeyFile creates it.
func writeKeyFile(name, key string) error {
// Make the directory if it does not exist.
if err := os.MkdirAll(filepath.Dir(name), 0700); err != nil {
return err
}
// Create the file.
const create = os.O_RDWR | os.O_CREATE | os.O_TRUNC
fd, err := os.OpenFile(name, create, 0400)
if os.IsPermission(err) && os.Remove(name) == nil {
// Create may fail if file already exists and is unwritable,
// which is how it was created.
fd, err = os.OpenFile(name, create, 0400)
}
if err != nil {
return err
}
// Write the key.
_, err = fd.WriteString(key)
if err != nil {
fd.Close()
return err
}
return fd.Close()
}
// writeKeys saves both the public and private keys to their respective files.
// If secretStr is non-empty it is appended as a comment to the private key.
// writeKeys will overwrite any existing keys.
func writeKeys(where, publicKey, privateKey, secretStr string) error {
if secretStr != "" {
privateKey = strings.TrimSpace(privateKey) + " # " + secretStr + "\n"
}
err := writeKeyFile(filepath.Join(where, "secret.upspinkey"), privateKey)
if err != nil {
return err
}
return writeKeyFile(filepath.Join(where, "public.upspinkey"), publicKey)
}
// SaveKeys writes the provided public and private keys to the given directory,
// rotating them if requested by appending the old secret to secret2.upspinkey.
// If secretStr is non-empty it is appended as a comment to the private key.
// If rotate is false and there are existing keys, SaveKeys returns an error.
// If rotate is true and there are no existing keys, SaveKeys returns an error.
func SaveKeys(where string, rotate bool, newPublic, newPrivate, secretStr string) error {
var (
publicFile = filepath.Join(where, "public.upspinkey")
privateFile = filepath.Join(where, "secret.upspinkey")
archiveFile = filepath.Join(where, "secret2.upspinkey")
)
// Read existing key pair.
private, err := ioutil.ReadFile(privateFile)
if os.IsNotExist(err) {
// There is nothing to save. Did we expect there to be?
if rotate {
return errors.Errorf("cannot rotate keys: no prior keys exist in %s", where)
}
// We didn't expect key rotation, so just write the new keys.
return writeKeys(where, newPublic, newPrivate, secretStr)
}
if err != nil {
return err
}
// There's an existing keypair. Did we not expect it?
if !rotate {
return errors.Errorf("prior keys exist in %s; rerun with rotate command to update keys", where)
}
public, err := ioutil.ReadFile(publicFile)
if err != nil {
return err // Halt. Existing files are corrupted and need manual attention.
}
if string(public) == newPublic && string(private) == newPrivate {
return nil // No need to save duplicates.
}
// Write old key pair to archive file.
archive, err := os.OpenFile(archiveFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
if err != nil {
return err // We don't have permission to archive old keys?
}
var modtime string
info, err := os.Stat(privateFile)
if err != nil {
modtime = ""
} else {
modtime = info.ModTime().UTC().Format(" 2006-01-02 15:04:05Z")
}
_, err = fmt.Fprintf(archive, "# EE%s\n%s%s", modtime, public, private)
if err != nil {
return err
}
err = archive.Close()
if err != nil {
return err
}
// Write the new keys.
return writeKeys(where, newPublic, newPrivate, secretStr)
}