pax_global_header00006660000000000000000000000064135703235170014520gustar00rootroot0000000000000052 comment=bd07abece1d40ea21962b95a6e72b99331eae714 fscrypt-0.2.5/000077500000000000000000000000001357032351700132165ustar00rootroot00000000000000fscrypt-0.2.5/.gitignore000066400000000000000000000002471357032351700152110ustar00rootroot00000000000000bin/fscrypt bin/pam_fscrypt.so bin/protoc bin/golint bin/protoc-gen-go bin/goimports bin/staticcheck bin/gocovmerge bin/misspell bin/config *coverage.out .vscode tags fscrypt-0.2.5/.travis.yml000066400000000000000000000043401357032351700153300ustar00rootroot00000000000000language: go sudo: false dist: bionic go: 1.12.x notifications: email: false stages: - name: build if: type = push - name: presubmits if: type = pr OR branch = master OR tag IS present - name: deploy if: type = push AND tag IS present jobs: include: - stage: build install: skip script: make - stage: presubmits env: Generate, Format, and Lint install: - make tools script: - make gen - bin/files-changed proto - make format - bin/files-changed format - make lint - &build env: Build and Unit Tests install: skip script: - go build github.com/google/fscrypt/cmd/fscrypt - make - <<: *build go: 1.11.x - env: Integration Tests sudo: required addons: apt: sources: - sourceline: 'deb http://en.archive.ubuntu.com/ubuntu/ artful main universe' packages: - e2fsprogs install: - go get -u github.com/mattn/goveralls - make test-setup script: - make coverage.out - goveralls -coverprofile=coverage.out -service=travis-ci - stage: deploy env: Release Binaries install: skip script: skip before_deploy: make deploy: - provider: releases api_key: secure: aq1sQacqogyjSaf8WJxorVEnXXe1TRz2gl6eoTDuMZVD7fyBV2cgyKgSX3kVt4DZlBTQWlagQQqEluOp6brSQVt2AzLLGPVtUN286M6dN8tSSenn3qDRS5UYyUIhg8KvtqOD6ItFlibCA+7t5B+j4KCCgAKBkDSORJoS180hUbsC6lOx/F/yhjMUTBq96NloIrk7N33JdOjQfeRKiEXZcbRsntzwJlmUjdGLxjIDI409Dx/oDh2rqXz/sJ98ljXebKa28ObyQW9f2UsIBGyFBOnUMK47neJqve/pjGWp1p8pyIGM9rsy+kAQ9htRARcYbBTwTavOCXRehMS+muqttI66G69teHkNfo/dSp52esmNi0p7SQJOydHFJ3gy9alYH4MHGCHnrRdcKnGPyAk3KcsCMxFehPx3pHMShrxh+x9tQz87G1FZlNpyiZiq5fHtb7IDV8l+0XaJKrNhCDvrBwNh8w7iQsAXapmk2Hn8UGTAWbKxCL1l4afB5LA6IGNISmEMuylhnaFZU2WvFzdO7iKY43TePaZJbmMtvovkOVuOWEqxgrkLMXsYrgx0bSV2UCVXJG3XEeJtQluwQgeEYeH6d9VR/chcQ4Qii99IzwbFk2iAk3RN3N94JWT+ubRhJUISOvZ8Z9OMSUBi517mxNgE1HxYa5AadiMQlKVUbDE= file: - bin/fscrypt - bin/pam_fscrypt.so skip_cleanup: true on: repo: google/fscrypt branch: master tags: true fscrypt-0.2.5/CONTRIBUTING.md000066400000000000000000000153121357032351700154510ustar00rootroot00000000000000# How to Contribute to fscrypt We'd love to accept your patches and contributions to this project. There are just a few small guidelines we ask you to follow. ## Contributor License Agreement Contributions to this project must be accompanied by a Contributor License Agreement. You (or your employer) retain the copyright to your contribution, this simply gives us permission to use and redistribute your contributions as part of the project. Head over to to see your current agreements on file or to sign a new one. You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. ## Reporting an Issue, Discussing Design, or Asking a Question __IMPORTANT__: Any significant security issues should __NOT__ be reported in the public issue tracker. Practice responsible disclosure by emailing and directly. Any bugs, problems, or design discussion relating to fscrypt should be raised in the [Github Issue Tracker](https://github.com/google/fscrypt/issues/new). When reporting an issue or problem, be sure to give as much information as possible. Also, make sure you are running the `fscrypt` and `pam_fscrypt.so` built from the current `master` branch. If reporting an issue around the fscrypt command-line tool, post the relevant output from fscrypt, running with the `--verbose` flag. For the `pam_fscrypt` module, use the `debug` option with the module and post the relevant parts of the syslog (usually at `/var/log/syslog`). Be sure to correctly tag your issue. The usage for the tags is as follows: * `bug` - General problems with the program's behavior * The program crashes or hangs * Directories cannot be locked/unlocked * Metadata corruption * Data loss/corruption * `documentation` * Typos or unclear explanations in `README.md` or man pages. * Outdated example output * Unclear or ambiguous error messages * `enhancement` - Things you want in fscrypt * `question` - You don't know how something works with fscrypt * This usually turns into a `documentation` issue. * `testing` - Strange test failures or missing tests ## Submitting a Change to fscrypt All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. On every pull request, [Travis CI](https://travis-ci.org/google/fscrypt) runs unit tests, integration tests, code formatters, and linters. To pass these checks you should make sure that in your submission: - `make` properly builds `fscrypt` and `pam_fscrypt.so`. - All tests, including [integration tests](#running-integration-tests), should pass. - `make format` has been run. - If you made any changes to files ending in `.proto`, the corresponding `.pb.go` files should be regenerated with `make gen`. - Any issues found by `make lint` have been addressed. - If any dependencies have changed, run `go mod tidy` and `go mod vendor`. - `make coverage.out` can be used to generate a coverage report for all of the tests, but isn't required for submission (ideally most code would be tested, we are far from that ideal). Essentially, if you run: ``` make test-setup make all make test-teardown go mod tidy go mod vendor ``` and everything succeeds, and no files are changed, you're good to submit. The `Makefile` should automatically download and build whatever it needs. The only exceptions to this rule are: - `make format` requires [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html). - `make test-setup` requires [`e2fsprogs`](https://en.wikipedia.org/wiki/E2fsprogs) version 1.43 or later (or any patched version that supports `-O encrypt`). ### Running Integration Tests Running `make test` will build each package and run the unit tests, but will skip the integration tests. To run the integration tests, you will need a filesystem that supports encryption. If you already have some empty filesystem at `/foo/bar` that supports filesystem encryption, just run: ```bash make test MOUNT=/foo/bar ``` Otherwise, you can use the `make test-setup`/`make test-teardown` commands to create/destroy a test filesystem for running integration tests. By default, a filesystem will be created (then destroyed) at `/tmp/fscrypt-mount` (using an image file at `/tmp/fscrypt-image`). To create/test/destroy a filesystem at a custom mountpoint `/foo/bar`, run: ```bash make test-setup MOUNT=/foo/bar make test MOUNT=/foo/bar make test-teardown MOUNT=/foo/bar ``` Running the commands without `MOUNT=/foo/bar` uses the default locations. Note that the setup/teardown commands require `sudo` to mount/unmount the test filesystem. ### Changing dependencies fscrypt's dependencies are managed using the [Go 1.11 module system](https://github.com/golang/go/wiki/Modules). If you add or remove a dependency, be sure to update `go.mod`, `go.sum`, and the `vendor/` directory by running `go mod tidy` and `go mod vendor`. fscrypt still vendor's it's dependencies for compatibility with older users, but this will probobly be removed once the module system becomes widespread. Also, when adding a dependency, the license of the package must be compatible with [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). See the [FSF's article](https://www.gnu.org/licenses/license-list.html) for more information. This (unfortunately) means we cannot use external packages under the [GPL](https://choosealicense.com/licenses/gpl-3.0) or [LGPL](https://choosealicense.com/licenses/lgpl-3.0/). We also cannot use packages with missing, misleading, or joke licenses (e.g. [Unlicense](http://unlicense.org/), [WTFPL](http://www.wtfpl.net/), [CC0](https://creativecommons.org/publicdomain/zero/1.0/)). ### Build System Details ### Under the hood, the Makefile uses many go tools to generate, format, and lint your code. `make gen`: - Downloads [`protoc`](https://github.com/google/protobuf) to compile the `.proto` files. - Turns each `.proto` file into a matching `.pb.go` file using [`protoc-gen-go`](https://github.com/golang/protobuf/tree/master/protoc-gen-go) (built from source in `vendor/`). `make format` runs: - [`goimports`](https://godoc.org/golang.org/x/tools/cmd/goimports) (built from source in `vendor/`) on the `.go` files. - [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html) on the `.c` and `.h` files. `make lint` runs: - [`go vet`](https://golang.org/cmd/vet/) - [`golint`](https://github.com/golang/lint) (built from source in `vendor/`) - [`staticcheck`](https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck) (built from source in `vendor/`) fscrypt-0.2.5/LICENSE000066400000000000000000000261361357032351700142330ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. fscrypt-0.2.5/Makefile000066400000000000000000000170321357032351700146610ustar00rootroot00000000000000# Makefile for fscrypt # # 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. # Update on each new release!! VERSION := v0.2.5 NAME := fscrypt PAM_NAME := pam_$(NAME) ###### Makefile Command Line Flags ###### # # BIN: The locaton where binaries will be built. Default: ./bin # INSTALL: The tool used to install binaries. Default: sudo install # DESTDIR: The location for the fscrypt binary. Default: /usr/local/bin # PAM_MODULE_DIR: The location for pam_fscrypt.so. Default: /lib/security # # MOUNT: The filesystem where our tests are run. Default: /mnt/fscrypt_mount # Ex: make test-setup MOUNT=/foo/bar # Creates a test filesystem at that location. # Ex: make test-teardown MOUNT=/foo/bar # Cleans up a test filesystem created with "make test-setup". # Ex: make test MOUNT=/foo/bar # Run all integration tests on that filesystem. This can be an existing # filesystem, or one created with "make test-setup" (this is the default). # # CFLAGS: The flags passed to the C compiler. Default: -O2 -Wall # Ex: make fscrypt "CFLAGS = -O3 -Werror" # Builds fscrypt with the C code failing on warnings and highly optimized. # # LDFLAGS: The flags passed to the C linker. Default empty # Ex: make fscrypt "LDFLAGS = -static -ldl -laudit -lcap-ng" # Builds fscrypt as a static binary. # # GO_FLAGS: The flags passed to "go build". Default empty # Ex: make fscrypt "GO_FLAGS = -race" # Builds fscrypt with race detection for the go code. # # GO_LINK_FLAGS: The flags passed to the go linker. Default: -s -w # Ex: make fscrypt GO_LINK_FLAGS="" # Builds fscrypt without stripping the binary. BIN := bin export PATH := $(BIN):$(PATH) PAM_MODULE := $(BIN)/$(PAM_NAME).so ###### Setup Build Flags ##### CFLAGS := -O2 -Wall # Pass CFLAGS to each cgo invocation. export CGO_CFLAGS = $(CFLAGS) # By default, we strip the binary to reduce size. GO_LINK_FLAGS := -s -w # Flag to embed the version (pulled from tags) into the binary. TAG_VERSION := $(shell git describe --tags) VERSION_FLAG := -X "main.version=$(if $(TAG_VERSION),$(TAG_VERSION),$(VERSION))" # Flag to embed the date and time of the build into the binary. DATE_FLAG := -X "main.buildTime=$(shell date)" override GO_LINK_FLAGS += $(VERSION_FLAG) $(DATE_FLAG) -extldflags "$(LDFLAGS)" override GO_FLAGS += --ldflags '$(GO_LINK_FLAGS)' ###### Find All Files and Directories ###### FILES := $(shell find . \( -path ./vendor -o -path "./.*" \) -prune -o -type f -printf "%P\n") GO_FILES := $(filter %.go,$(FILES)) GO_NONGEN_FILES := $(filter-out %.pb.go,$(GO_FILES)) GO_DIRS := $(sort $(dir $(GO_FILES))) C_FILES := $(filter %.c %.h,$(FILES)) PROTO_FILES := $(filter %.proto,$(FILES)) ###### Build, Formatting, and Linting Commands ###### .PHONY: default all gen format lint clean default: $(BIN)/$(NAME) $(PAM_MODULE) all: tools gen default format lint test $(BIN)/$(NAME): $(GO_FILES) $(C_FILES) go build $(GO_FLAGS) -o $@ ./cmd/$(NAME) $(PAM_MODULE): $(GO_FILES) $(C_FILES) go build -buildmode=c-shared $(GO_FLAGS) -o $@ ./$(PAM_NAME) rm -f $(BIN)/$(PAM_NAME).h gen: $(BIN)/protoc $(BIN)/protoc-gen-go $(PROTO_FILES) protoc --go_out=. $(PROTO_FILES) format: $(BIN)/goimports goimports -w $(GO_NONGEN_FILES) clang-format -i -style=Google $(C_FILES) lint: $(BIN)/golint $(BIN)/staticcheck $(BIN)/misspell go vet ./... go list ./... | xargs -L1 golint -set_exit_status staticcheck ./... misspell -source=text $(FILES) clean: rm -f $(BIN)/$(NAME) $(PAM_MODULE) $(TOOLS) coverage.out $(COVERAGE_FILES) $(PAM_CONFIG) ###### Testing Commands (setup/teardown require sudo) ###### .PHONY: test test-setup test-teardown # If MOUNT exists signal that we should run integration tests. MOUNT := /tmp/$(NAME)-mount IMAGE := /tmp/$(NAME)-image ifneq ("$(wildcard $(MOUNT))","") export TEST_FILESYSTEM_ROOT = $(MOUNT) endif test: go test -p 1 ./... test-setup: dd if=/dev/zero of=$(IMAGE) bs=1M count=20 mkfs.ext4 -b 4096 -O encrypt $(IMAGE) -F mkdir -p $(MOUNT) sudo mount -o rw,loop,user $(IMAGE) $(MOUNT) sudo chmod +777 $(MOUNT) test-teardown: sudo umount $(MOUNT) rmdir $(MOUNT) rm -f $(IMAGE) # Runs tests and generates coverage COVERAGE_FILES := $(addsuffix coverage.out,$(GO_DIRS)) coverage.out: $(BIN)/gocovmerge $(COVERAGE_FILES) @gocovmerge $(COVERAGE_FILES) > $@ %/coverage.out: $(GO_FILES) $(C_FILES) @go test -coverpkg=./... -covermode=count -coverprofile=$@ -p 1 ./$* 2> /dev/null ###### Installation Commands (require sudo) ##### .PHONY: install install-bin install-pam uninstall install: install-bin install-pam PREFIX := /usr/local DESTDIR := $(PREFIX)/bin install-bin: $(BIN)/$(NAME) install -d $(DESTDIR) install $< $(DESTDIR) PAM_MODULE_DIR := $(PREFIX)/lib/security PAM_INSTALL_PATH := $(PAM_MODULE_DIR)/$(PAM_NAME).so PAM_CONFIG := $(BIN)/config PAM_CONFIG_DIR := $(PREFIX)/share/pam-configs install-pam: $(PAM_MODULE) install -d $(PAM_MODULE_DIR) install $(PAM_MODULE) $(PAM_MODULE_DIR) m4 --define=PAM_INSTALL_PATH=$(PAM_INSTALL_PATH) < $(PAM_NAME)/config > $(PAM_CONFIG) install -d $(PAM_CONFIG_DIR) install $(PAM_CONFIG) $(PAM_CONFIG_DIR)/$(NAME) uninstall: rm -f $(DESTDIR)/$(NAME) $(PAM_INSTALL_PATH) $(PAM_CONFIG_DIR)/$(NAME) #### Tool Building Commands #### TOOLS := $(addprefix $(BIN)/,protoc golint protoc-gen-go goimports staticcheck gocovmerge misspell) .PHONY: tools tools: $(TOOLS) $(BIN)/golint: GO111MODULE=off go get golang.org/x/lint/golint GO111MODULE=off go build -o $@ golang.org/x/lint/golint $(BIN)/protoc-gen-go: GO111MODULE=off go get -d github.com/golang/protobuf/protoc-gen-go git -C "$(shell go env GOPATH)/src/github.com/golang/protobuf" checkout v1.2.0 go install github.com/golang/protobuf/protoc-gen-go GO111MODULE=off go build -o $@ github.com/golang/protobuf/protoc-gen-go $(BIN)/goimports: GO111MODULE=off go get golang.org/x/tools/cmd/goimports GO111MODULE=off go build -o $@ golang.org/x/tools/cmd/goimports $(BIN)/staticcheck: GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck GO111MODULE=off go build -o $@ honnef.co/go/tools/cmd/staticcheck $(BIN)/gocovmerge: GO111MODULE=off go get github.com/wadey/gocovmerge GO111MODULE=off go build -o $@ github.com/wadey/gocovmerge $(BIN)/misspell: GO111MODULE=off go get github.com/client9/misspell GO111MODULE=off go build -o $@ github.com/client9/misspell/cmd/misspell # Non-go tools downloaded from appropriate repository PROTOC_VERSION := 3.6.1 ARCH := $(shell uname -m) ifeq (x86_64,$(ARCH)) PROTOC_ARCH := x86_64 else ifneq ($(filter i386 i686,$(ARCH)),) PROTOC_ARCH := x86_32 else ifneq ($(filter aarch64 armv8l,$(ARCH)),) PROTOC_ARCH := aarch_64 endif ifdef PROTOC_ARCH PROTOC_URL := https://github.com/google/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-linux-$(PROTOC_ARCH).zip $(BIN)/protoc: wget -q $(PROTOC_URL) -O /tmp/protoc.zip unzip -q -j /tmp/protoc.zip bin/protoc -d $(BIN) else PROTOC_PATH := $(shell which protoc) $(BIN)/protoc: $(PROTOC_PATH) ifneq ($(wildcard $(PROTOC_PATH)),) cp $< $@ else $(error Could not download protoc binary or locate it on the system. Please install it) endif endif fscrypt-0.2.5/README.md000066400000000000000000000667521357032351700145150ustar00rootroot00000000000000# fscrypt [![GitHub version](https://badge.fury.io/go/github.com%2Fgoogle%2Ffscrypt.svg)](https://github.com/google/fscrypt/releases) [![Build Status](https://travis-ci.org/google/fscrypt.svg?branch=master)](https://travis-ci.org/google/fscrypt) [![Coverage Status](https://coveralls.io/repos/github/google/fscrypt/badge.svg?branch=master)](https://coveralls.io/github/google/fscrypt?branch=master) [![GoDoc](https://godoc.org/github.com/google/fscrypt?status.svg)](https://godoc.org/github.com/google/fscrypt) [![Go Report Card](https://goreportcard.com/badge/github.com/google/fscrypt)](https://goreportcard.com/report/github.com/google/fscrypt) [![License](https://img.shields.io/badge/LICENSE-Apache2.0-ff69b4.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) fscrypt is a high-level tool for the management of [Linux filesystem encryption](https://lwn.net/Articles/639427). This tool manages metadata, key generation, key wrapping, PAM integration, and provides a uniform interface for creating and modifying encrypted directories. For a small low-level tool that directly sets policies, see [fscryptctl](https://github.com/google/fscryptctl). To use fscrypt, you must have a filesystem with encryption enabled and a kernel that supports reading/writing from that filesystem. Currently, [ext4](https://en.wikipedia.org/wiki/Ext4), [F2FS](https://en.wikipedia.org/wiki/F2FS), and [UBIFS](https://en.wikipedia.org/wiki/UBIFS) support Linux filesystem encryption. Ext4 has supported Linux filesystem encryption [since v4.1](https://lwn.net/Articles/639427), F2FS [added support in v4.2](https://lwn.net/Articles/649652), and UBIFS [added support in v4.10](https://lwn.net/Articles/707900). Other filesystems may add support for native encryption in the future. Filesystems may additionally require certain kernel configuration options to be set to use native encryption. See [Runtime Dependencies](#runtime-dependencies). Most of the testing for fscrypt has been done with ext4 filesystems. However, the kernel uses a common userspace interface, so this tool should work with all existing and future filesystems which support encryption. If there is a problem using fscrypt with other filesystems, please open an issue. ### Other encryption solutions It is important to distinguish Linux filesystem encryption from two other encryption solutions: [eCryptfs](https://en.wikipedia.org/wiki/ECryptfs) and [dm-crypt](https://en.wikipedia.org/wiki/Dm-crypt). Currently, dm-crypt encrypts an entire block device with a single master key. dm-crypt can be used with or without fscrypt. All filesystem data (including all filesystem metadata) is encrypted with this single key when using dm-crypt, while fscrypt only encrypts the filenames and file contents in a specified directory. Note that using both dm-crypt and fscrypt simultaneously will give the protections and benefits of both; however, this may cause a decrease in your performance, as file contents are encrypted twice. One example of a reasonable setup could involve using dm-crypt with a TPM or Secure boot key, while using fscrypt for the contents of a home directory. This would still encrypt the entire drive, but would also tie the encryption of a user's personal documents to their passphrase. On the other hand, eCryptfs is another form of filesystem encryption on Linux; it encrypts a filesystem directory with some key or passphrase. eCryptfs sits on top of an existing filesystem. This makes eCryptfs an alternative choice if your filesystem or kernel does not support native filesystem encryption. Also note that fscrypt does not support or setup either eCryptfs or dm-crypt. For these tools, use [ecryptfs-utils](https://packages.debian.org/source/jessie/ecryptfs-utils) for eCryptfs or [cryptsetup](https://linux.die.net/man/8/cryptsetup) for dm-crypt. ## Features fscrypt is intended to improve upon the work in [e4crypt](http://man7.org/linux/man-pages/man8/e4crypt.8.html) by providing a more managed environment and handling more functionality in the background. fscrypt has a [design document](https://goo.gl/55cCrI) specifying the full architecture of fscrypt. Briefly, fscrypt deals with protectors and policies. Protectors represent some secret or information used to protect the confidentiality of your data. The three currently supported protector types are: 1. Your login passphrase, through [PAM](http://www.linux-pam.org/Linux-PAM-html) 2. A custom passphrase 3. A raw key file These protectors are mutable, so the information can change without needing to update any of your encrypted directories. Policies represent the actual key passed to the kernel. This "policy key" is immutable and policies are (usually) applied to a single directory. Protectors then protect policies, so that having one of the protectors for a policy is enough to get the policy key and access the data. Which protectors protect a policy can also be changed. This allows a user to change how a directory is protected without needing to reencrypt the directory's contents. Concretely, fscrypt contains the following functionality: * `fscrypt setup` - Initializes the `fscrypt.conf` file * This is the only functionality which requires root privileges * `fscrypt setup MOUNTPOINT` - Gets a filesystem ready for use with fscrypt * `fscrypt encrypt DIRECTORY` - Encrypts an empty directory * `fscrypt unlock DIRECTORY` - Unlocks an encrypted directory * `fscrypt purge MOUNTPOINT` - Removes keys for a filesystem before unmounting * `fscrypt status [PATH]` - Gets detailed info about filesystems or paths * `fscrypt metadata` - Manages policies or protectors directly The following functionality is planned: * `fscrypt backup` - Manages backups of the fscrypt metadata * `fscrypt recovery` - Manages recovery keys for directories * `fscrypt cleanup` - Scans filesystem for unused policies/protectors See the example usage section below or run `fscrypt COMMAND --help` for more information about each of the commands. ## Building and Installing fscrypt has a minimal set of build dependencies: * [Go](https://golang.org/doc/install) 1.10 or higher * A C compiler (`gcc` or `clang`) * `make` * Headers for [`libpam`](http://www.linux-pam.org/). Install them with the appropriate package manager: - Debian/Ubuntu: `sudo apt install libpam0g-dev` - Red Hat: `sudo yum install pam-devel` - Arch: [`pam`](https://www.archlinux.org/packages/core/x86_64/pam/) package (usually installed by default) Once all the dependencies are installed, you can get the repository by running: ```shell go get -d github.com/google/fscrypt/... ``` Running `make` in `$GOPATH/src/github.com/google/fscrypt` builds the executable (`fscrypt`) and PAM module (`pam_fscrypt.so`) in the `bin/` directory. Use `make bin/fscrypt` or `make bin/pam_fscrypt.so` to build only one. Running `sudo make install` installs `fscrypt` to `/usr/local/bin`, `pam_fscrypt.so` to `/usr/local/lib/security`, and `pam_fscrypt/config` to `/usr/local/share/pam-configs`. Use `make install-bin` to only install `fscrypt`. Use `make install-pam` to only install the pam files. See the `Makefile` for instructions on how to customize the build (e.g. installing to a custom location, using different build flags, building a static binary, etc ...) Alternatively, if you only want to install the fscrypt binary to `$GOPATH/bin`, simply run: ```shell go get github.com/google/fscrypt/cmd/fscrypt ``` ### Runtime Dependencies To run, fscrypt needs the following libraries: * `libpam.so` (almost certainly already on your system) In addition, fscrypt requires kernel support for encryption for your filesystem, and for some filesystems that a feature flag has been enabled in the on-disk filesystem superblock: * For ext4, the kernel must be v4.1 or later, and the kernel configuration must have either `CONFIG_FS_ENCRYPTION=y` (for kernels v5.1+) or `CONFIG_EXT4_ENCRYPTION=y` or `=m` (for older kernels). Also, the filesystem must have the `encrypt` feature flag enabled; see [here](#getting-encryption-not-enabled-on-an-ext4-filesystem) for how to enable it. * For f2fs, the kernel must be v4.2 or later, and the kernel configuration must have either `CONFIG_FS_ENCRYPTION=y` (for kernels v5.1+) or `CONFIG_F2FS_FS_ENCRYPTION=y` (for older kernels). Also, the filesystem must have the `encrypt` feature flag enabled. It can be enabled at format time by `mkfs.f2fs -O encrypt`, or later by `fsck.f2fs -O encrypt`. * For UBIFS, the kernel must be v4.10 or later, and the kernel configuration must have either `CONFIG_FS_ENCRYPTION=y` (for kernels v5.1+) or `CONFIG_UBIFS_FS_ENCRYPTION=y` (for older kernels). Be careful when using encryption on removable media, since filesystems with the `encrypt` feature cannot be mounted on systems with kernel versions older than the minimums listed above -- even to access unencrypted files! ### Setting up the PAM module Note that to make use of the installed PAM module, your [PAM configuration files](http://www.linux-pam.org/Linux-PAM-html/sag-configuration.html) in `/etc/pam.d` must be modified to add fscrypt. #### Automatic setup on Ubuntu fscrypt automatically installs the [PAM config file](https://wiki.ubuntu.com/PAMConfigFrameworkSpec) `pam_fscrypt/config` to `/usr/share/pam-configs/fscrypt`. This file contains reasonable defaults for the PAM module. To automatically apply these changes, run `sudo pam-auth-update` and follow the on-screen instructions. #### Manual setup The fscrypt PAM module implements the Auth, Session, and Password [types](http://www.linux-pam.org/Linux-PAM-html/sag-configuration-file.html). The Password functionality of `pam_fscrypt.so` is used to automatically rewrap a user's login protector when their unix passphrase changes. An easy way to get the working is to add the line: ``` password optional pam_fscrypt.so ``` after `pam_unix.so` in `/etc/pam.d/common-password` or similar. The Auth and Session functionality of `pam_fscrypt.so` are used to automatically unlock directories when logging in as a user. An easy way to get this working is to add the line: ``` auth optional pam_fscrypt.so ``` after `pam_unix.so` in `/etc/pam.d/common-auth` or similar, and to add the line: ``` session optional pam_fscrypt.so drop_caches lock_policies ``` after `pam_unix.so` in `/etc/pam.d/common-session` or similar. The `lock_policies` option locks the directories protected with the user's login passphrase when the last session ends. The `drop_caches` option tells fscrypt to clear the filesystem caches when the last session closes, ensuring all the locked data is inaccessible. All the types also support the `debug` option which prints additional debug information to the syslog. ## Note about stability fscrypt follows [semantic versioning](http://semver.org). As such, all versions below `1.0.0` should be considered development versions. This means no guarantees are make about the stability of APIs or formats of config files. As the on-disk metadata structures use [Protocol Buffers](https://github.com/google/protobuf), we don't expect to break backwards compatibility for metadata, but we give no guarantees. ## Example Usage All these examples assume we have ext4 filesystems mounted at `/` and `/mnt/disk` which both support encryption and that `/mnt/disk` contains directories we want to encrypt. ### Setting up fscrypt on a directory ```bash # Check which directories on our system support encryption >>>>> fscrypt status 2 filesystem(s) on this system support encryption MOUNTPOINT DEVICE FILESYSTEM STATUS / /dev/sda1 ext4 encryption not enabled /mnt/disk /dev/sdb ext4 not setup with fscrypt # Create the global configuration file. Nothing else needs root. >>>>> sudo fscrypt setup Create "/etc/fscrypt.conf"? [Y/n] y Customizing passphrase hashing difficulty for this system... Created global config file at "/etc/fscrypt.conf". # Start using fscrypt with our filesystem >>>>> fscrypt setup /mnt/disk Metadata directories created at "/mnt/disk/.fscrypt". Filesystem "/mnt/disk" (/dev/sdb) ready for use with ext4 encryption. # Initialize encryption on a new empty directory >>>>> mkdir /mnt/disk/dir1 >>>>> fscrypt encrypt /mnt/disk/dir1 Should we create a new protector? [Y/n] y Your data can be protected with one of the following sources: 1 - Your login passphrase (pam_passphrase) 2 - A custom passphrase (custom_passphrase) 3 - A raw 256-bit key (raw_key) Enter the source number for the new protector [2 - custom_passphrase]: 2 Enter a name for the new protector: Super Secret Enter custom passphrase for protector "Super Secret": Confirm passphrase: "/mnt/disk/dir1" is now encrypted, unlocked, and ready for use. # We can see this created one policy and one protector for this directory >>>>> fscrypt status /mnt/disk ext4 filesystem "/mnt/disk" has 1 protector(s) and 1 policy(ies) PROTECTOR LINKED DESCRIPTION 7626382168311a9d No custom protector "Super Secret" POLICY UNLOCKED PROTECTORS 7626382168311a9d Yes 7626382168311a9d ``` #### Quiet Version ```bash >>>>> sudo fscrypt setup --quiet --force >>>>> fscrypt setup /mnt/disk --quiet >>>>> echo "hunter2" | fscrypt encrypt /mnt/disk/dir1 --quiet --source=custom_passphrase --name="Super Secret" ``` ### Locking and unlocking a directory As noted in the troubleshooting below, we (as of now) have to unmount a filesystem after purging its keys to clear the necessary caches. ```bash # Write a file to our encrypted directory. >>>>> echo "Hello World" > /mnt/disk/dir1/secret.txt >>>>> fscrypt status /mnt/disk/dir1 "/mnt/disk/dir1" is encrypted with fscrypt. Policy: 16382f282d7b29ee Unlocked: Yes Protected with 1 protector(s): PROTECTOR LINKED DESCRIPTION 7626382168311a9d No custom protector "Super Secret" # Purging, unmounting, and remounting a filesystem locks all the files. >>>>> fscrypt purge /mnt/disk WARNING: This may make data encrypted with fscrypt inaccessible. Purge all policy keys from "/mnt/disk" (this will lock all encrypted directories) [y/N] y All keys purged for "/mnt/disk". Filesystem "/mnt/disk" should now be unmounted. >>>>> umount /mnt/disk >>>>> mount /mnt/disk >>>>> fscrypt status /mnt/disk/dir1 "/mnt/disk/dir1" is encrypted with fscrypt. Policy: 16382f282d7b29ee Unlocked: No Protected with 1 protector(s): PROTECTOR LINKED DESCRIPTION 7626382168311a9d No custom protector "Super Secret" # Now the filenames and file contents are inaccessible >>>>> ls /mnt/disk/dir1 u,k20l9HrtrizDjh0zGkw2dTfBkX4T0ZDUlsOhBLl4P >>>>> cat /mnt/disk/dir1/u,k20l9HrtrizDjh0zGkw2dTfBkX4T0ZDUlsOhBLl4P cat: /mnt/disk/dir1/u,k20l9HrtrizDjh0zGkw2dTfBkX4T0ZDUlsOhBLl4P: Required key not available # Unlocking the directory makes the contents available >>>>> fscrypt unlock /mnt/disk/dir1 Enter custom passphrase for protector "Super Secret": "/mnt/disk/dir1" is now unlocked and ready for use. >>>>> fscrypt status /mnt/disk/dir1 "/mnt/disk/dir1" is encrypted with fscrypt. Policy: 16382f282d7b29ee Unlocked: Yes Protected with 1 protector(s): PROTECTOR LINKED DESCRIPTION 7626382168311a9d No custom protector "Super Secret" >>>>> cat /mnt/disk/dir1/secret.txt Hello World ``` #### Quiet Version ```bash >>>>> fscrypt purge /mnt/disk --quiet --force >>>>> umount /mnt/disk >>>>> mount /mnt/disk >>>>> printf "hunter2" | fscrypt unlock /mnt/disk/dir1 --quiet ``` ### Protecting a directory with your login passphrase As noted above and in the troubleshooting below, fscrypt cannot (yet) detect when your login passphrase changes. So if you protect a directory with your login passphrase, you may have to do additional work when you change your system passphrase. ```bash # Login passphrases also require that fscrypt is setup on the root directory >>>>> sudo fscrypt setup / Filesystem "/" (/dev/dm-1) ready for use with ext4 encryption. # Select your login passphrase as the desired source. >>>>> mkdir /mnt/disk/dir2 >>>>> fscrypt encrypt /mnt/disk/dir2 Should we create a new protector? [Y/n] y Your data can be protected with one of the following sources: 1 - Your login passphrase (pam_passphrase) 2 - A custom passphrase (custom_passphrase) 3 - A raw 256-bit key (raw_key) Enter the source number for the new protector [2 - custom_passphrase]: 1 Enter login passphrase for joerichey: "/mnt/disk/dir2" is now encrypted, unlocked, and ready for use. # Note that the login protector actually sits on the root filesystem >>>>> fscrypt status /mnt/disk/dir2 "/mnt/disk/dir2" is encrypted with fscrypt. Policy: fe1c92009abc1cff Unlocked: Yes Protected with 1 protector(s): PROTECTOR LINKED DESCRIPTION 6891f0a901f0065e Yes (/) login protector for joerichey >>>>> fscrypt status /mnt/disk ext4 filesystem "/mnt/disk" has 3 protector(s) and 3 policy(ies) PROTECTOR LINKED DESCRIPTION 7626382168311a9d No custom protector "Super Secret" 6891f0a901f0065e Yes (/) login protector for joerichey POLICY UNLOCKED PROTECTORS 16382f282d7b29ee Yes 7626382168311a9d fe1c92009abc1cff Yes 6891f0a901f0065e >>>>> fscrypt status / ext4 filesystem "/" has 1 protector(s) and 0 policy(ies) PROTECTOR LINKED DESCRIPTION 6891f0a901f0065e No login protector for joerichey ``` #### Quiet Version ```bash >>>>> mkdir /mnt/disk/dir2 >>>>> echo "password" | fscrypt encrypt /mnt/disk/dir1 --source=pam_passphrase --quiet ``` ### Changing a custom passphrase ```bash # First we have to figure out which protector we wish to change. >>>>> fscrypt status /mnt/disk/dir1 "/mnt/disk/dir1" is encrypted with fscrypt. Policy: 16382f282d7b29ee Unlocked: Yes Protected with 1 protector(s): PROTECTOR LINKED DESCRIPTION 7626382168311a9d No custom protector "Super Secret" # Now specify the protector directly to the metadata command >>>>> fscrypt metadata change-passphrase --protector=/mnt/disk:7626382168311a9d Enter old custom passphrase for protector "Super Secret": Enter new custom passphrase for protector "Super Secret": Confirm passphrase: Passphrase for protector 7626382168311a9d successfully changed. ``` #### Quiet Version ```bash >>>>> printf "hunter2\nhunter3" | fscrypt metadata change-passphrase --protector=/mnt/disk:7626382168311a9d --quiet ``` ### Using a raw key protector fscrypt also supports protectors which use raw key files as the user-provided secret. These key files must be exactly 32 bytes long and contain the raw binary data of the key. Obviously, make sure to store the key file securely (and not in the directory you are encrypting with it). If generating the keys on Linux make sure you are aware of [how randomness works](http://man7.org/linux/man-pages/man7/random.7.html) and [some common myths](https://www.2uo.de/myths-about-urandom/). ```bash # Generate a 256-bit key file >>>>> head --bytes=32 /dev/urandom > secret.key # Now create a key file protector without using it on a directory. Note that we # could also use `fscrypt encrypt --key=secret.key` to achieve the same thing. >>>>> fscrypt metadata create protector /mnt/disk Create new protector on "/mnt/disk" [Y/n] y Your data can be protected with one of the following sources: 1 - Your login passphrase (pam_passphrase) 2 - A custom passphrase (custom_passphrase) 3 - A raw 256-bit key (raw_key) Enter the source number for the new protector [2 - custom_passphrase]: 3 Enter a name for the new protector: Skeleton Enter key file for protector "Skeleton": secret.key Protector 2c75f519b9c9959d created on filesystem "/mnt/disk". >>>>> fscrypt status /mnt/disk ext4 filesystem "/mnt/disk" has 3 protectors and 3 policies PROTECTOR LINKED DESCRIPTION 7626382168311a9d No custom protector "Super Secret" 2c75f519b9c9959d No raw key protector "Skeleton" 6891f0a901f0065e Yes (/) login protector for joerichey POLICY UNLOCKED PROTECTORS 16382f282d7b29ee Yes 7626382168311a9d fe1c92009abc1cff Yes 6891f0a901f0065e # Finally, we could apply this key to a directory >>>>> mkdir /mnt/disk/dir3 >>>>> fscrypt encrypt /mnt/disk/dir3 --protector=/mnt/disk:2c75f519b9c9959d Enter key file for protector "Skeleton": secret.key "/mnt/disk/dir3" is now encrypted, unlocked, and ready for use. ``` #### Quiet Version ```bash >>>>> head --bytes=32 /dev/urandom > secret.key >>>>> fscrypt encrypt /mnt/disk/dir3 --key=secret.key --source=raw_key --name=Skeleton ``` ### Using multiple protectors for a policy fscrypt supports the idea of protecting a single directory with multiple protectors. This means having access to any of the protectors is sufficient to decrypt the directory. This is useful for sharing data or setting up access control systems. ```bash # Add an existing protector to the policy for some directory >>>>> fscrypt status /mnt/disk ext4 filesystem "/mnt/disk" has 3 protectors and 3 policies PROTECTOR LINKED DESCRIPTION 7626382168311a9d No custom protector "Super Secret" 2c75f519b9c9959d No raw key protector "Skeleton" 6891f0a901f0065e Yes (/) login protector for joerichey POLICY UNLOCKED PROTECTORS d03fb894584a4318 No 2c75f519b9c9959d 16382f282d7b29ee No 7626382168311a9d fe1c92009abc1cff No 6891f0a901f0065e >>>>> fscrypt status /mnt/disk/dir1 "/mnt/disk/dir1" is encrypted with fscrypt. Policy: 16382f282d7b29ee Unlocked: No Protected with 1 protector: PROTECTOR LINKED DESCRIPTION 7626382168311a9d No custom protector "Super Secret" >>>>> fscrypt metadata add-protector-to-policy --protector=/mnt/disk:2c75f519b9c9959d --policy=/mnt/disk:16382f282d7b29ee WARNING: All files using this policy will be accessible with this protector!! Protect policy 16382f282d7b29ee with protector 2c75f519b9c9959d? [Y/n] Enter key file for protector "Skeleton": secret.key Enter custom passphrase for protector "Super Secret": Protector 2c75f519b9c9959d now protecting policy 16382f282d7b29ee. >>>>> fscrypt status /mnt/disk/dir1 "/mnt/disk/dir1" is encrypted with fscrypt. Policy: 16382f282d7b29ee Unlocked: No Protected with 2 protectors: PROTECTOR LINKED DESCRIPTION 7626382168311a9d No custom protector "Super Secret" 2c75f519b9c9959d No raw key protector "Skeleton" # Now the unlock command will prompt for which protector we want to use >>>>> fscrypt unlock /mnt/disk/dir1 The available protectors are: 0 - custom protector "Super Secret" 1 - raw key protector "Skeleton" Enter the number of protector to use: 1 Enter key file for protector "Skeleton": secret.key "/mnt/disk/dir1" is now unlocked and ready for use. # The protector can also be removed from the policy (if it is not the only one) >>>>> fscrypt metadata remove-protector-from-policy --protector=/mnt/disk:2c75f519b9c9959d --policy=/mnt/disk:16382f282d7b29ee WARNING: All files using this policy will NO LONGER be accessible with this protector!! Stop protecting policy 16382f282d7b29ee with protector 2c75f519b9c9959d? [y/N] y Protector 2c75f519b9c9959d no longer protecting policy 16382f282d7b29ee. ``` #### Quiet Version ```bash >>>>> echo "hunter2" | fscrypt metadata add-protector-to-policy --protector=/mnt/disk:2c75f519b9c9959d --policy=/mnt/disk:16382f282d7b29ee --key=secret.key --quiet >>>>> fscrypt metadata remove-protector-from-policy --protector=/mnt/disk:2c75f519b9c9959d --policy=/mnt/disk:16382f282d7b29ee --quiet --force ``` ## Contributing We would love to accept your contributions to fscrypt. See the `CONTRIBUTING.md` file for more information about signing the CLA and submitting a pull request. ## Troubleshooting In general, if you are encountering issues with fscrypt, [open an issue](https://github.com/google/fscrypt/issues/new), following the guidelines in `CONTRIBUTING.md`. We will try our best to help. #### I changed my login passphrase, now all my directories are inaccessible The PAM module provided by fscrypt (`pam_fscrypt.so`) should automatically detect changes to a user's login passphrase so that they can still access their encrypted directories. However, sometimes the login passphrase can become desynchronized from a user's login protector. This usually happens when the PAM passphrase is managed by an external system, if the PAM module is not installed, or if the PAM module is not properly configured. To fix your login protector, you first should find the appropriate protector ID by running `fscrypt status "/"`. Then, change the passphrase for this protector by running: ``` fscrypt metadata change-passphrase --protector=/:ID ``` #### Directories using my login passphrase are not automatically unlocking. Either the PAM module is not installed correctly, or your login passphrase changed and things got out of sync. Another reason that these directories might not unlock is if your session starts without password authentication. The most common case of this is public-key ssh login. To trigger a password authentication event, run `su $(whoami) -c exit`. #### Getting "encryption not enabled" on an ext4 filesystem. Getting this error on an ext4 filesystem usually means the filesystem has not been setup for encryption. The only other way to get this error is if filesystem encryption has been explicitly disabled in the kernel config. __IMPORTANT:__ Before enabling encryption on an ext4 filesystem __ALL__ of the following should be true: - Your filesystem is formatted as ext4. Other filesystems will have different ways of enabling encryption. - Your kernel page size (run `getconf PAGE_SIZE`) and your filesystem block size (run `tune2fs -l /dev/device | grep 'Block size'`) are the same. - You are ok with not being able to mount this filesystem with a v4.0 kernel or older. - Either you are __NOT__ using GRUB to boot directly off this filesystem, or you are using GRUB 2.04 or later. This is necessary because old versions of GRUB can't boot from ext4 filesystems that have the encryption feature enabled, even if none of the boot files are encrypted themselves. If, like most people, you have a separate `/boot` partition, you are fine. You are also fine if you are using the GRUB Debian package `2.02-2` or later (*not* a `2.02_beta*` version), including the version in Ubuntu 18.04 and later, since the patch to support encryption was backported. If any of the above is not true, __DO NOT ENABLE FILESYSTEM ENCRYPTION__. To turn on encryption for your filesystem, run ``` tune2fs -O encrypt /dev/device ``` Note that this does not actually encrypt any files. It just marks the filesystem as being allowed to contain encrypted files. To turn off encryption for your filesystem, first delete all encrypted files and directories, then run ``` fsck -fn /dev/device debugfs -w -R "feature -encrypt" /dev/device fsck -fn /dev/device ``` #### Getting "Operation not permitted" when moving files into an encrypted directory. This occurs when the kernel version is older than v5.1 and the source files are on the same filesystem and are either unencrypted or are in a different encrypted directory hierarchy. Solution: copy the files instead, e.g. with `cp`. `mv` works on kernels v5.1 and later, since those kernels return the correct error code to make `mv` fall back to a copy itself. __HOWEVER:__ in either case, it is important to realize that the original files may remain recoverable from free space on the disk after they are deleted. It's much better to keep all files encrypted from the very beginning. As a last resort, the `shred` program may be used to try to overwrite the original files, e.g.: ```shell cp file encrypted_dir/ shred -u file ``` However, `shred` isn't guaranteed to be effective on all filesystems and storage devices. ## Legal Copyright 2017 Google Inc. under the [Apache 2.0 License](https://www.apache.org/licenses/LICENSE-2.0); see the `LICENSE` file for more information. Author: Joe Richey This is not an official Google product. fscrypt-0.2.5/actions/000077500000000000000000000000001357032351700146565ustar00rootroot00000000000000fscrypt-0.2.5/actions/callback.go000066400000000000000000000117211357032351700167430ustar00rootroot00000000000000/* * callback.go - defines how the caller of an action function passes along a key * to be used in this package. * * 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 actions import ( "log" "github.com/pkg/errors" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" ) // ProtectorInfo is the information a caller will receive about a Protector // before they have to return the corresponding key. This is currently a // read-only view of metadata.ProtectorData. type ProtectorInfo struct { data *metadata.ProtectorData } // Descriptor is the Protector's descriptor used to uniquely identify it. func (pi *ProtectorInfo) Descriptor() string { return pi.data.GetProtectorDescriptor() } // Source indicates the type of the descriptor (how it should be unlocked). func (pi *ProtectorInfo) Source() metadata.SourceType { return pi.data.GetSource() } // Name is used to describe custom passphrase and raw key descriptors. func (pi *ProtectorInfo) Name() string { return pi.data.GetName() } // UID is used to identify the user for login passphrases. func (pi *ProtectorInfo) UID() int64 { return pi.data.GetUid() } // KeyFunc is passed to a function that will require some type of key. // The info parameter is provided so the callback knows which key to provide. // The retry parameter indicates that a previous key provided by this callback // was incorrect (this allows for user feedback like "incorrect passphrase"). // // For passphrase sources, the returned key should be a passphrase. For raw // sources, the returned key should be a 256-bit cryptographic key. Consumers // of the callback will wipe the returned key. An error returned by the callback // will be propagated back to the caller. type KeyFunc func(info ProtectorInfo, retry bool) (*crypto.Key, error) // getWrappingKey uses the provided callback to get the wrapping key // corresponding to the ProtectorInfo. This runs the passphrase hash for // passphrase sources or just relays the callback for raw sources. func getWrappingKey(info ProtectorInfo, keyFn KeyFunc, retry bool) (*crypto.Key, error) { // For raw key sources, we can just use the key directly. if info.Source() == metadata.SourceType_raw_key { return keyFn(info, retry) } // Run the passphrase hash for other sources. passphrase, err := keyFn(info, retry) if err != nil { return nil, err } defer passphrase.Wipe() log.Printf("running passphrase hash for protector %s", info.Descriptor()) return crypto.PassphraseHash(passphrase, info.data.Salt, info.data.Costs) } // unwrapProtectorKey uses the provided callback and ProtectorInfo to return // the unwrapped protector key. This will repeatedly call keyFn to get the // wrapping key until the correct key is returned by the callback or the // callback returns an error. func unwrapProtectorKey(info ProtectorInfo, keyFn KeyFunc) (*crypto.Key, error) { retry := false for { wrappingKey, err := getWrappingKey(info, keyFn, retry) if err != nil { return nil, err } protectorKey, err := crypto.Unwrap(wrappingKey, info.data.WrappedKey) wrappingKey.Wipe() switch errors.Cause(err) { case nil: log.Printf("valid wrapping key for protector %s", info.Descriptor()) return protectorKey, nil case crypto.ErrBadAuth: // After the first failure, we let the callback know we are retrying. log.Printf("invalid wrapping key for protector %s", info.Descriptor()) retry = true continue default: return nil, err } } } // ProtectorOption is information about a protector relative to a Policy. type ProtectorOption struct { ProtectorInfo // LinkedMount is the mountpoint for a linked protector. It is nil if // the protector is not a linked protector (or there is a LoadError). LinkedMount *filesystem.Mount // LoadError is non-nil if there was an error in getting the data for // the protector. LoadError error } // OptionFunc is passed to a function that needs to unlock a Policy. // The callback is used to specify which protector should be used to unlock a // Policy. The descriptor indicates which Policy we are using, while the options // correspond to the valid Protectors protecting the Policy. // // The OptionFunc should either return a valid index into options, which // corresponds to the desired protector, or an error (which will be propagated // back to the caller). type OptionFunc func(policyDescriptor string, options []*ProtectorOption) (int, error) fscrypt-0.2.5/actions/config.go000066400000000000000000000205031357032351700164520ustar00rootroot00000000000000/* * config.go - Actions for creating a new config file, which includes new * hashing costs and the config file's location. * * 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 actions import ( "bytes" "log" "os" "runtime" "time" "github.com/pkg/errors" "golang.org/x/sys/unix" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/util" ) // LegacyConfig indicates that keys should be inserted into the keyring with the // legacy service prefixes. Needed for kernels before v4.8. const LegacyConfig = "legacy" // ConfigFileLocation is the location of fscrypt's global settings. This can be // overridden by the user of this package. var ConfigFileLocation = "/etc/fscrypt.conf" const ( // Permissions of the config file (global readable) configPermissions = 0644 // Config file should be created for writing and not already exist createFlags = os.O_CREATE | os.O_WRONLY | os.O_EXCL // 128 MiB is a large enough amount of memory to make the password hash // very difficult to brute force on specialized hardware, but small // enough to work on most GNU/Linux systems. maxMemoryBytes = 128 * 1024 * 1024 ) var ( timingPassphrase = []byte("I am a fake passphrase") timingSalt = bytes.Repeat([]byte{42}, metadata.SaltLen) ) // CreateConfigFile creates a new config file at the appropriate location with // the appropriate hashing costs and encryption parameters. This creation is // configurable in two ways. First, a time target must be specified. This target // will determine the hashing costs, by picking parameters that make the hashing // take as long as the specified target. Second, the config can include the // legacy option, which is needed for systems with kernels older than v4.8. func CreateConfigFile(target time.Duration, useLegacy bool) error { // Create the config file before computing the hashing costs, so we fail // immediately if the program has insufficient permissions. configFile, err := os.OpenFile(ConfigFileLocation, createFlags, configPermissions) switch { case os.IsExist(err): return ErrConfigFileExists case err != nil: return err } defer configFile.Close() config := &metadata.Config{ Source: metadata.DefaultSource, Options: metadata.DefaultOptions, } if useLegacy { config.Compatibility = LegacyConfig log.Printf("Using %q compatibility option\n", LegacyConfig) } if config.HashCosts, err = getHashingCosts(target); err != nil { return err } log.Printf("Creating config at %q with %v\n", ConfigFileLocation, config) return metadata.WriteConfig(config, configFile) } // getConfig returns the current configuration struct. Any fields not specified // in the config file use the system defaults. An error is returned if the // config file hasn't been setup with CreateConfigFile yet or the config // contains invalid data. func getConfig() (*metadata.Config, error) { configFile, err := os.Open(ConfigFileLocation) switch { case os.IsNotExist(err): return nil, ErrNoConfigFile case err != nil: return nil, err } defer configFile.Close() log.Printf("Reading config from %q\n", ConfigFileLocation) config, err := metadata.ReadConfig(configFile) if err != nil { return nil, errors.Wrap(ErrBadConfigFile, err.Error()) } // Use system defaults if not specified if config.Source == metadata.SourceType_default { config.Source = metadata.DefaultSource log.Printf("Falling back to source of %q", config.Source.String()) } if config.Options.Padding == 0 { config.Options.Padding = metadata.DefaultOptions.Padding log.Printf("Falling back to padding of %d", config.Options.Padding) } if config.Options.Contents == metadata.EncryptionOptions_default { config.Options.Contents = metadata.DefaultOptions.Contents log.Printf("Falling back to contents mode of %q", config.Options.Contents) } if config.Options.Filenames == metadata.EncryptionOptions_default { config.Options.Filenames = metadata.DefaultOptions.Filenames log.Printf("Falling back to filenames mode of %q", config.Options.Filenames) } if err := config.CheckValidity(); err != nil { return nil, errors.Wrap(ErrBadConfigFile, err.Error()) } return config, nil } // getHashingCosts returns hashing costs so that hashing a password will take // approximately the target time. This is done using the total amount of RAM, // the number of CPUs present, and by running the passphrase hash many times. func getHashingCosts(target time.Duration) (*metadata.HashingCosts, error) { log.Printf("Finding hashing costs that take %v\n", target) // Start out with the minimal possible costs that use all the CPUs. nCPUs := int64(runtime.NumCPU()) costs := &metadata.HashingCosts{ Time: 1, Memory: 8 * nCPUs, Parallelism: nCPUs, } // If even the minimal costs are not fast enough, just return the // minimal costs and log a warning. t, err := timeHashingCosts(costs) if err != nil { return nil, err } log.Printf("Min Costs={%v}\t-> %v\n", costs, t) if t > target { log.Printf("time exceeded the target of %v.\n", target) return costs, nil } // Now we start doubling the costs until we reach the target. memoryKiBLimit := memoryBytesLimit() / 1024 for { // Store a copy of the previous costs costsPrev := *costs tPrev := t // Double the memory up to the max, then double the time. if costs.Memory < memoryKiBLimit { costs.Memory = util.MinInt64(2*costs.Memory, memoryKiBLimit) } else { costs.Time *= 2 } // If our hashing failed, return the last good set of costs. if t, err = timeHashingCosts(costs); err != nil { log.Printf("Hashing with costs={%v} failed: %v\n", costs, err) return &costsPrev, nil } log.Printf("Costs={%v}\t-> %v\n", costs, t) // If we have reached the target time, we return a set of costs // based on the linear interpolation between the last two times. if t >= target { f := float64(target-tPrev) / float64(t-tPrev) return &metadata.HashingCosts{ Time: betweenCosts(costsPrev.Time, costs.Time, f), Memory: betweenCosts(costsPrev.Memory, costs.Memory, f), Parallelism: costs.Parallelism, }, nil } } } // memoryBytesLimit returns the maximum amount of memory we will use for // passphrase hashing. This will never be more than a reasonable maximum (for // compatibility) or half the available system RAM. func memoryBytesLimit() int64 { // The sysinfo syscall only fails if given a bad address var info unix.Sysinfo_t err := unix.Sysinfo(&info) util.NeverError(err) totalRAMBytes := int64(info.Totalram) return util.MinInt64(totalRAMBytes/2, maxMemoryBytes) } // betweenCosts returns a cost between a and b. Specifically, it returns the // floor of a + f*(b-a). This way, f=0 returns a and f=1 returns b. func betweenCosts(a, b int64, f float64) int64 { return a + int64(f*float64(b-a)) } // timeHashingCosts runs the passphrase hash with the specified costs and // returns the time it takes to hash the passphrase. func timeHashingCosts(costs *metadata.HashingCosts) (time.Duration, error) { passphrase, err := crypto.NewKeyFromReader(bytes.NewReader(timingPassphrase)) if err != nil { return 0, err } defer passphrase.Wipe() // Be sure to measure CPU time, not wall time (time.Now) begin := cpuTimeInNanoseconds() hash, err := crypto.PassphraseHash(passphrase, timingSalt, costs) if err == nil { hash.Wipe() } end := cpuTimeInNanoseconds() return time.Duration((end - begin) / costs.Parallelism), nil } // cpuTimeInNanoseconds returns the nanosecond count based on the process's CPU usage. // This number has no absolute meaning, only relative meaning to other calls. func cpuTimeInNanoseconds() int64 { var ts unix.Timespec err := unix.ClockGettime(unix.CLOCK_PROCESS_CPUTIME_ID, &ts) // ClockGettime fails if given a bad address or on a VERY old system. util.NeverError(err) return unix.TimespecToNsec(ts) } fscrypt-0.2.5/actions/context.go000066400000000000000000000141151357032351700166730ustar00rootroot00000000000000/* * context.go - top-level interface to fscrypt packages * * 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 actions is the high-level interface to the fscrypt packages. The // functions here roughly correspond with commands for the tool in cmd/fscrypt. // All of the actions include a significant amount of logging, so that good // output can be provided for cmd/fscrypt's verbose mode. // The top-level actions currently include: // - Creating a new config file // - Creating a context on which to perform actions // - Creating, unlocking, and modifying Protectors // - Creating, unlocking, and modifying Policies package actions import ( "log" "os/user" "golang.org/x/sys/unix" "github.com/pkg/errors" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/util" ) // Errors relating to Config files or Config structures. var ( ErrNoConfigFile = errors.New("global config file does not exist") ErrBadConfigFile = errors.New("global config file has invalid data") ErrConfigFileExists = errors.New("global config file already exists") ErrBadConfig = errors.New("invalid Config structure provided") ErrLocked = errors.New("key needs to be unlocked first") ) // Context contains the necessary global state to perform most of fscrypt's // actions. type Context struct { // Config is the struct loaded from the global config file. It can be // modified after being loaded to customise parameters. Config *metadata.Config // Mount is the filesystem relative to which all Protectors and Policies // are added, edited, removed, and applied. Mount *filesystem.Mount // TargetUser is the user for which protectors are created and to whose // keyring policies are provisioned. TargetUser *user.User } // NewContextFromPath makes a context for the filesystem containing the // specified path and whose Config is loaded from the global config file. On // success, the Context contains a valid Config and Mount. The target defaults // to the current effective user if none is specified. func NewContextFromPath(path string, target *user.User) (*Context, error) { ctx, err := newContextFromUser(target) if err != nil { return nil, err } if ctx.Mount, err = filesystem.FindMount(path); err != nil { return nil, err } log.Printf("%s is on %s filesystem %q (%s)", path, ctx.Mount.Filesystem, ctx.Mount.Path, ctx.Mount.Device) return ctx, nil } // NewContextFromMountpoint makes a context for the filesystem at the specified // mountpoint and whose Config is loaded from the global config file. On // success, the Context contains a valid Config and Mount. The target defaults // to the current effective user if none is specified. func NewContextFromMountpoint(mountpoint string, target *user.User) (*Context, error) { ctx, err := newContextFromUser(target) if err != nil { return nil, err } if ctx.Mount, err = filesystem.GetMount(mountpoint); err != nil { return nil, err } log.Printf("found %s filesystem %q (%s)", ctx.Mount.Filesystem, ctx.Mount.Path, ctx.Mount.Device) return ctx, nil } // newContextFromUser makes a context with the corresponding target user, and // whose Config is loaded from the global config file. If the target is nil, the // effective user is used. func newContextFromUser(target *user.User) (*Context, error) { var err error if target == nil { if target, err = util.EffectiveUser(); err != nil { return nil, err } } ctx := &Context{TargetUser: target} if ctx.Config, err = getConfig(); err != nil { return nil, err } log.Printf("creating context for %q", target.Username) return ctx, nil } // checkContext verifies that the context contains a valid config and a mount // which is being used with fscrypt. func (ctx *Context) checkContext() error { if err := ctx.Config.CheckValidity(); err != nil { return errors.Wrap(ErrBadConfig, err.Error()) } return ctx.Mount.CheckSetup() } // getService returns the keyring service for this context. We use the presence // of the LegacyConfig flag to determine if we should use the legacy services. // For ext4 systems before v4.8 and f2fs systems before v4.6, filesystem // specific services must be used (these legacy services will still work with // later kernels). func (ctx *Context) getService() string { // For legacy configurations, we may need non-standard services if ctx.Config.HasCompatibilityOption(LegacyConfig) { switch ctx.Mount.Filesystem { case "ext4", "f2fs": return ctx.Mount.Filesystem + ":" } } return unix.FS_KEY_DESC_PREFIX } // getProtectorOption returns the ProtectorOption for the protector on the // context's mountpoint with the specified descriptor. func (ctx *Context) getProtectorOption(protectorDescriptor string) *ProtectorOption { mnt, data, err := ctx.Mount.GetProtector(protectorDescriptor) if err != nil { return &ProtectorOption{ProtectorInfo{}, nil, err} } info := ProtectorInfo{data} // No linked path if on the same mountpoint if mnt == ctx.Mount { return &ProtectorOption{info, nil, nil} } return &ProtectorOption{info, mnt, nil} } // ProtectorOptions creates a slice of all the options for all of the Protectors // on the Context's mountpoint. func (ctx *Context) ProtectorOptions() ([]*ProtectorOption, error) { if err := ctx.checkContext(); err != nil { return nil, err } descriptors, err := ctx.Mount.ListProtectors() if err != nil { return nil, err } options := make([]*ProtectorOption, len(descriptors)) for i, descriptor := range descriptors { options[i] = ctx.getProtectorOption(descriptor) } return options, nil } fscrypt-0.2.5/actions/context_test.go000066400000000000000000000046231357032351700177350ustar00rootroot00000000000000/* * context_test.go - tests for creating new contexts * * 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 actions import ( "fmt" "log" "os" "path/filepath" "testing" "time" "github.com/google/fscrypt/util" "github.com/pkg/errors" ) const testTime = 10 * time.Millisecond // holds the context we will use throughout the actions tests var testContext *Context // Makes a context using the testing locations for the filesystem and // configuration file. func setupContext() (ctx *Context, err error) { mountpoint, err := util.TestRoot() if err != nil { return nil, err } ConfigFileLocation = filepath.Join(mountpoint, "test.conf") // Should not be able to setup without a config file if badCtx, badCtxErr := NewContextFromMountpoint(mountpoint, nil); badCtxErr == nil { badCtx.Mount.RemoveAllMetadata() return nil, fmt.Errorf("created context at %q without config file", badCtx.Mount.Path) } if err = CreateConfigFile(testTime, true); err != nil { return nil, err } defer func() { if err != nil { os.RemoveAll(ConfigFileLocation) } }() ctx, err = NewContextFromMountpoint(mountpoint, nil) if err != nil { return nil, err } return ctx, ctx.Mount.Setup() } // Cleans up the testing config file and testing filesystem data. func cleaupContext(ctx *Context) error { err1 := os.RemoveAll(ConfigFileLocation) err2 := ctx.Mount.RemoveAllMetadata() if err1 != nil { return err1 } return err2 } func TestMain(m *testing.M) { log.SetFlags(log.LstdFlags | log.Lmicroseconds) var err error testContext, err = setupContext() if err != nil { fmt.Println(err) if errors.Cause(err) != util.ErrSkipIntegration { os.Exit(1) } os.Exit(0) } returnCode := m.Run() err = cleaupContext(testContext) if err != nil { fmt.Printf("cleanupContext() = %v\n", err) os.Exit(1) } os.Exit(returnCode) } fscrypt-0.2.5/actions/hashing_test.go000066400000000000000000000036241357032351700176720ustar00rootroot00000000000000/* * hashing_test.go - tests for computing and benchmarking hashing costs * * 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 actions import ( "io/ioutil" "log" "testing" "time" ) // Tests that we can find valid hashing costs for various time targets and the // estimations are somewhat close to the targets. func TestCostsSearch(t *testing.T) { for _, target := range []time.Duration{ 100 * time.Millisecond, 200 * time.Millisecond, 500 * time.Millisecond, } { costs, err := getHashingCosts(target) if err != nil { t.Error(err) } actual, err := timeHashingCosts(costs) if err != nil { t.Error(err) } if actual*3 < target { t.Errorf("actual=%v is too small (target=%v)", actual, target) } if target*3 < actual { t.Errorf("actual=%v is too big (target=%v)", actual, target) } } } func benchmarkCostsSearch(b *testing.B, target time.Duration) { // Disable logging for benchmarks log.SetOutput(ioutil.Discard) for i := 0; i < b.N; i++ { _, err := getHashingCosts(target) if err != nil { b.Fatal(err) } } } func BenchmarkCostsSearch10ms(b *testing.B) { benchmarkCostsSearch(b, 10*time.Millisecond) } func BenchmarkCostsSearch100ms(b *testing.B) { benchmarkCostsSearch(b, 100*time.Millisecond) } func BenchmarkCostsSearch1s(b *testing.B) { benchmarkCostsSearch(b, time.Second) } fscrypt-0.2.5/actions/policy.go000066400000000000000000000346071357032351700165160ustar00rootroot00000000000000/* * policy.go - functions for dealing with policies * * 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 actions import ( "fmt" "log" "github.com/golang/protobuf/proto" "github.com/pkg/errors" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) // Errors relating to Policies var ( ErrMissingPolicyMetadata = util.SystemError("missing policy metadata for encrypted directory") ErrPolicyMetadataMismatch = util.SystemError("inconsistent metadata between filesystem and directory") ErrDifferentFilesystem = errors.New("policies may only protect files on the same filesystem") ErrOnlyProtector = errors.New("cannot remove the only protector for a policy") ErrAlreadyProtected = errors.New("policy already protected by protector") ErrNotProtected = errors.New("policy not protected by protector") ) // PurgeAllPolicies removes all policy keys on the filesystem from the kernel // keyring. In order for this removal to have an effect, the filesystem should // also be unmounted. func PurgeAllPolicies(ctx *Context) error { if err := ctx.checkContext(); err != nil { return err } policies, err := ctx.Mount.ListPolicies() if err != nil { return err } for _, policyDescriptor := range policies { service := ctx.getService() err = security.RemoveKey(service+policyDescriptor, ctx.TargetUser) switch errors.Cause(err) { case nil, security.ErrKeySearch: // We don't care if the key has already been removed default: return err } } return nil } // Policy represents an unlocked policy, so it contains the PolicyData as well // as the actual protector key. These unlocked Polices can then be applied to a // directory, or have their key material inserted into the keyring (which will // allow encrypted files to be accessed). As with the key struct, a Policy // should be wiped after use. type Policy struct { Context *Context data *metadata.PolicyData key *crypto.Key created bool } // CreatePolicy creates a Policy protected by given Protector and stores the // appropriate data on the filesystem. On error, no data is changed on the // filesystem. func CreatePolicy(ctx *Context, protector *Protector) (*Policy, error) { if err := ctx.checkContext(); err != nil { return nil, err } // Randomly create the underlying policy key (and wipe if we fail) key, err := crypto.NewRandomKey(metadata.PolicyKeyLen) if err != nil { return nil, err } policy := &Policy{ Context: ctx, data: &metadata.PolicyData{ Options: ctx.Config.Options, KeyDescriptor: crypto.ComputeDescriptor(key), }, key: key, created: true, } if err = policy.AddProtector(protector); err != nil { policy.Lock() return nil, err } return policy, nil } // GetPolicy retrieves a locked policy with a specific descriptor. The Policy is // still locked in this case, so it must be unlocked before using certain // methods. func GetPolicy(ctx *Context, descriptor string) (*Policy, error) { if err := ctx.checkContext(); err != nil { return nil, err } data, err := ctx.Mount.GetPolicy(descriptor) if err != nil { return nil, err } log.Printf("got data for %s from %q", descriptor, ctx.Mount.Path) return &Policy{Context: ctx, data: data}, nil } // GetPolicyFromPath returns the locked policy descriptor for a file on the // filesystem. The Policy is still locked in this case, so it must be unlocked // before using certain methods. An error is returned if the metadata is // inconsistent or the path is not encrypted. func GetPolicyFromPath(ctx *Context, path string) (*Policy, error) { if err := ctx.checkContext(); err != nil { return nil, err } // We double check that the options agree for both the data we get from // the path, and the data we get from the mountpoint. pathData, err := metadata.GetPolicy(path) if err != nil { return nil, err } descriptor := pathData.KeyDescriptor log.Printf("found policy %s for %q", descriptor, path) mountData, err := ctx.Mount.GetPolicy(descriptor) if err != nil { log.Printf("getting policy metadata: %v", err) return nil, errors.Wrap(ErrMissingPolicyMetadata, path) } log.Printf("found data for policy %s on %q", descriptor, ctx.Mount.Path) if !proto.Equal(pathData.Options, mountData.Options) { log.Printf("options from path: %+v", pathData.Options) log.Printf("options from mount: %+v", mountData.Options) return nil, errors.Wrapf(ErrPolicyMetadataMismatch, "policy %s", descriptor) } log.Print("data from filesystem and path agree") return &Policy{Context: ctx, data: mountData}, nil } // ProtectorOptions creates a slice of ProtectorOptions for the protectors // protecting this policy. func (policy *Policy) ProtectorOptions() []*ProtectorOption { options := make([]*ProtectorOption, len(policy.data.WrappedPolicyKeys)) for i, wrappedPolicyKey := range policy.data.WrappedPolicyKeys { options[i] = policy.Context.getProtectorOption(wrappedPolicyKey.ProtectorDescriptor) } return options } // ProtectorDescriptors creates a slice of the Protector descriptors for the // protectors protecting this policy. func (policy *Policy) ProtectorDescriptors() []string { descriptors := make([]string, len(policy.data.WrappedPolicyKeys)) for i, wrappedPolicyKey := range policy.data.WrappedPolicyKeys { descriptors[i] = wrappedPolicyKey.ProtectorDescriptor } return descriptors } // Descriptor returns the key descriptor for this policy. func (policy *Policy) Descriptor() string { return policy.data.KeyDescriptor } // Description returns the description that will be used when the key for this // Policy is inserted into the keyring func (policy *Policy) Description() string { return policy.Context.getService() + policy.Descriptor() } // Options returns the encryption options of this policy. func (policy *Policy) Options() *metadata.EncryptionOptions { return policy.data.Options } // Destroy removes a policy from the filesystem. The internal key should still // be wiped with Lock(). func (policy *Policy) Destroy() error { return policy.Context.Mount.RemovePolicy(policy.Descriptor()) } // Revert destroys a policy if it was created, but does nothing if it was just // queried from the filesystem. func (policy *Policy) Revert() error { if !policy.created { return nil } return policy.Destroy() } func (policy *Policy) String() string { return fmt.Sprintf("Policy: %s\nMountpoint: %s\nOptions: %v\nProtectors:%+v", policy.Descriptor(), policy.Context.Mount, policy.data.Options, policy.ProtectorDescriptors()) } // Unlock unwraps the Policy's internal key. As a Protector is needed to unlock // the Policy, callbacks to select the Policy and get the key are needed. This // method will retry the keyFn as necessary to get the correct key for the // selected protector. Does nothing if policy is already unlocked. func (policy *Policy) Unlock(optionFn OptionFunc, keyFn KeyFunc) error { if policy.key != nil { return nil } options := policy.ProtectorOptions() // The OptionFunc indicates which option and wrapped key we should use. idx, err := optionFn(policy.Descriptor(), options) if err != nil { return err } option := options[idx] if option.LoadError != nil { return option.LoadError } log.Printf("protector %s selected in callback", option.Descriptor()) protectorKey, err := unwrapProtectorKey(option.ProtectorInfo, keyFn) if err != nil { return err } defer protectorKey.Wipe() log.Printf("unwrapping policy %s with protector", policy.Descriptor()) wrappedPolicyKey := policy.data.WrappedPolicyKeys[idx].WrappedKey policy.key, err = crypto.Unwrap(protectorKey, wrappedPolicyKey) return err } // UnlockWithProtector uses an unlocked Protector to unlock a policy. An error // is returned if the Protector is not yet unlocked or does not protect the // policy. Does nothing if policy is already unlocked. func (policy *Policy) UnlockWithProtector(protector *Protector) error { if policy.key != nil { return nil } if protector.key == nil { return ErrLocked } idx, ok := policy.findWrappedKeyIndex(protector.Descriptor()) if !ok { return ErrNotProtected } var err error wrappedPolicyKey := policy.data.WrappedPolicyKeys[idx].WrappedKey policy.key, err = crypto.Unwrap(protector.key, wrappedPolicyKey) return err } // Lock wipes a Policy's internal Key. It should always be called after using a // Policy. This is often done with a defer statement. There is no effect if // called multiple times. func (policy *Policy) Lock() error { err := policy.key.Wipe() policy.key = nil return err } // UsesProtector returns if the policy is protected with the protector func (policy *Policy) UsesProtector(protector *Protector) bool { _, ok := policy.findWrappedKeyIndex(protector.Descriptor()) return ok } // AddProtector updates the data that is wrapping the Policy Key so that the // provided Protector is now protecting the specified Policy. If an error is // returned, no data has been changed. If the policy and protector are on // different filesystems, a link will be created between them. The policy and // protector must both be unlocked. func (policy *Policy) AddProtector(protector *Protector) error { if policy.UsesProtector(protector) { return ErrAlreadyProtected } if policy.key == nil || protector.key == nil { return ErrLocked } // If the protector is on a different filesystem, we need to add a link // to it on the policy's filesystem. if policy.Context.Mount != protector.Context.Mount { log.Printf("policy on %s\n protector on %s\n", policy.Context.Mount, protector.Context.Mount) err := policy.Context.Mount.AddLinkedProtector( protector.Descriptor(), protector.Context.Mount) if err != nil { return err } } else { log.Printf("policy and protector both on %q", policy.Context.Mount) } // Create the wrapped policy key wrappedKey, err := crypto.Wrap(protector.key, policy.key) if err != nil { return err } // Append the wrapped key to the data policy.addKey(&metadata.WrappedPolicyKey{ ProtectorDescriptor: protector.Descriptor(), WrappedKey: wrappedKey, }) if err := policy.commitData(); err != nil { // revert the addition on failure policy.removeKey(len(policy.data.WrappedPolicyKeys) - 1) return err } return nil } // RemoveProtector updates the data that is wrapping the Policy Key so that the // provided Protector is no longer protecting the specified Policy. If an error // is returned, no data has been changed. Note that no protector links are // removed (in the case where the protector and policy are on different // filesystems). The policy and protector can be locked or unlocked. func (policy *Policy) RemoveProtector(protector *Protector) error { idx, ok := policy.findWrappedKeyIndex(protector.Descriptor()) if !ok { return ErrNotProtected } if len(policy.data.WrappedPolicyKeys) == 1 { return ErrOnlyProtector } // Remove the wrapped key from the data toRemove := policy.removeKey(idx) if err := policy.commitData(); err != nil { // revert the removal on failure (order is irrelevant) policy.addKey(toRemove) return err } return nil } // Apply sets the Policy on a specified directory. Currently we impose the // additional constraint that policies and the directories they are applied to // must reside on the same filesystem. func (policy *Policy) Apply(path string) error { if pathMount, err := filesystem.FindMount(path); err != nil { return err } else if pathMount != policy.Context.Mount { return ErrDifferentFilesystem } return metadata.SetPolicy(path, policy.data) } // IsProvisioned returns a boolean indicating if the policy has its key in the // keyring, meaning files and directories using this policy are accessible. func (policy *Policy) IsProvisioned() bool { _, err := security.FindKey(policy.Description(), policy.Context.TargetUser) return err == nil } // Provision inserts the Policy key into the kernel keyring. This allows reading // and writing of files encrypted with this directory. Requires unlocked Policy. func (policy *Policy) Provision() error { if policy.key == nil { return ErrLocked } return crypto.InsertPolicyKey(policy.key, policy.Description(), policy.Context.TargetUser) } // Deprovision removes the Policy key from the kernel keyring. This prevents // reading and writing to the directory once the caches are cleared. func (policy *Policy) Deprovision() error { return security.RemoveKey(policy.Description(), policy.Context.TargetUser) } // commitData writes the Policy's current data to the filesystem. func (policy *Policy) commitData() error { return policy.Context.Mount.AddPolicy(policy.data) } // findWrappedPolicyKey returns the index of the wrapped policy key // corresponding to this policy and protector. The returned bool is false if no // wrapped policy key corresponds to the specified protector, true otherwise. func (policy *Policy) findWrappedKeyIndex(protectorDescriptor string) (int, bool) { for idx, wrappedPolicyKey := range policy.data.WrappedPolicyKeys { if wrappedPolicyKey.ProtectorDescriptor == protectorDescriptor { return idx, true } } return 0, false } // addKey adds the wrapped policy key to end of the wrapped key data. func (policy *Policy) addKey(toAdd *metadata.WrappedPolicyKey) { policy.data.WrappedPolicyKeys = append(policy.data.WrappedPolicyKeys, toAdd) } // removeKey removes the wrapped policy key at the specified index. This // does not preserve the order of the wrapped policy key array. If no index is // specified the last key is removed. func (policy *Policy) removeKey(index int) *metadata.WrappedPolicyKey { lastIdx := len(policy.data.WrappedPolicyKeys) - 1 toRemove := policy.data.WrappedPolicyKeys[index] // See https://github.com/golang/go/wiki/SliceTricks policy.data.WrappedPolicyKeys[index] = policy.data.WrappedPolicyKeys[lastIdx] policy.data.WrappedPolicyKeys[lastIdx] = nil policy.data.WrappedPolicyKeys = policy.data.WrappedPolicyKeys[:lastIdx] return toRemove } fscrypt-0.2.5/actions/policy_test.go000066400000000000000000000114211357032351700175420ustar00rootroot00000000000000/* * policy_test.go - tests for creating and modifying policies * * 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 actions import ( "testing" "github.com/pkg/errors" ) // Makes a protector and policy func makeBoth() (*Protector, *Policy, error) { protector, err := CreateProtector(testContext, testProtectorName, goodCallback) if err != nil { return nil, nil, err } policy, err := CreatePolicy(testContext, protector) if err != nil { cleanupProtector(protector) return nil, nil, err } return protector, policy, nil } func cleanupProtector(protector *Protector) { protector.Lock() protector.Destroy() } func cleanupPolicy(policy *Policy) { policy.Lock() policy.Destroy() } // Tests that we can make a policy/protector pair func TestCreatePolicy(t *testing.T) { pro, pol, err := makeBoth() if err != nil { t.Error(err) } cleanupPolicy(pol) cleanupProtector(pro) } // Tests that we can add another protector to the policy func TestPolicyGoodAddProtector(t *testing.T) { pro1, pol, err := makeBoth() defer cleanupProtector(pro1) defer cleanupPolicy(pol) if err != nil { t.Fatal(err) } pro2, err := CreateProtector(testContext, testProtectorName2, goodCallback) if err != nil { t.Fatal(err) } defer cleanupProtector(pro2) err = pol.AddProtector(pro2) if err != nil { t.Error(err) } } // Tests that we cannot add a protector to a policy twice func TestPolicyBadAddProtector(t *testing.T) { pro, pol, err := makeBoth() defer cleanupProtector(pro) defer cleanupPolicy(pol) if err != nil { t.Fatal(err) } if pol.AddProtector(pro) == nil { t.Error("we should not be able to add the same protector twice") } } // Tests that we can remove a protector we added func TestPolicyGoodRemoveProtector(t *testing.T) { pro1, pol, err := makeBoth() defer cleanupProtector(pro1) defer cleanupPolicy(pol) if err != nil { t.Fatal(err) } pro2, err := CreateProtector(testContext, testProtectorName2, goodCallback) if err != nil { t.Fatal(err) } defer cleanupProtector(pro2) err = pol.AddProtector(pro2) if err != nil { t.Fatal(err) } err = pol.RemoveProtector(pro1) if err != nil { t.Error(err) } } // Tests various bad ways to remove protectors func TestPolicyBadRemoveProtector(t *testing.T) { pro1, pol, err := makeBoth() defer cleanupProtector(pro1) defer cleanupPolicy(pol) if err != nil { t.Fatal(err) } pro2, err := CreateProtector(testContext, testProtectorName2, goodCallback) if err != nil { t.Fatal(err) } defer cleanupProtector(pro2) if pol.RemoveProtector(pro2) == nil { t.Error("we should not be able to remove a protector we did not add") } if pol.RemoveProtector(pro1) == nil { t.Error("we should not be able to remove all the protectors from a policy") } } // Tests that policy can be unlocked with a callback. func TestPolicyUnlockWithCallback(t *testing.T) { // Our optionFunc just selects the first protector optionFn := func(policyDescriptor string, options []*ProtectorOption) (int, error) { return 0, nil } pro1, pol, err := makeBoth() defer cleanupProtector(pro1) defer cleanupPolicy(pol) if err != nil { t.Fatal(err) } if err := pol.Lock(); err != nil { t.Fatal(err) } if err := pol.Unlock(optionFn, goodCallback); err != nil { t.Error(err) } if err := pol.Lock(); err != nil { t.Error(err) } } // Tests that policy can be unlock with an unlocked protector. func TestPolicyUnlockWithProtector(t *testing.T) { pro1, pol, err := makeBoth() defer cleanupProtector(pro1) defer cleanupPolicy(pol) if err != nil { t.Fatal(err) } if err := pol.Lock(); err != nil { t.Fatal(err) } if err := pol.UnlockWithProtector(pro1); err != nil { t.Error(err) } if err := pol.Lock(); err != nil { t.Error(err) } } // Tests that locked protectors cannot unlock a policy. func TestPolicyUnlockWithLockedProtector(t *testing.T) { pro1, pol, err := makeBoth() defer cleanupProtector(pro1) defer cleanupPolicy(pol) if err != nil { t.Fatal(err) } if err := pol.Lock(); err != nil { t.Fatal(err) } if err := pro1.Lock(); err != nil { t.Fatal(err) } if err := pol.UnlockWithProtector(pro1); errors.Cause(err) != ErrLocked { t.Errorf("Expected a cause of %v got %v", ErrLocked, err) if err == nil { pol.Lock() } } } fscrypt-0.2.5/actions/protector.go000066400000000000000000000203741357032351700172340ustar00rootroot00000000000000/* * protector.go - functions for dealing with protectors * * 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 actions import ( "fmt" "log" "github.com/pkg/errors" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/util" ) // Errors relating to Protectors var ( ErrProtectorName = errors.New("login protectors do not need a name") ErrMissingProtectorName = errors.New("custom protectors must have a name") ErrDuplicateName = errors.New("protector with this name already exists") ErrDuplicateUID = errors.New("login protector for this user already exists") ) // checkForProtectorWithName returns an error if there is already a protector // on the filesystem with a specific name (or if we cannot read the necessary // data). func checkForProtectorWithName(ctx *Context, name string) error { options, err := ctx.ProtectorOptions() if err != nil { return err } for _, option := range options { if option.Name() == name { return errors.Wrapf(ErrDuplicateName, "name %q", name) } } return nil } // checkIfUserHasLoginProtector returns an error if there is already a login // protector on the filesystem for a specific user (or if we cannot read the // necessary data). func checkIfUserHasLoginProtector(ctx *Context, uid int64) error { options, err := ctx.ProtectorOptions() if err != nil { return err } for _, option := range options { if option.Source() == metadata.SourceType_pam_passphrase && option.UID() == uid { return errors.Wrapf(ErrDuplicateUID, "user %q", ctx.TargetUser.Username) } } return nil } // Protector represents an unlocked protector, so it contains the ProtectorData // as well as the actual protector key. These unlocked Protectors are necessary // to unlock policies and create new polices. As with the key struct, a // Protector should be wiped after use. type Protector struct { Context *Context data *metadata.ProtectorData key *crypto.Key created bool } // CreateProtector creates an unlocked protector with a given name (name only // needed for custom and raw protector types). The keyFn provided to create the // Protector key will only be called once. If an error is returned, no data has // been changed on the filesystem. func CreateProtector(ctx *Context, name string, keyFn KeyFunc) (*Protector, error) { if err := ctx.checkContext(); err != nil { return nil, err } // Sanity checks for names if ctx.Config.Source == metadata.SourceType_pam_passphrase { // login protectors don't need a name (we use the username instead) if name != "" { return nil, ErrProtectorName } } else { // non-login protectors need a name (so we can distinguish between them) if name == "" { return nil, ErrMissingProtectorName } // we don't want to duplicate naming if err := checkForProtectorWithName(ctx, name); err != nil { return nil, err } } var err error protector := &Protector{ Context: ctx, data: &metadata.ProtectorData{ Name: name, Source: ctx.Config.Source, }, created: true, } // Extra data is needed for some SourceTypes switch protector.data.Source { case metadata.SourceType_pam_passphrase: // As the pam passphrases are user specific, we also store the // UID for this kind of source. protector.data.Uid = int64(util.AtoiOrPanic(ctx.TargetUser.Uid)) // Make sure we aren't duplicating protectors if err = checkIfUserHasLoginProtector(ctx, protector.data.Uid); err != nil { return nil, err } fallthrough case metadata.SourceType_custom_passphrase: // Our passphrase sources need costs and a random salt. if protector.data.Salt, err = crypto.NewRandomBuffer(metadata.SaltLen); err != nil { return nil, err } protector.data.Costs = ctx.Config.HashCosts } // Randomly create the underlying protector key (and wipe if we fail) if protector.key, err = crypto.NewRandomKey(metadata.InternalKeyLen); err != nil { return nil, err } protector.data.ProtectorDescriptor = crypto.ComputeDescriptor(protector.key) if err = protector.Rewrap(keyFn); err != nil { protector.Lock() return nil, err } return protector, nil } // GetProtector retrieves a Protector with a specific descriptor. The Protector // is still locked in this case, so it must be unlocked before using certain // methods. func GetProtector(ctx *Context, descriptor string) (*Protector, error) { log.Printf("Getting protector %s", descriptor) err := ctx.checkContext() if err != nil { return nil, err } protector := &Protector{Context: ctx} protector.data, err = ctx.Mount.GetRegularProtector(descriptor) return protector, err } // GetProtectorFromOption retrieves a protector based on a protector option. // If the option had a load error, this function returns that error. The // Protector is still locked in this case, so it must be unlocked before using // certain methods. func GetProtectorFromOption(ctx *Context, option *ProtectorOption) (*Protector, error) { log.Printf("Getting protector %s from option", option.Descriptor()) if err := ctx.checkContext(); err != nil { return nil, err } if option.LoadError != nil { return nil, option.LoadError } // Replace the context if this is a linked protector if option.LinkedMount != nil { ctx = &Context{ctx.Config, option.LinkedMount, ctx.TargetUser} } return &Protector{Context: ctx, data: option.data}, nil } // Descriptor returns the protector descriptor. func (protector *Protector) Descriptor() string { return protector.data.ProtectorDescriptor } // Destroy removes a protector from the filesystem. The internal key should // still be wiped with Lock(). func (protector *Protector) Destroy() error { return protector.Context.Mount.RemoveProtector(protector.Descriptor()) } // Revert destroys a protector if it was created, but does nothing if it was // just queried from the filesystem. func (protector *Protector) Revert() error { if !protector.created { return nil } return protector.Destroy() } func (protector *Protector) String() string { return fmt.Sprintf("Protector: %s\nMountpoint: %s\nSource: %s\nName: %s\nCosts: %v\nUID: %d", protector.Descriptor(), protector.Context.Mount, protector.data.Source, protector.data.Name, protector.data.Costs, protector.data.Uid) } // Unlock unwraps the Protector's internal key. The keyFn provided to unwrap the // Protector key will be retried as necessary to get the correct key. Lock() // should be called after use. Does nothing if protector is already unlocked. func (protector *Protector) Unlock(keyFn KeyFunc) (err error) { if protector.key != nil { return } protector.key, err = unwrapProtectorKey(ProtectorInfo{protector.data}, keyFn) return } // Lock wipes a Protector's internal Key. It should always be called after using // an unlocked Protector. This is often done with a defer statement. There is // no effect if called multiple times. func (protector *Protector) Lock() error { err := protector.key.Wipe() protector.key = nil return err } // Rewrap updates the data that is wrapping the Protector Key. This is useful if // a user's password has changed, for example. The keyFn provided to rewrap // the Protector key will only be called once. Requires unlocked Protector. func (protector *Protector) Rewrap(keyFn KeyFunc) error { if protector.key == nil { return ErrLocked } wrappingKey, err := getWrappingKey(ProtectorInfo{protector.data}, keyFn, false) if err != nil { return err } // Revert change to wrapped key on failure oldWrappedKey := protector.data.WrappedKey defer func() { wrappingKey.Wipe() if err != nil { protector.data.WrappedKey = oldWrappedKey } }() if protector.data.WrappedKey, err = crypto.Wrap(wrappingKey, protector.key); err != nil { return err } return protector.Context.Mount.AddProtector(protector.data) } fscrypt-0.2.5/actions/protector_test.go000066400000000000000000000033661357032351700202750ustar00rootroot00000000000000/* * protector_test.go - tests for creating protectors * * 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 actions import ( "bytes" "testing" "github.com/pkg/errors" "github.com/google/fscrypt/crypto" ) const testProtectorName = "my favorite protector" const testProtectorName2 = testProtectorName + "2" var errCallback = errors.New("bad callback") func goodCallback(info ProtectorInfo, retry bool) (*crypto.Key, error) { return crypto.NewFixedLengthKeyFromReader(bytes.NewReader(timingPassphrase), len(timingPassphrase)) } func badCallback(info ProtectorInfo, retry bool) (*crypto.Key, error) { return nil, errCallback } // Tests that we can create a valid protector. func TestCreateProtector(t *testing.T) { p, err := CreateProtector(testContext, testProtectorName, goodCallback) if err != nil { t.Error(err) } else { p.Lock() p.Destroy() } } // Tests that a failure in the callback is relayed back to the caller. func TestBadCallback(t *testing.T) { p, err := CreateProtector(testContext, testProtectorName, badCallback) if err == nil { p.Lock() p.Destroy() } if err != errCallback { t.Error("callback error was not relayed back to caller") } } fscrypt-0.2.5/bin/000077500000000000000000000000001357032351700137665ustar00rootroot00000000000000fscrypt-0.2.5/bin/files-changed000077500000000000000000000013531357032351700164070ustar00rootroot00000000000000#!/usr/bin/env bash # Detect if any files have changed in the git repository. Output an appropriate # error message if they have changed. if [[ -n $(git status -s) ]]; then git diff --minimal HEAD echo echo "**************************************************" case "$1" in "proto") echo "* .pb.go files and .proto files are out of sync. *" echo "* Run \"make gen\" to generate them. *" ;; "format") echo "* C or Go files have incorrect formatting. *" echo "* Run \"make format\" to fix them. *" ;; *) echo "* Files have changed in this repository. *" ;; esac echo "**************************************************" git reset HEAD --hard exit 1 fifscrypt-0.2.5/cmd/000077500000000000000000000000001357032351700137615ustar00rootroot00000000000000fscrypt-0.2.5/cmd/fscrypt/000077500000000000000000000000001357032351700154535ustar00rootroot00000000000000fscrypt-0.2.5/cmd/fscrypt/commands.go000066400000000000000000000712501357032351700176100ustar00rootroot00000000000000/* * commands.go - Implementations of all of the fscrypt commands and subcommands. * This mostly just calls into the fscrypt/actions package. * * 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" "log" "os" "github.com/pkg/errors" "github.com/urfave/cli" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) // Setup is a command which can do global or per-filesystem initialization. var Setup = cli.Command{ Name: "setup", ArgsUsage: fmt.Sprintf("[%s]", mountpointArg), Usage: "perform global setup or filesystem setup", Description: fmt.Sprintf(`This command creates fscrypt's global config file or enables fscrypt on a filesystem. (1) When used without %[1]s, create the parameters in %[2]s. This is primarily used to configure the passphrase hashing parameters to the appropriate hardness (as determined by %[3]s). Being root is required to write the config file. (2) When used with %[1]s, enable fscrypt on %[1]s. This involves creating the necessary folders on the filesystem which will hold the metadata structures. Begin root may be required to create these folders.`, mountpointArg, actions.ConfigFileLocation, shortDisplay(timeTargetFlag)), Flags: []cli.Flag{timeTargetFlag, legacyFlag, forceFlag}, Action: setupAction, } func setupAction(c *cli.Context) error { var err error switch c.NArg() { case 0: // Case (1) - global setup err = createGlobalConfig(c.App.Writer, actions.ConfigFileLocation) case 1: // Case (2) - filesystem setup err = setupFilesystem(c.App.Writer, c.Args().Get(0)) default: return expectedArgsErr(c, 1, true) } if err != nil { return newExitError(c, err) } return nil } // Encrypt performs the functions of setupDirectory and Unlock in one command. var Encrypt = cli.Command{ Name: "encrypt", ArgsUsage: directoryArg, Usage: "enable filesystem encryption for a directory", Description: fmt.Sprintf(`This command enables filesystem encryption on %[1]s. This may involve creating a new policy (if one is not specified with %[2]s) or a new protector (if one is not specified with %[3]s). This command requires that the corresponding filesystem has been setup with "fscrypt setup %[4]s". By default, after %[1]s is setup, it is unlocked and can immediately be used.`, directoryArg, shortDisplay(policyFlag), shortDisplay(protectorFlag), mountpointArg), Flags: []cli.Flag{policyFlag, unlockWithFlag, protectorFlag, sourceFlag, userFlag, nameFlag, keyFileFlag, skipUnlockFlag}, Action: encryptAction, } func encryptAction(c *cli.Context) error { if c.NArg() != 1 { return expectedArgsErr(c, 1, false) } path := c.Args().Get(0) if err := encryptPath(path); err != nil { return newExitError(c, err) } // Most people expect that other users can't see their encrypted files // while they're unlocked, so change the directory's mode to 0700. if err := os.Chmod(path, 0700); err != nil { fmt.Fprintf(c.App.Writer, "Warning: unable to chmod %q to 0700 [%v]\n", path, err) // Continue on; don't consider this a fatal error. } if !skipUnlockFlag.Value { fmt.Fprintf(c.App.Writer, "%q is now encrypted, unlocked, and ready for use.\n", path) } else { fmt.Fprintf(c.App.Writer, "%q is now encrypted, but it is still locked.\n", path) fmt.Fprintln(c.App.Writer, `It can be unlocked with "fscrypt unlock".`) } return nil } // encryptPath sets up encryption on path and provisions the policy to the // keyring unless --skip-unlock is used. On failure, an error is returned, any // metadata creation is reverted, and the directory is unmodified. func encryptPath(path string) (err error) { target, err := parseUserFlag(!skipUnlockFlag.Value) if err != nil { return } ctx, err := actions.NewContextFromPath(path, target) if err != nil { return } if err = checkEncryptable(ctx, path); err != nil { return } var policy *actions.Policy if policyFlag.Value != "" { log.Printf("getting policy for %q", path) policy, err = getPolicyFromFlag(policyFlag.Value, ctx.TargetUser) } else { log.Printf("creating policy for %q", path) protector, created, protErr := selectOrCreateProtector(ctx) // Successfully created protector should be reverted on failure. if protErr != nil { return protErr } defer func() { protector.Lock() if err != nil && created { protector.Revert() } }() if err = protector.Unlock(existingKeyFn); err != nil { return } policy, err = actions.CreatePolicy(ctx, protector) } // Successfully created policy should be reverted on failure. if err != nil { return } defer func() { policy.Lock() if err != nil { policy.Deprovision() policy.Revert() } }() // Unlock() first, so if the Unlock() fails the directory isn't changed. if !skipUnlockFlag.Value { if err = policy.Unlock(optionFn, existingKeyFn); err != nil { return } if err = policy.Provision(); err != nil { return } } if err = policy.Apply(path); os.IsPermission(errors.Cause(err)) { // EACCES at this point indicates ownership issues. err = errors.Wrap(ErrBadOwners, path) } return } // checkEncryptable returns an error if the path cannot be encrypted. func checkEncryptable(ctx *actions.Context, path string) error { log.Printf("ensuring %s is an empty and readable directory", path) f, err := os.Open(path) if err != nil { return err } defer f.Close() switch names, err := f.Readdirnames(-1); { case err != nil: // Could not read directory (might not be a directory) log.Print(errors.Wrap(err, path)) return errors.Wrap(ErrNotEmptyDir, path) case len(names) > 0: log.Printf("directory %s is not empty", path) return errors.Wrap(ErrNotEmptyDir, path) } log.Printf("ensuring %s supports encryption and filesystem is using fscrypt", path) switch _, err := actions.GetPolicyFromPath(ctx, path); errors.Cause(err) { case metadata.ErrNotEncrypted: // We are not encrypted. Finally, we check that the filesystem // supports encryption return ctx.Mount.CheckSupport() case nil: // We are encrypted return errors.Wrap(metadata.ErrEncrypted, path) default: return err } } // selectOrCreateProtector uses user input (or flags) to either create a new // protector or select an existing one. The boolean return value is true if we // created a new protector. func selectOrCreateProtector(ctx *actions.Context) (*actions.Protector, bool, error) { if protectorFlag.Value != "" { protector, err := getProtectorFromFlag(protectorFlag.Value, ctx.TargetUser) return protector, false, err } options, err := expandedProtectorOptions(ctx) if err != nil { return nil, false, err } // Having no existing options to choose from or using creation-only // flags indicates we should make a new protector. if len(options) == 0 || nameFlag.Value != "" || sourceFlag.Value != "" { protector, err := createProtectorFromContext(ctx) return protector, true, err } shouldCreate, err := askQuestion("Should we create a new protector?", false) if err != nil { return nil, false, err } if shouldCreate { protector, err := createProtectorFromContext(ctx) return protector, true, err } log.Print("finding an existing protector to use") protector, err := selectExistingProtector(ctx, options) return protector, false, err } // Unlock takes an encrypted directory and unlocks it for reading and writing. var Unlock = cli.Command{ Name: "unlock", ArgsUsage: directoryArg, Usage: "unlock an encrypted directory", Description: fmt.Sprintf(`This command takes %s, a directory setup for use with fscrypt, and unlocks the directory by passing the appropriate key into the keyring. This requires unlocking one of the protectors protecting this directory (either by selecting a protector or specifying one with %s). This directory will be locked again upon reboot, or after running "fscrypt purge" and unmounting the corresponding filesystem.`, directoryArg, shortDisplay(unlockWithFlag)), Flags: []cli.Flag{unlockWithFlag, keyFileFlag, userFlag}, Action: unlockAction, } func unlockAction(c *cli.Context) error { if c.NArg() != 1 { return expectedArgsErr(c, 1, false) } target, err := parseUserFlag(true) if err != nil { return newExitError(c, err) } path := c.Args().Get(0) ctx, err := actions.NewContextFromPath(path, target) if err != nil { return newExitError(c, err) } log.Printf("performing sanity checks") // Ensure path is encrypted and filesystem is using fscrypt. policy, err := actions.GetPolicyFromPath(ctx, path) if err != nil { return newExitError(c, err) } // Check if directory is already unlocked if policy.IsProvisioned() { log.Printf("policy %s is already provisioned", policy.Descriptor()) return newExitError(c, errors.Wrapf(ErrPolicyUnlocked, path)) } if err := policy.Unlock(optionFn, existingKeyFn); err != nil { return newExitError(c, err) } defer policy.Lock() if err := policy.Provision(); err != nil { return newExitError(c, err) } fmt.Fprintf(c.App.Writer, "%q is now unlocked and ready for use.\n", path) return nil } // Purge removes all the policy keys from the keyring (also need unmount). var Purge = cli.Command{ Name: "purge", ArgsUsage: mountpointArg, Usage: "Remove a filesystem's keys", Description: fmt.Sprintf(`This command removes a user's policy keys for directories on %[1]s. This is intended to lock all files and directories encrypted by the user on %[1]s, in that unlocking them for reading will require providing a key again. However, there are four important things to note about this command: (1) When run with the default options, this command also clears the reclaimable dentries and inodes, so that the encrypted files and directories will no longer be visible. However, this requires root privileges. Note that any open file descriptors to plaintext data will not be affected by this command. (2) When run with %[2]s=false, the keyring is cleared and root permissions are not required, but recently accessed encrypted directories and files will remain cached for some time. Because of this, after purging a filesystem's keys in this manner, it is recommended to unmount the filesystem. (3) When run as root, this command removes the policy keys for all users. However, this will only work if the PAM module has been enabled. Otherwise, only root's keys may be removed. (4) Even after unmounting the filesystem or clearing the caches, the kernel may keep contents of files in memory. This means direct memory access (either though physical compromise or a kernel exploit) could compromise encrypted data. This weakness can be eliminated by cycling the power or mitigated by using page cache and slab cache poisoning.`, mountpointArg, shortDisplay(dropCachesFlag)), Flags: []cli.Flag{forceFlag, dropCachesFlag, userFlag}, Action: purgeAction, } func purgeAction(c *cli.Context) error { if c.NArg() != 1 { return expectedArgsErr(c, 1, false) } if dropCachesFlag.Value { if !util.IsUserRoot() { return newExitError(c, ErrDropCachesPerm) } } target, err := parseUserFlag(true) if err != nil { return newExitError(c, err) } mountpoint := c.Args().Get(0) ctx, err := actions.NewContextFromMountpoint(mountpoint, target) if err != nil { return newExitError(c, err) } question := fmt.Sprintf("Purge all policy keys from %q", ctx.Mount.Path) if dropCachesFlag.Value { question += " and drop global inode cache" } warning := "Encrypted data on this filesystem will be inaccessible until unlocked again!!" if err = askConfirmation(question+"?", false, warning); err != nil { return newExitError(c, err) } if err = actions.PurgeAllPolicies(ctx); err != nil { return newExitError(c, err) } fmt.Fprintf(c.App.Writer, "Policies purged for %q.\n", ctx.Mount.Path) if dropCachesFlag.Value { if err = security.DropFilesystemCache(); err != nil { return newExitError(c, err) } fmt.Fprintf(c.App.Writer, "Encrypted data removed from filesystem cache.\n") } else { fmt.Fprintf(c.App.Writer, "Filesystem %q should now be unmounted.\n", ctx.Mount.Path) } return nil } // Status is a command with three subcommands relating to printing out status. var Status = cli.Command{ Name: "status", ArgsUsage: fmt.Sprintf("[%s]", pathArg), Usage: "print the global, filesystem, or file status", Description: fmt.Sprintf(`This command prints out the global, per-filesystem, or per-file status. (1) When used without %[1]s, print all of the currently visible filesystems which support use with fscrypt. For each of the filesystems, this command also notes if they are actually being used by fscrypt. This command will fail if no there is no support for fscrypt anywhere on the system. (2) When %[1]s is a filesystem mountpoint, list information about all the policies and protectors which exist on %[1]s. This command will fail if %[1]s is not being used with fscrypt. For each policy, this command also notes if the policy is currently unlocked. (3) When %[1]s is just a normal path, print information about the policy being used on %[1]s and the protectors protecting this file or directory. This command will fail if %[1]s is not setup for encryption with fscrypt.`, pathArg), Action: statusAction, } func statusAction(c *cli.Context) error { var err error switch c.NArg() { case 0: // Case (1) - global status err = writeGlobalStatus(c.App.Writer) case 1: path := c.Args().Get(0) ctx, mntErr := actions.NewContextFromMountpoint(path, nil) switch errors.Cause(mntErr) { case nil: // Case (2) - mountpoint status err = writeFilesystemStatus(c.App.Writer, ctx) case filesystem.ErrNotAMountpoint: // Case (3) - file or directory status err = writePathStatus(c.App.Writer, path) default: err = mntErr } default: return expectedArgsErr(c, 1, true) } if err != nil { return newExitError(c, err) } return nil } // Metadata is a collection of commands for manipulating the metadata files. var Metadata = cli.Command{ Name: "metadata", Usage: "[ADVANCED] manipulate the policy or protector metadata", Description: `These commands allow a user to directly create, delete, or change the metadata files. It is important to note that using these commands, especially the destructive ones, can make files encrypted with fscrypt unavailable. For instance, deleting a policy effectively deletes all the contents of the corresponding directory. Some example use cases include: (1) Directly creating protectors and policies using the "create" subcommand. These can then be applied with "fscrypt encrypt". (2) Changing the passphrase for a passphrase protector using the "change-passphrase" subcommand. (3) Creating a policy protected with multiple protectors using the "create policy" and "add-protector-to-policy" subcommands. (4) Changing the protector protecting a policy using the "add-protector-to-policy" and "remove-protector-from-policy" subcommands.`, Subcommands: []cli.Command{createMetadata, destroyMetadata, changePassphrase, addProtectorToPolicy, removeProtectorFromPolicy, dumpMetadata}, } var createMetadata = cli.Command{ Name: "create", ArgsUsage: fmt.Sprintf("[protector | policy] %s", mountpointArg), Usage: "manually create new metadata on a filesystem", Subcommands: []cli.Command{createProtector, createPolicy}, } var createProtector = cli.Command{ Name: "protector", ArgsUsage: mountpointArg, Usage: "create a new protector on a filesystem", Description: fmt.Sprintf(`This command creates a new protector on %s that does not (yet) protect any policy. After creation, the user can use %s with "fscrypt encrypt" to protect a directory with this new protector. The creation process is identical to the first step of "fscrypt encrypt" when the user has requested to create a new passphrase. The user will be prompted for the source, name, and secret data for the new protector (when applicable). As with "fscrypt encrypt", these prompts can be disabled with the appropriate flags.`, mountpointArg, shortDisplay(protectorFlag)), Flags: []cli.Flag{sourceFlag, nameFlag, keyFileFlag, userFlag}, Action: createProtectorAction, } func createProtectorAction(c *cli.Context) error { if c.NArg() != 1 { return expectedArgsErr(c, 1, false) } target, err := parseUserFlag(false) if err != nil { return newExitError(c, err) } mountpoint := c.Args().Get(0) ctx, err := actions.NewContextFromMountpoint(mountpoint, target) if err != nil { return newExitError(c, err) } prompt := fmt.Sprintf("Create new protector on %q", ctx.Mount.Path) if err = askConfirmation(prompt, true, ""); err != nil { return newExitError(c, err) } protector, err := createProtectorFromContext(ctx) if err != nil { return newExitError(c, err) } protector.Lock() fmt.Fprintf(c.App.Writer, "Protector %s created on filesystem %q.\n", protector.Descriptor(), ctx.Mount.Path) return nil } var createPolicy = cli.Command{ Name: "policy", ArgsUsage: fmt.Sprintf("%s %s", mountpointArg, shortDisplay(protectorFlag)), Usage: "create a new protector on a filesystem", Description: fmt.Sprintf(`This command creates a new protector on %s that has not (yet) been applied to any directory. After creation, the user can use %s with "fscrypt encrypt" to encrypt a directory with this new policy. As all policies must be protected with at least one protector, this command requires specifying one with %s. To create a policy protected by many protectors, use this command and "fscrypt metadata add-protector-to-policy".`, mountpointArg, shortDisplay(policyFlag), shortDisplay(protectorFlag)), Flags: []cli.Flag{protectorFlag, keyFileFlag}, Action: createPolicyAction, } func createPolicyAction(c *cli.Context) error { if c.NArg() != 1 { return expectedArgsErr(c, 1, false) } ctx, err := actions.NewContextFromMountpoint(c.Args().Get(0), nil) if err != nil { return newExitError(c, err) } if err = checkRequiredFlags(c, []*stringFlag{protectorFlag}); err != nil { return err } protector, err := getProtectorFromFlag(protectorFlag.Value, ctx.TargetUser) if err != nil { return newExitError(c, err) } if err = protector.Unlock(existingKeyFn); err != nil { return newExitError(c, err) } defer protector.Lock() prompt := fmt.Sprintf("Create new policy on %q", ctx.Mount.Path) if err = askConfirmation(prompt, true, ""); err != nil { return newExitError(c, err) } policy, err := actions.CreatePolicy(ctx, protector) if err != nil { return newExitError(c, err) } policy.Lock() fmt.Fprintf(c.App.Writer, "Policy %s created on filesystem %q.\n", policy.Descriptor(), ctx.Mount.Path) return nil } var destroyMetadata = cli.Command{ Name: "destroy", ArgsUsage: fmt.Sprintf("[%s | %s | %s]", shortDisplay(protectorFlag), shortDisplay(policyFlag), mountpointArg), Usage: "delete a filesystem's, protector's, or policy's metadata", Description: fmt.Sprintf(`This command can be used to perform three different destructive operations. Note that in all of these cases, data will usually be lost, so use with care. (1) If used with %[1]s, this command deletes all the data associated with that protector. This means all directories protected with that protector will become PERMANENTLY inaccessible (unless the policies were protected by multiple protectors). (2) If used with %[2]s, this command deletes all the data associated with that policy. This means all directories (usually just one) using this policy will become PERMANENTLY inaccessible. (3) If used with %[3]s, all the metadata on that filesystem will be deleted, causing all directories on that filesystem using fscrypt to become PERMANENTLY inaccessible. To start using this directory again, "fscrypt setup %[3]s" will need to be rerun.`, shortDisplay(protectorFlag), shortDisplay(policyFlag), mountpointArg), Flags: []cli.Flag{protectorFlag, policyFlag, forceFlag}, Action: destroyMetadataAction, } func destroyMetadataAction(c *cli.Context) error { switch c.NArg() { case 0: switch { case protectorFlag.Value != "": // Case (1) - protector destroy protector, err := getProtectorFromFlag(protectorFlag.Value, nil) if err != nil { return newExitError(c, err) } prompt := fmt.Sprintf("Destroy protector %s on %q?", protector.Descriptor(), protector.Context.Mount.Path) warning := "All files protected only with this protector will be lost!!" if err := askConfirmation(prompt, false, warning); err != nil { return newExitError(c, err) } if err := protector.Destroy(); err != nil { return newExitError(c, err) } fmt.Fprintf(c.App.Writer, "Protector %s deleted from filesystem %q.\n", protector.Descriptor(), protector.Context.Mount.Path) case policyFlag.Value != "": // Case (2) - policy destroy policy, err := getPolicyFromFlag(policyFlag.Value, nil) if err != nil { return newExitError(c, err) } prompt := fmt.Sprintf("Destroy policy %s on %q?", policy.Descriptor(), policy.Context.Mount.Path) warning := "All files using this policy will be lost!!" if err := askConfirmation(prompt, false, warning); err != nil { return newExitError(c, err) } if err := policy.Destroy(); err != nil { return newExitError(c, err) } fmt.Fprintf(c.App.Writer, "Policy %s deleted from filesystem %q.\n", policy.Descriptor(), policy.Context.Mount.Path) default: message := fmt.Sprintf("Must specify one of: %s, %s, or %s", mountpointArg, shortDisplay(protectorFlag), shortDisplay(policyFlag)) return &usageError{c, message} } case 1: // Case (3) - mountpoint destroy path := c.Args().Get(0) ctx, err := actions.NewContextFromMountpoint(path, nil) if err != nil { return newExitError(c, err) } prompt := fmt.Sprintf("Destroy all the metadata on %q?", ctx.Mount.Path) warning := "All the encrypted files on this filesystem will be lost!!" if err := askConfirmation(prompt, false, warning); err != nil { return newExitError(c, err) } if err := ctx.Mount.RemoveAllMetadata(); err != nil { return newExitError(c, err) } fmt.Fprintf(c.App.Writer, "All metadata on %q deleted.\n", ctx.Mount.Path) default: return expectedArgsErr(c, 1, true) } return nil } var changePassphrase = cli.Command{ Name: "change-passphrase", ArgsUsage: shortDisplay(protectorFlag), Usage: "change the passphrase used for a protector", Description: `This command takes a specified passphrase protector and changes the corresponding passphrase. Note that this does not create or destroy any protectors.`, Flags: []cli.Flag{protectorFlag}, Action: changePassphraseAction, } func changePassphraseAction(c *cli.Context) error { if c.NArg() != 0 { return expectedArgsErr(c, 0, false) } if err := checkRequiredFlags(c, []*stringFlag{protectorFlag}); err != nil { return err } protector, err := getProtectorFromFlag(protectorFlag.Value, nil) if err != nil { return newExitError(c, err) } if err := protector.Unlock(oldExistingKeyFn); err != nil { return newExitError(c, err) } defer protector.Lock() if err := protector.Rewrap(newCreateKeyFn); err != nil { return newExitError(c, err) } fmt.Fprintf(c.App.Writer, "Passphrase for protector %s successfully changed.\n", protector.Descriptor()) return nil } var addProtectorToPolicy = cli.Command{ Name: "add-protector-to-policy", ArgsUsage: fmt.Sprintf("%s %s", shortDisplay(protectorFlag), shortDisplay(policyFlag)), Usage: "start protecting a policy with some protector", Description: `This command changes the specified policy to be protected with the specified protector. This means that any directories using this policy will now be accessible with this protector. This command will fail if the policy is already protected with this protector.`, Flags: []cli.Flag{protectorFlag, policyFlag, unlockWithFlag, keyFileFlag}, Action: addProtectorAction, } func addProtectorAction(c *cli.Context) error { if c.NArg() != 0 { return expectedArgsErr(c, 0, false) } if err := checkRequiredFlags(c, []*stringFlag{protectorFlag, policyFlag}); err != nil { return err } protector, err := getProtectorFromFlag(protectorFlag.Value, nil) if err != nil { return newExitError(c, err) } policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser) if err != nil { return newExitError(c, err) } // Sanity check before unlocking everything if err := policy.AddProtector(protector); errors.Cause(err) != actions.ErrLocked { return newExitError(c, err) } prompt := fmt.Sprintf("Protect policy %s with protector %s?", policy.Descriptor(), protector.Descriptor()) warning := "All files using this policy will be accessible with this protector!!" if err := askConfirmation(prompt, true, warning); err != nil { return newExitError(c, err) } if err := protector.Unlock(existingKeyFn); err != nil { return newExitError(c, err) } if err := policy.Unlock(optionFn, existingKeyFn); err != nil { return newExitError(c, err) } if err := policy.AddProtector(protector); err != nil { return newExitError(c, err) } fmt.Fprintf(c.App.Writer, "Protector %s now protecting policy %s.\n", protector.Descriptor(), policy.Descriptor()) return nil } var removeProtectorFromPolicy = cli.Command{ Name: "remove-protector-from-policy", ArgsUsage: fmt.Sprintf("%s %s", shortDisplay(protectorFlag), shortDisplay(policyFlag)), Usage: "stop protecting a policy with some protector", Description: `This command changes the specified policy to no longer be protected with the specified protector. This means that any directories using this policy will cannot be accessed with this protector. This command will fail if the policy not already protected with this protector or if it is the policy's only protector.`, Flags: []cli.Flag{protectorFlag, policyFlag, forceFlag}, Action: removeProtectorAction, } func removeProtectorAction(c *cli.Context) error { if c.NArg() != 0 { return expectedArgsErr(c, 0, false) } if err := checkRequiredFlags(c, []*stringFlag{protectorFlag, policyFlag}); err != nil { return err } // We do not need to unlock anything for this operation protector, err := getProtectorFromFlag(protectorFlag.Value, nil) if err != nil { return newExitError(c, err) } policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser) if err != nil { return newExitError(c, err) } prompt := fmt.Sprintf("Stop protecting policy %s with protector %s?", policy.Descriptor(), protector.Descriptor()) warning := "All files using this policy will NO LONGER be accessible with this protector!!" if err := askConfirmation(prompt, false, warning); err != nil { return newExitError(c, err) } if err := policy.RemoveProtector(protector); err != nil { return newExitError(c, err) } fmt.Fprintf(c.App.Writer, "Protector %s no longer protecting policy %s.\n", protector.Descriptor(), policy.Descriptor()) return nil } var dumpMetadata = cli.Command{ Name: "dump", ArgsUsage: fmt.Sprintf("[%s | %s]", shortDisplay(protectorFlag), shortDisplay(policyFlag)), Usage: "print debug data for a policy or protector", Description: fmt.Sprintf(`This commands dumps all of the debug data for a protector (if %s is used) or policy (if %s is used). This data includes the data pulled from the %q config file, the appropriate mountpoint data, and any options for the policy or hashing costs for the protector. Any cryptographic keys are wiped and are not printed out.`, shortDisplay(protectorFlag), shortDisplay(policyFlag), actions.ConfigFileLocation), Flags: []cli.Flag{protectorFlag, policyFlag}, Action: dumpMetadataAction, } func dumpMetadataAction(c *cli.Context) error { switch { case protectorFlag.Value != "": // Case (1) - protector print protector, err := getProtectorFromFlag(protectorFlag.Value, nil) if err != nil { return newExitError(c, err) } fmt.Fprintln(c.App.Writer, protector) case policyFlag.Value != "": // Case (2) - policy print policy, err := getPolicyFromFlag(policyFlag.Value, nil) if err != nil { return newExitError(c, err) } fmt.Fprintln(c.App.Writer, policy) default: message := fmt.Sprintf("Must specify one of: %s or %s", shortDisplay(protectorFlag), shortDisplay(policyFlag)) return &usageError{c, message} } return nil } fscrypt-0.2.5/cmd/fscrypt/errors.go000066400000000000000000000220571357032351700173240ustar00rootroot00000000000000/* * errors.go - File which contains common error handling code for fscrypt * commands. This includes handling for bad usage, invalid commands, and errors * from the other packages * * 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 ( "bytes" "fmt" "os" "path/filepath" "unicode/utf8" "github.com/pkg/errors" "github.com/urfave/cli" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) // failureExitCode is the value fscrypt will return on failure. const failureExitCode = 1 // Various errors used for the top level user interface var ( ErrCanceled = errors.New("operation canceled") ErrNoDestructiveOps = errors.New("operation would be destructive") ErrMaxPassphrase = util.SystemError("max passphrase length exceeded") ErrInvalidSource = errors.New("invalid source type") ErrPassphraseMismatch = errors.New("entered passphrases do not match") ErrSpecifyProtector = errors.New("multiple protectors available") ErrWrongKey = errors.New("incorrect key provided") ErrSpecifyKeyFile = errors.New("no key file specified") ErrKeyFileLength = errors.Errorf("key file must be %d bytes", metadata.InternalKeyLen) ErrAllLoadsFailed = errors.New("could not load any protectors") ErrMustBeRoot = errors.New("this command must be run as root") ErrPolicyUnlocked = errors.New("this file or directory is already unlocked") ErrBadOwners = errors.New("you do not own this directory") ErrNotEmptyDir = errors.New("not an empty directory") ErrNotPassphrase = errors.New("protector does not use a passphrase") ErrUnknownUser = errors.New("unknown user") ErrDropCachesPerm = errors.New("inode cache can only be dropped as root") ErrSpecifyUser = errors.New("user must be specified when run as root") ) var loadHelpText = fmt.Sprintf("You may need to mount a linked filesystem. Run with %s for more information.", shortDisplay(verboseFlag)) // getFullName returns the full name of the application or command being used. func getFullName(c *cli.Context) string { if c.Command.HelpName != "" { return c.Command.HelpName } return c.App.HelpName } // getErrorSuggestions returns a string containing suggestions about how to fix // an error. If no suggestion is necessary or available, return empty string. func getErrorSuggestions(err error) string { switch errors.Cause(err) { case filesystem.ErrNotSetup: return fmt.Sprintf(`Run "fscrypt setup %s" to use fscrypt on this filesystem.`, mountpointArg) case crypto.ErrKeyLock: return `Too much memory was requested to be locked in RAM. The current limit for this user can be checked with "ulimit -l". The limit can be modified by either changing the "memlock" item in /etc/security/limits.conf or by changing the "LimitMEMLOCK" value in systemd.` case metadata.ErrEncryptionNotSupported: return `Encryption for this type of filesystem is not supported on this kernel version.` case metadata.ErrEncryptionNotEnabled: return `Encryption is either disabled in the kernel config, or needs to be enabled for this filesystem. See the documentation on how to enable encryption on ext4 systems (and the risks of doing so).` case security.ErrSessionUserKeying: return `This is usually the result of a bad PAM configuration. Either correct the problem in your PAM stack, enable pam_keyinit.so, or run "keyctl link @u @s".` case security.ErrAccessUserKeyring: return fmt.Sprintf(`You can only use %s to access the user keyring of another user if you are running as root.`, shortDisplay(userFlag)) case actions.ErrBadConfigFile: return `Run "sudo fscrypt setup" to recreate the file.` case actions.ErrNoConfigFile: return `Run "sudo fscrypt setup" to create the file.` case actions.ErrMissingPolicyMetadata: return `This file or directory has either been encrypted with another tool (such as e4crypt) or the corresponding filesystem metadata has been deleted.` case actions.ErrPolicyMetadataMismatch: return `The metadata for this encrypted directory is in an inconsistent state. This most likely means the filesystem metadata is corrupted.` case actions.ErrMissingProtectorName: return fmt.Sprintf("Use %s to specify a protector name.", shortDisplay(nameFlag)) case ErrNoDestructiveOps: return fmt.Sprintf("Use %s to automatically run destructive operations.", shortDisplay(forceFlag)) case ErrSpecifyProtector: return fmt.Sprintf("Use %s to specify a protector.", shortDisplay(protectorFlag)) case ErrSpecifyKeyFile: return fmt.Sprintf("Use %s to specify a key file.", shortDisplay(keyFileFlag)) case ErrBadOwners: return `Encryption can only be setup on directories you own, even if you have write permission for the directory.` case ErrNotEmptyDir: return `Encryption can only be setup on empty directories; files cannot be encrypted in-place. Instead, encrypt an empty directory, copy the files into that encrypted directory, and securely delete the originals with "shred".` case ErrDropCachesPerm: return fmt.Sprintf(`Either this command should be run as root to properly clear the inode cache, or it should be run with %s=false (this may leave encrypted files and directories in an accessible state).`, shortDisplay(dropCachesFlag)) case ErrSpecifyUser: return fmt.Sprintf(`When running this command as root, you usually still want to provision/remove keys for a normal user's keyring and use a normal user's login passphrase as a protector (so the corresponding files will be accessible for that user). This can be done with %s. To use the root user's keyring or passphrase, use --%s=root.`, shortDisplay(userFlag), userFlag.GetName()) case ErrAllLoadsFailed: return loadHelpText default: return "" } } // newExitError creates a new error for a given context and normal error. The // returned error prepends the name of the relevant command and will make // fscrypt return a non-zero exit value. func newExitError(c *cli.Context, err error) error { // Prepend the full name and append suggestions (if any) fullNamePrefix := getFullName(c) + ": " message := fullNamePrefix + wrapText(err.Error(), utf8.RuneCountInString(fullNamePrefix)) if suggestion := getErrorSuggestions(err); suggestion != "" { message += "\n\n" + wrapText(suggestion, 0) } return cli.NewExitError(message, failureExitCode) } // usageError implements cli.ExitCoder to print the usage and return a non-zero // value. This error should be used when a command is used incorrectly. type usageError struct { c *cli.Context message string } func (u *usageError) Error() string { return fmt.Sprintf("%s: %s", getFullName(u.c), u.message) } // We get the help to print after the error by having it run right before the // application exits. This is very nasty, but there isn't a better way to do it // with the constraints of urfave/cli. func (u *usageError) ExitCode() int { // Redirect help output to a buffer, so we can customize it. buf := new(bytes.Buffer) oldWriter := u.c.App.Writer u.c.App.Writer = buf // Get the appropriate help if getFullName(u.c) == filepath.Base(os.Args[0]) { cli.ShowAppHelp(u.c) } else { cli.ShowCommandHelp(u.c, u.c.Command.Name) } // Remove first line from help and print it out buf.ReadBytes('\n') buf.WriteTo(oldWriter) u.c.App.Writer = oldWriter return failureExitCode } // expectedArgsErr creates a usage error for the incorrect number of arguments // being specified. atMost should be true only if any number of arguments from 0 // to expectedArgs would be acceptable. func expectedArgsErr(c *cli.Context, expectedArgs int, atMost bool) error { message := "expected " if atMost { message += "at most " } message += fmt.Sprintf("%s, got %s", pluralize(expectedArgs, "argument"), pluralize(c.NArg(), "argument")) return &usageError{c, message} } // onUsageError is a function handler for the application and each command. func onUsageError(c *cli.Context, err error, _ bool) error { return &usageError{c, err.Error()} } // checkRequiredFlags makes sure that all of the specified string flags have // been given nonempty values. Returns a usage error on failure. func checkRequiredFlags(c *cli.Context, flags []*stringFlag) error { for _, flag := range flags { if flag.Value == "" { message := fmt.Sprintf("required flag %s not provided", shortDisplay(flag)) return &usageError{c, message} } } return nil } fscrypt-0.2.5/cmd/fscrypt/flags.go000066400000000000000000000227271357032351700171100ustar00rootroot00000000000000/* * flags.go - File which contains all the flags used by the application. This * includes both global flags and command specific flags. When applicable, it * also includes the default values. * * 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 ( "flag" "fmt" "log" "os/user" "regexp" "strconv" "time" "github.com/urfave/cli" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) // We define the types boolFlag, durationFlag, and stringFlag here instead of // using those present in urfave/cli because we need them to conform to the // prettyFlag interface (in format.go). The Getters just get the corresponding // variables, String() just uses longDisplay, and Apply just sets the // corresponding type of flag. type boolFlag struct { Name string Usage string Default bool Value bool } func (b *boolFlag) GetName() string { return b.Name } func (b *boolFlag) GetArgName() string { return "" } func (b *boolFlag) GetUsage() string { return b.Usage } func (b *boolFlag) String() string { if !b.Default { return longDisplay(b) } return longDisplay(b, strconv.FormatBool(b.Default)) } func (b *boolFlag) Apply(set *flag.FlagSet) { set.BoolVar(&b.Value, b.Name, b.Default, b.Usage) } type durationFlag struct { Name string ArgName string Usage string Default time.Duration Value time.Duration } func (d *durationFlag) GetName() string { return d.Name } func (d *durationFlag) GetArgName() string { return d.ArgName } func (d *durationFlag) GetUsage() string { return d.Usage } func (d *durationFlag) String() string { if d.Default == 0 { return longDisplay(d) } return longDisplay(d, d.Value.String()) } func (d *durationFlag) Apply(set *flag.FlagSet) { set.DurationVar(&d.Value, d.Name, d.Default, d.Usage) } type stringFlag struct { Name string ArgName string Usage string Default string Value string } func (s *stringFlag) GetName() string { return s.Name } func (s *stringFlag) GetArgName() string { return s.ArgName } func (s *stringFlag) GetUsage() string { return s.Usage } func (s *stringFlag) String() string { if s.Default == "" { return longDisplay(s) } return longDisplay(s, strconv.Quote(s.Default)) } func (s *stringFlag) Apply(set *flag.FlagSet) { set.StringVar(&s.Value, s.Name, s.Default, s.Usage) } var ( // allFlags contains every defined flag (used for formatting). // UPDATE THIS ARRAY WHEN ADDING NEW FLAGS!!! // TODO(joerichey) add presubmit rule to enforce this allFlags = []prettyFlag{helpFlag, versionFlag, verboseFlag, quietFlag, forceFlag, legacyFlag, skipUnlockFlag, timeTargetFlag, sourceFlag, nameFlag, keyFileFlag, protectorFlag, unlockWithFlag, policyFlag} // universalFlags contains flags that should be on every command universalFlags = []cli.Flag{verboseFlag, quietFlag, helpFlag} ) // Bool flags: used to switch some behavior on or off var ( helpFlag = &boolFlag{ Name: "help", Usage: `Prints help screen for commands and subcommands.`, } versionFlag = &boolFlag{ Name: "version", Usage: `Prints version and license information.`, } verboseFlag = &boolFlag{ Name: "verbose", Usage: `Prints additional debug messages to standard output.`, } quietFlag = &boolFlag{ Name: "quiet", Usage: `Prints nothing to standard output except for errors. Selects the default for any options that would normally show a prompt.`, } forceFlag = &boolFlag{ Name: "force", Usage: fmt.Sprintf(`Suppresses all confirmation prompts and warnings, causing any action to automatically proceed. WARNING: This bypasses confirmations for protective operations, use with care.`), } legacyFlag = &boolFlag{ Name: "legacy", Usage: `Allow for support of older kernels with ext4 (before v4.8) and F2FS (before v4.6) filesystems.`, Default: true, } skipUnlockFlag = &boolFlag{ Name: "skip-unlock", Usage: `Leave the directory in a locked state after setup. "fscrypt unlock" will need to be run in order to use the directory.`, } dropCachesFlag = &boolFlag{ Name: "drop-caches", Usage: `After purging the keys from the keyring, drop the associated caches for the purge to take effect. Without this flag, cached encrypted files may still have their plaintext visible. Requires root privileges.`, Default: true, } ) // Option flags: used to specify options instead of being prompted for them var ( timeTargetFlag = &durationFlag{ Name: "time", ArgName: "TIME", Usage: `Set the global options so that passphrase hashing takes TIME long. TIME should be formatted as a sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "1.5s" or "2h45m". Valid time units are "ms", "s", "m", and "h".`, Default: 1 * time.Second, } sourceFlag = &stringFlag{ Name: "source", ArgName: "SOURCE", Usage: fmt.Sprintf(`New protectors will have type SOURCE. SOURCE can be one of pam_passphrase, custom_passphrase, or raw_key. If not specified, the user will be prompted for the source, with a default pulled from %s.`, actions.ConfigFileLocation), } nameFlag = &stringFlag{ Name: "name", ArgName: "PROTECTOR_NAME", Usage: `New custom_passphrase and raw_key protectors will be named PROTECTOR_NAME. If not specified, the user will be prompted for a name.`, } keyFileFlag = &stringFlag{ Name: "key", ArgName: "FILE", Usage: `Use the contents of FILE as the wrapping key when creating or unlocking raw_key protectors. FILE should be formatted as raw binary and should be exactly 32 bytes long.`, } userFlag = &stringFlag{ Name: "user", ArgName: "USERNAME", Usage: `Specify which user should be used for login passphrases or to which user's keyring keys should be provisioned.`, } protectorFlag = &stringFlag{ Name: "protector", ArgName: "MOUNTPOINT:ID", Usage: `Specify an existing protector on filesystem MOUNTPOINT with protector descriptor ID which should be used in the command.`, } unlockWithFlag = &stringFlag{ Name: "unlock-with", ArgName: "MOUNTPOINT:ID", Usage: `Specify an existing protector on filesystem MOUNTPOINT with protector descriptor ID which should be used to unlock a policy (usually specified with --policy). This flag is only useful if a policy is protected with multiple protectors. If not specified, the user will be prompted for a protector.`, } policyFlag = &stringFlag{ Name: "policy", ArgName: "MOUNTPOINT:ID", Usage: `Specify an existing policy on filesystem MOUNTPOINT with key descriptor ID which should be used in the command.`, } ) // The first group is optional and corresponds to the mountpoint. The second // group is required and corresponds to the descriptor. var idFlagRegex = regexp.MustCompile("^([[:print:]]+):([[:alnum:]]+)$") func matchMetadataFlag(flagValue string) (mountpoint, descriptor string, err error) { matches := idFlagRegex.FindStringSubmatch(flagValue) if matches == nil { return "", "", fmt.Errorf("flag value %q does not have format %s", flagValue, mountpointIDArg) } log.Printf("parsed flag: mountpoint=%q descriptor=%s", matches[1], matches[2]) return matches[1], matches[2], nil } // parseMetadataFlag takes the value of either protectorFlag or policyFlag // formatted as MOUNTPOINT:DESCRIPTOR, and returns a context for the mountpoint // and a string for the descriptor. func parseMetadataFlag(flagValue string, target *user.User) (*actions.Context, string, error) { mountpoint, descriptor, err := matchMetadataFlag(flagValue) if err != nil { return nil, "", err } ctx, err := actions.NewContextFromMountpoint(mountpoint, target) return ctx, descriptor, err } // getProtectorFromFlag gets an existing locked protector from protectorFlag. func getProtectorFromFlag(flagValue string, target *user.User) (*actions.Protector, error) { ctx, descriptor, err := parseMetadataFlag(flagValue, target) if err != nil { return nil, err } return actions.GetProtector(ctx, descriptor) } // getPolicyFromFlag gets an existing locked policy from policyFlag. func getPolicyFromFlag(flagValue string, target *user.User) (*actions.Policy, error) { ctx, descriptor, err := parseMetadataFlag(flagValue, target) if err != nil { return nil, err } return actions.GetPolicy(ctx, descriptor) } // parseUserFlag returns the user specified by userFlag or the current effective // user if the flag value is missing. If the effective user is root, however, a // user must specified in the flag. If checkKeyring is true, we also make sure // there are no problems accessing the user keyring. func parseUserFlag(checkKeyring bool) (targetUser *user.User, err error) { if userFlag.Value != "" { targetUser, err = user.Lookup(userFlag.Value) } else { if util.IsUserRoot() { return nil, ErrSpecifyUser } targetUser, err = util.EffectiveUser() } if err != nil { return nil, err } if checkKeyring { _, err = security.UserKeyringID(targetUser, true) } return targetUser, err } fscrypt-0.2.5/cmd/fscrypt/format.go000066400000000000000000000122151357032351700172730ustar00rootroot00000000000000/* * format.go - Contains all the functionality for formatting the command line * output. This includes formatting the description and flags so that the whole * text is <= LineLength characters. * * 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 ( "bytes" "fmt" "os" "regexp" "strings" "unicode/utf8" "github.com/urfave/cli" "golang.org/x/crypto/ssh/terminal" "github.com/google/fscrypt/util" ) var ( // lineLength is the maximum width of fscrypt's formatted output. It is // usually the width of the terminal. lineLength int fallbackLineLength = 80 // fallback is punch cards maxLineLength = 120 // IndentLength is the number spaces to indent by. indentLength = 2 // length of the longest shortDisplay for a flag maxShortDisplay int // how much the a flag's usage text needs to be moved over flagPaddingLength int ) // We use the init() function to compute our longest short display length. This // is then used to compute the formatting and padding strings. This ensures we // will always have room to display our flags, and the flag descriptions always // appear in the same place. func init() { for _, flag := range allFlags { displayLength := utf8.RuneCountInString(shortDisplay(flag)) if displayLength > maxShortDisplay { maxShortDisplay = displayLength } } // Pad usage enough so the flags have room. flagPaddingLength = maxShortDisplay + 2*indentLength // We use the width of the terminal unless we cannot get the width. width, _, err := terminal.GetSize(int(os.Stdout.Fd())) if err != nil { lineLength = fallbackLineLength } else { lineLength = util.MinInt(width, maxLineLength) } } // Flags that conform to this interface can be used with a urfave/cli // application and can be printed in the correct format. type prettyFlag interface { cli.Flag GetArgName() string GetUsage() string } // How a flag should appear on the command line. We have two formats: // --name // --name=ARG_NAME // The ARG_NAME appears if the prettyFlag's GetArgName() method returns a // non-empty string. The returned string from shortDisplay() does not include // any leading or trailing whitespace. func shortDisplay(f prettyFlag) string { if argName := f.GetArgName(); argName != "" { return fmt.Sprintf("--%s=%s", f.GetName(), argName) } return fmt.Sprintf("--%s", f.GetName()) } // How our flags should appear when displaying their usage. An example would be: // // --help Prints help screen for commands and subcommands. // // If a default is specified, this if appended to the usage. Example: // // --legacy Allow for support of older kernels with ext4 // (before v4.8) and F2FS (before v4.6) filesystems. // (default: true) // func longDisplay(f prettyFlag, defaultString ...string) string { usage := f.GetUsage() if len(defaultString) > 0 { usage += fmt.Sprintf(" (default: %v)", defaultString[0]) } // We pad the shortDisplay on the right with enough spaces to equal the // longest flag's display shortDisp := shortDisplay(f) length := utf8.RuneCountInString(shortDisp) shortDisp += strings.Repeat(" ", maxShortDisplay-length) return indent + shortDisp + indent + wrapText(usage, flagPaddingLength) } // Regex that determines if we are starting an ordered list var listRegex = regexp.MustCompile(`^\([\d]+\)$`) // Takes an input string text, and wraps the text so that each line begins with // padding spaces (except for the first line), ends with a newline (except the // last line), and each line has length less than lineLength. If the text // contains a word which is too long, that word gets its own line. func wrapText(text string, padding int) string { // We use a buffer to format the wrapped text so we get O(n) runtime var buffer bytes.Buffer spaceLeft := 0 maxTextLen := lineLength - padding delimiter := strings.Repeat(" ", padding) for i, word := range strings.Fields(text) { wordLen := utf8.RuneCountInString(word) switch { case i == 0: // No delimiter for the first line buffer.WriteString(word) spaceLeft = maxTextLen - wordLen case listRegex.Match([]byte(word)): // Add an additional line to separate list items. buffer.WriteString("\n") fallthrough case wordLen+1 > spaceLeft: // If no room left, write the word on the next line. buffer.WriteString("\n") buffer.WriteString(delimiter) buffer.WriteString(word) spaceLeft = maxTextLen - wordLen default: // Write word on this line buffer.WriteByte(' ') buffer.WriteString(word) spaceLeft -= 1 + wordLen } } return buffer.String() } fscrypt-0.2.5/cmd/fscrypt/fscrypt.go000066400000000000000000000077341357032351700175070ustar00rootroot00000000000000/* * fscrypt.go - File which starts up and runs the application. Initializes * information about the application like the name, version, author, etc... * * 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. */ /* fscrypt is a command line tool for managing linux filesystem encryption. */ package main import ( "fmt" "io/ioutil" "log" "os" "time" "github.com/urfave/cli" ) var ( // Current version of the program (set by Makefile) version string // Formatted build time (set by Makefile) buildTime string // Authors to display in the info command Authors = []cli.Author{{ Name: "Joe Richey", Email: "joerichey@google.com", }} ) func main() { cli.AppHelpTemplate = appHelpTemplate cli.CommandHelpTemplate = commandHelpTemplate cli.SubcommandHelpTemplate = subcommandHelpTemplate // Create our command line application app := cli.NewApp() app.Usage = shortUsage app.Authors = Authors app.Copyright = apache2GoogleCopyright // Grab the version and compilation time passed in from the Makefile. app.Version = version app.Compiled, _ = time.Parse(time.UnixDate, buildTime) app.OnUsageError = onUsageError // Setup global flags cli.HelpFlag = helpFlag cli.VersionFlag = versionFlag cli.VersionPrinter = func(c *cli.Context) { cli.HelpPrinter(c.App.Writer, versionInfoTemplate, c.App) } app.Flags = universalFlags // We hide the help subcommand so that "fscrypt --help" works // and "fscrypt help" does not. app.HideHelp = true // Initialize command list and setup all of the commands. app.Action = defaultAction app.Commands = []cli.Command{Setup, Encrypt, Unlock, Purge, Status, Metadata} for i := range app.Commands { setupCommand(&app.Commands[i]) } app.Run(os.Args) } // setupCommand performs some common setup for each command. This includes // hiding the help, formatting the description, adding in the necessary // flags, setting up error handlers, etc... Note that the command is modified // in place and its subcommands are also setup. func setupCommand(command *cli.Command) { command.Description = wrapText(command.Description, indentLength) command.HideHelp = true command.Flags = append(command.Flags, universalFlags...) if command.Action == nil { command.Action = defaultAction } // Setup function handlers command.OnUsageError = onUsageError if len(command.Subcommands) == 0 { command.Before = setupBefore } else { // Setup subcommands (if applicable) for i := range command.Subcommands { setupCommand(&command.Subcommands[i]) } } } // setupBefore makes sure our logs, errors, and output are going to the correct // io.Writers and that we haven't over-specified our flags. We only print the // logs when using verbose, and only print normal stuff when not using quiet. func setupBefore(c *cli.Context) error { log.SetOutput(ioutil.Discard) c.App.Writer = ioutil.Discard if verboseFlag.Value { log.SetOutput(os.Stdout) } if !quietFlag.Value { c.App.Writer = os.Stdout } return nil } // defaultAction will be run when no command is specified. func defaultAction(c *cli.Context) error { // Always default to showing the help if helpFlag.Value { cli.ShowAppHelp(c) return nil } // Only exit when not calling with the help command var message string if args := c.Args(); args.Present() { message = fmt.Sprintf("command \"%s\" not found", args.First()) } else { message = "no command was specified" } return &usageError{c, message} } fscrypt-0.2.5/cmd/fscrypt/fscrypt_test.go000066400000000000000000000014201357032351700205300ustar00rootroot00000000000000/* * fscrypt_test.go - Stub test file that has one test that always passes. * * 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 "testing" func TestTrivial(t *testing.T) {} fscrypt-0.2.5/cmd/fscrypt/keys.go000066400000000000000000000127741357032351700167700ustar00rootroot00000000000000/* * 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. 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.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 } } } fscrypt-0.2.5/cmd/fscrypt/prompt.go000066400000000000000000000213371357032351700173310ustar00rootroot00000000000000/* * prompt.go - Functions for handling user input and options * * 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" "log" "os" "os/user" "strconv" "strings" "github.com/pkg/errors" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/util" ) const ( // Suffixes for questions with a yes or no default defaultYesSuffix = " [Y/n] " defaultNoSuffix = " [y/N] " ) // Descriptions for each of the protector sources var sourceDescriptions = map[metadata.SourceType]string{ metadata.SourceType_pam_passphrase: "Your login passphrase", metadata.SourceType_custom_passphrase: "A custom passphrase", metadata.SourceType_raw_key: "A raw 256-bit key", } // askQuestion asks the user a yes or no question. Returning a boolean on a // successful answer and an error if there was not a response from the user. // Returns the defaultChoice on empty input (or in quiet mode). func askQuestion(question string, defaultChoice bool) (bool, error) { // If in quiet mode, we just use the default if quietFlag.Value { return defaultChoice, nil } // Loop until failure or valid input for { if defaultChoice { fmt.Print(question + defaultYesSuffix) } else { fmt.Print(question + defaultNoSuffix) } input, err := util.ReadLine() if err != nil { return false, err } switch strings.ToLower(input) { case "y", "yes": return true, nil case "n", "no": return false, nil case "": return defaultChoice, nil } } } // askConfirmation asks the user for confirmation of a specific action. An error // is returned if the user declines or IO fails. func askConfirmation(question string, defaultChoice bool, warning string) error { // All confirmations are "yes" if we are forcing. if forceFlag.Value { return nil } // Defaults of "no" require forcing. if !defaultChoice { if quietFlag.Value { return ErrNoDestructiveOps } } if warning != "" && !quietFlag.Value { fmt.Println(wrapText("WARNING: "+warning, 0)) } confirmed, err := askQuestion(question, defaultChoice) if err != nil { return err } if !confirmed { return ErrCanceled } return nil } // usernameFromID returns the username for the provided UID. If the UID does not // correspond to a user or the username is blank, an error is returned. func usernameFromID(uid int64) (string, error) { u, err := user.LookupId(strconv.Itoa(int(uid))) if err != nil || u.Username == "" { return "", errors.Wrapf(ErrUnknownUser, "uid %d", uid) } return u.Username, nil } // formatUsername either returns the username for the provided UID, or a string // containing the error for unknown UIDs. func formatUsername(uid int64) string { username, err := usernameFromID(uid) if err != nil { return fmt.Sprintf("[%v]", err) } return username } // formatInfo gives a string description of metadata.ProtectorData. func formatInfo(data actions.ProtectorInfo) string { switch data.Source() { case metadata.SourceType_pam_passphrase: return "login protector for " + formatUsername(data.UID()) case metadata.SourceType_custom_passphrase: return fmt.Sprintf("custom protector %q", data.Name()) case metadata.SourceType_raw_key: return fmt.Sprintf("raw key protector %q", data.Name()) default: panic(ErrInvalidSource) } } // promptForName gets a name from user input (or flags) and returns it. func promptForName(ctx *actions.Context) (string, error) { // A name flag means we do not need to prompt if nameFlag.Value != "" { return nameFlag.Value, nil } // Don't ask for a name if we do not need it if quietFlag.Value || ctx.Config.Source == metadata.SourceType_pam_passphrase { return "", nil } for { fmt.Print("Enter a name for the new protector: ") name, err := util.ReadLine() if err != nil { return "", err } if name != "" { return name, nil } } } // promptForSource gets a source type from user input (or flags) and modifies // the context to use that source. func promptForSource(ctx *actions.Context) error { // A source flag overrides everything else. if sourceFlag.Value != "" { val, ok := metadata.SourceType_value[sourceFlag.Value] if !ok || val == 0 { return ErrInvalidSource } ctx.Config.Source = metadata.SourceType(val) return nil } // Just use the default in quiet mode if quietFlag.Value { return nil } // We print all the sources with their number, description, and name. fmt.Println("Your data can be protected with one of the following sources:") for idx := 1; idx < len(metadata.SourceType_value); idx++ { source := metadata.SourceType(idx) description := sourceDescriptions[source] fmt.Printf("%d - %s (%s)\n", idx, description, source) } for { fmt.Printf("Enter the source number for the new protector [%d - %s]: ", ctx.Config.Source, ctx.Config.Source) input, err := util.ReadLine() if err != nil { return err } // Use the default if the user just hits enter if input == "" { return nil } // Check for a valid index, reprompt if invalid. index, err := strconv.Atoi(input) if err == nil && index >= 1 && index < len(metadata.SourceType_value) { ctx.Config.Source = metadata.SourceType(index) return nil } } } // promptForKeyFile returns an open file that should be used to create or unlock // a raw_key protector. Be sure to close the file when done. func promptForKeyFile(prompt string) (*os.File, error) { // If specified on the command line, we only try no open it once. if keyFileFlag.Value != "" { return os.Open(keyFileFlag.Value) } if quietFlag.Value { return nil, ErrSpecifyKeyFile } // Prompt for a valid path until we get a file we can open. for { fmt.Print(prompt) filename, err := util.ReadLine() if err != nil { return nil, err } file, err := os.Open(filename) if err == nil { return file, nil } fmt.Println(err) } } // promptForProtector, given a non-empty list of protector options, uses user // input to select the desired protector. If there is only one option to choose // from, that protector is automatically selected. func promptForProtector(options []*actions.ProtectorOption) (int, error) { numOptions := len(options) log.Printf("selecting from %s", pluralize(numOptions, "protector")) // Get the number of load errors. numLoadErrors := 0 for _, option := range options { if option.LoadError != nil { log.Printf("when loading option: %v", option.LoadError) numLoadErrors++ } } if numLoadErrors == numOptions { return 0, ErrAllLoadsFailed } if numOptions == 1 { return 0, nil } if quietFlag.Value { return 0, ErrSpecifyProtector } // List all of the protector options which did not have a load error. fmt.Println("The available protectors are: ") for idx, option := range options { if option.LoadError != nil { continue } description := fmt.Sprintf("%d - %s", idx, formatInfo(option.ProtectorInfo)) if option.LinkedMount != nil { description += fmt.Sprintf(" (linked protector on %q)", option.LinkedMount.Path) } fmt.Println(description) } if numLoadErrors > 0 { fmt.Print(wrapText("NOTE: %d of the %d protectors failed to load. "+loadHelpText, 0)) } for { fmt.Print("Enter the number of protector to use: ") input, err := util.ReadLine() if err != nil { return 0, err } // Check for a valid index, reprompt if invalid. index, err := strconv.Atoi(input) if err == nil && index >= 0 && index < len(options) { return index, nil } } } // optionFn is an actions.OptionFunc which handles selecting an option for a // specific policy. This is either done interactively, or by deferring to the // protectorFlag. func optionFn(policyDescriptor string, options []*actions.ProtectorOption) (int, error) { // If we have an unlock-with flag, we directly select the specified // protector to unlock the policy. if unlockWithFlag.Value != "" { log.Printf("optionFn(%s) w/ unlock flag", policyDescriptor) protector, err := getProtectorFromFlag(unlockWithFlag.Value, nil) if err != nil { return 0, err } for idx, option := range options { if option.Descriptor() == protector.Descriptor() { return idx, nil } } return 0, actions.ErrNotProtected } log.Printf("optionFn(%s)", policyDescriptor) return promptForProtector(options) } fscrypt-0.2.5/cmd/fscrypt/protector.go000066400000000000000000000076201357032351700200300ustar00rootroot00000000000000/* * protector.go - Functions for creating and getting action.Protectors which * ensure that login passphrases are on the correct filesystem. * * 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 ( "log" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" ) // createProtector makes a new protector on either ctx.Mount or if the requested // source is a pam_passphrase, creates it on the root filesystem. Prompts for // user input are used to get the source, name and keys. func createProtectorFromContext(ctx *actions.Context) (*actions.Protector, error) { if err := promptForSource(ctx); err != nil { return nil, err } log.Printf("using source: %s", ctx.Config.Source.String()) name, err := promptForName(ctx) if err != nil { return nil, err } log.Printf("using name: %s", name) // We only want to create new login protectors on the root filesystem. // So we make a new context if necessary. if ctx.Config.Source == metadata.SourceType_pam_passphrase && ctx.Mount.Path != "/" { log.Printf("creating login protector on %q instead of %q", "/", ctx.Mount.Path) if ctx, err = modifiedContext(ctx); err != nil { return nil, err } } return actions.CreateProtector(ctx, name, createKeyFn) } // selectExistingProtector returns a locked Protector which corresponds to an // option in the non-empty slice of options. Prompts for user input are used to // get the keys and select the option. func selectExistingProtector(ctx *actions.Context, options []*actions.ProtectorOption) (*actions.Protector, error) { idx, err := promptForProtector(options) if err != nil { return nil, err } option := options[idx] log.Printf("using %s", formatInfo(option.ProtectorInfo)) return actions.GetProtectorFromOption(ctx, option) } // expandedProtectorOptions gets all the actions.ProtectorOptions for ctx.Mount // as well as any pam_passphrase protectors for the root filesystem. func expandedProtectorOptions(ctx *actions.Context) ([]*actions.ProtectorOption, error) { options, err := ctx.ProtectorOptions() if err != nil { return nil, err } // Do nothing different if we are at the root, or cannot load the root. if ctx.Mount.Path == "/" { return options, nil } if ctx, err = modifiedContext(ctx); err != nil { log.Print(err) return options, nil } rootOptions, err := ctx.ProtectorOptions() if err != nil { log.Print(err) return options, nil } log.Print("adding additional ProtectorOptions") // Keep track of what we have seen, so we don't have duplicates seenOptions := make(map[string]bool) for _, option := range options { seenOptions[option.Descriptor()] = true } for _, option := range rootOptions { // Add in unseen passphrase protectors on the root filesystem // to the options list as potential linked protectors. if option.Source() == metadata.SourceType_pam_passphrase && !seenOptions[option.Descriptor()] { option.LinkedMount = ctx.Mount options = append(options, option) } } return options, nil } // modifiedContext returns a copy of ctx with the mountpoint replaced by that of // the root filesystem. func modifiedContext(ctx *actions.Context) (*actions.Context, error) { mnt, err := filesystem.GetMount("/") if err != nil { return nil, err } modifiedCtx := *ctx modifiedCtx.Mount = mnt return &modifiedCtx, nil } fscrypt-0.2.5/cmd/fscrypt/setup.go000066400000000000000000000041601357032351700171430ustar00rootroot00000000000000/* * setup.go - File containing the functionality for initializing directories and * the global config file. * * 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" "os" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/util" ) // createGlobalConfig creates (or overwrites) the global config file func createGlobalConfig(w io.Writer, path string) error { if !util.IsUserRoot() { return ErrMustBeRoot } // Ask to create or replace the config file _, err := os.Stat(path) switch { case err == nil: err = askConfirmation(fmt.Sprintf("Replace %q?", path), false, "") if err == nil { err = os.Remove(path) } case os.IsNotExist(err): err = askConfirmation(fmt.Sprintf("Create %q?", path), true, "") } if err != nil { return err } fmt.Fprintln(w, "Customizing passphrase hashing difficulty for this system...") err = actions.CreateConfigFile(timeTargetFlag.Value, legacyFlag.Value) if err != nil { return err } fmt.Fprintf(w, "Created global config file at %q.\n", path) return nil } // setupFilesystem creates the directories for a filesystem to use fscrypt. func setupFilesystem(w io.Writer, path string) error { ctx, err := actions.NewContextFromMountpoint(path, nil) if err != nil { return err } if err = ctx.Mount.Setup(); err != nil { return err } fmt.Fprintf(w, "Metadata directories created at %q.\n", ctx.Mount.BaseDir()) fmt.Fprintf(w, "Filesystem %q (%s) ready for use with %s encryption.\n", ctx.Mount.Path, ctx.Mount.Device, ctx.Mount.Filesystem) return nil } fscrypt-0.2.5/cmd/fscrypt/status.go000066400000000000000000000121151357032351700173250ustar00rootroot00000000000000/* * status.go - File which contains the functions for outputting the status of * fscrypt, a filesystem, or a directory. * * 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" "strings" "text/tabwriter" "github.com/pkg/errors" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" ) // Creates a writer which correctly aligns tabs with the specified header. // Must call Flush() when done. func makeTableWriter(w io.Writer, header string) *tabwriter.Writer { tableWriter := tabwriter.NewWriter(w, 0, indentLength, indentLength, ' ', 0) fmt.Fprintln(tableWriter, header) return tableWriter } // encryptionStatus will be printed in the ENCRYPTION column. An empty string // indicates the filesystem should not be printed. func encryptionStatus(err error) string { switch errors.Cause(err) { case nil: return "supported" case metadata.ErrEncryptionNotEnabled: return "not enabled" case metadata.ErrEncryptionNotSupported: return "not supported" default: // Unknown error regarding support return "" } } func yesNoString(b bool) string { if b { return "Yes" } return "No" } // writeGlobalStatus prints all the filesystems that use (or could use) fscrypt. func writeGlobalStatus(w io.Writer) error { mounts, err := filesystem.AllFilesystems() if err != nil { return err } supportCount := 0 useCount := 0 t := makeTableWriter(w, "MOUNTPOINT\tDEVICE\tFILESYSTEM\tENCRYPTION\tFSCRYPT") for _, mount := range mounts { // Only print mountpoints backed by devices or using fscrypt. usingFscrypt := mount.CheckSetup() == nil if !usingFscrypt && mount.Device == "" { continue } // Only print a mountpoint if we can determine its support. supportErr := mount.CheckSupport() supportString := encryptionStatus(supportErr) if supportString == "" { log.Print(supportErr) continue } fmt.Fprintf(t, "%s\t%s\t%s\t%s\t%s\n", mount.Path, mount.Device, mount.Filesystem, supportString, yesNoString(usingFscrypt)) if supportErr == nil { supportCount++ } if usingFscrypt { useCount++ } } fmt.Fprintf(w, "filesystems supporting encryption: %d\n", supportCount) fmt.Fprintf(w, "filesystems with fscrypt metadata: %d\n\n", useCount) return t.Flush() } // writeOptions writes a table of the status for a slice of protector options. func writeOptions(w io.Writer, options []*actions.ProtectorOption) { t := makeTableWriter(w, "PROTECTOR\tLINKED\tDESCRIPTION") for _, option := range options { if option.LoadError != nil { fmt.Fprintf(t, "%s\t\t[%s]\n", option.Descriptor(), option.LoadError) continue } // For linked protectors, indicate which filesystem. isLinked := option.LinkedMount != nil linkedText := yesNoString(isLinked) if isLinked { linkedText += fmt.Sprintf(" (%s)", option.LinkedMount.Path) } fmt.Fprintf(t, "%s\t%s\t%s\n", option.Descriptor(), linkedText, formatInfo(option.ProtectorInfo)) } t.Flush() } func writeFilesystemStatus(w io.Writer, ctx *actions.Context) error { options, err := ctx.ProtectorOptions() if err != nil { return err } policyDescriptors, err := ctx.Mount.ListPolicies() if err != nil { return err } fmt.Fprintf(w, "%s filesystem %q has %s and %s\n\n", ctx.Mount.Filesystem, ctx.Mount.Path, pluralize(len(options), "protector"), pluralize(len(policyDescriptors), "policy")) if len(options) > 0 { writeOptions(w, options) } if len(policyDescriptors) == 0 { return nil } fmt.Fprintln(w) t := makeTableWriter(w, "POLICY\tUNLOCKED\tPROTECTORS") for _, descriptor := range policyDescriptors { policy, err := actions.GetPolicy(ctx, descriptor) if err != nil { fmt.Fprintf(t, "%s\t\t[%s]\n", descriptor, err) continue } fmt.Fprintf(t, "%s\t%s\t%s\n", descriptor, yesNoString(policy.IsProvisioned()), strings.Join(policy.ProtectorDescriptors(), ", ")) } return t.Flush() } func writePathStatus(w io.Writer, path string) error { ctx, err := actions.NewContextFromPath(path, nil) if err != nil { return err } policy, err := actions.GetPolicyFromPath(ctx, path) if err != nil { return err } fmt.Fprintf(w, "%q is encrypted with fscrypt.\n", path) fmt.Fprintln(w) fmt.Fprintf(w, "Policy: %s\n", policy.Descriptor()) fmt.Fprintf(w, "Options: %s\n", policy.Options()) fmt.Fprintf(w, "Unlocked: %s\n", yesNoString(policy.IsProvisioned())) fmt.Fprintln(w) options := policy.ProtectorOptions() fmt.Fprintf(w, "Protected with %s:\n", pluralize(len(options), "protector")) writeOptions(w, options) return nil } fscrypt-0.2.5/cmd/fscrypt/strings.go000066400000000000000000000100351357032351700174720ustar00rootroot00000000000000/* * strings.go - File which contains the specific strings used for output and * formatting in fscrypt. * * 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" "strings" ) // Global application strings const ( shortUsage = "A tool for managing Linux filesystem encryption" apache2GoogleCopyright = `Copyright 2017 Google, Inc. 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.` ) // Argument usage strings const ( directoryArg = "DIRECTORY" mountpointArg = "MOUNTPOINT" pathArg = "PATH" mountpointIDArg = mountpointArg + ":ID" ) // Text Templates which format our command line output (using text/template) var ( // indent is the prefix for the output lines in each section indent = strings.Repeat(" ", indentLength) // Top level help output: what is printed for "fscrypt" or "fscrypt --help" appHelpTemplate = `{{.HelpName}} - {{.Usage}} Usage: ` + indent + `{{.HelpName}} COMMAND [arguments] [options] Commands:{{range .VisibleCommands}} ` + indent + `{{join .Names ", "}}{{"\t- "}}{{.Usage}}{{end}} {{if .Description}} Description: ` + indent + `{{.Description}} {{end}} Options: {{range .VisibleFlags}}{{.}} {{end}}` // Command help output, used when a command has no subcommands commandHelpTemplate = `{{.HelpName}} - {{.Usage}} Usage: ` + indent + `{{.HelpName}}{{if .ArgsUsage}} {{.ArgsUsage}}{{end}}{{if .VisibleFlags}} [options]{{end}} {{if .Description}} Description: ` + indent + `{{.Description}} {{end}}{{if .VisibleFlags}} Options: {{range .VisibleFlags}}{{.}} {{end}}{{end}}` // Subcommand help output, used when a command has subcommands subcommandHelpTemplate = `{{.HelpName}} - {{.Usage}} Usage: ` + indent + `{{.HelpName}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}SUBCOMMAND [arguments]{{end}}{{if .VisibleFlags}} [options]{{end}} Subcommands:{{range .VisibleCommands}} ` + indent + `{{join .Names ", "}}{{"\t- "}}{{.Usage}}{{end}} {{if .Description}} Description: ` + indent + `{{.Description}} {{end}}{{if .VisibleFlags}} Options: {{range .VisibleFlags}}{{.}} {{end}}{{end}}` // Additional info, used with "fscrypt version" versionInfoTemplate = `{{.HelpName}} - {{.Usage}} {{if .Version}}Version: ` + indent + `{{.Version}} {{end}}{{if not .Compiled.IsZero}}Compiled: ` + indent + `{{.Compiled}} {{end}}{{if len .Authors}}Author{{with $length := len .Authors}}{{if ne 1 $length}}s{{end}}{{end}}:{{range .Authors}} ` + indent + `{{.}}{{end}} {{end}}{{if .Copyright}}Copyright: ` + indent + `{{.Copyright}} {{end}}` ) // Add words to this map to have pluralize support them. var plurals = map[string]string{ "argument": "arguments", "filesystem": "filesystems", "protector": "protectors", "policy": "policies", } // pluralize prints out the correct pluralization of a word along with the // specified count. This means pluralize(1, "policy") = "1 policy" but // pluralize(2, "policy") = "2 policies" func pluralize(count int, word string) string { if count != 1 { word = plurals[word] } return fmt.Sprintf("%d %s", count, word) } fscrypt-0.2.5/crypto/000077500000000000000000000000001357032351700145365ustar00rootroot00000000000000fscrypt-0.2.5/crypto/crypto.go000066400000000000000000000160531357032351700164120ustar00rootroot00000000000000/* * crypto.go - Cryptographic algorithms used by the rest of fscrypt. * * 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 crypto manages all the cryptography for fscrypt. This includes: // - Key management (key.go) // - Securely holding keys in memory // - Making recovery keys // - Randomness (rand.go) // - Cryptographic algorithms (crypto.go) // - encryption (AES256-CTR) // - authentication (SHA256-based HMAC) // - key stretching (SHA256-based HKDF) // - key wrapping/unwrapping (Encrypt then MAC) // - passphrase-based key derivation (Argon2id) // - descriptor computation (double SHA512) package crypto import ( "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/sha256" "crypto/sha512" "encoding/hex" "github.com/pkg/errors" "golang.org/x/crypto/argon2" "golang.org/x/crypto/hkdf" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/util" ) // Crypto error values var ( ErrBadAuth = errors.New("key authentication check failed") ErrNegativeLength = errors.New("keys cannot have negative lengths") ErrRecoveryCode = errors.New("invalid recovery code") ErrGetrandomFail = util.SystemError("getrandom() failed") ErrKeyAlloc = util.SystemError("could not allocate memory for key") ErrKeyFree = util.SystemError("could not free memory of key") ErrKeyLock = errors.New("could not lock key in memory") ) // panicInputLength panics if "name" has invalid length (expected != actual) func panicInputLength(name string, expected, actual int) { if err := util.CheckValidLength(expected, actual); err != nil { panic(errors.Wrap(err, name)) } } // checkWrappingKey returns an error if the wrapping key has the wrong length func checkWrappingKey(wrappingKey *Key) error { err := util.CheckValidLength(metadata.InternalKeyLen, wrappingKey.Len()) return errors.Wrap(err, "wrapping key") } // stretchKey stretches a key of length InternalKeyLen using unsalted HKDF to // make two keys of length InternalKeyLen. func stretchKey(key *Key) (encKey, authKey *Key) { panicInputLength("hkdf key", metadata.InternalKeyLen, key.Len()) // The new hkdf function uses the hash and key to create a reader that // can be used to securely initialize multiple keys. This means that // reads on the hkdf give independent cryptographic keys. The hkdf will // also always have enough entropy to read two keys. hkdf := hkdf.New(sha256.New, key.data, nil, nil) encKey, err := NewFixedLengthKeyFromReader(hkdf, metadata.InternalKeyLen) util.NeverError(err) authKey, err = NewFixedLengthKeyFromReader(hkdf, metadata.InternalKeyLen) util.NeverError(err) return } // aesCTR runs AES256-CTR on the input using the provided key and iv. This // function can be used to either encrypt or decrypt input of any size. Note // that input and output must be the same size. func aesCTR(key *Key, iv, input, output []byte) { panicInputLength("aesCTR key", metadata.InternalKeyLen, key.Len()) panicInputLength("aesCTR iv", metadata.IVLen, len(iv)) panicInputLength("aesCTR output", len(input), len(output)) blockCipher, err := aes.NewCipher(key.data) util.NeverError(err) // Key is checked to have correct length stream := cipher.NewCTR(blockCipher, iv) stream.XORKeyStream(output, input) } // getHMAC returns the SHA256-based HMAC of some data using the provided key. func getHMAC(key *Key, data ...[]byte) []byte { panicInputLength("hmac key", metadata.InternalKeyLen, key.Len()) mac := hmac.New(sha256.New, key.data) for _, buffer := range data { // SHA256 HMAC should never be unable to write the data _, err := mac.Write(buffer) util.NeverError(err) } return mac.Sum(nil) } // Wrap takes a wrapping Key of length InternalKeyLen, and uses it to wrap a // secret Key of any length. This wrapping uses a random IV, the encrypted data, // and an HMAC to verify the wrapping key was correct. All of this is included // in the returned WrappedKeyData structure. func Wrap(wrappingKey, secretKey *Key) (*metadata.WrappedKeyData, error) { if err := checkWrappingKey(wrappingKey); err != nil { return nil, err } data := &metadata.WrappedKeyData{EncryptedKey: make([]byte, secretKey.Len())} // Get random IV var err error if data.IV, err = NewRandomBuffer(metadata.IVLen); err != nil { return nil, err } // Stretch key for encryption and authentication (unsalted). encKey, authKey := stretchKey(wrappingKey) defer encKey.Wipe() defer authKey.Wipe() // Encrypt the secret and include the HMAC of the output ("Encrypt-then-MAC"). aesCTR(encKey, data.IV, secretKey.data, data.EncryptedKey) data.Hmac = getHMAC(authKey, data.IV, data.EncryptedKey) return data, nil } // Unwrap takes a wrapping Key of length InternalKeyLen, and uses it to unwrap // the WrappedKeyData to get the unwrapped secret Key. The Wrapped Key data // includes an authentication check, so an error will be returned if that check // fails. func Unwrap(wrappingKey *Key, data *metadata.WrappedKeyData) (*Key, error) { if err := checkWrappingKey(wrappingKey); err != nil { return nil, err } // Stretch key for encryption and authentication (unsalted). encKey, authKey := stretchKey(wrappingKey) defer encKey.Wipe() defer authKey.Wipe() // Check validity of the HMAC if !hmac.Equal(getHMAC(authKey, data.IV, data.EncryptedKey), data.Hmac) { return nil, ErrBadAuth } secretKey, err := newBlankKey(len(data.EncryptedKey)) if err != nil { return nil, err } aesCTR(encKey, data.IV, data.EncryptedKey, secretKey.data) return secretKey, nil } // ComputeDescriptor computes the descriptor for a given cryptographic key. In // keeping with the process used in e4crypt, this uses the initial bytes // (formatted as hexadecimal) of the double application of SHA512 on the key. func ComputeDescriptor(key *Key) string { h1 := sha512.Sum512(key.data) h2 := sha512.Sum512(h1[:]) length := hex.DecodedLen(metadata.DescriptorLen) return hex.EncodeToString(h2[:length]) } // PassphraseHash uses Argon2id to produce a Key given the passphrase, salt, and // hashing costs. This method is designed to take a long time and consume // considerable memory. For more information, see the documentation at // https://godoc.org/golang.org/x/crypto/argon2. func PassphraseHash(passphrase *Key, salt []byte, costs *metadata.HashingCosts) (*Key, error) { t := uint32(costs.Time) m := uint32(costs.Memory) p := uint8(costs.Parallelism) key := argon2.IDKey(passphrase.data, salt, t, m, p, metadata.InternalKeyLen) hash, err := newBlankKey(metadata.InternalKeyLen) if err != nil { return nil, err } copy(hash.data, key) return hash, nil } fscrypt-0.2.5/crypto/crypto_test.go000066400000000000000000000401501357032351700174440ustar00rootroot00000000000000/* * crypto_test.go - tests for the crypto package * * 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 crypto import ( "bytes" "compress/zlib" "crypto/aes" "crypto/sha256" "encoding/hex" "fmt" "io" "os" "testing" "golang.org/x/sys/unix" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) // Reader that always returns the same byte type ConstReader byte func (r ConstReader) Read(b []byte) (n int, err error) { for i := range b { b[i] = byte(r) } return len(b), nil } // Makes a key of the same repeating byte func makeKey(b byte, n int) (*Key, error) { return NewFixedLengthKeyFromReader(ConstReader(b), n) } var ( fakeValidDescriptor = "0123456789abcdef" fakeSalt = bytes.Repeat([]byte{'a'}, metadata.SaltLen) fakePassword = []byte("password") defaultService = unix.FS_KEY_DESC_PREFIX fakeValidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen) fakeInvalidPolicyKey, _ = makeKey(42, metadata.PolicyKeyLen-1) fakeWrappingKey, _ = makeKey(17, metadata.InternalKeyLen) testUser, _ = util.EffectiveUser() ) // As the passphrase hashing function clears the passphrase, we need to make // a new passphrase key for each test func fakePassphraseKey() (*Key, error) { return NewFixedLengthKeyFromReader(bytes.NewReader(fakePassword), len(fakePassword)) } // Values for test cases pulled from argon2 command line tool. // To generate run: // echo "password" | argon2 "aaaaaaaaaaaaaaaa" -id -t -m -p

-l 32 // where costs.Time = , costs.Memory = 2^, and costs.Parallelism =

. type hashTestCase struct { costs *metadata.HashingCosts hexHash string } var hashTestCases = []hashTestCase{ { costs: &metadata.HashingCosts{Time: 1, Memory: 1 << 10, Parallelism: 1}, hexHash: "a66f5398e33761bf161fdf1273e99b148f07d88d12d85b7673fddd723f95ec34", }, { costs: &metadata.HashingCosts{Time: 10, Memory: 1 << 10, Parallelism: 1}, hexHash: "5fa2cb89db1f7413ba1776258b7c8ee8c377d122078d28fe1fd645c353787f50", }, { costs: &metadata.HashingCosts{Time: 1, Memory: 1 << 15, Parallelism: 1}, hexHash: "f474a213ed14d16ead619568000939b938ddfbd2ac4a82d253afa81b5ebaef84", }, { costs: &metadata.HashingCosts{Time: 1, Memory: 1 << 10, Parallelism: 10}, hexHash: "b7c3d7a0be222680b5ea3af3fb1a0b7b02b92cbd7007821dc8b84800c86c7783", }, } // Checks that len(array) == expected func lengthCheck(name string, array []byte, expected int) error { if len(array) != expected { return fmt.Errorf("length of %s should be %d", name, expected) } return nil } // Tests the two ways of making keys func TestMakeKeys(t *testing.T) { data := []byte("1234\n6789") key1, err := NewKeyFromReader(bytes.NewReader(data)) if err != nil { t.Fatal(err) } defer key1.Wipe() if !bytes.Equal(data, key1.data) { t.Error("Key from reader contained incorrect data") } key2, err := NewFixedLengthKeyFromReader(bytes.NewReader(data), 6) if err != nil { t.Fatal(err) } defer key2.Wipe() if !bytes.Equal([]byte("1234\n6"), key2.data) { t.Error("Fixed length key from reader contained incorrect data") } } // Tests that wipe succeeds func TestWipe(t *testing.T) { key, err := makeKey(1, 1000) if err != nil { t.Fatal(err) } if err := key.Wipe(); err != nil { t.Error(err) } } // Making keys with negative length should fail func TestInvalidLength(t *testing.T) { key, err := NewFixedLengthKeyFromReader(ConstReader(1), -1) if err == nil { key.Wipe() t.Error("Negative lengths should cause failure") } } // Test making keys of zero length func TestZeroLength(t *testing.T) { key1, err := NewFixedLengthKeyFromReader(os.Stdin, 0) if err != nil { t.Fatal(err) } defer key1.Wipe() if key1.data != nil { t.Error("Fixed length key from reader contained data") } key2, err := NewKeyFromReader(bytes.NewReader(nil)) if err != nil { t.Fatal(err) } defer key2.Wipe() if key2.data != nil { t.Error("Key from empty reader contained data") } } // Test that enabling then disabling memory locking succeeds even if a key is // active when the variable changes. func TestEnableDisableMemoryLocking(t *testing.T) { // Mlock on for creation, off for wiping key, err := NewRandomKey(metadata.InternalKeyLen) UseMlock = false defer func() { UseMlock = true }() if err != nil { t.Fatal(err) } if err := key.Wipe(); err != nil { t.Error(err) } } // Test that disabling then enabling memory locking succeeds even if a key is // active when the variable changes. func TestDisableEnableMemoryLocking(t *testing.T) { // Mlock off for creation, on for wiping UseMlock = false key2, err := NewRandomKey(metadata.InternalKeyLen) UseMlock = true if err != nil { t.Fatal(err) } if err := key2.Wipe(); err != nil { t.Error(err) } } // Test making keys long enough that the keys will have to resize func TestKeyResize(t *testing.T) { // Key will have to resize once r := io.LimitReader(ConstReader(1), int64(os.Getpagesize())+1) key, err := NewKeyFromReader(r) if err != nil { t.Fatal(err) } defer key.Wipe() for i, b := range key.data { if b != 1 { t.Fatalf("Byte %d contained invalid data %q", i, b) } } } // Test making keys so long that many resizes are necessary func TestKeyLargeResize(t *testing.T) { // Key will have to resize 7 times r := io.LimitReader(ConstReader(1), int64(os.Getpagesize())*65) // Turn off Mlocking as the key will exceed the limit on some systems. UseMlock = false key, err := NewKeyFromReader(r) UseMlock = true if err != nil { t.Fatal(err) } defer key.Wipe() for i, b := range key.data { if b != 1 { t.Fatalf("Byte %d contained invalid data %q", i, b) } } } // Adds and removes a key with various services. func TestAddRemoveKeys(t *testing.T) { for _, service := range []string{defaultService, "ext4:", "f2fs:"} { validDescription := service + fakeValidDescriptor if err := InsertPolicyKey(fakeValidPolicyKey, validDescription, testUser); err != nil { t.Error(err) } if err := security.RemoveKey(validDescription, testUser); err != nil { t.Error(err) } } } // Adds a key twice (both should succeed) func TestAddTwice(t *testing.T) { validDescription := defaultService + fakeValidDescriptor InsertPolicyKey(fakeValidPolicyKey, validDescription, testUser) if InsertPolicyKey(fakeValidPolicyKey, validDescription, testUser) != nil { t.Error("InsertPolicyKey should not fail if key already exists") } security.RemoveKey(validDescription, testUser) } // Makes sure a key fails with bad policy or service func TestBadAddKeys(t *testing.T) { validDescription := defaultService + fakeValidDescriptor if InsertPolicyKey(fakeInvalidPolicyKey, validDescription, testUser) == nil { security.RemoveKey(validDescription, testUser) t.Error("InsertPolicyKey should fail with bad policy key") } invalidDescription := "ext4" + fakeValidDescriptor if InsertPolicyKey(fakeValidPolicyKey, invalidDescription, testUser) == nil { security.RemoveKey(invalidDescription, testUser) t.Error("InsertPolicyKey should fail with bad service") } } // Check that we can create random keys. All this test does to test the // "randomness" is generate a page of random bytes and attempts compression. // If the data can be compressed it is probably not very random. This isn't // intended to be a sufficient test for randomness (which is impossible), but a // way to catch simple regressions (key is all zeros or contains a repeating // pattern). func TestRandomKeyGen(t *testing.T) { key, err := NewRandomKey(os.Getpagesize()) if err != nil { t.Fatal(err) } defer key.Wipe() if didCompress(key.data) { t.Errorf("Random key (%d bytes) should not be compressible", key.Len()) } } func TestBigKeyGen(t *testing.T) { key, err := NewRandomKey(4096 * 4096) switch err { case nil: key.Wipe() return case ErrKeyLock: // Don't fail just because "ulimit -l" is too low. return default: t.Fatal(err) } } // didCompress checks if the given data can be compressed. Specifically, it // returns true if running zlib on the provided input produces a shorter output. func didCompress(input []byte) bool { var output bytes.Buffer w := zlib.NewWriter(&output) _, err := w.Write(input) w.Close() return err == nil && len(input) > output.Len() } // Checks that the input arrays are all distinct func buffersDistinct(buffers ...[]byte) bool { for i := 0; i < len(buffers); i++ { for j := i + 1; j < len(buffers); j++ { if bytes.Equal(buffers[i], buffers[j]) { // Different entry, but equal arrays return false } } } return true } // Checks that our cryptographic operations all produce distinct data func TestKeysAndOutputsDistinct(t *testing.T) { data, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) if err != nil { t.Fatal(err) } encKey, authKey := stretchKey(fakeWrappingKey) defer encKey.Wipe() defer authKey.Wipe() if !buffersDistinct(fakeWrappingKey.data, fakeValidPolicyKey.data, encKey.data, authKey.data, data.IV, data.EncryptedKey, data.Hmac) { t.Error("Key wrapping produced duplicate data") } } // Check that Wrap() works with fixed keys func TestWrapSucceeds(t *testing.T) { data, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) if err != nil { t.Fatal(err) } if err = lengthCheck("IV", data.IV, aes.BlockSize); err != nil { t.Error(err) } if err = lengthCheck("Encrypted Key", data.EncryptedKey, metadata.PolicyKeyLen); err != nil { t.Error(err) } if err = lengthCheck("HMAC", data.Hmac, sha256.Size); err != nil { t.Error(err) } } // Checks that applying Wrap then Unwrap gives the original data func testWrapUnwrapEqual(wrappingKey *Key, secretKey *Key) error { data, err := Wrap(wrappingKey, secretKey) if err != nil { return err } secret, err := Unwrap(wrappingKey, data) if err != nil { return err } defer secret.Wipe() if !bytes.Equal(secretKey.data, secret.data) { return fmt.Errorf("Got %x after wrap/unwrap with w=%x and s=%x", secret.data, wrappingKey.data, secretKey.data) } return nil } // Check that Unwrap(Wrap(x)) == x with fixed keys func TestWrapUnwrapEqual(t *testing.T) { if err := testWrapUnwrapEqual(fakeWrappingKey, fakeValidPolicyKey); err != nil { t.Error(err) } } // Check that Unwrap(Wrap(x)) == x with random keys func TestRandomWrapUnwrapEqual(t *testing.T) { for i := 0; i < 10; i++ { wk, err := NewRandomKey(metadata.InternalKeyLen) if err != nil { t.Fatal(err) } sk, err := NewRandomKey(metadata.InternalKeyLen) if err != nil { t.Fatal(err) } if err = testWrapUnwrapEqual(wk, sk); err != nil { t.Error(err) } wk.Wipe() sk.Wipe() } } // Check that Unwrap(Wrap(x)) == x with differing lengths of secret key func TestDifferentLengthSecretKey(t *testing.T) { wk, err := makeKey(1, metadata.InternalKeyLen) if err != nil { t.Fatal(err) } defer wk.Wipe() for i := 0; i < 100; i++ { sk, err := makeKey(2, i) if err != nil { t.Fatal(err) } if err = testWrapUnwrapEqual(wk, sk); err != nil { t.Error(err) } sk.Wipe() } } // Wrong length of wrapping key should fail func TestWrongWrappingKeyLength(t *testing.T) { _, err := Wrap(fakeValidPolicyKey, fakeWrappingKey) if err == nil { t.Fatal("using a policy key for wrapping should fail") } } // Wrong length of unwrapping key should fail func TestWrongUnwrappingKeyLength(t *testing.T) { data, err := Wrap(fakeWrappingKey, fakeWrappingKey) if err != nil { t.Fatal(err) } if k, err := Unwrap(fakeValidPolicyKey, data); err == nil { k.Wipe() t.Fatal("using a policy key for unwrapping should fail") } } // Wrapping twice with the same keys should give different components func TestWrapTwiceDistinct(t *testing.T) { data1, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) if err != nil { t.Fatal(err) } data2, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) if err != nil { t.Fatal(err) } if !buffersDistinct(data1.IV, data1.EncryptedKey, data1.Hmac, data2.IV, data2.EncryptedKey, data2.Hmac) { t.Error("Wrapping same keys twice should give distinct results") } } // Attempts to Unwrap data with key after altering tweak, should fail func testFailWithTweak(key *Key, data *metadata.WrappedKeyData, tweak []byte) error { tweak[0]++ key, err := Unwrap(key, data) if err == nil { key.Wipe() } tweak[0]-- return err } // Wrapping then unwrapping with different components altered func TestUnwrapWrongKey(t *testing.T) { data, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) if err != nil { t.Fatal(err) } if testFailWithTweak(fakeWrappingKey, data, fakeWrappingKey.data) == nil { t.Error("using a different wrapping key should make unwrap fail") } } func TestUnwrapWrongData(t *testing.T) { data, err := Wrap(fakeWrappingKey, fakeValidPolicyKey) if err != nil { t.Fatal(err) } if testFailWithTweak(fakeWrappingKey, data, data.EncryptedKey) == nil { t.Error("changing encryption key should make unwrap fail") } if testFailWithTweak(fakeWrappingKey, data, data.IV) == nil { t.Error("changing IV should make unwrap fail") } if testFailWithTweak(fakeWrappingKey, data, data.Hmac) == nil { t.Error("changing HMAC should make unwrap fail") } } // Run our test cases for passphrase hashing func TestPassphraseHashing(t *testing.T) { for i, testCase := range hashTestCases { pk, err := fakePassphraseKey() if err != nil { t.Fatal(err) } defer pk.Wipe() hash, err := PassphraseHash(pk, fakeSalt, testCase.costs) if err != nil { t.Fatal(err) } defer hash.Wipe() actual := hex.EncodeToString(hash.data) if actual != testCase.hexHash { t.Errorf("Hash test %d: for costs=%+v expected hash of %q got %q", i, testCase.costs, testCase.hexHash, actual) } } } func BenchmarkWrap(b *testing.B) { for n := 0; n < b.N; n++ { Wrap(fakeWrappingKey, fakeValidPolicyKey) } } func BenchmarkUnwrap(b *testing.B) { b.StopTimer() data, _ := Wrap(fakeWrappingKey, fakeValidPolicyKey) b.StartTimer() for n := 0; n < b.N; n++ { key, err := Unwrap(fakeWrappingKey, data) if err != nil { b.Fatal(err) } key.Wipe() } } func BenchmarkUnwrapNolock(b *testing.B) { b.StopTimer() UseMlock = false defer func() { UseMlock = true }() data, _ := Wrap(fakeWrappingKey, fakeValidPolicyKey) b.StartTimer() for n := 0; n < b.N; n++ { key, err := Unwrap(fakeWrappingKey, data) if err != nil { b.Fatal(err) } key.Wipe() } } func BenchmarkRandomWrapUnwrap(b *testing.B) { for n := 0; n < b.N; n++ { wk, _ := NewRandomKey(metadata.InternalKeyLen) sk, _ := NewRandomKey(metadata.InternalKeyLen) testWrapUnwrapEqual(wk, sk) // Must manually call wipe here, or test will use too much memory. wk.Wipe() sk.Wipe() } } func benchmarkPassphraseHashing(b *testing.B, costs *metadata.HashingCosts) { b.StopTimer() pk, err := fakePassphraseKey() if err != nil { b.Fatal(err) } defer pk.Wipe() b.StartTimer() for n := 0; n < b.N; n++ { hash, err := PassphraseHash(pk, fakeSalt, costs) hash.Wipe() if err != nil { b.Fatal(err) } } } func BenchmarkPassphraseHashing_1MB_1Thread(b *testing.B) { benchmarkPassphraseHashing(b, &metadata.HashingCosts{Time: 1, Memory: 1 << 10, Parallelism: 1}) } func BenchmarkPassphraseHashing_1GB_1Thread(b *testing.B) { benchmarkPassphraseHashing(b, &metadata.HashingCosts{Time: 1, Memory: 1 << 20, Parallelism: 1}) } func BenchmarkPassphraseHashing_128MB_1Thread(b *testing.B) { benchmarkPassphraseHashing(b, &metadata.HashingCosts{Time: 1, Memory: 1 << 17, Parallelism: 1}) } func BenchmarkPassphraseHashing_128MB_8Thread(b *testing.B) { benchmarkPassphraseHashing(b, &metadata.HashingCosts{Time: 1, Memory: 1 << 17, Parallelism: 8}) } func BenchmarkPassphraseHashing_128MB_8Pass(b *testing.B) { benchmarkPassphraseHashing(b, &metadata.HashingCosts{Time: 8, Memory: 1 << 17, Parallelism: 1}) } fscrypt-0.2.5/crypto/key.go000066400000000000000000000255771357032351700156750ustar00rootroot00000000000000/* * key.go - Cryptographic key management for fscrypt. Ensures that sensitive * material is properly handled throughout the program. * * 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 crypto /* #include #include */ import "C" import ( "bytes" "crypto/subtle" "encoding/base32" "io" "log" "os" "os/user" "runtime" "unsafe" "github.com/pkg/errors" "golang.org/x/sys/unix" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) const ( // Keys need to readable and writable, but hidden from other processes. keyProtection = unix.PROT_READ | unix.PROT_WRITE keyMmapFlags = unix.MAP_PRIVATE | unix.MAP_ANONYMOUS ) /* UseMlock determines whether we should use the mlock/munlock syscalls to prevent sensitive data like keys and passphrases from being paged to disk. UseMlock defaults to true, but can be set to false if the application calling into this library has insufficient privileges to lock memory. Code using this package could also bind this setting to a flag by using: flag.BoolVar(&crypto.UseMlock, "lock-memory", true, "lock keys in memory") */ var UseMlock = true /* Key protects some arbitrary buffer of cryptographic material. Its methods ensure that the Key's data is locked in memory before being used (if UseMlock is set to true), and is wiped and unlocked after use (via the Wipe() method). This data is never accessed outside of the fscrypt/crypto package (except for the UnsafeData method). If a key is successfully created, the Wipe() method should be called after it's use. For example: func UseKeyFromStdin() error { key, err := NewKeyFromReader(os.Stdin) if err != nil { return err } defer key.Wipe() // Do stuff with key return nil } The Wipe() method will also be called when a key is garbage collected; however, it is best practice to clear the key as soon as possible, so it spends a minimal amount of time in memory. Note that Key is not thread safe, as a key could be wiped while another thread is using it. Also, calling Wipe() from two threads could cause an error as memory could be freed twice. */ type Key struct { data []byte } // newBlankKey constructs a blank key of a specified length and returns an error // if we are unable to allocate or lock the necessary memory. func newBlankKey(length int) (*Key, error) { if length == 0 { return &Key{data: nil}, nil } else if length < 0 { return nil, errors.Wrapf(ErrNegativeLength, "length of %d requested", length) } flags := keyMmapFlags if UseMlock { flags |= unix.MAP_LOCKED } // See MAP_ANONYMOUS in http://man7.org/linux/man-pages/man2/mmap.2.html data, err := unix.Mmap(-1, 0, length, keyProtection, flags) if err == unix.EAGAIN { return nil, ErrKeyLock } if err != nil { log.Printf("unix.Mmap() with length=%d failed: %v", length, err) return nil, ErrKeyAlloc } key := &Key{data: data} // Backup finalizer in case user forgets to "defer key.Wipe()" runtime.SetFinalizer(key, (*Key).Wipe) return key, nil } // Wipe destroys a Key by zeroing and freeing the memory. The data is zeroed // even if Wipe returns an error, which occurs if we are unable to unlock or // free the key memory. Wipe does nothing if the key is already wiped or is nil. func (key *Key) Wipe() error { // We do nothing if key or key.data is nil so that Wipe() is idempotent // and so Wipe() can be called on keys which have already been cleared. if key != nil && key.data != nil { data := key.data key.data = nil for i := range data { data[i] = 0 } if err := unix.Munmap(data); err != nil { log.Printf("unix.Munmap() failed: %v", err) return ErrKeyFree } } return nil } // Len is the underlying data buffer's length. func (key *Key) Len() int { return len(key.data) } // Equals compares the contents of two keys, returning true if they have the same // key data. This function runs in constant time. func (key *Key) Equals(key2 *Key) bool { return subtle.ConstantTimeCompare(key.data, key2.data) == 1 } // resize returns a new key with size requestedSize and the appropriate data // copied over. The original data is wiped. This method does nothing and returns // itself if the key's length equals requestedSize. func (key *Key) resize(requestedSize int) (*Key, error) { if key.Len() == requestedSize { return key, nil } defer key.Wipe() resizedKey, err := newBlankKey(requestedSize) if err != nil { return nil, err } copy(resizedKey.data, key.data) return resizedKey, nil } // UnsafeToCString makes a copy of the string's data into a null-terminated C // string allocated by C. Note that this method is unsafe as this C copy has no // locking or wiping functionality. The key shouldn't contain any `\0` bytes. func (key *Key) UnsafeToCString() unsafe.Pointer { size := C.size_t(key.Len()) data := C.calloc(size+1, 1) C.memcpy(data, util.Ptr(key.data), size) return data } // NewKeyFromCString creates of a copy of some C string's data in a key. Note // that the original C string is not modified at all, so steps must be taken to // ensure that this original copy is secured. func NewKeyFromCString(str unsafe.Pointer) (*Key, error) { size := C.strlen((*C.char)(str)) key, err := newBlankKey(int(size)) if err != nil { return nil, err } C.memcpy(util.Ptr(key.data), str, size) return key, nil } // NewKeyFromReader constructs a key of arbitrary length by reading from reader // until hitting EOF. func NewKeyFromReader(reader io.Reader) (*Key, error) { // Use an initial key size of a page. As Mmap allocates a page anyway, // there isn't much additional overhead from starting with a whole page. key, err := newBlankKey(os.Getpagesize()) if err != nil { return nil, err } totalBytesRead := 0 for { bytesRead, err := reader.Read(key.data[totalBytesRead:]) totalBytesRead += bytesRead switch err { case nil: // Need to continue reading. Grow key if necessary if key.Len() == totalBytesRead { if key, err = key.resize(2 * key.Len()); err != nil { return nil, err } } case io.EOF: // Getting the EOF error means we are done return key.resize(totalBytesRead) default: // Fail if Read() has a failure key.Wipe() return nil, err } } } // NewFixedLengthKeyFromReader constructs a key with a specified length by // reading exactly length bytes from reader. func NewFixedLengthKeyFromReader(reader io.Reader, length int) (*Key, error) { key, err := newBlankKey(length) if err != nil { return nil, err } if _, err := io.ReadFull(reader, key.data); err != nil { key.Wipe() return nil, err } return key, nil } // InsertPolicyKey puts the provided policy key into the kernel keyring with the // provided description, and type logon. The key must be a policy key. func InsertPolicyKey(key *Key, description string, target *user.User) error { if err := util.CheckValidLength(metadata.PolicyKeyLen, key.Len()); err != nil { return errors.Wrap(err, "policy key") } // Create our payload (containing an FscryptKey) payload, err := newBlankKey(int(unsafe.Sizeof(unix.FscryptKey{}))) if err != nil { return err } defer payload.Wipe() // Cast the payload to an FscryptKey so we can initialize the fields. fscryptKey := (*unix.FscryptKey)(util.Ptr(payload.data)) // Mode is ignored by the kernel fscryptKey.Mode = 0 fscryptKey.Size = metadata.PolicyKeyLen copy(fscryptKey.Raw[:], key.data) return security.InsertKey(payload.data, description, target) } var ( // The recovery code is base32 with a dash between each block of 8 characters. encoding = base32.StdEncoding blockSize = 8 separator = []byte("-") encodedLength = encoding.EncodedLen(metadata.PolicyKeyLen) decodedLength = encoding.DecodedLen(encodedLength) // RecoveryCodeLength is the number of bytes in every recovery code RecoveryCodeLength = (encodedLength/blockSize)*(blockSize+len(separator)) - len(separator) ) // WriteRecoveryCode outputs key's recovery code to the provided writer. // WARNING: This recovery key is enough to derive the original key, so it must // be given the same level of protection as a raw cryptographic key. func WriteRecoveryCode(key *Key, writer io.Writer) error { if err := util.CheckValidLength(metadata.PolicyKeyLen, key.Len()); err != nil { return errors.Wrap(err, "recovery key") } // We store the base32 encoded data (without separators) in a temp key encodedKey, err := newBlankKey(encodedLength) if err != nil { return err } defer encodedKey.Wipe() encoding.Encode(encodedKey.data, key.data) w := util.NewErrWriter(writer) // Write the blocks with separators between them w.Write(encodedKey.data[:blockSize]) for blockStart := blockSize; blockStart < encodedLength; blockStart += blockSize { w.Write(separator) blockEnd := util.MinInt(blockStart+blockSize, encodedLength) w.Write(encodedKey.data[blockStart:blockEnd]) } // If any writes have failed, return the error return w.Err() } // ReadRecoveryCode gets the recovery code from the provided reader and returns // the corresponding cryptographic key. // WARNING: This recovery key is enough to derive the original key, so it must // be given the same level of protection as a raw cryptographic key. func ReadRecoveryCode(reader io.Reader) (*Key, error) { // We store the base32 encoded data (without separators) in a temp key encodedKey, err := newBlankKey(encodedLength) if err != nil { return nil, err } defer encodedKey.Wipe() r := util.NewErrReader(reader) // Read the other blocks, checking the separators between them r.Read(encodedKey.data[:blockSize]) inputSeparator := make([]byte, len(separator)) for blockStart := blockSize; blockStart < encodedLength; blockStart += blockSize { r.Read(inputSeparator) if r.Err() == nil && !bytes.Equal(separator, inputSeparator) { err = errors.Wrapf(ErrRecoveryCode, "invalid separator %q", inputSeparator) return nil, err } blockEnd := util.MinInt(blockStart+blockSize, encodedLength) r.Read(encodedKey.data[blockStart:blockEnd]) } // If any reads have failed, return the error if r.Err() != nil { return nil, errors.Wrapf(ErrRecoveryCode, "read error %v", r.Err()) } // Now we decode the key, resizing if necessary decodedKey, err := newBlankKey(decodedLength) if err != nil { return nil, err } if _, err = encoding.Decode(decodedKey.data, encodedKey.data); err != nil { return nil, errors.Wrap(ErrRecoveryCode, err.Error()) } return decodedKey.resize(metadata.PolicyKeyLen) } fscrypt-0.2.5/crypto/rand.go000066400000000000000000000043341357032351700160150ustar00rootroot00000000000000/* * rand.go - Reader used to generate secure random data for fscrypt. * * 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 crypto import ( "io" "github.com/pkg/errors" "golang.org/x/sys/unix" ) // NewRandomBuffer uses the Linux Getrandom() syscall to create random bytes. If // the operating system has insufficient randomness, the buffer creation will // fail. This is an improvement over Go's built-in crypto/rand which will still // return bytes if the system has insufficiency entropy. // See: https://github.com/golang/go/issues/19274 // // While this syscall was only introduced in Kernel v3.17, it predates the // introduction of filesystem encryption, so it introduces no additional // compatibility issues. func NewRandomBuffer(length int) ([]byte, error) { buffer := make([]byte, length) if _, err := io.ReadFull(randReader{}, buffer); err != nil { return nil, err } return buffer, nil } // NewRandomKey creates a random key of the specified length. This function uses // the same random number generation process as NewRandomBuffer. func NewRandomKey(length int) (*Key, error) { return NewFixedLengthKeyFromReader(randReader{}, length) } // randReader just calls into Getrandom, so no internal data is needed. type randReader struct{} func (r randReader) Read(buffer []byte) (int, error) { n, err := unix.Getrandom(buffer, unix.GRND_NONBLOCK) switch err { case nil: return n, nil case unix.EAGAIN: return 0, errors.Wrap(ErrGetrandomFail, "insufficient entropy in pool") case unix.ENOSYS: return 0, errors.Wrap(ErrGetrandomFail, "kernel must be v3.17 or later") default: return 0, errors.Wrap(ErrGetrandomFail, err.Error()) } } fscrypt-0.2.5/crypto/recovery_test.go000066400000000000000000000125431357032351700177670ustar00rootroot00000000000000/* * recovery_test.go - tests for recovery codes in the crypto package * tests key wrapping/unwrapping and key generation * * 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 crypto import ( "bytes" "fmt" "testing" "github.com/google/fscrypt/metadata" ) const fakeSecretRecoveryCode = "EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJQ=" var fakeSecretKey, _ = makeKey(38, metadata.PolicyKeyLen) // Note that this function is INSECURE. FOR TESTING ONLY func getRecoveryCodeFromKey(key *Key) ([]byte, error) { var buf bytes.Buffer if err := WriteRecoveryCode(key, &buf); err != nil { return nil, err } return buf.Bytes(), nil } func getRandomRecoveryCodeBuffer() ([]byte, error) { key, err := NewRandomKey(metadata.PolicyKeyLen) if err != nil { return nil, err } defer key.Wipe() return getRecoveryCodeFromKey(key) } func getKeyFromRecoveryCode(buf []byte) (*Key, error) { return ReadRecoveryCode(bytes.NewReader(buf)) } // Given a key, make a recovery code from that key, use that code to rederive // another key and check if they are the same. func testKeyEncodeDecode(key *Key) error { buf, err := getRecoveryCodeFromKey(key) if err != nil { return err } key2, err := getKeyFromRecoveryCode(buf) if err != nil { return err } defer key2.Wipe() if !bytes.Equal(key.data, key2.data) { return fmt.Errorf("encoding then decoding %x didn't yield the same key", key.data) } return nil } // Given a recovery code, make a key from that recovery code, use that key to // rederive another recovery code and check if they are the same. func testRecoveryDecodeEncode(buf []byte) error { key, err := getKeyFromRecoveryCode(buf) if err != nil { return err } defer key.Wipe() buf2, err := getRecoveryCodeFromKey(key) if err != nil { return err } if !bytes.Equal(buf, buf2) { return fmt.Errorf("decoding then encoding %x didn't yield the same key", buf) } return nil } func TestGetRandomRecoveryString(t *testing.T) { b, err := getRandomRecoveryCodeBuffer() if err != nil { t.Fatal(err) } t.Log(string(b)) // t.Fail() // Uncomment to see an example random recovery code } func TestFakeSecretKey(t *testing.T) { buf, err := getRecoveryCodeFromKey(fakeSecretKey) if err != nil { t.Fatal(err) } recoveryCode := string(buf) if recoveryCode != fakeSecretRecoveryCode { t.Errorf("got '%s' instead of '%s'", recoveryCode, fakeSecretRecoveryCode) } } func TestEncodeDecode(t *testing.T) { key, err := NewRandomKey(metadata.PolicyKeyLen) if err != nil { t.Fatal(err) } defer key.Wipe() if err = testKeyEncodeDecode(key); err != nil { t.Error(err) } } func TestDecodeEncode(t *testing.T) { buf, err := getRandomRecoveryCodeBuffer() if err != nil { t.Fatal(err) } if err = testRecoveryDecodeEncode(buf); err != nil { t.Error(err) } } func TestWrongLengthError(t *testing.T) { key, err := NewRandomKey(metadata.PolicyKeyLen - 1) if err != nil { t.Fatal(err) } defer key.Wipe() if _, err = getRecoveryCodeFromKey(key); err == nil { t.Error("key with wrong length should have failed to encode") } } func TestBadCharacterError(t *testing.T) { buf, err := getRandomRecoveryCodeBuffer() if err != nil { t.Fatal(err) } // Lowercase letters not allowed buf[3] = 'k' if key, err := getKeyFromRecoveryCode(buf); err == nil { key.Wipe() t.Error("lowercase letters should make decoding fail") } } func TestBadEndCharacterError(t *testing.T) { buf, err := getRandomRecoveryCodeBuffer() if err != nil { t.Fatal(err) } // Separator must be '-' buf[blockSize] = '_' if key, err := getKeyFromRecoveryCode(buf); err == nil { key.Wipe() t.Error("any separator that isn't '-' should make decoding fail") } } func BenchmarkEncode(b *testing.B) { b.StopTimer() key, err := NewRandomKey(metadata.PolicyKeyLen) if err != nil { b.Fatal(err) } defer key.Wipe() b.StartTimer() for n := 0; n < b.N; n++ { if _, err = getRecoveryCodeFromKey(key); err != nil { b.Fatal(err) } } } func BenchmarkDecode(b *testing.B) { b.StopTimer() buf, err := getRandomRecoveryCodeBuffer() if err != nil { b.Fatal(err) } b.StartTimer() for n := 0; n < b.N; n++ { key, err := getKeyFromRecoveryCode(buf) if err != nil { b.Fatal(err) } key.Wipe() } } func BenchmarkEncodeDecode(b *testing.B) { b.StopTimer() key, err := NewRandomKey(metadata.PolicyKeyLen) if err != nil { b.Fatal(err) } defer key.Wipe() b.StartTimer() for n := 0; n < b.N; n++ { if err = testKeyEncodeDecode(key); err != nil { b.Fatal(err) } } } func BenchmarkDecodeEncode(b *testing.B) { b.StopTimer() buf, err := getRandomRecoveryCodeBuffer() if err != nil { b.Fatal(err) } b.StartTimer() for n := 0; n < b.N; n++ { if err = testRecoveryDecodeEncode(buf); err != nil { b.Fatal(err) } } } fscrypt-0.2.5/filesystem/000077500000000000000000000000001357032351700154025ustar00rootroot00000000000000fscrypt-0.2.5/filesystem/filesystem.go000066400000000000000000000414621357032351700201240ustar00rootroot00000000000000/* * filesystem.go - Contains the functionality for a specific filesystem. This * includes the commands to setup the filesystem, apply policies, and locate * metadata. * * 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 filesystem deals with the structure of the files on disk used to // store the metadata for fscrypt. Specifically, this package includes: // - mountpoint management (mountpoint.go) // - querying existing mounted filesystems // - getting filesystems from a UUID // - finding the filesystem for a specific path // - metadata organization (filesystem.go) // - setting up a mounted filesystem for use with fscrypt // - adding/querying/deleting metadata // - making links to other filesystems' metadata // - following links to get data from other filesystems package filesystem import ( "fmt" "io/ioutil" "log" "os" "path/filepath" "strings" "github.com/golang/protobuf/proto" "github.com/pkg/errors" "golang.org/x/sys/unix" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/util" ) // Filesystem error values var ( ErrNotAMountpoint = errors.New("not a mountpoint") ErrAlreadySetup = errors.New("already setup for use with fscrypt") ErrNotSetup = errors.New("not setup for use with fscrypt") ErrNoMetadata = errors.New("could not find metadata") ErrLinkedProtector = errors.New("not a regular protector") ErrInvalidMetadata = errors.New("provided metadata is invalid") ErrFollowLink = errors.New("cannot follow filesystem link") ErrLinkExpired = errors.New("no longer exists on linked filesystem") ErrMakeLink = util.SystemError("cannot create filesystem link") ErrGlobalMountInfo = util.SystemError("creating global mountpoint list failed") ErrCorruptMetadata = util.SystemError("on-disk metadata is corrupt") ) // Mount contains information for a specific mounted filesystem. // Path - Absolute path where the directory is mounted // Filesystem - Name of the mounted filesystem // Options - List of options used when mounting the filesystem // Device - Device for filesystem (empty string if we cannot find one) // // In order to use a Mount to store fscrypt metadata, some directories must be // setup first. Specifically, the directories created look like: // // └── .fscrypt // ├── policies // └── protectors // // These "policies" and "protectors" directories will contain files that are // the corresponding metadata structures for policies and protectors. The public // interface includes functions for setting up these directories and Adding, // Getting, and Removing these files. // // There is also the ability to reference another filesystem's metadata. This is // used when a Policy on filesystem A is protected with Protector on filesystem // B. In this scenario, we store a "link file" in the protectors directory whose // contents look like "UUID=3a6d9a76-47f0-4f13-81bf-3332fbe984fb". type Mount struct { Path string Filesystem string Options []string Device string } // PathSorter allows mounts to be sorted by Path. type PathSorter []*Mount func (p PathSorter) Len() int { return len(p) } func (p PathSorter) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p PathSorter) Less(i, j int) bool { return p[i].Path < p[j].Path } const ( // Names of the various directories used in fscrypt baseDirName = ".fscrypt" policyDirName = "policies" protectorDirName = "protectors" tempPrefix = ".tmp" linkFileExtension = ".link" // The base directory should be read-only (except for the creator) basePermissions = 0755 // The subdirectories should be writable to everyone, but they have the // sticky bit set so users cannot delete other users' metadata. dirPermissions = os.ModeSticky | 0777 // The metadata files are globally visible, but can only be deleted by // the user that created them filePermissions = 0644 ) func (m *Mount) String() string { return fmt.Sprintf(`%s Filsystem: %s Options: %v Device: %s`, m.Path, m.Filesystem, m.Options, m.Device) } // BaseDir returns the path of the base fscrypt directory on this filesystem. func (m *Mount) BaseDir() string { return filepath.Join(m.Path, baseDirName) } // ProtectorDir returns the directory containing the protector metadata. func (m *Mount) ProtectorDir() string { return filepath.Join(m.BaseDir(), protectorDirName) } // protectorPath returns the full path to a regular protector file with the // specified descriptor. func (m *Mount) protectorPath(descriptor string) string { return filepath.Join(m.ProtectorDir(), descriptor) } // linkedProtectorPath returns the full path to a linked protector file with the // specified descriptor. func (m *Mount) linkedProtectorPath(descriptor string) string { return m.protectorPath(descriptor) + linkFileExtension } // PolicyDir returns the directory containing the policy metadata. func (m *Mount) PolicyDir() string { return filepath.Join(m.BaseDir(), policyDirName) } // policyPath returns the full path to a regular policy file with the // specified descriptor. func (m *Mount) policyPath(descriptor string) string { return filepath.Join(m.PolicyDir(), descriptor) } // tempMount creates a temporary Mount under the main directory. The path for // the returned tempMount should be removed by the caller. func (m *Mount) tempMount() (*Mount, error) { trashDir, err := ioutil.TempDir(m.Path, tempPrefix) return &Mount{Path: trashDir}, err } // err modifies an error to contain the path of this filesystem. func (m *Mount) err(err error) error { return errors.Wrapf(err, "filesystem %s", m.Path) } // CheckSupport returns an error if this filesystem does not support filesystem // encryption. func (m *Mount) CheckSupport() error { return m.err(metadata.CheckSupport(m.Path)) } // CheckSetup returns an error if all the fscrypt metadata directories do not // exist. Will log any unexpected errors or incorrect permissions. func (m *Mount) CheckSetup() error { // Run all the checks so we will always get all the warnings baseGood := isDirCheckPerm(m.BaseDir(), basePermissions) policyGood := isDirCheckPerm(m.PolicyDir(), dirPermissions) protectorGood := isDirCheckPerm(m.ProtectorDir(), dirPermissions) if baseGood && policyGood && protectorGood { return nil } return m.err(ErrNotSetup) } // makeDirectories creates the three metadata directories with the correct // permissions. Note that this function overrides the umask. func (m *Mount) makeDirectories() error { // Zero the umask so we get the permissions we want oldMask := unix.Umask(0) defer func() { unix.Umask(oldMask) }() if err := os.Mkdir(m.BaseDir(), basePermissions); err != nil { return err } if err := os.Mkdir(m.PolicyDir(), dirPermissions); err != nil { return err } return os.Mkdir(m.ProtectorDir(), dirPermissions) } // Setup sets up the filesystem for use with fscrypt. Note that this merely // creates the appropriate files on the filesystem. It does not actually modify // the filesystem's feature flags. This operation is atomic; it either succeeds // or no files in the baseDir are created. func (m *Mount) Setup() error { if m.CheckSetup() == nil { return m.err(ErrAlreadySetup) } // We build the directories under a temp Mount and then move into place. temp, err := m.tempMount() if err != nil { return m.err(err) } defer os.RemoveAll(temp.Path) if err = temp.makeDirectories(); err != nil { return m.err(err) } // Atomically move directory into place. return m.err(os.Rename(temp.BaseDir(), m.BaseDir())) } // RemoveAllMetadata removes all the policy and protector metadata from the // filesystem. This operation is atomic; it either succeeds or no files in the // baseDir are removed. // WARNING: Will cause data loss if the metadata is used to encrypt // directories (this could include directories on other filesystems). func (m *Mount) RemoveAllMetadata() error { if err := m.CheckSetup(); err != nil { return err } // temp will hold the old metadata temporarily temp, err := m.tempMount() if err != nil { return m.err(err) } defer os.RemoveAll(temp.Path) // Move directory into temp (to be destroyed on defer) return m.err(os.Rename(m.BaseDir(), temp.BaseDir())) } func syncDirectory(dirPath string) error { dirFile, err := os.Open(dirPath) if err != nil { return err } if err = dirFile.Sync(); err != nil { dirFile.Close() return err } return dirFile.Close() } // writeDataAtomic writes the data to the path such that the data is either // written to stable storage or an error is returned. func (m *Mount) writeDataAtomic(path string, data []byte) error { // Write the data to a temporary file, sync it, then rename into place // so that the operation will be atomic. dirPath := filepath.Dir(path) tempFile, err := ioutil.TempFile(dirPath, tempPrefix) if err != nil { return err } defer os.Remove(tempFile.Name()) // TempFile() creates the file with mode 0600. Change it to 0644. if err = tempFile.Chmod(filePermissions); err != nil { tempFile.Close() return err } if _, err = tempFile.Write(data); err != nil { tempFile.Close() return err } if err = tempFile.Sync(); err != nil { tempFile.Close() return err } if err = tempFile.Close(); err != nil { return err } if err = os.Rename(tempFile.Name(), path); err != nil { return err } // Ensure the rename has been persisted before returning success. return syncDirectory(dirPath) } // addMetadata writes the metadata structure to the file with the specified // path. This will overwrite any existing data. The operation is atomic. func (m *Mount) addMetadata(path string, md metadata.Metadata) error { if err := md.CheckValidity(); err != nil { return errors.Wrap(ErrInvalidMetadata, err.Error()) } data, err := proto.Marshal(md) if err != nil { return err } log.Printf("writing metadata to %q", path) return m.writeDataAtomic(path, data) } // getMetadata reads the metadata structure from the file with the specified // path. Only reads normal metadata files, not linked metadata. func (m *Mount) getMetadata(path string, md metadata.Metadata) error { data, err := ioutil.ReadFile(path) if err != nil { log.Printf("could not read metadata at %q", path) if os.IsNotExist(err) { return errors.Wrapf(ErrNoMetadata, "descriptor %s", filepath.Base(path)) } return err } if err := proto.Unmarshal(data, md); err != nil { return errors.Wrap(ErrCorruptMetadata, err.Error()) } if err := md.CheckValidity(); err != nil { log.Printf("metadata at %q is not valid", path) return errors.Wrap(ErrCorruptMetadata, err.Error()) } log.Printf("successfully read metadata from %q", path) return nil } // removeMetadata deletes the metadata struct from the file with the specified // path. Works with regular or linked metadata. func (m *Mount) removeMetadata(path string) error { if err := os.Remove(path); err != nil { log.Printf("could not remove metadata at %q", path) if os.IsNotExist(err) { return errors.Wrapf(ErrNoMetadata, "descriptor %s", filepath.Base(path)) } return err } log.Printf("successfully removed metadata at %q", path) return nil } // AddProtector adds the protector metadata to this filesystem's storage. This // will overwrite the value of an existing protector with this descriptor. This // will fail with ErrLinkedProtector if a linked protector with this descriptor // already exists on the filesystem. func (m *Mount) AddProtector(data *metadata.ProtectorData) error { if err := m.CheckSetup(); err != nil { return err } if isRegularFile(m.linkedProtectorPath(data.ProtectorDescriptor)) { return m.err(ErrLinkedProtector) } path := m.protectorPath(data.ProtectorDescriptor) return m.err(m.addMetadata(path, data)) } // AddLinkedProtector adds a link in this filesystem to the protector metadata // in the dest filesystem. func (m *Mount) AddLinkedProtector(descriptor string, dest *Mount) error { if err := m.CheckSetup(); err != nil { return err } // Check that the link is good (descriptor exists, filesystem has UUID). if _, err := dest.GetRegularProtector(descriptor); err != nil { return err } // Right now, we only make links using UUIDs. link, err := makeLink(dest, "UUID") if err != nil { return dest.err(err) } path := m.linkedProtectorPath(descriptor) return m.err(m.writeDataAtomic(path, []byte(link))) } // GetRegularProtector looks up the protector metadata by descriptor. This will // fail with ErrNoMetadata if the descriptor is a linked protector. func (m *Mount) GetRegularProtector(descriptor string) (*metadata.ProtectorData, error) { if err := m.CheckSetup(); err != nil { return nil, err } data := new(metadata.ProtectorData) path := m.protectorPath(descriptor) return data, m.err(m.getMetadata(path, data)) } // GetProtector returns the Mount of the filesystem containing the information // and that protector's data. If the descriptor is a regular (not linked) // protector, the mount will return itself. func (m *Mount) GetProtector(descriptor string) (*Mount, *metadata.ProtectorData, error) { if err := m.CheckSetup(); err != nil { return nil, nil, err } // Get the link data from the link file link, err := ioutil.ReadFile(m.linkedProtectorPath(descriptor)) if err != nil { // If the link doesn't exist, try for a regular protector. if os.IsNotExist(err) { data, err := m.GetRegularProtector(descriptor) return m, data, err } return nil, nil, m.err(err) } // As the link could refer to multiple filesystems, we check each one // for valid metadata. mnts, err := getMountsFromLink(string(link)) if err != nil { return nil, nil, m.err(err) } for _, mnt := range mnts { if data, err := mnt.GetRegularProtector(descriptor); err != nil { log.Print(err) } else { return mnt, data, nil } } return nil, nil, m.err(errors.Wrapf(ErrLinkExpired, "protector %s", descriptor)) } // RemoveProtector deletes the protector metadata (or a link to another // filesystem's metadata) from the filesystem storage. func (m *Mount) RemoveProtector(descriptor string) error { if err := m.CheckSetup(); err != nil { return err } // We first try to remove the linkedProtector. If that metadata does not // exist, we try to remove the normal protector. err := m.removeMetadata(m.linkedProtectorPath(descriptor)) if errors.Cause(err) == ErrNoMetadata { err = m.removeMetadata(m.protectorPath(descriptor)) } return m.err(err) } // ListProtectors lists the descriptors of all protectors on this filesystem. // This does not include linked protectors. func (m *Mount) ListProtectors() ([]string, error) { if err := m.CheckSetup(); err != nil { return nil, err } protectors, err := m.listDirectory(m.ProtectorDir()) return protectors, m.err(err) } // AddPolicy adds the policy metadata to the filesystem storage. func (m *Mount) AddPolicy(data *metadata.PolicyData) error { if err := m.CheckSetup(); err != nil { return err } return m.err(m.addMetadata(m.policyPath(data.KeyDescriptor), data)) } // GetPolicy looks up the policy metadata by descriptor. func (m *Mount) GetPolicy(descriptor string) (*metadata.PolicyData, error) { if err := m.CheckSetup(); err != nil { return nil, err } data := new(metadata.PolicyData) return data, m.err(m.getMetadata(m.policyPath(descriptor), data)) } // RemovePolicy deletes the policy metadata from the filesystem storage. func (m *Mount) RemovePolicy(descriptor string) error { if err := m.CheckSetup(); err != nil { return err } return m.err(m.removeMetadata(m.policyPath(descriptor))) } // ListPolicies lists the descriptors of all policies on this filesystem. func (m *Mount) ListPolicies() ([]string, error) { if err := m.CheckSetup(); err != nil { return nil, err } policies, err := m.listDirectory(m.PolicyDir()) return policies, m.err(err) } // listDirectory returns a list of descriptors for a metadata directory, // including files which are links to other filesystem's metadata. func (m *Mount) listDirectory(directoryPath string) ([]string, error) { log.Printf("listing descriptors in %q", directoryPath) dir, err := os.Open(directoryPath) if err != nil { return nil, err } defer dir.Close() names, err := dir.Readdirnames(-1) if err != nil { return nil, err } var descriptors []string for _, name := range names { // Be sure to include links as well descriptors = append(descriptors, strings.TrimSuffix(name, linkFileExtension)) } log.Printf("found %d descriptor(s)", len(descriptors)) return descriptors, nil } fscrypt-0.2.5/filesystem/filesystem_test.go000066400000000000000000000172111357032351700211560ustar00rootroot00000000000000/* * filesystem_test.go - Tests for reading/writing metadata to disk. * * 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 filesystem import ( "os" "path/filepath" "testing" "github.com/golang/protobuf/proto" "github.com/pkg/errors" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/util" ) var ( fakeProtectorKey, _ = crypto.NewRandomKey(metadata.InternalKeyLen) fakePolicyKey, _ = crypto.NewRandomKey(metadata.PolicyKeyLen) wrappedProtectorKey, _ = crypto.Wrap(fakeProtectorKey, fakeProtectorKey) wrappedPolicyKey, _ = crypto.Wrap(fakeProtectorKey, fakePolicyKey) ) // Gets the mount corresponding to the integration test path. func getTestMount(t *testing.T) (*Mount, error) { mountpoint, err := util.TestRoot() if err != nil { t.Skip(err) } return GetMount(mountpoint) } func getFakeProtector() *metadata.ProtectorData { return &metadata.ProtectorData{ ProtectorDescriptor: "fedcba9876543210", Name: "goodProtector", Source: metadata.SourceType_raw_key, WrappedKey: wrappedProtectorKey, } } func getFakePolicy() *metadata.PolicyData { return &metadata.PolicyData{ KeyDescriptor: "0123456789abcdef", Options: metadata.DefaultOptions, WrappedPolicyKeys: []*metadata.WrappedPolicyKey{ { ProtectorDescriptor: "fedcba9876543210", WrappedKey: wrappedPolicyKey, }, }, } } // Gets the mount and sets it up func getSetupMount(t *testing.T) (*Mount, error) { mnt, err := getTestMount(t) if err != nil { return nil, err } return mnt, mnt.Setup() } // Tests that the setup works and creates the correct files func TestSetup(t *testing.T) { mnt, err := getSetupMount(t) if err != nil { t.Fatal(err) } if err := mnt.CheckSetup(); err != nil { t.Error(err) } os.RemoveAll(mnt.BaseDir()) } // Tests that we can remove all of the metadata func TestRemoveAllMetadata(t *testing.T) { mnt, err := getSetupMount(t) if err != nil { t.Fatal(err) } if err = mnt.RemoveAllMetadata(); err != nil { t.Fatal(err) } if isDir(mnt.BaseDir()) { t.Error("metadata was not removed") } } // Adding a good Protector should succeed, adding a bad one should fail func TestAddProtector(t *testing.T) { mnt, err := getSetupMount(t) if err != nil { t.Fatal(err) } defer mnt.RemoveAllMetadata() protector := getFakeProtector() if err = mnt.AddProtector(protector); err != nil { t.Error(err) } // Change the source to bad one, or one that requires hashing costs protector.Source = metadata.SourceType_default if mnt.AddProtector(protector) == nil { t.Error("bad source for a descriptor should make metadata invalid") } protector.Source = metadata.SourceType_custom_passphrase if mnt.AddProtector(protector) == nil { t.Error("protectors using passphrases should require hashing costs") } protector.Source = metadata.SourceType_raw_key // Use a bad wrapped key protector.WrappedKey = wrappedPolicyKey if mnt.AddProtector(protector) == nil { t.Error("bad length for protector keys should make metadata invalid") } protector.WrappedKey = wrappedProtectorKey // Change the descriptor (to a bad length) protector.ProtectorDescriptor = "abcde" if mnt.AddProtector(protector) == nil { t.Error("bad descriptor length should make metadata invalid") } } // Adding a good Policy should succeed, adding a bad one should fail func TestAddPolicy(t *testing.T) { mnt, err := getSetupMount(t) if err != nil { t.Fatal(err) } defer mnt.RemoveAllMetadata() policy := getFakePolicy() if err = mnt.AddPolicy(policy); err != nil { t.Error(err) } // Bad encryption options should make policy invalid policy.Options.Padding = 7 if mnt.AddPolicy(policy) == nil { t.Error("padding not a power of 2 should make metadata invalid") } policy.Options.Padding = 16 policy.Options.Filenames = metadata.EncryptionOptions_default if mnt.AddPolicy(policy) == nil { t.Error("encryption mode not set should make metadata invalid") } policy.Options.Filenames = metadata.EncryptionOptions_AES_256_CTS // Use a bad wrapped key policy.WrappedPolicyKeys[0].WrappedKey = wrappedProtectorKey if mnt.AddPolicy(policy) == nil { t.Error("bad length for policy keys should make metadata invalid") } policy.WrappedPolicyKeys[0].WrappedKey = wrappedPolicyKey // Change the descriptor (to a bad length) policy.KeyDescriptor = "abcde" if mnt.AddPolicy(policy) == nil { t.Error("bad descriptor length should make metadata invalid") } } // Tests that we can set a policy and get it back func TestSetPolicy(t *testing.T) { mnt, err := getSetupMount(t) if err != nil { t.Fatal(err) } defer mnt.RemoveAllMetadata() policy := getFakePolicy() if err = mnt.AddPolicy(policy); err != nil { t.Fatal(err) } realPolicy, err := mnt.GetPolicy(policy.KeyDescriptor) if err != nil { t.Fatal(err) } if !proto.Equal(realPolicy, policy) { t.Errorf("policy %+v does not equal expected policy %+v", realPolicy, policy) } } // Tests that we can set a normal protector and get it back func TestSetProtector(t *testing.T) { mnt, err := getSetupMount(t) if err != nil { t.Fatal(err) } defer mnt.RemoveAllMetadata() protector := getFakeProtector() if err = mnt.AddProtector(protector); err != nil { t.Fatal(err) } realProtector, err := mnt.GetRegularProtector(protector.ProtectorDescriptor) if err != nil { t.Fatal(err) } if !proto.Equal(realProtector, protector) { t.Errorf("protector %+v does not equal expected protector %+v", realProtector, protector) } } // Gets a setup mount and a fake second mount func getTwoSetupMounts(t *testing.T) (realMnt, fakeMnt *Mount, err error) { if realMnt, err = getSetupMount(t); err != nil { return } // Create and setup a fake filesystem fakeMountpoint := filepath.Join(realMnt.Path, "fake") if err = os.MkdirAll(fakeMountpoint, basePermissions); err != nil { return } fakeMnt = &Mount{Path: fakeMountpoint} err = fakeMnt.Setup() return } // Removes all the data from the fake and real filesystems func cleanupTwoMounts(realMnt, fakeMnt *Mount) { realMnt.RemoveAllMetadata() os.RemoveAll(fakeMnt.Path) } // Tests that we can set a linked protector and get it back func TestLinkedProtector(t *testing.T) { realMnt, fakeMnt, err := getTwoSetupMounts(t) if err != nil { t.Fatal(err) } defer cleanupTwoMounts(realMnt, fakeMnt) // Add the protector to the first filesystem protector := getFakeProtector() if err = realMnt.AddProtector(protector); err != nil { t.Fatal(err) } // Add the link to the second filesystem if err = fakeMnt.AddLinkedProtector(protector.ProtectorDescriptor, realMnt); err != nil { t.Fatal(err) } // Get the protector though the second system _, err = fakeMnt.GetRegularProtector(protector.ProtectorDescriptor) if errors.Cause(err) != ErrNoMetadata { t.Fatal(err) } retMnt, retProtector, err := fakeMnt.GetProtector(protector.ProtectorDescriptor) if err != nil { t.Fatal(err) } if retMnt != realMnt { t.Error("mount returned was incorrect") } if !proto.Equal(retProtector, protector) { t.Errorf("protector %+v does not equal expected protector %+v", retProtector, protector) } } fscrypt-0.2.5/filesystem/mountpoint.go000066400000000000000000000205721357032351700201530ustar00rootroot00000000000000/* * mountpoint.go - Contains all the functionality for finding mountpoints and * using UUIDs to refer to them. Specifically, we can find the mountpoint of a * path, get info about a mountpoint, and find mountpoints with a specific UUID. * * 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 filesystem import ( "fmt" "io/ioutil" "log" "os" "path/filepath" "sort" "strings" "sync" "github.com/pkg/errors" ) /* #include // setmntent, getmntent, endmntent // The file containing mountpoints info and how we should read it const char* mountpoints_filename = "/proc/mounts"; const char* read_mode = "r"; */ import "C" var ( // These maps hold data about the state of the system's mountpoints. mountsByPath map[string]*Mount mountsByDevice map[string][]*Mount // Used to make the mount functions thread safe mountMutex sync.Mutex // True if the maps have been successfully initialized. mountsInitialized bool // Supported tokens for filesystem links uuidToken = "UUID" // Location to perform UUID lookup uuidDirectory = "/dev/disk/by-uuid" ) // getMountInfo populates the Mount mappings by parsing the filesystem // description file using the getmntent functions. Returns ErrBadLoad if the // Mount mappings cannot be populated. func getMountInfo() error { if mountsInitialized { return nil } // make new maps mountsByPath = make(map[string]*Mount) mountsByDevice = make(map[string][]*Mount) // Load the mount information from mountpoints_filename fileHandle := C.setmntent(C.mountpoints_filename, C.read_mode) if fileHandle == nil { return errors.Wrapf(ErrGlobalMountInfo, "could not read %q", C.GoString(C.mountpoints_filename)) } defer C.endmntent(fileHandle) for { entry := C.getmntent(fileHandle) // When getmntent returns nil, we have read all of the entries. if entry == nil { mountsInitialized = true return nil } // Create the Mount structure by converting types. mnt := Mount{ Path: C.GoString(entry.mnt_dir), Filesystem: C.GoString(entry.mnt_type), Options: strings.Split(C.GoString(entry.mnt_opts), ","), } // Skip invalid mountpoints var err error if mnt.Path, err = canonicalizePath(mnt.Path); err != nil { log.Printf("getting mnt_dir: %v", err) continue } // We can only use mountpoints that are directories for fscrypt. if !isDir(mnt.Path) { log.Printf("mnt_dir %v: not a directory", mnt.Path) continue } // Note this overrides the info if we have seen the mountpoint // earlier in the file. This is correct behavior because the // filesystems are listed in mount order. mountsByPath[mnt.Path] = &mnt deviceName, err := canonicalizePath(C.GoString(entry.mnt_fsname)) // Only use real valid devices (unlike cgroups, tmpfs, ...) if err == nil && isDevice(deviceName) { mnt.Device = deviceName mountsByDevice[deviceName] = append(mountsByDevice[deviceName], &mnt) } } } // AllFilesystems lists all the Mounts on the current system ordered by path. // Use CheckSetup() to see if they are used with fscrypt. func AllFilesystems() ([]*Mount, error) { mountMutex.Lock() defer mountMutex.Unlock() if err := getMountInfo(); err != nil { return nil, err } mounts := make([]*Mount, 0, len(mountsByPath)) for _, mount := range mountsByPath { mounts = append(mounts, mount) } sort.Sort(PathSorter(mounts)) return mounts, nil } // UpdateMountInfo updates the filesystem mountpoint maps with the current state // of the filesystem mountpoints. Returns error if the initialization fails. func UpdateMountInfo() error { mountMutex.Lock() defer mountMutex.Unlock() mountsInitialized = false return getMountInfo() } // FindMount returns the corresponding Mount object for some path in a // filesystem. Note that in the case of a bind mounts there may be two Mount // objects for the same underlying filesystem. An error is returned if the path // is invalid or we cannot load the required mount data. If a filesystem has // been updated since the last call to one of the mount functions, run // UpdateMountInfo to see changes. func FindMount(path string) (*Mount, error) { path, err := canonicalizePath(path) if err != nil { return nil, err } mountMutex.Lock() defer mountMutex.Unlock() if err = getMountInfo(); err != nil { return nil, err } // Traverse up the directory tree until we find a mountpoint for { if mnt, ok := mountsByPath[path]; ok { return mnt, nil } // Move to the parent directory unless we have reached the root. parent := filepath.Dir(path) if parent == path { return nil, errors.Wrap(ErrNotAMountpoint, path) } path = parent } } // GetMount returns the Mount object with a matching mountpoint. An error is // returned if the path is invalid or we cannot load the required mount data. If // a filesystem has been updated since the last call to one of the mount // functions, run UpdateMountInfo to see changes. func GetMount(mountpoint string) (*Mount, error) { mountpoint, err := canonicalizePath(mountpoint) if err != nil { return nil, err } mountMutex.Lock() defer mountMutex.Unlock() if err = getMountInfo(); err != nil { return nil, err } if mnt, ok := mountsByPath[mountpoint]; ok { return mnt, nil } return nil, errors.Wrap(ErrNotAMountpoint, mountpoint) } // getMountsFromLink returns the Mount objects which match the provided link. // This link is formatted as a tag (e.g. =) similar to how they // appear in "/etc/fstab". Currently, only "UUID" tokens are supported. Note // that this can match multiple Mounts (due to the existence of bind mounts). An // error is returned if the link is invalid or we cannot load the required mount // data. If a filesystem has been updated since the last call to one of the // mount functions, run UpdateMountInfo to see the change. func getMountsFromLink(link string) ([]*Mount, error) { // Parse the link linkComponents := strings.Split(link, "=") if len(linkComponents) != 2 { return nil, errors.Wrapf(ErrFollowLink, "link %q format is invalid", link) } token := linkComponents[0] value := linkComponents[1] if token != uuidToken { return nil, errors.Wrapf(ErrFollowLink, "token type %q not supported", token) } // See if UUID points to an existing device searchPath := filepath.Join(uuidDirectory, value) if filepath.Base(searchPath) != value { return nil, errors.Wrapf(ErrFollowLink, "value %q is not a UUID", value) } devicePath, err := canonicalizePath(searchPath) if err != nil { return nil, errors.Wrapf(ErrFollowLink, "no device with UUID %q", value) } // Lookup mountpoints for device in global store mountMutex.Lock() defer mountMutex.Unlock() if err := getMountInfo(); err != nil { return nil, err } mnts, ok := mountsByDevice[devicePath] if !ok { return nil, errors.Wrapf(ErrFollowLink, "no mounts for device %q", devicePath) } return mnts, nil } // makeLink returns a link of the form = where value is the tag // value for the Mount's device. Currently, only "UUID" tokens are supported. An // error is returned if the mount has no device, or no UUID. func makeLink(mnt *Mount, token string) (string, error) { if token != uuidToken { return "", errors.Wrapf(ErrMakeLink, "token type %q not supported", token) } if mnt.Device == "" { return "", errors.Wrapf(ErrMakeLink, "no device for mount %q", mnt.Path) } dirContents, err := ioutil.ReadDir(uuidDirectory) if err != nil { return "", errors.Wrap(ErrMakeLink, err.Error()) } for _, fileInfo := range dirContents { if fileInfo.Mode()&os.ModeSymlink == 0 { continue // Only interested in UUID symlinks } uuid := fileInfo.Name() devicePath, err := canonicalizePath(filepath.Join(uuidDirectory, uuid)) if err != nil { log.Print(err) continue } if mnt.Device == devicePath { return fmt.Sprintf("%s=%s", uuidToken, uuid), nil } } return "", errors.Wrapf(ErrMakeLink, "device %q has no UUID", mnt.Device) } fscrypt-0.2.5/filesystem/mountpoint_test.go000066400000000000000000000020531357032351700212040ustar00rootroot00000000000000/* * mountpoint_test.go - Tests for reading information about all mountpoints. * * 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 filesystem import ( "testing" ) func TestLoadMountInfo(t *testing.T) { if err := UpdateMountInfo(); err != nil { t.Error(err) } } // Benchmarks how long it takes to update the mountpoint data func BenchmarkLoadFirst(b *testing.B) { for n := 0; n < b.N; n++ { err := UpdateMountInfo() if err != nil { b.Fatal(err) } } } fscrypt-0.2.5/filesystem/path.go000066400000000000000000000050731357032351700166720ustar00rootroot00000000000000/* * path.go - Utility functions for dealing with filesystem paths * * 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 filesystem import ( "log" "os" "path/filepath" "github.com/pkg/errors" ) // We only check the unix permissions and the sticky bit const permMask = os.ModeSticky | os.ModePerm // canonicalizePath turns path into an absolute path without symlinks. func canonicalizePath(path string) (string, error) { path, err := filepath.Abs(path) if err != nil { return "", err } path, err = filepath.EvalSymlinks(path) // Get a better error if we have an invalid path if pathErr, ok := err.(*os.PathError); ok { err = errors.Wrap(pathErr.Err, pathErr.Path) } return path, err } // loggedStat runs os.Stat, but it logs the error if stat returns any error // other than nil or IsNotExist. func loggedStat(name string) (os.FileInfo, error) { info, err := os.Stat(name) if err != nil && !os.IsNotExist(err) { log.Print(err) } return info, err } // isDir returns true if the path exists and is that of a directory. func isDir(path string) bool { info, err := loggedStat(path) return err == nil && info.IsDir() } // isDevice returns true if the path exists and is that of a device. func isDevice(path string) bool { info, err := loggedStat(path) return err == nil && info.Mode()&os.ModeDevice != 0 } // isDirCheckPerm returns true if the path exists and is a directory. If the // specified permissions and sticky bit of mode do not match the path, an error // is logged. func isDirCheckPerm(path string, mode os.FileMode) bool { info, err := loggedStat(path) // Check if directory if err != nil || !info.IsDir() { return false } // Check for bad permissions if info.Mode()&permMask != mode&permMask { log.Printf("directory %s has incorrect permissions", path) } return true } // isRegularFile returns true if the path exists and is that of a regular file. func isRegularFile(path string) bool { info, err := loggedStat(path) return err == nil && info.Mode().IsRegular() } fscrypt-0.2.5/go.mod000066400000000000000000000006071357032351700143270ustar00rootroot00000000000000module github.com/google/fscrypt go 1.12 require ( github.com/golang/protobuf v1.2.0 github.com/pkg/errors v0.8.0 github.com/urfave/cli v1.20.0 golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b ) fscrypt-0.2.5/go.sum000066400000000000000000000024631357032351700143560ustar00rootroot00000000000000github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg= golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b h1:cmOZLU2i7CLArKNViO+ZCQ47wqYFyKEIpbGWp+b6Uoc= golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= fscrypt-0.2.5/metadata/000077500000000000000000000000001357032351700147765ustar00rootroot00000000000000fscrypt-0.2.5/metadata/checks.go000066400000000000000000000131561357032351700165730ustar00rootroot00000000000000/* * checks.go - Some sanity check methods for our metadata structures * * 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 metadata import ( "github.com/golang/protobuf/proto" "github.com/pkg/errors" "github.com/google/fscrypt/util" ) var errNotInitialized = errors.New("not initialized") // Metadata is the interface to all of the protobuf structures that can be // checked for validity. type Metadata interface { CheckValidity() error proto.Message } // CheckValidity ensures the mode has a name and isn't empty. func (m EncryptionOptions_Mode) CheckValidity() error { if m == EncryptionOptions_default { return errNotInitialized } if m.String() == "" { return errors.Errorf("unknown %d", m) } return nil } // CheckValidity ensures the source has a name and isn't empty. func (s SourceType) CheckValidity() error { if s == SourceType_default { return errNotInitialized } if s.String() == "" { return errors.Errorf("unknown %d", s) } return nil } // CheckValidity ensures the hash costs will be accepted by Argon2. func (h *HashingCosts) CheckValidity() error { if h == nil { return errNotInitialized } if h.Time <= 0 { return errors.Errorf("time=%d is not positive", h.Time) } if h.Parallelism <= 0 { return errors.Errorf("parallelism=%d is not positive", h.Parallelism) } minMemory := 8 * h.Parallelism if h.Memory < minMemory { return errors.Errorf("memory=%d is less than minimum (%d)", h.Memory, minMemory) } return nil } // CheckValidity ensures our buffers are the correct length. func (w *WrappedKeyData) CheckValidity() error { if w == nil { return errNotInitialized } if len(w.EncryptedKey) == 0 { return errors.Wrap(errNotInitialized, "encrypted key") } if err := util.CheckValidLength(IVLen, len(w.IV)); err != nil { return errors.Wrap(err, "IV") } return errors.Wrap(util.CheckValidLength(HMACLen, len(w.Hmac)), "HMAC") } // CheckValidity ensures our ProtectorData has the correct fields for its source. func (p *ProtectorData) CheckValidity() error { if p == nil { return errNotInitialized } if err := p.Source.CheckValidity(); err != nil { return errors.Wrap(err, "protector source") } // Source specific checks switch p.Source { case SourceType_pam_passphrase: if p.Uid < 0 { return errors.Errorf("UID=%d is negative", p.Uid) } fallthrough case SourceType_custom_passphrase: if err := p.Costs.CheckValidity(); err != nil { return errors.Wrap(err, "passphrase hashing costs") } if err := util.CheckValidLength(SaltLen, len(p.Salt)); err != nil { return errors.Wrap(err, "passphrase hashing salt") } } // Generic checks if err := p.WrappedKey.CheckValidity(); err != nil { return errors.Wrap(err, "wrapped protector key") } if err := util.CheckValidLength(DescriptorLen, len(p.ProtectorDescriptor)); err != nil { return errors.Wrap(err, "protector descriptor") } err := util.CheckValidLength(InternalKeyLen, len(p.WrappedKey.EncryptedKey)) return errors.Wrap(err, "encrypted protector key") } // CheckValidity ensures each of the options is valid. func (e *EncryptionOptions) CheckValidity() error { if e == nil { return errNotInitialized } if _, ok := util.Index(e.Padding, paddingArray); !ok { return errors.Errorf("padding of %d is invalid", e.Padding) } if err := e.Contents.CheckValidity(); err != nil { return errors.Wrap(err, "contents encryption mode") } return errors.Wrap(e.Filenames.CheckValidity(), "filenames encryption mode") } // CheckValidity ensures the fields are valid and have the correct lengths. func (w *WrappedPolicyKey) CheckValidity() error { if w == nil { return errNotInitialized } if err := w.WrappedKey.CheckValidity(); err != nil { return errors.Wrap(err, "wrapped key") } if err := util.CheckValidLength(PolicyKeyLen, len(w.WrappedKey.EncryptedKey)); err != nil { return errors.Wrap(err, "encrypted key") } err := util.CheckValidLength(DescriptorLen, len(w.ProtectorDescriptor)) return errors.Wrap(err, "wrapping protector descriptor") } // CheckValidity ensures the fields and each wrapped key are valid. func (p *PolicyData) CheckValidity() error { if p == nil { return errNotInitialized } // Check each wrapped key for i, w := range p.WrappedPolicyKeys { if err := w.CheckValidity(); err != nil { return errors.Wrapf(err, "policy key slot %d", i) } } if err := util.CheckValidLength(DescriptorLen, len(p.KeyDescriptor)); err != nil { return errors.Wrap(err, "policy key descriptor") } return errors.Wrap(p.Options.CheckValidity(), "policy options") } // CheckValidity ensures the Config has all the necessary info for its Source. func (c *Config) CheckValidity() error { // General checks if c == nil { return errNotInitialized } if err := c.Source.CheckValidity(); err != nil { return errors.Wrap(err, "default config source") } // Source specific checks switch c.Source { case SourceType_pam_passphrase, SourceType_custom_passphrase: if err := c.HashCosts.CheckValidity(); err != nil { return errors.Wrap(err, "config hashing costs") } } return errors.Wrap(c.Options.CheckValidity(), "config options") } fscrypt-0.2.5/metadata/config.go000066400000000000000000000043021357032351700165710ustar00rootroot00000000000000/* * config.go - Parsing for our global config file. The file is simply the JSON * output of the Config protocol buffer. * * 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 metadata contains all of the on disk structures. // These structures are defined in metadata.proto. The package also // contains functions for manipulating these structures, specifically: // * Reading and Writing the Config file to disk // * Getting and Setting Policies for directories // * Reasonable defaults for a Policy's EncryptionOptions package metadata import ( "io" "strings" "github.com/golang/protobuf/jsonpb" ) // WriteConfig outputs the Config data as nicely formatted JSON func WriteConfig(config *Config, out io.Writer) error { m := jsonpb.Marshaler{ EmitDefaults: true, EnumsAsInts: false, Indent: "\t", OrigName: true, } if err := m.Marshal(out, config); err != nil { return err } _, err := out.Write([]byte{'\n'}) return err } // ReadConfig writes the JSON data into the config structure func ReadConfig(in io.Reader) (*Config, error) { config := new(Config) // Allow (and ignore) unknown fields for forwards compatibility. u := jsonpb.Unmarshaler{ AllowUnknownFields: true, } return config, u.Unmarshal(in, config) } // HasCompatibilityOption returns true if the specified string is in the list of // compatibility options. This assumes the compatibility options are in a comma // separated string. func (c *Config) HasCompatibilityOption(option string) bool { options := strings.Split(c.Compatibility, ",") for _, o := range options { if o == option { return true } } return false } fscrypt-0.2.5/metadata/config_test.go000066400000000000000000000035101357032351700176300ustar00rootroot00000000000000/* * config_test.go - Tests the processing of the config file * * 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 metadata import ( "bytes" "testing" "github.com/golang/protobuf/proto" ) var testConfig = &Config{ Source: SourceType_custom_passphrase, HashCosts: &HashingCosts{ Time: 10, Memory: 1 << 12, Parallelism: 8, }, Compatibility: "", Options: DefaultOptions, } var testConfigString = `{ "source": "custom_passphrase", "hash_costs": { "time": "10", "memory": "4096", "parallelism": "8" }, "compatibility": "", "options": { "padding": "32", "contents": "AES_256_XTS", "filenames": "AES_256_CTS" } } ` // Makes sure that writing a config and reading it back gives the same thing. func TestWrite(t *testing.T) { var b bytes.Buffer err := WriteConfig(testConfig, &b) if err != nil { t.Fatal(err) } t.Logf("json encoded config:\n%s", b.String()) if b.String() != testConfigString { t.Errorf("did not match: %s", testConfigString) } } func TestRead(t *testing.T) { buf := bytes.NewBufferString(testConfigString) cfg, err := ReadConfig(buf) if err != nil { t.Fatal(err) } t.Logf("decoded config:\n%s", cfg) if !proto.Equal(cfg, testConfig) { t.Errorf("did not match: %s", testConfig) } } fscrypt-0.2.5/metadata/constants.go000066400000000000000000000031621357032351700173430ustar00rootroot00000000000000/* * constants.go - Some metadata constants used throughout fscrypt * * 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 metadata import ( "crypto/sha256" "golang.org/x/sys/unix" ) // Lengths for our keys, buffers, and strings used in fscrypt. const ( // DescriptorLen is the length of all Protector and Policy descriptors. DescriptorLen = 2 * unix.FS_KEY_DESCRIPTOR_SIZE // We always use 256-bit keys internally (compared to 512-bit policy keys). InternalKeyLen = 32 IVLen = 16 SaltLen = 16 // We use SHA256 for the HMAC, and len(HMAC) == len(hash size). HMACLen = sha256.Size // PolicyKeyLen is the length of all keys passed directly to the Keyring PolicyKeyLen = unix.FS_MAX_KEY_SIZE ) var ( // DefaultOptions use the supported encryption modes and max padding. DefaultOptions = &EncryptionOptions{ Padding: 32, Contents: EncryptionOptions_AES_256_XTS, Filenames: EncryptionOptions_AES_256_CTS, } // DefaultSource is the source we use if none is specified. DefaultSource = SourceType_custom_passphrase ) fscrypt-0.2.5/metadata/metadata.pb.go000066400000000000000000000514321357032351700175120ustar00rootroot00000000000000// Code generated by protoc-gen-go. DO NOT EDIT. // source: metadata/metadata.proto package metadata import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package // Specifies the method in which an outside secret is obtained for a Protector type SourceType int32 const ( SourceType_default SourceType = 0 SourceType_pam_passphrase SourceType = 1 SourceType_custom_passphrase SourceType = 2 SourceType_raw_key SourceType = 3 ) var SourceType_name = map[int32]string{ 0: "default", 1: "pam_passphrase", 2: "custom_passphrase", 3: "raw_key", } var SourceType_value = map[string]int32{ "default": 0, "pam_passphrase": 1, "custom_passphrase": 2, "raw_key": 3, } func (x SourceType) String() string { return proto.EnumName(SourceType_name, int32(x)) } func (SourceType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_metadata_5e732a616277e389, []int{0} } // Type of encryption; should match declarations of unix.FS_ENCRYPTION_MODE type EncryptionOptions_Mode int32 const ( EncryptionOptions_default EncryptionOptions_Mode = 0 EncryptionOptions_AES_256_XTS EncryptionOptions_Mode = 1 EncryptionOptions_AES_256_GCM EncryptionOptions_Mode = 2 EncryptionOptions_AES_256_CBC EncryptionOptions_Mode = 3 EncryptionOptions_AES_256_CTS EncryptionOptions_Mode = 4 EncryptionOptions_AES_128_CBC EncryptionOptions_Mode = 5 EncryptionOptions_AES_128_CTS EncryptionOptions_Mode = 6 EncryptionOptions_Adiantum EncryptionOptions_Mode = 9 ) var EncryptionOptions_Mode_name = map[int32]string{ 0: "default", 1: "AES_256_XTS", 2: "AES_256_GCM", 3: "AES_256_CBC", 4: "AES_256_CTS", 5: "AES_128_CBC", 6: "AES_128_CTS", 9: "Adiantum", } var EncryptionOptions_Mode_value = map[string]int32{ "default": 0, "AES_256_XTS": 1, "AES_256_GCM": 2, "AES_256_CBC": 3, "AES_256_CTS": 4, "AES_128_CBC": 5, "AES_128_CTS": 6, "Adiantum": 9, } func (x EncryptionOptions_Mode) String() string { return proto.EnumName(EncryptionOptions_Mode_name, int32(x)) } func (EncryptionOptions_Mode) EnumDescriptor() ([]byte, []int) { return fileDescriptor_metadata_5e732a616277e389, []int{3, 0} } // Cost parameters to be used in our hashing functions. type HashingCosts struct { Time int64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` Memory int64 `protobuf:"varint,3,opt,name=memory,proto3" json:"memory,omitempty"` Parallelism int64 `protobuf:"varint,4,opt,name=parallelism,proto3" json:"parallelism,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *HashingCosts) Reset() { *m = HashingCosts{} } func (m *HashingCosts) String() string { return proto.CompactTextString(m) } func (*HashingCosts) ProtoMessage() {} func (*HashingCosts) Descriptor() ([]byte, []int) { return fileDescriptor_metadata_5e732a616277e389, []int{0} } func (m *HashingCosts) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_HashingCosts.Unmarshal(m, b) } func (m *HashingCosts) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_HashingCosts.Marshal(b, m, deterministic) } func (dst *HashingCosts) XXX_Merge(src proto.Message) { xxx_messageInfo_HashingCosts.Merge(dst, src) } func (m *HashingCosts) XXX_Size() int { return xxx_messageInfo_HashingCosts.Size(m) } func (m *HashingCosts) XXX_DiscardUnknown() { xxx_messageInfo_HashingCosts.DiscardUnknown(m) } var xxx_messageInfo_HashingCosts proto.InternalMessageInfo func (m *HashingCosts) GetTime() int64 { if m != nil { return m.Time } return 0 } func (m *HashingCosts) GetMemory() int64 { if m != nil { return m.Memory } return 0 } func (m *HashingCosts) GetParallelism() int64 { if m != nil { return m.Parallelism } return 0 } // This structure is used for our authenticated wrapping/unwrapping of keys. type WrappedKeyData struct { IV []byte `protobuf:"bytes,1,opt,name=IV,proto3" json:"IV,omitempty"` EncryptedKey []byte `protobuf:"bytes,2,opt,name=encrypted_key,json=encryptedKey,proto3" json:"encrypted_key,omitempty"` Hmac []byte `protobuf:"bytes,3,opt,name=hmac,proto3" json:"hmac,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *WrappedKeyData) Reset() { *m = WrappedKeyData{} } func (m *WrappedKeyData) String() string { return proto.CompactTextString(m) } func (*WrappedKeyData) ProtoMessage() {} func (*WrappedKeyData) Descriptor() ([]byte, []int) { return fileDescriptor_metadata_5e732a616277e389, []int{1} } func (m *WrappedKeyData) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_WrappedKeyData.Unmarshal(m, b) } func (m *WrappedKeyData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_WrappedKeyData.Marshal(b, m, deterministic) } func (dst *WrappedKeyData) XXX_Merge(src proto.Message) { xxx_messageInfo_WrappedKeyData.Merge(dst, src) } func (m *WrappedKeyData) XXX_Size() int { return xxx_messageInfo_WrappedKeyData.Size(m) } func (m *WrappedKeyData) XXX_DiscardUnknown() { xxx_messageInfo_WrappedKeyData.DiscardUnknown(m) } var xxx_messageInfo_WrappedKeyData proto.InternalMessageInfo func (m *WrappedKeyData) GetIV() []byte { if m != nil { return m.IV } return nil } func (m *WrappedKeyData) GetEncryptedKey() []byte { if m != nil { return m.EncryptedKey } return nil } func (m *WrappedKeyData) GetHmac() []byte { if m != nil { return m.Hmac } return nil } // The associated data for each protector type ProtectorData struct { ProtectorDescriptor string `protobuf:"bytes,1,opt,name=protector_descriptor,json=protectorDescriptor,proto3" json:"protector_descriptor,omitempty"` Source SourceType `protobuf:"varint,2,opt,name=source,proto3,enum=metadata.SourceType" json:"source,omitempty"` // These are only used by some of the protector types Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` Costs *HashingCosts `protobuf:"bytes,4,opt,name=costs,proto3" json:"costs,omitempty"` Salt []byte `protobuf:"bytes,5,opt,name=salt,proto3" json:"salt,omitempty"` Uid int64 `protobuf:"varint,6,opt,name=uid,proto3" json:"uid,omitempty"` WrappedKey *WrappedKeyData `protobuf:"bytes,7,opt,name=wrapped_key,json=wrappedKey,proto3" json:"wrapped_key,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *ProtectorData) Reset() { *m = ProtectorData{} } func (m *ProtectorData) String() string { return proto.CompactTextString(m) } func (*ProtectorData) ProtoMessage() {} func (*ProtectorData) Descriptor() ([]byte, []int) { return fileDescriptor_metadata_5e732a616277e389, []int{2} } func (m *ProtectorData) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ProtectorData.Unmarshal(m, b) } func (m *ProtectorData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_ProtectorData.Marshal(b, m, deterministic) } func (dst *ProtectorData) XXX_Merge(src proto.Message) { xxx_messageInfo_ProtectorData.Merge(dst, src) } func (m *ProtectorData) XXX_Size() int { return xxx_messageInfo_ProtectorData.Size(m) } func (m *ProtectorData) XXX_DiscardUnknown() { xxx_messageInfo_ProtectorData.DiscardUnknown(m) } var xxx_messageInfo_ProtectorData proto.InternalMessageInfo func (m *ProtectorData) GetProtectorDescriptor() string { if m != nil { return m.ProtectorDescriptor } return "" } func (m *ProtectorData) GetSource() SourceType { if m != nil { return m.Source } return SourceType_default } func (m *ProtectorData) GetName() string { if m != nil { return m.Name } return "" } func (m *ProtectorData) GetCosts() *HashingCosts { if m != nil { return m.Costs } return nil } func (m *ProtectorData) GetSalt() []byte { if m != nil { return m.Salt } return nil } func (m *ProtectorData) GetUid() int64 { if m != nil { return m.Uid } return 0 } func (m *ProtectorData) GetWrappedKey() *WrappedKeyData { if m != nil { return m.WrappedKey } return nil } // Encryption policy specifics, corresponds to the fscrypt_policy struct type EncryptionOptions struct { Padding int64 `protobuf:"varint,1,opt,name=padding,proto3" json:"padding,omitempty"` Contents EncryptionOptions_Mode `protobuf:"varint,2,opt,name=contents,proto3,enum=metadata.EncryptionOptions_Mode" json:"contents,omitempty"` Filenames EncryptionOptions_Mode `protobuf:"varint,3,opt,name=filenames,proto3,enum=metadata.EncryptionOptions_Mode" json:"filenames,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *EncryptionOptions) Reset() { *m = EncryptionOptions{} } func (m *EncryptionOptions) String() string { return proto.CompactTextString(m) } func (*EncryptionOptions) ProtoMessage() {} func (*EncryptionOptions) Descriptor() ([]byte, []int) { return fileDescriptor_metadata_5e732a616277e389, []int{3} } func (m *EncryptionOptions) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_EncryptionOptions.Unmarshal(m, b) } func (m *EncryptionOptions) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_EncryptionOptions.Marshal(b, m, deterministic) } func (dst *EncryptionOptions) XXX_Merge(src proto.Message) { xxx_messageInfo_EncryptionOptions.Merge(dst, src) } func (m *EncryptionOptions) XXX_Size() int { return xxx_messageInfo_EncryptionOptions.Size(m) } func (m *EncryptionOptions) XXX_DiscardUnknown() { xxx_messageInfo_EncryptionOptions.DiscardUnknown(m) } var xxx_messageInfo_EncryptionOptions proto.InternalMessageInfo func (m *EncryptionOptions) GetPadding() int64 { if m != nil { return m.Padding } return 0 } func (m *EncryptionOptions) GetContents() EncryptionOptions_Mode { if m != nil { return m.Contents } return EncryptionOptions_default } func (m *EncryptionOptions) GetFilenames() EncryptionOptions_Mode { if m != nil { return m.Filenames } return EncryptionOptions_default } type WrappedPolicyKey struct { ProtectorDescriptor string `protobuf:"bytes,1,opt,name=protector_descriptor,json=protectorDescriptor,proto3" json:"protector_descriptor,omitempty"` WrappedKey *WrappedKeyData `protobuf:"bytes,2,opt,name=wrapped_key,json=wrappedKey,proto3" json:"wrapped_key,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *WrappedPolicyKey) Reset() { *m = WrappedPolicyKey{} } func (m *WrappedPolicyKey) String() string { return proto.CompactTextString(m) } func (*WrappedPolicyKey) ProtoMessage() {} func (*WrappedPolicyKey) Descriptor() ([]byte, []int) { return fileDescriptor_metadata_5e732a616277e389, []int{4} } func (m *WrappedPolicyKey) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_WrappedPolicyKey.Unmarshal(m, b) } func (m *WrappedPolicyKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_WrappedPolicyKey.Marshal(b, m, deterministic) } func (dst *WrappedPolicyKey) XXX_Merge(src proto.Message) { xxx_messageInfo_WrappedPolicyKey.Merge(dst, src) } func (m *WrappedPolicyKey) XXX_Size() int { return xxx_messageInfo_WrappedPolicyKey.Size(m) } func (m *WrappedPolicyKey) XXX_DiscardUnknown() { xxx_messageInfo_WrappedPolicyKey.DiscardUnknown(m) } var xxx_messageInfo_WrappedPolicyKey proto.InternalMessageInfo func (m *WrappedPolicyKey) GetProtectorDescriptor() string { if m != nil { return m.ProtectorDescriptor } return "" } func (m *WrappedPolicyKey) GetWrappedKey() *WrappedKeyData { if m != nil { return m.WrappedKey } return nil } // The associated data for each policy type PolicyData struct { KeyDescriptor string `protobuf:"bytes,1,opt,name=key_descriptor,json=keyDescriptor,proto3" json:"key_descriptor,omitempty"` Options *EncryptionOptions `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"` WrappedPolicyKeys []*WrappedPolicyKey `protobuf:"bytes,3,rep,name=wrapped_policy_keys,json=wrappedPolicyKeys,proto3" json:"wrapped_policy_keys,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *PolicyData) Reset() { *m = PolicyData{} } func (m *PolicyData) String() string { return proto.CompactTextString(m) } func (*PolicyData) ProtoMessage() {} func (*PolicyData) Descriptor() ([]byte, []int) { return fileDescriptor_metadata_5e732a616277e389, []int{5} } func (m *PolicyData) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PolicyData.Unmarshal(m, b) } func (m *PolicyData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_PolicyData.Marshal(b, m, deterministic) } func (dst *PolicyData) XXX_Merge(src proto.Message) { xxx_messageInfo_PolicyData.Merge(dst, src) } func (m *PolicyData) XXX_Size() int { return xxx_messageInfo_PolicyData.Size(m) } func (m *PolicyData) XXX_DiscardUnknown() { xxx_messageInfo_PolicyData.DiscardUnknown(m) } var xxx_messageInfo_PolicyData proto.InternalMessageInfo func (m *PolicyData) GetKeyDescriptor() string { if m != nil { return m.KeyDescriptor } return "" } func (m *PolicyData) GetOptions() *EncryptionOptions { if m != nil { return m.Options } return nil } func (m *PolicyData) GetWrappedPolicyKeys() []*WrappedPolicyKey { if m != nil { return m.WrappedPolicyKeys } return nil } // Data stored in the config file type Config struct { Source SourceType `protobuf:"varint,1,opt,name=source,proto3,enum=metadata.SourceType" json:"source,omitempty"` HashCosts *HashingCosts `protobuf:"bytes,2,opt,name=hash_costs,json=hashCosts,proto3" json:"hash_costs,omitempty"` Compatibility string `protobuf:"bytes,3,opt,name=compatibility,proto3" json:"compatibility,omitempty"` Options *EncryptionOptions `protobuf:"bytes,4,opt,name=options,proto3" json:"options,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Config) Reset() { *m = Config{} } func (m *Config) String() string { return proto.CompactTextString(m) } func (*Config) ProtoMessage() {} func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor_metadata_5e732a616277e389, []int{6} } func (m *Config) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Config.Unmarshal(m, b) } func (m *Config) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Config.Marshal(b, m, deterministic) } func (dst *Config) XXX_Merge(src proto.Message) { xxx_messageInfo_Config.Merge(dst, src) } func (m *Config) XXX_Size() int { return xxx_messageInfo_Config.Size(m) } func (m *Config) XXX_DiscardUnknown() { xxx_messageInfo_Config.DiscardUnknown(m) } var xxx_messageInfo_Config proto.InternalMessageInfo func (m *Config) GetSource() SourceType { if m != nil { return m.Source } return SourceType_default } func (m *Config) GetHashCosts() *HashingCosts { if m != nil { return m.HashCosts } return nil } func (m *Config) GetCompatibility() string { if m != nil { return m.Compatibility } return "" } func (m *Config) GetOptions() *EncryptionOptions { if m != nil { return m.Options } return nil } func init() { proto.RegisterType((*HashingCosts)(nil), "metadata.HashingCosts") proto.RegisterType((*WrappedKeyData)(nil), "metadata.WrappedKeyData") proto.RegisterType((*ProtectorData)(nil), "metadata.ProtectorData") proto.RegisterType((*EncryptionOptions)(nil), "metadata.EncryptionOptions") proto.RegisterType((*WrappedPolicyKey)(nil), "metadata.WrappedPolicyKey") proto.RegisterType((*PolicyData)(nil), "metadata.PolicyData") proto.RegisterType((*Config)(nil), "metadata.Config") proto.RegisterEnum("metadata.SourceType", SourceType_name, SourceType_value) proto.RegisterEnum("metadata.EncryptionOptions_Mode", EncryptionOptions_Mode_name, EncryptionOptions_Mode_value) } func init() { proto.RegisterFile("metadata/metadata.proto", fileDescriptor_metadata_5e732a616277e389) } var fileDescriptor_metadata_5e732a616277e389 = []byte{ // 656 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xef, 0x6a, 0xdb, 0x3c, 0x14, 0xc6, 0x5f, 0xdb, 0x69, 0xd2, 0x9c, 0xfc, 0x79, 0x5d, 0xb5, 0x6f, 0x5f, 0xb3, 0x7d, 0x09, 0xde, 0x06, 0x65, 0x94, 0x8e, 0x66, 0x74, 0x6c, 0x30, 0x06, 0x5d, 0x5a, 0xb6, 0xae, 0x94, 0x75, 0x4a, 0xe8, 0x36, 0x18, 0x04, 0xd5, 0x56, 0x1b, 0x51, 0xdb, 0x12, 0x96, 0x42, 0xf0, 0xb7, 0x7d, 0xdb, 0x05, 0xec, 0x5a, 0xb6, 0x8b, 0xd8, 0x55, 0x0d, 0xc9, 0xb1, 0xe3, 0xb4, 0x50, 0xba, 0x7d, 0x31, 0x47, 0x8f, 0xa4, 0xf3, 0x1c, 0xfd, 0xa4, 0x63, 0xf8, 0x3f, 0xa6, 0x8a, 0x84, 0x44, 0x91, 0x27, 0x45, 0xb0, 0x23, 0x52, 0xae, 0x38, 0x5a, 0x2d, 0xc6, 0xfe, 0x17, 0x68, 0xbf, 0x25, 0x72, 0xc2, 0x92, 0xcb, 0x01, 0x97, 0x4a, 0x22, 0x04, 0x35, 0xc5, 0x62, 0xea, 0xd9, 0x3d, 0x6b, 0xcb, 0xc1, 0x26, 0x46, 0x9b, 0x50, 0x8f, 0x69, 0xcc, 0xd3, 0xcc, 0x73, 0x8c, 0x3a, 0x1f, 0xa1, 0x1e, 0xb4, 0x04, 0x49, 0x49, 0x14, 0xd1, 0x88, 0xc9, 0xd8, 0xab, 0x99, 0xc9, 0xaa, 0xe4, 0x7f, 0x86, 0xee, 0xc7, 0x94, 0x08, 0x41, 0xc3, 0x63, 0x9a, 0x1d, 0x10, 0x45, 0x50, 0x17, 0xec, 0xa3, 0x33, 0xcf, 0xea, 0x59, 0x5b, 0x6d, 0x6c, 0x1f, 0x9d, 0xa1, 0x07, 0xd0, 0xa1, 0x49, 0x90, 0x66, 0x42, 0xd1, 0x70, 0x7c, 0x45, 0x33, 0x63, 0xdc, 0xc6, 0xed, 0x52, 0x3c, 0xa6, 0x99, 0x2e, 0x6a, 0x12, 0x93, 0xc0, 0xd8, 0xb7, 0xb1, 0x89, 0xfd, 0xef, 0x36, 0x74, 0x4e, 0x53, 0xae, 0x68, 0xa0, 0x78, 0x6a, 0x52, 0xef, 0xc2, 0x86, 0x28, 0x84, 0x71, 0x48, 0x65, 0x90, 0x32, 0xa1, 0x78, 0x6a, 0xcc, 0x9a, 0x78, 0xbd, 0x9c, 0x3b, 0x28, 0xa7, 0xd0, 0x36, 0xd4, 0x25, 0x9f, 0xa6, 0x41, 0x7e, 0xde, 0x6e, 0x7f, 0x63, 0xa7, 0x04, 0x35, 0x34, 0xfa, 0x28, 0x13, 0x14, 0xcf, 0xd7, 0xe8, 0x32, 0x12, 0x12, 0x53, 0x53, 0x46, 0x13, 0x9b, 0x18, 0x6d, 0xc3, 0x4a, 0xa0, 0xc1, 0x99, 0xd3, 0xb7, 0xfa, 0x9b, 0x8b, 0x04, 0x55, 0xac, 0x38, 0x5f, 0xa4, 0x33, 0x48, 0x12, 0x29, 0x6f, 0x25, 0x3f, 0x88, 0x8e, 0x91, 0x0b, 0xce, 0x94, 0x85, 0x5e, 0xdd, 0xd0, 0xd3, 0x21, 0x7a, 0x01, 0xad, 0x59, 0x4e, 0xcd, 0x10, 0x69, 0x98, 0xcc, 0xde, 0x22, 0xf3, 0x32, 0x52, 0x0c, 0xb3, 0x72, 0xec, 0xff, 0xb0, 0x61, 0xed, 0x30, 0x47, 0xc7, 0x78, 0xf2, 0xde, 0x7c, 0x25, 0xf2, 0xa0, 0x21, 0x48, 0x18, 0xb2, 0xe4, 0xd2, 0xc0, 0x70, 0x70, 0x31, 0x44, 0x2f, 0x61, 0x35, 0xe0, 0x89, 0xa2, 0x89, 0x92, 0x73, 0x04, 0xbd, 0x85, 0xcf, 0x8d, 0x44, 0x3b, 0x27, 0x3c, 0xa4, 0xb8, 0xdc, 0x81, 0x5e, 0x41, 0xf3, 0x82, 0x45, 0x54, 0x83, 0x90, 0x86, 0xca, 0x5d, 0xb6, 0x2f, 0xb6, 0xf8, 0xdf, 0x2c, 0xa8, 0x69, 0x0d, 0xb5, 0xa0, 0x11, 0xd2, 0x0b, 0x32, 0x8d, 0x94, 0xfb, 0x0f, 0xfa, 0x17, 0x5a, 0xfb, 0x87, 0xc3, 0x71, 0x7f, 0xef, 0xd9, 0xf8, 0xd3, 0x68, 0xe8, 0x5a, 0x55, 0xe1, 0xcd, 0xe0, 0xc4, 0xb5, 0xab, 0xc2, 0xe0, 0xf5, 0xc0, 0x75, 0x96, 0x84, 0xd1, 0xd0, 0xad, 0x15, 0xc2, 0x6e, 0xff, 0xb9, 0x59, 0xb1, 0xb2, 0x24, 0x8c, 0x86, 0x6e, 0x1d, 0xb5, 0x61, 0x75, 0x3f, 0x64, 0x24, 0x51, 0xd3, 0xd8, 0x6d, 0xfa, 0x5f, 0x2d, 0x70, 0xe7, 0x58, 0x4f, 0x79, 0xc4, 0x82, 0x4c, 0x3f, 0xbb, 0xbf, 0x78, 0x50, 0xd7, 0xae, 0xce, 0xfe, 0x83, 0xab, 0xfb, 0x69, 0x01, 0xe4, 0xde, 0xe6, 0x35, 0x3f, 0x82, 0xee, 0x15, 0xcd, 0x6e, 0xda, 0x76, 0xae, 0x68, 0x56, 0x31, 0xdc, 0x83, 0x06, 0xcf, 0xe9, 0xce, 0xcd, 0xee, 0xdf, 0x72, 0x01, 0xb8, 0x58, 0x8b, 0xde, 0xc1, 0x7a, 0x51, 0xa7, 0x30, 0x9e, 0xba, 0x5c, 0x7d, 0x87, 0xce, 0x56, 0xab, 0x7f, 0xef, 0x46, 0xbd, 0x25, 0x13, 0xbc, 0x36, 0xbb, 0xa6, 0x48, 0xff, 0x97, 0x05, 0xf5, 0x01, 0x4f, 0x2e, 0xd8, 0x65, 0xa5, 0x9f, 0xac, 0x3b, 0xf4, 0xd3, 0x1e, 0xc0, 0x84, 0xc8, 0xc9, 0x38, 0x6f, 0x20, 0xfb, 0xd6, 0x06, 0x6a, 0xea, 0x95, 0xf9, 0x2f, 0xea, 0x21, 0x74, 0x02, 0x1e, 0x0b, 0xa2, 0xd8, 0x39, 0x8b, 0x98, 0xca, 0xe6, 0xfd, 0xb8, 0x2c, 0x56, 0xc1, 0xd4, 0xee, 0x0e, 0xe6, 0xf1, 0x07, 0x80, 0x45, 0xa5, 0xcb, 0xef, 0x12, 0x41, 0x57, 0x90, 0x78, 0x2c, 0x88, 0x94, 0x62, 0x92, 0x12, 0x49, 0x5d, 0x0b, 0xfd, 0x07, 0x6b, 0xc1, 0x54, 0x2a, 0xbe, 0x24, 0xdb, 0x7a, 0x5f, 0x4a, 0x66, 0x9a, 0xa9, 0xeb, 0x9c, 0xd7, 0xcd, 0x3f, 0xf7, 0xe9, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4b, 0xbe, 0x84, 0xbc, 0x8e, 0x05, 0x00, 0x00, } fscrypt-0.2.5/metadata/metadata.proto000066400000000000000000000051001357032351700176370ustar00rootroot00000000000000/* * metadata.proto - File which contains all of the metadata structures which we * write to metadata files. Must be compiled with protoc to use the library. * Compilation can be invoked with go generate. * * 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. */ // If you modify this file, be sure to run "go generate" on this package. syntax = "proto3"; package metadata; // Cost parameters to be used in our hashing functions. message HashingCosts { int64 time = 2; int64 memory = 3; int64 parallelism = 4; } // This structure is used for our authenticated wrapping/unwrapping of keys. message WrappedKeyData { bytes IV = 1; bytes encrypted_key = 2; bytes hmac = 3; } // Specifies the method in which an outside secret is obtained for a Protector enum SourceType { default = 0; pam_passphrase = 1; custom_passphrase = 2; raw_key = 3; } // The associated data for each protector message ProtectorData { string protector_descriptor = 1; SourceType source = 2; // These are only used by some of the protector types string name = 3; HashingCosts costs = 4; bytes salt = 5; int64 uid = 6; WrappedKeyData wrapped_key = 7; } // Encryption policy specifics, corresponds to the fscrypt_policy struct message EncryptionOptions { int64 padding = 1; // Type of encryption; should match declarations of unix.FS_ENCRYPTION_MODE enum Mode { default = 0; AES_256_XTS = 1; AES_256_GCM = 2; AES_256_CBC = 3; AES_256_CTS = 4; AES_128_CBC = 5; AES_128_CTS = 6; Adiantum = 9; } Mode contents = 2; Mode filenames = 3; } message WrappedPolicyKey { string protector_descriptor = 1; WrappedKeyData wrapped_key = 2; } // The associated data for each policy message PolicyData { string key_descriptor = 1; EncryptionOptions options = 2; repeated WrappedPolicyKey wrapped_policy_keys = 3; } // Data stored in the config file message Config { SourceType source = 1; HashingCosts hash_costs = 2; string compatibility = 3; EncryptionOptions options = 4; } fscrypt-0.2.5/metadata/policy.go000066400000000000000000000160531357032351700166310ustar00rootroot00000000000000/* * policy.go - Functions for getting and setting policies on a specified * directory or file. * * 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 metadata import ( "encoding/hex" "log" "math" "os" "unsafe" "github.com/pkg/errors" "golang.org/x/sys/unix" "github.com/google/fscrypt/util" ) // Encryption specific errors var ( ErrEncryptionNotSupported = errors.New("encryption not supported") ErrEncryptionNotEnabled = errors.New("encryption not enabled") ErrNotEncrypted = errors.New("file or directory not encrypted") ErrEncrypted = errors.New("file or directory already encrypted") ErrBadEncryptionOptions = util.SystemError("invalid encryption options provided") ) // policyIoctl is a wrapper for the ioctl syscall. It passes the correct // pointers and file descriptors to the IOCTL syscall. This function also takes // some of the unclear errors returned by the syscall and translates then into // more specific error strings. func policyIoctl(file *os.File, request uintptr, policy *unix.FscryptPolicy) error { // The returned errno value can sometimes give strange errors, so we // return encryption specific errors. _, _, errno := unix.Syscall(unix.SYS_IOCTL, file.Fd(), request, uintptr(unsafe.Pointer(policy))) switch errno { case 0: return nil case unix.ENOTTY: return ErrEncryptionNotSupported case unix.EOPNOTSUPP: return ErrEncryptionNotEnabled case unix.ENODATA, unix.ENOENT: // ENOENT was returned instead of ENODATA on some filesystems before v4.11. return ErrNotEncrypted case unix.EEXIST: // EINVAL was returned instead of EEXIST on some filesystems before v4.11. return ErrEncrypted default: return errno } } // Maps EncryptionOptions.Padding <-> FscryptPolicy.Flags var ( paddingArray = []int64{4, 8, 16, 32} flagsArray = []int64{unix.FS_POLICY_FLAGS_PAD_4, unix.FS_POLICY_FLAGS_PAD_8, unix.FS_POLICY_FLAGS_PAD_16, unix.FS_POLICY_FLAGS_PAD_32} ) // GetPolicy returns the Policy data for the given directory or file (includes // the KeyDescriptor and the encryption options). Returns an error if the // path is not encrypted or the policy couldn't be retrieved. func GetPolicy(path string) (*PolicyData, error) { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() var policy unix.FscryptPolicy if err = policyIoctl(file, unix.FS_IOC_GET_ENCRYPTION_POLICY, &policy); err != nil { return nil, errors.Wrapf(err, "get encryption policy %s", path) } // Convert the padding flag into an amount of padding paddingFlag := int64(policy.Flags & unix.FS_POLICY_FLAGS_PAD_MASK) // This lookup should always succeed padding, ok := util.Lookup(paddingFlag, flagsArray, paddingArray) if !ok { log.Panicf("padding flag of %x not found", paddingFlag) } return &PolicyData{ KeyDescriptor: hex.EncodeToString(policy.Master_key_descriptor[:]), Options: &EncryptionOptions{ Padding: padding, Contents: EncryptionOptions_Mode(policy.Contents_encryption_mode), Filenames: EncryptionOptions_Mode(policy.Filenames_encryption_mode), }, }, nil } // For improved performance, use the DIRECT_KEY flag when using ciphers that // support it, e.g. Adiantum. It is safe because fscrypt won't reuse the key // for any other policy. (Multiple directories with same policy are okay.) func shouldUseDirectKeyFlag(options *EncryptionOptions) bool { // Contents and filenames encryption modes must be the same if options.Contents != options.Filenames { return false } // Whitelist the modes that take a 24+ byte IV (enough room for the per-file nonce) return options.Contents == EncryptionOptions_Adiantum } // SetPolicy sets up the specified directory to be encrypted with the specified // policy. Returns an error if we cannot set the policy for any reason (not a // directory, invalid options or KeyDescriptor, etc). func SetPolicy(path string, data *PolicyData) error { file, err := os.Open(path) if err != nil { return err } defer file.Close() if err = data.CheckValidity(); err != nil { return errors.Wrap(err, "invalid policy") } // This lookup should always succeed (as policy is valid) flags, ok := util.Lookup(data.Options.Padding, paddingArray, flagsArray) if !ok { log.Panicf("padding of %d was not found", data.Options.Padding) } descriptorBytes, err := hex.DecodeString(data.KeyDescriptor) if err != nil { return errors.New("invalid descriptor: " + data.KeyDescriptor) } if shouldUseDirectKeyFlag(data.Options) { // TODO: use unix.FS_POLICY_FLAG_DIRECT_KEY here once available flags |= 0x4 } policy := unix.FscryptPolicy{ Version: 0, // Version must always be zero Contents_encryption_mode: uint8(data.Options.Contents), Filenames_encryption_mode: uint8(data.Options.Filenames), Flags: uint8(flags), } copy(policy.Master_key_descriptor[:], descriptorBytes) if err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, &policy); err == unix.EINVAL { // Before kernel v4.11, many different errors all caused unix.EINVAL to be returned. // We try to disambiguate this error here. This disambiguation will not always give // the correct error due to a potential race condition on path. if info, statErr := os.Stat(path); statErr != nil || !info.IsDir() { // Checking if the path is not a directory err = unix.ENOTDIR } else if _, policyErr := GetPolicy(path); policyErr == nil { // Checking if a policy is already set on this directory err = ErrEncrypted } else { // Default to generic "bad options". err = ErrBadEncryptionOptions } } return errors.Wrapf(err, "set encryption policy %s", path) } // CheckSupport returns an error if the filesystem containing path does not // support filesystem encryption. This can be for many reasons including an // incompatible kernel or filesystem or not enabling the right feature flags. func CheckSupport(path string) error { file, err := os.Open(path) if err != nil { return err } defer file.Close() // On supported directories, giving a bad policy will return EINVAL badPolicy := unix.FscryptPolicy{ Version: math.MaxUint8, Contents_encryption_mode: math.MaxUint8, Filenames_encryption_mode: math.MaxUint8, Flags: math.MaxUint8, } err = policyIoctl(file, unix.FS_IOC_SET_ENCRYPTION_POLICY, &badPolicy) switch err { case nil: log.Panicf(`FS_IOC_SET_ENCRYPTION_POLICY succeeded when it should have failed. Please open an issue, filesystem %q may be corrupted.`, path) case unix.EINVAL, unix.EACCES: return nil } return err } fscrypt-0.2.5/metadata/policy_test.go000066400000000000000000000107521357032351700176700ustar00rootroot00000000000000/* * policy_test.go - Tests the getting/setting of encryption policies * * 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 metadata import ( "fmt" "os" "path/filepath" "testing" "github.com/golang/protobuf/proto" "github.com/google/fscrypt/util" ) const goodDescriptor = "0123456789abcdef" var goodPolicy = &PolicyData{ KeyDescriptor: goodDescriptor, Options: DefaultOptions, } // Creates a temporary directory for testing. func createTestDirectory(t *testing.T) (directory string, err error) { baseDirectory, err := util.TestRoot() if err != nil { t.Skip(err) } if s, err := os.Stat(baseDirectory); err != nil || !s.IsDir() { return "", fmt.Errorf("test directory %q is not valid", baseDirectory) } directoryPath := filepath.Join(baseDirectory, "test") return directoryPath, os.MkdirAll(directoryPath, os.ModePerm) } // Makes a test directory, makes a file in the directory, and fills the file // with data. Returns the directory name, file name, and error (if one). func createTestFile(t *testing.T) (directory, file string, err error) { if directory, err = createTestDirectory(t); err != nil { return } // Cleanup if the file creation fails defer func() { if err != nil { os.RemoveAll(directory) } }() filePath := filepath.Join(directory, "test.txt") fileHandle, err := os.Create(filePath) if err != nil { return directory, filePath, err } defer fileHandle.Close() _, err = fileHandle.Write([]byte("Here is some test data!\n")) return directory, filePath, err } // Tests that we can set a policy on an empty directory func TestSetPolicyEmptyDirectory(t *testing.T) { directory, err := createTestDirectory(t) if err != nil { t.Fatal(err) } defer os.RemoveAll(directory) if err = SetPolicy(directory, goodPolicy); err != nil { t.Error(err) } } // Tests that we cannot set a policy on a nonempty directory func TestSetPolicyNonemptyDirectory(t *testing.T) { directory, _, err := createTestFile(t) if err != nil { t.Fatal(err) } defer os.RemoveAll(directory) if err = SetPolicy(directory, goodPolicy); err == nil { t.Error("should have failed to set policy on a nonempty directory") } } // Tests that we cannot set a policy on a file func TestSetPolicyFile(t *testing.T) { directory, file, err := createTestFile(t) if err != nil { t.Fatal(err) } defer os.RemoveAll(directory) if err = SetPolicy(file, goodPolicy); err == nil { t.Error("should have failed to set policy on a file") } } // Tests that we fail when using bad policies func TestSetPolicyBadDescriptors(t *testing.T) { // Policies that are too short, have invalid chars, or are too long badDescriptors := []string{"123456789abcde", "xxxxxxxxxxxxxxxx", "0123456789abcdef00"} for _, badDescriptor := range badDescriptors { badPolicy := &PolicyData{KeyDescriptor: badDescriptor, Options: DefaultOptions} directory, err := createTestDirectory(t) if err != nil { t.Fatal(err) } if err = SetPolicy(directory, badPolicy); err == nil { t.Errorf("descriptor %q should have made SetPolicy fail", badDescriptor) } os.RemoveAll(directory) } } // Tests that we get back the same policy that we set on a directory func TestGetPolicyEmptyDirectory(t *testing.T) { directory, err := createTestDirectory(t) if err != nil { t.Fatal(err) } defer os.RemoveAll(directory) var actualPolicy *PolicyData if err = SetPolicy(directory, goodPolicy); err != nil { t.Fatal(err) } if actualPolicy, err = GetPolicy(directory); err != nil { t.Fatal(err) } if !proto.Equal(actualPolicy, goodPolicy) { t.Errorf("policy %+v does not equal expected policy %+v", actualPolicy, goodPolicy) } } // Tests that we cannot get a policy on an unencrypted directory func TestGetPolicyUnencrypted(t *testing.T) { directory, err := createTestDirectory(t) if err != nil { t.Fatal(err) } defer os.RemoveAll(directory) if _, err = GetPolicy(directory); err == nil { t.Error("should have failed to set policy on a file") } } fscrypt-0.2.5/pam/000077500000000000000000000000001357032351700137735ustar00rootroot00000000000000fscrypt-0.2.5/pam/constants.go000066400000000000000000000104041357032351700163350ustar00rootroot00000000000000/* * constants.go - PAM flags and item types from github.com/msteinert/pam * * Modifications Copyright 2017 Google Inc. * Modifications 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. */ /* * Copyright 2011, krockot * Copyright 2015, Michael Steinert * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package pam /* #cgo LDFLAGS: -lpam #include */ import "C" // Item is a PAM information type. type Item int // PAM Item types. const ( // Service is the name which identifies the PAM stack. Service Item = C.PAM_SERVICE // User identifies the username identity used by a service. User = C.PAM_USER // Tty is the terminal name. Tty = C.PAM_TTY // Rhost is the requesting host name. Rhost = C.PAM_RHOST // Authtok is the currently active authentication token. Authtok = C.PAM_AUTHTOK // Oldauthtok is the old authentication token. Oldauthtok = C.PAM_OLDAUTHTOK // Ruser is the requesting user name. Ruser = C.PAM_RUSER // UserPrompt is the string use to prompt for a username. UserPrompt = C.PAM_USER_PROMPT ) // Flag is used as input to various PAM functions. Flags can be combined with a // bitwise or. Refer to the official PAM documentation for which flags are // accepted by which functions. type Flag int // PAM Flag types. const ( // Silent indicates that no messages should be emitted. Silent Flag = C.PAM_SILENT // DisallowNullAuthtok indicates that authorization should fail // if the user does not have a registered authentication token. DisallowNullAuthtok = C.PAM_DISALLOW_NULL_AUTHTOK // EstablishCred indicates that credentials should be established // for the user. EstablishCred = C.PAM_ESTABLISH_CRED // DeleteCred inidicates that credentials should be deleted. DeleteCred = C.PAM_DELETE_CRED // ReinitializeCred indicates that credentials should be fully // reinitialized. ReinitializeCred = C.PAM_REINITIALIZE_CRED // RefreshCred indicates that the lifetime of existing credentials // should be extended. RefreshCred = C.PAM_REFRESH_CRED // ChangeExpiredAuthtok indicates that the authentication token // should be changed if it has expired. ChangeExpiredAuthtok = C.PAM_CHANGE_EXPIRED_AUTHTOK // PrelimCheck indicates that the modules are being probed as to their // ready status for altering the user's authentication token. PrelimCheck = C.PAM_PRELIM_CHECK // UpdateAuthtok informs the module that this is the call it should // change the authorization tokens. UpdateAuthtok = C.PAM_UPDATE_AUTHTOK ) fscrypt-0.2.5/pam/login.go000066400000000000000000000063651357032351700154440ustar00rootroot00000000000000/* * login.go - Checks the validity of a login token key against PAM. * * 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 pam contains all the functionality for interfacing with Linux // Pluggable Authentication Modules (PAM). Currently, all this package does is // check the validity of a user's login passphrase. // See http://www.linux-pam.org/Linux-PAM-html/ for more information. package pam import "C" import ( "fmt" "log" "sync" "github.com/pkg/errors" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/util" ) // Pam error values var ( ErrPassphrase = errors.New("incorrect login passphrase") ) // Global state is needed for the PAM callback, so we guard this function with a // lock. tokenToCheck is only ever non-nil when tokenLock is held. var ( tokenLock sync.Mutex tokenToCheck *crypto.Key ) // userInput is run when the callback needs some input from the user. We prompt // the user for information and return their answer. A return value of nil // indicates an error occurred. //export userInput func userInput(prompt *C.char) *C.char { fmt.Print(C.GoString(prompt)) input, err := util.ReadLine() if err != nil { log.Printf("getting input for PAM: %s", err) return nil } return C.CString(input) } // passphraseInput is run when the callback needs a passphrase from the user. We // pass along the tokenToCheck without prompting. A return value of nil // indicates an error occurred. //export passphraseInput func passphraseInput(prompt *C.char) *C.char { log.Printf("getting secret data for PAM: %q", C.GoString(prompt)) if tokenToCheck == nil { log.Print("secret data requested multiple times") return nil } // Subsequent calls to passphrase input should fail input := (*C.char)(tokenToCheck.UnsafeToCString()) tokenToCheck = nil return input } // IsUserLoginToken returns nil if the presented token is the user's login key, // and returns an error otherwise. Note that unless we are currently running as // root, this check will only work for the user running this process. func IsUserLoginToken(username string, token *crypto.Key, quiet bool) error { log.Printf("Checking login token for %s", username) // We require global state for the function. This function never takes // ownership of the token, so it is not responsible for wiping it. tokenLock.Lock() tokenToCheck = token defer func() { tokenToCheck = nil tokenLock.Unlock() }() transaction, err := Start("fscrypt", username) if err != nil { return err } defer transaction.End() // Ask PAM to authenticate the token. authenticated, err := transaction.Authenticate(quiet) if err != nil { return err } if !authenticated { return ErrPassphrase } return nil } fscrypt-0.2.5/pam/pam.c000066400000000000000000000061651357032351700147240ustar00rootroot00000000000000/* * pam.c - Functions to let us call into libpam from Go. * * 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. */ #include "pam.h" #include #include #include #include #include // mlock/munlock #include "_cgo_export.h" // for input callbacks static int conversation(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) { if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) { return PAM_CONV_ERR; } // Allocate the response table with num_msg entries. *resp = calloc(num_msg, sizeof **resp); if (!*resp) { return PAM_BUF_ERR; } // Check each message to see if we need to run a callback. char* callback_msg = NULL; char* callback_resp = NULL; int i; for (i = 0; i < num_msg; ++i) { callback_msg = (char*)msg[i]->msg; // We run our input callback if the style tells us we need data. Otherwise, // we just print the error messages or text info to standard output. switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_OFF: callback_resp = passphraseInput(callback_msg); break; case PAM_PROMPT_ECHO_ON: callback_resp = userInput(callback_msg); break; case PAM_ERROR_MSG: case PAM_TEXT_INFO: fprintf(stderr, "%s\n", callback_msg); continue; } if (!callback_resp) { // If the callback failed, free each nonempty response in the response // table and the response table itself. while (--i >= 0) { free((*resp)[i].resp); } free(*resp); *resp = NULL; return PAM_CONV_ERR; } (*resp)[i].resp = callback_resp; } return PAM_SUCCESS; } static const struct pam_conv conv = {conversation, NULL}; const struct pam_conv* goConv = &conv; void freeData(pam_handle_t* pamh, void* data, int error_status) { free(data); } void freeArray(pam_handle_t* pamh, void** array, int error_status) { int i; for (i = 0; array[i]; ++i) { free(array[i]); } free(array); } void* copyIntoSecret(void* data) { size_t size = strlen(data) + 1; // include null terminator void* copy = malloc(size); mlock(copy, size); memcpy(copy, data, size); return copy; } void freeSecret(pam_handle_t* pamh, char* data, int error_status) { size_t size = strlen(data) + 1; // Include null terminator // Use volatile function pointer to actually clear the memory. static void* (*const volatile memset_sec)(void*, int, size_t) = &memset; memset_sec(data, 0, size); munlock(data, size); free(data); } fscrypt-0.2.5/pam/pam.go000066400000000000000000000134241357032351700151030ustar00rootroot00000000000000/* * pam.go - Utility functions for interfacing with the PAM libraries. * * 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 pam /* #cgo LDFLAGS: -lpam #include "pam.h" #include #include #include */ import "C" import ( "errors" "log" "os/user" "unsafe" "github.com/google/fscrypt/security" ) // Handle wraps the C pam_handle_t type. This is used from within modules. type Handle struct { handle *C.pam_handle_t status C.int origPrivs *security.Privileges // PamUser is the user for whom the PAM module is running. PamUser *user.User } // NewHandle creates a Handle from a raw pointer. func NewHandle(pamh unsafe.Pointer) (*Handle, error) { var err error h := &Handle{ handle: (*C.pam_handle_t)(pamh), status: C.PAM_SUCCESS, } var pamUsername *C.char h.status = C.pam_get_user(h.handle, &pamUsername, nil) if err = h.err(); err != nil { return nil, err } h.PamUser, err = user.Lookup(C.GoString(pamUsername)) return h, err } func (h *Handle) setData(name string, data unsafe.Pointer, cleanup C.CleanupFunc) error { cName := C.CString(name) defer C.free(unsafe.Pointer(cName)) h.status = C.pam_set_data(h.handle, cName, data, cleanup) return h.err() } func (h *Handle) getData(name string) (unsafe.Pointer, error) { var data unsafe.Pointer cName := C.CString(name) defer C.free(unsafe.Pointer(cName)) h.status = C.pam_get_data(h.handle, cName, &data) return data, h.err() } // ClearData remotes the PAM data with the specified name. func (h *Handle) ClearData(name string) error { return h.setData(name, unsafe.Pointer(C.CString("")), C.CleanupFunc(C.freeData)) } // SetSecret sets a copy of the C string secret into the PAM data with the // specified name. This copy will be held in locked memory until this PAM data // is cleared. func (h *Handle) SetSecret(name string, secret unsafe.Pointer) error { return h.setData(name, C.copyIntoSecret(secret), C.CleanupFunc(C.freeSecret)) } // GetSecret returns a pointer to the C string PAM data with the specified name. // This a pointer directory to the data, so it shouldn't be modified. It should // have been previously set with SetSecret(). func (h *Handle) GetSecret(name string) (unsafe.Pointer, error) { return h.getData(name) } // SetString sets a string value for the PAM data with the specified name. func (h *Handle) SetString(name string, s string) error { return h.setData(name, unsafe.Pointer(C.CString(s)), C.CleanupFunc(C.freeData)) } // GetString gets a string value for the PAM data with the specified name. It // should have been previously set with SetString(). func (h *Handle) GetString(name string) (string, error) { data, err := h.getData(name) if err != nil { return "", err } return C.GoString((*C.char)(data)), nil } // GetItem retrieves a PAM information item. This is a pointer directly to the // data, so it shouldn't be modified. func (h *Handle) GetItem(i Item) (unsafe.Pointer, error) { var data unsafe.Pointer h.status = C.pam_get_item(h.handle, C.int(i), &data) if err := h.err(); err != nil { return nil, err } if data == nil { return nil, errors.New("item not found") } return data, nil } // StartAsPamUser sets the effective privileges to that of the PAM user, and // configures the PAM user's keyrings to be properly linked. func (h *Handle) StartAsPamUser() error { if _, err := security.UserKeyringID(h.PamUser, true); err != nil { log.Printf("Setting up keyrings in PAM: %v", err) } userPrivs, err := security.UserPrivileges(h.PamUser) if err != nil { return err } if h.origPrivs, err = security.ProcessPrivileges(); err != nil { return err } return security.SetProcessPrivileges(userPrivs) } // StopAsPamUser restores the original privileges that were running the // PAM module (this is usually root). func (h *Handle) StopAsPamUser() error { err := security.SetProcessPrivileges(h.origPrivs) if err != nil { log.Print(err) } return err } func (h *Handle) err() error { if h.status == C.PAM_SUCCESS { return nil } s := C.GoString(C.pam_strerror(h.handle, C.int(h.status))) return errors.New(s) } // Transaction represents a wrapped pam_handle_t type created with pam_start // form an application. type Transaction Handle // Start initializes a pam Transaction. End() should be called after the // Transaction is no longer needed. func Start(service, username string) (*Transaction, error) { cService := C.CString(service) defer C.free(unsafe.Pointer(cService)) cUsername := C.CString(username) defer C.free(unsafe.Pointer(cUsername)) t := &Transaction{ handle: nil, status: C.PAM_SUCCESS, } t.status = C.pam_start( cService, cUsername, C.goConv, &t.handle) return t, (*Handle)(t).err() } // End finalizes a pam Transaction with pam_end(). func (t *Transaction) End() { C.pam_end(t.handle, t.status) } // Authenticate returns a boolean indicating if the user authenticated correctly // or not. If the authentication check did not complete, an error is returned. func (t *Transaction) Authenticate(quiet bool) (bool, error) { var flags C.int = C.PAM_DISALLOW_NULL_AUTHTOK if quiet { flags |= C.PAM_SILENT } t.status = C.pam_authenticate(t.handle, flags) if t.status == C.PAM_AUTH_ERR { return false, nil } return true, (*Handle)(t).err() } fscrypt-0.2.5/pam/pam.h000066400000000000000000000030401357032351700147160ustar00rootroot00000000000000/* * pam.h - Functions to let us call into libpam from Go. * * 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. */ #ifndef FSCRYPT_PAM_H #define FSCRYPT_PAM_H #include // Conversation that will call back into Go code when appropriate. const struct pam_conv *goConv; // CleaupFuncs are used to cleanup specific PAM data. typedef void (*CleanupFunc)(pam_handle_t *pamh, void *data, int error_status); // CleaupFunc that calls free() on data. void freeData(pam_handle_t *pamh, void *data, int error_status); // CleaupFunc that frees each item in a null terminated array of pointers and // then frees the array itself. void freeArray(pam_handle_t *pamh, void **array, int error_status); // Creates a copy of a C string, which resides in a locked buffer. void *copyIntoSecret(void *data); // CleaupFunc that Zeros wipes a C string and unlocks and frees its memory. void freeSecret(pam_handle_t *pamh, char *data, int error_status); #endif // FSCRYPT_PAM_H fscrypt-0.2.5/pam/pam_test.go000066400000000000000000000014131357032351700161350ustar00rootroot00000000000000/* * pam_test.go - Stub test file that has one test that always passes. * * Copyright 2018 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 pam import "testing" func TestTrivial(t *testing.T) {} fscrypt-0.2.5/pam_fscrypt/000077500000000000000000000000001357032351700155455ustar00rootroot00000000000000fscrypt-0.2.5/pam_fscrypt/config000066400000000000000000000004731357032351700167410ustar00rootroot00000000000000Name: fscrypt PAM passphrase support Default: yes Priority: 0 Auth-Type: Additional Auth-Final: optional PAM_INSTALL_PATH Session-Type: Additional Session-Interactive-Only: yes Session-Final: optional PAM_INSTALL_PATH drop_caches lock_policies Password-Type: Additional Password-Final: optional PAM_INSTALL_PATH fscrypt-0.2.5/pam_fscrypt/pam_fscrypt.go000066400000000000000000000210301357032351700204170ustar00rootroot00000000000000/* * pam_fscrypt.go - Checks the validity of a login token key against PAM. * * 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 /* #cgo LDFLAGS: -lpam -fPIC #include #include #include */ import "C" import ( "log" "unsafe" "github.com/pkg/errors" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/pam" "github.com/google/fscrypt/security" ) const ( moduleName = "pam_fscrypt" // authtokLabel tags the AUTHTOK in the PAM data. authtokLabel = "fscrypt_authtok" // These flags are used to toggle behavior of the PAM module. debugFlag = "debug" lockFlag = "lock_policies" cacheFlag = "drop_caches" ) var ( // PamFuncs for our 4 provided methods authenticateFunc = PamFunc{"Authenticate", Authenticate} openSessionFunc = PamFunc{"OpenSession", OpenSession} closeSessionFunc = PamFunc{"CloseSession", CloseSession} chauthtokFunc = PamFunc{"Chauthtok", Chauthtok} ) // Authenticate copies the AUTHTOK (if necessary) into the PAM data so it can be // used in pam_sm_open_session. func Authenticate(handle *pam.Handle, _ map[string]bool) error { if err := handle.StartAsPamUser(); err != nil { return err } defer handle.StopAsPamUser() // If this user doesn't have a login protector, no unlocking is needed. if _, err := loginProtector(handle); err != nil { log.Printf("no protector, no need for AUTHTOK: %s", err) return nil } log.Print("copying AUTHTOK for use in the session open") authtok, err := handle.GetItem(pam.Authtok) if err != nil { return errors.Wrap(err, "could not get AUTHTOK") } err = handle.SetSecret(authtokLabel, authtok) return errors.Wrap(err, "could not set AUTHTOK data") } // OpenSession provisions any policies protected with the login protector. func OpenSession(handle *pam.Handle, _ map[string]bool) error { // We will always clear the AUTHTOK data defer handle.ClearData(authtokLabel) // Increment the count as we add a session if _, err := AdjustCount(handle, +1); err != nil { return err } if err := handle.StartAsPamUser(); err != nil { return err } defer handle.StopAsPamUser() // If there are no polices for the login protector, no unlocking needed. protector, err := loginProtector(handle) if err != nil { log.Printf("no protector to unlock: %s", err) return nil } policies := policiesUsingProtector(protector) if len(policies) == 0 { log.Print("no policies to unlock") return nil } log.Printf("unlocking %d policies protected with AUTHTOK", len(policies)) keyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) { if retry { // Login passphrase and login protector have diverged. // We could prompt the user for the old passphrase and // rewrap, but we currently don't. return nil, pam.ErrPassphrase } authtok, err := handle.GetSecret(authtokLabel) if err != nil { // pam_sm_authenticate was not run before the session is // opened. This can happen when a user does something // like "sudo su ". We could prompt for the // login passphrase here, but we currently don't. return nil, errors.Wrap(err, "AUTHTOK data missing") } return crypto.NewKeyFromCString(authtok) } if err := protector.Unlock(keyFn); err != nil { return errors.Wrapf(err, "unlocking protector %s", protector.Descriptor()) } defer protector.Lock() // We don't stop provisioning polices on error, we try all of them. for _, policy := range policies { if policy.IsProvisioned() { log.Printf("policy %s already provisioned", policy.Descriptor()) continue } if err := policy.UnlockWithProtector(protector); err != nil { log.Printf("unlocking policy %s: %s", policy.Descriptor(), err) continue } defer policy.Lock() if err := policy.Provision(); err != nil { log.Printf("provisioning policy %s: %s", policy.Descriptor(), err) continue } log.Printf("policy %s provisioned", policy.Descriptor()) } return nil } // CloseSession can deprovision all keys provisioned at the start of the // session. It can also clear the cache so these changes take effect. func CloseSession(handle *pam.Handle, args map[string]bool) error { // Only do stuff on session close when we are the last session if count, err := AdjustCount(handle, -1); err != nil || count != 0 { log.Printf("count is %d and we are not locking", count) return err } var errLock, errCache error // Don't automatically drop privileges, we may need them to drop caches. if args[lockFlag] { log.Print("locking polices protected with login protector") errLock = lockLoginPolicies(handle) } if args[cacheFlag] { log.Print("dropping appropriate filesystem caches at session close") errCache = security.DropFilesystemCache() } if errLock != nil { return errLock } return errCache } // lockLoginPolicies deprovisions all policy keys that are protected by // the user's login protector. func lockLoginPolicies(handle *pam.Handle) error { if err := handle.StartAsPamUser(); err != nil { return err } defer handle.StopAsPamUser() // If there are no polices for the login protector, no locking needed. protector, err := loginProtector(handle) if err != nil { log.Printf("nothing to lock: %s", err) return nil } policies := policiesUsingProtector(protector) if len(policies) == 0 { log.Print("no policies to lock") return nil } // We will try to deprovision all of the policies. for _, policy := range policies { if !policy.IsProvisioned() { log.Printf("policy %s not provisioned", policy.Descriptor()) continue } if err := policy.Deprovision(); err != nil { log.Printf("deprovisioning policy %s: %s", policy.Descriptor(), err) continue } log.Printf("policy %s deprovisioned", policy.Descriptor()) } return nil } // Chauthtok rewraps the login protector when the passphrase changes. func Chauthtok(handle *pam.Handle, _ map[string]bool) error { if err := handle.StartAsPamUser(); err != nil { return err } defer handle.StopAsPamUser() protector, err := loginProtector(handle) if err != nil { log.Printf("no login protector to rewrap: %s", err) return nil } oldKeyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) { if retry { // If the OLDAUTHTOK disagrees with the login protector, // we do nothing, as the protector will (probably) still // disagree after the login passphrase changes. return nil, pam.ErrPassphrase } authtok, err := handle.GetItem(pam.Oldauthtok) if err != nil { return nil, errors.Wrap(err, "could not get OLDAUTHTOK") } return crypto.NewKeyFromCString(authtok) } newKeyFn := func(_ actions.ProtectorInfo, _ bool) (*crypto.Key, error) { authtok, err := handle.GetItem(pam.Authtok) if err != nil { return nil, errors.Wrap(err, "could not get AUTHTOK") } return crypto.NewKeyFromCString(authtok) } log.Print("rewrapping login protector") if err = protector.Unlock(oldKeyFn); err != nil { return err } defer protector.Lock() return protector.Rewrap(newKeyFn) } //export pam_sm_authenticate func pam_sm_authenticate(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { return authenticateFunc.Run(pamh, argc, argv) } // pam_sm_setcred needed because we use pam_sm_authenticate. //export pam_sm_setcred func pam_sm_setcred(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { return C.PAM_SUCCESS } //export pam_sm_open_session func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { return openSessionFunc.Run(pamh, argc, argv) } //export pam_sm_close_session func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { return closeSessionFunc.Run(pamh, argc, argv) } //export pam_sm_chauthtok func pam_sm_chauthtok(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { // Only do rewrapping if we have both AUTHTOKs and a login protector. if pam.Flag(flags)&pam.PrelimCheck != 0 { return C.PAM_SUCCESS } return chauthtokFunc.Run(pamh, argc, argv) } // main() is needed to make a shared library compile func main() {} fscrypt-0.2.5/pam_fscrypt/run_fscrypt.go000066400000000000000000000150001357032351700204460ustar00rootroot00000000000000/* * run_fscrypt.go - Helpers for running functions in the PAM module. * * 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 /* #cgo LDFLAGS: -lpam -fPIC #include #include #include */ import "C" import ( "fmt" "io" "io/ioutil" "log" "log/syslog" "os" "path/filepath" "runtime/debug" "unsafe" "golang.org/x/sys/unix" "github.com/pkg/errors" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/pam" "github.com/google/fscrypt/util" ) const ( // countDirectory is in a tmpfs filesystem so it will reset on reboot. countDirectory = "/run/fscrypt" // count files should only be readable and writable by root countDirectoryPermissions = 0700 countFilePermissions = 0600 countFileFormat = "%d\n" ) // PamFunc is used to define the various actions in the PAM module. type PamFunc struct { // Name of the function being executed name string // Go implementation of this function impl func(handle *pam.Handle, args map[string]bool) error } // Run is used to convert between the Go functions and exported C funcs. func (f *PamFunc) Run(pamh unsafe.Pointer, argc C.int, argv **C.char) (ret C.int) { args := parseArgs(argc, argv) errorWriter := setupLogging(args) // Log any panics to the errorWriter defer func() { if r := recover(); r != nil { ret = C.PAM_SERVICE_ERR fmt.Fprintf(errorWriter, "%s(%v) panicked: %s\nPlease open a bug.\n%s", f.name, args, r, debug.Stack()) } }() log.Printf("%s(%v) starting", f.name, args) handle, err := pam.NewHandle(pamh) if err == nil { err = f.impl(handle, args) } if err != nil { fmt.Fprintf(errorWriter, "%s(%v) failed: %s", f.name, args, err) return C.PAM_SERVICE_ERR } log.Printf("%s(%v) succeeded", f.name, args) return C.PAM_SUCCESS } // parseArgs takes a list of C arguments into a PAM function and returns a map // where a key has a value of true if it appears in the argument list. func parseArgs(argc C.int, argv **C.char) map[string]bool { args := make(map[string]bool) if argc == 0 || argv == nil { return args } for _, cString := range util.PointerSlice(unsafe.Pointer(argv))[:argc] { args[C.GoString((*C.char)(cString))] = true } return args } // setupLogging directs turns off standard logging (or redirects it to debug // syslog if the "debug" argument is passed) and returns a writer to the error // syslog. func setupLogging(args map[string]bool) io.Writer { log.SetFlags(0) // Syslog already includes time data itself log.SetOutput(ioutil.Discard) if args[debugFlag] { debugWriter, err := syslog.New(syslog.LOG_DEBUG, moduleName) if err == nil { log.SetOutput(debugWriter) } } errorWriter, err := syslog.New(syslog.LOG_ERR, moduleName) if err != nil { return ioutil.Discard } return errorWriter } // loginProtector returns the login protector corresponding to the PAM_USER if // one exists. This protector descriptor (if found) will be cached in the pam // data, under descriptorLabel. func loginProtector(handle *pam.Handle) (*actions.Protector, error) { ctx, err := actions.NewContextFromMountpoint("/", handle.PamUser) if err != nil { return nil, err } // Find the user's PAM protector. options, err := ctx.ProtectorOptions() if err != nil { return nil, err } uid := int64(util.AtoiOrPanic(handle.PamUser.Uid)) for _, option := range options { if option.Source() == metadata.SourceType_pam_passphrase && option.UID() == uid { return actions.GetProtectorFromOption(ctx, option) } } return nil, errors.Errorf("no PAM protector for UID=%d on %q", uid, ctx.Mount.Path) } // policiesUsingProtector searches all the mountpoints for any policies // protected with the specified protector. func policiesUsingProtector(protector *actions.Protector) []*actions.Policy { mounts, err := filesystem.AllFilesystems() if err != nil { log.Print(err) return nil } var policies []*actions.Policy for _, mount := range mounts { // Skip mountpoints that do not use the protector. if _, _, err := mount.GetProtector(protector.Descriptor()); err != nil { continue } policyDescriptors, err := mount.ListPolicies() if err != nil { log.Printf("listing policies: %s", err) continue } // Clone context but modify the mountpoint ctx := *protector.Context ctx.Mount = mount for _, policyDescriptor := range policyDescriptors { policy, err := actions.GetPolicy(&ctx, policyDescriptor) if err != nil { log.Printf("reading policy: %s", err) continue } if policy.UsesProtector(protector) { policies = append(policies, policy) } } } return policies } // AdjustCount changes the session count for the pam user by the specified // amount. If the count file does not exist, create it as if it had a count of // zero. If the adjustment would bring the count below zero, the count is set to // zero. The value of the new count is returned. Requires root privileges. func AdjustCount(handle *pam.Handle, delta int) (int, error) { // Make sure the directory exists if err := os.MkdirAll(countDirectory, countDirectoryPermissions); err != nil { return 0, err } path := filepath.Join(countDirectory, handle.PamUser.Uid+".count") file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, countFilePermissions) if err != nil { return 0, err } if err = unix.Flock(int(file.Fd()), unix.LOCK_EX); err != nil { return 0, err } defer file.Close() newCount := util.MaxInt(getCount(file)+delta, 0) if _, err = file.Seek(0, io.SeekStart); err != nil { return 0, err } if _, err = fmt.Fprintf(file, countFileFormat, newCount); err != nil { return 0, err } log.Printf("Session count for UID=%s updated to %d", handle.PamUser.Uid, newCount) return newCount, nil } // Returns the count in the file (or zero if the count cannot be read). func getCount(file *os.File) int { var count int if _, err := fmt.Fscanf(file, countFileFormat, &count); err != nil { return 0 } return count } fscrypt-0.2.5/pam_fscrypt/run_test.go000066400000000000000000000017551357032351700177470ustar00rootroot00000000000000/* * run_test.go - tests that the PAM helper functions work properly * * 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 ( "testing" ) func TestParseArgsEmpty(t *testing.T) { // An empty argv should create a map with no entries args := parseArgs(0, nil) if args == nil { t.Fatal("args map should not be nil") } if len(args) > 0 { t.Fatal("args map should not have any entries") } } fscrypt-0.2.5/security/000077500000000000000000000000001357032351700150655ustar00rootroot00000000000000fscrypt-0.2.5/security/cache.go000066400000000000000000000031661357032351700164650ustar00rootroot00000000000000/* * cache.go - Handles cache clearing and management. * * 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 security import ( "log" "os" "golang.org/x/sys/unix" ) // DropFilesystemCache instructs the kernel to free the reclaimable inodes and // dentries. This has the effect of making encrypted directories whose keys are // not present no longer accessible. Requires root privileges. func DropFilesystemCache() error { // Dirty reclaimable inodes must be synced so that they will be freed. log.Print("syncing changes to filesystem") unix.Sync() // See: https://www.kernel.org/doc/Documentation/sysctl/vm.txt log.Print("freeing reclaimable inodes and dentries") file, err := os.OpenFile("/proc/sys/vm/drop_caches", os.O_WRONLY|os.O_SYNC, 0) if err != nil { return err } defer file.Close() // "2" just frees the reclaimable inodes and dentries. The associated // pages to these inodes will be freed. We do not need to free the // entire pagecache (as this will severely impact performance). _, err = file.WriteString("2") return err } fscrypt-0.2.5/security/keyring.go000066400000000000000000000155031357032351700170700ustar00rootroot00000000000000/* * keyring.go - Handles inserting/removing into user keyrings. * * 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 security import ( "fmt" "log" "os/user" "sync" "github.com/pkg/errors" "golang.org/x/sys/unix" "github.com/google/fscrypt/util" ) // KeyType is always logon as required by filesystem encryption. const KeyType = "logon" // Keyring related error values var ( ErrKeySearch = errors.New("could not find key with descriptor") ErrKeyRemove = util.SystemError("could not remove key from the keyring") ErrKeyInsert = util.SystemError("could not insert key into the keyring") ErrSessionUserKeying = errors.New("user keyring not linked into session keyring") ErrAccessUserKeyring = errors.New("could not access user keyring") ErrLinkUserKeyring = util.SystemError("could not link user keyring into root keyring") ) // FindKey tries to locate a key in the kernel keyring with the provided // description. The key ID is returned if we can find the key. An error is // returned if the key does not exist. func FindKey(description string, target *user.User) (int, error) { keyringID, err := UserKeyringID(target, false) if err != nil { return 0, err } keyID, err := unix.KeyctlSearch(keyringID, KeyType, description, 0) log.Printf("KeyctlSearch(%d, %s, %s) = %d, %v", keyringID, KeyType, description, keyID, err) if err != nil { return 0, errors.Wrap(ErrKeySearch, err.Error()) } return keyID, err } // RemoveKey tries to remove a policy key from the kernel keyring with the // provided description. An error is returned if the key does not exist. func RemoveKey(description string, target *user.User) error { keyID, err := FindKey(description, target) if err != nil { return err } // We use KEYCTL_INVALIDATE instead of KEYCTL_REVOKE because // invalidating a key immediately removes it. _, err = unix.KeyctlInt(unix.KEYCTL_INVALIDATE, keyID, 0, 0, 0) log.Printf("KeyctlInvalidate(%d) = %v", keyID, err) if err != nil { return errors.Wrap(ErrKeyRemove, err.Error()) } return nil } // InsertKey puts the provided data into the kernel keyring with the provided // description. func InsertKey(data []byte, description string, target *user.User) error { keyringID, err := UserKeyringID(target, true) if err != nil { return err } keyID, err := unix.AddKey(KeyType, description, data, keyringID) log.Printf("KeyctlAddKey(%s, %s, , %d) = %d, %v", KeyType, description, keyringID, keyID, err) if err != nil { return errors.Wrap(ErrKeyInsert, err.Error()) } return nil } var ( keyringIDCache = make(map[int]int) cacheLock sync.Mutex ) // UserKeyringID returns the key id of the target user's user keyring. We also // ensure that the keyring will be accessible by linking it into the process // keyring and linking it into the root user keyring (permissions allowing). If // checkSession is true, an error is returned if a normal user requests their // user keyring, but it is not in the current session keyring. func UserKeyringID(target *user.User, checkSession bool) (int, error) { uid := util.AtoiOrPanic(target.Uid) targetKeyring, err := userKeyringIDLookup(uid) if err != nil { return 0, errors.Wrap(ErrAccessUserKeyring, err.Error()) } if !util.IsUserRoot() { // Make sure the returned keyring will be accessible by checking // that it is in the session keyring. if checkSession && !isUserKeyringInSession(uid) { return 0, ErrSessionUserKeying } return targetKeyring, nil } // Make sure the returned keyring will be accessible by linking it into // the root user's user keyring (which will not be garbage collected). rootKeyring, err := userKeyringIDLookup(0) if err != nil { return 0, errors.Wrap(ErrLinkUserKeyring, err.Error()) } if rootKeyring != targetKeyring { if err = keyringLink(targetKeyring, rootKeyring); err != nil { return 0, errors.Wrap(ErrLinkUserKeyring, err.Error()) } } return targetKeyring, nil } func userKeyringIDLookup(uid int) (keyringID int, err error) { cacheLock.Lock() defer cacheLock.Unlock() var ok bool if keyringID, ok = keyringIDCache[uid]; ok { return } // Our goals here are to: // - Find the user keyring (for the provided uid) // - Link it into the current process keyring (so we can use it) // - Make no permanent changes to the process privileges // Complicating this are the facts that: // - The value of KEY_SPEC_USER_KEYRING is determined by the ruid // - Keyring linking permissions use the euid // So we have to change both the ruid and euid to make this work, // setting the suid to 0 so that we can later switch back. ruid, euid, suid := getUids() if ruid != uid || euid != uid { if err = setUids(uid, uid, 0); err != nil { return } defer func() { resetErr := setUids(ruid, euid, suid) if resetErr != nil { err = resetErr } }() } // We get the value of KEY_SPEC_USER_KEYRING. Note that this will also // trigger the creation of the uid keyring if it does not yet exist. keyringID, err = unix.KeyctlGetKeyringID(unix.KEY_SPEC_USER_KEYRING, true) log.Printf("keyringID(_uid.%d) = %d, %v", uid, keyringID, err) if err != nil { return 0, err } // We still want to use this keyring after our privileges are reset. So // we link it into the process keyring, preventing a loss of access. if err = keyringLink(keyringID, unix.KEY_SPEC_PROCESS_KEYRING); err != nil { return 0, err } keyringIDCache[uid] = keyringID return keyringID, nil } // isUserKeyringInSession tells us if the user's uid keyring is in the current // session keyring. func isUserKeyringInSession(uid int) bool { // We cannot use unix.KEY_SPEC_SESSION_KEYRING directly as that might // create a session keyring if one does not exist. sessionKeyring, err := unix.KeyctlGetKeyringID(unix.KEY_SPEC_SESSION_KEYRING, false) log.Printf("keyringID(session) = %d, %v", sessionKeyring, err) if err != nil { return false } description := fmt.Sprintf("_uid.%d", uid) id, err := unix.KeyctlSearch(sessionKeyring, "keyring", description, 0) log.Printf("KeyctlSearch(%d, keyring, %s) = %d, %v", sessionKeyring, description, id, err) return err == nil } func keyringLink(keyID int, keyringID int) error { _, err := unix.KeyctlInt(unix.KEYCTL_LINK, keyID, keyringID, 0, 0) log.Printf("KeyctlLink(%d, %d) = %v", keyID, keyringID, err) return err } fscrypt-0.2.5/security/privileges.go000066400000000000000000000127161357032351700175740ustar00rootroot00000000000000/* * privileges.go - Functions for managing users and privileges. * * 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 security manages: // - Cache clearing (cache.go) // - Keyring Operations (keyring.go) // - Privilege manipulation (privileges.go) // - Maintaining the link between the root and user keyrings. package security // Use the libc versions of setreuid, setregid, and setgroups instead of the // "sys/unix" versions. The "sys/unix" versions use the raw syscalls which // operate on the calling thread only, whereas the libc versions operate on the // whole process. And we need to operate on the whole process, firstly for // pam_fscrypt to prevent the privileges of Go worker threads from diverging // from the PAM stack's "main" thread, violating libc's assumption and causing // an abort() later in the PAM stack; and secondly because Go code may migrate // between OS-level threads while it's running. // // See also: https://github.com/golang/go/issues/1435 // // Also we need to wrap the libc functions in our own C functions rather than // calling them directly because in the glibc headers (but not necessarily in // the headers for other C libraries that may be used on Linux) they are // declared to take __uid_t and __gid_t arguments rather than uid_t and gid_t. // And while these are typedef'ed to the same underlying type, before Go 1.10, // cgo maps them to different Go types. /* #define _GNU_SOURCE // for getresuid and setresuid #include #include // getting and setting uids and gids #include // setgroups */ import "C" import ( "log" "os/user" "syscall" "github.com/pkg/errors" "github.com/google/fscrypt/util" ) // Privileges encapsulate the effective uid/gid and groups of a process. type Privileges struct { euid C.uid_t egid C.gid_t groups []C.gid_t } // ProcessPrivileges returns the process's current effective privileges. func ProcessPrivileges() (*Privileges, error) { ruid := C.getuid() euid := C.geteuid() rgid := C.getgid() egid := C.getegid() var groups []C.gid_t n, err := C.getgroups(0, nil) if n < 0 { return nil, err } // If n == 0, the user isn't in any groups, so groups == nil is fine. if n > 0 { groups = make([]C.gid_t, n) n, err = C.getgroups(n, &groups[0]) if n < 0 { return nil, err } groups = groups[:n] } log.Printf("Current privs (real, effective): uid=(%d,%d) gid=(%d,%d) groups=%v", ruid, euid, rgid, egid, groups) return &Privileges{euid, egid, groups}, nil } // UserPrivileges returns the default privileges for the specified user. func UserPrivileges(user *user.User) (*Privileges, error) { privs := &Privileges{ euid: C.uid_t(util.AtoiOrPanic(user.Uid)), egid: C.gid_t(util.AtoiOrPanic(user.Gid)), } userGroups, err := user.GroupIds() if err != nil { return nil, util.SystemError(err.Error()) } privs.groups = make([]C.gid_t, len(userGroups)) for i, group := range userGroups { privs.groups[i] = C.gid_t(util.AtoiOrPanic(group)) } return privs, nil } // SetProcessPrivileges sets the privileges of the current process to have those // specified by privs. The original privileges can be obtained by first saving // the output of ProcessPrivileges, calling SetProcessPrivileges with the // desired privs, then calling SetProcessPrivileges with the saved privs. func SetProcessPrivileges(privs *Privileges) error { log.Printf("Setting euid=%d egid=%d groups=%v", privs.euid, privs.egid, privs.groups) // If setting privs as root, we need to set the euid to 0 first, so that // we will have the necessary permissions to make the other changes to // the groups/egid/euid, regardless of our original euid. C.seteuid(0) // Separately handle the case where the user is in no groups. numGroups := C.size_t(len(privs.groups)) groupsPtr := (*C.gid_t)(nil) if numGroups > 0 { groupsPtr = &privs.groups[0] } if res, err := C.setgroups(numGroups, groupsPtr); res < 0 { return errors.Wrapf(err.(syscall.Errno), "setting groups") } if res, err := C.setegid(privs.egid); res < 0 { return errors.Wrapf(err.(syscall.Errno), "setting egid") } if res, err := C.seteuid(privs.euid); res < 0 { return errors.Wrapf(err.(syscall.Errno), "setting euid") } ProcessPrivileges() return nil } func setUids(ruid, euid, suid int) error { log.Printf("Setting ruid=%d euid=%d suid=%d", ruid, euid, suid) // We elevate all the privs before setting them. This prevents issues // with (ruid=1000,euid=1000,suid=0), where just a single call to // setresuid might fail with permission denied. if res, err := C.setresuid(0, 0, 0); res < 0 { return errors.Wrapf(err.(syscall.Errno), "setting uids") } if res, err := C.setresuid(C.uid_t(ruid), C.uid_t(euid), C.uid_t(suid)); res < 0 { return errors.Wrapf(err.(syscall.Errno), "setting uids") } return nil } func getUids() (int, int, int) { var ruid, euid, suid C.uid_t C.getresuid(&ruid, &euid, &suid) return int(ruid), int(euid), int(suid) } fscrypt-0.2.5/security/security_test.go000066400000000000000000000014251357032351700203240ustar00rootroot00000000000000/* * security_test.go - Stub test file that has one test that always passes. * * Copyright 2018 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 security import "testing" func TestTrivial(t *testing.T) {} fscrypt-0.2.5/util/000077500000000000000000000000001357032351700141735ustar00rootroot00000000000000fscrypt-0.2.5/util/errors.go000066400000000000000000000075351357032351700160500ustar00rootroot00000000000000/* * errors.go - Custom errors and error functions used by fscrypt * * 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 util import ( "fmt" "io" "log" "os" "github.com/pkg/errors" ) // ErrReader wraps an io.Reader, passing along calls to Read() until a read // fails. Then, the error is stored, and all subsequent calls to Read() do // nothing. This allows you to write code which has many subsequent reads and // do all of the error checking at the end. For example: // // r := NewErrReader(reader) // r.Read(foo) // r.Read(bar) // r.Read(baz) // if r.Err() != nil { // // Handle error // } // // Taken from https://blog.golang.org/errors-are-values by Rob Pike. type ErrReader struct { r io.Reader err error } // NewErrReader creates an ErrReader which wraps the provided reader. func NewErrReader(reader io.Reader) *ErrReader { return &ErrReader{r: reader, err: nil} } // Read runs ReadFull on the wrapped reader if no errors have occurred. // Otherwise, the previous error is just returned and no reads are attempted. func (e *ErrReader) Read(p []byte) (n int, err error) { if e.err == nil { n, e.err = io.ReadFull(e.r, p) } return n, e.err } // Err returns the first encountered err (or nil if no errors occurred). func (e *ErrReader) Err() error { return e.err } // ErrWriter works exactly like ErrReader, except with io.Writer. type ErrWriter struct { w io.Writer err error } // NewErrWriter creates an ErrWriter which wraps the provided writer. func NewErrWriter(writer io.Writer) *ErrWriter { return &ErrWriter{w: writer, err: nil} } // Write runs the wrapped writer's Write if no errors have occurred. Otherwise, // the previous error is just returned and no writes are attempted. func (e *ErrWriter) Write(p []byte) (n int, err error) { if e.err == nil { n, e.err = e.w.Write(p) } return n, e.err } // Err returns the first encountered err (or nil if no errors occurred). func (e *ErrWriter) Err() error { return e.err } // CheckValidLength returns an invalid length error if expected != actual func CheckValidLength(expected, actual int) error { if expected == actual { return nil } return fmt.Errorf("expected length of %d, got %d", expected, actual) } // SystemError is an error that should indicate something has gone wrong in the // underlying system (syscall failure, bad ioctl, etc...). type SystemError string func (s SystemError) Error() string { return "system error: " + string(s) } // NeverError panics if a non-nil error is passed in. It should be used to check // for logic errors, not to handle recoverable errors. func NeverError(err error) { if err != nil { log.Panicf("NeverError() check failed: %v", err) } } var ( // testEnvVarName is the name of an environment variable that should be // set to an empty mountpoint. This is only used for integration tests. // If not set, integration tests are skipped. testEnvVarName = "TEST_FILESYSTEM_ROOT" // ErrSkipIntegration indicates integration tests shouldn't be run. ErrSkipIntegration = errors.New("skipping integration test") ) // TestRoot returns a the root of a filesystem specified by testEnvVarName. This // function is only used for integration tests. func TestRoot() (string, error) { path := os.Getenv(testEnvVarName) if path == "" { return "", ErrSkipIntegration } return path, nil } fscrypt-0.2.5/util/util.go000066400000000000000000000066421357032351700155070ustar00rootroot00000000000000/* * util.go - Various helpers used throughout fscrypt * * 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 util contains useful components for simplifying Go code. // // The package contains common error types (errors.go) and functions for // converting arrays to pointers. package util import ( "bufio" "os" "os/user" "strconv" "unsafe" ) // Ptr converts a Go byte array to a pointer to the start of the array. func Ptr(slice []byte) unsafe.Pointer { if len(slice) == 0 { return nil } return unsafe.Pointer(&slice[0]) } // ByteSlice takes a pointer to some data and views it as a slice of bytes. // Note, indexing into this slice is unsafe. func ByteSlice(ptr unsafe.Pointer) []byte { // Slice must fit in the smallest address space go supports. return (*[1 << 30]byte)(ptr)[:] } // PointerSlice takes a pointer to an array of pointers and views it as a slice // of pointers. Note, indexing into this slice is unsafe. func PointerSlice(ptr unsafe.Pointer) []unsafe.Pointer { // Slice must fit in the smallest address space go supports. return (*[1 << 28]unsafe.Pointer)(ptr)[:] } // Index returns the first index i such that inVal == inArray[i]. // ok is true if we find a match, false otherwise. func Index(inVal int64, inArray []int64) (index int, ok bool) { for index, val := range inArray { if val == inVal { return index, true } } return 0, false } // Lookup finds inVal in inArray and returns the corresponding element in // outArray. Specifically, if inVal == inArray[i], outVal == outArray[i]. // ok is true if we find a match, false otherwise. func Lookup(inVal int64, inArray, outArray []int64) (outVal int64, ok bool) { index, ok := Index(inVal, inArray) if !ok { return 0, false } return outArray[index], true } // MinInt returns the lesser of a and b. func MinInt(a, b int) int { if a < b { return a } return b } // MaxInt returns the greater of a and b. func MaxInt(a, b int) int { if a > b { return a } return b } // MinInt64 returns the lesser of a and b. func MinInt64(a, b int64) int64 { if a < b { return a } return b } // ReadLine returns a line of input from standard input. An empty string is // returned if the user didn't insert anything or on error. func ReadLine() (string, error) { scanner := bufio.NewScanner(os.Stdin) scanner.Scan() return scanner.Text(), scanner.Err() } // AtoiOrPanic converts a string to an int or it panics. Should only be used in // situations where the input MUST be a decimal number. func AtoiOrPanic(input string) int { i, err := strconv.Atoi(input) if err != nil { panic(err) } return i } // EffectiveUser returns the user entry corresponding to the effective user. func EffectiveUser() (*user.User, error) { return user.LookupId(strconv.Itoa(os.Geteuid())) } // IsUserRoot checks if the effective user is root. func IsUserRoot() bool { return os.Geteuid() == 0 } fscrypt-0.2.5/util/util_test.go000066400000000000000000000043371357032351700165450ustar00rootroot00000000000000/* * util_test.go - Tests the util package * * 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 util import ( "bytes" "testing" "unsafe" ) const offset = 3 var ( byteArr = []byte{'a', 'b', 'c', 'd'} ptrArr = []*int{&a, &b, &c, &d} a = 1 b = 2 c = 3 d = 4 ) // Make sure the address behaves well under slicing func TestPtrOffset(t *testing.T) { i1 := uintptr(Ptr(byteArr[offset:])) i2 := uintptr(Ptr(byteArr)) if i1 != i2+offset { t.Errorf("pointers %v and %v do not have an offset of %v", i1, i2, offset) } } // Tests that the ByteSlice method essentially reverses the Ptr method func TestByteSlice(t *testing.T) { ptr := Ptr(byteArr) generatedArr := ByteSlice(ptr)[:len(byteArr)] if !bytes.Equal(byteArr, generatedArr) { t.Errorf("generated array (%v) and original array (%v) do not agree", generatedArr, byteArr) } } // Tests that the PointerSlice method correctly handles Go Pointers func TestPointerSlice(t *testing.T) { arrPtr := unsafe.Pointer(&ptrArr[0]) // Convert an array of unsafe pointers to int pointers. for i, ptr := range PointerSlice(arrPtr)[:len(ptrArr)] { if ptrArr[i] != (*int)(ptr) { t.Errorf("generated array and original array disagree at %d", i) } } } // Make sure NeverError actually panics func TestNeverErrorPanic(t *testing.T) { defer func() { if r := recover(); r == nil { t.Errorf("NeverError did not panic") } }() err := SystemError("Hello") NeverError(err) } // Make sure NeverError doesn't panic on nil func TestNeverErrorNoPanic(t *testing.T) { defer func() { if r := recover(); r != nil { t.Errorf("NeverError panicked") } }() NeverError(nil) }