 9bdcbe0447
			
		
	
	9bdcbe0447
	
	
	
		
			
			Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			209 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			209 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2019 The age Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package age
 | |
| 
 | |
| import (
 | |
| 	"crypto/rand"
 | |
| 	"crypto/sha256"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"strings"
 | |
| 
 | |
| 	"filippo.io/age/internal/bech32"
 | |
| 	"filippo.io/age/internal/format"
 | |
| 	"golang.org/x/crypto/chacha20poly1305"
 | |
| 	"golang.org/x/crypto/curve25519"
 | |
| 	"golang.org/x/crypto/hkdf"
 | |
| )
 | |
| 
 | |
| const x25519Label = "age-encryption.org/v1/X25519"
 | |
| 
 | |
| // X25519Recipient is the standard age public key. Messages encrypted to this
 | |
| // recipient can be decrypted with the corresponding X25519Identity.
 | |
| //
 | |
| // This recipient is anonymous, in the sense that an attacker can't tell from
 | |
| // the message alone if it is encrypted to a certain recipient.
 | |
| type X25519Recipient struct {
 | |
| 	theirPublicKey []byte
 | |
| }
 | |
| 
 | |
| var _ Recipient = &X25519Recipient{}
 | |
| 
 | |
| // newX25519RecipientFromPoint returns a new X25519Recipient from a raw Curve25519 point.
 | |
| func newX25519RecipientFromPoint(publicKey []byte) (*X25519Recipient, error) {
 | |
| 	if len(publicKey) != curve25519.PointSize {
 | |
| 		return nil, errors.New("invalid X25519 public key")
 | |
| 	}
 | |
| 	r := &X25519Recipient{
 | |
| 		theirPublicKey: make([]byte, curve25519.PointSize),
 | |
| 	}
 | |
| 	copy(r.theirPublicKey, publicKey)
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| // ParseX25519Recipient returns a new X25519Recipient from a Bech32 public key
 | |
| // encoding with the "age1" prefix.
 | |
| func ParseX25519Recipient(s string) (*X25519Recipient, error) {
 | |
| 	t, k, err := bech32.Decode(s)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("malformed recipient %q: %v", s, err)
 | |
| 	}
 | |
| 	if t != "age" {
 | |
| 		return nil, fmt.Errorf("malformed recipient %q: invalid type %q", s, t)
 | |
| 	}
 | |
| 	r, err := newX25519RecipientFromPoint(k)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("malformed recipient %q: %v", s, err)
 | |
| 	}
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| func (r *X25519Recipient) Wrap(fileKey []byte) ([]*Stanza, error) {
 | |
| 	ephemeral := make([]byte, curve25519.ScalarSize)
 | |
| 	if _, err := rand.Read(ephemeral); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	ourPublicKey, err := curve25519.X25519(ephemeral, curve25519.Basepoint)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	sharedSecret, err := curve25519.X25519(ephemeral, r.theirPublicKey)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	l := &Stanza{
 | |
| 		Type: "X25519",
 | |
| 		Args: []string{format.EncodeToString(ourPublicKey)},
 | |
| 	}
 | |
| 
 | |
| 	salt := make([]byte, 0, len(ourPublicKey)+len(r.theirPublicKey))
 | |
| 	salt = append(salt, ourPublicKey...)
 | |
| 	salt = append(salt, r.theirPublicKey...)
 | |
| 	h := hkdf.New(sha256.New, sharedSecret, salt, []byte(x25519Label))
 | |
| 	wrappingKey := make([]byte, chacha20poly1305.KeySize)
 | |
| 	if _, err := io.ReadFull(h, wrappingKey); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	wrappedKey, err := aeadEncrypt(wrappingKey, fileKey)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	l.Body = wrappedKey
 | |
| 
 | |
| 	return []*Stanza{l}, nil
 | |
| }
 | |
| 
 | |
| // String returns the Bech32 public key encoding of r.
 | |
| func (r *X25519Recipient) String() string {
 | |
| 	s, _ := bech32.Encode("age", r.theirPublicKey)
 | |
| 	return s
 | |
| }
 | |
| 
 | |
| // X25519Identity is the standard age private key, which can decrypt messages
 | |
| // encrypted to the corresponding X25519Recipient.
 | |
| type X25519Identity struct {
 | |
| 	secretKey, ourPublicKey []byte
 | |
| }
 | |
| 
 | |
| var _ Identity = &X25519Identity{}
 | |
| 
 | |
| // newX25519IdentityFromScalar returns a new X25519Identity from a raw Curve25519 scalar.
 | |
| func newX25519IdentityFromScalar(secretKey []byte) (*X25519Identity, error) {
 | |
| 	if len(secretKey) != curve25519.ScalarSize {
 | |
| 		return nil, errors.New("invalid X25519 secret key")
 | |
| 	}
 | |
| 	i := &X25519Identity{
 | |
| 		secretKey: make([]byte, curve25519.ScalarSize),
 | |
| 	}
 | |
| 	copy(i.secretKey, secretKey)
 | |
| 	i.ourPublicKey, _ = curve25519.X25519(i.secretKey, curve25519.Basepoint)
 | |
| 	return i, nil
 | |
| }
 | |
| 
 | |
| // GenerateX25519Identity randomly generates a new X25519Identity.
 | |
| func GenerateX25519Identity() (*X25519Identity, error) {
 | |
| 	secretKey := make([]byte, curve25519.ScalarSize)
 | |
| 	if _, err := rand.Read(secretKey); err != nil {
 | |
| 		return nil, fmt.Errorf("internal error: %v", err)
 | |
| 	}
 | |
| 	return newX25519IdentityFromScalar(secretKey)
 | |
| }
 | |
| 
 | |
| // ParseX25519Identity returns a new X25519Identity from a Bech32 private key
 | |
| // encoding with the "AGE-SECRET-KEY-1" prefix.
 | |
| func ParseX25519Identity(s string) (*X25519Identity, error) {
 | |
| 	t, k, err := bech32.Decode(s)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("malformed secret key: %v", err)
 | |
| 	}
 | |
| 	if t != "AGE-SECRET-KEY-" {
 | |
| 		return nil, fmt.Errorf("malformed secret key: unknown type %q", t)
 | |
| 	}
 | |
| 	r, err := newX25519IdentityFromScalar(k)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("malformed secret key: %v", err)
 | |
| 	}
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| func (i *X25519Identity) Unwrap(stanzas []*Stanza) ([]byte, error) {
 | |
| 	return multiUnwrap(i.unwrap, stanzas)
 | |
| }
 | |
| 
 | |
| func (i *X25519Identity) unwrap(block *Stanza) ([]byte, error) {
 | |
| 	if block.Type != "X25519" {
 | |
| 		return nil, ErrIncorrectIdentity
 | |
| 	}
 | |
| 	if len(block.Args) != 1 {
 | |
| 		return nil, errors.New("invalid X25519 recipient block")
 | |
| 	}
 | |
| 	publicKey, err := format.DecodeString(block.Args[0])
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to parse X25519 recipient: %v", err)
 | |
| 	}
 | |
| 	if len(publicKey) != curve25519.PointSize {
 | |
| 		return nil, errors.New("invalid X25519 recipient block")
 | |
| 	}
 | |
| 
 | |
| 	sharedSecret, err := curve25519.X25519(i.secretKey, publicKey)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("invalid X25519 recipient: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	salt := make([]byte, 0, len(publicKey)+len(i.ourPublicKey))
 | |
| 	salt = append(salt, publicKey...)
 | |
| 	salt = append(salt, i.ourPublicKey...)
 | |
| 	h := hkdf.New(sha256.New, sharedSecret, salt, []byte(x25519Label))
 | |
| 	wrappingKey := make([]byte, chacha20poly1305.KeySize)
 | |
| 	if _, err := io.ReadFull(h, wrappingKey); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	fileKey, err := aeadDecrypt(wrappingKey, fileKeySize, block.Body)
 | |
| 	if err == errIncorrectCiphertextSize {
 | |
| 		return nil, errors.New("invalid X25519 recipient block: incorrect file key size")
 | |
| 	} else if err != nil {
 | |
| 		return nil, ErrIncorrectIdentity
 | |
| 	}
 | |
| 	return fileKey, nil
 | |
| }
 | |
| 
 | |
| // Recipient returns the public X25519Recipient value corresponding to i.
 | |
| func (i *X25519Identity) Recipient() *X25519Recipient {
 | |
| 	r := &X25519Recipient{}
 | |
| 	r.theirPublicKey = i.ourPublicKey
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| // String returns the Bech32 private key encoding of i.
 | |
| func (i *X25519Identity) String() string {
 | |
| 	s, _ := bech32.Encode("AGE-SECRET-KEY-", i.secretKey)
 | |
| 	return strings.ToUpper(s)
 | |
| }
 |