// Copyright (C) MongoDB, Inc. 2022-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 package unified import ( "context" "errors" "fmt" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" ) // parseDataKeyOptions will parse an options document and return an options.DataKeyOptions instance. func parseDataKeyOptions(opts bson.Raw) (*options.DataKeyOptions, error) { elems, err := opts.Elements() if err != nil { return nil, err } dko := options.DataKey() for _, elem := range elems { key := elem.Key() val := elem.Value() switch key { case "masterKey": masterKey := make(map[string]interface{}) if err := val.Unmarshal(&masterKey); err != nil { return nil, fmt.Errorf("error unmarshaling 'masterKey': %w", err) } dko.SetMasterKey(masterKey) case "keyAltNames": keyAltNames := []string{} if err := val.Unmarshal(&keyAltNames); err != nil { return nil, fmt.Errorf("error unmarshaling 'keyAltNames': %w", err) } dko.SetKeyAltNames(keyAltNames) case "keyMaterial": bin := primitive.Binary{} if err := val.Unmarshal(&bin); err != nil { return nil, fmt.Errorf("error unmarshaling 'keyMaterial': %w", err) } dko.SetKeyMaterial(bin.Data) default: return nil, fmt.Errorf("unrecognized DataKeyOptions arg: %q", key) } } return dko, nil } // executeAddKeyAltName adds a keyAltName to the keyAltNames array of the key document in the key vault collection with // the given UUID (BSON binary subtype 0x04). Returns the previous version of the key document. func executeAddKeyAltName(ctx context.Context, operation *operation) (*operationResult, error) { cee, err := entities(ctx).clientEncryption(operation.Object) if err != nil { return nil, err } var id primitive.Binary var keyAltName string elems, err := operation.Arguments.Elements() if err != nil { return nil, err } for _, elem := range elems { key := elem.Key() val := elem.Value() switch key { case "id": subtype, data := val.Binary() id = primitive.Binary{Subtype: subtype, Data: data} case "keyAltName": keyAltName = val.StringValue() default: return nil, fmt.Errorf("unrecognized AddKeyAltName arg: %q", key) } } res, err := cee.AddKeyAltName(ctx, id, keyAltName).Raw() // Ignore ErrNoDocuments errors from Raw. In the event that the cursor returned in a find operation has no // associated documents, Raw will return ErrNoDocuments. if errors.Is(err, mongo.ErrNoDocuments) { err = nil } return newDocumentResult(res, err), nil } // executeCreateDataKey will attempt to create a client-encrypted key for a unified operation. func executeCreateDataKey(ctx context.Context, operation *operation) (*operationResult, error) { cee, err := entities(ctx).clientEncryption(operation.Object) if err != nil { return nil, err } var kmsProvider string var dko *options.DataKeyOptions elems, err := operation.Arguments.Elements() if err != nil { return nil, err } for _, elem := range elems { key := elem.Key() val := elem.Value() switch key { case "kmsProvider": kmsProvider = val.StringValue() case "opts": dko, err = parseDataKeyOptions(val.Document()) if err != nil { return nil, err } default: return nil, fmt.Errorf("unrecognized CreateDataKey arg: %q", key) } } if kmsProvider == "" { return nil, newMissingArgumentError("kmsProvider") } bin, err := cee.CreateDataKey(ctx, kmsProvider, dko) if bin.Data != nil { bsonType, bsonData, err := bson.MarshalValue(bin) if err != nil { return nil, err } return newValueResult(bsonType, bsonData, err), nil } return newErrorResult(err), nil } // executeDeleteKey removes the key document with the given UUID (BSON binary subtype 0x04) from the key vault // collection. Returns the result of the internal deleteOne() operation on the key vault collection. func executeDeleteKey(ctx context.Context, operation *operation) (*operationResult, error) { cee, err := entities(ctx).clientEncryption(operation.Object) if err != nil { return nil, err } var id primitive.Binary elems, err := operation.Arguments.Elements() if err != nil { return nil, err } for _, elem := range elems { key := elem.Key() val := elem.Value() switch key { case "id": subtype, data := val.Binary() id = primitive.Binary{Subtype: subtype, Data: data} default: return nil, fmt.Errorf("unrecognized DeleteKey arg: %q", key) } } res, err := cee.DeleteKey(ctx, id) raw := emptyCoreDocument if res != nil { raw = bsoncore.NewDocumentBuilder(). AppendInt64("deletedCount", res.DeletedCount). Build() } return newDocumentResult(raw, err), nil } // executeGetKeyByAltName returns a key document in the key vault collection with the given keyAltName. func executeGetKeyByAltName(ctx context.Context, operation *operation) (*operationResult, error) { cee, err := entities(ctx).clientEncryption(operation.Object) if err != nil { return nil, err } var keyAltName string elems, err := operation.Arguments.Elements() if err != nil { return nil, err } for _, elem := range elems { key := elem.Key() val := elem.Value() switch key { case "keyAltName": keyAltName = val.StringValue() default: return nil, fmt.Errorf("unrecognized GetKeyByAltName arg: %q", key) } } res, err := cee.GetKeyByAltName(ctx, keyAltName).Raw() // Ignore ErrNoDocuments errors from Raw. In the event that the cursor returned in a find operation has no // associated documents, Raw will return ErrNoDocuments. if errors.Is(err, mongo.ErrNoDocuments) { err = nil } return newDocumentResult(res, err), nil } // executeGetKey finds a single key document with the given UUID (BSON binary subtype 0x04). Returns the result of the // internal find() operation on the key vault collection. func executeGetKey(ctx context.Context, operation *operation) (*operationResult, error) { cee, err := entities(ctx).clientEncryption(operation.Object) if err != nil { return nil, err } var id primitive.Binary elems, err := operation.Arguments.Elements() if err != nil { return nil, err } for _, elem := range elems { key := elem.Key() val := elem.Value() switch key { case "id": subtype, data := val.Binary() id = primitive.Binary{Subtype: subtype, Data: data} default: return nil, fmt.Errorf("unrecognized GetKey arg: %q", key) } } res, err := cee.GetKey(ctx, id).Raw() // Ignore ErrNoDocuments errors from Raw. In the event that the cursor returned in a find operation has no // associated documents, Raw will return ErrNoDocuments. if errors.Is(err, mongo.ErrNoDocuments) { err = nil } return newDocumentResult(res, err), nil } // executeGetKeys finds all documents in the key vault collection. Returns the result of the internal find() operation // on the key vault collection. func executeGetKeys(ctx context.Context, operation *operation) (*operationResult, error) { cee, err := entities(ctx).clientEncryption(operation.Object) if err != nil { return nil, err } cursor, err := cee.GetKeys(ctx) if err != nil { return newErrorResult(err), nil } var docs []bson.Raw if err := cursor.All(ctx, &docs); err != nil { return newErrorResult(err), nil } return newCursorResult(docs), nil } // executeRemoveKeyAltName will remove keyAltName from the data key if it is present on the data key. func executeRemoveKeyAltName(ctx context.Context, operation *operation) (*operationResult, error) { cee, err := entities(ctx).clientEncryption(operation.Object) if err != nil { return nil, err } var id primitive.Binary var keyAltName string elems, err := operation.Arguments.Elements() if err != nil { return nil, err } for _, elem := range elems { key := elem.Key() val := elem.Value() switch key { case "id": subtype, data := val.Binary() id = primitive.Binary{Subtype: subtype, Data: data} case "keyAltName": keyAltName = val.StringValue() default: return nil, fmt.Errorf("unrecognized RemoveKeyAltName arg: %q", key) } } res, err := cee.RemoveKeyAltName(ctx, id, keyAltName).Raw() // Ignore ErrNoDocuments errors from Raw. In the event that the cursor returned in a find operation has no // associated documents, Raw will return ErrNoDocuments. if errors.Is(err, mongo.ErrNoDocuments) { err = nil } return newDocumentResult(res, err), nil } // parseRewrapManyDataKeyOptions will parse an options document and return an // options.RewrapManyDataKeyOptions instance. func parseRewrapManyDataKeyOptions(opts bson.Raw) (*options.RewrapManyDataKeyOptions, error) { elems, err := opts.Elements() if err != nil { return nil, err } rmdko := options.RewrapManyDataKey() for _, elem := range elems { key := elem.Key() val := elem.Value() switch key { case "provider": rmdko.SetProvider(val.StringValue()) case "masterKey": rmdko.SetMasterKey(val.Document()) default: return nil, fmt.Errorf("unrecognized RewrapManyDataKeyOptions arg: %q", key) } } return rmdko, nil } // rewrapManyDataKeyResultsOpResult will wrap the result of rewrapping a data key into an operation result for test // validation. func rewrapManyDataKeyResultsOpResult(result *mongo.RewrapManyDataKeyResult) (*operationResult, error) { raw := bsoncore.NewDocumentBuilder() if res := result.BulkWriteResult; res != nil { rawUpsertedIDs := emptyDocument var marshalErr error if res.UpsertedIDs != nil { rawUpsertedIDs, marshalErr = bson.Marshal(res.UpsertedIDs) if marshalErr != nil { return nil, fmt.Errorf("error marshalling UpsertedIDs map to BSON: %w", marshalErr) } } bulkWriteResult := bsoncore.NewDocumentBuilder() bulkWriteResult. AppendInt64("insertedCount", res.InsertedCount). AppendInt64("deletedCount", res.DeletedCount). AppendInt64("matchedCount", res.MatchedCount). AppendInt64("modifiedCount", res.ModifiedCount). AppendInt64("upsertedCount", res.UpsertedCount). AppendDocument("upsertedIds", rawUpsertedIDs) raw.AppendDocument("bulkWriteResult", bulkWriteResult.Build()) } return newDocumentResult(raw.Build(), nil), nil } // executeRewrapManyDataKey will attempt to re-wrap a number of data keys given a new provider, master key, and filter. func executeRewrapManyDataKey(ctx context.Context, operation *operation) (*operationResult, error) { cee, err := entities(ctx).clientEncryption(operation.Object) if err != nil { return nil, err } var filter bson.Raw var rmdko *options.RewrapManyDataKeyOptions elems, err := operation.Arguments.Elements() if err != nil { return nil, err } for _, elem := range elems { key := elem.Key() val := elem.Value() switch key { case "filter": filter = val.Document() case "opts": rmdko, err = parseRewrapManyDataKeyOptions(val.Document()) if err != nil { return nil, err } default: return nil, fmt.Errorf("unrecognized RewrapManyDataKey arg: %q", key) } } if filter == nil { return nil, newMissingArgumentError("filter") } result, err := cee.RewrapManyDataKey(ctx, filter, rmdko) if err != nil { return newErrorResult(err), nil } return rewrapManyDataKeyResultsOpResult(result) }