forked from TrueCloudLab/frostfs-sdk-go
1ed426b8a6
Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
353 lines
8.3 KiB
Go
353 lines
8.3 KiB
Go
package bearer
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
|
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
|
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
|
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
|
"github.com/nspcc-dev/neofs-sdk-go/user"
|
|
)
|
|
|
|
var (
|
|
errNilBearerTokenBody = errors.New("bearer token body is not set")
|
|
errNilBearerTokenEACL = errors.New("bearer token ContainerEACL table is not set")
|
|
)
|
|
|
|
// Token represents bearer token for object service operations.
|
|
//
|
|
// Token is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/acl.BearerToken
|
|
// message. See ReadFromV2 / WriteToV2 methods.
|
|
//
|
|
// Instances can be created using built-in var declaration.
|
|
//
|
|
// Note that direct typecast is not safe and may result in loss of compatibility:
|
|
// _ = Token(acl.BearerToken{}) // not recommended
|
|
type Token acl.BearerToken
|
|
|
|
// ReadFromV2 reads Token from the acl.BearerToken message.
|
|
//
|
|
// See also WriteToV2.
|
|
func (b *Token) ReadFromV2(m acl.BearerToken) {
|
|
*b = Token(m)
|
|
}
|
|
|
|
// WriteToV2 writes Token to the acl.BearerToken message.
|
|
// The message must not be nil.
|
|
//
|
|
// See also ReadFromV2.
|
|
func (b Token) WriteToV2(m *acl.BearerToken) {
|
|
*m = (acl.BearerToken)(b)
|
|
}
|
|
|
|
// SetExpiration sets "exp" (expiration time) claim which identifies the
|
|
// expiration time (in NeoFS epochs) on or after which the Token MUST NOT be
|
|
// accepted for processing. The processing of the "exp" claim requires that the
|
|
// current epoch MUST be before the expiration epoch listed in the "exp" claim.
|
|
//
|
|
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4.
|
|
//
|
|
// See also Expiration.
|
|
func (b *Token) SetExpiration(exp uint64) {
|
|
v2token := (*acl.BearerToken)(b)
|
|
|
|
body := v2token.GetBody()
|
|
if body == nil {
|
|
body = new(acl.BearerTokenBody)
|
|
}
|
|
|
|
lt := new(acl.TokenLifetime)
|
|
lt.SetExp(exp)
|
|
lt.SetNbf(body.GetLifetime().GetNbf())
|
|
lt.SetIat(body.GetLifetime().GetIat())
|
|
|
|
body.SetLifetime(lt)
|
|
v2token.SetBody(body)
|
|
}
|
|
|
|
// Expiration returns "exp" claim.
|
|
//
|
|
// Empty Token has zero "exp".
|
|
//
|
|
// See also SetExpiration.
|
|
func (b Token) Expiration() uint64 {
|
|
v2token := (acl.BearerToken)(b)
|
|
return v2token.GetBody().GetLifetime().GetExp()
|
|
}
|
|
|
|
// SetNotBefore sets "nbf" (not before) claim which identifies the time (in
|
|
// NeoFS epochs) before which the Token MUST NOT be accepted for processing. The
|
|
// processing of the "nbf" claim requires that the current epoch MUST be
|
|
// after or equal to the not-before epoch listed in the "nbf" claim.
|
|
//
|
|
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5.
|
|
//
|
|
// See also NotBefore.
|
|
func (b *Token) SetNotBefore(nbf uint64) {
|
|
v2token := (*acl.BearerToken)(b)
|
|
|
|
body := v2token.GetBody()
|
|
if body == nil {
|
|
body = new(acl.BearerTokenBody)
|
|
}
|
|
|
|
lt := new(acl.TokenLifetime)
|
|
lt.SetExp(body.GetLifetime().GetExp())
|
|
lt.SetNbf(nbf)
|
|
lt.SetIat(body.GetLifetime().GetIat())
|
|
|
|
body.SetLifetime(lt)
|
|
v2token.SetBody(body)
|
|
}
|
|
|
|
// NotBefore returns "nbf" claim.
|
|
//
|
|
// Empty Token has zero "nbf".
|
|
//
|
|
// See also SetNotBefore.
|
|
func (b Token) NotBefore() uint64 {
|
|
v2token := (acl.BearerToken)(b)
|
|
return v2token.GetBody().GetLifetime().GetNbf()
|
|
}
|
|
|
|
// SetIssuedAt sets "iat" (issued at) claim which identifies the time (in NeoFS
|
|
// epochs) at which the Token was issued. This claim can be used to determine
|
|
// the age of the Token.
|
|
//
|
|
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6.
|
|
//
|
|
// See also IssuedAt.
|
|
func (b *Token) SetIssuedAt(iat uint64) {
|
|
v2token := (*acl.BearerToken)(b)
|
|
|
|
body := v2token.GetBody()
|
|
if body == nil {
|
|
body = new(acl.BearerTokenBody)
|
|
}
|
|
|
|
lt := new(acl.TokenLifetime)
|
|
lt.SetExp(body.GetLifetime().GetExp())
|
|
lt.SetNbf(body.GetLifetime().GetNbf())
|
|
lt.SetIat(iat)
|
|
|
|
body.SetLifetime(lt)
|
|
v2token.SetBody(body)
|
|
}
|
|
|
|
// IssuedAt returns "iat" claim.
|
|
//
|
|
// Empty Token has zero "iat".
|
|
//
|
|
// See also SetIssuedAt.
|
|
func (b Token) IssuedAt() uint64 {
|
|
v2token := (acl.BearerToken)(b)
|
|
return v2token.GetBody().GetLifetime().GetIat()
|
|
}
|
|
|
|
// SetEACLTable sets extended ACL table that should be used during object
|
|
// service request processing with bearer token.
|
|
//
|
|
// See also EACLTable.
|
|
func (b *Token) SetEACLTable(table eacl.Table) {
|
|
v2 := (*acl.BearerToken)(b)
|
|
|
|
body := v2.GetBody()
|
|
if body == nil {
|
|
body = new(acl.BearerTokenBody)
|
|
}
|
|
|
|
body.SetEACL(table.ToV2())
|
|
v2.SetBody(body)
|
|
}
|
|
|
|
// EACLTable returns extended ACL table that should be used during object
|
|
// service request processing with bearer token.
|
|
//
|
|
// See also SetEACLTable.
|
|
func (b Token) EACLTable() eacl.Table {
|
|
v2 := (acl.BearerToken)(b)
|
|
return *eacl.NewTableFromV2(v2.GetBody().GetEACL())
|
|
}
|
|
|
|
// SetOwnerID sets user.ID value of the user who can attach bearer token to
|
|
// its requests.
|
|
//
|
|
// See also OwnerID.
|
|
func (b *Token) SetOwnerID(id user.ID) {
|
|
v2 := (*acl.BearerToken)(b)
|
|
|
|
body := v2.GetBody()
|
|
if body == nil {
|
|
body = new(acl.BearerTokenBody)
|
|
}
|
|
|
|
var idV2 refs.OwnerID
|
|
id.WriteToV2(&idV2)
|
|
|
|
body.SetOwnerID(&idV2)
|
|
v2.SetBody(body)
|
|
}
|
|
|
|
// OwnerID returns user.ID value of the user who can attach bearer token to
|
|
// its requests.
|
|
//
|
|
// See also SetOwnerID.
|
|
func (b Token) OwnerID() user.ID {
|
|
v2 := (acl.BearerToken)(b)
|
|
|
|
var id user.ID
|
|
|
|
idV2 := v2.GetBody().GetOwnerID()
|
|
if idV2 != nil {
|
|
_ = id.ReadFromV2(*idV2)
|
|
}
|
|
|
|
return id
|
|
}
|
|
|
|
// Sign signs bearer token. This method should be invoked with the private
|
|
// key of container owner to allow overriding extended ACL table of the container
|
|
// included in this token.
|
|
//
|
|
// See also Signature.
|
|
func (b *Token) Sign(key ecdsa.PrivateKey) error {
|
|
err := sanityCheck(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
m := (*acl.BearerToken)(b)
|
|
|
|
data, err := m.GetBody().StableMarshal(nil)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal body: %w", err)
|
|
}
|
|
|
|
var sig neofscrypto.Signature
|
|
|
|
err = sig.Calculate(neofsecdsa.Signer(key), data)
|
|
if err != nil {
|
|
return fmt.Errorf("calculate signature: %w", err)
|
|
}
|
|
|
|
var sigV2 refs.Signature
|
|
sig.WriteToV2(&sigV2)
|
|
|
|
m.SetSignature(&sigV2)
|
|
|
|
return nil
|
|
}
|
|
|
|
// VerifySignature returns nil if bearer token contains correct signature.
|
|
func (b Token) VerifySignature() error {
|
|
if b.isEmpty() {
|
|
return nil
|
|
}
|
|
|
|
m := (acl.BearerToken)(b)
|
|
|
|
sigV2 := m.GetSignature()
|
|
if sigV2 == nil {
|
|
return errors.New("missing signature")
|
|
}
|
|
|
|
data, err := m.GetBody().StableMarshal(nil)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal body: %w", err)
|
|
}
|
|
|
|
var sig neofscrypto.Signature
|
|
sig.ReadFromV2(*sigV2)
|
|
|
|
if !sig.Verify(data) {
|
|
return errors.New("wrong signature")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Issuer returns user.ID associated with the key that signed bearer token.
|
|
// To pass node validation it should be owner of requested container.
|
|
//
|
|
// If token is not signed, Issuer returns empty owner ID and false `ok` flag.
|
|
//
|
|
// See also Sign.
|
|
func (b Token) Issuer() (id user.ID, ok bool) {
|
|
v2 := (acl.BearerToken)(b)
|
|
|
|
pub, _ := keys.NewPublicKeyFromBytes(v2.GetSignature().GetKey(), elliptic.P256())
|
|
|
|
ok = pub != nil
|
|
if ok {
|
|
user.IDFromKey(&id, (ecdsa.PublicKey)(*pub))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// sanityCheck if bearer token is ready to be issued.
|
|
func sanityCheck(b *Token) error {
|
|
v2 := (*acl.BearerToken)(b)
|
|
|
|
switch {
|
|
case v2.GetBody() == nil:
|
|
return errNilBearerTokenBody
|
|
case v2.GetBody().GetEACL() == nil:
|
|
return errNilBearerTokenEACL
|
|
}
|
|
|
|
// consider checking ContainerEACL sanity there, lifetime correctness, etc.
|
|
|
|
return nil
|
|
}
|
|
|
|
// Marshal marshals Token into a canonical NeoFS binary format (proto3
|
|
// with direct field order).
|
|
//
|
|
// See also Unmarshal.
|
|
func (b Token) Marshal() []byte {
|
|
v2 := (acl.BearerToken)(b)
|
|
|
|
data, err := v2.StableMarshal(nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
// Unmarshal unmarshals Token from canonical NeoFS binary format (proto3
|
|
// with direct field order).
|
|
//
|
|
// See also Marshal.
|
|
func (b *Token) Unmarshal(data []byte) error {
|
|
v2 := (*acl.BearerToken)(b)
|
|
return v2.Unmarshal(data)
|
|
}
|
|
|
|
// MarshalJSON encodes Token to protobuf JSON format.
|
|
//
|
|
// See also UnmarshalJSON.
|
|
func (b Token) MarshalJSON() ([]byte, error) {
|
|
v2 := (acl.BearerToken)(b)
|
|
return v2.MarshalJSON()
|
|
}
|
|
|
|
// UnmarshalJSON decodes Token from protobuf JSON format.
|
|
//
|
|
// See also MarshalJSON.
|
|
func (b *Token) UnmarshalJSON(data []byte) error {
|
|
v2 := (*acl.BearerToken)(b)
|
|
return v2.UnmarshalJSON(data)
|
|
}
|
|
|
|
func (b Token) isEmpty() bool {
|
|
v2token := (acl.BearerToken)(b)
|
|
return v2token.GetBody() == nil && v2token.GetSignature() == nil
|
|
}
|