aboutsummaryrefslogtreecommitdiff
path: root/crypto_context.go
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2023-09-24 02:13:15 +0700
committerShulhan <ms@kilabit.info>2023-09-26 00:24:08 +0700
commit9ae9a42e37b35e17120045da8bb72b07f6de2a44 (patch)
tree32613a49f0d0f1e2d68ab6475745f791dba9cb37 /crypto_context.go
parent8cc52027d243946c03c6b0d1016ca7cc3d7de09a (diff)
downloadawwan-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.go128
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
+}