diff --git a/internal/configfile/config_file.go b/internal/configfile/config_file.go index ffc41de..e919f8b 100644 --- a/internal/configfile/config_file.go +++ b/internal/configfile/config_file.go @@ -301,7 +301,7 @@ func (cf *ConfFile) WriteFile() error { 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/v2/issues/390 + // "operation not supported": https://github.com/rfjakob/gocryptfs/issues/390 // Try sync instead syscall.Sync() } @@ -322,8 +322,8 @@ func getKeyEncrypter(scryptHash []byte, useHKDF bool) *contentenc.ContentEnc { if useHKDF { IVLen = contentenc.DefaultIVBits } - cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, IVLen, useHKDF, false) - ce := contentenc.New(cc, 4096, false) + cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, IVLen, useHKDF) + ce := contentenc.New(cc, 4096) return ce } diff --git a/internal/contentenc/content.go b/internal/contentenc/content.go index 6b9fd41..7046c39 100644 --- a/internal/contentenc/content.go +++ b/internal/contentenc/content.go @@ -10,7 +10,6 @@ import ( "sync" "../cryptocore" - "../stupidgcm" ) const ( @@ -40,8 +39,6 @@ type ContentEnc struct { 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). @@ -59,7 +56,7 @@ type ContentEnc struct { } // New returns an initialized ContentEnc instance. -func New(cc *cryptocore.CryptoCore, plainBS uint64, forceDecode bool) *ContentEnc { +func New(cc *cryptocore.CryptoCore, plainBS uint64) *ContentEnc { if MAX_KERNEL_WRITE%plainBS != 0 { log.Panicf("unaligned MAX_KERNEL_WRITE=%d", MAX_KERNEL_WRITE) } @@ -77,7 +74,6 @@ func New(cc *cryptocore.CryptoCore, plainBS uint64, forceDecode bool) *ContentEn cipherBS: cipherBS, allZeroBlock: make([]byte, cipherBS), allZeroNonce: make([]byte, cc.IVLen), - forceDecode: forceDecode, cBlockPool: newBPool(int(cipherBS)), CReqPool: newBPool(cReqSize), pBlockPool: newBPool(int(plainBS)), @@ -107,9 +103,7 @@ func (be *ContentEnc) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, file var pBlock []byte pBlock, err = be.DecryptBlock(cBlock, blockNo, fileID) if err != nil { - if !(be.forceDecode && err == stupidgcm.ErrAuth) { - break - } + break } pBuf.Write(pBlock) be.pBlockPool.Put(pBlock) @@ -159,7 +153,7 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b nonce := ciphertext[:be.cryptoCore.IVLen] if bytes.Equal(nonce, be.allZeroNonce) { // Bug in tmpfs? - // https://github.com/rfjakob/gocryptfs/v2/issues/56 + // https://github.com/rfjakob/gocryptfs/issues/56 // http://www.spinics.net/lists/kernel/msg2370127.html return nil, errors.New("all-zero nonce") } @@ -172,9 +166,6 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b 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 } diff --git a/internal/cryptocore/cryptocore.go b/internal/cryptocore/cryptocore.go index 03e0416..6a6a659 100644 --- a/internal/cryptocore/cryptocore.go +++ b/internal/cryptocore/cryptocore.go @@ -31,11 +31,11 @@ type AEADTypeEnum struct { NonceSize int } -// BackendOpenSSL specifies the OpenSSL backend. +// BackendOpenSSL specifies the OpenSSL AES-256-GCM backend. // "AES-GCM-256-OpenSSL" in gocryptfs -speed. var BackendOpenSSL AEADTypeEnum = AEADTypeEnum{"AES-GCM-256-OpenSSL", 16} -// BackendGoGCM specifies the Go based GCM backend. +// BackendGoGCM specifies the Go based AES-256-GCM backend. // "AES-GCM-256-Go" in gocryptfs -speed. var BackendGoGCM AEADTypeEnum = AEADTypeEnum{"AES-GCM-256-Go", 16} @@ -47,6 +47,9 @@ var BackendAESSIV AEADTypeEnum = AEADTypeEnum{"AES-SIV-512-Go", siv_aead.NonceSi // "XChaCha20-Poly1305-Go" in gocryptfs -speed. var BackendXChaCha20Poly1305 AEADTypeEnum = AEADTypeEnum{"XChaCha20-Poly1305-Go", chacha20poly1305.NonceSizeX} +// BackendXChaCha20Poly1305OpenSSL specifies XChaCha20-Poly1305-OpenSSL. +var BackendXChaCha20Poly1305OpenSSL AEADTypeEnum = AEADTypeEnum{"XChaCha20-Poly1305-OpenSSL", chacha20poly1305.NonceSizeX} + // CryptoCore is the low level crypto implementation. type CryptoCore struct { // EME is used for filename encryption. @@ -69,7 +72,7 @@ type CryptoCore struct { // // 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 { +func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool) *CryptoCore { if len(key) != KeyLen { log.Panicf("Unsupported key length of %d bytes", len(key)) } @@ -113,7 +116,7 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec if IVBitLen != 128 { log.Panicf("stupidgcm only supports 128-bit IVs, you wanted %d", IVBitLen) } - aeadCipher = stupidgcm.New(gcmKey, forceDecode) + aeadCipher = stupidgcm.NewAES256GCM(gcmKey) case BackendGoGCM: goGcmBlockCipher, err := aes.NewCipher(gcmKey) if err != nil { @@ -123,6 +126,8 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec if err != nil { log.Panic(err) } + default: + log.Panicf("BUG: unhandled case: %v", aeadType) } for i := range gcmKey { gcmKey[i] = 0 @@ -147,7 +152,7 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec for i := range key64 { key64[i] = 0 } - } else if aeadType == BackendXChaCha20Poly1305 { + } else if aeadType == BackendXChaCha20Poly1305 || aeadType == BackendXChaCha20Poly1305OpenSSL { // We don't support legacy modes with XChaCha20-Poly1305 if IVBitLen != chacha20poly1305.NonceSizeX*8 { log.Panicf("XChaCha20-Poly1305 must use 192-bit IVs, you wanted %d", IVBitLen) @@ -156,7 +161,13 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec log.Panic("XChaCha20-Poly1305 must use HKDF, but it is disabled") } derivedKey := hkdfDerive(key, hkdfInfoXChaChaPoly1305Content, chacha20poly1305.KeySize) - aeadCipher, err = chacha20poly1305.NewX(derivedKey) + if aeadType == BackendXChaCha20Poly1305 { + aeadCipher, err = chacha20poly1305.NewX(derivedKey) + } else if aeadType == BackendXChaCha20Poly1305OpenSSL { + aeadCipher = stupidgcm.NewXchacha20poly1305(derivedKey) + } else { + log.Panicf("BUG: unhandled case: %v", aeadType) + } if err != nil { log.Panic(err) } diff --git a/internal/nametransform/diriv.go b/internal/nametransform/diriv.go index 1e1e887..81da739 100644 --- a/internal/nametransform/diriv.go +++ b/internal/nametransform/diriv.go @@ -67,7 +67,7 @@ func WriteDirIVAt(dirfd int) error { 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/v2/commit/7d38f80a78644c8ec4900cc990bfb894387112ed + // 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 diff --git a/internal/nametransform/perms.go b/internal/nametransform/perms.go index 6b88afd..cfcd062 100644 --- a/internal/nametransform/perms.go +++ b/internal/nametransform/perms.go @@ -6,14 +6,14 @@ const ( // never chmod'ed or chown'ed. // // Group-readable so the FS can be mounted by several users in the same group - // (see https://github.com/rfjakob/gocryptfs/v2/issues/387 ). + // (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. // // World-readable so an encrypted directory can be copied by the non-root // owner when gocryptfs is running as root - // ( https://github.com/rfjakob/gocryptfs/v2/issues/539 ). + // ( https://github.com/rfjakob/gocryptfs/issues/539 ). dirivPerms = 0444 // Permissions for gocryptfs.longname.[sha256].name files. diff --git a/internal/stupidgcm/.gitignore b/internal/stupidgcm/.gitignore new file mode 100644 index 0000000..5761abc --- /dev/null +++ b/internal/stupidgcm/.gitignore @@ -0,0 +1 @@ +*.o diff --git a/internal/stupidgcm/Makefile b/internal/stupidgcm/Makefile new file mode 100644 index 0000000..143819d --- /dev/null +++ b/internal/stupidgcm/Makefile @@ -0,0 +1,18 @@ +.PHONY: test +test: gcc + # All three ways of building this must work + go build + go build -tags without_openssl + CGO_ENABLED=0 go build -tags without_openssl + # Likewise, all three ways of testing this must work + go test -v + go test -v -tags without_openssl + CGO_ENABLED=0 go test -v -tags without_openssl + +.PHONY: gcc +gcc: + gcc -Wall -Wextra -Wformat-security -Wconversion -lcrypto -c *.c + +.PHONY: format +format: + clang-format --style=WebKit -i *.c *.h diff --git a/internal/stupidgcm/chacha.go b/internal/stupidgcm/chacha.go new file mode 100644 index 0000000..e09ed0b --- /dev/null +++ b/internal/stupidgcm/chacha.go @@ -0,0 +1,54 @@ +// +build !without_openssl + +package stupidgcm + +import ( + "crypto/cipher" + "log" + + "golang.org/x/crypto/chacha20poly1305" +) + +/* +#include +*/ +import "C" + +type stupidChacha20poly1305 struct { + stupidAEADCommon +} + +// Verify that we satisfy the cipher.AEAD interface +var _ cipher.AEAD = &stupidChacha20poly1305{} + +// _EVP_chacha20_poly1305 caches C.EVP_chacha20_poly1305() to avoid the Cgo call +// overhead for each instantiation of NewChacha20poly1305. +var _EVP_chacha20_poly1305 *C.EVP_CIPHER + +func init() { + _EVP_chacha20_poly1305 = C.EVP_chacha20_poly1305() +} + +// NewChacha20poly1305 returns a new instance of the OpenSSL ChaCha20-Poly1305 AEAD +// cipher ( https://www.openssl.org/docs/man1.1.1/man3/EVP_chacha20_poly1305.html ). +// +// gocryptfs only uses ChaCha20-Poly1305 as a building block for OpenSSL +// XChaCha20-Poly1305. This function is hot because it gets called once for each +// block by XChaCha20-Poly1305. +// +// Only 32-bytes keys and 12-byte IVs are supported. +func NewChacha20poly1305(key []byte) cipher.AEAD { + if len(key) != chacha20poly1305.KeySize { + log.Panicf("Only %d-byte keys are supported, you passed %d bytes", chacha20poly1305.KeySize, len(key)) + } + // private copy + key2 := make([]byte, chacha20poly1305.KeySize) + copy(key2, key) + return &stupidChacha20poly1305{ + stupidAEADCommon{ + key: key2, + openSSLEVPCipher: _EVP_chacha20_poly1305, + nonceSize: chacha20poly1305.NonceSize, + }, + } +} diff --git a/internal/stupidgcm/common.go b/internal/stupidgcm/common.go new file mode 100644 index 0000000..bb100eb --- /dev/null +++ b/internal/stupidgcm/common.go @@ -0,0 +1,70 @@ +// +build !without_openssl + +package stupidgcm + +import ( + "log" +) + +/* +#include +*/ +import "C" + +type stupidAEADCommon struct { + wiped bool + key []byte + openSSLEVPCipher *C.EVP_CIPHER + nonceSize int +} + +// Overhead returns the number of bytes that are added for authentication. +// +// Part of the cipher.AEAD interface. +func (c *stupidAEADCommon) Overhead() int { + return tagLen +} + +// NonceSize returns the required size of the nonce / IV +// +// Part of the cipher.AEAD interface. +func (c *stupidAEADCommon) NonceSize() int { + return c.nonceSize +} + +// Seal encrypts "in" using "iv" and "authData" and append the result to "dst" +// +// Part of the cipher.AEAD interface. +func (c *stupidAEADCommon) Seal(dst, iv, in, authData []byte) []byte { + return openSSLSeal(c, dst, iv, in, authData) +} + +// Open decrypts "in" using "iv" and "authData" and append the result to "dst" +// +// Part of the cipher.AEAD interface. +func (c *stupidAEADCommon) Open(dst, iv, in, authData []byte) ([]byte, error) { + return openSSLOpen(c, dst, iv, in, authData) +} + +// Wipe tries to wipe the key from memory by overwriting it with zeros. +// +// This is not bulletproof due to possible GC copies, but +// still raises the bar for extracting the key. +func (c *stupidAEADCommon) Wipe() { + key := c.key + c.wiped = true + c.key = nil + for i := range key { + key[i] = 0 + } +} + +func (c *stupidAEADCommon) Wiped() bool { + if c.wiped { + return true + } + if len(c.key) != keyLen { + log.Panicf("wrong key length %d", len(c.key)) + } + return false +} diff --git a/internal/stupidgcm/doc.go b/internal/stupidgcm/doc.go new file mode 100644 index 0000000..36c189b --- /dev/null +++ b/internal/stupidgcm/doc.go @@ -0,0 +1,59 @@ +// Package stupidgcm wraps OpenSSL to provide a cipher.AEAD interface for +// authenticated encryption algorithms. +// +// The supported algorithms are: +// +// (1) AES-GCM-256 (OpenSSL EVP_aes_256_gcm) +// +// (2) ChaCha20-Poly1305 (OpenSSL EVP_chacha20_poly1305) +// +// (3) XChaCha20-Poly1305 (OpenSSL EVP_chacha20_poly1305 + Go HChaCha20) +// +// The golang.org/x/crypto libraries provides implementations for all algorithms, +// and the test suite verifies that the implementation in this package gives +// the exact same results. +// +// However, OpenSSL has optimized assembly for almost all platforms, which Go +// does not. Example for a 32-bit ARM device (Odroid XU4): +// +// $ gocrypts -speed +// gocryptfs v2.1-68-gedf9d4c.stupidchacha; go-fuse v2.1.1-0.20210825171523-3ab5d95a30ae; 2021-09-04 go1.16.7 linux/arm +// AES-GCM-256-OpenSSL 56.84 MB/s (selected in auto mode) +// AES-GCM-256-Go 16.61 MB/s +// AES-SIV-512-Go 16.49 MB/s +// XChaCha20-Poly1305-Go 39.08 MB/s (use via -xchacha flag) +// XChaCha20-Poly1305-OpenSSL 141.82 MB/s +// +// This package is "stupid" in the sense that it only supports a narrow set of +// key- and iv-lengths, and panics if it does not like what you pass it. +// See the constructor functions for which restrictions apply for each algorithm. +// Also, it is only tested for block lengths up to 5000 bytes, because this is +// what gocryptfs uses. +// +// Corrupt ciphertexts never cause a panic. Instead, ErrAuth is returned on +// decryption. +// +// XChaCha20-Poly1305 +// +// The XChaCha20-Poly1305 implementation is more complicated than the others, +// because OpenSSL does not support XChaCha20-Poly1305 directly. Follow +// https://github.com/openssl/openssl/issues/5523 to get notified when it is +// accepted into OpenSSL. +// +// Fortunately, XChaCha20-Poly1305 is just ChaCha20-Poly1305 with some key+iv +// mixing using HChaCha20 in front: +// +// key (32 bytes), iv (24 bytes) +// | +// v +// HChaCha20 (provided by golang.org/x/crypto/chacha20) +// | +// v +// key2 (32 bytes), iv2 (16 bytes) +// | +// v +// ChaCha20-Poly1305 (OpenSSL EVP_chacha20_poly1305) +// +// As HChaCha20 is very fast, XChaCha20-Poly1305 gets almost the same throughput +// as ChaCha20-Poly1305 (for 4kiB blocks). +package stupidgcm diff --git a/internal/stupidgcm/gcm.go b/internal/stupidgcm/gcm.go new file mode 100644 index 0000000..00819dd --- /dev/null +++ b/internal/stupidgcm/gcm.go @@ -0,0 +1,41 @@ +// +build !without_openssl + +package stupidgcm + +// #include +import "C" + +import ( + "crypto/cipher" + "log" +) + +const ( + // BuiltWithoutOpenssl indicates if openssl been disabled at compile-time + BuiltWithoutOpenssl = false + + keyLen = 32 + ivLen = 16 + tagLen = 16 +) + +type stupidGCM struct { + stupidAEADCommon +} + +// NewAES256GCM returns a new AES-256-GCM cipher that satisfies the cipher.AEAD interface. +// +// Only 32-bytes keys and 16-byte IVs are supported. +func NewAES256GCM(keyIn []byte) cipher.AEAD { + if len(keyIn) != keyLen { + log.Panicf("Only %d-byte keys are supported", keyLen) + } + return &stupidGCM{ + stupidAEADCommon{ + // Create a private copy of the key + key: append([]byte{}, keyIn...), + openSSLEVPCipher: C.EVP_aes_256_gcm(), + nonceSize: ivLen, + }, + } +} diff --git a/internal/stupidgcm/openssl.go b/internal/stupidgcm/openssl.go new file mode 100644 index 0000000..ae0ee5c --- /dev/null +++ b/internal/stupidgcm/openssl.go @@ -0,0 +1,123 @@ +// +build !without_openssl + +package stupidgcm + +import ( + "fmt" + "log" +) + +/* +#include "openssl_aead.h" +#cgo pkg-config: libcrypto +*/ +import "C" + +func openSSLSeal(a *stupidAEADCommon, dst, iv, in, authData []byte) []byte { + if a.Wiped() { + log.Panic("BUG: tried to use wiped key") + } + if len(iv) != a.NonceSize() { + log.Panicf("Only %d-byte IVs are supported, you passed %d bytes", a.NonceSize(), len(iv)) + } + + // 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) + } + + res := int(C.openssl_aead_seal(a.openSSLEVPCipher, + slicePointerOrNull(in), + C.int(len(in)), + (*C.uchar)(&authData[0]), + C.int(len(authData)), + (*C.uchar)(&a.key[0]), + C.int(len(a.key)), + (*C.uchar)(&iv[0]), + C.int(len(iv)), + (*C.uchar)(&buf[0]), + C.int(len(buf)))) + + if res != outLen { + log.Panicf("expected length %d, got %d", outLen, res) + } + + if inplace { + return dst[:len(dst)+outLen] + } + return append(dst, buf...) +} + +func openSSLOpen(a *stupidAEADCommon, dst, iv, in, authData []byte) ([]byte, error) { + if a.Wiped() { + log.Panic("BUG: tried to use wiped key") + } + if len(iv) != a.NonceSize() { + log.Panicf("Only %d-byte IVs are supported, you passed %d bytes", a.NonceSize(), len(iv)) + } + if len(in) < tagLen { + return nil, fmt.Errorf("stupidChacha20poly1305: 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:] + + res := int(C.openssl_aead_open(a.openSSLEVPCipher, + slicePointerOrNull(ciphertext), + C.int(len(ciphertext)), + (*C.uchar)(&authData[0]), + C.int(len(authData)), + (*C.uchar)(&tag[0]), + C.int(len(tag)), + (*C.uchar)(&a.key[0]), + C.int(len(a.key)), + (*C.uchar)(&iv[0]), + C.int(len(iv)), + slicePointerOrNull(buf), + C.int(len(buf)))) + + if res < 0 { + return nil, ErrAuth + } + if res != outLen { + log.Panicf("unexpected length %d", res) + } + + if inplace { + return dst[:len(dst)+outLen], nil + } + return append(dst, buf...), nil +} + +// slicePointerOrNull returns a C pointer to the beginning of the byte slice, +// or NULL if the byte slice is empty. This is useful for slices that can be +// empty, otherwise you can directly use "(*C.uchar)(&s[0])". +func slicePointerOrNull(s []byte) (ptr *C.uchar) { + if len(s) == 0 { + return + } + return (*C.uchar)(&s[0]) +} + +// This functions exists to benchmark the C call overhead from Go. +// See BenchmarkCCall for resuts. +func noopCFunction() { + C.noop_c_function() +} diff --git a/internal/stupidgcm/openssl_aead.c b/internal/stupidgcm/openssl_aead.c new file mode 100644 index 0000000..e02466f --- /dev/null +++ b/internal/stupidgcm/openssl_aead.c @@ -0,0 +1,185 @@ +// +build !without_openssl + +#include "openssl_aead.h" +#include +#include +//#cgo pkg-config: libcrypto + +static void panic(const char* const msg) +{ + fprintf(stderr, "panic in C code: %s\n", msg); + __builtin_trap(); +} + +// We only support 16-byte tags +static const int supportedTagLen = 16; + +// https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Encryption_using_GCM_mode +int openssl_aead_seal( + const EVP_CIPHER* evpCipher, + const unsigned char* const plaintext, + const int plaintextLen, + const unsigned char* const authData, + const int authDataLen, + const unsigned char* const key, + const int keyLen, + const unsigned char* const iv, + const int ivLen, + unsigned char* const ciphertext, + const int ciphertextBufLen) +{ + // Create scratch space "ctx" + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + panic("EVP_CIPHER_CTX_new failed"); + } + + // Set cipher + if (EVP_EncryptInit_ex(ctx, evpCipher, NULL, NULL, NULL) != 1) { + panic("EVP_EncryptInit_ex set cipher failed"); + } + + // Check keyLen by trying to set it (fails if keyLen != 32) + if (EVP_CIPHER_CTX_set_key_length(ctx, keyLen) != 1) { + panic("keyLen mismatch"); + } + + // Set IV length so we do not depend on the default + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, ivLen, NULL) != 1) { + panic("EVP_CTRL_AEAD_SET_IVLEN failed"); + } + + // Set key and IV + if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1) { + panic("EVP_EncryptInit_ex set key & iv failed"); + } + + // Provide authentication data + int outLen = 0; + if (EVP_EncryptUpdate(ctx, NULL, &outLen, authData, authDataLen) != 1) { + panic("EVP_EncryptUpdate authData failed"); + } + if (outLen != authDataLen) { + panic("EVP_EncryptUpdate authData: unexpected length"); + } + + // Encrypt "plaintext" into "ciphertext" + if (plaintextLen > ciphertextBufLen) { + panic("plaintext overflows output buffer"); + } + if (EVP_EncryptUpdate(ctx, ciphertext, &outLen, plaintext, plaintextLen) != 1) { + panic("EVP_EncryptUpdate ciphertext failed"); + } + if (outLen != plaintextLen) { + panic("EVP_EncryptUpdate ciphertext: unexpected length"); + } + int ciphertextLen = outLen; + + // Finalise encryption + // Normally ciphertext bytes may be written at this stage, but this does not occur in GCM mode + if (EVP_EncryptFinal_ex(ctx, ciphertext + plaintextLen, &outLen) != 1) { + panic("EVP_EncryptFinal_ex failed"); + } + if (outLen != 0) { + panic("EVP_EncryptFinal_ex: unexpected length"); + } + + // Get MAC tag and append it to the ciphertext + if (ciphertextLen + supportedTagLen > ciphertextBufLen) { + panic("tag overflows output buffer"); + } + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, supportedTagLen, ciphertext + plaintextLen) != 1) { + panic("EVP_CTRL_AEAD_GET_TAG failed"); + } + ciphertextLen += supportedTagLen; + + // Free scratch space + EVP_CIPHER_CTX_free(ctx); + + return ciphertextLen; +} + +int openssl_aead_open( + const EVP_CIPHER* evpCipher, + const unsigned char* const ciphertext, + const int ciphertextLen, + const unsigned char* const authData, + const int authDataLen, + unsigned char* const tag, + const int tagLen, + const unsigned char* const key, + const int keyLen, + const unsigned char* const iv, + const int ivLen, + unsigned char* const plaintext, + const int plaintextBufLen) +{ + // Create scratch space "ctx" + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + panic("EVP_CIPHER_CTX_new failed"); + } + + // Set cipher + if (EVP_DecryptInit_ex(ctx, evpCipher, NULL, NULL, NULL) != 1) { + panic("EVP_DecryptInit_ex set cipher failed"); + } + + // Check keyLen by trying to set it (fails if keyLen != 32) + if (EVP_CIPHER_CTX_set_key_length(ctx, keyLen) != 1) { + panic("keyLen mismatch"); + } + + // Set IV length so we do not depend on the default + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, ivLen, NULL) != 1) { + panic("EVP_CTRL_AEAD_SET_IVLEN failed"); + } + + // Set key and IV + if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) != 1) { + panic("EVP_DecryptInit_ex set key & iv failed"); + } + + // Provide authentication data + int outLen = 0; + if (EVP_DecryptUpdate(ctx, NULL, &outLen, authData, authDataLen) != 1) { + panic("EVP_DecryptUpdate authData failed"); + } + if (outLen != authDataLen) { + panic("EVP_DecryptUpdate authData: unexpected length"); + } + + // Decrypt "ciphertext" into "plaintext" + if (ciphertextLen > plaintextBufLen) { + panic("ciphertextLen overflows output buffer"); + } + if (EVP_DecryptUpdate(ctx, plaintext, &outLen, ciphertext, ciphertextLen) != 1) { + panic("EVP_DecryptUpdate failed"); + } + int plaintextLen = outLen; + + // Check tag + if (tagLen != supportedTagLen) { + panic("unsupported tag length"); + } + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tagLen, tag) != 1) { + panic("EVP_CTRL_AEAD_SET_TAG failed"); + } + if (EVP_DecryptFinal_ex(ctx, plaintext + plaintextLen, &outLen) != 1) { + // authentication failed + return -1; + } + if (outLen != 0) { + panic("EVP_EncryptFinal_ex: unexpected length"); + } + + /* Clean up */ + EVP_CIPHER_CTX_free(ctx); + + return plaintextLen; +} + +// This functions exists to benchmark the C call overhead from Go. +void noop_c_function(void) { + return; +} diff --git a/internal/stupidgcm/openssl_aead.h b/internal/stupidgcm/openssl_aead.h new file mode 100644 index 0000000..820beeb --- /dev/null +++ b/internal/stupidgcm/openssl_aead.h @@ -0,0 +1,31 @@ +#include + +int openssl_aead_seal( + const EVP_CIPHER* evpCipher, + const unsigned char* const plaintext, + const int plaintextLen, + const unsigned char* const authData, + const int authDataLen, + const unsigned char* const key, + const int keyLen, + const unsigned char* const iv, + const int ivLen, + unsigned char* const ciphertext, + const int ciphertextBufLen); + +int openssl_aead_open( + const EVP_CIPHER* evpCipher, + const unsigned char* const ciphertext, + const int ciphertextLen, + const unsigned char* const authData, + const int authDataLen, + unsigned char* const tag, + const int tagLen, + const unsigned char* const key, + const int keyLen, + const unsigned char* const iv, + const int ivLen, + unsigned char* const plaintext, + const int plaintextBufLen); + +void noop_c_function(void); diff --git a/internal/stupidgcm/prefer.go b/internal/stupidgcm/prefer.go index 94c1b6c..bb613c3 100644 --- a/internal/stupidgcm/prefer.go +++ b/internal/stupidgcm/prefer.go @@ -6,7 +6,8 @@ import ( "golang.org/x/sys/cpu" ) -// PreferOpenSSL tells us if OpenSSL is faster than Go GCM on this machine. +// PreferOpenSSLAES256GCM tells us if OpenSSL AES-256-GCM is faster than Go stdlib +// on this machine. // // Go GCM is only faster if the CPU either: // @@ -14,22 +15,46 @@ import ( // 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/v2/wiki/CPU-Benchmarks +// See https://github.com/rfjakob/gocryptfs/wiki/CPU-Benchmarks // for benchmarks. -func PreferOpenSSL() bool { +func PreferOpenSSLAES256GCM() 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 + // If the CPU has AES acceleration, Go stdlib is faster + if CpuHasAES() { return false } - // On the Apple M1, Go stdlib is faster than OpenSSL, despite cpu.ARM64.HasAES - // reading false: https://github.com/rfjakob/gocryptfs/v2/issues/556#issuecomment-848079309 - if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { - return false - } - // OpenSSL is probably faster + // Otherwise OpenSSL is probably faster return true } + +// PreferOpenSSLXchacha20poly1305 returns true if OpenSSL Xchacha20poly1305 is +// faster than Go stdlib on this machine. +func PreferOpenSSLXchacha20poly1305() bool { + if BuiltWithoutOpenssl { + return false + } + // Go x/crypto has optimized assembly for amd64: + // https://github.com/golang/crypto/blob/master/chacha20poly1305/chacha20poly1305_amd64.s + if runtime.GOARCH == "amd64" { + return false + } + // On arm64 and arm, OpenSSL is faster. Probably everwhere else too. + return true +} + +// CpuHasAES tells you if the CPU we are running has AES acceleration that is +// usable by the Go crypto library. +func CpuHasAES() bool { + // Safe to call on other architectures - will just read false. + if cpu.X86.HasAES || cpu.ARM64.HasAES { + return true + } + // On the Apple M1, the CPU has AES acceleration, despite cpu.ARM64.HasAES + // reading false: https://github.com/rfjakob/gocryptfs/issues/556#issuecomment-848079309 + if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { + return true + } + return false +} diff --git a/internal/stupidgcm/stupidgcm.go b/internal/stupidgcm/stupidgcm.go deleted file mode 100644 index 01db41b..0000000 --- a/internal/stupidgcm/stupidgcm.go +++ /dev/null @@ -1,249 +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 -// #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 { - 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 -} diff --git a/internal/stupidgcm/xchacha.go b/internal/stupidgcm/xchacha.go new file mode 100644 index 0000000..ca740e4 --- /dev/null +++ b/internal/stupidgcm/xchacha.go @@ -0,0 +1,117 @@ +// +build !without_openssl + +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Copied from +// https://github.com/golang/crypto/blob/32db794688a5a24a23a43f2a984cecd5b3d8da58/chacha20poly1305/xchacha20poly1305.go +// and adapted for stupidgcm by @rfjakob. + +package stupidgcm + +import ( + "crypto/cipher" + "errors" + "log" + + "golang.org/x/crypto/chacha20" + "golang.org/x/crypto/chacha20poly1305" +) + +type stupidXchacha20poly1305 struct { + // array instead of byte slice like + // `struct xchacha20poly1305` in x/crypto/chacha20poly1305 + key [chacha20poly1305.KeySize]byte + wiped bool +} + +// NewXchacha20poly1305 returns a XChaCha20-Poly1305 cipher that satisfied the +// cipher.AEAD interface. +// +// XChaCha20-Poly1305 is a ChaCha20-Poly1305 variant that takes a longer nonce, +// suitable to be generated randomly without risk of collisions. It should be +// preferred when nonce uniqueness cannot be trivially ensured, or whenever +// nonces are randomly generated. +// +// Only 32-bytes keys and 24-byte IVs are supported. +func NewXchacha20poly1305(key []byte) cipher.AEAD { + if len(key) != chacha20poly1305.KeySize { + log.Panic("bad key length") + } + ret := new(stupidXchacha20poly1305) + copy(ret.key[:], key) + return ret +} + +func (*stupidXchacha20poly1305) NonceSize() int { + return chacha20poly1305.NonceSizeX +} + +func (*stupidXchacha20poly1305) Overhead() int { + return tagLen +} + +func (x *stupidXchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte { + if x.wiped { + log.Panic("BUG: tried to use wiped key") + } + if len(nonce) != chacha20poly1305.NonceSizeX { + log.Panic("bad nonce length passed to Seal") + } + + // XChaCha20-Poly1305 technically supports a 64-bit counter, so there is no + // size limit. However, since we reuse the ChaCha20-Poly1305 implementation, + // the second half of the counter is not available. This is unlikely to be + // an issue because the cipher.AEAD API requires the entire message to be in + // memory, and the counter overflows at 256 GB. + if uint64(len(plaintext)) > (1<<38)-64 { + log.Panic("plaintext too large") + } + + hKey, _ := chacha20.HChaCha20(x.key[:], nonce[0:16]) + c := NewChacha20poly1305(hKey).(*stupidChacha20poly1305) + defer c.Wipe() + + // The first 4 bytes of the final nonce are unused counter space. + cNonce := make([]byte, chacha20poly1305.NonceSize) + copy(cNonce[4:12], nonce[16:24]) + + return c.Seal(dst, cNonce[:], plaintext, additionalData) +} + +func (x *stupidXchacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { + if x.wiped { + log.Panic("BUG: tried to use wiped key") + } + if len(nonce) != chacha20poly1305.NonceSizeX { + log.Panic("bad nonce length passed to Open") + } + if len(ciphertext) < 16 { + return nil, errors.New("message too short") + } + if uint64(len(ciphertext)) > (1<<38)-48 { + log.Panic("ciphertext too large") + } + + hKey, _ := chacha20.HChaCha20(x.key[:], nonce[0:16]) + c := NewChacha20poly1305(hKey).(*stupidChacha20poly1305) + defer c.Wipe() + + // The first 4 bytes of the final nonce are unused counter space. + cNonce := make([]byte, chacha20poly1305.NonceSize) + copy(cNonce[4:12], nonce[16:24]) + + return c.Open(dst, cNonce[:], ciphertext, additionalData) +} + +// Wipe tries to wipe the key from memory by overwriting it with zeros. +// +// This is not bulletproof due to possible GC copies, but +// still raises the bar for extracting the key. +func (g *stupidXchacha20poly1305) Wipe() { + g.wiped = true + for i := range g.key { + g.key[i] = 0 + } +} diff --git a/internal/syscallcompat/eintr.go b/internal/syscallcompat/eintr.go index 2e2bb18..cdde806 100644 --- a/internal/syscallcompat/eintr.go +++ b/internal/syscallcompat/eintr.go @@ -12,7 +12,7 @@ import ( // https://github.com/golang/go/blob/d2a80f3fb5b44450e0b304ac5a718f99c053d82a/src/os/file_posix.go#L243 // // This is needed because CIFS throws lots of EINTR errors: -// https://github.com/rfjakob/gocryptfs/v2/issues/483 +// https://github.com/rfjakob/gocryptfs/issues/483 // // Don't use retryEINTR() with syscall.Close()! // See https://code.google.com/p/chromium/issues/detail?id=269623 . diff --git a/internal/syscallcompat/getdents_linux.go b/internal/syscallcompat/getdents_linux.go index 5fa2a82..df886b4 100644 --- a/internal/syscallcompat/getdents_linux.go +++ b/internal/syscallcompat/getdents_linux.go @@ -19,7 +19,7 @@ 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/v2/issues/197 for details. +// See https://github.com/rfjakob/gocryptfs/issues/197 for details. const maxReclen = 280 type DirEntry struct { diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go index 70beb5f..d0fa027 100644 --- a/internal/syscallcompat/sys_linux.go +++ b/internal/syscallcompat/sys_linux.go @@ -35,7 +35,7 @@ func EnospcPrealloc(fd int, off int64, len int64) (err error) { } if err == syscall.EOPNOTSUPP { // ZFS and ext3 do not support fallocate. Warn but continue anyway. - // https://github.com/rfjakob/gocryptfs/v2/issues/22 + // https://github.com/rfjakob/gocryptfs/issues/22 return nil } return err diff --git a/main.go b/main.go index 38dd16d..a663e02 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,6 @@ +// gocryptfs is an encrypted overlay filesystem written in Go. +// See README.md ( https://github.com/rfjakob/gocryptfs/blob/master/README.md ) +// and the official website ( https://nuetzlich.net/gocryptfs/ ) for details. package main func main() {} diff --git a/volume.go b/volume.go index 52ff4d1..15d0a82 100644 --- a/volume.go +++ b/volume.go @@ -64,18 +64,25 @@ func registerNewVolume(rootCipherDir string, masterkey []byte, cf *configfile.Co newVolume.plainTextNames = cf.IsFeatureFlagSet(configfile.FlagPlaintextNames) - // Init crypto backend - cryptoBackend := cryptocore.BackendGoGCM - if cf.IsFeatureFlagSet(configfile.FlagAESSIV) { - cryptoBackend = cryptocore.BackendAESSIV - } else if stupidgcm.PreferOpenSSL() { + cryptoBackend, err := cf.ContentEncryption() + if err != nil { + return -1 + } + if cryptoBackend == cryptocore.BackendXChaCha20Poly1305 && stupidgcm.PreferOpenSSLXchacha20poly1305() { + cryptoBackend = cryptocore.BackendXChaCha20Poly1305OpenSSL + } else if cryptoBackend == cryptocore.BackendGoGCM && stupidgcm.PreferOpenSSLAES256GCM() { cryptoBackend = cryptocore.BackendOpenSSL } - forcedecode := false - newVolume.cryptoCore = cryptocore.New(masterkey, cryptoBackend, contentenc.DefaultIVBits, true, forcedecode) - newVolume.contentEnc = contentenc.New(newVolume.cryptoCore, contentenc.DefaultBS, forcedecode) + newVolume.cryptoCore = cryptocore.New(masterkey, cryptoBackend, cryptoBackend.NonceSize*8, cf.IsFeatureFlagSet(configfile.FlagHKDF)) + newVolume.contentEnc = contentenc.New(newVolume.cryptoCore, contentenc.DefaultBS) var badname []string - newVolume.nameTransform = nametransform.New(newVolume.cryptoCore.EMECipher, true, true, badname, false) + newVolume.nameTransform = nametransform.New( + newVolume.cryptoCore.EMECipher, + true, + cf.IsFeatureFlagSet(configfile.FlagRaw64), + badname, + !cf.IsFeatureFlagSet(configfile.FlagDirIV), + ) //copying rootCipherDir var grcd strings.Builder @@ -155,7 +162,16 @@ func gcf_change_password(rootCipherDir string, oldPassword, givenScryptHash, new } //export gcf_create_volume -func gcf_create_volume(rootCipherDir string, password []byte, plaintextNames bool, logN int, creator string) bool { +func gcf_create_volume(rootCipherDir string, password []byte, plaintextNames bool, xchacha int8, logN int, creator string) bool { + var useXChaCha bool + switch xchacha { + case 1: + useXChaCha = true + case 0: + useXChaCha = false + default: + useXChaCha = !stupidgcm.CpuHasAES() + } err := configfile.Create(&configfile.CreateArgs{ Filename: filepath.Join(rootCipherDir, configfile.ConfDefaultName), Password: password, @@ -164,7 +180,7 @@ func gcf_create_volume(rootCipherDir string, password []byte, plaintextNames boo Creator: creator, AESSIV: false, DeterministicNames: false, - XChaCha20Poly1305: false, + XChaCha20Poly1305: useXChaCha, }) if err == nil { if plaintextNames {