aboutsummaryrefslogtreecommitdiff
path: root/cmd/fscrypt/keys.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/fscrypt/keys.go')
-rw-r--r--cmd/fscrypt/keys.go198
1 files changed, 198 insertions, 0 deletions
diff --git a/cmd/fscrypt/keys.go b/cmd/fscrypt/keys.go
new file mode 100644
index 0000000..45dc294
--- /dev/null
+++ b/cmd/fscrypt/keys.go
@@ -0,0 +1,198 @@
+/*
+ * keys.go - Functions and readers for getting passphrases and raw keys via
+ * the command line. Includes ability to hide the entered passphrase, or use a
+ * raw key as input.
+ *
+ * Copyright 2017 Google Inc.
+ * Author: Joe Richey (joerichey@google.com)
+ *
+ * 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
+ *
+ * http://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 main
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "os"
+
+ "github.com/pkg/errors"
+ "golang.org/x/crypto/ssh/terminal"
+
+ "fscrypt/actions"
+ "fscrypt/crypto"
+ "fscrypt/metadata"
+ "fscrypt/pam"
+)
+
+// The file descriptor for standard input
+const stdinFd = 0
+
+// actions.KeyFuncs for getting or creating cryptographic keys
+var (
+ // getting an existing key
+ existingKeyFn = makeKeyFunc(true, false, "")
+ // creating a new key
+ createKeyFn = makeKeyFunc(false, true, "")
+)
+
+// passphraseReader is an io.Reader intended for terminal passphrase input. The
+// struct is empty as the reader needs to maintain no internal state.
+type passphraseReader struct{}
+
+// Read gets input from the terminal until a newline is encountered. This read
+// should be called with the maximum buffer size for the passphrase.
+func (p passphraseReader) Read(buf []byte) (int, error) {
+ // We read one byte at a time to handle backspaces
+ position := 0
+ for {
+ if position == len(buf) {
+ return position, ErrMaxPassphrase
+ }
+ if _, err := io.ReadFull(os.Stdin, buf[position:position+1]); err != nil {
+ return position, err
+ }
+ switch buf[position] {
+ case '\r', '\n':
+ return position, io.EOF
+ case 3, 4:
+ return position, ErrCanceled
+ case 8, 127:
+ if position > 0 {
+ position--
+ }
+ default:
+ position++
+ }
+ }
+}
+
+// getPassphraseKey puts the terminal into raw mode for the entry of the user's
+// passphrase into a key. If we are not reading from a terminal, just read into
+// the passphrase into the key normally.
+func getPassphraseKey(prompt string) (*crypto.Key, error) {
+ if !quietFlag.Value {
+ fmt.Printf(prompt)
+ }
+
+ // Only disable echo if stdin is actually a terminal.
+ if terminal.IsTerminal(stdinFd) {
+ state, err := terminal.MakeRaw(stdinFd)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ terminal.Restore(stdinFd, state)
+ fmt.Println() // To align input
+ }()
+ }
+
+ return crypto.NewKeyFromReader(passphraseReader{})
+}
+
+// makeKeyFunc creates an actions.KeyFunc. This function customizes the KeyFunc
+// to whether or not it supports retrying, whether it confirms the passphrase,
+// and custom prefix for printing (if any).
+func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFunc {
+ return func(info actions.ProtectorInfo, retry bool) (*crypto.Key, error) {
+ log.Printf("KeyFunc(%s, %v)", formatInfo(info), retry)
+ if retry {
+ if !supportRetry {
+ panic("this KeyFunc does not support retrying")
+ }
+ // Don't retry for non-interactive sessions
+ if quietFlag.Value {
+ return nil, ErrWrongKey
+ }
+ fmt.Println("Incorrect Passphrase")
+ }
+
+ switch info.Source() {
+ case metadata.SourceType_pam_passphrase:
+ prompt := fmt.Sprintf("Enter %slogin passphrase for %s: ",
+ prefix, getUsername(info.UID()))
+ key, err := getPassphraseKey(prompt)
+ if err != nil {
+ return nil, err
+ }
+
+ // To confirm, check that the passphrase is the user's
+ // login passphrase.
+ if shouldConfirm {
+ username := getUsername(info.UID())
+ ok, err := pam.IsUserLoginToken(username, key)
+ if err != nil {
+ key.Wipe()
+ return nil, err
+ }
+ if !ok {
+ key.Wipe()
+ return nil, ErrPAMPassphrase
+ }
+ }
+ return key, nil
+
+ case metadata.SourceType_custom_passphrase:
+ prompt := fmt.Sprintf("Enter %scustom passphrase for protector %q: ",
+ prefix, info.Name())
+ key, err := getPassphraseKey(prompt)
+ if err != nil {
+ return nil, err
+ }
+
+ // To confirm, make sure the user types the same
+ // passphrase in again.
+ if shouldConfirm && !quietFlag.Value {
+ key2, err := getPassphraseKey("Confirm passphrase: ")
+ if err != nil {
+ key.Wipe()
+ return nil, err
+ }
+ defer key2.Wipe()
+
+ if !key.Equals(key2) {
+ key.Wipe()
+ return nil, ErrPassphraseMismatch
+ }
+ }
+ return key, nil
+
+ case metadata.SourceType_raw_key:
+ // Only use prefixes with passphrase protectors.
+ if prefix != "" {
+ return nil, ErrNotPassphrase
+ }
+ prompt := fmt.Sprintf("Enter key file for protector %q: ", info.Name())
+ // Raw keys use a file containing the key data.
+ file, err := promptForKeyFile(prompt)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return nil, err
+ }
+
+ if fileInfo.Size() != metadata.InternalKeyLen {
+ return nil, errors.Wrap(ErrKeyFileLength, file.Name())
+ }
+ return crypto.NewFixedLengthKeyFromReader(file, metadata.InternalKeyLen)
+
+ default:
+ return nil, ErrInvalidSource
+ }
+ }
+}