mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-27 04:21:11 +00:00 
			
		
		
		
	Harden the current checks in place, I doubt these will ever hit (you can prove easily by reading the current source code this cannot happen) but just in case a new Go version does something weird or something else goes catastrophicly wrong, this should add an extra defense-in-depth layer. `n != aeadKeySize` will panic a nil error, don't think it's needed to add more logic to this, a nil error is enough to indicate that that condition failed (given the other condition is `err != nil`). Also move constant integers to being `const`, this helps reducing the amount of instructions being done for the extra check.
		
			
				
	
	
		
			132 lines
		
	
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			132 lines
		
	
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2024 The Forgejo Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| // Keying is a module that allows for subkeys to be deterministically generated
 | |
| // from the same master key. It allows for domain separation 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
 | |
| 	// The pseudorandom key generated by HKDF-Extract.
 | |
| 	prk []byte
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	aeadKeySize   = chacha20poly1305.KeySize
 | |
| 	aeadNonceSize = chacha20poly1305.NonceSizeX
 | |
| )
 | |
| 
 | |
| // 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
 | |
| 
 | |
| var (
 | |
| 	// Used for the `push_mirror` table.
 | |
| 	ContextPushMirror Context = "pushmirror"
 | |
| 	// Used for the `two_factor` table.
 | |
| 	ContextTOTP Context = "totp"
 | |
| )
 | |
| 
 | |
| // Derive *the* key for a given context, this is a deterministic function.
 | |
| // The same key will be provided for the same context.
 | |
| func DeriveKey(context Context) *Key {
 | |
| 	if len(prk) != sha256.Size {
 | |
| 		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 n, err := r.Read(key); err != nil || n != aeadKeySize {
 | |
| 		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 derived; this allows for more granularity 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 n, err := rand.Read(nonce); err != nil || n != aeadNonceSize {
 | |
| 		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))
 | |
| }
 |