diff options
author | Daniel Baumann <daniel@debian.org> | 2024-10-18 20:33:49 +0200 |
---|---|---|
committer | Daniel Baumann <daniel@debian.org> | 2024-12-12 23:57:56 +0100 |
commit | e68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch) | |
tree | 97775d6c13b0f416af55314eb6a89ef792474615 /modules/keying/keying.go | |
parent | Initial commit. (diff) | |
download | forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip |
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'modules/keying/keying.go')
-rw-r--r-- | modules/keying/keying.go | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/modules/keying/keying.go b/modules/keying/keying.go new file mode 100644 index 0000000..7c595c7 --- /dev/null +++ b/modules/keying/keying.go @@ -0,0 +1,125 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +// Keying is a module that allows for subkeys to be determistically generated +// from the same master key. It allows for domain seperation to take place by +// using new keys for new subsystems/domains. These subkeys are provided with +// an API to encrypt and decrypt data. The module panics if a bad interaction +// happened, the panic should be seen as an non-recoverable error. +// +// HKDF (per RFC 5869) is used to derive new subkeys in a safe manner. It +// provides a KDF security property, which is required for Forgejo, as the +// secret key would be an ASCII string and isn't a random uniform bit string. +// XChaCha-Poly1305 (per draft-irtf-cfrg-xchacha-01) is used as AEAD to encrypt +// and decrypt messages. A new fresh random nonce is generated for every +// encryption. The nonce gets prepended to the ciphertext. +package keying + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/binary" + + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/hkdf" +) + +var ( + // The hash used for HKDF. + hash = sha256.New + // The AEAD used for encryption/decryption. + aead = chacha20poly1305.NewX + aeadKeySize = chacha20poly1305.KeySize + aeadNonceSize = chacha20poly1305.NonceSizeX + // The pseudorandom key generated by HKDF-Extract. + prk []byte +) + +// Set the main IKM for this module. +func Init(ikm []byte) { + // Salt is intentionally left empty, it's not useful to Forgejo's use case. + prk = hkdf.Extract(hash, ikm, nil) +} + +// Specifies the context for which a subkey should be derived for. +// This must be a hardcoded string and must not be arbitrarily constructed. +type Context string + +// Used for the `push_mirror` table. +var ContextPushMirror Context = "pushmirror" + +// Derive *the* key for a given context, this is a determistic function. The +// same key will be provided for the same context. +func DeriveKey(context Context) *Key { + if len(prk) == 0 { + panic("keying: not initialized") + } + + r := hkdf.Expand(hash, prk, []byte(context)) + + key := make([]byte, aeadKeySize) + // This should never return an error, but if it does, panic. + if _, err := r.Read(key); err != nil { + panic(err) + } + + return &Key{key} +} + +type Key struct { + key []byte +} + +// Encrypts the specified plaintext with some additional data that is tied to +// this plaintext. The additional data can be seen as the context in which the +// data is being encrypted for, this is different than the context for which the +// key was derrived this allows for more granuality without deriving new keys. +// Avoid any user-generated data to be passed into the additional data. The most +// common usage of this would be to encrypt a database field, in that case use +// the ID and database column name as additional data. The additional data isn't +// appended to the ciphertext and may be publicly known, it must be available +// when decryping the ciphertext. +func (k *Key) Encrypt(plaintext, additionalData []byte) []byte { + // Construct a new AEAD with the key. + e, err := aead(k.key) + if err != nil { + panic(err) + } + + // Generate a random nonce. + nonce := make([]byte, aeadNonceSize) + if _, err := rand.Read(nonce); err != nil { + panic(err) + } + + // Returns the ciphertext of this plaintext. + return e.Seal(nonce, nonce, plaintext, additionalData) +} + +// Decrypts the ciphertext and authenticates it against the given additional +// data that was given when it was encrypted. It returns an error if the +// authentication failed. +func (k *Key) Decrypt(ciphertext, additionalData []byte) ([]byte, error) { + if len(ciphertext) <= aeadNonceSize { + panic("keying: ciphertext is too short") + } + + e, err := aead(k.key) + if err != nil { + panic(err) + } + + nonce, ciphertext := ciphertext[:aeadNonceSize], ciphertext[aeadNonceSize:] + + return e.Open(nil, nonce, ciphertext, additionalData) +} + +// ColumnAndID generates a context that can be used as additional context for +// encrypting and decrypting data. It requires the column name and the row ID +// (this requires to be known beforehand). Be careful when using this, as the +// table name isn't part of this context. This means it's not bound to a +// particular table. The table should be part of the context that the key was +// derived for, in which case it binds through that. +func ColumnAndID(column string, id int64) []byte { + return binary.BigEndian.AppendUint64(append([]byte(column), ':'), uint64(id)) +} |