/
localsecrets.go
164 lines (146 loc) · 5.16 KB
/
localsecrets.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
// Copyright 2018 The Go Cloud Development Kit Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package localsecrets provides a secrets implementation using a locally
// locally provided symmetric key.
// Use NewKeeper to construct a *secrets.Keeper.
//
// URLs
//
// For secrets.OpenKeeper, localsecrets registers for the scheme "base64key".
// To customize the URL opener, or for more details on the URL format,
// see URLOpener.
// See https://github.com/eliben/gocdkx/concepts/urls/ for background information.
//
// As
//
// localsecrets does not support any types for As.
package localsecrets // import "github.com/eliben/gocdkx/secrets/localsecrets"
import (
"context"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
"net/url"
"github.com/eliben/gocdkx/gcerrors"
"github.com/eliben/gocdkx/secrets"
"golang.org/x/crypto/nacl/secretbox"
)
func init() {
secrets.DefaultURLMux().RegisterKeeper(Scheme, &URLOpener{})
}
// Scheme is the URL scheme localsecrets registers its URLOpener under on
// secrets.DefaultMux.
// See the package documentation and/or URLOpener for details.
const (
Scheme = "base64key"
)
// URLOpener opens localsecrets URLs like "base64key://smGbjm71Nxd1Ig5FS0wj9SlbzAIrnolCz9bQQ6uAhl4=".
//
// The URL host must be base64 encoded, and must decode to exactly 32 bytes.
// If the URL host is empty (e.g., "base64key://"), a new random key is generated.
//
// No query parameters are supported.
type URLOpener struct{}
// OpenKeeperURL opens Keeper URLs.
func (o *URLOpener) OpenKeeperURL(ctx context.Context, u *url.URL) (*secrets.Keeper, error) {
for param := range u.Query() {
return nil, fmt.Errorf("open keeper %v: invalid query parameter %q", u, param)
}
var sk [32]byte
var err error
if u.Host == "" {
sk, err = NewRandomKey()
} else {
sk, err = Base64Key(u.Host)
}
if err != nil {
return nil, fmt.Errorf("open keeper %v: failed to get key: %v", u, err)
}
return NewKeeper(sk), nil
}
// keeper holds a secret for use in symmetric encryption,
// and implements driver.Keeper.
type keeper struct {
secretKey [32]byte // secretbox key size
}
// NewKeeper returns a *secrets.Keeper that uses the given symmetric
// key. See the package documentation for an example.
func NewKeeper(sk [32]byte) *secrets.Keeper {
return secrets.NewKeeper(
&keeper{secretKey: sk},
)
}
// Base64Key takes a secret key as a base64 string and converts it
// to a [32]byte, erroring if the decoded data is not 32 bytes.
func Base64Key(base64str string) ([32]byte, error) {
var sk32 [32]byte
key, err := base64.StdEncoding.DecodeString(base64str)
if err != nil {
return sk32, err
}
keySize := len([]byte(key))
if keySize != 32 {
return sk32, fmt.Errorf("Base64Key: secret key material is %v bytes, want 32 bytes", keySize)
}
copy(sk32[:], key)
return sk32, nil
}
// NewRandomKey will generate random secret key material suitable to be
// used as the secret key argument to NewKeeper.
func NewRandomKey() ([32]byte, error) {
var sk32 [32]byte
// Read random numbers into the passed slice until it's full.
_, err := rand.Read(sk32[:])
if err != nil {
return sk32, err
}
return sk32, nil
}
const nonceSize = 24
// Encrypt encrypts a message using a per-message generated nonce and
// the secret held in the Keeper.
func (k *keeper) Encrypt(ctx context.Context, message []byte) ([]byte, error) {
var nonce [nonceSize]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
return nil, err
}
// secretbox.Seal appends the encrypted message to its first argument and returns
// the result; using a slice on top of the nonce array for this "out" arg allows reading
// the nonce out of the first nonceSize bytes when the message is decrypted.
return secretbox.Seal(nonce[:], message, &nonce, &k.secretKey), nil
}
// Decrypt decrypts a message using a nonce that is read out of the first nonceSize bytes
// of the message and a secret held in the Keeper.
func (k *keeper) Decrypt(ctx context.Context, message []byte) ([]byte, error) {
if len(message) < nonceSize {
return nil, fmt.Errorf("localsecrets: invalid message length (%d, expected at least %d)", len(message), nonceSize)
}
var decryptNonce [nonceSize]byte
copy(decryptNonce[:], message[:nonceSize])
decrypted, ok := secretbox.Open(nil, message[nonceSize:], &decryptNonce, &k.secretKey)
if !ok {
return nil, errors.New("localsecrets: Decrypt failed")
}
return decrypted, nil
}
// Close implements driver.Keeper.Close.
func (k *keeper) Close() error { return nil }
// ErrorAs implements driver.Keeper.ErrorAs.
func (k *keeper) ErrorAs(err error, i interface{}) bool {
return false
}
// ErrorCode implements driver.ErrorCode.
func (k *keeper) ErrorCode(error) gcerrors.ErrorCode { return gcerrors.Unknown }