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 (goodDirectory) {
|
||||||
if (GocryptfsVolume.createVolume(currentVolumePath, password, GocryptfsVolume.ScryptDefaultLogN, ConstValues.creator)) {
|
if (GocryptfsVolume.createVolume(currentVolumePath, password, false, GocryptfsVolume.ScryptDefaultLogN, ConstValues.creator)) {
|
||||||
var returnedHash: ByteArray? = null
|
var returnedHash: ByteArray? = null
|
||||||
if (checkbox_save_password.isChecked){
|
if (checkbox_save_password.isChecked){
|
||||||
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
|
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_is_closed(sessionID: Int): Boolean
|
||||||
private external fun native_list_dir(sessionID: Int, dir_path: String): MutableList<ExplorerElement>
|
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_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_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_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_path_exists(sessionID: Int, file_path: String): Boolean
|
||||||
private external fun native_get_size(sessionID: Int, file_path: String): Long
|
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_close_file(sessionID: Int, handleID: Int)
|
||||||
private external fun native_remove_file(sessionID: Int, file_path: String): Boolean
|
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_rmdir(sessionID: Int, dir_path: String): Boolean
|
||||||
private external fun native_rename(sessionID: Int, old_path: String, new_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 KeyLen = 32
|
||||||
const val ScryptDefaultLogN = 16
|
const val ScryptDefaultLogN = 16
|
||||||
const val DefaultBS = 4096
|
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 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
|
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 {
|
fun mkdir(dir_path: String): Boolean {
|
||||||
synchronized(this){
|
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 {
|
fun openWriteMode(file_path: String): Int {
|
||||||
synchronized(this){
|
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) {
|
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.net.Uri
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import sushi.hardcore.droidfs.GocryptfsVolume
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.util.Wiper
|
import sushi.hardcore.droidfs.util.Wiper
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -90,7 +90,7 @@ class TextEditor: FileViewerActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (offset == content.size.toLong()){
|
if (offset == content.size.toLong()){
|
||||||
success = gocryptfsVolume.truncate(filePath, offset)
|
success = gocryptfsVolume.truncate(handleID, offset)
|
||||||
}
|
}
|
||||||
gocryptfsVolume.closeFile(handleID)
|
gocryptfsVolume.closeFile(handleID)
|
||||||
buff.close()
|
buff.close()
|
||||||
|
@ -33,6 +33,7 @@ void jbyteArray_to_unsignedCharArray(const jbyte* src, unsigned char* dst, const
|
|||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *env, jclass clazz,
|
Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *env, jclass clazz,
|
||||||
jstring jroot_cipher_dir, jcharArray jpassword,
|
jstring jroot_cipher_dir, jcharArray jpassword,
|
||||||
|
jboolean plainTextNames,
|
||||||
jint logN,
|
jint logN,
|
||||||
jstring jcreator) {
|
jstring jcreator) {
|
||||||
const char* root_cipher_dir = (*env)->GetStringUTFChars(env, jroot_cipher_dir, NULL);
|
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);
|
jcharArray_to_charArray(jchar_password, password, password_len);
|
||||||
GoSlice go_password = {password, password_len, 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, jroot_cipher_dir, root_cipher_dir);
|
||||||
(*env)->ReleaseStringUTFChars(env, jcreator, creator);
|
(*env)->ReleaseStringUTFChars(env, jcreator, creator);
|
||||||
@ -317,11 +318,12 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1open_1read_1mode(JNIEnv *env
|
|||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1open_1write_1mode(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1open_1write_1mode(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID,
|
jint sessionID,
|
||||||
jstring jfile_path) {
|
jstring jfile_path,
|
||||||
|
jint mode) {
|
||||||
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
||||||
GoString go_file_path = {file_path, strlen(file_path)};
|
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);
|
(*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
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1truncate(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1truncate(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID,
|
jint sessionID,
|
||||||
jstring jfile_path, jlong offset) {
|
jint handleID, jlong offset) {
|
||||||
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
return gcf_truncate(sessionID, handleID, offset);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
@ -395,11 +390,11 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1remove_1file(JNIEnv *env, jo
|
|||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1mkdir(JNIEnv *env, jobject thiz,
|
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);
|
const char* dir_path = (*env)->GetStringUTFChars(env, jdir_path, NULL);
|
||||||
GoString go_dir_path = {dir_path, strlen(dir_path)};
|
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);
|
(*env)->ReleaseStringUTFChars(env, jdir_path, dir_path);
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
android:src="@drawable/exo_icon_previous"/>
|
android:src="@drawable/exo_icon_previous"/>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/image_next"
|
android:id="@+id/image_rotate_left"
|
||||||
android:layout_width="@dimen/image_button_size"
|
android:layout_width="@dimen/image_button_size"
|
||||||
android:layout_height="@dimen/image_button_size"
|
android:layout_height="@dimen/image_button_size"
|
||||||
android:layout_margin="10dp"
|
android:layout_margin="10dp"
|
||||||
@ -89,7 +89,7 @@
|
|||||||
android:src="@drawable/icon_rotate_right"/>
|
android:src="@drawable/icon_rotate_right"/>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/image_rotate_left"
|
android:id="@+id/image_next"
|
||||||
android:layout_width="@dimen/image_button_size"
|
android:layout_width="@dimen/image_button_size"
|
||||||
android:layout_height="@dimen/image_button_size"
|
android:layout_height="@dimen/image_button_size"
|
||||||
android:layout_margin="10dp"
|
android:layout_margin="10dp"
|
||||||
|
Loading…
Reference in New Issue
Block a user