/* * 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" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/metadata" "github.com/google/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, "") // getting an existing key when changing passphrases oldExistingKeyFn = makeKeyFunc(true, false, "old ") // creating a new key createKeyFn = makeKeyFunc(false, true, "") // creating a new key when changing passphrases newCreateKeyFn = makeKeyFunc(false, true, "new ") ) // 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 or the given // buffer is full. 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, nil } 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.Print(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, formatUsername(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, err := usernameFromID(info.UID()) if err != nil { key.Wipe() return nil, err } err = pam.IsUserLoginToken(username, key, quietFlag.Value) if err != nil { key.Wipe() return nil, err } } 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 } } }