diff options
| author | Shulhan <ms@kilabit.info> | 2023-09-24 02:13:15 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2023-09-26 00:24:08 +0700 |
| commit | 9ae9a42e37b35e17120045da8bb72b07f6de2a44 (patch) | |
| tree | 32613a49f0d0f1e2d68ab6475745f791dba9cb37 /crypto_context.go | |
| parent | 8cc52027d243946c03c6b0d1016ca7cc3d7de09a (diff) | |
| download | awwan-9ae9a42e37b35e17120045da8bb72b07f6de2a44.tar.xz | |
all: move fields and methods related to encryption to struct cryptoContext
The cryptoContext contains the default hash, loaded privateKey, dummy
terminal, base directory, and default label; all of those fields are
required for encryption and decryption.
The cryptoContext have three methods: encrypt, decrypt, and
loadPrivateKey.
By moving to separate struct the cryptoContext instance can be shared
with Session.
Diffstat (limited to 'crypto_context.go')
| -rw-r--r-- | crypto_context.go | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/crypto_context.go b/crypto_context.go new file mode 100644 index 0000000..777134c --- /dev/null +++ b/crypto_context.go @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: 2023 M. Shulhan <ms@kilabit.info> +// SPDX-License-Identifier: GPL-3.0-or-later + +package awwan + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "errors" + "fmt" + "hash" + "io" + "io/fs" + "os" + "path/filepath" + + libcrypto "github.com/shuLhan/share/lib/crypto" +) + +// errPrivateKeyMissing returned when private key file is missing or not +// loaded when command require loading encrypted file. +var errPrivateKeyMissing = errors.New(`private key is missing or not loaded`) + +// cryptoContext hold fields and operation for encryption and decryption. +type cryptoContext struct { + hash hash.Hash + + // privateKey the key for encrypt and decrypt command. + privateKey *rsa.PrivateKey + + // termrw the ReadWriter to prompt and read passphrase for + // privateKey. + // This field should be nil, only used during testing. + termrw io.ReadWriter + + baseDir string + + label []byte +} + +func newCryptoContext(baseDir string) (cryptoc *cryptoContext) { + cryptoc = &cryptoContext{ + hash: sha256.New(), + baseDir: baseDir, + label: []byte(`awwan`), + } + return cryptoc +} + +func (cryptoc *cryptoContext) decrypt(cipher []byte) (plain []byte, err error) { + if cryptoc.privateKey == nil { + err = cryptoc.loadPrivateKey() + if err != nil { + return nil, err + } + if cryptoc.privateKey == nil { + return nil, errPrivateKeyMissing + } + } + + plain, err = libcrypto.DecryptOaep(cryptoc.hash, rand.Reader, + cryptoc.privateKey, cipher, cryptoc.label) + if err != nil { + return nil, err + } + + return plain, nil +} + +func (cryptoc *cryptoContext) encrypt(plain []byte) (cipher []byte, err error) { + if cryptoc.privateKey == nil { + err = cryptoc.loadPrivateKey() + if err != nil { + return nil, err + } + if cryptoc.privateKey == nil { + return nil, errPrivateKeyMissing + } + } + + cipher, err = libcrypto.EncryptOaep(cryptoc.hash, rand.Reader, + &cryptoc.privateKey.PublicKey, plain, cryptoc.label) + if err != nil { + return nil, err + } + + return cipher, nil +} + +// loadPrivateKey from file "{{baseDir}}/.awwan.key" if its exist. +func (cryptoc *cryptoContext) loadPrivateKey() (err error) { + var ( + fileKey = filepath.Join(cryptoc.baseDir, defFilePrivateKey) + + pkey crypto.PrivateKey + ok bool + ) + + _, err = os.Stat(fileKey) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return nil + } + return err + } + + fmt.Printf("--- Loading private key file %q (enter to skip passphrase) ...\n", fileKey) + + pkey, err = libcrypto.LoadPrivateKeyInteractive(cryptoc.termrw, fileKey) + if err != nil { + if errors.Is(err, libcrypto.ErrEmptyPassphrase) { + // Ignore empty passphrase error, in case the + // command does not need to decrypt files when + // running. + return nil + } + return err + } + + cryptoc.privateKey, ok = pkey.(*rsa.PrivateKey) + if !ok { + return fmt.Errorf(`the private key type must be RSA, got %T`, pkey) + } + + return nil +} |
