github.com/cornelk/go-cloud@v0.17.1/secrets/localsecrets/localsecrets.go (about) 1 // Copyright 2018 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package localsecrets provides a secrets implementation using a locally 16 // provided symmetric key. 17 // Use NewKeeper to construct a *secrets.Keeper. 18 // 19 // URLs 20 // 21 // For secrets.OpenKeeper, localsecrets registers for the scheme "base64key". 22 // To customize the URL opener, or for more details on the URL format, 23 // see URLOpener. 24 // See https://github.com/cornelk/go-cloud/concepts/urls/ for background information. 25 // 26 // As 27 // 28 // localsecrets does not support any types for As. 29 package localsecrets // import "github.com/cornelk/go-cloud/secrets/localsecrets" 30 31 import ( 32 "context" 33 "crypto/rand" 34 "encoding/base64" 35 "errors" 36 "fmt" 37 "io" 38 "net/url" 39 40 "github.com/cornelk/go-cloud/gcerrors" 41 "github.com/cornelk/go-cloud/secrets" 42 "golang.org/x/crypto/nacl/secretbox" 43 ) 44 45 func init() { 46 secrets.DefaultURLMux().RegisterKeeper(Scheme, &URLOpener{}) 47 } 48 49 // Scheme is the URL scheme localsecrets registers its URLOpener under on 50 // secrets.DefaultMux. 51 // See the package documentation and/or URLOpener for details. 52 const ( 53 Scheme = "base64key" 54 ) 55 56 // URLOpener opens localsecrets URLs like "base64key://smGbjm71Nxd1Ig5FS0wj9SlbzAIrnolCz9bQQ6uAhl4=". 57 // 58 // The URL host must be base64 encoded, and must decode to exactly 32 bytes. 59 // If the URL host is empty (e.g., "base64key://"), a new random key is generated. 60 // 61 // No query parameters are supported. 62 type URLOpener struct{} 63 64 // OpenKeeperURL opens Keeper URLs. 65 func (o *URLOpener) OpenKeeperURL(ctx context.Context, u *url.URL) (*secrets.Keeper, error) { 66 for param := range u.Query() { 67 return nil, fmt.Errorf("open keeper %v: invalid query parameter %q", u, param) 68 } 69 var sk [32]byte 70 var err error 71 if u.Host == "" { 72 sk, err = NewRandomKey() 73 } else { 74 sk, err = Base64Key(u.Host) 75 } 76 if err != nil { 77 return nil, fmt.Errorf("open keeper %v: failed to get key: %v", u, err) 78 } 79 return NewKeeper(sk), nil 80 } 81 82 // keeper holds a secret for use in symmetric encryption, 83 // and implements driver.Keeper. 84 type keeper struct { 85 secretKey [32]byte // secretbox key size 86 } 87 88 // NewKeeper returns a *secrets.Keeper that uses the given symmetric 89 // key. See the package documentation for an example. 90 func NewKeeper(sk [32]byte) *secrets.Keeper { 91 return secrets.NewKeeper( 92 &keeper{secretKey: sk}, 93 ) 94 } 95 96 // Base64Key takes a secret key as a base64 string and converts it 97 // to a [32]byte, erroring if the decoded data is not 32 bytes. 98 func Base64Key(base64str string) ([32]byte, error) { 99 var sk32 [32]byte 100 key, err := base64.StdEncoding.DecodeString(base64str) 101 if err != nil { 102 return sk32, err 103 } 104 keySize := len([]byte(key)) 105 if keySize != 32 { 106 return sk32, fmt.Errorf("Base64Key: secret key material is %v bytes, want 32 bytes", keySize) 107 } 108 copy(sk32[:], key) 109 return sk32, nil 110 } 111 112 // NewRandomKey will generate random secret key material suitable to be 113 // used as the secret key argument to NewKeeper. 114 func NewRandomKey() ([32]byte, error) { 115 var sk32 [32]byte 116 // Read random numbers into the passed slice until it's full. 117 _, err := rand.Read(sk32[:]) 118 if err != nil { 119 return sk32, err 120 } 121 return sk32, nil 122 } 123 124 const nonceSize = 24 125 126 // Encrypt encrypts a message using a per-message generated nonce and 127 // the secret held in the Keeper. 128 func (k *keeper) Encrypt(ctx context.Context, message []byte) ([]byte, error) { 129 var nonce [nonceSize]byte 130 if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { 131 return nil, err 132 } 133 // secretbox.Seal appends the encrypted message to its first argument and returns 134 // the result; using a slice on top of the nonce array for this "out" arg allows reading 135 // the nonce out of the first nonceSize bytes when the message is decrypted. 136 return secretbox.Seal(nonce[:], message, &nonce, &k.secretKey), nil 137 } 138 139 // Decrypt decrypts a message using a nonce that is read out of the first nonceSize bytes 140 // of the message and a secret held in the Keeper. 141 func (k *keeper) Decrypt(ctx context.Context, message []byte) ([]byte, error) { 142 if len(message) < nonceSize { 143 return nil, fmt.Errorf("localsecrets: invalid message length (%d, expected at least %d)", len(message), nonceSize) 144 } 145 var decryptNonce [nonceSize]byte 146 copy(decryptNonce[:], message[:nonceSize]) 147 148 decrypted, ok := secretbox.Open(nil, message[nonceSize:], &decryptNonce, &k.secretKey) 149 if !ok { 150 return nil, errors.New("localsecrets: Decrypt failed") 151 } 152 return decrypted, nil 153 } 154 155 // Close implements driver.Keeper.Close. 156 func (k *keeper) Close() error { return nil } 157 158 // ErrorAs implements driver.Keeper.ErrorAs. 159 func (k *keeper) ErrorAs(err error, i interface{}) bool { 160 return false 161 } 162 163 // ErrorCode implements driver.ErrorCode. 164 func (k *keeper) ErrorCode(error) gcerrors.ErrorCode { return gcerrors.Unknown }