Use scope-specific token DEKs (#342)

This commit is contained in:
Jeff Mitchell authored and GitHub committed 2020-09-14 12:30:11 -04:00
1 parent 67278af339
commit 6201357902
11 files changed
+366 -108

No files matched your search

+145 -85
View File
@@ -11,6 +11,7 @@ import (
"github.com/btcsuite/btcutil/base58"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
"github.com/hashicorp/boundary/internal/gen/controller/tokens"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/servers/controller/common"
@@ -46,20 +47,16 @@ var verifierKey key
// RequestInfo contains request parameters necessary for checking authn/authz
type RequestInfo struct {
Path string
Method string
PublicId string
Token string
TokenFormat TokenFormat
// These are set by the service handlers via options
scopeId string
pin string
// This is used by the scopes collection
userIdOverride string
Path string
Method string
PublicId string
EncryptedToken string
Token string
TokenFormat TokenFormat
// The following are useful for tests
scopeIdOverride string
userIdOverride string
DisableAuthzFailures bool
DisableAuthEntirely bool
}
@@ -122,7 +119,7 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
ret.Scope = new(scopes.ScopeInfo)
if v.requestInfo.DisableAuthEntirely {
ret.Scope.Id = v.requestInfo.scopeId
ret.Scope.Id = v.requestInfo.scopeIdOverride
if ret.Scope.Id == "" {
ret.Scope.Id = opts.withScopeId
}
@@ -147,6 +144,10 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
Type: opts.withType,
}
if v.requestInfo.EncryptedToken != "" {
v.decryptToken()
}
var authResults *perms.ACLResults
var err error
authResults, ret.UserId, ret.Scope, err = v.performAuthCheck()
@@ -184,6 +185,14 @@ func (v verifier) performAuthCheck() (aclResults *perms.ACLResults, userId strin
// Validate the token and fetch the corresponding user ID
switch v.requestInfo.TokenFormat {
case AuthTokenTypeUnknown:
// Nothing; remain as the anonymous user
case AuthTokenTypeRecoveryKms:
// We validated the encrypted token in decryptToken and handled the
// nonces there, so just set the user
userId = "u_recovery"
case AuthTokenTypeBearer, AuthTokenTypeSplitCookie:
if v.requestInfo.Token == "" {
// This will end up staying as the anonymous user
@@ -204,40 +213,6 @@ func (v verifier) performAuthCheck() (aclResults *perms.ACLResults, userId strin
if at != nil {
userId = at.GetIamUserId()
}
case AuthTokenTypeRecoveryKms:
userId = "u_recovery"
if v.kms == nil {
retErr = errors.New("perform auth check: no KMS object available to authz system")
return
}
wrapper := v.kms.GetExternalWrappers().Recovery()
if wrapper == nil {
retErr = errors.New("perform auth check: no recovery KMS is available")
return
}
info, err := recovery.ParseRecoveryToken(v.ctx, wrapper, v.requestInfo.Token)
if err != nil {
retErr = fmt.Errorf("perform auth check: error validating recovery token: %w", err)
return
}
// If we add the validity period to the creation time (which we've
// verified is before the current time, with a minute of fudging), and
// it's before now, it's expired and might be a replay.
if info.CreationTime.Add(globals.RecoveryTokenValidityPeriod).Before(time.Now()) {
retErr = errors.New("WARNING: perform auth check: recovery token has expired (possible replay attack)")
return
}
repo, err := v.serversRepoFn()
if err != nil {
retErr = fmt.Errorf("perform auth check: error fetching servers repo: %w", err)
return
}
if err := repo.AddRecoveryNonce(v.ctx, info.Nonce); err != nil {
retErr = fmt.Errorf("WARNING: perform auth check: error adding nonce to database (possible replay attack): %w", err)
return
}
v.logger.Warn("NOTE: recovery KMS was used to authorize a call", "url", v.requestInfo.Path, "method", v.requestInfo.Method)
}
iamRepo, err := v.iamRepoFn()
@@ -315,7 +290,7 @@ func (v verifier) performAuthCheck() (aclResults *perms.ACLResults, userId strin
// GetTokenFromRequest pulls the token from either the Authorization header or
// split cookies and parses it. If it cannot be parsed successfully, the issue
// is logged and we return blank, so logic will continue as the anonymous user.
// The public ID and token are returned along with the token format.
// The public ID and _encrypted_ token are returned along with the token format.
func GetTokenFromRequest(logger hclog.Logger, kmsCache *kms.Kms, req *http.Request) (string, string, TokenFormat) {
// First, get the token, either from the authorization header or from split
// cookies
@@ -361,42 +336,127 @@ func GetTokenFromRequest(logger hclog.Logger, kmsCache *kms.Kms, req *http.Reque
}
publicId := strings.Join(splitFullToken[0:2], "_")
encryptedToken := splitFullToken[2]
// This is hardcoded to global because at this point we don't know the
// scope. But that's okay; this is not really a security feature so much as
// an anti-DDoSing-the-backing-database feature.
tokenWrapper, err := kmsCache.GetWrapper(req.Context(), scope.Global.String(), kms.KeyPurposeTokens)
if err != nil {
logger.Warn("get token from request: unable to get wrapper for tokens; continuing as anonymous user", "error", err)
return "", "", AuthTokenTypeUnknown
}
version := splitFullToken[2][0:len(globals.ServiceTokenV1)]
switch version {
case globals.ServiceTokenV1:
default:
logger.Trace("get token from request: unknown token encryption version; continuing as anonymous user", "version", version)
return "", "", AuthTokenTypeUnknown
}
marshaledToken := base58.Decode(splitFullToken[2][len(globals.ServiceTokenV1):])
blobInfo := new(wrapping.EncryptedBlobInfo)
if err := proto.Unmarshal(marshaledToken, blobInfo); err != nil {
logger.Trace("get token from request: error decoding encrypted token; continuing as anonymous user", "error", err)
return "", "", AuthTokenTypeUnknown
}
tokenBytes, err := tokenWrapper.Decrypt(req.Context(), blobInfo, []byte(publicId))
if err != nil {
logger.Trace("get token from request: error decrypting encrypted token; continuing as anonymous user", "error", err)
return "", "", AuthTokenTypeUnknown
}
token := string(tokenBytes)
if receivedTokenType == AuthTokenTypeUnknown || token == "" || publicId == "" {
logger.Trace("get token from request: after parsing, could not find valid token; continuing as anonymous user")
return "", "", AuthTokenTypeUnknown
}
return publicId, token, receivedTokenType
return publicId, encryptedToken, receivedTokenType
}
func (v *verifier) decryptToken() {
switch v.requestInfo.TokenFormat {
case AuthTokenTypeUnknown:
// Nothing to decrypt
return
case AuthTokenTypeBearer, AuthTokenTypeSplitCookie:
if v.kms == nil {
v.logger.Trace("decrypt token: no KMS object available to authz system")
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
tokenRepo, err := v.authTokenRepoFn()
if err != nil {
v.logger.Warn("decrypt bearer token: failed to get authtoken repo", "error", err)
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
at, err := tokenRepo.LookupAuthToken(v.ctx, v.requestInfo.PublicId)
if err != nil {
v.logger.Trace("decrypt bearer token: failed to look up auth token by public ID", "error", err)
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
if at == nil {
v.logger.Trace("decrypt bearer token: nil result from looking up auth token by public ID")
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
tokenWrapper, err := v.kms.GetWrapper(v.ctx, at.GetScopeId(), kms.KeyPurposeTokens)
if err != nil {
v.logger.Warn("decrypt bearer token: unable to get wrapper for tokens; continuing as anonymous user", "error", err)
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
version := v.requestInfo.EncryptedToken[0:len(globals.ServiceTokenV1)]
switch version {
case globals.ServiceTokenV1:
default:
v.logger.Trace("decrypt bearer token: unknown token encryption version; continuing as anonymous user", "version", version)
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
marshaledToken := base58.Decode(v.requestInfo.EncryptedToken[len(globals.ServiceTokenV1):])
blobInfo := new(wrapping.EncryptedBlobInfo)
if err := proto.Unmarshal(marshaledToken, blobInfo); err != nil {
v.logger.Trace("decrypt bearer token: error decoding encrypted token; continuing as anonymous user", "error", err)
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
s1Bytes, err := tokenWrapper.Decrypt(v.ctx, blobInfo, []byte(v.requestInfo.PublicId))
if err != nil {
v.logger.Trace("decrypt bearer token: error decrypting encrypted token; continuing as anonymous user", "error", err)
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
var s1Info tokens.S1TokenInfo
if err := proto.Unmarshal(s1Bytes, &s1Info); err != nil {
v.logger.Trace("decrypt bearer token: error unmarshaling token info; continuing as anonymous user", "error", err)
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
if v.requestInfo.TokenFormat == AuthTokenTypeUnknown || s1Info.Token == "" || v.requestInfo.PublicId == "" {
v.logger.Trace("decrypt bearer token: after parsing, could not find valid token; continuing as anonymous user")
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
v.requestInfo.Token = s1Info.Token
return
case AuthTokenTypeRecoveryKms:
if v.kms == nil {
v.logger.Trace("decrypt recovery token: no KMS object available to authz system")
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
wrapper := v.kms.GetExternalWrappers().Recovery()
if wrapper == nil {
v.logger.Trace("decrypt recovery token: no recovery KMS is available")
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
info, err := recovery.ParseRecoveryToken(v.ctx, wrapper, v.requestInfo.EncryptedToken)
if err != nil {
v.logger.Trace("decrypt recovery token: error parsing and validating recovery token", "error", err)
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
// If we add the validity period to the creation time (which we've
// verified is before the current time, with a minute of fudging), and
// it's before now, it's expired and might be a replay.
if info.CreationTime.Add(globals.RecoveryTokenValidityPeriod).Before(time.Now()) {
v.logger.Warn("WARNING: decrypt recovery token: recovery token has expired (possible replay attack)")
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
repo, err := v.serversRepoFn()
if err != nil {
v.logger.Trace("decrypt recovery token: error fetching servers repo", "error", err)
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
if err := repo.AddRecoveryNonce(v.ctx, info.Nonce); err != nil {
v.logger.Warn("WARNING: decrypt recovery token: error adding nonce to database (possible replay attack)", "error", err)
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
return
}
v.logger.Info("NOTE: recovery KMS was used to authorize a call", "url", v.requestInfo.Path, "method", v.requestInfo.Method)
}
}
+4 -2
View File
@@ -39,7 +39,7 @@ func TestAuthTokenAuthenticator(t *testing.T) {
o, _ := iam.TestScopes(t, iamRepo)
at := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId())
encToken, err := authtoken.EncryptToken(context.Background(), kms, at.GetPublicId(), at.GetToken())
encToken, err := authtoken.EncryptToken(context.Background(), kms, o.GetPublicId(), at.GetPublicId(), at.GetToken())
require.NoError(t, err)
tokValue := at.GetPublicId() + "_" + encToken
@@ -113,7 +113,7 @@ func TestAuthTokenAuthenticator(t *testing.T) {
Path: req.URL.Path,
Method: req.Method,
}
requestInfo.PublicId, requestInfo.Token, requestInfo.TokenFormat = GetTokenFromRequest(logger, kms, req)
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = GetTokenFromRequest(logger, kms, req)
assert.Equal(t, tc.tokenFormat, requestInfo.TokenFormat)
if tc.userId == "" {
@@ -125,6 +125,8 @@ func TestAuthTokenAuthenticator(t *testing.T) {
require.True(t, ok)
require.NotNil(t, v)
v.decryptToken()
at, err := tokenRepo.ValidateToken(ctx, v.requestInfo.PublicId, v.requestInfo.Token)
require.NoError(t, err)
assert.Equal(t, tc.userId, at.GetIamUserId())
+1 -2
View File
@@ -6,8 +6,7 @@ import "context"
func DisabledAuthTestContext(opt ...Option) context.Context {
reqInfo := RequestInfo{DisableAuthEntirely: true}
opts := getOpts(opt...)
reqInfo.scopeId = opts.withScopeId
reqInfo.pin = opts.withPin
reqInfo.scopeIdOverride = opts.withScopeId
reqInfo.userIdOverride = opts.withUserId
return NewVerifierContext(context.Background(), nil, nil, nil, nil, opts.withKms, reqInfo)
}
+20 -8
View File
@@ -3,13 +3,15 @@ package authtoken
import (
"context"
"fmt"
mathrand "math/rand"
"time"
"github.com/btcsuite/btcutil/base58"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/authtoken/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/gen/controller/tokens"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/types/scope"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-kms-wrapping/structwrapping"
"github.com/hashicorp/vault/sdk/helper/base62"
@@ -101,17 +103,27 @@ func newAuthToken() (string, error) {
}
// EncryptToken is a shared function for encrypting a token value for return to
// the user. We always use the global scope because on authenticate we don't
// have scope info at this point and the idea is to remove a DB lookup if the
// token is made up/invalid so as to prevent DDoS against a third party service
// by just randomly guessing tokens.
func EncryptToken(ctx context.Context, kmsCache *kms.Kms, publicId, token string) (string, error) {
tokenWrapper, err := kmsCache.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeTokens)
// the user.
func EncryptToken(ctx context.Context, kmsCache *kms.Kms, scopeId, publicId, token string) (string, error) {
r := mathrand.New(mathrand.NewSource(time.Now().UnixNano()))
s1Info := &tokens.S1TokenInfo{
Token: token,
Confounder: make([]byte, r.Intn(30)),
}
r.Read(s1Info.Confounder)
marshaledS1Info, err := proto.Marshal(s1Info)
if err != nil {
return "", fmt.Errorf("error marshaling token info: %w", err)
}
tokenWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeTokens)
if err != nil {
return "", fmt.Errorf("unable to get wrapper: %w", err)
}
blobInfo, err := tokenWrapper.Encrypt(ctx, []byte(token), []byte(publicId))
blobInfo, err := tokenWrapper.Encrypt(ctx, []byte(marshaledS1Info), []byte(publicId))
if err != nil {
return "", fmt.Errorf("error encrypting token: %w", err)
}
+1 -1
View File
@@ -198,7 +198,7 @@ func (c *Command) Run(args []string) int {
case "table":
if len(listedScopes) == 0 {
c.UI.Output("No scopes found")
c.UI.Output("No child scopes found")
return 0
}
var output []string
+162
View File
@@ -0,0 +1,162 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.12.4
// source: controller/tokens/v1/tokens.proto
package tokens
import (
proto "github.com/golang/protobuf/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
type S1TokenInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Token string `protobuf:"bytes,10,opt,name=token,proto3" json:"token,omitempty"`
Confounder []byte `protobuf:"bytes,20,opt,name=confounder,proto3" json:"confounder,omitempty"`
}
func (x *S1TokenInfo) Reset() {
*x = S1TokenInfo{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_tokens_v1_tokens_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *S1TokenInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*S1TokenInfo) ProtoMessage() {}
func (x *S1TokenInfo) ProtoReflect() protoreflect.Message {
mi := &file_controller_tokens_v1_tokens_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use S1TokenInfo.ProtoReflect.Descriptor instead.
func (*S1TokenInfo) Descriptor() ([]byte, []int) {
return file_controller_tokens_v1_tokens_proto_rawDescGZIP(), []int{0}
}
func (x *S1TokenInfo) GetToken() string {
if x != nil {
return x.Token
}
return ""
}
func (x *S1TokenInfo) GetConfounder() []byte {
if x != nil {
return x.Confounder
}
return nil
}
var File_controller_tokens_v1_tokens_proto protoreflect.FileDescriptor
var file_controller_tokens_v1_tokens_proto_rawDesc = []byte{
0x0a, 0x21, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x74, 0x6f, 0x6b,
0x65, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x14, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x22, 0x43, 0x0a, 0x0b, 0x53, 0x31, 0x54,
0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65,
0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1e,
0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x14, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x42, 0x45,
0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73,
0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x3b, 0x74,
0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_controller_tokens_v1_tokens_proto_rawDescOnce sync.Once
file_controller_tokens_v1_tokens_proto_rawDescData = file_controller_tokens_v1_tokens_proto_rawDesc
)
func file_controller_tokens_v1_tokens_proto_rawDescGZIP() []byte {
file_controller_tokens_v1_tokens_proto_rawDescOnce.Do(func() {
file_controller_tokens_v1_tokens_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_tokens_v1_tokens_proto_rawDescData)
})
return file_controller_tokens_v1_tokens_proto_rawDescData
}
var file_controller_tokens_v1_tokens_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_controller_tokens_v1_tokens_proto_goTypes = []interface{}{
(*S1TokenInfo)(nil), // 0: controller.tokens.v1.S1TokenInfo
}
var file_controller_tokens_v1_tokens_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_controller_tokens_v1_tokens_proto_init() }
func file_controller_tokens_v1_tokens_proto_init() {
if File_controller_tokens_v1_tokens_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_controller_tokens_v1_tokens_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*S1TokenInfo); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_tokens_v1_tokens_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_controller_tokens_v1_tokens_proto_goTypes,
DependencyIndexes: file_controller_tokens_v1_tokens_proto_depIdxs,
MessageInfos: file_controller_tokens_v1_tokens_proto_msgTypes,
}.Build()
File_controller_tokens_v1_tokens_proto = out.File
file_controller_tokens_v1_tokens_proto_rawDesc = nil
file_controller_tokens_v1_tokens_proto_goTypes = nil
file_controller_tokens_v1_tokens_proto_depIdxs = nil
}
@@ -0,0 +1,10 @@
syntax = "proto3";
package controller.tokens.v1;
option go_package = "github.com/hashicorp/boundary/internal/gen/controller/tokens;tokens";
message S1TokenInfo {
string token = 10;
bytes confounder = 20;
}
+1 -1
View File
@@ -82,7 +82,7 @@ func TestHandler_CORS(t *testing.T) {
require.NoError(t, err)
org := iam.TestOrg(t, repo)
encToken, err := authtoken.EncryptToken(context.Background(), tc.c.kms, "fo_o", "bar")
encToken, err := authtoken.EncryptToken(context.Background(), tc.c.kms, org.GetPublicId(), "fo_o", "bar")
require.NoError(t, err)
token := "fo_o_" + encToken
+1 -1
View File
@@ -229,7 +229,7 @@ func wrapHandlerWithCommonFuncs(h http.Handler, c *Controller, props HandlerProp
DisableAuthzFailures: disableAuthzFailures,
}
requestInfo.PublicId, requestInfo.Token, requestInfo.TokenFormat = auth.GetTokenFromRequest(c.logger, c.kms, r)
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = auth.GetTokenFromRequest(c.logger, c.kms, r)
ctx = auth.NewVerifierContext(ctx, c.logger, c.IamRepoFn, c.AuthTokenRepoFn, c.ServersRepoFn, c.kms, requestInfo)
// Set the context back on the request
@@ -111,7 +111,7 @@ func (s Service) authenticateWithRepo(ctx context.Context, scopeId, authMethodId
return nil, err
}
token, err := authtoken.EncryptToken(ctx, s.kms, tok.GetPublicId(), tok.GetToken())
token, err := authtoken.EncryptToken(ctx, s.kms, scopeId, tok.GetPublicId(), tok.GetToken())
if err != nil {
return nil, err
}
+20 -7
View File
@@ -15,7 +15,10 @@ import (
"google.golang.org/protobuf/proto"
)
const nonceLength = 32
const (
nonceLength = 32
v1String = "1"
)
// FutureLeeway indicates how far in the future we should allow the creation
// time of the token to be, in order to account for clock drift
@@ -58,18 +61,28 @@ func formatToken(ctx context.Context, wrapper wrapping.Wrapper, info *Info) (str
return "", fmt.Errorf("error marshaling encrypted blob: %w", err)
}
return fmt.Sprintf("r_%s", base58.Encode(marshaledBlob)), nil
return fmt.Sprintf("r_%s%s", v1String, base58.Encode(marshaledBlob)), nil
}
func ParseRecoveryToken(ctx context.Context, wrapper wrapping.Wrapper, token string) (*Info, error) {
token = strings.TrimSpace(token)
if token == "" {
func ParseRecoveryToken(ctx context.Context, wrapper wrapping.Wrapper, versionedToken string) (*Info, error) {
versionedToken = strings.TrimSpace(versionedToken)
if versionedToken == "" {
return nil, errors.New("empty token")
}
if !strings.HasPrefix(token, "r_") {
if !strings.HasPrefix(versionedToken, "r_") {
return nil, errors.New("token has wrong format")
}
token = strings.TrimPrefix(token, "r_")
versionedToken = strings.TrimPrefix(versionedToken, "r_")
ver, token := versionedToken[0:1], versionedToken[1:]
// Don't do anything yet, but if we ever need to adjust data based on
// version we'd do it here
switch ver {
case v1String:
default:
return nil, fmt.Errorf("unknown recovery token version %s", ver)
}
marshaledBlob := base58.Decode(token)
if len(marshaledBlob) == 0 {