d1

package module
v2.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2022 License: AGPL-3.0 Imports: 10 Imported by: 0

README

CYBERCRYPT D1 library

The CYBERCRYPT D1 library provides cryptographic functions for data encryption and decryption, as well as enforcing access control around the encrypted data. It allows developers to easily implement various security schemes and protocols for protecting data.

Features

  • Authenticated Encryption with Associated Data (AEAD)
    • Binary data can be encrypted and decrypted using the standardized AES256-GCM authenticated encryption algorithm.
    • The Ciphertext and Associated Data can be securely stored in untrusted locations as the recovery of the plaintext is computationally infeasible without the Encryption Key, and any changes to the Ciphertext or Associated Data will be detected during the decryption process.
  • Key Wrapping with Authenticated Encryption
    • The data and its corresponding access control artifacts are encrypted with random 256-bit keys which are then wrapped with the configured Wrapping Key using the standardized KWP-AE algorithm.
    • Wrapped keys can be securely stored in untrusted locations (alongside the encrypted data) as the recovery of the plaintext key is computationally infeasible without the Wrapping Key, and any changes to the Wrapped Key will be detected during the unwrapping process.
  • Discretionary Access Control
    • Each encrypted data object has individual access controls defined by the data owner.
    • Data owners decide who can access the decrypted objects.
  • OpenID Connect (OIDC) Integration
    • User authentication can be done using the OpenID Connect Protocol.
    • Other Identity and Access Management (IAM) solutions can be easily integrated, or the Standalone Identity Provider can be used to easily get started without uisng an existing IAM.
  • Searcheable Symmetric Encryption (SSE)
    • The ability to efficiently search over encrypted objects without decrypting them can be added by creating an index of keywords and adding (Keyword, ID) pairs to it.
    • The index object hides the contents and the number of keywords and encrypted objects, as well as the mapping between them, so it can be securely stored in untrusted locations (alongside the encrypted data).
  • Encrypted Security Tokens
    • Security tokens with built-in expiry times and arbitrary encrypted data can be generated and verified.
    • Can be used for implementing token-based authentication/authorization schemes.

Installation

To use the library in your Go project, you first need to add it to your go.mod file by running the following command in the root folder of the project:

go get -u github.com/cybercryptio/d1-lib/v2@latest

Then, you can access the D1 library functions by importing the library in your application code:

import github.com/cybercryptio/d1-lib/v2

Usage

For examples on how to use the D1 library see the Examples section in the godocs.

In order to receive debug logging from the library you can pass a zerolog logger via the context (see zerolog.Logger.WithContext). Note that the library only logs at debug level, and that the logs may contain sensitive information. This functionality should therefore not be used under normal circumstances.

High level overview

For a high level overview of the main concepts and use cases of the D1 library see our Explainer Document.

API Reference

The API reference is published available on the Go Packages website at https://pkg.go.dev/github.com/cybercryptio/d1-lib/v2

License

The software in the CYBERCRYPT D1-Lib repository is dual-licensed under AGPL and a commercial license. If you wish to use the library under a commercial license please visit d1.cyber-crypt.com for details on how to obtain a commercial license.

Documentation

Overview

D1 is a library that provides easy access to data encryption with built in access control.

Example (AccessControl)

This example demonstrates how to use the D1 library to enforce discretionary access control for binary data.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/gofrs/uuid"

	d1lib "github.com/cybercryptio/d1-lib/v2"
	"github.com/cybercryptio/d1-lib/v2/data"
)

// The UserData struct models the data of a user. It contains both private data that should be kept confidential and public data that can be shared
// with other users, being protected cryptographically.
type UserData struct {
	private PrivateUserData
	public  PublicUserData
}

type PrivateUserData struct {
	token string
	data  data.Object
}

type PublicUserData struct {
	uid string
	gid string
	oid uuid.UUID
}

// createUserData instantiates a user with its private and public data.
func createUserData(ctx context.Context, d1 d1lib.D1) UserData {
	uid, gid, token := NewUser(ctx)

	privateUserObject := data.Object{
		Plaintext:      []byte("Plaintext"),
		AssociatedData: []byte("AssociatedData"),
	}

	oid, err := d1.Encrypt(ctx, token, &privateUserObject)
	if err != nil {
		log.Fatalf("Error encrypting object: %v", err)
	}

	err = d1.AddGroupsToAccess(ctx, token, oid, gid)
	if err != nil {
		log.Fatalf("Error adding group to access list: %v", err)
	}

	return UserData{
		PrivateUserData{token, privateUserObject},
		PublicUserData{uid, gid, oid},
	}
}

// This example demonstrates how to use the D1 library to enforce discretionary access control for binary data.
func main() {
	ctx := context.Background()

	// Instantiate the D1 library with the given keys.
	d1, err := d1lib.New(ctx, &keyProvider, &ioProvider, &idProvider)
	if err != nil {
		log.Fatalf("Error instantiating D1: %v", err)
	}

	// Create three users with their data.
	alice := createUserData(ctx, d1)
	bob := createUserData(ctx, d1)
	charlie := createUserData(ctx, d1)

	// charlie wants to share her data with bob.
	err = d1.AddGroupsToAccess(ctx, charlie.private.token, charlie.public.oid, bob.public.uid)
	if err != nil {
		log.Fatalf("Error adding group to access: %v", err)
	}

	// bob can now decrypt charlie's encrypted data.
	charliesDecryptedData, err := d1.Decrypt(ctx, bob.private.token, charlie.public.oid)
	if err != nil {
		log.Fatalf("Error decrypting object: %v", err)
	}
	fmt.Printf("%s %s\n", charliesDecryptedData.Plaintext, charliesDecryptedData.AssociatedData)

	// alice wants to form a group with charlie so that all of his previously encrypted data can be decrypted by charlie.
	err = idProvider.AddUserToGroups(ctx, alice.private.token, charlie.public.uid, alice.public.gid)
	if err != nil {
		log.Fatalf("Error adding user to group: %v", err)
	}

	// charlie can now decrypt all of alice's previously encrypted data.
	alicesDecryptedData, err := d1.Decrypt(ctx, charlie.private.token, alice.public.oid)
	if err != nil {
		log.Fatalf("Error decrypting object: %v", err)
	}
	fmt.Printf("%s %s\n", alicesDecryptedData.Plaintext, alicesDecryptedData.AssociatedData)

}
Output:

Plaintext AssociatedData
Plaintext AssociatedData
Example (BasicEncryptDecrypt)

This is a basic example demonstrating how to use the D1 library to encrypt and decrypt binary data.

package main

import (
	"context"
	"fmt"
	"log"

	d1lib "github.com/cybercryptio/d1-lib/v2"
	"github.com/cybercryptio/d1-lib/v2/data"
	"github.com/cybercryptio/d1-lib/v2/id"
	"github.com/cybercryptio/d1-lib/v2/io"
	"github.com/cybercryptio/d1-lib/v2/key"
)

// These are insecure keys used only for demonstration purposes.
var keyProvider = key.NewStatic(key.Keys{
	KEK: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	AEK: []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
	TEK: []byte{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
	IEK: []byte{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
})

// Store encrypted data in memory.
var ioProvider = io.NewMem()

var idProvider, _ = id.NewStandalone(
	id.StandaloneConfig{
		UEK: []byte{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
		GEK: []byte{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5},
		TEK: []byte{6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6},
	},
	&ioProvider,
)

func NewUser(ctx context.Context) (string, string, string) {
	uid, password, err := (&idProvider).NewUser(ctx, id.ScopeAll)
	if err != nil {
		log.Fatalf("Error creating user: %v", err)
	}

	token, _, err := (&idProvider).LoginUser(ctx, uid, password)
	if err != nil {
		log.Fatalf("Error logging in user: %v", err)
	}

	gid, err := idProvider.NewGroup(ctx, token, id.ScopeAll)
	if err != nil {
		log.Fatalf("Error creating group: %v", err)
	}

	err = idProvider.AddUserToGroups(ctx, token, uid, gid)
	if err != nil {
		log.Fatalf("Error adding user to group: %v", err)
	}

	return uid, gid, token
}

// This is a basic example demonstrating how to use the D1 library to encrypt and decrypt binary data.
func main() {
	ctx := context.Background()

	// Instantiate the D1 library with the given keys.
	d1, err := d1lib.New(ctx, &keyProvider, &ioProvider, &idProvider)
	if err != nil {
		log.Fatalf("Error instantiating D1: %v", err)
	}

	// Create a basic user.
	_, _, token := NewUser(ctx)

	// A simple binary object with associated data.
	binaryObject := data.Object{
		Plaintext:      []byte("Plaintext"),
		AssociatedData: []byte("AssociatedData"),
	}

	// Encrypt the object and get the resulting object ID. By default, only the default group of the
	// user who encrypted the object is allowed to decrypt the object.
	oid, err := d1.Encrypt(ctx, token, &binaryObject)
	if err != nil {
		log.Fatalf("Error encrypting object: %v", err)
	}

	// Decrypt the object using the given user as the authorizer.
	decryptedObject, err := d1.Decrypt(ctx, token, oid)
	if err != nil {
		log.Fatalf("Error decrypting object: %v", err)
	}

	fmt.Printf("Decrypted object plaintext: %s\nDecrypted object associated data: %s\n", decryptedObject.Plaintext, decryptedObject.AssociatedData)

}
Output:

Decrypted object plaintext: Plaintext
Decrypted object associated data: AssociatedData
Example (CreateToken)
ctx := context.Background()

// Instantiate the D1 library with the given keys.
d1, err := d1lib.New(ctx, &keyProvider, &ioProvider, &idProvider)
if err != nil {
	log.Fatalf("Error instantiating D1: %v", err)
}

// Create a token with encrypted contents and default expiry time.
token, err := d1.CreateToken(ctx, []byte("token contents"))
if err != nil {
	log.Fatalf("Error creating token: %v", err)
}

// Validate the token and fetch its decrypted contents. This call will fail if the token has expired or has been tampered with.
tokenContents, err := d1.GetTokenContents(ctx, &token)
if err != nil {
	log.Fatalf("Invalid token: %v", err)
}
fmt.Printf("%s", tokenContents)
Output:

token contents

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrAccessAlreadyExists = errors.New("access already exists")

Error returned if an access list already exists in the IO Provider.

View Source
var ErrAccessNotFound = errors.New("access not found")

Error returned if an access list was not found in the IO Provider.

View Source
var ErrNotAuthenticated = errors.New("not authenticated")

Error returned if the caller cannot be authenticated by the Identity Provider.

View Source
var ErrNotAuthorized = errors.New("not authorized")

Error returned if the caller tries to access data they are not authorized for.

View Source
var ErrObjectAlreadyExists = errors.New("object already exists")

Error returned if an object already exists in the IO Provider.

View Source
var ErrObjectNotFound = errors.New("object not found")

Error returned if an object was not found in the IO Provider.

Functions

This section is empty.

Types

type D1

type D1 struct {
	// contains filtered or unexported fields
}

D1 is the entry point to the library. All main functionality is exposed through methods on this struct.

func New

func New(ctx context.Context, keyProvider key.Provider, ioProvider io.Provider, idProvider id.Provider) (D1, error)

New creates a new instance of D1 configured with the given providers.

func (*D1) AddGroupsToAccess

func (d *D1) AddGroupsToAccess(ctx context.Context, token string, oid uuid.UUID, groups ...string) error

AddGroupsToAccess appends the provided groups to the object's access list, giving them access to the associated object. The authorizing Identity must be part of the access list.

The input ID is the identifier obtained by previously calling Encrypt.

Required scopes: - ModifyAccessGroups

func (*D1) AuthorizeIdentity

func (d *D1) AuthorizeIdentity(ctx context.Context, token string, oid uuid.UUID) error

AuthorizeIdentity checks whether the provided Identity is part of the object's access list, i.e. whether they are authorized to access the associated object. An error is returned if the Identity is not authorized.

The input ID is the identifier obtained by previously calling Encrypt.

Required scopes: - GetAccessGroups

func (*D1) CreateToken

func (d *D1) CreateToken(ctx context.Context, plaintext []byte) (data.SealedToken, error)

CreateToken encapsulates the provided plaintext data in an opaque, self contained token with an expiry time given by TokenValidity.

The contents of the token can be validated and retrieved with the GetTokenContents method.

func (*D1) Decrypt

func (d *D1) Decrypt(ctx context.Context, token string, oid uuid.UUID) (data.Object, error)

Decrypt fetches a sealed object and extracts the plaintext. The authorizing Identity must be part of the provided access list, either directly or through group membership.

The input ID is the identifier obtained by previously calling Encrypt.

The unsealed object may contain sensitive data.

Required scopes: - Decrypt

func (*D1) Delete

func (d *D1) Delete(ctx context.Context, token string, oid uuid.UUID) error

Delete deletes a sealed object. The authorizing Identity must be part of the provided access list, either directly or through group membership.

The input ID is the identifier obtained by previously calling Encrypt.

Required scopes: - Delete

func (*D1) Encrypt

func (d *D1) Encrypt(ctx context.Context, token string, object *data.Object, groups ...string) (uuid.UUID, error)

Encrypt creates a new sealed object containing the provided plaintext data as well as an access list that controls access to that data. The Identity of the caller is automatically added to the access list. To grant access to other callers either specify them in the optional 'groups' argument or see AddGroupsToAccess.

The returned ID is the unique identifier of the sealed object. It is used to identify the object and related data about the object to the IO Provider, and needs to be provided when decrypting the object.

For all practical purposes, the size of the ciphertext in the SealedObject is len(plaintext) + 48 bytes.

Required scopes: - Encrypt

func (*D1) GetAccessGroups

func (d *D1) GetAccessGroups(ctx context.Context, token string, oid uuid.UUID) (map[string]struct{}, error)

GetAccessGroups extracts the set of group IDs contained in the object's access list. The authorizing Identity must be part of the access list.

The input ID is the identifier obtained by previously calling Encrypt.

The set of group IDs is somewhat sensitive data, as it reveals what Identities have access to the associated object.

Required scopes: - GetAccessGroups

func (*D1) GetTokenContents

func (d *D1) GetTokenContents(ctx context.Context, token *data.SealedToken) ([]byte, error)

GetTokenContents extracts the plaintext data from a sealed token, provided that the token has not expired.

func (*D1) RemoveGroupsFromAccess

func (d *D1) RemoveGroupsFromAccess(ctx context.Context, token string, oid uuid.UUID, groups ...string) error

RemoveGroupsFromAccess removes the provided groups from the object's access list, preventing them from accessing the associated object. The authorizing Identity must be part of the access object.

The input ID is the identifier obtained by previously calling Encrypt.

Required scopes: - ModifyAccessGroups

func (*D1) Update

func (d *D1) Update(ctx context.Context, token string, oid uuid.UUID, object *data.Object) error

Update creates a new sealed object containing the provided plaintext data but uses a previously created access list to control access to that data. The authorizing Identity must be part of the provided access list, either directly or through group membership.

The input ID is the identifier obtained by previously calling Encrypt.

Required scopes: - Update

Directories

Path Synopsis
Package io contains the definition of the IO Provider, as well as various implementations of the concept.
Package io contains the definition of the IO Provider, as well as various implementations of the concept.
Package key contains the definition of the Key Provider, as well as various implementations of the concept.
Package key contains the definition of the Key Provider, as well as various implementations of the concept.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL