Switch to external libgocryptfs
This commit is contained in:
parent
5da1c05c7b
commit
ae93d78615
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "app/libgocryptfs"]
|
||||
path = app/libgocryptfs
|
||||
url = https://forge.chapril.org/hardcoresushi/libgocryptfs.git
|
1
app/libgocryptfs
Submodule
1
app/libgocryptfs
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 847d4fa7817c84fe1b78726f031172ea508dab19
|
4
app/libgocryptfs/.gitignore
vendored
4
app/libgocryptfs/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
openssl*
|
||||
lib
|
||||
include
|
||||
build
|
@ -1,75 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -z ${ANDROID_NDK_HOME+x} ]; then
|
||||
echo "Error: \$ANDROID_NDK_HOME is not defined."
|
||||
elif [ -z ${OPENSSL_PATH+x} ]; then
|
||||
echo "Error: \$OPENSSL_PATH is not defined."
|
||||
else
|
||||
NDK_BIN_PATH="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||
declare -a ABIs=("x86_64" "arm64-v8a" "armeabi-v7a")
|
||||
|
||||
compile_openssl(){
|
||||
if [ ! -d "./lib/$1" ]; then
|
||||
if [ "$1" = "x86_64" ]; then
|
||||
OPENSSL_ARCH="android-x86_64"
|
||||
elif [ "$1" = "arm64-v8a" ]; then
|
||||
OPENSSL_ARCH="android-arm64"
|
||||
elif [ "$1" = "armeabi-v7a" ]; then
|
||||
OPENSSL_ARCH="android-arm"
|
||||
else
|
||||
echo "Invalid ABI: $1"
|
||||
exit
|
||||
fi
|
||||
|
||||
export CFLAGS=-D__ANDROID_API__=21
|
||||
export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$ANDROID_NDK_HOME/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin:$PATH
|
||||
(cd "$OPENSSL_PATH" && if [ -f "Makefile" ]; then make clean; fi && ./Configure $OPENSSL_ARCH -D__ANDROID_API__=21 no-stdio && make build_libs)
|
||||
mkdir -p "./lib/$1" && cp "$OPENSSL_PATH/libcrypto.a" "$OPENSSL_PATH/libssl.a" "./lib/$1"
|
||||
mkdir -p "./include/$1" && cp -r "$OPENSSL_PATH"/include/* "./include/$1/"
|
||||
fi
|
||||
}
|
||||
|
||||
compile_for_arch(){
|
||||
compile_openssl $1
|
||||
MAIN_PACKAGE="main.go"
|
||||
if [ "$1" = "x86_64" ]; then
|
||||
CFN="x86_64-linux-android21-clang"
|
||||
elif [ "$1" = "arm64-v8a" ]; then
|
||||
CFN="aarch64-linux-android21-clang"
|
||||
export GOARCH=arm64
|
||||
export GOARM=7
|
||||
elif [ "$1" = "armeabi-v7a" ]; then
|
||||
CFN="armv7a-linux-androideabi21-clang"
|
||||
export GOARCH=arm
|
||||
export GOARM=7
|
||||
MAIN_PACKAGE="main32.go"
|
||||
#patch arch specific code
|
||||
sed "s/C.malloc(C.ulong/C.malloc(C.uint/g" main.go > $MAIN_PACKAGE
|
||||
sed -i "s/st.Mtim.Sec/int64(st.Mtim.Sec)/g" $MAIN_PACKAGE
|
||||
else
|
||||
echo "Invalid ABI: $1"
|
||||
exit
|
||||
fi
|
||||
|
||||
export CC="$NDK_BIN_PATH/$CFN"
|
||||
export CXX="$NDK_BIN_PATH/$CFN++"
|
||||
export CGO_ENABLED=1
|
||||
export GOOS=android
|
||||
export CGO_CFLAGS="-I ${PWD}/include/$1"
|
||||
export CGO_LDFLAGS="-Wl,-soname=libgocryptfs.so -L${PWD}/lib/$1"
|
||||
go build -o build/$1/libgocryptfs.so -buildmode=c-shared $MAIN_PACKAGE
|
||||
if [ $MAIN_PACKAGE = "main32.go" ]; then
|
||||
rm $MAIN_PACKAGE
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$#" -eq 1 ]; then
|
||||
compile_for_arch $1
|
||||
else
|
||||
for abi in ${ABIs[@]}; do
|
||||
echo "Compiling for $abi..."
|
||||
compile_for_arch $abi
|
||||
done
|
||||
fi
|
||||
echo "Done."
|
||||
fi
|
@ -1,168 +0,0 @@
|
||||
// Package cryptocore wraps OpenSSL and Go GCM crypto and provides
|
||||
// a nonce generator.
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
|
||||
"../eme"
|
||||
|
||||
"../siv_aead"
|
||||
"../stupidgcm"
|
||||
)
|
||||
|
||||
const (
|
||||
// KeyLen is the cipher key length in bytes. 32 for AES-256.
|
||||
KeyLen = 32
|
||||
// AuthTagLen is the length of a GCM auth tag in bytes.
|
||||
AuthTagLen = 16
|
||||
)
|
||||
|
||||
// AEADTypeEnum indicates the type of AEAD backend in use.
|
||||
type AEADTypeEnum int
|
||||
|
||||
const (
|
||||
// BackendOpenSSL specifies the OpenSSL backend.
|
||||
BackendOpenSSL AEADTypeEnum = 3
|
||||
// BackendGoGCM specifies the Go based GCM backend.
|
||||
BackendGoGCM AEADTypeEnum = 4
|
||||
// BackendAESSIV specifies an AESSIV backend.
|
||||
BackendAESSIV AEADTypeEnum = 5
|
||||
)
|
||||
|
||||
// CryptoCore is the low level crypto implementation.
|
||||
type CryptoCore struct {
|
||||
// EME is used for filename encryption.
|
||||
EMECipher *eme.EMECipher
|
||||
// GCM or AES-SIV. This is used for content encryption.
|
||||
AEADCipher cipher.AEAD
|
||||
// Which backend is behind AEADCipher?
|
||||
AEADBackend AEADTypeEnum
|
||||
// GCM needs unique IVs (nonces)
|
||||
IVGenerator *nonceGenerator
|
||||
IVLen int
|
||||
}
|
||||
|
||||
// New returns a new CryptoCore object or panics.
|
||||
//
|
||||
// Even though the "GCMIV128" feature flag is now mandatory, we must still
|
||||
// support 96-bit IVs here because they were used for encrypting the master
|
||||
// key in gocryptfs.conf up to gocryptfs v1.2. v1.3 switched to 128 bits.
|
||||
//
|
||||
// Note: "key" is either the scrypt hash of the password (when decrypting
|
||||
// a config file) or the masterkey (when finally mounting the filesystem).
|
||||
func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDecode bool) *CryptoCore {
|
||||
if len(key) != KeyLen {
|
||||
log.Panic(fmt.Sprintf("Unsupported key length %d", len(key)))
|
||||
}
|
||||
// We want the IV size in bytes
|
||||
IVLen := IVBitLen / 8
|
||||
|
||||
// Initialize EME for filename encryption.
|
||||
var emeCipher *eme.EMECipher
|
||||
var err error
|
||||
{
|
||||
var emeBlockCipher cipher.Block
|
||||
if useHKDF {
|
||||
emeKey := HkdfDerive(key, HkdfInfoEMENames, KeyLen)
|
||||
emeBlockCipher, err = aes.NewCipher(emeKey)
|
||||
for i := range emeKey {
|
||||
emeKey[i] = 0
|
||||
}
|
||||
} else {
|
||||
emeBlockCipher, err = aes.NewCipher(key)
|
||||
}
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
emeCipher = eme.New(emeBlockCipher)
|
||||
}
|
||||
|
||||
// Initialize an AEAD cipher for file content encryption.
|
||||
var aeadCipher cipher.AEAD
|
||||
if aeadType == BackendOpenSSL || aeadType == BackendGoGCM {
|
||||
var gcmKey []byte
|
||||
if useHKDF {
|
||||
gcmKey = HkdfDerive(key, hkdfInfoGCMContent, KeyLen)
|
||||
} else {
|
||||
gcmKey = append([]byte{}, key...)
|
||||
}
|
||||
switch aeadType {
|
||||
case BackendOpenSSL:
|
||||
if IVLen != 16 {
|
||||
log.Panic("stupidgcm only supports 128-bit IVs")
|
||||
}
|
||||
aeadCipher = stupidgcm.New(gcmKey, forceDecode)
|
||||
case BackendGoGCM:
|
||||
goGcmBlockCipher, err := aes.NewCipher(gcmKey)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
aeadCipher, err = cipher.NewGCMWithNonceSize(goGcmBlockCipher, IVLen)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
for i := range gcmKey {
|
||||
gcmKey[i] = 0
|
||||
}
|
||||
} else if aeadType == BackendAESSIV {
|
||||
if IVLen != 16 {
|
||||
// SIV supports any nonce size, but we only use 16.
|
||||
log.Panic("AES-SIV must use 16-byte nonces")
|
||||
}
|
||||
// AES-SIV uses 1/2 of the key for authentication, 1/2 for
|
||||
// encryption, so we need a 64-bytes key for AES-256. Derive it from
|
||||
// the 32-byte master key using HKDF, or, for older filesystems, with
|
||||
// SHA256.
|
||||
var key64 []byte
|
||||
if useHKDF {
|
||||
key64 = HkdfDerive(key, hkdfInfoSIVContent, siv_aead.KeyLen)
|
||||
} else {
|
||||
s := sha512.Sum512(key)
|
||||
key64 = s[:]
|
||||
}
|
||||
aeadCipher = siv_aead.New(key64)
|
||||
for i := range key64 {
|
||||
key64[i] = 0
|
||||
}
|
||||
} else {
|
||||
log.Panic("unknown backend cipher")
|
||||
}
|
||||
return &CryptoCore{
|
||||
EMECipher: emeCipher,
|
||||
AEADCipher: aeadCipher,
|
||||
AEADBackend: aeadType,
|
||||
IVGenerator: &nonceGenerator{nonceLen: IVLen},
|
||||
IVLen: IVLen,
|
||||
}
|
||||
}
|
||||
|
||||
type wiper interface {
|
||||
Wipe()
|
||||
}
|
||||
|
||||
// Wipe tries to wipe secret keys from memory by overwriting them with zeros
|
||||
// and/or setting references to nil.
|
||||
//
|
||||
// This is not bulletproof due to possible GC copies, but
|
||||
// still raises to bar for extracting the key.
|
||||
func (c *CryptoCore) Wipe() {
|
||||
be := c.AEADBackend
|
||||
if be == BackendOpenSSL || be == BackendAESSIV {
|
||||
// We don't use "x, ok :=" because we *want* to crash loudly if the
|
||||
// type assertion fails.
|
||||
w := c.AEADCipher.(wiper)
|
||||
w.Wipe()
|
||||
}
|
||||
// We have no access to the keys (or key-equivalents) stored inside the
|
||||
// Go stdlib. Best we can is to nil the references and force a GC.
|
||||
c.AEADCipher = nil
|
||||
c.EMECipher = nil
|
||||
runtime.GC()
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"log"
|
||||
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
const (
|
||||
// "info" data that HKDF mixes into the generated key to make it unique.
|
||||
// For convenience, we use a readable string.
|
||||
HkdfInfoEMENames = "EME filename encryption"
|
||||
hkdfInfoGCMContent = "AES-GCM file content encryption"
|
||||
hkdfInfoSIVContent = "AES-SIV file content encryption"
|
||||
)
|
||||
|
||||
// hkdfDerive derives "outLen" bytes from "masterkey" and "info" using
|
||||
// HKDF-SHA256 (RFC 5869).
|
||||
// It returns the derived bytes or panics.
|
||||
func HkdfDerive(masterkey []byte, info string, outLen int) (out []byte) {
|
||||
h := hkdf.New(sha256.New, masterkey, nil, []byte(info))
|
||||
out = make([]byte, outLen)
|
||||
n, err := h.Read(out)
|
||||
if n != outLen || err != nil {
|
||||
log.Panicf("hkdfDerive: hkdf read failed, got %d bytes, error: %v", n, err)
|
||||
}
|
||||
return out
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"log"
|
||||
)
|
||||
|
||||
// RandBytes gets "n" random bytes from /dev/urandom or panics
|
||||
func RandBytes(n int) []byte {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
log.Panic("Failed to read random bytes: " + err.Error())
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// RandUint64 returns a secure random uint64
|
||||
func RandUint64() uint64 {
|
||||
b := RandBytes(8)
|
||||
return binary.BigEndian.Uint64(b)
|
||||
}
|
||||
|
||||
type nonceGenerator struct {
|
||||
nonceLen int // bytes
|
||||
}
|
||||
|
||||
// Get a random "nonceLen"-byte nonce
|
||||
func (n *nonceGenerator) Get() []byte {
|
||||
return randPrefetcher.read(n.nonceLen)
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Number of bytes to prefetch.
|
||||
// 512 looks like a good compromise between throughput and latency - see
|
||||
// randsize_test.go for numbers.
|
||||
const prefetchN = 512
|
||||
|
||||
func init() {
|
||||
randPrefetcher.refill = make(chan []byte)
|
||||
go randPrefetcher.refillWorker()
|
||||
}
|
||||
|
||||
type randPrefetcherT struct {
|
||||
sync.Mutex
|
||||
buf bytes.Buffer
|
||||
refill chan []byte
|
||||
}
|
||||
|
||||
func (r *randPrefetcherT) read(want int) (out []byte) {
|
||||
out = make([]byte, want)
|
||||
r.Lock()
|
||||
// Note: don't use defer, it slows us down!
|
||||
have, err := r.buf.Read(out)
|
||||
if have == want && err == nil {
|
||||
r.Unlock()
|
||||
return out
|
||||
}
|
||||
// Buffer was empty -> re-fill
|
||||
fresh := <-r.refill
|
||||
if len(fresh) != prefetchN {
|
||||
log.Panicf("randPrefetcher: refill: got %d bytes instead of %d", len(fresh), prefetchN)
|
||||
}
|
||||
r.buf.Reset()
|
||||
r.buf.Write(fresh)
|
||||
have, err = r.buf.Read(out)
|
||||
if have != want || err != nil {
|
||||
log.Panicf("randPrefetcher could not satisfy read: have=%d want=%d err=%v", have, want, err)
|
||||
}
|
||||
r.Unlock()
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *randPrefetcherT) refillWorker() {
|
||||
for {
|
||||
r.refill <- RandBytes(prefetchN)
|
||||
}
|
||||
}
|
||||
|
||||
var randPrefetcher randPrefetcherT
|
@ -1,11 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.11.x # Debian 10 "Buster"
|
||||
- 1.12.x # Ubuntu 19.10
|
||||
- 1.13.x # Debian 11 "Bullseye"
|
||||
- stable
|
||||
|
||||
script:
|
||||
- go build
|
||||
- ./test.bash
|
@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Jakob Unterwurzacher
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,111 +0,0 @@
|
||||
EME for Go [![Build Status](https://travis-ci.org/rfjakob/eme.svg?branch=master)](https://travis-ci.org/rfjakob/eme) [![GoDoc](https://godoc.org/github.com/rfjakob/eme?status.svg)](https://godoc.org/github.com/rfjakob/eme) ![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)
|
||||
==========
|
||||
|
||||
**EME** (ECB-Mix-ECB or, clearer, **Encrypt-Mix-Encrypt**) is a wide-block
|
||||
encryption mode developed by Halevi
|
||||
and Rogaway in 2003 [[eme]](#eme).
|
||||
|
||||
EME uses multiple invocations of a block cipher to construct a new
|
||||
cipher of bigger block size (in multiples of 16 bytes, up to 2048 bytes).
|
||||
|
||||
Quoting from the original [[eme]](#eme) paper:
|
||||
|
||||
> We describe a block-cipher mode of operation, EME, that turns an n-bit block cipher into
|
||||
> a tweakable enciphering scheme that acts on strings of mn bits, where m ∈ [1..n]. The mode is
|
||||
> parallelizable, but as serial-efficient as the non-parallelizable mode CMC [6]. EME can be used
|
||||
> to solve the disk-sector encryption problem. The algorithm entails two layers of ECB encryption
|
||||
> and a “lightweight mixing” in between. We prove EME secure, in the reduction-based sense of
|
||||
> modern cryptography.
|
||||
|
||||
Figure 2 from the [[eme]](#eme) paper shows an overview of the transformation:
|
||||
|
||||
[![Figure 2 from [eme]](paper-eme-fig2.png)](#)
|
||||
|
||||
This is an implementation of EME in Go, complete with test vectors from IEEE [[p1619-2]](#p1619-2)
|
||||
and Halevi [[eme-32-testvec]](#eme-32-testvec).
|
||||
|
||||
It has no dependencies outside the standard library.
|
||||
|
||||
Is it patentend?
|
||||
----------------
|
||||
|
||||
In 2007, the UC Davis has decided to abandon [[patabandon]](#patabandon)
|
||||
the patent application [[patappl]](#patappl) for EME.
|
||||
|
||||
Related algorithms
|
||||
------------------
|
||||
|
||||
**EME-32** is EME with the cipher set to AES and the length set to 512.
|
||||
That is, EME-32 [[eme-32-pdf]](#eme-32-pdf) is a subset of EME.
|
||||
|
||||
**EME2**, also known as EME\* [[emestar]](#emestar), is an extended version of EME
|
||||
that has built-in handling for data that is not a multiple of 16 bytes
|
||||
long.
|
||||
EME2 has been selected for standardization in IEEE P1619.2 [[p1619.2]](#p1619.2).
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
#### [eme]
|
||||
*A Parallelizable Enciphering Mode*
|
||||
Shai Halevi, Phillip Rogaway, 28 Jul 2003
|
||||
https://eprint.iacr.org/2003/147.pdf
|
||||
|
||||
Note: This is the original EME paper. EME is specified for an arbitrary
|
||||
number of block-cipher blocks. EME-32 is a concrete implementation of
|
||||
EME with a fixed length of 32 AES blocks.
|
||||
|
||||
#### [eme-32-email]
|
||||
*Re: EME-32-AES with editorial comments*
|
||||
Shai Halevi, 07 Jun 2005
|
||||
http://grouper.ieee.org/groups/1619/email/msg00310.html
|
||||
|
||||
#### [eme-32-pdf]
|
||||
*Draft Standard for Tweakable Wide-block Encryption*
|
||||
Shai Halevi, 02 June 2005
|
||||
http://grouper.ieee.org/groups/1619/email/pdf00020.pdf
|
||||
|
||||
Note: This is the latest version of the EME-32 draft that I could find. It
|
||||
includes test vectors and C source code.
|
||||
|
||||
#### [eme-32-testvec]
|
||||
*Re: Test vectors for LRW and EME*
|
||||
Shai Halevi, 16 Nov 2004
|
||||
http://grouper.ieee.org/groups/1619/email/msg00218.html
|
||||
|
||||
#### [emestar]
|
||||
*EME\*: extending EME to handle arbitrary-length messages with associated data*
|
||||
Shai Halevi, 27 May 2004
|
||||
https://eprint.iacr.org/2004/125.pdf
|
||||
|
||||
#### [patabandon]
|
||||
*Re: [P1619-2] Non-awareness patent statement made by UC Davis*
|
||||
Mat Ball, 26 Nov 2007
|
||||
http://grouper.ieee.org/groups/1619/email-2/msg00005.html
|
||||
|
||||
#### [patappl]
|
||||
*Block cipher mode of operation for constructing a wide-blocksize block cipher from a conventional block cipher*
|
||||
US patent application US20040131182
|
||||
http://www.google.com/patents/US20040131182
|
||||
|
||||
#### [p1619-2]
|
||||
*IEEE P1619.2™/D9 Draft Standard for Wide-Block Encryption for Shared Storage Media*
|
||||
IEEE, Dec 2008
|
||||
http://siswg.net/index2.php?option=com_docman&task=doc_view&gid=156&Itemid=41
|
||||
|
||||
Note: This is a draft version. The final version is not freely available
|
||||
and must be bought from IEEE.
|
||||
|
||||
Package Changelog
|
||||
-----------------
|
||||
|
||||
v1.1.1, 2020-04-13
|
||||
* Update `go vet` call in `test.bash` to work on recent Go versions
|
||||
* No code changes
|
||||
|
||||
v1.1, 2017-03-05
|
||||
* Add eme.New() / \*EMECipher convenience wrapper
|
||||
* Improve panic message and parameter wording
|
||||
|
||||
v1.0, 2015-12-08
|
||||
* Stable release
|
@ -1,3 +0,0 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
go test -bench=.
|
@ -1,206 +0,0 @@
|
||||
// EME (ECB-Mix-ECB or, clearer, Encrypt-Mix-Encrypt) is a wide-block
|
||||
// encryption mode developed by Halevi and Rogaway.
|
||||
//
|
||||
// It was presented in the 2003 paper "A Parallelizable Enciphering Mode" by
|
||||
// Halevi and Rogaway.
|
||||
//
|
||||
// EME uses multiple invocations of a block cipher to construct a new cipher
|
||||
// of bigger block size (in multiples of 16 bytes, up to 2048 bytes).
|
||||
package eme
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"log"
|
||||
)
|
||||
|
||||
type directionConst bool
|
||||
|
||||
const (
|
||||
// Encrypt "inputData"
|
||||
DirectionEncrypt = directionConst(true)
|
||||
// Decrypt "inputData"
|
||||
DirectionDecrypt = directionConst(false)
|
||||
)
|
||||
|
||||
// multByTwo - GF multiplication as specified in the EME-32 draft
|
||||
func multByTwo(out []byte, in []byte) {
|
||||
if len(in) != 16 {
|
||||
panic("len must be 16")
|
||||
}
|
||||
tmp := make([]byte, 16)
|
||||
|
||||
tmp[0] = 2 * in[0]
|
||||
if in[15] >= 128 {
|
||||
tmp[0] = tmp[0] ^ 135
|
||||
}
|
||||
for j := 1; j < 16; j++ {
|
||||
tmp[j] = 2 * in[j]
|
||||
if in[j-1] >= 128 {
|
||||
tmp[j] += 1
|
||||
}
|
||||
}
|
||||
copy(out, tmp)
|
||||
}
|
||||
|
||||
func xorBlocks(out []byte, in1 []byte, in2 []byte) {
|
||||
if len(in1) != len(in2) {
|
||||
log.Panicf("len(in1)=%d is not equal to len(in2)=%d", len(in1), len(in2))
|
||||
}
|
||||
|
||||
for i := range in1 {
|
||||
out[i] = in1[i] ^ in2[i]
|
||||
}
|
||||
}
|
||||
|
||||
// aesTransform - encrypt or decrypt (according to "direction") using block
|
||||
// cipher "bc" (typically AES)
|
||||
func aesTransform(dst []byte, src []byte, direction directionConst, bc cipher.Block) {
|
||||
if direction == DirectionEncrypt {
|
||||
bc.Encrypt(dst, src)
|
||||
return
|
||||
} else if direction == DirectionDecrypt {
|
||||
bc.Decrypt(dst, src)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// tabulateL - calculate L_i for messages up to a length of m cipher blocks
|
||||
func tabulateL(bc cipher.Block, m int) [][]byte {
|
||||
/* set L0 = 2*AESenc(K; 0) */
|
||||
eZero := make([]byte, 16)
|
||||
Li := make([]byte, 16)
|
||||
bc.Encrypt(Li, eZero)
|
||||
|
||||
LTable := make([][]byte, m)
|
||||
// Allocate pool once and slice into m pieces in the loop
|
||||
pool := make([]byte, m*16)
|
||||
for i := 0; i < m; i++ {
|
||||
multByTwo(Li, Li)
|
||||
LTable[i] = pool[i*16 : (i+1)*16]
|
||||
copy(LTable[i], Li)
|
||||
}
|
||||
return LTable
|
||||
}
|
||||
|
||||
// Transform - EME-encrypt or EME-decrypt, according to "direction"
|
||||
// (defined in the constants DirectionEncrypt and DirectionDecrypt).
|
||||
// The data in "inputData" is en- or decrypted with the block ciper "bc" under
|
||||
// "tweak" (also known as IV).
|
||||
//
|
||||
// The tweak is used to randomize the encryption in the same way as an
|
||||
// IV. A use of this encryption mode envisioned by the authors of the
|
||||
// algorithm was to encrypt each sector of a disk, with the tweak
|
||||
// being the sector number. If you encipher the same data with the
|
||||
// same tweak you will get the same ciphertext.
|
||||
//
|
||||
// The result is returned in a freshly allocated slice of the same
|
||||
// size as inputData.
|
||||
//
|
||||
// Limitations:
|
||||
// * The block cipher must have block size 16 (usually AES).
|
||||
// * The size of "tweak" must be 16
|
||||
// * "inputData" must be a multiple of 16 bytes long
|
||||
// If any of these pre-conditions are not met, the function will panic.
|
||||
//
|
||||
// Note that you probably don't want to call this function directly and instead
|
||||
// use eme.New(), which provides conventient wrappers.
|
||||
func Transform(bc cipher.Block, tweak []byte, inputData []byte, direction directionConst) []byte {
|
||||
// In the paper, the tweak is just called "T". Call it the same here to
|
||||
// make following the paper easy.
|
||||
T := tweak
|
||||
// In the paper, the plaintext data is called "P" and the ciphertext is
|
||||
// called "C". Because encryption and decryption are virtually identical,
|
||||
// we share the code and always call the input data "P" and the output data
|
||||
// "C", regardless of the direction.
|
||||
P := inputData
|
||||
|
||||
if bc.BlockSize() != 16 {
|
||||
log.Panicf("Using a block size other than 16 is not implemented")
|
||||
}
|
||||
if len(T) != 16 {
|
||||
log.Panicf("Tweak must be 16 bytes long, is %d", len(T))
|
||||
}
|
||||
if len(P)%16 != 0 {
|
||||
log.Panicf("Data P must be a multiple of 16 long, is %d", len(P))
|
||||
}
|
||||
m := len(P) / 16
|
||||
if m == 0 || m > 16*8 {
|
||||
log.Panicf("EME operates on 1 to %d block-cipher blocks, you passed %d", 16*8, m)
|
||||
}
|
||||
|
||||
C := make([]byte, len(P))
|
||||
|
||||
LTable := tabulateL(bc, m)
|
||||
|
||||
PPj := make([]byte, 16)
|
||||
for j := 0; j < m; j++ {
|
||||
Pj := P[j*16 : (j+1)*16]
|
||||
/* PPj = 2**(j-1)*L xor Pj */
|
||||
xorBlocks(PPj, Pj, LTable[j])
|
||||
/* PPPj = AESenc(K; PPj) */
|
||||
aesTransform(C[j*16:(j+1)*16], PPj, direction, bc)
|
||||
}
|
||||
|
||||
/* MP =(xorSum PPPj) xor T */
|
||||
MP := make([]byte, 16)
|
||||
xorBlocks(MP, C[0:16], T)
|
||||
for j := 1; j < m; j++ {
|
||||
xorBlocks(MP, MP, C[j*16:(j+1)*16])
|
||||
}
|
||||
|
||||
/* MC = AESenc(K; MP) */
|
||||
MC := make([]byte, 16)
|
||||
aesTransform(MC, MP, direction, bc)
|
||||
|
||||
/* M = MP xor MC */
|
||||
M := make([]byte, 16)
|
||||
xorBlocks(M, MP, MC)
|
||||
CCCj := make([]byte, 16)
|
||||
for j := 1; j < m; j++ {
|
||||
multByTwo(M, M)
|
||||
/* CCCj = 2**(j-1)*M xor PPPj */
|
||||
xorBlocks(CCCj, C[j*16:(j+1)*16], M)
|
||||
copy(C[j*16:(j+1)*16], CCCj)
|
||||
}
|
||||
|
||||
/* CCC1 = (xorSum CCCj) xor T xor MC */
|
||||
CCC1 := make([]byte, 16)
|
||||
xorBlocks(CCC1, MC, T)
|
||||
for j := 1; j < m; j++ {
|
||||
xorBlocks(CCC1, CCC1, C[j*16:(j+1)*16])
|
||||
}
|
||||
copy(C[0:16], CCC1)
|
||||
|
||||
for j := 0; j < m; j++ {
|
||||
/* CCj = AES-enc(K; CCCj) */
|
||||
aesTransform(C[j*16:(j+1)*16], C[j*16:(j+1)*16], direction, bc)
|
||||
/* Cj = 2**(j-1)*L xor CCj */
|
||||
xorBlocks(C[j*16:(j+1)*16], C[j*16:(j+1)*16], LTable[j])
|
||||
}
|
||||
|
||||
return C
|
||||
}
|
||||
|
||||
// EMECipher provides EME-Encryption and -Decryption functions that are more
|
||||
// convenient than calling Transform directly.
|
||||
type EMECipher struct {
|
||||
bc cipher.Block
|
||||
}
|
||||
|
||||
// New returns a new EMECipher object. "bc" must have a block size of 16,
|
||||
// or subsequent calls to Encrypt and Decrypt will panic.
|
||||
func New(bc cipher.Block) *EMECipher {
|
||||
return &EMECipher{
|
||||
bc: bc,
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt is equivalent to calling Transform with direction=DirectionEncrypt.
|
||||
func (e *EMECipher) Encrypt(tweak []byte, inputData []byte) []byte {
|
||||
return Transform(e.bc, tweak, inputData, DirectionEncrypt)
|
||||
}
|
||||
|
||||
// Decrypt is equivalent to calling Transform with direction=DirectionDecrypt.
|
||||
func (e *EMECipher) Decrypt(tweak []byte, inputData []byte) []byte {
|
||||
return Transform(e.bc, tweak, inputData, DirectionDecrypt)
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB |
@ -1,97 +0,0 @@
|
||||
// Package exitcodes contains all well-defined exit codes that gocryptfs
|
||||
// can return.
|
||||
package exitcodes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
// Usage - usage error like wrong cli syntax, wrong number of parameters.
|
||||
Usage = 1
|
||||
// 2 is reserved because it is used by Go panic
|
||||
// 3 is reserved because it was used by earlier gocryptfs version as a generic
|
||||
// "mount" error.
|
||||
|
||||
// CipherDir means that the CIPHERDIR does not exist, is not empty, or is not
|
||||
// a directory.
|
||||
CipherDir = 6
|
||||
// Init is an error on filesystem init
|
||||
Init = 7
|
||||
// LoadConf is an error while loading gocryptfs.conf
|
||||
LoadConf = 8
|
||||
// ReadPassword means something went wrong reading the password
|
||||
ReadPassword = 9
|
||||
// MountPoint error means that the mountpoint is invalid (not empty etc).
|
||||
MountPoint = 10
|
||||
// Other error - please inspect the message
|
||||
Other = 11
|
||||
// PasswordIncorrect - the password was incorrect when mounting or when
|
||||
// changing the password.
|
||||
PasswordIncorrect = 12
|
||||
// ScryptParams means that scrypt was called with invalid parameters
|
||||
ScryptParams = 13
|
||||
// MasterKey means that something went wrong when parsing the "-masterkey"
|
||||
// command line option
|
||||
MasterKey = 14
|
||||
// SigInt means we got SIGINT
|
||||
SigInt = 15
|
||||
// PanicLogNotEmpty means the panic log was not empty when we were unmounted
|
||||
PanicLogNotEmpty = 16
|
||||
// ForkChild means forking the worker child failed
|
||||
ForkChild = 17
|
||||
// OpenSSL means you tried to enable OpenSSL, but we were compiled without it.
|
||||
OpenSSL = 18
|
||||
// FuseNewServer - this exit code means that the call to fuse.NewServer failed.
|
||||
// This usually means that there was a problem executing fusermount, or
|
||||
// fusermount could not attach the mountpoint to the kernel.
|
||||
FuseNewServer = 19
|
||||
// CtlSock - the control socket file could not be created.
|
||||
CtlSock = 20
|
||||
// Downgraded to a warning in gocryptfs v1.4
|
||||
//PanicLogCreate = 21
|
||||
|
||||
// PasswordEmpty - we received an empty password
|
||||
PasswordEmpty = 22
|
||||
// OpenConf - the was an error opening the gocryptfs.conf file for reading
|
||||
OpenConf = 23
|
||||
// WriteConf - could not write the gocryptfs.conf
|
||||
WriteConf = 24
|
||||
// Profiler - error occurred when trying to write cpu or memory profile or
|
||||
// execution trace
|
||||
Profiler = 25
|
||||
// FsckErrors - the filesystem check found errors
|
||||
FsckErrors = 26
|
||||
// DeprecatedFS - this filesystem is deprecated
|
||||
DeprecatedFS = 27
|
||||
// skip 28
|
||||
// ExcludeError - an error occurred while processing "-exclude"
|
||||
ExcludeError = 29
|
||||
// DevNull means that /dev/null could not be opened
|
||||
DevNull = 30
|
||||
)
|
||||
|
||||
// Err wraps an error with an associated numeric exit code
|
||||
type Err struct {
|
||||
error
|
||||
code int
|
||||
}
|
||||
|
||||
// NewErr returns an error containing "msg" and the exit code "code".
|
||||
func NewErr(msg string, code int) Err {
|
||||
return Err{
|
||||
error: fmt.Errorf(msg),
|
||||
code: code,
|
||||
}
|
||||
}
|
||||
|
||||
// Exit extracts the numeric exit code from "err" (if available) and exits the
|
||||
// application.
|
||||
func Exit(err error) {
|
||||
err2, ok := err.(Err)
|
||||
if !ok {
|
||||
os.Exit(Other)
|
||||
}
|
||||
os.Exit(err2.code)
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
@ -1,4 +0,0 @@
|
||||
# Cf. http://docs.travis-ci.com/user/getting-started/
|
||||
# Cf. http://docs.travis-ci.com/user/languages/go/
|
||||
|
||||
language: go
|
@ -1,202 +0,0 @@
|
||||
|
||||
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.
|
@ -1,10 +0,0 @@
|
||||
This repository contains Go packages related to cryptographic standards that are
|
||||
not included in the Go standard library. These include:
|
||||
|
||||
* [SIV mode][siv], which provides deterministic encryption with
|
||||
authentication.
|
||||
|
||||
* [CMAC][cmac], a message authentication system used by SIV mode.
|
||||
|
||||
[siv]: https://godoc.org/github.com/jacobsa/crypto/siv
|
||||
[cmac]: https://godoc.org/github.com/jacobsa/crypto/cmac
|
@ -1,23 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 cmac
|
||||
|
||||
import "crypto/aes"
|
||||
|
||||
// The size of an AES-CMAC checksum, in bytes.
|
||||
const Size = aes.BlockSize
|
||||
|
||||
const blockSize = Size
|
@ -1,19 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 cmac implements the CMAC mode for message authentication, as defined
|
||||
// by NIST Special Publication 800-38B. When a 16-byte key is used, this
|
||||
// matches the AES-CMAC algorithm defined by RFC 4493.
|
||||
package cmac
|
@ -1,170 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 cmac
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
"hash"
|
||||
"unsafe"
|
||||
|
||||
"../common"
|
||||
)
|
||||
|
||||
type cmacHash struct {
|
||||
// An AES cipher configured with the original key.
|
||||
ciph cipher.Block
|
||||
|
||||
// Generated sub-keys.
|
||||
k1 []byte
|
||||
k2 []byte
|
||||
|
||||
// Data that has been seen by Write but not yet incorporated into x, due to
|
||||
// us not being sure if it is the final block or not.
|
||||
//
|
||||
// INVARIANT: len(data) <= blockSize
|
||||
data []byte
|
||||
|
||||
// The current value of X, as defined in the AES-CMAC algorithm in RFC 4493.
|
||||
// Initially this is a 128-bit zero, and it is updated with the current block
|
||||
// when we're sure it's not the last one.
|
||||
x []byte
|
||||
}
|
||||
|
||||
func (h *cmacHash) Write(p []byte) (n int, err error) {
|
||||
n = len(p)
|
||||
|
||||
// First step: consume enough data to expand h.data to a full block, if
|
||||
// possible.
|
||||
{
|
||||
toConsume := blockSize - len(h.data)
|
||||
if toConsume > len(p) {
|
||||
toConsume = len(p)
|
||||
}
|
||||
|
||||
h.data = append(h.data, p[:toConsume]...)
|
||||
p = p[toConsume:]
|
||||
}
|
||||
|
||||
// If there's no data left in p, it means h.data might not be a full block.
|
||||
// Even if it is, we're not sure it's the final block, which we must treat
|
||||
// specially. So we must stop here.
|
||||
if len(p) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// h.data is a full block and is not the last; process it.
|
||||
h.writeBlocks(h.data)
|
||||
h.data = h.data[:0]
|
||||
|
||||
// Consume any further full blocks in p that we're sure aren't the last. Note
|
||||
// that we're sure that len(p) is greater than zero here.
|
||||
blocksToProcess := (len(p) - 1) / blockSize
|
||||
bytesToProcess := blocksToProcess * blockSize
|
||||
|
||||
h.writeBlocks(p[:bytesToProcess])
|
||||
p = p[bytesToProcess:]
|
||||
|
||||
// Store the rest for later.
|
||||
h.data = append(h.data, p...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Process block-aligned data that we're sure does not contain the final block.
|
||||
//
|
||||
// REQUIRES: len(p) % blockSize == 0
|
||||
func (h *cmacHash) writeBlocks(p []byte) {
|
||||
y := make([]byte, blockSize)
|
||||
|
||||
for off := 0; off < len(p); off += blockSize {
|
||||
block := p[off : off+blockSize]
|
||||
|
||||
xorBlock(
|
||||
unsafe.Pointer(&y[0]),
|
||||
unsafe.Pointer(&h.x[0]),
|
||||
unsafe.Pointer(&block[0]))
|
||||
|
||||
h.ciph.Encrypt(h.x, y)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (h *cmacHash) Sum(b []byte) []byte {
|
||||
dataLen := len(h.data)
|
||||
|
||||
// We should have at most one block left.
|
||||
if dataLen > blockSize {
|
||||
panic(fmt.Sprintf("Unexpected data: %x", h.data))
|
||||
}
|
||||
|
||||
// Calculate M_last.
|
||||
mLast := make([]byte, blockSize)
|
||||
if dataLen == blockSize {
|
||||
common.Xor(mLast, h.data, h.k1)
|
||||
} else {
|
||||
// TODO(jacobsa): Accept a destination buffer in common.PadBlock and
|
||||
// simplify this code.
|
||||
common.Xor(mLast, common.PadBlock(h.data), h.k2)
|
||||
}
|
||||
|
||||
y := make([]byte, blockSize)
|
||||
common.Xor(y, mLast, h.x)
|
||||
|
||||
result := make([]byte, blockSize)
|
||||
h.ciph.Encrypt(result, y)
|
||||
|
||||
b = append(b, result...)
|
||||
return b
|
||||
}
|
||||
|
||||
func (h *cmacHash) Reset() {
|
||||
h.data = h.data[:0]
|
||||
h.x = make([]byte, blockSize)
|
||||
}
|
||||
|
||||
func (h *cmacHash) Size() int {
|
||||
return h.ciph.BlockSize()
|
||||
}
|
||||
|
||||
func (h *cmacHash) BlockSize() int {
|
||||
return h.ciph.BlockSize()
|
||||
}
|
||||
|
||||
// New returns an AES-CMAC hash using the supplied key. The key must be 16, 24,
|
||||
// or 32 bytes long.
|
||||
func New(key []byte) (hash.Hash, error) {
|
||||
switch len(key) {
|
||||
case 16, 24, 32:
|
||||
default:
|
||||
return nil, fmt.Errorf("AES-CMAC requires a 16-, 24-, or 32-byte key.")
|
||||
}
|
||||
|
||||
// Create a cipher.
|
||||
ciph, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes.NewCipher: %v", err)
|
||||
}
|
||||
|
||||
// Set up the hash object.
|
||||
h := &cmacHash{ciph: ciph}
|
||||
h.k1, h.k2 = generateSubkeys(ciph)
|
||||
h.Reset()
|
||||
|
||||
return h, nil
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build 386 arm,!arm64 mips mipsle
|
||||
|
||||
package cmac
|
||||
|
||||
import (
|
||||
"log"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// XOR the blockSize bytes starting at a and b, writing the result over dst.
|
||||
func xorBlock(
|
||||
dstPtr unsafe.Pointer,
|
||||
aPtr unsafe.Pointer,
|
||||
bPtr unsafe.Pointer) {
|
||||
// Check assumptions. (These are compile-time constants, so this should
|
||||
// compile out.)
|
||||
const wordSize = unsafe.Sizeof(uintptr(0))
|
||||
if blockSize != 4*wordSize {
|
||||
log.Panicf("%d %d", blockSize, wordSize)
|
||||
}
|
||||
|
||||
// Convert.
|
||||
a := (*[4]uintptr)(aPtr)
|
||||
b := (*[4]uintptr)(bPtr)
|
||||
dst := (*[4]uintptr)(dstPtr)
|
||||
|
||||
// Compute.
|
||||
dst[0] = a[0] ^ b[0]
|
||||
dst[1] = a[1] ^ b[1]
|
||||
dst[2] = a[2] ^ b[2]
|
||||
dst[3] = a[3] ^ b[3]
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build amd64 arm64 ppc64 ppc64le s390x mips64 mips64le
|
||||
|
||||
// This code assumes that it's safe to perform unaligned word-sized loads. This is safe on:
|
||||
// - arm64 per http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01s02.html
|
||||
// - Section "5.5.8 Alignment Interrupt" of PowerPC Operating Environment Architecture Book III Version 2.02
|
||||
// (the first PowerPC ISA version to include 64-bit), available from
|
||||
// http://www.ibm.com/developerworks/systems/library/es-archguide-v2.html does not permit fixed-point loads
|
||||
// or stores to generate exceptions on unaligned access
|
||||
// - IBM mainframe's have allowed unaligned accesses since the System/370 arrived in 1970
|
||||
// - On mips unaligned accesses are fixed up by the kernel per https://www.linux-mips.org/wiki/Alignment
|
||||
// so performance might be quite bad but it will work.
|
||||
|
||||
package cmac
|
||||
|
||||
import (
|
||||
"log"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// XOR the blockSize bytes starting at a and b, writing the result over dst.
|
||||
func xorBlock(
|
||||
dstPtr unsafe.Pointer,
|
||||
aPtr unsafe.Pointer,
|
||||
bPtr unsafe.Pointer) {
|
||||
// Check assumptions. (These are compile-time constants, so this should
|
||||
// compile out.)
|
||||
const wordSize = unsafe.Sizeof(uintptr(0))
|
||||
if blockSize != 2*wordSize {
|
||||
log.Panicf("%d %d", blockSize, wordSize)
|
||||
}
|
||||
|
||||
// Convert.
|
||||
a := (*[2]uintptr)(aPtr)
|
||||
b := (*[2]uintptr)(bPtr)
|
||||
dst := (*[2]uintptr)(dstPtr)
|
||||
|
||||
// Compute.
|
||||
dst[0] = a[0] ^ b[0]
|
||||
dst[1] = a[1] ^ b[1]
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 cmac
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
|
||||
"../common"
|
||||
)
|
||||
|
||||
var subkeyZero []byte
|
||||
var subkeyRb []byte
|
||||
|
||||
func init() {
|
||||
subkeyZero = bytes.Repeat([]byte{0x00}, blockSize)
|
||||
subkeyRb = append(bytes.Repeat([]byte{0x00}, blockSize-1), 0x87)
|
||||
}
|
||||
|
||||
// Given the supplied cipher, whose block size must be 16 bytes, return two
|
||||
// subkeys that can be used in MAC generation. See section 5.3 of NIST SP
|
||||
// 800-38B. Note that the other NIST-approved block size of 8 bytes is not
|
||||
// supported by this function.
|
||||
func generateSubkeys(ciph cipher.Block) (k1 []byte, k2 []byte) {
|
||||
if ciph.BlockSize() != blockSize {
|
||||
panic("generateSubkeys requires a cipher with a block size of 16 bytes.")
|
||||
}
|
||||
|
||||
// Step 1
|
||||
l := make([]byte, blockSize)
|
||||
ciph.Encrypt(l, subkeyZero)
|
||||
|
||||
// Step 2: Derive the first subkey.
|
||||
if common.Msb(l) == 0 {
|
||||
// TODO(jacobsa): Accept a destination buffer in ShiftLeft and then hoist
|
||||
// the allocation in the else branch below.
|
||||
k1 = common.ShiftLeft(l)
|
||||
} else {
|
||||
k1 = make([]byte, blockSize)
|
||||
common.Xor(k1, common.ShiftLeft(l), subkeyRb)
|
||||
}
|
||||
|
||||
// Step 3: Derive the second subkey.
|
||||
if common.Msb(k1) == 0 {
|
||||
k2 = common.ShiftLeft(k1)
|
||||
} else {
|
||||
k2 = make([]byte, blockSize)
|
||||
common.Xor(k2, common.ShiftLeft(k1), subkeyRb)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 common contains common implementation details of other packages, and
|
||||
// should not be used directly.
|
||||
package common
|
@ -1,26 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 common
|
||||
|
||||
// Msb returns the most significant bit of the supplied data (which must be
|
||||
// non-empty). This is the MSB(L) function of RFC 4493.
|
||||
func Msb(buf []byte) uint8 {
|
||||
if len(buf) == 0 {
|
||||
panic("msb requires non-empty buffer.")
|
||||
}
|
||||
|
||||
return buf[0] >> 7
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 common
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
)
|
||||
|
||||
// PadBlock pads a string of bytes less than 16 bytes long to a full block size
|
||||
// by appending a one bit followed by zero bits. This is the padding function
|
||||
// used in RFCs 4493 and 5297.
|
||||
func PadBlock(block []byte) []byte {
|
||||
blockLen := len(block)
|
||||
if blockLen >= aes.BlockSize {
|
||||
panic("PadBlock input must be less than 16 bytes.")
|
||||
}
|
||||
|
||||
result := make([]byte, aes.BlockSize)
|
||||
copy(result, block)
|
||||
result[blockLen] = 0x80
|
||||
|
||||
return result
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 common
|
||||
|
||||
// ShiftLeft shifts the binary string left by one bit, causing the
|
||||
// most-signficant bit to disappear and a zero to be introduced at the right.
|
||||
// This corresponds to the `x << 1` notation of RFC 4493.
|
||||
func ShiftLeft(b []byte) []byte {
|
||||
l := len(b)
|
||||
if l == 0 {
|
||||
panic("shiftLeft requires a non-empty buffer.")
|
||||
}
|
||||
|
||||
output := make([]byte, l)
|
||||
|
||||
overflow := byte(0)
|
||||
for i := int(l - 1); i >= 0; i-- {
|
||||
output[i] = b[i] << 1
|
||||
output[i] |= overflow
|
||||
overflow = (b[i] & 0x80) >> 7
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 common
|
||||
|
||||
import "log"
|
||||
|
||||
// Xor computes `a XOR b`, as defined by RFC 4493. dst, a, and b must all have
|
||||
// the same length.
|
||||
func Xor(dst []byte, a []byte, b []byte) {
|
||||
// TODO(jacobsa): Consider making this a helper function with known sizes
|
||||
// where it is most hot, then even trying to inline it entirely.
|
||||
|
||||
if len(dst) != len(a) || len(a) != len(b) {
|
||||
log.Panicf("Bad buffer lengths: %d, %d, %d", len(dst), len(a), len(b))
|
||||
}
|
||||
|
||||
for i, _ := range a {
|
||||
dst[i] = a[i] ^ b[i]
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 siv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
|
||||
"../common"
|
||||
)
|
||||
|
||||
var dblRb []byte
|
||||
|
||||
func init() {
|
||||
dblRb = append(bytes.Repeat([]byte{0x00}, 15), 0x87)
|
||||
}
|
||||
|
||||
// Given a 128-bit binary string, shift the string left by one bit and XOR the
|
||||
// result with 0x00...87 if the bit shifted off was one. This is the dbl
|
||||
// function of RFC 5297.
|
||||
func dbl(b []byte) []byte {
|
||||
if len(b) != aes.BlockSize {
|
||||
panic("dbl requires a 16-byte buffer.")
|
||||
}
|
||||
|
||||
shiftedOne := common.Msb(b) == 1
|
||||
b = common.ShiftLeft(b)
|
||||
if shiftedOne {
|
||||
tmp := make([]byte, aes.BlockSize)
|
||||
common.Xor(tmp, b, dblRb)
|
||||
b = tmp
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 siv
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// *NotAuthenticError is returned by Decrypt if the input is otherwise
|
||||
// well-formed but the ciphertext doesn't check out as authentic. This could be
|
||||
// due to an incorrect key, corrupted ciphertext, or incorrect/corrupted
|
||||
// associated data.
|
||||
type NotAuthenticError struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e *NotAuthenticError) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
// Given ciphertext previously generated by Encrypt and the key and associated
|
||||
// data that were used when generating the ciphertext, return the original
|
||||
// plaintext given to Encrypt. If the input is well-formed but the key is
|
||||
// incorrect, return an instance of WrongKeyError.
|
||||
func Decrypt(key, ciphertext []byte, associated [][]byte) ([]byte, error) {
|
||||
keyLen := len(key)
|
||||
associatedLen := len(associated)
|
||||
|
||||
// The first 16 bytes of the ciphertext are the SIV.
|
||||
if len(ciphertext) < aes.BlockSize {
|
||||
return nil, fmt.Errorf("Invalid ciphertext; length must be at least 16.")
|
||||
}
|
||||
|
||||
v := ciphertext[0:aes.BlockSize]
|
||||
c := ciphertext[aes.BlockSize:]
|
||||
|
||||
// Make sure the key length is legal.
|
||||
switch keyLen {
|
||||
case 32, 48, 64:
|
||||
default:
|
||||
return nil, fmt.Errorf("SIV requires a 32-, 48-, or 64-byte key.")
|
||||
}
|
||||
|
||||
// Derive subkeys.
|
||||
k1 := key[:keyLen/2]
|
||||
k2 := key[keyLen/2:]
|
||||
|
||||
// Make sure the number of associated data is legal, per RFC 5297 section 7.
|
||||
if associatedLen > 126 {
|
||||
return nil, fmt.Errorf("len(associated) may be no more than 126.")
|
||||
}
|
||||
|
||||
// Create a CTR cipher using a version of v with the 31st and 63rd bits
|
||||
// zeroed out.
|
||||
q := dup(v)
|
||||
q[aes.BlockSize-4] &= 0x7f
|
||||
q[aes.BlockSize-8] &= 0x7f
|
||||
|
||||
ciph, err := aes.NewCipher(k2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes.NewCipher: %v", err)
|
||||
}
|
||||
|
||||
ctrCiph := cipher.NewCTR(ciph, q)
|
||||
|
||||
// Decrypt the ciphertext.
|
||||
plaintext := make([]byte, len(c))
|
||||
ctrCiph.XORKeyStream(plaintext, c)
|
||||
|
||||
// Verify the SIV.
|
||||
s2vStrings := make([][]byte, associatedLen+1)
|
||||
copy(s2vStrings, associated)
|
||||
s2vStrings[associatedLen] = plaintext
|
||||
|
||||
t := s2v(k1, s2vStrings, nil)
|
||||
if len(t) != aes.BlockSize {
|
||||
panic(fmt.Sprintf("Unexpected output of S2V: %v", t))
|
||||
}
|
||||
|
||||
if subtle.ConstantTimeCompare(t, v) != 1 {
|
||||
return nil, &NotAuthenticError{
|
||||
"Couldn't validate the authenticity of the ciphertext and " +
|
||||
"associated data."}
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 siv implements the SIV (Synthetic Initialization Vector) mode of
|
||||
// AES, as defined by RFC 5297.
|
||||
//
|
||||
// This mode offers the choice of deterministic authenticated encryption or
|
||||
// nonce-based, misuse-resistant authenticated encryption.
|
||||
package siv
|
@ -1,124 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 siv
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func dup(d []byte) []byte {
|
||||
result := make([]byte, len(d))
|
||||
copy(result, d)
|
||||
return result
|
||||
}
|
||||
|
||||
// Given a key and plaintext, encrypt the plaintext using the SIV mode of AES,
|
||||
// as defined by RFC 5297, append the result (including both the synthetic
|
||||
// initialization vector and the ciphertext) to dst, and return the updated
|
||||
// slice. The output can later be fed to Decrypt to recover the plaintext.
|
||||
//
|
||||
// In addition to confidentiality, this function also offers authenticity. That
|
||||
// is, without the secret key an attacker is unable to construct a byte string
|
||||
// that Decrypt will accept.
|
||||
//
|
||||
// The supplied key must be 32, 48, or 64 bytes long.
|
||||
//
|
||||
// The supplied associated data, up to 126 strings, is also authenticated,
|
||||
// though it is not included in the ciphertext. The user must supply the same
|
||||
// associated data to Decrypt in order for the Decrypt call to succeed. If no
|
||||
// associated data is desired, pass an empty slice.
|
||||
//
|
||||
// If the same key, plaintext, and associated data are supplied to this
|
||||
// function multiple times, the output is guaranteed to be identical. As per
|
||||
// RFC 5297 section 3, you may use this function for nonce-based authenticated
|
||||
// encryption by passing a nonce as the last associated data element.
|
||||
func Encrypt(dst, key, plaintext []byte, associated [][]byte) ([]byte, error) {
|
||||
keyLen := len(key)
|
||||
associatedLen := len(associated)
|
||||
|
||||
// The output will consist of the current contents of dst, followed by the IV
|
||||
// generated by s2v, followed by the ciphertext (which is the same size as
|
||||
// the plaintext).
|
||||
//
|
||||
// Make sure dst is long enough, then carve it up.
|
||||
var iv []byte
|
||||
var ciphertext []byte
|
||||
{
|
||||
dstSize := len(dst)
|
||||
dstAndIVSize := dstSize + s2vSize
|
||||
outputSize := dstAndIVSize + len(plaintext)
|
||||
|
||||
if cap(dst) < outputSize {
|
||||
tmp := make([]byte, dstSize, outputSize+outputSize/4)
|
||||
copy(tmp, dst)
|
||||
dst = tmp
|
||||
}
|
||||
|
||||
dst = dst[:outputSize]
|
||||
iv = dst[dstSize:dstAndIVSize]
|
||||
ciphertext = dst[dstAndIVSize:outputSize]
|
||||
}
|
||||
|
||||
// Make sure the key length is legal.
|
||||
switch keyLen {
|
||||
case 32, 48, 64:
|
||||
default:
|
||||
return nil, fmt.Errorf("SIV requires a 32-, 48-, or 64-byte key.")
|
||||
}
|
||||
|
||||
// Make sure the number of associated data is legal, per RFC 5297 section 7.
|
||||
if associatedLen > 126 {
|
||||
return nil, fmt.Errorf("len(associated) may be no more than 126.")
|
||||
}
|
||||
|
||||
// Derive subkeys.
|
||||
k1 := key[:keyLen/2]
|
||||
k2 := key[keyLen/2:]
|
||||
|
||||
// Call S2V to derive the synthetic initialization vector. Use the ciphertext
|
||||
// output buffer as scratch space, since it's the same length as the final
|
||||
// string.
|
||||
s2vStrings := make([][]byte, associatedLen+1)
|
||||
copy(s2vStrings, associated)
|
||||
s2vStrings[associatedLen] = plaintext
|
||||
|
||||
v := s2v(k1, s2vStrings, ciphertext)
|
||||
if len(v) != len(iv) {
|
||||
panic(fmt.Sprintf("Unexpected vector: %v", v))
|
||||
}
|
||||
|
||||
copy(iv, v)
|
||||
|
||||
// Create a CTR cipher using a version of v with the 31st and 63rd bits
|
||||
// zeroed out.
|
||||
q := dup(v)
|
||||
q[aes.BlockSize-4] &= 0x7f
|
||||
q[aes.BlockSize-8] &= 0x7f
|
||||
|
||||
ciph, err := aes.NewCipher(k2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes.NewCipher: %v", err)
|
||||
}
|
||||
|
||||
ctrCiph := cipher.NewCTR(ciph, q)
|
||||
|
||||
// Fill in the ciphertext.
|
||||
ctrCiph.XORKeyStream(ciphertext, plaintext)
|
||||
|
||||
return dst, nil
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 siv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"fmt"
|
||||
|
||||
"../cmac"
|
||||
"../common"
|
||||
)
|
||||
|
||||
var s2vZero []byte
|
||||
|
||||
func init() {
|
||||
s2vZero = bytes.Repeat([]byte{0x00}, aes.BlockSize)
|
||||
}
|
||||
|
||||
// The output size of the s2v function.
|
||||
const s2vSize = cmac.Size
|
||||
|
||||
// Run the S2V "string to vector" function of RFC 5297 using the input key and
|
||||
// string vector, which must be non-empty. (RFC 5297 defines S2V to handle the
|
||||
// empty vector case, but it is never used that way by higher-level functions.)
|
||||
//
|
||||
// If provided, the supplied scatch space will be used to avoid an allocation.
|
||||
// It should be (but is not required to be) as large as the last element of
|
||||
// strings.
|
||||
//
|
||||
// The result is guaranteed to be of length s2vSize.
|
||||
func s2v(key []byte, strings [][]byte, scratch []byte) []byte {
|
||||
numStrings := len(strings)
|
||||
if numStrings == 0 {
|
||||
panic("strings vector must be non-empty.")
|
||||
}
|
||||
|
||||
// Create a CMAC hash.
|
||||
h, err := cmac.New(key)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cmac.New: %v", err))
|
||||
}
|
||||
|
||||
// Initialize.
|
||||
if _, err := h.Write(s2vZero); err != nil {
|
||||
panic(fmt.Sprintf("h.Write: %v", err))
|
||||
}
|
||||
|
||||
d := h.Sum([]byte{})
|
||||
h.Reset()
|
||||
|
||||
// Handle all strings but the last.
|
||||
for i := 0; i < numStrings-1; i++ {
|
||||
if _, err := h.Write(strings[i]); err != nil {
|
||||
panic(fmt.Sprintf("h.Write: %v", err))
|
||||
}
|
||||
|
||||
common.Xor(d, dbl(d), h.Sum([]byte{}))
|
||||
h.Reset()
|
||||
}
|
||||
|
||||
// Handle the last string.
|
||||
lastString := strings[numStrings-1]
|
||||
var t []byte
|
||||
if len(lastString) >= aes.BlockSize {
|
||||
// Make an output buffer the length of lastString.
|
||||
if cap(scratch) >= len(lastString) {
|
||||
t = scratch[:len(lastString)]
|
||||
} else {
|
||||
t = make([]byte, len(lastString))
|
||||
}
|
||||
|
||||
// XOR d on the end of lastString.
|
||||
xorend(t, lastString, d)
|
||||
} else {
|
||||
t = make([]byte, aes.BlockSize)
|
||||
common.Xor(t, dbl(d), common.PadBlock(lastString))
|
||||
}
|
||||
|
||||
if _, err := h.Write(t); err != nil {
|
||||
panic(fmt.Sprintf("h.Write: %v", err))
|
||||
}
|
||||
|
||||
return h.Sum([]byte{})
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// 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 siv
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"../common"
|
||||
)
|
||||
|
||||
// The xorend operator of RFC 5297.
|
||||
//
|
||||
// Given strings A and B with len(A) >= len(B), let D be len(A) - len(B). Write
|
||||
// A[:D] followed by xor(A[D:], B) into dst. In other words, xor B over the
|
||||
// rightmost end of A and write the result into dst.
|
||||
func xorend(dst, a, b []byte) {
|
||||
aLen := len(a)
|
||||
bLen := len(b)
|
||||
dstLen := len(dst)
|
||||
|
||||
if dstLen < aLen || aLen < bLen {
|
||||
log.Panicf("Bad buffer lengths: %d, %d, %d", dstLen, aLen, bLen)
|
||||
}
|
||||
|
||||
// Copy the left part.
|
||||
difference := aLen - bLen
|
||||
copy(dst, a[:difference])
|
||||
|
||||
// XOR in the right part.
|
||||
common.Xor(dst[difference:difference+bLen], a[difference:], b)
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
package nametransform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"../cryptocore"
|
||||
"../../rewrites/syscallcompat"
|
||||
)
|
||||
|
||||
const (
|
||||
// DirIVLen is identical to AES block size
|
||||
DirIVLen = 16
|
||||
// DirIVFilename is the filename used to store directory IV.
|
||||
// Exported because we have to ignore this name in directory listing.
|
||||
DirIVFilename = "gocryptfs.diriv"
|
||||
)
|
||||
|
||||
// ReadDirIVAt reads "gocryptfs.diriv" from the directory that is opened as "dirfd".
|
||||
// Using the dirfd makes it immune to concurrent renames of the directory.
|
||||
func ReadDirIVAt(dirfd int) (iv []byte, err error) {
|
||||
fdRaw, err := syscallcompat.Openat(dirfd, DirIVFilename,
|
||||
syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fd := os.NewFile(uintptr(fdRaw), DirIVFilename)
|
||||
defer fd.Close()
|
||||
return fdReadDirIV(fd)
|
||||
}
|
||||
|
||||
// allZeroDirIV is preallocated to quickly check if the data read from disk is all zero
|
||||
var allZeroDirIV = make([]byte, DirIVLen)
|
||||
|
||||
// fdReadDirIV reads and verifies the DirIV from an opened gocryptfs.diriv file.
|
||||
func fdReadDirIV(fd *os.File) (iv []byte, err error) {
|
||||
// We want to detect if the file is bigger than DirIVLen, so
|
||||
// make the buffer 1 byte bigger than necessary.
|
||||
iv = make([]byte, DirIVLen+1)
|
||||
n, err := fd.Read(iv)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, fmt.Errorf("read failed: %v", err)
|
||||
}
|
||||
iv = iv[0:n]
|
||||
if len(iv) != DirIVLen {
|
||||
return nil, fmt.Errorf("wanted %d bytes, got %d", DirIVLen, len(iv))
|
||||
}
|
||||
if bytes.Equal(iv, allZeroDirIV) {
|
||||
return nil, fmt.Errorf("diriv is all-zero")
|
||||
}
|
||||
return iv, nil
|
||||
}
|
||||
|
||||
// WriteDirIVAt - create a new gocryptfs.diriv file in the directory opened at
|
||||
// "dirfd". On error we try to delete the incomplete file.
|
||||
// This function is exported because it is used from fusefrontend, main,
|
||||
// and also the automated tests.
|
||||
func WriteDirIVAt(dirfd int) error {
|
||||
// It makes sense to have the diriv files group-readable so the FS can
|
||||
// be mounted from several users from a network drive (see
|
||||
// https://github.com/rfjakob/gocryptfs/issues/387 ).
|
||||
//
|
||||
// Note that gocryptfs.conf is still created with 0400 permissions so the
|
||||
// owner must explicitly chmod it to permit access.
|
||||
const dirivPerms = 0440
|
||||
|
||||
iv := cryptocore.RandBytes(DirIVLen)
|
||||
// 0400 permissions: gocryptfs.diriv should never be modified after creation.
|
||||
// Don't use "ioutil.WriteFile", it causes trouble on NFS:
|
||||
// https://github.com/rfjakob/gocryptfs/commit/7d38f80a78644c8ec4900cc990bfb894387112ed
|
||||
fd, err := syscallcompat.Openat(dirfd, DirIVFilename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, dirivPerms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Wrap the fd in an os.File - we need the write retry logic.
|
||||
f := os.NewFile(uintptr(fd), DirIVFilename)
|
||||
_, err = f.Write(iv)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
// Delete incomplete gocryptfs.diriv file
|
||||
syscallcompat.Unlinkat(dirfd, DirIVFilename, 0)
|
||||
return err
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
// Delete incomplete gocryptfs.diriv file
|
||||
syscallcompat.Unlinkat(dirfd, DirIVFilename, 0)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// encryptAndHashName encrypts "name" and hashes it to a longname if it is
|
||||
// too long.
|
||||
// Returns ENAMETOOLONG if "name" is longer than 255 bytes.
|
||||
func (be *NameTransform) EncryptAndHashName(name string, iv []byte) (string, error) {
|
||||
// Prevent the user from creating files longer than 255 chars.
|
||||
if len(name) > NameMax {
|
||||
return "", syscall.ENAMETOOLONG
|
||||
}
|
||||
cName := be.EncryptName(name, iv)
|
||||
if be.longNames && len(cName) > NameMax {
|
||||
return be.HashLongName(cName), nil
|
||||
}
|
||||
return cName, nil
|
||||
}
|
||||
|
||||
// Dir is like filepath.Dir but returns "" instead of ".".
|
||||
func Dir(path string) string {
|
||||
d := filepath.Dir(path)
|
||||
if d == "." {
|
||||
return ""
|
||||
}
|
||||
return d
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
package nametransform
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"../../rewrites/syscallcompat"
|
||||
)
|
||||
|
||||
const (
|
||||
// LongNameSuffix is the suffix used for files with long names.
|
||||
// Files with long names are stored in two files:
|
||||
// gocryptfs.longname.[sha256] <--- File content, prefix = gocryptfs.longname.
|
||||
// gocryptfs.longname.[sha256].name <--- File name, suffix = .name
|
||||
LongNameSuffix = ".name"
|
||||
longNamePrefix = "gocryptfs.longname."
|
||||
)
|
||||
|
||||
// HashLongName - take the hash of a long string "name" and return
|
||||
// "gocryptfs.longname.[sha256]"
|
||||
//
|
||||
// This function does not do any I/O.
|
||||
func (n *NameTransform) HashLongName(name string) string {
|
||||
hashBin := sha256.Sum256([]byte(name))
|
||||
hashBase64 := n.B64.EncodeToString(hashBin[:])
|
||||
return longNamePrefix + hashBase64
|
||||
}
|
||||
|
||||
// Values returned by IsLongName
|
||||
const (
|
||||
// LongNameContent is the file that stores the file content.
|
||||
// Example: gocryptfs.longname.URrM8kgxTKYMgCk4hKk7RO9Lcfr30XQof4L_5bD9Iro=
|
||||
LongNameContent = iota
|
||||
// LongNameFilename is the file that stores the full encrypted filename.
|
||||
// Example: gocryptfs.longname.URrM8kgxTKYMgCk4hKk7RO9Lcfr30XQof4L_5bD9Iro=.name
|
||||
LongNameFilename = iota
|
||||
// LongNameNone is used when the file does not have a long name.
|
||||
// Example: i1bpTaVLZq7sRNA9mL_2Ig==
|
||||
LongNameNone = iota
|
||||
)
|
||||
|
||||
// NameType - detect if cName is
|
||||
// gocryptfs.longname.[sha256] ........ LongNameContent (content of a long name file)
|
||||
// gocryptfs.longname.[sha256].name .... LongNameFilename (full file name of a long name file)
|
||||
// else ................................ LongNameNone (normal file)
|
||||
//
|
||||
// This function does not do any I/O.
|
||||
func NameType(cName string) int {
|
||||
if !strings.HasPrefix(cName, longNamePrefix) {
|
||||
return LongNameNone
|
||||
}
|
||||
if strings.HasSuffix(cName, LongNameSuffix) {
|
||||
return LongNameFilename
|
||||
}
|
||||
return LongNameContent
|
||||
}
|
||||
|
||||
// IsLongContent returns true if "cName" is the content store of a long name
|
||||
// file (looks like "gocryptfs.longname.[sha256]").
|
||||
//
|
||||
// This function does not do any I/O.
|
||||
func IsLongContent(cName string) bool {
|
||||
return NameType(cName) == LongNameContent
|
||||
}
|
||||
|
||||
// RemoveLongNameSuffix removes the ".name" suffix from cName, returning the corresponding
|
||||
// content file name.
|
||||
// No check is made if cName actually is a LongNameFilename.
|
||||
func RemoveLongNameSuffix(cName string) string {
|
||||
return cName[:len(cName)-len(LongNameSuffix)]
|
||||
}
|
||||
|
||||
// ReadLongName - read cName + ".name" from the directory opened as dirfd.
|
||||
//
|
||||
// Symlink-safe through Openat().
|
||||
func ReadLongNameAt(dirfd int, cName string) (string, error) {
|
||||
cName += LongNameSuffix
|
||||
var f *os.File
|
||||
{
|
||||
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
f = os.NewFile(uintptr(fd), "")
|
||||
// fd runs out of scope here
|
||||
}
|
||||
defer f.Close()
|
||||
// 256 (=255 padded to 16) bytes base64-encoded take 344 bytes: "AAAAAAA...AAA=="
|
||||
lim := 344
|
||||
// Allocate a bigger buffer so we see whether the file is too big
|
||||
buf := make([]byte, lim+1)
|
||||
n, err := f.ReadAt(buf, 0)
|
||||
if err != nil && err != io.EOF {
|
||||
return "", err
|
||||
}
|
||||
if n == 0 {
|
||||
return "", fmt.Errorf("ReadLongName: empty file")
|
||||
}
|
||||
if n > lim {
|
||||
return "", fmt.Errorf("ReadLongName: size=%d > limit=%d", n, lim)
|
||||
}
|
||||
return string(buf[0:n]), nil
|
||||
}
|
||||
|
||||
// DeleteLongName deletes "hashName.name" in the directory opened at "dirfd".
|
||||
//
|
||||
// This function is symlink-safe through the use of Unlinkat().
|
||||
func DeleteLongNameAt(dirfd int, hashName string) error {
|
||||
return syscallcompat.Unlinkat(dirfd, hashName+LongNameSuffix, 0)
|
||||
}
|
||||
|
||||
// WriteLongName encrypts plainName and writes it into "hashName.name".
|
||||
// For the convenience of the caller, plainName may also be a path and will be
|
||||
// Base()named internally.
|
||||
//
|
||||
// This function is symlink-safe through the use of Openat().
|
||||
func (n *NameTransform) WriteLongNameAt(dirfd int, hashName string, plainName string) (err error) {
|
||||
plainName = filepath.Base(plainName)
|
||||
|
||||
// Encrypt the basename
|
||||
dirIV, err := ReadDirIVAt(dirfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cName := n.EncryptName(plainName, dirIV)
|
||||
|
||||
// Write the encrypted name into hashName.name
|
||||
fdRaw, err := syscallcompat.Openat(dirfd, hashName+LongNameSuffix,
|
||||
syscall.O_WRONLY|syscall.O_CREAT|syscall.O_EXCL, 0400)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd := os.NewFile(uintptr(fdRaw), hashName+LongNameSuffix)
|
||||
_, err = fd.Write([]byte(cName))
|
||||
if err != nil {
|
||||
fd.Close()
|
||||
// Delete incomplete longname file
|
||||
syscallcompat.Unlinkat(dirfd, hashName+LongNameSuffix, 0)
|
||||
return err
|
||||
}
|
||||
err = fd.Close()
|
||||
if err != nil {
|
||||
// Delete incomplete longname file
|
||||
syscallcompat.Unlinkat(dirfd, hashName+LongNameSuffix, 0)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
// Package nametransform encrypts and decrypts filenames.
|
||||
package nametransform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"encoding/base64"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"../eme"
|
||||
)
|
||||
|
||||
const (
|
||||
// Like ext4, we allow at most 255 bytes for a file name.
|
||||
NameMax = 255
|
||||
)
|
||||
|
||||
// NameTransformer is an interface used to transform filenames.
|
||||
type NameTransformer interface {
|
||||
DecryptName(cipherName string, iv []byte) (string, error)
|
||||
EncryptName(plainName string, iv []byte) string
|
||||
EncryptAndHashName(name string, iv []byte) (string, error)
|
||||
HashLongName(name string) string
|
||||
WriteLongNameAt(dirfd int, hashName string, plainName string) error
|
||||
B64EncodeToString(src []byte) string
|
||||
B64DecodeString(s string) ([]byte, error)
|
||||
}
|
||||
|
||||
// NameTransform is used to transform filenames.
|
||||
type NameTransform struct {
|
||||
emeCipher *eme.EMECipher
|
||||
longNames bool
|
||||
// B64 = either base64.URLEncoding or base64.RawURLEncoding, depending
|
||||
// on the Raw64 feature flag
|
||||
B64 *base64.Encoding
|
||||
// Patterns to bypass decryption
|
||||
BadnamePatterns []string
|
||||
}
|
||||
|
||||
// New returns a new NameTransform instance.
|
||||
func New(e *eme.EMECipher, longNames bool, raw64 bool) *NameTransform {
|
||||
b64 := base64.URLEncoding
|
||||
if raw64 {
|
||||
b64 = base64.RawURLEncoding
|
||||
}
|
||||
return &NameTransform{
|
||||
emeCipher: e,
|
||||
longNames: longNames,
|
||||
B64: b64,
|
||||
}
|
||||
}
|
||||
|
||||
// DecryptName calls decryptName to try and decrypt a base64-encoded encrypted
|
||||
// filename "cipherName", and failing that checks if it can be bypassed
|
||||
func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) {
|
||||
res, err := n.decryptName(cipherName, iv)
|
||||
if err != nil {
|
||||
for _, pattern := range n.BadnamePatterns {
|
||||
match, err := filepath.Match(pattern, cipherName)
|
||||
if err == nil && match { // Pattern should have been validated already
|
||||
// Find longest decryptable substring
|
||||
// At least 16 bytes due to AES --> at least 22 characters in base64
|
||||
nameMin := n.B64.EncodedLen(aes.BlockSize)
|
||||
for charpos := len(cipherName) - 1; charpos >= nameMin; charpos-- {
|
||||
res, err = n.decryptName(cipherName[:charpos], iv)
|
||||
if err == nil {
|
||||
return res + cipherName[charpos:] + " GOCRYPTFS_BAD_NAME", nil
|
||||
}
|
||||
}
|
||||
return cipherName + " GOCRYPTFS_BAD_NAME", nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// decryptName decrypts a base64-encoded encrypted filename "cipherName" using the
|
||||
// initialization vector "iv".
|
||||
func (n *NameTransform) decryptName(cipherName string, iv []byte) (string, error) {
|
||||
bin, err := n.B64.DecodeString(cipherName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(bin) == 0 {
|
||||
return "", syscall.EBADMSG
|
||||
}
|
||||
if len(bin)%aes.BlockSize != 0 {
|
||||
return "", syscall.EBADMSG
|
||||
}
|
||||
bin = n.emeCipher.Decrypt(iv, bin)
|
||||
bin, err = unPad16(bin)
|
||||
if err != nil {
|
||||
// unPad16 returns detailed errors including the position of the
|
||||
// incorrect bytes. Kill the padding oracle by lumping everything into
|
||||
// a generic error.
|
||||
return "", syscall.EBADMSG
|
||||
}
|
||||
// A name can never contain a null byte or "/". Make sure we never return those
|
||||
// to the kernel, even when we read a corrupted (or fuzzed) filesystem.
|
||||
if bytes.Contains(bin, []byte{0}) || bytes.Contains(bin, []byte("/")) {
|
||||
return "", syscall.EBADMSG
|
||||
}
|
||||
// The name should never be "." or "..".
|
||||
if bytes.Equal(bin, []byte(".")) || bytes.Equal(bin, []byte("..")) {
|
||||
return "", syscall.EBADMSG
|
||||
}
|
||||
plain := string(bin)
|
||||
return plain, err
|
||||
}
|
||||
|
||||
// EncryptName encrypts "plainName", returns a base64-encoded "cipherName64",
|
||||
// encrypted using EME (https://github.com/rfjakob/eme).
|
||||
//
|
||||
// This function is exported because in some cases, fusefrontend needs access
|
||||
// to the full (not hashed) name if longname is used.
|
||||
func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 string) {
|
||||
bin := []byte(plainName)
|
||||
bin = pad16(bin)
|
||||
bin = n.emeCipher.Encrypt(iv, bin)
|
||||
cipherName64 = n.B64.EncodeToString(bin)
|
||||
return cipherName64
|
||||
}
|
||||
|
||||
// B64EncodeToString returns a Base64-encoded string
|
||||
func (n *NameTransform) B64EncodeToString(src []byte) string {
|
||||
return n.B64.EncodeToString(src)
|
||||
}
|
||||
|
||||
// B64DecodeString decodes a Base64-encoded string
|
||||
func (n *NameTransform) B64DecodeString(s string) ([]byte, error) {
|
||||
return n.B64.DecodeString(s)
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
package nametransform
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// pad16 - pad data to AES block size (=16 byte) using standard PKCS#7 padding
|
||||
// https://tools.ietf.org/html/rfc5652#section-6.3
|
||||
func pad16(orig []byte) (padded []byte) {
|
||||
oldLen := len(orig)
|
||||
if oldLen == 0 {
|
||||
log.Panic("Padding zero-length string makes no sense")
|
||||
}
|
||||
padLen := aes.BlockSize - oldLen%aes.BlockSize
|
||||
if padLen == 0 {
|
||||
padLen = aes.BlockSize
|
||||
}
|
||||
newLen := oldLen + padLen
|
||||
padded = make([]byte, newLen)
|
||||
copy(padded, orig)
|
||||
padByte := byte(padLen)
|
||||
for i := oldLen; i < newLen; i++ {
|
||||
padded[i] = padByte
|
||||
}
|
||||
return padded
|
||||
}
|
||||
|
||||
// unPad16 - remove padding
|
||||
func unPad16(padded []byte) ([]byte, error) {
|
||||
oldLen := len(padded)
|
||||
if oldLen == 0 {
|
||||
return nil, errors.New("Empty input")
|
||||
}
|
||||
if oldLen%aes.BlockSize != 0 {
|
||||
return nil, errors.New("Unaligned size")
|
||||
}
|
||||
// The last byte is always a padding byte
|
||||
padByte := padded[oldLen-1]
|
||||
// The padding byte's value is the padding length
|
||||
padLen := int(padByte)
|
||||
// Padding must be at least 1 byte
|
||||
if padLen == 0 {
|
||||
return nil, errors.New("Padding cannot be zero-length")
|
||||
}
|
||||
// Padding more than 16 bytes make no sense
|
||||
if padLen > aes.BlockSize {
|
||||
return nil, fmt.Errorf("Padding too long, padLen=%d > 16", padLen)
|
||||
}
|
||||
// Padding cannot be as long as (or longer than) the whole string,
|
||||
if padLen >= oldLen {
|
||||
return nil, fmt.Errorf("Padding too long, oldLen=%d >= padLen=%d", oldLen, padLen)
|
||||
}
|
||||
// All padding bytes must be identical
|
||||
for i := oldLen - padLen; i < oldLen; i++ {
|
||||
if padded[i] != padByte {
|
||||
return nil, fmt.Errorf("Padding byte at i=%d is invalid", i)
|
||||
}
|
||||
}
|
||||
newLen := oldLen - padLen
|
||||
return padded[0:newLen], nil
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
../stupidgcm/benchmark.bash
|
@ -1,97 +0,0 @@
|
||||
// Package siv_aead wraps the functions provided by siv
|
||||
// in a crypto.AEAD interface.
|
||||
package siv_aead
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"log"
|
||||
|
||||
"../jacobsa_crypto/siv"
|
||||
)
|
||||
|
||||
type sivAead struct {
|
||||
key []byte
|
||||
}
|
||||
|
||||
var _ cipher.AEAD = &sivAead{}
|
||||
|
||||
const (
|
||||
// KeyLen is the required key length. The SIV algorithm supports other lengths,
|
||||
// but we only support 64.
|
||||
KeyLen = 64
|
||||
)
|
||||
|
||||
// New returns a new cipher.AEAD implementation.
|
||||
func New(key []byte) cipher.AEAD {
|
||||
if len(key) != KeyLen {
|
||||
// SIV supports 32, 48 or 64-byte keys, but in gocryptfs we
|
||||
// exclusively use 64.
|
||||
log.Panicf("Key must be %d byte long (you passed %d)", KeyLen, len(key))
|
||||
}
|
||||
return new2(key)
|
||||
}
|
||||
|
||||
// Same as "New" without the 64-byte restriction.
|
||||
func new2(keyIn []byte) cipher.AEAD {
|
||||
// Create a private copy so the caller can zero the one he owns
|
||||
key := append([]byte{}, keyIn...)
|
||||
return &sivAead{
|
||||
key: key,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sivAead) NonceSize() int {
|
||||
// SIV supports any nonce size, but in gocryptfs we exclusively use 16.
|
||||
return 16
|
||||
}
|
||||
|
||||
func (s *sivAead) Overhead() int {
|
||||
return 16
|
||||
}
|
||||
|
||||
// Seal encrypts "in" using "nonce" and "authData" and appends the result to "dst"
|
||||
func (s *sivAead) Seal(dst, nonce, plaintext, authData []byte) []byte {
|
||||
if len(nonce) != 16 {
|
||||
// SIV supports any nonce size, but in gocryptfs we exclusively use 16.
|
||||
log.Panic("nonce must be 16 bytes long")
|
||||
}
|
||||
if len(s.key) == 0 {
|
||||
log.Panic("Key has been wiped?")
|
||||
}
|
||||
// https://github.com/jacobsa/crypto/blob/master/siv/encrypt.go#L48:
|
||||
// As per RFC 5297 section 3, you may use this function for nonce-based
|
||||
// authenticated encryption by passing a nonce as the last associated
|
||||
// data element.
|
||||
associated := [][]byte{authData, nonce}
|
||||
out, err := siv.Encrypt(dst, s.key, plaintext, associated)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Open decrypts "in" using "nonce" and "authData" and appends the result to "dst"
|
||||
func (s *sivAead) Open(dst, nonce, ciphertext, authData []byte) ([]byte, error) {
|
||||
if len(nonce) != 16 {
|
||||
// SIV supports any nonce size, but in gocryptfs we exclusively use 16.
|
||||
log.Panic("nonce must be 16 bytes long")
|
||||
}
|
||||
if len(s.key) == 0 {
|
||||
log.Panic("Key has been wiped?")
|
||||
}
|
||||
associated := [][]byte{authData, nonce}
|
||||
dec, err := siv.Decrypt(s.key, ciphertext, associated)
|
||||
return append(dst, dec...), err
|
||||
}
|
||||
|
||||
// Wipe tries to wipe the AES key from memory by overwriting it with zeros
|
||||
// and setting the reference to nil.
|
||||
//
|
||||
// This is not bulletproof due to possible GC copies, but
|
||||
// still raises to bar for extracting the key.
|
||||
func (s *sivAead) Wipe() {
|
||||
for i := range s.key {
|
||||
s.key[i] = 0
|
||||
}
|
||||
s.key = nil
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package stupidgcm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrAuth is returned when the message authentication fails
|
||||
var ErrAuth = fmt.Errorf("stupidgcm: message authentication failed")
|
@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
exec ../speed/benchmark.bash
|
@ -1,28 +0,0 @@
|
||||
// +build !without_openssl
|
||||
|
||||
package stupidgcm
|
||||
|
||||
// In general, OpenSSL is only threadsafe if you provide a locking function
|
||||
// through CRYPTO_set_locking_callback. However, the GCM operations that
|
||||
// stupidgcm uses never call that function. Additionally, the manual locking
|
||||
// has been removed completely in openssl 1.1.0.
|
||||
|
||||
/*
|
||||
#include <openssl/crypto.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static void dummy_callback(int mode, int n, const char *file, int line) {
|
||||
printf("stupidgcm: thread locking is not implemented and should not be "
|
||||
"needed. Please upgrade openssl.\n");
|
||||
// panic
|
||||
__builtin_trap();
|
||||
}
|
||||
static void set_dummy_callback() {
|
||||
CRYPTO_set_locking_callback(dummy_callback);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func init() {
|
||||
C.set_dummy_callback()
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package stupidgcm
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
// PreferOpenSSL tells us if OpenSSL is faster than Go GCM on this machine.
|
||||
//
|
||||
// Go GCM is only faster if the CPU either:
|
||||
//
|
||||
// 1) Is X86_64 && has AES instructions && Go is v1.6 or higher
|
||||
// 2) Is ARM64 && has AES instructions && Go is v1.11 or higher
|
||||
// (commit https://github.com/golang/go/commit/4f1f503373cda7160392be94e3849b0c9b9ebbda)
|
||||
//
|
||||
// See https://github.com/rfjakob/gocryptfs/wiki/CPU-Benchmarks
|
||||
// for benchmarks.
|
||||
func PreferOpenSSL() bool {
|
||||
if BuiltWithoutOpenssl {
|
||||
return false
|
||||
}
|
||||
// Safe to call on other architectures - will just read false.
|
||||
if cpu.X86.HasAES || cpu.ARM64.HasAES {
|
||||
// Go stdlib is probably faster
|
||||
return false
|
||||
}
|
||||
// Openssl is probably faster
|
||||
return true
|
||||
}
|
@ -1,250 +0,0 @@
|
||||
// +build !without_openssl
|
||||
|
||||
// Package stupidgcm is a thin wrapper for OpenSSL's GCM encryption and
|
||||
// decryption functions. It only support 32-byte keys and 16-bit IVs.
|
||||
package stupidgcm
|
||||
|
||||
//#include <openssl/err.h>
|
||||
// #include <openssl/evp.h>
|
||||
// #cgo pkg-config: libcrypto
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
"log"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// BuiltWithoutOpenssl indicates if openssl been disabled at compile-time
|
||||
BuiltWithoutOpenssl = false
|
||||
|
||||
keyLen = 32
|
||||
ivLen = 16
|
||||
tagLen = 16
|
||||
)
|
||||
|
||||
// StupidGCM implements the cipher.AEAD interface
|
||||
type StupidGCM struct {
|
||||
key []byte
|
||||
forceDecode bool
|
||||
}
|
||||
|
||||
// Verify that we satisfy the cipher.AEAD interface
|
||||
var _ cipher.AEAD = &StupidGCM{}
|
||||
|
||||
// New returns a new cipher.AEAD implementation..
|
||||
func New(keyIn []byte, forceDecode bool) cipher.AEAD {
|
||||
if len(keyIn) != keyLen {
|
||||
log.Panicf("Only %d-byte keys are supported", keyLen)
|
||||
}
|
||||
// Create a private copy of the key
|
||||
key := append([]byte{}, keyIn...)
|
||||
return &StupidGCM{key: key, forceDecode: forceDecode}
|
||||
}
|
||||
|
||||
// NonceSize returns the required size of the nonce / IV.
|
||||
func (g *StupidGCM) NonceSize() int {
|
||||
return ivLen
|
||||
}
|
||||
|
||||
// Overhead returns the number of bytes that are added for authentication.
|
||||
func (g *StupidGCM) Overhead() int {
|
||||
return tagLen
|
||||
}
|
||||
|
||||
// Seal encrypts "in" using "iv" and "authData" and append the result to "dst"
|
||||
func (g *StupidGCM) Seal(dst, iv, in, authData []byte) []byte {
|
||||
if len(iv) != ivLen {
|
||||
log.Panicf("Only %d-byte IVs are supported", ivLen)
|
||||
}
|
||||
if len(in) == 0 {
|
||||
log.Panic("Zero-length input data is not supported")
|
||||
}
|
||||
if len(g.key) != keyLen {
|
||||
log.Panicf("Wrong key length: %d. Key has been wiped?", len(g.key))
|
||||
}
|
||||
|
||||
// If the "dst" slice is large enough we can use it as our output buffer
|
||||
outLen := len(in) + tagLen
|
||||
var buf []byte
|
||||
inplace := false
|
||||
if cap(dst)-len(dst) >= outLen {
|
||||
inplace = true
|
||||
buf = dst[len(dst) : len(dst)+outLen]
|
||||
} else {
|
||||
buf = make([]byte, outLen)
|
||||
}
|
||||
|
||||
// https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Encryption_using_GCM_mode
|
||||
|
||||
// Create scratch space "context"
|
||||
ctx := C.EVP_CIPHER_CTX_new()
|
||||
if ctx == nil {
|
||||
log.Panic("EVP_CIPHER_CTX_new failed")
|
||||
}
|
||||
|
||||
// Set cipher to AES-256
|
||||
if C.EVP_EncryptInit_ex(ctx, C.EVP_aes_256_gcm(), nil, nil, nil) != 1 {
|
||||
log.Panic("EVP_EncryptInit_ex I failed")
|
||||
}
|
||||
|
||||
// Use 16-byte IV
|
||||
if C.EVP_CIPHER_CTX_ctrl(ctx, C.EVP_CTRL_GCM_SET_IVLEN, ivLen, nil) != 1 {
|
||||
log.Panic("EVP_CIPHER_CTX_ctrl EVP_CTRL_GCM_SET_IVLEN failed")
|
||||
}
|
||||
|
||||
// Set key and IV
|
||||
if C.EVP_EncryptInit_ex(ctx, nil, nil, (*C.uchar)(&g.key[0]), (*C.uchar)(&iv[0])) != 1 {
|
||||
log.Panic("EVP_EncryptInit_ex II failed")
|
||||
}
|
||||
|
||||
// Provide authentication data
|
||||
var resultLen C.int
|
||||
if C.EVP_EncryptUpdate(ctx, nil, &resultLen, (*C.uchar)(&authData[0]), C.int(len(authData))) != 1 {
|
||||
log.Panic("EVP_EncryptUpdate authData failed")
|
||||
}
|
||||
if int(resultLen) != len(authData) {
|
||||
log.Panicf("Unexpected length %d", resultLen)
|
||||
}
|
||||
|
||||
// Encrypt "in" into "buf"
|
||||
if C.EVP_EncryptUpdate(ctx, (*C.uchar)(&buf[0]), &resultLen, (*C.uchar)(&in[0]), C.int(len(in))) != 1 {
|
||||
log.Panic("EVP_EncryptUpdate failed")
|
||||
}
|
||||
if int(resultLen) != len(in) {
|
||||
log.Panicf("Unexpected length %d", resultLen)
|
||||
}
|
||||
|
||||
// Finalise encryption
|
||||
// Because GCM is a stream encryption, this will not write out any data.
|
||||
dummy := make([]byte, 16)
|
||||
if C.EVP_EncryptFinal_ex(ctx, (*C.uchar)(&dummy[0]), &resultLen) != 1 {
|
||||
log.Panic("EVP_EncryptFinal_ex failed")
|
||||
}
|
||||
if resultLen != 0 {
|
||||
log.Panicf("Unexpected length %d", resultLen)
|
||||
}
|
||||
|
||||
// Get GMAC tag and append it to the ciphertext in "buf"
|
||||
if C.EVP_CIPHER_CTX_ctrl(ctx, C.EVP_CTRL_GCM_GET_TAG, tagLen, (unsafe.Pointer)(&buf[len(in)])) != 1 {
|
||||
log.Panic("EVP_CIPHER_CTX_ctrl EVP_CTRL_GCM_GET_TAG failed")
|
||||
}
|
||||
|
||||
// Free scratch space
|
||||
C.EVP_CIPHER_CTX_free(ctx)
|
||||
|
||||
if inplace {
|
||||
return dst[:len(dst)+outLen]
|
||||
}
|
||||
return append(dst, buf...)
|
||||
}
|
||||
|
||||
// Open decrypts "in" using "iv" and "authData" and append the result to "dst"
|
||||
func (g *StupidGCM) Open(dst, iv, in, authData []byte) ([]byte, error) {
|
||||
if len(iv) != ivLen {
|
||||
log.Panicf("Only %d-byte IVs are supported", ivLen)
|
||||
}
|
||||
if len(g.key) != keyLen {
|
||||
log.Panicf("Wrong key length: %d. Key has been wiped?", len(g.key))
|
||||
}
|
||||
if len(in) <= tagLen {
|
||||
return nil, fmt.Errorf("stupidgcm: input data too short (%d bytes)", len(in))
|
||||
}
|
||||
|
||||
// If the "dst" slice is large enough we can use it as our output buffer
|
||||
outLen := len(in) - tagLen
|
||||
var buf []byte
|
||||
inplace := false
|
||||
if cap(dst)-len(dst) >= outLen {
|
||||
inplace = true
|
||||
buf = dst[len(dst) : len(dst)+outLen]
|
||||
} else {
|
||||
buf = make([]byte, len(in)-tagLen)
|
||||
}
|
||||
|
||||
ciphertext := in[:len(in)-tagLen]
|
||||
tag := in[len(in)-tagLen:]
|
||||
|
||||
// https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Encryption_using_GCM_mode
|
||||
|
||||
// Create scratch space "context"
|
||||
ctx := C.EVP_CIPHER_CTX_new()
|
||||
if ctx == nil {
|
||||
log.Panic("EVP_CIPHER_CTX_new failed")
|
||||
}
|
||||
|
||||
// Set cipher to AES-256
|
||||
if C.EVP_DecryptInit_ex(ctx, C.EVP_aes_256_gcm(), nil, nil, nil) != 1 {
|
||||
log.Panic("EVP_DecryptInit_ex I failed")
|
||||
}
|
||||
|
||||
// Use 16-byte IV
|
||||
if C.EVP_CIPHER_CTX_ctrl(ctx, C.EVP_CTRL_GCM_SET_IVLEN, ivLen, nil) != 1 {
|
||||
log.Panic("EVP_CIPHER_CTX_ctrl EVP_CTRL_GCM_SET_IVLEN failed")
|
||||
}
|
||||
|
||||
// Set key and IV
|
||||
if C.EVP_DecryptInit_ex(ctx, nil, nil, (*C.uchar)(&g.key[0]), (*C.uchar)(&iv[0])) != 1 {
|
||||
log.Panic("EVP_DecryptInit_ex II failed")
|
||||
}
|
||||
|
||||
// Set expected GMAC tag
|
||||
if C.EVP_CIPHER_CTX_ctrl(ctx, C.EVP_CTRL_GCM_SET_TAG, tagLen, (unsafe.Pointer)(&tag[0])) != 1 {
|
||||
log.Panic("EVP_CIPHER_CTX_ctrl failed")
|
||||
}
|
||||
|
||||
// Provide authentication data
|
||||
var resultLen C.int
|
||||
if C.EVP_DecryptUpdate(ctx, nil, &resultLen, (*C.uchar)(&authData[0]), C.int(len(authData))) != 1 {
|
||||
log.Panic("EVP_DecryptUpdate authData failed")
|
||||
}
|
||||
if int(resultLen) != len(authData) {
|
||||
log.Panicf("Unexpected length %d", resultLen)
|
||||
}
|
||||
|
||||
// Decrypt "ciphertext" into "buf"
|
||||
if C.EVP_DecryptUpdate(ctx, (*C.uchar)(&buf[0]), &resultLen, (*C.uchar)(&ciphertext[0]), C.int(len(ciphertext))) != 1 {
|
||||
log.Panic("EVP_DecryptUpdate failed")
|
||||
}
|
||||
if int(resultLen) != len(ciphertext) {
|
||||
log.Panicf("Unexpected length %d", resultLen)
|
||||
}
|
||||
|
||||
// Check GMAC
|
||||
dummy := make([]byte, 16)
|
||||
res := C.EVP_DecryptFinal_ex(ctx, (*C.uchar)(&dummy[0]), &resultLen)
|
||||
if resultLen != 0 {
|
||||
log.Panicf("Unexpected length %d", resultLen)
|
||||
}
|
||||
|
||||
// Free scratch space
|
||||
C.EVP_CIPHER_CTX_free(ctx)
|
||||
|
||||
if res != 1 {
|
||||
// The error code must always be checked by the calling function, because the decrypted buffer
|
||||
// may contain corrupted data that we are returning in case the user forced reads
|
||||
if g.forceDecode == true {
|
||||
return append(dst, buf...), ErrAuth
|
||||
}
|
||||
return nil, ErrAuth
|
||||
}
|
||||
|
||||
if inplace {
|
||||
return dst[:len(dst)+outLen], nil
|
||||
}
|
||||
return append(dst, buf...), nil
|
||||
}
|
||||
|
||||
// Wipe tries to wipe the AES key from memory by overwriting it with zeros
|
||||
// and setting the reference to nil.
|
||||
//
|
||||
// This is not bulletproof due to possible GC copies, but
|
||||
// still raises to bar for extracting the key.
|
||||
func (g *StupidGCM) Wipe() {
|
||||
for i := range g.key {
|
||||
g.key[i] = 0
|
||||
}
|
||||
g.key = nil
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
// +build without_openssl
|
||||
|
||||
package stupidgcm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||
)
|
||||
|
||||
type StupidGCM struct{}
|
||||
|
||||
const (
|
||||
// BuiltWithoutOpenssl indicates if openssl been disabled at compile-time
|
||||
BuiltWithoutOpenssl = true
|
||||
)
|
||||
|
||||
func errExit() {
|
||||
fmt.Fprintln(os.Stderr, "gocryptfs has been compiled without openssl support but you are still trying to use openssl")
|
||||
os.Exit(exitcodes.OpenSSL)
|
||||
}
|
||||
|
||||
func New(_ []byte, _ bool) *StupidGCM {
|
||||
errExit()
|
||||
// Never reached
|
||||
return &StupidGCM{}
|
||||
}
|
||||
|
||||
func (g *StupidGCM) NonceSize() int {
|
||||
errExit()
|
||||
return -1
|
||||
}
|
||||
|
||||
func (g *StupidGCM) Overhead() int {
|
||||
errExit()
|
||||
return -1
|
||||
}
|
||||
|
||||
func (g *StupidGCM) Seal(_, _, _, _ []byte) []byte {
|
||||
errExit()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *StupidGCM) Open(_, _, _, _ []byte) ([]byte, error) {
|
||||
errExit()
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (g *StupidGCM) Wipe() {
|
||||
errExit()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,325 +0,0 @@
|
||||
// Package configfile reads and writes gocryptfs.conf does the key
|
||||
// wrapping.
|
||||
package configfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"syscall"
|
||||
|
||||
"../contentenc"
|
||||
"../../gocryptfs_internal/cryptocore"
|
||||
"../../gocryptfs_internal/exitcodes"
|
||||
)
|
||||
import "os"
|
||||
|
||||
const (
|
||||
// ConfDefaultName is the default configuration file name.
|
||||
// The dot "." is not used in base64url (RFC4648), hence
|
||||
// we can never clash with an encrypted file.
|
||||
ConfDefaultName = "gocryptfs.conf"
|
||||
// ConfReverseName is the default configuration file name in reverse mode,
|
||||
// the config file gets stored next to the plain-text files. Make it hidden
|
||||
// (start with dot) to not annoy the user.
|
||||
ConfReverseName = ".gocryptfs.reverse.conf"
|
||||
)
|
||||
|
||||
// ConfFile is the content of a config file.
|
||||
type ConfFile struct {
|
||||
// Creator is the gocryptfs version string.
|
||||
// This only documents the config file for humans who look at it. The actual
|
||||
// technical info is contained in FeatureFlags.
|
||||
Creator string
|
||||
// EncryptedKey holds an encrypted AES key, unlocked using a password
|
||||
// hashed with scrypt
|
||||
EncryptedKey []byte
|
||||
// ScryptObject stores parameters for scrypt hashing (key derivation)
|
||||
ScryptObject ScryptKDF
|
||||
// Version is the On-Disk-Format version this filesystem uses
|
||||
Version uint16
|
||||
// FeatureFlags is a list of feature flags this filesystem has enabled.
|
||||
// If gocryptfs encounters a feature flag it does not support, it will refuse
|
||||
// mounting. This mechanism is analogous to the ext4 feature flags that are
|
||||
// stored in the superblock.
|
||||
FeatureFlags []string
|
||||
// Filename is the name of the config file. Not exported to JSON.
|
||||
filename string
|
||||
}
|
||||
|
||||
// randBytesDevRandom gets "n" random bytes from /dev/random or panics
|
||||
func randBytesDevRandom(n int) []byte {
|
||||
f, err := os.Open("/dev/random")
|
||||
if err != nil {
|
||||
log.Panic("Failed to open /dev/random: " + err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
b := make([]byte, n)
|
||||
_, err = io.ReadFull(f, b)
|
||||
if err != nil {
|
||||
log.Panic("Failed to read random bytes: " + err.Error())
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Create - create a new config with a random key encrypted with
|
||||
// "password" and write it to "filename".
|
||||
// Uses scrypt with cost parameter logN.
|
||||
func Create(filename string, password []byte, plaintextNames bool,
|
||||
logN int, creator string, aessiv bool, devrandom bool) error {
|
||||
var cf ConfFile
|
||||
cf.filename = filename
|
||||
cf.Creator = creator
|
||||
cf.Version = contentenc.CurrentVersion
|
||||
|
||||
// Set feature flags
|
||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagGCMIV128])
|
||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagHKDF])
|
||||
if plaintextNames {
|
||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagPlaintextNames])
|
||||
} else {
|
||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagDirIV])
|
||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagEMENames])
|
||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagLongNames])
|
||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagRaw64])
|
||||
}
|
||||
if aessiv {
|
||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagAESSIV])
|
||||
}
|
||||
{
|
||||
// Generate new random master key
|
||||
var key []byte
|
||||
if devrandom {
|
||||
key = randBytesDevRandom(cryptocore.KeyLen)
|
||||
} else {
|
||||
key = cryptocore.RandBytes(cryptocore.KeyLen)
|
||||
}
|
||||
// Encrypt it using the password
|
||||
// This sets ScryptObject and EncryptedKey
|
||||
// Note: this looks at the FeatureFlags, so call it AFTER setting them.
|
||||
cf.EncryptKey(key, password, logN, false)
|
||||
for i := range key {
|
||||
key[i] = 0
|
||||
}
|
||||
// key runs out of scope here
|
||||
}
|
||||
// Write file to disk
|
||||
return cf.WriteFile()
|
||||
}
|
||||
|
||||
// LoadAndDecrypt - read config file from disk and decrypt the
|
||||
// contained key using "password".
|
||||
// Returns the decrypted key and the ConfFile object
|
||||
//
|
||||
// If "password" is empty, the config file is read
|
||||
// but the key is not decrypted (returns nil in its place).
|
||||
func LoadAndDecrypt(filename string, password []byte) ([]byte, *ConfFile, error) {
|
||||
cf, err := Load(filename)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(password) == 0 {
|
||||
// We have validated the config file, but without a password we cannot
|
||||
// decrypt the master key. Return only the parsed config.
|
||||
return nil, cf, nil
|
||||
// TODO: Make this an error in gocryptfs v1.7. All code should now call
|
||||
// Load() instead of calling LoadAndDecrypt() with an empty password.
|
||||
}
|
||||
|
||||
// Decrypt the masterkey using the password
|
||||
key, _, err := cf.DecryptMasterKey(password, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return key, cf, err
|
||||
}
|
||||
|
||||
// Load loads and parses the config file at "filename".
|
||||
func Load(filename string) (*ConfFile, error) {
|
||||
var cf ConfFile
|
||||
cf.filename = filename
|
||||
|
||||
// Read from disk
|
||||
js, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(js) == 0 {
|
||||
return nil, fmt.Errorf("Config file is empty")
|
||||
}
|
||||
|
||||
// Unmarshal
|
||||
err = json.Unmarshal(js, &cf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cf.Version != contentenc.CurrentVersion {
|
||||
return nil, fmt.Errorf("Unsupported on-disk format %d", cf.Version)
|
||||
}
|
||||
|
||||
// Check that all set feature flags are known
|
||||
for _, flag := range cf.FeatureFlags {
|
||||
if !cf.isFeatureFlagKnown(flag) {
|
||||
return nil, fmt.Errorf("Unsupported feature flag %q", flag)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all required feature flags are set
|
||||
var requiredFlags []flagIota
|
||||
if cf.IsFeatureFlagSet(FlagPlaintextNames) {
|
||||
requiredFlags = requiredFlagsPlaintextNames
|
||||
} else {
|
||||
requiredFlags = requiredFlagsNormal
|
||||
}
|
||||
deprecatedFs := false
|
||||
for _, i := range requiredFlags {
|
||||
if !cf.IsFeatureFlagSet(i) {
|
||||
fmt.Fprintf(os.Stderr, "Required feature flag %q is missing\n", knownFlags[i])
|
||||
deprecatedFs = true
|
||||
}
|
||||
}
|
||||
if deprecatedFs {
|
||||
return nil, exitcodes.NewErr("Deprecated filesystem", exitcodes.DeprecatedFS)
|
||||
}
|
||||
|
||||
// All good
|
||||
return &cf, nil
|
||||
}
|
||||
|
||||
// DecryptMasterKey decrypts the masterkey stored in cf.EncryptedKey using
|
||||
// password.
|
||||
func (cf *ConfFile) DecryptMasterKey(password []byte, giveHash bool) (masterkey, scryptHash []byte, err error) {
|
||||
// Generate derived key from password
|
||||
scryptHash = cf.ScryptObject.DeriveKey(password)
|
||||
|
||||
// Unlock master key using password-based key
|
||||
useHKDF := cf.IsFeatureFlagSet(FlagHKDF)
|
||||
ce := GetKeyEncrypter(scryptHash, useHKDF)
|
||||
|
||||
masterkey, err = ce.DecryptBlock(cf.EncryptedKey, 0, nil)
|
||||
|
||||
ce.Wipe()
|
||||
ce = nil
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, exitcodes.NewErr("Password incorrect.", exitcodes.PasswordIncorrect)
|
||||
}
|
||||
|
||||
if !giveHash {
|
||||
// Purge scrypt-derived key
|
||||
for i := range scryptHash {
|
||||
scryptHash[i] = 0
|
||||
}
|
||||
scryptHash = nil
|
||||
}
|
||||
|
||||
return masterkey, scryptHash, nil
|
||||
}
|
||||
|
||||
// EncryptKey - encrypt "key" using an scrypt hash generated from "password"
|
||||
// and store it in cf.EncryptedKey.
|
||||
// Uses scrypt with cost parameter logN and stores the scrypt parameters in
|
||||
// cf.ScryptObject.
|
||||
func (cf *ConfFile) EncryptKey(key []byte, password []byte, logN int, giveHash bool) []byte {
|
||||
// Generate scrypt-derived key from password
|
||||
cf.ScryptObject = NewScryptKDF(logN)
|
||||
scryptHash := cf.ScryptObject.DeriveKey(password)
|
||||
|
||||
// Lock master key using password-based key
|
||||
useHKDF := cf.IsFeatureFlagSet(FlagHKDF)
|
||||
ce := GetKeyEncrypter(scryptHash, useHKDF)
|
||||
cf.EncryptedKey = ce.EncryptBlock(key, 0, nil)
|
||||
|
||||
if !giveHash {
|
||||
// Purge scrypt-derived key
|
||||
for i := range scryptHash {
|
||||
scryptHash[i] = 0
|
||||
}
|
||||
scryptHash = nil
|
||||
}
|
||||
ce.Wipe()
|
||||
ce = nil
|
||||
|
||||
return scryptHash
|
||||
}
|
||||
|
||||
// DroidFS function to allow masterkey to be decrypted directely using the scrypt hash and return it if requested
|
||||
func (cf *ConfFile) GetMasterkey(password, givenScryptHash, returnedScryptHashBuff []byte) []byte {
|
||||
var masterkey []byte
|
||||
var err error
|
||||
var scryptHash []byte
|
||||
if len(givenScryptHash) > 0 { //decrypt with hash
|
||||
useHKDF := cf.IsFeatureFlagSet(FlagHKDF)
|
||||
ce := GetKeyEncrypter(givenScryptHash, useHKDF)
|
||||
masterkey, err = ce.DecryptBlock(cf.EncryptedKey, 0, nil)
|
||||
ce.Wipe()
|
||||
ce = nil
|
||||
if err == nil {
|
||||
return masterkey
|
||||
}
|
||||
} else { //decrypt with password
|
||||
masterkey, scryptHash, err = cf.DecryptMasterKey(password, len(returnedScryptHashBuff)>0)
|
||||
//copy and wipe scryptHash
|
||||
for i := range scryptHash {
|
||||
returnedScryptHashBuff[i] = scryptHash[i]
|
||||
scryptHash[i] = 0
|
||||
}
|
||||
if err == nil {
|
||||
return masterkey
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteFile - write out config in JSON format to file "filename.tmp"
|
||||
// then rename over "filename".
|
||||
// This way a password change atomically replaces the file.
|
||||
func (cf *ConfFile) WriteFile() error {
|
||||
tmp := cf.filename + ".tmp"
|
||||
// 0400 permissions: gocryptfs.conf should be kept secret and never be written to.
|
||||
fd, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0400)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
js, err := json.MarshalIndent(cf, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// For convenience for the user, add a newline at the end.
|
||||
js = append(js, '\n')
|
||||
_, err = fd.Write(js)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = fd.Sync()
|
||||
if err != nil {
|
||||
// This can happen on network drives: FRITZ.NAS mounted on MacOS returns
|
||||
// "operation not supported": https://github.com/rfjakob/gocryptfs/issues/390
|
||||
// Try sync instead
|
||||
syscall.Sync()
|
||||
}
|
||||
err = fd.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Rename(tmp, cf.filename)
|
||||
return err
|
||||
}
|
||||
|
||||
// getKeyEncrypter is a helper function that returns the right ContentEnc
|
||||
// instance for the "useHKDF" setting.
|
||||
func GetKeyEncrypter(scryptHash []byte, useHKDF bool) *contentenc.ContentEnc {
|
||||
IVLen := 96
|
||||
// gocryptfs v1.2 and older used 96-bit IVs for master key encryption.
|
||||
// v1.3 adds the "HKDF" feature flag, which also enables 128-bit nonces.
|
||||
if useHKDF {
|
||||
IVLen = contentenc.DefaultIVBits
|
||||
}
|
||||
cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, IVLen, useHKDF, false)
|
||||
ce := contentenc.New(cc, 4096, false)
|
||||
return ce
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package configfile
|
||||
|
||||
type flagIota int
|
||||
|
||||
const (
|
||||
// FlagPlaintextNames indicates that filenames are unencrypted.
|
||||
FlagPlaintextNames flagIota = iota
|
||||
// FlagDirIV indicates that a per-directory IV file is used.
|
||||
FlagDirIV
|
||||
// FlagEMENames indicates EME (ECB-Mix-ECB) filename encryption.
|
||||
// This flag is mandatory since gocryptfs v1.0.
|
||||
FlagEMENames
|
||||
// FlagGCMIV128 indicates 128-bit GCM IVs.
|
||||
// This flag is mandatory since gocryptfs v1.0.
|
||||
FlagGCMIV128
|
||||
// FlagLongNames allows file names longer than 176 bytes.
|
||||
FlagLongNames
|
||||
// FlagAESSIV selects an AES-SIV based crypto backend.
|
||||
FlagAESSIV
|
||||
// FlagRaw64 enables raw (unpadded) base64 encoding for file names
|
||||
FlagRaw64
|
||||
// FlagHKDF enables HKDF-derived keys for use with GCM, EME and SIV
|
||||
// instead of directly using the master key (GCM and EME) or the SHA-512
|
||||
// hashed master key (SIV).
|
||||
// Note that this flag does not change the password hashing algorithm
|
||||
// which always is scrypt.
|
||||
FlagHKDF
|
||||
)
|
||||
|
||||
// knownFlags stores the known feature flags and their string representation
|
||||
var knownFlags = map[flagIota]string{
|
||||
FlagPlaintextNames: "PlaintextNames",
|
||||
FlagDirIV: "DirIV",
|
||||
FlagEMENames: "EMENames",
|
||||
FlagGCMIV128: "GCMIV128",
|
||||
FlagLongNames: "LongNames",
|
||||
FlagAESSIV: "AESSIV",
|
||||
FlagRaw64: "Raw64",
|
||||
FlagHKDF: "HKDF",
|
||||
}
|
||||
|
||||
// Filesystems that do not have these feature flags set are deprecated.
|
||||
var requiredFlagsNormal = []flagIota{
|
||||
FlagDirIV,
|
||||
FlagEMENames,
|
||||
FlagGCMIV128,
|
||||
}
|
||||
|
||||
// Filesystems without filename encryption obviously don't have or need the
|
||||
// filename related feature flags.
|
||||
var requiredFlagsPlaintextNames = []flagIota{
|
||||
FlagGCMIV128,
|
||||
}
|
||||
|
||||
// isFeatureFlagKnown verifies that we understand a feature flag.
|
||||
func (cf *ConfFile) isFeatureFlagKnown(flag string) bool {
|
||||
for _, knownFlag := range knownFlags {
|
||||
if knownFlag == flag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsFeatureFlagSet returns true if the feature flag "flagWant" is enabled.
|
||||
func (cf *ConfFile) IsFeatureFlagSet(flagWant flagIota) bool {
|
||||
flagString := knownFlags[flagWant]
|
||||
for _, flag := range cf.FeatureFlags {
|
||||
if flag == flagString {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
package configfile
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"golang.org/x/crypto/scrypt"
|
||||
|
||||
"../../gocryptfs_internal/cryptocore"
|
||||
)
|
||||
|
||||
const (
|
||||
// ScryptDefaultLogN is the default scrypt logN configuration parameter.
|
||||
// logN=16 (N=2^16) uses 64MB of memory and takes 4 seconds on my Atom Z3735F
|
||||
// netbook.
|
||||
ScryptDefaultLogN = 16
|
||||
// From RFC7914, section 2:
|
||||
// At the current time, r=8 and p=1 appears to yield good
|
||||
// results, but as memory latency and CPU parallelism increase, it is
|
||||
// likely that the optimum values for both r and p will increase.
|
||||
// We reject all lower values that we might get through modified config files.
|
||||
scryptMinR = 8
|
||||
scryptMinP = 1
|
||||
// logN=10 takes 6ms on a Pentium G630. This should be fast enough for all
|
||||
// purposes. We reject lower values.
|
||||
scryptMinLogN = 10
|
||||
// We always generate 32-byte salts. Anything smaller than that is rejected.
|
||||
scryptMinSaltLen = 32
|
||||
)
|
||||
|
||||
// ScryptKDF is an instance of the scrypt key deriviation function.
|
||||
type ScryptKDF struct {
|
||||
// Salt is the random salt that is passed to scrypt
|
||||
Salt []byte
|
||||
// N: scrypt CPU/Memory cost parameter
|
||||
N int
|
||||
// R: scrypt block size parameter
|
||||
R int
|
||||
// P: scrypt parallelization parameter
|
||||
P int
|
||||
// KeyLen is the output data length
|
||||
KeyLen int
|
||||
}
|
||||
|
||||
// NewScryptKDF returns a new instance of ScryptKDF.
|
||||
func NewScryptKDF(logN int) ScryptKDF {
|
||||
var s ScryptKDF
|
||||
s.Salt = cryptocore.RandBytes(cryptocore.KeyLen)
|
||||
if logN <= 0 {
|
||||
s.N = 1 << ScryptDefaultLogN
|
||||
} else {
|
||||
s.N = 1 << uint32(logN)
|
||||
}
|
||||
s.R = 8 // Always 8
|
||||
s.P = 1 // Always 1
|
||||
s.KeyLen = cryptocore.KeyLen
|
||||
return s
|
||||
}
|
||||
|
||||
// DeriveKey returns a new key from a supplied password.
|
||||
func (s *ScryptKDF) DeriveKey(pw []byte) []byte {
|
||||
if s.validateParams() {
|
||||
k, err := scrypt.Key(pw, s.Salt, s.N, s.R, s.P, s.KeyLen)
|
||||
if err != nil {
|
||||
log.Panicf("DeriveKey failed: %v", err)
|
||||
}
|
||||
return k
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// LogN - N is saved as 2^LogN, but LogN is much easier to work with.
|
||||
// This function gives you LogN = Log2(N).
|
||||
func (s *ScryptKDF) LogN() int {
|
||||
return int(math.Log2(float64(s.N)) + 0.5)
|
||||
}
|
||||
|
||||
// validateParams checks that all parameters are at or above hardcoded limits.
|
||||
// If not, it exists with an error message.
|
||||
// This makes sure we do not get weak parameters passed through a
|
||||
// rougue gocryptfs.conf.
|
||||
func (s *ScryptKDF) validateParams() bool {
|
||||
minN := 1 << scryptMinLogN
|
||||
if s.N < minN {
|
||||
return false//os.Exit(exitcodes.ScryptParams)
|
||||
}
|
||||
if s.R < scryptMinR {
|
||||
return false//os.Exit(exitcodes.ScryptParams)
|
||||
}
|
||||
if s.P < scryptMinP {
|
||||
return false//os.Exit(exitcodes.ScryptParams)
|
||||
}
|
||||
if len(s.Salt) < scryptMinSaltLen {
|
||||
return false//os.Exit(exitcodes.ScryptParams)
|
||||
}
|
||||
if s.KeyLen < cryptocore.KeyLen {
|
||||
return false//os.Exit(exitcodes.ScryptParams)
|
||||
}
|
||||
return true
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package contentenc
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// bPool is a byte slice pool
|
||||
type bPool struct {
|
||||
sync.Pool
|
||||
sliceLen int
|
||||
}
|
||||
|
||||
func newBPool(sliceLen int) bPool {
|
||||
return bPool{
|
||||
Pool: sync.Pool{
|
||||
New: func() interface{} { return make([]byte, sliceLen) },
|
||||
},
|
||||
sliceLen: sliceLen,
|
||||
}
|
||||
}
|
||||
|
||||
// Put grows the slice "s" to its maximum capacity and puts it into the pool.
|
||||
func (b *bPool) Put(s []byte) {
|
||||
s = s[:cap(s)]
|
||||
if len(s) != b.sliceLen {
|
||||
log.Panicf("wrong len=%d, want=%d", len(s), b.sliceLen)
|
||||
}
|
||||
b.Pool.Put(s)
|
||||
}
|
||||
|
||||
// Get returns a byte slice from the pool.
|
||||
func (b *bPool) Get() (s []byte) {
|
||||
s = b.Pool.Get().([]byte)
|
||||
if len(s) != b.sliceLen {
|
||||
log.Panicf("wrong len=%d, want=%d", len(s), b.sliceLen)
|
||||
}
|
||||
return s
|
||||
}
|
@ -1,335 +0,0 @@
|
||||
// Package contentenc encrypts and decrypts file blocks.
|
||||
package contentenc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"log"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"../../gocryptfs_internal/cryptocore"
|
||||
"../../gocryptfs_internal/stupidgcm"
|
||||
)
|
||||
|
||||
// NonceMode determines how nonces are created.
|
||||
type NonceMode int
|
||||
|
||||
const (
|
||||
//value from FUSE doc
|
||||
MAX_KERNEL_WRITE = 128 * 1024
|
||||
|
||||
|
||||
// DefaultBS is the default plaintext block size
|
||||
DefaultBS = 4096
|
||||
// DefaultIVBits is the default length of IV, in bits.
|
||||
// We always use 128-bit IVs for file content, but the
|
||||
// master key in the config file is encrypted with a 96-bit IV for
|
||||
// gocryptfs v1.2 and earlier. v1.3 switched to 128 bit.
|
||||
DefaultIVBits = 128
|
||||
|
||||
_ = iota // skip zero
|
||||
// RandomNonce chooses a random nonce.
|
||||
RandomNonce NonceMode = iota
|
||||
// ReverseDeterministicNonce chooses a deterministic nonce, suitable for
|
||||
// use in reverse mode.
|
||||
ReverseDeterministicNonce NonceMode = iota
|
||||
// ExternalNonce derives a nonce from external sources.
|
||||
ExternalNonce NonceMode = iota
|
||||
)
|
||||
|
||||
// ContentEnc is used to encipher and decipher file content.
|
||||
type ContentEnc struct {
|
||||
// Cryptographic primitives
|
||||
cryptoCore *cryptocore.CryptoCore
|
||||
// Plaintext block size
|
||||
plainBS uint64
|
||||
// Ciphertext block size
|
||||
cipherBS uint64
|
||||
// All-zero block of size cipherBS, for fast compares
|
||||
allZeroBlock []byte
|
||||
// All-zero block of size IVBitLen/8, for fast compares
|
||||
allZeroNonce []byte
|
||||
// Force decode even if integrity check fails (openSSL only)
|
||||
forceDecode bool
|
||||
|
||||
// Ciphertext block "sync.Pool" pool. Always returns cipherBS-sized byte
|
||||
// slices (usually 4128 bytes).
|
||||
cBlockPool bPool
|
||||
// Plaintext block pool. Always returns plainBS-sized byte slices
|
||||
// (usually 4096 bytes).
|
||||
pBlockPool bPool
|
||||
// Ciphertext request data pool. Always returns byte slices of size
|
||||
// fuse.MAX_KERNEL_WRITE + encryption overhead.
|
||||
// Used by Read() to temporarily store the ciphertext as it is read from
|
||||
// disk.
|
||||
CReqPool bPool
|
||||
// Plaintext request data pool. Slice have size fuse.MAX_KERNEL_WRITE.
|
||||
PReqPool bPool
|
||||
}
|
||||
|
||||
// New returns an initialized ContentEnc instance.
|
||||
func New(cc *cryptocore.CryptoCore, plainBS uint64, forceDecode bool) *ContentEnc {
|
||||
if MAX_KERNEL_WRITE%plainBS == 0 {
|
||||
cipherBS := plainBS + uint64(cc.IVLen) + cryptocore.AuthTagLen
|
||||
// Take IV and GHASH overhead into account.
|
||||
cReqSize := int(MAX_KERNEL_WRITE / plainBS * cipherBS)
|
||||
// Unaligned reads (happens during fsck, could also happen with O_DIRECT?)
|
||||
// touch one additional ciphertext and plaintext block. Reserve space for the
|
||||
// extra block.
|
||||
cReqSize += int(cipherBS)
|
||||
pReqSize := MAX_KERNEL_WRITE + int(plainBS)
|
||||
c := &ContentEnc{
|
||||
cryptoCore: cc,
|
||||
plainBS: plainBS,
|
||||
cipherBS: cipherBS,
|
||||
allZeroBlock: make([]byte, cipherBS),
|
||||
allZeroNonce: make([]byte, cc.IVLen),
|
||||
forceDecode: forceDecode,
|
||||
cBlockPool: newBPool(int(cipherBS)),
|
||||
CReqPool: newBPool(cReqSize),
|
||||
pBlockPool: newBPool(int(plainBS)),
|
||||
PReqPool: newBPool(pReqSize),
|
||||
}
|
||||
return c
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// PlainBS returns the plaintext block size
|
||||
func (be *ContentEnc) PlainBS() uint64 {
|
||||
return be.plainBS
|
||||
}
|
||||
|
||||
// CipherBS returns the ciphertext block size
|
||||
func (be *ContentEnc) CipherBS() uint64 {
|
||||
return be.cipherBS
|
||||
}
|
||||
|
||||
// DecryptBlocks decrypts a number of blocks
|
||||
func (be *ContentEnc) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, fileID []byte) ([]byte, error) {
|
||||
cBuf := bytes.NewBuffer(ciphertext)
|
||||
var err error
|
||||
pBuf := bytes.NewBuffer(be.PReqPool.Get()[:0])
|
||||
blockNo := firstBlockNo
|
||||
for cBuf.Len() > 0 {
|
||||
cBlock := cBuf.Next(int(be.cipherBS))
|
||||
var pBlock []byte
|
||||
pBlock, err = be.DecryptBlock(cBlock, blockNo, fileID)
|
||||
if err != nil {
|
||||
if !be.forceDecode || err != stupidgcm.ErrAuth {
|
||||
break
|
||||
}
|
||||
}
|
||||
pBuf.Write(pBlock)
|
||||
be.pBlockPool.Put(pBlock)
|
||||
blockNo++
|
||||
}
|
||||
return pBuf.Bytes(), err
|
||||
}
|
||||
|
||||
// concatAD concatenates the block number and the file ID to a byte blob
|
||||
// that can be passed to AES-GCM as associated data (AD).
|
||||
// Result is: aData = [blockNo.bigEndian fileID].
|
||||
func concatAD(blockNo uint64, fileID []byte) (aData []byte) {
|
||||
if fileID != nil && len(fileID) != headerIDLen {
|
||||
// fileID is nil when decrypting the master key from the config file,
|
||||
// and for symlinks and xattrs.
|
||||
log.Panicf("wrong fileID length: %d", len(fileID))
|
||||
}
|
||||
const lenUint64 = 8
|
||||
// Preallocate space to save an allocation in append()
|
||||
aData = make([]byte, lenUint64, lenUint64+headerIDLen)
|
||||
binary.BigEndian.PutUint64(aData, blockNo)
|
||||
aData = append(aData, fileID...)
|
||||
return aData
|
||||
}
|
||||
|
||||
// DecryptBlock - Verify and decrypt GCM block
|
||||
//
|
||||
// Corner case: A full-sized block of all-zero ciphertext bytes is translated
|
||||
// to an all-zero plaintext block, i.e. file hole passthrough.
|
||||
func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []byte) ([]byte, error) {
|
||||
// Empty block?
|
||||
if len(ciphertext) == 0 {
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
// All-zero block?
|
||||
if bytes.Equal(ciphertext, be.allZeroBlock) {
|
||||
return make([]byte, be.plainBS), nil
|
||||
}
|
||||
|
||||
if len(ciphertext) < be.cryptoCore.IVLen {
|
||||
return nil, errors.New("Block is too short")
|
||||
}
|
||||
|
||||
// Extract nonce
|
||||
nonce := ciphertext[:be.cryptoCore.IVLen]
|
||||
if bytes.Equal(nonce, be.allZeroNonce) {
|
||||
// Bug in tmpfs?
|
||||
// https://github.com/rfjakob/gocryptfs/issues/56
|
||||
// http://www.spinics.net/lists/kernel/msg2370127.html
|
||||
return nil, errors.New("all-zero nonce")
|
||||
}
|
||||
ciphertext = ciphertext[be.cryptoCore.IVLen:]
|
||||
|
||||
// Decrypt
|
||||
plaintext := be.pBlockPool.Get()
|
||||
plaintext = plaintext[:0]
|
||||
aData := concatAD(blockNo, fileID)
|
||||
plaintext, err := be.cryptoCore.AEADCipher.Open(plaintext, nonce, ciphertext, aData)
|
||||
if err != nil {
|
||||
if be.forceDecode && err == stupidgcm.ErrAuth {
|
||||
return plaintext, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// At some point, splitting the ciphertext into more groups will not improve
|
||||
// performance, as spawning goroutines comes at a cost.
|
||||
// 2 seems to work ok for now.
|
||||
const encryptMaxSplit = 2
|
||||
|
||||
// encryptBlocksParallel splits the plaintext into parts and encrypts them
|
||||
// in parallel.
|
||||
func (be *ContentEnc) encryptBlocksParallel(plaintextBlocks [][]byte, ciphertextBlocks [][]byte, firstBlockNo uint64, fileID []byte) {
|
||||
ncpu := runtime.NumCPU()
|
||||
if ncpu > encryptMaxSplit {
|
||||
ncpu = encryptMaxSplit
|
||||
}
|
||||
groupSize := len(plaintextBlocks) / ncpu
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < ncpu; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
low := i * groupSize
|
||||
high := (i + 1) * groupSize
|
||||
if i == ncpu-1 {
|
||||
// Last part picks up any left-over blocks
|
||||
//
|
||||
// The last part could run in the original goroutine, but
|
||||
// doing that complicates the code, and, surprisingly,
|
||||
// incurs a 1 % performance penalty.
|
||||
high = len(plaintextBlocks)
|
||||
}
|
||||
be.doEncryptBlocks(plaintextBlocks[low:high], ciphertextBlocks[low:high], firstBlockNo+uint64(low), fileID)
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// EncryptBlocks is like EncryptBlock but takes multiple plaintext blocks.
|
||||
// Returns a byte slice from CReqPool - so don't forget to return it
|
||||
// to the pool.
|
||||
func (be *ContentEnc) EncryptBlocks(plaintextBlocks [][]byte, firstBlockNo uint64, fileID []byte) []byte {
|
||||
ciphertextBlocks := make([][]byte, len(plaintextBlocks))
|
||||
// For large writes, we parallelize encryption.
|
||||
if len(plaintextBlocks) >= 32 && runtime.NumCPU() >= 2 {
|
||||
be.encryptBlocksParallel(plaintextBlocks, ciphertextBlocks, firstBlockNo, fileID)
|
||||
} else {
|
||||
be.doEncryptBlocks(plaintextBlocks, ciphertextBlocks, firstBlockNo, fileID)
|
||||
}
|
||||
// Concatenate ciphertext into a single byte array.
|
||||
tmp := be.CReqPool.Get()
|
||||
out := bytes.NewBuffer(tmp[:0])
|
||||
for _, v := range ciphertextBlocks {
|
||||
out.Write(v)
|
||||
// Return the memory to cBlockPool
|
||||
be.cBlockPool.Put(v)
|
||||
}
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
// doEncryptBlocks is called by EncryptBlocks to do the actual encryption work
|
||||
func (be *ContentEnc) doEncryptBlocks(in [][]byte, out [][]byte, firstBlockNo uint64, fileID []byte) {
|
||||
for i, v := range in {
|
||||
out[i] = be.EncryptBlock(v, firstBlockNo+uint64(i), fileID)
|
||||
}
|
||||
}
|
||||
|
||||
// EncryptBlock - Encrypt plaintext using a random nonce.
|
||||
// blockNo and fileID are used as associated data.
|
||||
// The output is nonce + ciphertext + tag.
|
||||
func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) []byte {
|
||||
// Get a fresh random nonce
|
||||
nonce := be.cryptoCore.IVGenerator.Get()
|
||||
return be.doEncryptBlock(plaintext, blockNo, fileID, nonce)
|
||||
}
|
||||
|
||||
// EncryptBlockNonce - Encrypt plaintext using a nonce chosen by the caller.
|
||||
// blockNo and fileID are used as associated data.
|
||||
// The output is nonce + ciphertext + tag.
|
||||
// This function can only be used in SIV mode.
|
||||
func (be *ContentEnc) EncryptBlockNonce(plaintext []byte, blockNo uint64, fileID []byte, nonce []byte) []byte {
|
||||
if be.cryptoCore.AEADBackend != cryptocore.BackendAESSIV {
|
||||
log.Panic("deterministic nonces are only secure in SIV mode")
|
||||
}
|
||||
return be.doEncryptBlock(plaintext, blockNo, fileID, nonce)
|
||||
}
|
||||
|
||||
// doEncryptBlock is the backend for EncryptBlock and EncryptBlockNonce.
|
||||
// blockNo and fileID are used as associated data.
|
||||
// The output is nonce + ciphertext + tag.
|
||||
func (be *ContentEnc) doEncryptBlock(plaintext []byte, blockNo uint64, fileID []byte, nonce []byte) []byte {
|
||||
// Empty block?
|
||||
if len(plaintext) == 0 {
|
||||
return plaintext
|
||||
}
|
||||
if len(nonce) != be.cryptoCore.IVLen {
|
||||
log.Panic("wrong nonce length")
|
||||
}
|
||||
// Block is authenticated with block number and file ID
|
||||
aData := concatAD(blockNo, fileID)
|
||||
// Get a cipherBS-sized block of memory, copy the nonce into it and truncate to
|
||||
// nonce length
|
||||
cBlock := be.cBlockPool.Get()
|
||||
copy(cBlock, nonce)
|
||||
cBlock = cBlock[0:len(nonce)]
|
||||
// Encrypt plaintext and append to nonce
|
||||
ciphertext := be.cryptoCore.AEADCipher.Seal(cBlock, nonce, plaintext, aData)
|
||||
overhead := int(be.cipherBS - be.plainBS)
|
||||
if len(plaintext)+overhead != len(ciphertext) {
|
||||
log.Panicf("unexpected ciphertext length: plaintext=%d, overhead=%d, ciphertext=%d",
|
||||
len(plaintext), overhead, len(ciphertext))
|
||||
}
|
||||
return ciphertext
|
||||
}
|
||||
|
||||
// MergeBlocks - Merge newData into oldData at offset
|
||||
// New block may be bigger than both newData and oldData
|
||||
func (be *ContentEnc) MergeBlocks(oldData []byte, newData []byte, offset int) []byte {
|
||||
// Fastpath for small-file creation
|
||||
if len(oldData) == 0 && offset == 0 {
|
||||
return newData
|
||||
}
|
||||
|
||||
// Make block of maximum size
|
||||
out := make([]byte, be.plainBS)
|
||||
|
||||
// Copy old and new data into it
|
||||
copy(out, oldData)
|
||||
l := len(newData)
|
||||
copy(out[offset:offset+l], newData)
|
||||
|
||||
// Crop to length
|
||||
outLen := len(oldData)
|
||||
newLen := offset + len(newData)
|
||||
if outLen < newLen {
|
||||
outLen = newLen
|
||||
}
|
||||
return out[0:outLen]
|
||||
}
|
||||
|
||||
// Wipe tries to wipe secret keys from memory by overwriting them with zeros
|
||||
// and/or setting references to nil.
|
||||
func (be *ContentEnc) Wipe() {
|
||||
be.cryptoCore.Wipe()
|
||||
be.cryptoCore = nil
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
package contentenc
|
||||
|
||||
// Per-file header
|
||||
//
|
||||
// Format: [ "Version" uint16 big endian ] [ "Id" 16 random bytes ]
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"../../gocryptfs_internal/cryptocore"
|
||||
)
|
||||
|
||||
const (
|
||||
// CurrentVersion is the current On-Disk-Format version
|
||||
CurrentVersion = 2
|
||||
|
||||
headerVersionLen = 2 // uint16
|
||||
headerIDLen = 16 // 128 bit random file id
|
||||
// HeaderLen is the total header length
|
||||
HeaderLen = headerVersionLen + headerIDLen
|
||||
)
|
||||
|
||||
// FileHeader represents the header stored on each non-empty file.
|
||||
type FileHeader struct {
|
||||
Version uint16
|
||||
ID []byte
|
||||
}
|
||||
|
||||
// Pack - serialize fileHeader object
|
||||
func (h *FileHeader) Pack() []byte {
|
||||
if len(h.ID) != headerIDLen || h.Version != CurrentVersion {
|
||||
log.Panic("FileHeader object not properly initialized")
|
||||
}
|
||||
buf := make([]byte, HeaderLen)
|
||||
binary.BigEndian.PutUint16(buf[0:headerVersionLen], h.Version)
|
||||
copy(buf[headerVersionLen:], h.ID)
|
||||
return buf
|
||||
|
||||
}
|
||||
|
||||
// allZeroFileID is preallocated to quickly check if the data read from disk is all zero
|
||||
var allZeroFileID = make([]byte, headerIDLen)
|
||||
var allZeroHeader = make([]byte, HeaderLen)
|
||||
|
||||
// ParseHeader - parse "buf" into fileHeader object
|
||||
func ParseHeader(buf []byte) (*FileHeader, error) {
|
||||
if len(buf) != HeaderLen {
|
||||
return nil, fmt.Errorf("ParseHeader: invalid length, want=%d have=%d", HeaderLen, len(buf))
|
||||
}
|
||||
if bytes.Equal(buf, allZeroHeader) {
|
||||
return nil, fmt.Errorf("ParseHeader: header is all-zero. Header hexdump: %s", hex.EncodeToString(buf))
|
||||
}
|
||||
var h FileHeader
|
||||
h.Version = binary.BigEndian.Uint16(buf[0:headerVersionLen])
|
||||
if h.Version != CurrentVersion {
|
||||
return nil, fmt.Errorf("ParseHeader: invalid version, want=%d have=%d. Header hexdump: %s",
|
||||
CurrentVersion, h.Version, hex.EncodeToString(buf))
|
||||
}
|
||||
h.ID = buf[headerVersionLen:]
|
||||
if bytes.Equal(h.ID, allZeroFileID) {
|
||||
return nil, fmt.Errorf("ParseHeader: file id is all-zero. Header hexdump: %s",
|
||||
hex.EncodeToString(buf))
|
||||
}
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
// RandomHeader - create new fileHeader object with random Id
|
||||
func RandomHeader() *FileHeader {
|
||||
var h FileHeader
|
||||
h.Version = CurrentVersion
|
||||
h.ID = cryptocore.RandBytes(headerIDLen)
|
||||
return &h
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package contentenc
|
||||
|
||||
// IntraBlock identifies a part of a file block
|
||||
type IntraBlock struct {
|
||||
// BlockNo is the block number in the file
|
||||
BlockNo uint64
|
||||
// Skip is an offset into the block payload
|
||||
// In forward mode: block plaintext
|
||||
// In reverse mode: offset into block ciphertext. Takes the header into
|
||||
// account.
|
||||
Skip uint64
|
||||
// Length of payload data in this block
|
||||
// In forward mode: length of the plaintext
|
||||
// In reverse mode: length of the ciphertext. Takes header and trailer into
|
||||
// account.
|
||||
Length uint64
|
||||
fs *ContentEnc
|
||||
}
|
||||
|
||||
// IsPartial - is the block partial? This means we have to do read-modify-write.
|
||||
func (ib *IntraBlock) IsPartial() bool {
|
||||
if ib.Skip > 0 || ib.Length < ib.fs.plainBS {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// BlockCipherOff returns the ciphertext offset corresponding to BlockNo
|
||||
func (ib *IntraBlock) BlockCipherOff() (offset uint64) {
|
||||
return ib.fs.BlockNoToCipherOff(ib.BlockNo)
|
||||
}
|
||||
|
||||
// BlockPlainOff returns the plaintext offset corresponding to BlockNo
|
||||
func (ib *IntraBlock) BlockPlainOff() (offset uint64) {
|
||||
return ib.fs.BlockNoToPlainOff(ib.BlockNo)
|
||||
}
|
||||
|
||||
// CropBlock - crop a potentially larger plaintext block down to the relevant part
|
||||
func (ib *IntraBlock) CropBlock(d []byte) []byte {
|
||||
lenHave := len(d)
|
||||
lenWant := int(ib.Skip + ib.Length)
|
||||
if lenHave < lenWant {
|
||||
return d[ib.Skip:lenHave]
|
||||
}
|
||||
return d[ib.Skip:lenWant]
|
||||
}
|
||||
|
||||
// JointCiphertextRange is the ciphertext range corresponding to the sum of all
|
||||
// "blocks" (complete blocks)
|
||||
func (ib *IntraBlock) JointCiphertextRange(blocks []IntraBlock) (offset uint64, length uint64) {
|
||||
firstBlock := blocks[0]
|
||||
lastBlock := blocks[len(blocks)-1]
|
||||
|
||||
offset = ib.fs.BlockNoToCipherOff(firstBlock.BlockNo)
|
||||
offsetLast := ib.fs.BlockNoToCipherOff(lastBlock.BlockNo)
|
||||
length = offsetLast + ib.fs.cipherBS - offset
|
||||
|
||||
return offset, length
|
||||
}
|
||||
|
||||
// JointPlaintextRange is the plaintext range corresponding to the sum of all
|
||||
// "blocks" (complete blocks)
|
||||
func JointPlaintextRange(blocks []IntraBlock) (offset uint64, length uint64) {
|
||||
firstBlock := blocks[0]
|
||||
lastBlock := blocks[len(blocks)-1]
|
||||
|
||||
offset = firstBlock.BlockPlainOff()
|
||||
length = lastBlock.BlockPlainOff() + lastBlock.fs.PlainBS() - offset
|
||||
|
||||
return offset, length
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
package contentenc
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
// Contentenc methods that translate offsets between ciphertext and plaintext
|
||||
|
||||
// PlainOffToBlockNo converts a plaintext offset to the ciphertext block number.
|
||||
func (be *ContentEnc) PlainOffToBlockNo(plainOffset uint64) uint64 {
|
||||
return plainOffset / be.plainBS
|
||||
}
|
||||
|
||||
// CipherOffToBlockNo converts the ciphertext offset to the plaintext block number.
|
||||
func (be *ContentEnc) CipherOffToBlockNo(cipherOffset uint64) uint64 {
|
||||
if cipherOffset < HeaderLen {
|
||||
log.Panicf("BUG: offset %d is inside the file header", cipherOffset)
|
||||
}
|
||||
return (cipherOffset - HeaderLen) / be.cipherBS
|
||||
}
|
||||
|
||||
// BlockNoToCipherOff gets the ciphertext offset of block "blockNo"
|
||||
func (be *ContentEnc) BlockNoToCipherOff(blockNo uint64) uint64 {
|
||||
return HeaderLen + blockNo*be.cipherBS
|
||||
}
|
||||
|
||||
// BlockNoToPlainOff gets the plaintext offset of block "blockNo"
|
||||
func (be *ContentEnc) BlockNoToPlainOff(blockNo uint64) uint64 {
|
||||
return blockNo * be.plainBS
|
||||
}
|
||||
|
||||
// CipherSizeToPlainSize calculates the plaintext size from a ciphertext size
|
||||
func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
||||
// Zero-sized files stay zero-sized
|
||||
if cipherSize == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if cipherSize == HeaderLen {
|
||||
// This can happen between createHeader() and Write() and is harmless.
|
||||
return 0
|
||||
}
|
||||
|
||||
if cipherSize < HeaderLen {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Block number at last byte
|
||||
blockNo := be.CipherOffToBlockNo(cipherSize - 1)
|
||||
blockCount := blockNo + 1
|
||||
|
||||
overhead := be.BlockOverhead()*blockCount + HeaderLen
|
||||
|
||||
if overhead > cipherSize {
|
||||
return 0
|
||||
}
|
||||
|
||||
return cipherSize - overhead
|
||||
}
|
||||
|
||||
// PlainSizeToCipherSize calculates the ciphertext size from a plaintext size
|
||||
func (be *ContentEnc) PlainSizeToCipherSize(plainSize uint64) uint64 {
|
||||
// Zero-sized files stay zero-sized
|
||||
if plainSize == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Block number at last byte
|
||||
blockNo := be.PlainOffToBlockNo(plainSize - 1)
|
||||
blockCount := blockNo + 1
|
||||
|
||||
overhead := be.BlockOverhead()*blockCount + HeaderLen
|
||||
|
||||
return plainSize + overhead
|
||||
}
|
||||
|
||||
// ExplodePlainRange splits a plaintext byte range into (possibly partial) blocks
|
||||
// Returns an empty slice if length == 0.
|
||||
func (be *ContentEnc) ExplodePlainRange(offset uint64, length uint64) []IntraBlock {
|
||||
var blocks []IntraBlock
|
||||
var nextBlock IntraBlock
|
||||
nextBlock.fs = be
|
||||
|
||||
for length > 0 {
|
||||
nextBlock.BlockNo = be.PlainOffToBlockNo(offset)
|
||||
nextBlock.Skip = offset - be.BlockNoToPlainOff(nextBlock.BlockNo)
|
||||
|
||||
// Minimum of remaining plaintext data and remaining space in the block
|
||||
nextBlock.Length = MinUint64(length, be.plainBS-nextBlock.Skip)
|
||||
|
||||
blocks = append(blocks, nextBlock)
|
||||
offset += nextBlock.Length
|
||||
length -= nextBlock.Length
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
// ExplodeCipherRange splits a ciphertext byte range into (possibly partial)
|
||||
// blocks This is used in reverse mode when reading files
|
||||
func (be *ContentEnc) ExplodeCipherRange(offset uint64, length uint64) []IntraBlock {
|
||||
var blocks []IntraBlock
|
||||
var nextBlock IntraBlock
|
||||
nextBlock.fs = be
|
||||
|
||||
for length > 0 {
|
||||
nextBlock.BlockNo = be.CipherOffToBlockNo(offset)
|
||||
nextBlock.Skip = offset - be.BlockNoToCipherOff(nextBlock.BlockNo)
|
||||
|
||||
// This block can carry up to "maxLen" payload bytes
|
||||
maxLen := be.cipherBS - nextBlock.Skip
|
||||
nextBlock.Length = maxLen
|
||||
// But if the user requested less, we truncate the block to "length".
|
||||
if length < maxLen {
|
||||
nextBlock.Length = length
|
||||
}
|
||||
|
||||
blocks = append(blocks, nextBlock)
|
||||
offset += nextBlock.Length
|
||||
length -= nextBlock.Length
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
// BlockOverhead returns the per-block overhead.
|
||||
func (be *ContentEnc) BlockOverhead() uint64 {
|
||||
return be.cipherBS - be.plainBS
|
||||
}
|
||||
|
||||
// MinUint64 returns the minimum of two uint64 values.
|
||||
func MinUint64(x uint64, y uint64) uint64 {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package syscallcompat
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var chdirMutex sync.Mutex
|
||||
|
||||
// emulateMknodat emulates the syscall for platforms that don't have it
|
||||
// in the kernel (darwin).
|
||||
func emulateMknodat(dirfd int, path string, mode uint32, dev int) error {
|
||||
if !filepath.IsAbs(path) {
|
||||
chdirMutex.Lock()
|
||||
defer chdirMutex.Unlock()
|
||||
cwd, err := syscall.Open(".", syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(cwd)
|
||||
err = syscall.Fchdir(dirfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Fchdir(cwd)
|
||||
}
|
||||
return syscall.Mknod(path, mode, dev)
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
// +build linux
|
||||
|
||||
package syscallcompat
|
||||
|
||||
// Other implementations of getdents in Go:
|
||||
// https://github.com/ericlagergren/go-gnulib/blob/cb7a6e136427e242099b2c29d661016c19458801/dirent/getdents_unix.go
|
||||
// https://github.com/golang/tools/blob/5831d16d18029819d39f99bdc2060b8eff410b6b/imports/fastwalk_unix.go
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"bytes"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const sizeofDirent = int(unsafe.Sizeof(unix.Dirent{}))
|
||||
|
||||
// maxReclen sanity check: Reclen should never be larger than this.
|
||||
// Due to padding between entries, it is 280 even on 32-bit architectures.
|
||||
// See https://github.com/rfjakob/gocryptfs/issues/197 for details.
|
||||
const maxReclen = 280
|
||||
|
||||
type DirEntry struct {
|
||||
Name string
|
||||
Mode uint32
|
||||
}
|
||||
|
||||
// getdents wraps unix.Getdents and converts the result to []fuse.DirEntry.
|
||||
func getdents(fd int) ([]DirEntry, error) {
|
||||
// Collect syscall result in smartBuf.
|
||||
// "bytes.Buffer" is smart about expanding the capacity and avoids the
|
||||
// exponential runtime of simple append().
|
||||
var smartBuf bytes.Buffer
|
||||
tmp := make([]byte, 10000)
|
||||
for {
|
||||
n, err := unix.Getdents(fd, tmp)
|
||||
// unix.Getdents has been observed to return EINTR on cifs mounts
|
||||
if err == unix.EINTR {
|
||||
if n > 0 {
|
||||
smartBuf.Write(tmp[:n])
|
||||
}
|
||||
continue
|
||||
} else if err != nil {
|
||||
if smartBuf.Len() > 0 {
|
||||
return nil, syscall.EIO
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
smartBuf.Write(tmp[:n])
|
||||
}
|
||||
// Make sure we have at least Sizeof(Dirent) of zeros after the last
|
||||
// entry. This prevents a cast to Dirent from reading past the buffer.
|
||||
smartBuf.Grow(sizeofDirent)
|
||||
buf := smartBuf.Bytes()
|
||||
// Count the number of directory entries in the buffer so we can allocate
|
||||
// a fuse.DirEntry slice of the correct size at once.
|
||||
var numEntries, offset int
|
||||
for offset < len(buf) {
|
||||
s := *(*unix.Dirent)(unsafe.Pointer(&buf[offset]))
|
||||
if s.Reclen == 0 {
|
||||
// EBADR = Invalid request descriptor
|
||||
return nil, syscall.EBADR
|
||||
}
|
||||
if int(s.Reclen) > maxReclen {
|
||||
return nil, syscall.EBADR
|
||||
}
|
||||
offset += int(s.Reclen)
|
||||
numEntries++
|
||||
}
|
||||
// Parse the buffer into entries.
|
||||
// Note: syscall.ParseDirent() only returns the names,
|
||||
// we want all the data, so we have to implement
|
||||
// it on our own.
|
||||
entries := make([]DirEntry, 0, numEntries)
|
||||
offset = 0
|
||||
for offset < len(buf) {
|
||||
s := *(*unix.Dirent)(unsafe.Pointer(&buf[offset]))
|
||||
name, err := getdentsName(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset += int(s.Reclen)
|
||||
if name == "." || name == ".." {
|
||||
// os.File.Readdir() drops "." and "..". Let's be compatible.
|
||||
continue
|
||||
}
|
||||
mode, err := convertDType(fd, name, s.Type)
|
||||
if err != nil {
|
||||
// The uint32file may have been deleted in the meantime. Just skip it
|
||||
// and go on.
|
||||
continue
|
||||
}
|
||||
entries = append(entries, DirEntry{
|
||||
Name: name,
|
||||
Mode: mode,
|
||||
})
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// getdentsName extracts the filename from a Dirent struct and returns it as
|
||||
// a Go string.
|
||||
func getdentsName(s unix.Dirent) (string, error) {
|
||||
// After the loop, l contains the index of the first '\0'.
|
||||
l := 0
|
||||
for l = range s.Name {
|
||||
if s.Name[l] == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if l < 1 {
|
||||
// EBADR = Invalid request descriptor
|
||||
return "", syscall.EBADR
|
||||
}
|
||||
// Copy to byte slice.
|
||||
name := make([]byte, l)
|
||||
for i := range name {
|
||||
name[i] = byte(s.Name[i])
|
||||
}
|
||||
return string(name), nil
|
||||
}
|
||||
|
||||
var dtUnknownWarnOnce sync.Once
|
||||
|
||||
func dtUnknownWarn(dirfd int) {
|
||||
const XFS_SUPER_MAGIC = 0x58465342 // From man 2 statfs
|
||||
var buf syscall.Statfs_t
|
||||
syscall.Fstatfs(dirfd, &buf)
|
||||
}
|
||||
|
||||
// convertDType converts a Dirent.Type to at Stat_t.Mode value.
|
||||
func convertDType(dirfd int, name string, dtype uint8) (uint32, error) {
|
||||
if dtype != syscall.DT_UNKNOWN {
|
||||
// Shift up by four octal digits = 12 bits
|
||||
return uint32(dtype) << 12, nil
|
||||
}
|
||||
// DT_UNKNOWN: we have to call stat()
|
||||
dtUnknownWarnOnce.Do(func() { dtUnknownWarn(dirfd) })
|
||||
var st unix.Stat_t
|
||||
err := Fstatat(dirfd, name, &st, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// The S_IFMT bit mask extracts the file type from the mode.
|
||||
return st.Mode & syscall.S_IFMT, nil
|
||||
}
|
@ -1 +0,0 @@
|
||||
package syscallcompat
|
@ -1,21 +0,0 @@
|
||||
package syscallcompat
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// IsENOSPC tries to find out if "err" is a (potentially wrapped) ENOSPC error.
|
||||
func IsENOSPC(err error) bool {
|
||||
// syscallcompat.EnospcPrealloc returns the naked syscall error
|
||||
if err == syscall.ENOSPC {
|
||||
return true
|
||||
}
|
||||
// os.File.WriteAt returns &PathError
|
||||
if err2, ok := err.(*os.PathError); ok {
|
||||
if err2.Err == syscall.ENOSPC {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package syscallcompat
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// OpenDirNofollow opens the dir at "relPath" in a way that is secure against
|
||||
// symlink attacks. Symlinks that are part of "relPath" are never followed.
|
||||
// This function is implemented by walking the directory tree, starting at
|
||||
// "baseDir", using the Openat syscall with the O_NOFOLLOW flag.
|
||||
// Symlinks that are part of the "baseDir" path are followed.
|
||||
func OpenDirNofollow(baseDir string, relPath string) (fd int, err error) {
|
||||
if !filepath.IsAbs(baseDir) {
|
||||
return -1, syscall.EINVAL
|
||||
}
|
||||
if filepath.IsAbs(relPath) {
|
||||
return -1, syscall.EINVAL
|
||||
}
|
||||
// Open the base dir (following symlinks)
|
||||
dirfd, err := syscall.Open(baseDir, syscall.O_DIRECTORY|O_PATH, 0)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
// Caller wanted to open baseDir itself?
|
||||
if relPath == "" {
|
||||
return dirfd, nil
|
||||
}
|
||||
// Split the path into components
|
||||
parts := strings.Split(relPath, "/")
|
||||
// Walk the directory tree
|
||||
var dirfd2 int
|
||||
for _, name := range parts {
|
||||
dirfd2, err = Openat(dirfd, name, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|O_PATH, 0)
|
||||
syscall.Close(dirfd)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
dirfd = dirfd2
|
||||
}
|
||||
// Return fd to final directory
|
||||
return dirfd, nil
|
||||
}
|
@ -1,221 +0,0 @@
|
||||
package syscallcompat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// PATH_MAX is the maximum allowed path length on Linux.
|
||||
// It is not defined on Darwin, so we use the Linux value.
|
||||
const PATH_MAX = 4096
|
||||
|
||||
// Readlinkat is a convenience wrapper around unix.Readlinkat() that takes
|
||||
// care of buffer sizing. Implemented like os.Readlink().
|
||||
func Readlinkat(dirfd int, path string) (string, error) {
|
||||
// Allocate the buffer exponentially like os.Readlink does.
|
||||
for bufsz := 128; ; bufsz *= 2 {
|
||||
buf := make([]byte, bufsz)
|
||||
n, err := unix.Readlinkat(dirfd, path, buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if n < bufsz {
|
||||
return string(buf[0:n]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Faccessat exists both in Linux and in MacOS 10.10+, but the Linux version
|
||||
// DOES NOT support any flags. Emulate AT_SYMLINK_NOFOLLOW like glibc does.
|
||||
func Faccessat(dirfd int, path string, mode uint32) error {
|
||||
var st unix.Stat_t
|
||||
err := Fstatat(dirfd, path, &st, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if st.Mode&syscall.S_IFMT == syscall.S_IFLNK {
|
||||
// Pretend that a symlink is always accessible
|
||||
return nil
|
||||
}
|
||||
return unix.Faccessat(dirfd, path, mode, 0)
|
||||
}
|
||||
|
||||
// Openat wraps the Openat syscall.
|
||||
func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
|
||||
/*if flags&syscall.O_CREAT != 0 {
|
||||
// O_CREAT should be used with O_EXCL. O_NOFOLLOW has no effect with O_EXCL.
|
||||
if flags&syscall.O_EXCL == 0 {
|
||||
flags |= syscall.O_EXCL
|
||||
}
|
||||
} else {
|
||||
// If O_CREAT is not used, we should use O_NOFOLLOW
|
||||
if flags&syscall.O_NOFOLLOW == 0 {
|
||||
flags |= syscall.O_NOFOLLOW
|
||||
}
|
||||
}*/
|
||||
if flags&syscall.O_CREAT == 0 {
|
||||
// If O_CREAT is not used, we should use O_NOFOLLOW
|
||||
if flags&syscall.O_NOFOLLOW == 0 {
|
||||
flags |= syscall.O_NOFOLLOW
|
||||
}
|
||||
}
|
||||
return unix.Openat(dirfd, path, flags, mode)
|
||||
}
|
||||
|
||||
// Renameat wraps the Renameat syscall.
|
||||
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error) {
|
||||
return unix.Renameat(olddirfd, oldpath, newdirfd, newpath)
|
||||
}
|
||||
|
||||
// Unlinkat syscall.
|
||||
func Unlinkat(dirfd int, path string, flags int) (err error) {
|
||||
return unix.Unlinkat(dirfd, path, flags)
|
||||
}
|
||||
|
||||
// Fchownat syscall.
|
||||
func Fchownat(dirfd int, path string, uid int, gid int, flags int) (err error) {
|
||||
// Why would we ever want to call this without AT_SYMLINK_NOFOLLOW?
|
||||
if flags&unix.AT_SYMLINK_NOFOLLOW == 0 {
|
||||
flags |= unix.AT_SYMLINK_NOFOLLOW
|
||||
}
|
||||
return unix.Fchownat(dirfd, path, uid, gid, flags)
|
||||
}
|
||||
|
||||
// Linkat exists both in Linux and in MacOS 10.10+.
|
||||
func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flags int) (err error) {
|
||||
return unix.Linkat(olddirfd, oldpath, newdirfd, newpath, flags)
|
||||
}
|
||||
|
||||
// Symlinkat syscall.
|
||||
func Symlinkat(oldpath string, newdirfd int, newpath string) (err error) {
|
||||
return unix.Symlinkat(oldpath, newdirfd, newpath)
|
||||
}
|
||||
|
||||
// Mkdirat syscall.
|
||||
func Mkdirat(dirfd int, path string, mode uint32) (err error) {
|
||||
return unix.Mkdirat(dirfd, path, mode)
|
||||
}
|
||||
|
||||
// Fstatat syscall.
|
||||
func Fstatat(dirfd int, path string, stat *unix.Stat_t, flags int) (err error) {
|
||||
// Why would we ever want to call this without AT_SYMLINK_NOFOLLOW?
|
||||
if flags&unix.AT_SYMLINK_NOFOLLOW == 0 {
|
||||
flags |= unix.AT_SYMLINK_NOFOLLOW
|
||||
}
|
||||
return unix.Fstatat(dirfd, path, stat, flags)
|
||||
}
|
||||
|
||||
const XATTR_SIZE_MAX = 65536
|
||||
|
||||
// Make the buffer 1kB bigger so we can detect overflows
|
||||
const XATTR_BUFSZ = XATTR_SIZE_MAX + 1024
|
||||
|
||||
// Fgetxattr is a wrapper around unix.Fgetxattr that handles the buffer sizing.
|
||||
func Fgetxattr(fd int, attr string) (val []byte, err error) {
|
||||
// If the buffer is too small to fit the value, Linux and MacOS react
|
||||
// differently:
|
||||
// Linux: returns an ERANGE error and "-1" bytes.
|
||||
// MacOS: truncates the value and returns "size" bytes.
|
||||
//
|
||||
// We choose the simple approach of buffer that is bigger than the limit on
|
||||
// Linux, and return an error for everything that is bigger (which can
|
||||
// only happen on MacOS).
|
||||
//
|
||||
// See https://github.com/pkg/xattr for a smarter solution.
|
||||
// TODO: smarter buffer sizing?
|
||||
buf := make([]byte, XATTR_BUFSZ)
|
||||
sz, err := unix.Fgetxattr(fd, attr, buf)
|
||||
if err == syscall.ERANGE {
|
||||
// Do NOT return ERANGE - the user might retry ad inifinitum!
|
||||
return nil, syscall.EOVERFLOW
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sz >= XATTR_SIZE_MAX {
|
||||
return nil, syscall.EOVERFLOW
|
||||
}
|
||||
// Copy only the actually used bytes to a new (smaller) buffer
|
||||
// so "buf" never leaves the function and can be allocated on the stack.
|
||||
val = make([]byte, sz)
|
||||
copy(val, buf)
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// Lgetxattr is a wrapper around unix.Lgetxattr that handles the buffer sizing.
|
||||
func Lgetxattr(path string, attr string) (val []byte, err error) {
|
||||
// See the buffer sizing comments in Fgetxattr.
|
||||
// TODO: smarter buffer sizing?
|
||||
buf := make([]byte, XATTR_BUFSZ)
|
||||
sz, err := unix.Lgetxattr(path, attr, buf)
|
||||
if err == syscall.ERANGE {
|
||||
// Do NOT return ERANGE - the user might retry ad inifinitum!
|
||||
return nil, syscall.EOVERFLOW
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sz >= XATTR_SIZE_MAX {
|
||||
return nil, syscall.EOVERFLOW
|
||||
}
|
||||
// Copy only the actually used bytes to a new (smaller) buffer
|
||||
// so "buf" never leaves the function and can be allocated on the stack.
|
||||
val = make([]byte, sz)
|
||||
copy(val, buf)
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// Flistxattr is a wrapper for unix.Flistxattr that handles buffer sizing and
|
||||
// parsing the returned blob to a string slice.
|
||||
func Flistxattr(fd int) (attrs []string, err error) {
|
||||
// See the buffer sizing comments in Fgetxattr.
|
||||
// TODO: smarter buffer sizing?
|
||||
buf := make([]byte, XATTR_BUFSZ)
|
||||
sz, err := unix.Flistxattr(fd, buf)
|
||||
if err == syscall.ERANGE {
|
||||
// Do NOT return ERANGE - the user might retry ad inifinitum!
|
||||
return nil, syscall.EOVERFLOW
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sz >= XATTR_SIZE_MAX {
|
||||
return nil, syscall.EOVERFLOW
|
||||
}
|
||||
attrs = parseListxattrBlob(buf[:sz])
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
// Llistxattr is a wrapper for unix.Llistxattr that handles buffer sizing and
|
||||
// parsing the returned blob to a string slice.
|
||||
func Llistxattr(path string) (attrs []string, err error) {
|
||||
// TODO: smarter buffer sizing?
|
||||
buf := make([]byte, XATTR_BUFSZ)
|
||||
sz, err := unix.Llistxattr(path, buf)
|
||||
if err == syscall.ERANGE {
|
||||
// Do NOT return ERANGE - the user might retry ad inifinitum!
|
||||
return nil, syscall.EOVERFLOW
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sz >= XATTR_SIZE_MAX {
|
||||
return nil, syscall.EOVERFLOW
|
||||
}
|
||||
attrs = parseListxattrBlob(buf[:sz])
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
func parseListxattrBlob(buf []byte) (attrs []string) {
|
||||
parts := bytes.Split(buf, []byte{0})
|
||||
for _, part := range parts {
|
||||
if len(part) == 0 {
|
||||
// Last part is empty, ignore
|
||||
continue
|
||||
}
|
||||
attrs = append(attrs, string(part))
|
||||
}
|
||||
return attrs
|
||||
}
|
@ -1,215 +0,0 @@
|
||||
package syscallcompat
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// O_DIRECT means oncached I/O on Linux. No direct equivalent on MacOS and defined
|
||||
// to zero there.
|
||||
O_DIRECT = 0
|
||||
|
||||
// O_PATH is only defined on Linux
|
||||
O_PATH = 0
|
||||
|
||||
// KAUTH_UID_NONE and KAUTH_GID_NONE are special values to
|
||||
// revert permissions to the process credentials.
|
||||
KAUTH_UID_NONE = ^uint32(0) - 100
|
||||
KAUTH_GID_NONE = ^uint32(0) - 100
|
||||
)
|
||||
|
||||
// Unfortunately pthread_setugid_np does not have a syscall wrapper yet.
|
||||
func pthread_setugid_np(uid uint32, gid uint32) (err error) {
|
||||
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETTID, uintptr(uid), uintptr(gid), 0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Unfortunately fsetattrlist does not have a syscall wrapper yet.
|
||||
func fsetattrlist(fd int, list unsafe.Pointer, buf unsafe.Pointer, size uintptr, options int) (err error) {
|
||||
_, _, e1 := syscall.Syscall6(syscall.SYS_FSETATTRLIST, uintptr(fd), uintptr(list), uintptr(buf), uintptr(size), uintptr(options), 0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Setattrlist already has a syscall wrapper, but it is not exported.
|
||||
func setattrlist(path *byte, list unsafe.Pointer, buf unsafe.Pointer, size uintptr, options int) (err error) {
|
||||
_, _, e1 := syscall.Syscall6(syscall.SYS_SETATTRLIST, uintptr(unsafe.Pointer(path)), uintptr(list), uintptr(buf), uintptr(size), uintptr(options), 0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Sorry, fallocate is not available on OSX at all and
|
||||
// fcntl F_PREALLOCATE is not accessible from Go.
|
||||
// See https://github.com/rfjakob/gocryptfs/issues/18 if you want to help.
|
||||
func EnospcPrealloc(fd int, off int64, len int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// See above.
|
||||
func Fallocate(fd int, mode uint32, off int64, len int64) error {
|
||||
return syscall.EOPNOTSUPP
|
||||
}
|
||||
|
||||
// Dup3 is not available on Darwin, so we use Dup2 instead.
|
||||
func Dup3(oldfd int, newfd int, flags int) (err error) {
|
||||
if flags != 0 {
|
||||
log.Panic("darwin does not support dup3 flags")
|
||||
}
|
||||
return syscall.Dup2(oldfd, newfd)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//// Emulated Syscalls (see emulate.go) ////////////////
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) {
|
||||
if context != nil {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
|
||||
}
|
||||
|
||||
return Openat(dirfd, path, flags, mode)
|
||||
}
|
||||
|
||||
func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) {
|
||||
return emulateMknodat(dirfd, path, mode, dev)
|
||||
}
|
||||
|
||||
func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) {
|
||||
if context != nil {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
|
||||
}
|
||||
|
||||
return Mknodat(dirfd, path, mode, dev)
|
||||
}
|
||||
|
||||
func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) {
|
||||
return unix.Fchmodat(dirfd, path, mode, unix.AT_SYMLINK_NOFOLLOW)
|
||||
}
|
||||
|
||||
func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) {
|
||||
if context != nil {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
|
||||
}
|
||||
|
||||
return Symlinkat(oldpath, newdirfd, newpath)
|
||||
}
|
||||
|
||||
func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) {
|
||||
if context != nil {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
|
||||
}
|
||||
|
||||
return Mkdirat(dirfd, path, mode)
|
||||
}
|
||||
|
||||
type attrList struct {
|
||||
bitmapCount uint16
|
||||
_ uint16
|
||||
CommonAttr uint32
|
||||
VolAttr uint32
|
||||
DirAttr uint32
|
||||
FileAttr uint32
|
||||
Forkattr uint32
|
||||
}
|
||||
|
||||
func timesToAttrList(a *time.Time, m *time.Time) (attrList attrList, attributes [2]unix.Timespec) {
|
||||
attrList.bitmapCount = unix.ATTR_BIT_MAP_COUNT
|
||||
attrList.CommonAttr = 0
|
||||
i := 0
|
||||
if m != nil {
|
||||
attributes[i] = unix.Timespec(fuse.UtimeToTimespec(m))
|
||||
attrList.CommonAttr |= unix.ATTR_CMN_MODTIME
|
||||
i += 1
|
||||
}
|
||||
if a != nil {
|
||||
attributes[i] = unix.Timespec(fuse.UtimeToTimespec(a))
|
||||
attrList.CommonAttr |= unix.ATTR_CMN_ACCTIME
|
||||
i += 1
|
||||
}
|
||||
return attrList, attributes
|
||||
}
|
||||
|
||||
// FutimesNano syscall.
|
||||
func FutimesNano(fd int, a *time.Time, m *time.Time) (err error) {
|
||||
attrList, attributes := timesToAttrList(a, m)
|
||||
return fsetattrlist(fd, unsafe.Pointer(&attrList), unsafe.Pointer(&attributes),
|
||||
unsafe.Sizeof(attributes), 0)
|
||||
}
|
||||
|
||||
// UtimesNanoAtNofollow is like UtimesNanoAt but never follows symlinks.
|
||||
//
|
||||
// Unfortunately we cannot use unix.UtimesNanoAt since it is broken and just
|
||||
// ignores the provided 'dirfd'. In addition, it also lacks handling of 'nil'
|
||||
// pointers (used to preserve one of both timestamps).
|
||||
func UtimesNanoAtNofollow(dirfd int, path string, a *time.Time, m *time.Time) (err error) {
|
||||
if !filepath.IsAbs(path) {
|
||||
chdirMutex.Lock()
|
||||
defer chdirMutex.Unlock()
|
||||
var cwd int
|
||||
cwd, err = syscall.Open(".", syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(cwd)
|
||||
err = syscall.Fchdir(dirfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Fchdir(cwd)
|
||||
}
|
||||
|
||||
_p0, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attrList, attributes := timesToAttrList(a, m)
|
||||
return setattrlist(_p0, unsafe.Pointer(&attrList), unsafe.Pointer(&attributes),
|
||||
unsafe.Sizeof(attributes), unix.FSOPT_NOFOLLOW)
|
||||
}
|
||||
|
||||
func Getdents(fd int) ([]fuse.DirEntry, error) {
|
||||
return emulateGetdents(fd)
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
// Package syscallcompat wraps Linux-specific syscalls.
|
||||
package syscallcompat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
_FALLOC_FL_KEEP_SIZE = 0x01
|
||||
|
||||
// O_DIRECT means oncached I/O on Linux. No direct equivalent on MacOS and defined
|
||||
// to zero there.
|
||||
O_DIRECT = syscall.O_DIRECT
|
||||
|
||||
// O_PATH is only defined on Linux
|
||||
O_PATH = unix.O_PATH
|
||||
)
|
||||
|
||||
var preallocWarn sync.Once
|
||||
|
||||
// EnospcPrealloc preallocates ciphertext space without changing the file
|
||||
// size. This guarantees that we don't run out of space while writing a
|
||||
// ciphertext block (that would corrupt the block).
|
||||
func EnospcPrealloc(fd int, off int64, len int64) (err error) {
|
||||
for {
|
||||
err = syscall.Fallocate(fd, _FALLOC_FL_KEEP_SIZE, off, len)
|
||||
if err == syscall.EINTR {
|
||||
// fallocate, like many syscalls, can return EINTR. This is not an
|
||||
// error and just signifies that the operation was interrupted by a
|
||||
// signal and we should try again.
|
||||
continue
|
||||
}
|
||||
if err == syscall.EOPNOTSUPP {
|
||||
// ZFS and ext3 do not support fallocate. Warn but continue anyway.
|
||||
// https://github.com/rfjakob/gocryptfs/issues/22
|
||||
preallocWarn.Do(func() {})
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Fallocate wraps the Fallocate syscall.
|
||||
func Fallocate(fd int, mode uint32, off int64, len int64) (err error) {
|
||||
return syscall.Fallocate(fd, mode, off, len)
|
||||
}
|
||||
|
||||
func getSupplementaryGroups(pid uint32) (gids []int) {
|
||||
procPath := fmt.Sprintf("/proc/%d/task/%d/status", pid, pid)
|
||||
blob, err := ioutil.ReadFile(procPath)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
lines := strings.Split(string(blob), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "Groups:") {
|
||||
f := strings.Fields(line[7:])
|
||||
gids = make([]int, len(f))
|
||||
for i := range gids {
|
||||
val, err := strconv.ParseInt(f[i], 10, 32)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
gids[i] = int(val)
|
||||
}
|
||||
return gids
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mknodat wraps the Mknodat syscall.
|
||||
func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) {
|
||||
return syscall.Mknodat(dirfd, path, mode, dev)
|
||||
}
|
||||
|
||||
// Dup3 wraps the Dup3 syscall. We want to use Dup3 rather than Dup2 because Dup2
|
||||
// is not implemented on arm64.
|
||||
func Dup3(oldfd int, newfd int, flags int) (err error) {
|
||||
return syscall.Dup3(oldfd, newfd, flags)
|
||||
}
|
||||
|
||||
// FchmodatNofollow is like Fchmodat but never follows symlinks.
|
||||
//
|
||||
// This should be handled by the AT_SYMLINK_NOFOLLOW flag, but Linux
|
||||
// does not implement it, so we have to perform an elaborate dance
|
||||
// with O_PATH and /proc/self/fd.
|
||||
//
|
||||
// See also: Qemu implemented the same logic as fchmodat_nofollow():
|
||||
// https://git.qemu.org/?p=qemu.git;a=blob;f=hw/9pfs/9p-local.c#l335
|
||||
func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) {
|
||||
// Open handle to the filename (but without opening the actual file).
|
||||
// This succeeds even when we don't have read permissions to the file.
|
||||
fd, err := syscall.Openat(dirfd, path, syscall.O_NOFOLLOW|O_PATH, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
// Now we can check the type without the risk of race-conditions.
|
||||
// Return syscall.ELOOP if it is a symlink.
|
||||
var st syscall.Stat_t
|
||||
err = syscall.Fstat(fd, &st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if st.Mode&syscall.S_IFMT == syscall.S_IFLNK {
|
||||
return syscall.ELOOP
|
||||
}
|
||||
|
||||
// Change mode of the actual file. Fchmod does not work with O_PATH,
|
||||
// but Chmod via /proc/self/fd works.
|
||||
procPath := fmt.Sprintf("/proc/self/fd/%d", fd)
|
||||
return syscall.Chmod(procPath, mode)
|
||||
}
|
||||
|
||||
func timesToTimespec(a *time.Time, m *time.Time) []unix.Timespec {
|
||||
ts := make([]unix.Timespec, 2)
|
||||
ta, _ := unix.TimeToTimespec(*a)
|
||||
ts[0] = unix.Timespec(ta)
|
||||
tm, _ := unix.TimeToTimespec(*m)
|
||||
ts[1] = unix.Timespec(tm)
|
||||
return ts
|
||||
}
|
||||
|
||||
// FutimesNano syscall.
|
||||
func FutimesNano(fd int, a *time.Time, m *time.Time) (err error) {
|
||||
ts := timesToTimespec(a, m)
|
||||
// To avoid introducing a separate syscall wrapper for futimens()
|
||||
// (as done in go-fuse, for example), we instead use the /proc/self/fd trick.
|
||||
procPath := fmt.Sprintf("/proc/self/fd/%d", fd)
|
||||
return unix.UtimesNanoAt(unix.AT_FDCWD, procPath, ts, 0)
|
||||
}
|
||||
|
||||
// UtimesNanoAtNofollow is like UtimesNanoAt but never follows symlinks.
|
||||
func UtimesNanoAtNofollow(dirfd int, path string, a *time.Time, m *time.Time) (err error) {
|
||||
ts := timesToTimespec(a, m)
|
||||
return unix.UtimesNanoAt(dirfd, path, ts, unix.AT_SYMLINK_NOFOLLOW)
|
||||
}
|
||||
|
||||
func Getdents(fd int) ([]DirEntry, error) {
|
||||
return getdents(fd)
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package syscallcompat
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Unix2syscall converts a unix.Stat_t struct to a syscall.Stat_t struct.
|
||||
func Unix2syscall(u unix.Stat_t) syscall.Stat_t {
|
||||
return syscall.Stat_t{
|
||||
Dev: u.Dev,
|
||||
Ino: u.Ino,
|
||||
Nlink: u.Nlink,
|
||||
Mode: u.Mode,
|
||||
Uid: u.Uid,
|
||||
Gid: u.Gid,
|
||||
Rdev: u.Rdev,
|
||||
Size: u.Size,
|
||||
Blksize: u.Blksize,
|
||||
Blocks: u.Blocks,
|
||||
Atimespec: syscall.Timespec(u.Atim),
|
||||
Mtimespec: syscall.Timespec(u.Mtim),
|
||||
Ctimespec: syscall.Timespec(u.Ctim),
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package syscallcompat
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Unix2syscall converts a unix.Stat_t struct to a syscall.Stat_t struct.
|
||||
// A direct cast does not work because the padding is named differently in
|
||||
// unix.Stat_t for some reason ("X__unused" in syscall, "_" in unix).
|
||||
func Unix2syscall(u unix.Stat_t) syscall.Stat_t {
|
||||
return syscall.Stat_t{
|
||||
Dev: u.Dev,
|
||||
Ino: u.Ino,
|
||||
Nlink: u.Nlink,
|
||||
Mode: u.Mode,
|
||||
Uid: u.Uid,
|
||||
Gid: u.Gid,
|
||||
Rdev: u.Rdev,
|
||||
Size: u.Size,
|
||||
Blksize: u.Blksize,
|
||||
Blocks: u.Blocks,
|
||||
Atim: syscall.NsecToTimespec(unix.TimespecToNsec(u.Atim)),
|
||||
Mtim: syscall.NsecToTimespec(unix.TimespecToNsec(u.Mtim)),
|
||||
Ctim: syscall.NsecToTimespec(unix.TimespecToNsec(u.Ctim)),
|
||||
}
|
||||
}
|
@ -111,7 +111,7 @@ class CreateActivity : VolumeActionActivity() {
|
||||
}
|
||||
}
|
||||
if (goodDirectory) {
|
||||
if (GocryptfsVolume.createVolume(currentVolumePath, password, GocryptfsVolume.ScryptDefaultLogN, ConstValues.creator)) {
|
||||
if (GocryptfsVolume.createVolume(currentVolumePath, password, false, GocryptfsVolume.ScryptDefaultLogN, ConstValues.creator)) {
|
||||
var returnedHash: ByteArray? = null
|
||||
if (checkbox_save_password.isChecked){
|
||||
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
|
||||
|
@ -14,15 +14,15 @@ class GocryptfsVolume(var sessionID: Int) {
|
||||
private external fun native_is_closed(sessionID: Int): Boolean
|
||||
private external fun native_list_dir(sessionID: Int, dir_path: String): MutableList<ExplorerElement>
|
||||
private external fun native_open_read_mode(sessionID: Int, file_path: String): Int
|
||||
private external fun native_open_write_mode(sessionID: Int, file_path: String): Int
|
||||
private external fun native_open_write_mode(sessionID: Int, file_path: String, mode: Int): Int
|
||||
private external fun native_read_file(sessionID: Int, handleID: Int, offset: Long, buff: ByteArray): Int
|
||||
private external fun native_write_file(sessionID: Int, handleID: Int, offset: Long, buff: ByteArray, buff_size: Int): Int
|
||||
private external fun native_truncate(sessionID: Int, file_path: String, offset: Long): Boolean
|
||||
private external fun native_truncate(sessionID: Int, handleID: Int, offset: Long): Boolean
|
||||
private external fun native_path_exists(sessionID: Int, file_path: String): Boolean
|
||||
private external fun native_get_size(sessionID: Int, file_path: String): Long
|
||||
private external fun native_close_file(sessionID: Int, handleID: Int)
|
||||
private external fun native_remove_file(sessionID: Int, file_path: String): Boolean
|
||||
private external fun native_mkdir(sessionID: Int, dir_path: String): Boolean
|
||||
private external fun native_mkdir(sessionID: Int, dir_path: String, mode: Int): Boolean
|
||||
private external fun native_rmdir(sessionID: Int, dir_path: String): Boolean
|
||||
private external fun native_rename(sessionID: Int, old_path: String, new_path: String): Boolean
|
||||
|
||||
@ -30,7 +30,7 @@ class GocryptfsVolume(var sessionID: Int) {
|
||||
const val KeyLen = 32
|
||||
const val ScryptDefaultLogN = 16
|
||||
const val DefaultBS = 4096
|
||||
external fun createVolume(root_cipher_dir: String, password: CharArray, logN: Int, creator: String): Boolean
|
||||
external fun createVolume(root_cipher_dir: String, password: CharArray, plainTextNames: Boolean, logN: Int, creator: String): Boolean
|
||||
external fun init(root_cipher_dir: String, password: CharArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int
|
||||
external fun changePassword(root_cipher_dir: String, old_password: CharArray?, givenHash: ByteArray?, new_password: CharArray, returnedHash: ByteArray?): Boolean
|
||||
|
||||
@ -66,7 +66,7 @@ class GocryptfsVolume(var sessionID: Int) {
|
||||
|
||||
fun mkdir(dir_path: String): Boolean {
|
||||
synchronized(this){
|
||||
return native_mkdir(sessionID, dir_path)
|
||||
return native_mkdir(sessionID, dir_path, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ class GocryptfsVolume(var sessionID: Int) {
|
||||
|
||||
fun openWriteMode(file_path: String): Int {
|
||||
synchronized(this){
|
||||
return native_open_write_mode(sessionID, file_path)
|
||||
return native_open_write_mode(sessionID, file_path, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,9 +124,9 @@ class GocryptfsVolume(var sessionID: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
fun truncate(file_path: String, offset: Long): Boolean {
|
||||
fun truncate(handleID: Int, offset: Long): Boolean {
|
||||
synchronized(this) {
|
||||
return native_truncate(sessionID, file_path, offset)
|
||||
return native_truncate(sessionID, handleID, offset)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,9 @@ import android.graphics.drawable.Icon
|
||||
import android.net.Uri
|
||||
import android.os.*
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.util.Wiper
|
||||
import java.io.File
|
||||
|
@ -90,7 +90,7 @@ class TextEditor: FileViewerActivity() {
|
||||
}
|
||||
}
|
||||
if (offset == content.size.toLong()){
|
||||
success = gocryptfsVolume.truncate(filePath, offset)
|
||||
success = gocryptfsVolume.truncate(handleID, offset)
|
||||
}
|
||||
gocryptfsVolume.closeFile(handleID)
|
||||
buff.close()
|
||||
|
@ -33,6 +33,7 @@ void jbyteArray_to_unsignedCharArray(const jbyte* src, unsigned char* dst, const
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *env, jclass clazz,
|
||||
jstring jroot_cipher_dir, jcharArray jpassword,
|
||||
jboolean plainTextNames,
|
||||
jint logN,
|
||||
jstring jcreator) {
|
||||
const char* root_cipher_dir = (*env)->GetStringUTFChars(env, jroot_cipher_dir, NULL);
|
||||
@ -45,7 +46,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *
|
||||
jcharArray_to_charArray(jchar_password, password, password_len);
|
||||
GoSlice go_password = {password, password_len, password_len};
|
||||
|
||||
GoUint8 result = gcf_create_volume(gofilename, go_password, logN, gocreator);
|
||||
GoUint8 result = gcf_create_volume(gofilename, go_password, plainTextNames, logN, gocreator);
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, jroot_cipher_dir, root_cipher_dir);
|
||||
(*env)->ReleaseStringUTFChars(env, jcreator, creator);
|
||||
@ -317,11 +318,12 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1open_1read_1mode(JNIEnv *env
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1open_1write_1mode(JNIEnv *env, jobject thiz,
|
||||
jint sessionID,
|
||||
jstring jfile_path) {
|
||||
jstring jfile_path,
|
||||
jint mode) {
|
||||
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
||||
GoString go_file_path = {file_path, strlen(file_path)};
|
||||
|
||||
GoInt handleID = gcf_open_write_mode(sessionID, go_file_path);
|
||||
GoInt handleID = gcf_open_write_mode(sessionID, go_file_path, mode);
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, jfile_path, file_path);
|
||||
|
||||
@ -362,15 +364,8 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1read_1file(JNIEnv *env, jobj
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1truncate(JNIEnv *env, jobject thiz,
|
||||
jint sessionID,
|
||||
jstring jfile_path, jlong offset) {
|
||||
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
||||
GoString go_file_path = {file_path, strlen(file_path)};
|
||||
|
||||
GoUint8 result = gcf_truncate(sessionID, go_file_path, offset);
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, jfile_path, file_path);
|
||||
|
||||
return result;
|
||||
jint handleID, jlong offset) {
|
||||
return gcf_truncate(sessionID, handleID, offset);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
@ -395,11 +390,11 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1remove_1file(JNIEnv *env, jo
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1mkdir(JNIEnv *env, jobject thiz,
|
||||
jint sessionID, jstring jdir_path) {
|
||||
jint sessionID, jstring jdir_path, jint mode) {
|
||||
const char* dir_path = (*env)->GetStringUTFChars(env, jdir_path, NULL);
|
||||
GoString go_dir_path = {dir_path, strlen(dir_path)};
|
||||
|
||||
GoUint8 result = gcf_mkdir(sessionID, go_dir_path);
|
||||
GoUint8 result = gcf_mkdir(sessionID, go_dir_path, mode);
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, jdir_path, dir_path);
|
||||
|
||||
|
@ -71,7 +71,7 @@
|
||||
android:src="@drawable/exo_icon_previous"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/image_next"
|
||||
android:id="@+id/image_rotate_left"
|
||||
android:layout_width="@dimen/image_button_size"
|
||||
android:layout_height="@dimen/image_button_size"
|
||||
android:layout_margin="10dp"
|
||||
@ -89,7 +89,7 @@
|
||||
android:src="@drawable/icon_rotate_right"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/image_rotate_left"
|
||||
android:id="@+id/image_next"
|
||||
android:layout_width="@dimen/image_button_size"
|
||||
android:layout_height="@dimen/image_button_size"
|
||||
android:layout_margin="10dp"
|
||||
|
Loading…
Reference in New Issue
Block a user