forked from TrueCloudLab/frostfs-s3-gw
81408dcc1c
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
232 lines
6.1 KiB
Go
232 lines
6.1 KiB
Go
package accessbox
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/cipher"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/nspcc-dev/neofs-api-go/pkg/session"
|
|
"github.com/nspcc-dev/neofs-api-go/pkg/token"
|
|
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
"golang.org/x/crypto/chacha20poly1305"
|
|
"golang.org/x/crypto/hkdf"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// GateData represents gate tokens in AccessBox.
|
|
type GateData struct {
|
|
AccessKey string
|
|
BearerToken *token.BearerToken
|
|
SessionToken *session.Token
|
|
GateKey *ecdsa.PublicKey
|
|
}
|
|
|
|
// NewGateData returns GateData from provided bearer token and public gate key.
|
|
func NewGateData(gateKey *ecdsa.PublicKey, bearerTkn *token.BearerToken) *GateData {
|
|
return &GateData{GateKey: gateKey, BearerToken: bearerTkn}
|
|
}
|
|
|
|
// Secrets represents AccessKey and key to encrypt gate tokens.
|
|
type Secrets struct {
|
|
AccessKey string
|
|
EphemeralKey *ecdsa.PrivateKey
|
|
}
|
|
|
|
// Marshal returns the wire-format of AccessBox.
|
|
func (x *AccessBox) Marshal() ([]byte, error) {
|
|
return proto.Marshal(x)
|
|
}
|
|
|
|
// Unmarshal parses the wire-format message and put data to x.
|
|
func (x *AccessBox) Unmarshal(data []byte) error {
|
|
return proto.Unmarshal(data, x)
|
|
}
|
|
|
|
// PackTokens adds a bearer and session tokens to BearerTokens and SessionToken lists respectively.
|
|
// Session token can be nil.
|
|
func PackTokens(gatesData []*GateData) (*AccessBox, *Secrets, error) {
|
|
box := &AccessBox{}
|
|
ephemeralKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
box.OwnerPublicKey = crypto.MarshalPublicKey(&ephemeralKey.PublicKey)
|
|
|
|
secret, err := generateSecret()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to generate accessKey as hex: %w", err)
|
|
}
|
|
|
|
if err := box.addTokens(gatesData, ephemeralKey, secret); err != nil {
|
|
return nil, nil, fmt.Errorf("failed to add tokens to accessbox: %w", err)
|
|
}
|
|
|
|
return box, &Secrets{hex.EncodeToString(secret), ephemeralKey}, err
|
|
}
|
|
|
|
// GetTokens returns gate tokens from AccessBox.
|
|
func (x *AccessBox) GetTokens(owner *ecdsa.PrivateKey) (*GateData, error) {
|
|
sender := crypto.UnmarshalPublicKey(x.OwnerPublicKey)
|
|
ownerKey := crypto.MarshalPublicKey(&owner.PublicKey)
|
|
for _, gate := range x.Gates {
|
|
if !bytes.Equal(gate.GatePublicKey, ownerKey) {
|
|
continue
|
|
}
|
|
|
|
gateData, err := decodeGate(gate, owner, sender)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode gate: %w", err)
|
|
}
|
|
return gateData, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("no gate data for key %x was found", ownerKey)
|
|
}
|
|
|
|
func (x *AccessBox) addTokens(gatesData []*GateData, ephemeralKey *ecdsa.PrivateKey, secret []byte) error {
|
|
for i, gate := range gatesData {
|
|
encBearer, err := gate.BearerToken.Marshal()
|
|
if err != nil {
|
|
return fmt.Errorf("%w, sender = %d", err, i)
|
|
}
|
|
var encSession []byte
|
|
if gate.SessionToken != nil {
|
|
encSession, err = gate.SessionToken.Marshal()
|
|
if err != nil {
|
|
return fmt.Errorf("%w, sender = %d", err, i)
|
|
}
|
|
}
|
|
|
|
tokens := new(Tokens)
|
|
tokens.AccessKey = secret
|
|
tokens.BearerToken = encBearer
|
|
tokens.SessionToken = encSession
|
|
|
|
boxGate, err := encodeGate(ephemeralKey, gate.GateKey, tokens)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
x.Gates = append(x.Gates, boxGate)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func encodeGate(ephemeralKey *ecdsa.PrivateKey, ownerKey *ecdsa.PublicKey, tokens *Tokens) (*AccessBox_Gate, error) {
|
|
data, err := proto.Marshal(tokens)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
encrypted, err := encrypt(ephemeralKey, ownerKey, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gate := new(AccessBox_Gate)
|
|
gate.GatePublicKey = crypto.MarshalPublicKey(ownerKey)
|
|
gate.Tokens = encrypted
|
|
return gate, nil
|
|
}
|
|
|
|
func decodeGate(gate *AccessBox_Gate, owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey) (*GateData, error) {
|
|
data, err := decrypt(owner, sender, gate.Tokens)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tokens := new(Tokens)
|
|
if err := proto.Unmarshal(data, tokens); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bearerTkn := token.NewBearerToken()
|
|
if err := bearerTkn.Unmarshal(tokens.BearerToken); err != nil {
|
|
return nil, err
|
|
}
|
|
sessionTkn := session.NewToken()
|
|
if err := sessionTkn.Unmarshal(tokens.SessionToken); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gateData := NewGateData(&owner.PublicKey, bearerTkn)
|
|
gateData.SessionToken = sessionTkn
|
|
gateData.AccessKey = hex.EncodeToString(tokens.AccessKey)
|
|
return gateData, nil
|
|
}
|
|
|
|
func generateShared256(prv *ecdsa.PrivateKey, pub *ecdsa.PublicKey) (sk []byte, err error) {
|
|
if prv.PublicKey.Curve != pub.Curve {
|
|
return nil, fmt.Errorf("not equal curves")
|
|
}
|
|
|
|
x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes())
|
|
if x == nil {
|
|
return nil, fmt.Errorf("shared key is point at infinity")
|
|
}
|
|
|
|
sk = make([]byte, 32)
|
|
skBytes := x.Bytes()
|
|
copy(sk[len(sk)-len(skBytes):], skBytes)
|
|
return sk, nil
|
|
}
|
|
|
|
func deriveKey(secret []byte) ([]byte, error) {
|
|
hash := sha256.New
|
|
kdf := hkdf.New(hash, secret, nil, nil)
|
|
key := make([]byte, 32)
|
|
_, err := io.ReadFull(kdf, key)
|
|
return key, err
|
|
}
|
|
|
|
func encrypt(owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey, data []byte) ([]byte, error) {
|
|
enc, err := getCipher(owner, sender)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nonce := make([]byte, enc.NonceSize(), enc.NonceSize()+len(data)+enc.Overhead())
|
|
if _, err := rand.Read(nonce); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return enc.Seal(nonce, nonce, data, nil), nil
|
|
}
|
|
|
|
func decrypt(owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey, data []byte) ([]byte, error) {
|
|
dec, err := getCipher(owner, sender)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if ld, ns := len(data), dec.NonceSize(); ld < ns {
|
|
return nil, fmt.Errorf("wrong data size (%d), should be greater than %d", ld, ns)
|
|
}
|
|
|
|
nonce, cypher := data[:dec.NonceSize()], data[dec.NonceSize():]
|
|
return dec.Open(nil, nonce, cypher, nil)
|
|
}
|
|
|
|
func getCipher(owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey) (cipher.AEAD, error) {
|
|
secret, err := generateShared256(owner, sender)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
key, err := deriveKey(secret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return chacha20poly1305.NewX(key)
|
|
}
|
|
|
|
func generateSecret() ([]byte, error) {
|
|
b := make([]byte, 32)
|
|
_, err := io.ReadFull(rand.Reader, b)
|
|
return b, err
|
|
}
|