mirror of
https://github.com/rwinkhart/rcw.git
synced 2026-08-28 04:46:42 -04:00
Rename all occurrences of "passphrase" to "password"
This commit is contained in:
@@ -3,13 +3,13 @@ RCW is a cascading symmetric cryptography agent meant to be embedded within Go p
|
|||||||
|
|
||||||
It encrypts all data with both AES256-GCM and ChaCha20-Poly1305.
|
It encrypts all data with both AES256-GCM and ChaCha20-Poly1305.
|
||||||
|
|
||||||
Passphrases are securely cached for three minutes and RPC authentication is used to
|
Passwords are securely cached for three minutes and RPC authentication is used to
|
||||||
ensure that only the binary+user responsible for caching the passphrase can utilize it.
|
ensure that only the binary+user responsible for caching the password can utilize it.
|
||||||
This feature is supported on Linux, FreeBSD, MacOS, and Windows. It is disabled if
|
This feature is supported on Linux, FreeBSD, MacOS, and Windows. It is disabled if
|
||||||
built with `-tags=interactive`.
|
built with `-tags=interactive`.
|
||||||
|
|
||||||
RCW also features a sanity check to ensure no data loss occurs due to a user entering the
|
RCW also features a sanity check to ensure no data loss occurs due to a user entering the
|
||||||
incorrect passphrase during encryption.
|
incorrect password during encryption.
|
||||||
|
|
||||||
Please note that RCW is a work-in-progress and breaking changes should be expected.
|
Please note that RCW is a work-in-progress and breaking changes should be expected.
|
||||||
Future versions may not be capable of decrypting the output of the current version.
|
Future versions may not be capable of decrypting the output of the current version.
|
||||||
|
|||||||
+5
-5
@@ -11,16 +11,16 @@ import (
|
|||||||
var Timeout = 300 // seconds for RPC server timeout; configurable
|
var Timeout = 300 // seconds for RPC server timeout; configurable
|
||||||
|
|
||||||
var daemonHash []byte
|
var daemonHash []byte
|
||||||
var globalPassphrase []byte
|
var globalPassword []byte
|
||||||
|
|
||||||
// RCWService provides an RPC method.
|
// RCWService provides an RPC method.
|
||||||
type RCWService struct{}
|
type RCWService struct{}
|
||||||
|
|
||||||
// DecryptRequest is the RPC method that decrypts the incoming data using
|
// DecryptRequest is the RPC method that decrypts the incoming data using
|
||||||
// the global passphrase and returns the decrypted data
|
// the global password and returns the decrypted data
|
||||||
func (h *RCWService) DecryptRequest(encBytes []byte, reply *[]byte) error {
|
func (h *RCWService) DecryptRequest(encBytes []byte, reply *[]byte) error {
|
||||||
var err error
|
var err error
|
||||||
*reply, err = wrappers.DecryptAndZeroizePassphrase(encBytes, append([]byte{}, globalPassphrase...)) // pass new slice to avoid zeroizing cached passphrase)
|
*reply, err = wrappers.DecryptAndZeroizePassword(encBytes, append([]byte{}, globalPassword...)) // pass new slice to avoid zeroizing cached password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -28,9 +28,9 @@ func (h *RCWService) DecryptRequest(encBytes []byte, reply *[]byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EncryptRequestAndZeroizeDecBytes is the RPC method that encrypts the incoming data using
|
// EncryptRequestAndZeroizeDecBytes is the RPC method that encrypts the incoming data using
|
||||||
// the global passphrase and returns the encrypted data
|
// the global password and returns the encrypted data
|
||||||
func (h *RCWService) EncryptRequestAndZeroizeDecBytes(decBytes []byte, reply *[]byte) error {
|
func (h *RCWService) EncryptRequestAndZeroizeDecBytes(decBytes []byte, reply *[]byte) error {
|
||||||
*reply = wrappers.EncryptAndZeroizeDecBytesAndPassphrase(decBytes, append([]byte{}, globalPassphrase...)) // pass new slice to avoid zeroizing cached passphrase
|
*reply = wrappers.EncryptAndZeroizeDecBytesAndPassword(decBytes, append([]byte{}, globalPassword...)) // pass new slice to avoid zeroizing cached password
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ import (
|
|||||||
|
|
||||||
// Start is the entry point for the RPC server responsible for
|
// Start is the entry point for the RPC server responsible for
|
||||||
// returning decrypted data to authenticated clients.
|
// returning decrypted data to authenticated clients.
|
||||||
func Start(passphrase []byte) {
|
func Start(password []byte) {
|
||||||
// store passphrase to be referenced by DecryptRequest method
|
// store password to be referenced by DecryptRequest method
|
||||||
globalPassphrase = passphrase
|
globalPassword = password
|
||||||
|
|
||||||
// register RCWService with the RPC package
|
// register RCWService with the RPC package
|
||||||
if err := rpc.Register(&RCWService{}); err != nil {
|
if err := rpc.Register(&RCWService{}); err != nil {
|
||||||
@@ -69,9 +69,9 @@ func Start(passphrase []byte) {
|
|||||||
// handleConn verifies the identity of the client.
|
// handleConn verifies the identity of the client.
|
||||||
// It uses the file descriptor of the connection to get the PID of the client,
|
// It uses the file descriptor of the connection to get the PID of the client,
|
||||||
// which is then used to get the path of the client's executable and calculate its hash.
|
// which is then used to get the path of the client's executable and calculate its hash.
|
||||||
// The passphrase is only returned if the client's executable hash matches the daemon's hash
|
// The password is only returned if the client's executable hash matches the daemon's hash
|
||||||
// and if the request is coming from the same user.
|
// and if the request is coming from the same user.
|
||||||
// This ensures that only the binary the daemon is embedded in can retrieve the passphrase.
|
// This ensures that only the binary the daemon is embedded in can retrieve the password.
|
||||||
func handleConn(conn net.Conn, sigChan chan os.Signal) {
|
func handleConn(conn net.Conn, sigChan chan os.Signal) {
|
||||||
ucred := peercred.Get(conn)
|
ucred := peercred.Get(conn)
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ const (
|
|||||||
|
|
||||||
// Start is the entry point for the RPC server responsible for
|
// Start is the entry point for the RPC server responsible for
|
||||||
// returning decrypted data to authenticated clients.
|
// returning decrypted data to authenticated clients.
|
||||||
func Start(passphrase []byte) {
|
func Start(password []byte) {
|
||||||
// store passphrase to be referenced by DecryptRequest method
|
// store password to be referenced by DecryptRequest method
|
||||||
globalPassphrase = passphrase
|
globalPassword = password
|
||||||
|
|
||||||
// register RCWService with the RPC package
|
// register RCWService with the RPC package
|
||||||
if err := rpc.Register(&RCWService{}); err != nil {
|
if err := rpc.Register(&RCWService{}); err != nil {
|
||||||
|
|||||||
+13
-16
@@ -14,25 +14,25 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage:
|
// Usage:
|
||||||
// rcw init <passwd> : Generates the required sanity check file
|
// rcw init <passwd> : Generates the required sanity check file
|
||||||
// rcw <passphrase> : Runs the rcw daemon to decrypt data for three minutes
|
// rcw <password> : Runs the rcw daemon to decrypt data for three minutes
|
||||||
// rcw enc <text> : Encrypts the provided text and outputs the ciphertext to ex-cipher.rcw (attempts to use daemon, falls back to user input for passphrase)
|
// rcw enc <text> : Encrypts the provided text and outputs the ciphertext to ex-cipher.rcw (attempts to use daemon, falls back to user input for password)
|
||||||
// rcw dec : Decrypts ex-cipher.rcw and outputs the plaintext to stdout (attempts to use daemon, falls back to user input for passphrase)
|
// rcw dec : Decrypts ex-cipher.rcw and outputs the plaintext to stdout (attempts to use daemon, falls back to user input for password)
|
||||||
|
|
||||||
// Implementation Notes:
|
// Implementation Notes:
|
||||||
// There are two main ways to use the RCW library:
|
// There are two main ways to use the RCW library:
|
||||||
//
|
//
|
||||||
// 1. Daemon mode:
|
// 1. Daemon mode:
|
||||||
// The daemon is started with a passphrase and runs in the background.
|
// The daemon is started with a password and runs in the background.
|
||||||
// All encryption/decryption occurs in the daemon.
|
// All encryption/decryption occurs in the daemon.
|
||||||
// Avoid using the wrapper.Encrypt/Decrypt functions directly.
|
// Avoid using the wrapper.Encrypt/Decrypt functions directly.
|
||||||
// Instead, cache the passphrase with the daemon and use the daemon to encrypt/decrypt data.
|
// Instead, cache the password with the daemon and use the daemon to encrypt/decrypt data.
|
||||||
//
|
//
|
||||||
// 2. Standalone mode:
|
// 2. Standalone mode:
|
||||||
// The wrapper.Encrypt/Decrypt functions are used directly.
|
// The wrapper.Encrypt/Decrypt functions are used directly.
|
||||||
// The passphrase is provided directly to the functions.
|
// The password is provided directly to the functions.
|
||||||
//
|
//
|
||||||
// It is up to the client to perform the sanity check before encrypting data.
|
// It is up to the client to perform the sanity check before encrypting data.
|
||||||
// This means that when using the daemon to cache the passphrase, the client should
|
// This means that when using the daemon to cache the password, the client should
|
||||||
// perform the sanity check before activating the daemon.
|
// perform the sanity check before activating the daemon.
|
||||||
|
|
||||||
// TODO Tests:
|
// TODO Tests:
|
||||||
@@ -66,7 +66,7 @@ func main() {
|
|||||||
if daemon.IsOpen() {
|
if daemon.IsOpen() {
|
||||||
decBytes = daemon.GetDec(encBytes)
|
decBytes = daemon.GetDec(encBytes)
|
||||||
} else {
|
} else {
|
||||||
decBytes, err = wrappers.DecryptAndZeroizePassphrase(encBytes, front.InputHidden("Enter RCW passphrase:"))
|
decBytes, err = wrappers.DecryptAndZeroizePassword(encBytes, front.InputSecret("Enter RCW password:"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
@@ -81,8 +81,7 @@ func main() {
|
|||||||
fmt.Println("Daemon already running")
|
fmt.Println("Daemon already running")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := wrappers.RunSanityCheck(sanityFile, []byte(os.Args[1]))
|
if err := wrappers.RunSanityCheck(sanityFile, []byte(os.Args[1])); err != nil {
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -91,8 +90,7 @@ func main() {
|
|||||||
if os.Args[1] == "init" {
|
if os.Args[1] == "init" {
|
||||||
// create sanity check file
|
// create sanity check file
|
||||||
// rcw init <passwd>
|
// rcw init <passwd>
|
||||||
err := wrappers.GenSanityCheckAndZeroizePassphrase(sanityFile, []byte(os.Args[2]))
|
if err := wrappers.GenSanityCheckAndZeroizePassword(sanityFile, []byte(os.Args[2])); err != nil {
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -104,13 +102,12 @@ func main() {
|
|||||||
if daemon.IsOpen() {
|
if daemon.IsOpen() {
|
||||||
encBytes = daemon.GetEncAndZeroizeDecBytes(decBytes)
|
encBytes = daemon.GetEncAndZeroizeDecBytes(decBytes)
|
||||||
} else {
|
} else {
|
||||||
passphrase := front.InputHidden("Enter RCW passphrase: ")
|
password := front.InputSecret("Enter RCW password: ")
|
||||||
err := wrappers.RunSanityCheck(sanityFile, append([]byte{}, passphrase...)) // pass new slice to avoid zeroizing passphrase)
|
if err := wrappers.RunSanityCheck(sanityFile, append([]byte{}, password...)); err != nil { // pass new slice to avoid zeroizing password)
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
encBytes = wrappers.EncryptAndZeroizeDecBytesAndPassphrase(decBytes, passphrase)
|
encBytes = wrappers.EncryptAndZeroizeDecBytesAndPassword(decBytes, password)
|
||||||
}
|
}
|
||||||
os.WriteFile(outputFile, encBytes, 0600)
|
os.WriteFile(outputFile, encBytes, 0600)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ go 1.25.7
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.6.2
|
github.com/Microsoft/go-winio v0.6.2
|
||||||
github.com/rwinkhart/go-boilerplate v0.2.3-0.20260210031547-48e6abea8b2f
|
github.com/rwinkhart/go-boilerplate v0.2.3-0.20260211013046-29315c4b764f
|
||||||
github.com/rwinkhart/peercred-mini v0.1.3
|
github.com/rwinkhart/peercred-mini v0.1.3
|
||||||
golang.org/x/crypto v0.48.0
|
golang.org/x/crypto v0.48.0
|
||||||
golang.org/x/sys v0.41.0
|
golang.org/x/sys v0.41.0
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
github.com/rwinkhart/go-boilerplate v0.2.3-0.20260210031547-48e6abea8b2f h1:QloiAudLU8WAVs+loMU4ZiSd8ve0eiSkGtFhQUibhTw=
|
github.com/rwinkhart/go-boilerplate v0.2.3-0.20260210031547-48e6abea8b2f h1:QloiAudLU8WAVs+loMU4ZiSd8ve0eiSkGtFhQUibhTw=
|
||||||
github.com/rwinkhart/go-boilerplate v0.2.3-0.20260210031547-48e6abea8b2f/go.mod h1:ES13A2r9fnCVfyezwMBgY/RgA4pOIudOUXz3Jk/ikes=
|
github.com/rwinkhart/go-boilerplate v0.2.3-0.20260210031547-48e6abea8b2f/go.mod h1:ES13A2r9fnCVfyezwMBgY/RgA4pOIudOUXz3Jk/ikes=
|
||||||
|
github.com/rwinkhart/go-boilerplate v0.2.3-0.20260211013046-29315c4b764f h1:ITbAql2EG50U0Z9JnPqupvdZ5zp0i44LnqZUsdz6DNc=
|
||||||
|
github.com/rwinkhart/go-boilerplate v0.2.3-0.20260211013046-29315c4b764f/go.mod h1:ES13A2r9fnCVfyezwMBgY/RgA4pOIudOUXz3Jk/ikes=
|
||||||
github.com/rwinkhart/go-winio v0.1.1 h1:kAJKiqneR7cUR01Wn5/doAAV4kOGTEGPug4oinXc5N4=
|
github.com/rwinkhart/go-winio v0.1.1 h1:kAJKiqneR7cUR01Wn5/doAAV4kOGTEGPug4oinXc5N4=
|
||||||
github.com/rwinkhart/go-winio v0.1.1/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0=
|
github.com/rwinkhart/go-winio v0.1.1/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0=
|
||||||
github.com/rwinkhart/peercred-mini v0.1.3 h1:Pbyk/f4oXVtX38uuZAyxZZ8ZzRImL6WWgCr0pSktCKU=
|
github.com/rwinkhart/peercred-mini v0.1.3 h1:Pbyk/f4oXVtX38uuZAyxZZ8ZzRImL6WWgCr0pSktCKU=
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import (
|
|||||||
"github.com/rwinkhart/go-boilerplate/security"
|
"github.com/rwinkhart/go-boilerplate/security"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DecryptAndZeroizePassphrase decrypts the provided byte slice using the provided passphrase.
|
// DecryptAndZeroizePassword decrypts the provided byte slice using the provided password.
|
||||||
func DecryptAndZeroizePassphrase(encBytes, passphrase []byte) ([]byte, error) {
|
func DecryptAndZeroizePassword(encBytes, password []byte) ([]byte, error) {
|
||||||
if len(encBytes) < saltSize1 {
|
if len(encBytes) < saltSize1 {
|
||||||
return nil, errors.New("high-level decrypt: encrypted data is too short (invalid Argon2 salt)")
|
return nil, errors.New("high-level decrypt: encrypted data is too short (invalid Argon2 salt)")
|
||||||
}
|
}
|
||||||
salt1 := encBytes[:saltSize1]
|
salt1 := encBytes[:saltSize1]
|
||||||
encBytes = encBytes[saltSize1:]
|
encBytes = encBytes[saltSize1:]
|
||||||
key1 := derivePrimaryKey(passphrase, salt1)
|
key1 := derivePrimaryKey(password, salt1)
|
||||||
security.ZeroizeBytes(passphrase)
|
security.ZeroizeBytes(password)
|
||||||
security.ZeroizeBytes(salt1)
|
security.ZeroizeBytes(salt1)
|
||||||
var err error
|
var err error
|
||||||
encBytes, err = decryptCha(encBytes, key1)
|
encBytes, err = decryptCha(encBytes, key1)
|
||||||
@@ -29,15 +29,15 @@ func DecryptAndZeroizePassphrase(encBytes, passphrase []byte) ([]byte, error) {
|
|||||||
return encBytes, err
|
return encBytes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptAndZeroizeDecBytesAndPassphrase encrypts the provided byte slice using the provided passphrase.
|
// EncryptAndZeroizeDecBytesAndPassword encrypts the provided byte slice using the provided password.
|
||||||
func EncryptAndZeroizeDecBytesAndPassphrase(decBytes, passphrase []byte) []byte {
|
func EncryptAndZeroizeDecBytesAndPassword(decBytes, password []byte) []byte {
|
||||||
defer security.ZeroizeBytes(decBytes)
|
defer security.ZeroizeBytes(decBytes)
|
||||||
salt1 := getRandomBytes(saltSize1)
|
salt1 := getRandomBytes(saltSize1)
|
||||||
defer security.ZeroizeBytes(salt1)
|
defer security.ZeroizeBytes(salt1)
|
||||||
salt2AES := getRandomBytes(saltSize2)
|
salt2AES := getRandomBytes(saltSize2)
|
||||||
salt2Cha := getRandomBytes(saltSize2)
|
salt2Cha := getRandomBytes(saltSize2)
|
||||||
key1 := derivePrimaryKey(passphrase, salt1)
|
key1 := derivePrimaryKey(password, salt1)
|
||||||
security.ZeroizeBytes(passphrase)
|
security.ZeroizeBytes(password)
|
||||||
key2AES := deriveSecondaryKey(key1, salt2AES, []byte(hkdfInfoAES))
|
key2AES := deriveSecondaryKey(key1, salt2AES, []byte(hkdfInfoAES))
|
||||||
key2Cha := deriveSecondaryKey(key1, salt2Cha, []byte(hkdfInfoCha))
|
key2Cha := deriveSecondaryKey(key1, salt2Cha, []byte(hkdfInfoCha))
|
||||||
security.ZeroizeBytes(key1)
|
security.ZeroizeBytes(key1)
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ const (
|
|||||||
saltSize2 = 32 // 256 bits, recommended salt size for HKDF
|
saltSize2 = 32 // 256 bits, recommended salt size for HKDF
|
||||||
)
|
)
|
||||||
|
|
||||||
// derivePrimaryKey derives an encryption key from a passphrase using Argon2.
|
// derivePrimaryKey derives an encryption key from a password using Argon2.
|
||||||
// The resulting key is not meant to be used directly for encryption, but rather as a key to derive other keys.
|
// The resulting key is not meant to be used directly for encryption, but rather as a key to derive other keys.
|
||||||
func derivePrimaryKey(passphrase, salt []byte) []byte {
|
func derivePrimaryKey(password, salt []byte) []byte {
|
||||||
return argon2.IDKey(passphrase, salt, argonTime, argonMemory, argonThreads, keyLen)
|
return argon2.IDKey(password, salt, argonTime, argonMemory, argonThreads, keyLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deriveSecondaryKey derives a secondary key from the primary key using HKDF.
|
// deriveSecondaryKey derives a secondary key from the primary key using HKDF.
|
||||||
// It is meant to be an efficient way to derive multiple keys from a single passphrase.
|
// It is meant to be an efficient way to derive multiple keys from a single password.
|
||||||
func deriveSecondaryKey(primaryKey, salt, info []byte) []byte {
|
func deriveSecondaryKey(primaryKey, salt, info []byte) []byte {
|
||||||
h := hkdf.New(sha256.New, primaryKey, salt, info)
|
h := hkdf.New(sha256.New, primaryKey, salt, info)
|
||||||
derivedKey := make([]byte, keyLen)
|
derivedKey := make([]byte, keyLen)
|
||||||
|
|||||||
@@ -8,28 +8,28 @@ import (
|
|||||||
"github.com/rwinkhart/go-boilerplate/security"
|
"github.com/rwinkhart/go-boilerplate/security"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenSanityCheckAndZeroizePassphrase creates an encrypted file containing known plaintext
|
// GenSanityCheckAndZeroizePassword creates an encrypted file containing known plaintext
|
||||||
// to later be used for ensuring the user does not encrypt data with
|
// to later be used for ensuring the user does not encrypt data with
|
||||||
// an incorrect passphrase.
|
// an incorrect password.
|
||||||
func GenSanityCheckAndZeroizePassphrase(path string, passphrase []byte) error {
|
func GenSanityCheckAndZeroizePassword(path string, password []byte) error {
|
||||||
err := os.WriteFile(path, EncryptAndZeroizeDecBytesAndPassphrase([]byte("thx4usin'rcw"), passphrase), 0600)
|
err := os.WriteFile(path, EncryptAndZeroizeDecBytesAndPassword([]byte("thx4usin'rcw"), password), 0600)
|
||||||
security.ZeroizeBytes(passphrase)
|
security.ZeroizeBytes(password)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunSanityCheck should be run before any encryption operation
|
// RunSanityCheck should be run before any encryption operation
|
||||||
// to ensure the user does not encrypt data with an incorrect passphrase.
|
// to ensure the user does not encrypt data with an incorrect password.
|
||||||
// Failure to perform this check could result in data loss.
|
// Failure to perform this check could result in data loss.
|
||||||
func RunSanityCheck(path string, passphrase []byte) error {
|
func RunSanityCheck(path string, password []byte) error {
|
||||||
encBytes, err := os.ReadFile(path)
|
encBytes, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Failed to read sanity check file (" + path + ")")
|
return errors.New("Failed to read sanity check file (" + path + ")")
|
||||||
}
|
}
|
||||||
decBytes, err := DecryptAndZeroizePassphrase(encBytes, passphrase)
|
decBytes, err := DecryptAndZeroizePassword(encBytes, password)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if bytes.Equal(decBytes, []byte("thx4usin'rcw")) {
|
if bytes.Equal(decBytes, []byte("thx4usin'rcw")) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errors.New("sanity check failed (likely due to inconsistent passphrase)")
|
return errors.New("sanity check failed (likely due to inconsistent password)")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user