Skip to content

Commit

Permalink
Add TKN20 ciphertext-policy attribute based encryption scheme
Browse files Browse the repository at this point in the history
  • Loading branch information
tanyav2 authored and armfazh committed Nov 9, 2022
1 parent 6ab4dfe commit 34cd12b
Show file tree
Hide file tree
Showing 38 changed files with 6,098 additions and 0 deletions.
132 changes: 132 additions & 0 deletions abe/cpabe/tkn20/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package tkn20_test

import (
"bytes"
"crypto/rand"
"fmt"
"log"
"strconv"

cpabe "github.com/cloudflare/circl/abe/cpabe/tkn20"
)

func checkPolicy(in map[string][]string) bool {
possiblePairs := map[string][]string{
"occupation": {"wizard", "doctor", "ghost"},
"country": {"US", "croatia"},
"age": {},
}
isValid := func(key string, value string) bool {
vs, ok := possiblePairs[key]
if !ok {
return false
}
if key == "age" {
age, err := strconv.Atoi(value)
if err != nil {
return false
}
if age < 13 || age > 100 {
return false
}
} else {
for _, v := range vs {
if value == v {
return true
}
}
}
return false
}
for k, v := range in {
for _, value := range v {
if !isValid(k, value) {
return false
}
}
}
return true
}

func Example() {
policyStr := `(occupation: doctor) and (country: US)`
invalidPolicyStr := `(ocupation: doctor) and (country: pacific)`
msgStr := `must have the precious 🎃`
wrongAttrsMap := map[string]string{"occupation": "doctor", "country": "croatia"}
rightAttrsMap := map[string]string{"occupation": "doctor", "country": "US", "age": "16"}

publicKey, systemSecretKey, err := cpabe.Setup(rand.Reader)
if err != nil {
log.Fatalf("%s", err)
}

policy := cpabe.Policy{}
err = policy.FromString(policyStr)
if err != nil {
log.Fatal(err)
}
if !checkPolicy(policy.ExtractAttributeValuePairs()) {
log.Fatalf("policy check failed for valid policy")
}

fmt.Println(policy.String())
invalidPolicy := cpabe.Policy{}
err = invalidPolicy.FromString(invalidPolicyStr)
if err != nil {
log.Fatal(err)
}
if checkPolicy(invalidPolicy.ExtractAttributeValuePairs()) {
log.Fatalf("policy check should fail for invalid policy")
}

// encrypt the secret message for a given policy
ct, err := publicKey.Encrypt(rand.Reader, policy, []byte(msgStr))
if err != nil {
log.Fatalf("%s", err)
}

// generate secret key for certain set of attributes
wrongAttrs := cpabe.Attributes{}
wrongAttrs.FromMap(wrongAttrsMap)
rightAttrs := cpabe.Attributes{}
rightAttrs.FromMap(rightAttrsMap)

wrongSecretKey, _ := systemSecretKey.KeyGen(rand.Reader, wrongAttrs)
rightSecretKey, _ := systemSecretKey.KeyGen(rand.Reader, rightAttrs)

wrongSat := policy.Satisfaction(wrongAttrs)
if wrongSat {
log.Fatalf("wrong attributes should not satisfy policy")
}
rightSat := policy.Satisfaction(rightAttrs)
if !rightSat {
log.Fatalf("right attributes should satisfy policy")
}

// wrong attrs should not satisfy ciphertext
wrongCtSat := wrongAttrs.CouldDecrypt(ct)
if wrongCtSat {
log.Fatalf("wrong attrs should not satisfy ciphertext")
}
rightCtSat := rightAttrs.CouldDecrypt(ct)
if rightCtSat == false {
log.Fatalf("right attrs should satisfy ciphertext")
}

// attempt to decrypt with wrong attributes should fail
pt, err := wrongSecretKey.Decrypt(ct)
if err == nil {
log.Fatalf("decryption using wrong attrs should have failed, plaintext: %s", pt)
}

pt, err = rightSecretKey.Decrypt(ct)
if err != nil {
log.Fatalf("decryption using right attrs should have succeeded, plaintext: %s", pt)
}
if !bytes.Equal(pt, []byte(msgStr)) {
log.Fatalf("recoverd plaintext: %s is not equal to original msg: %s", pt, msgStr)
}
fmt.Println("Successfully recovered plaintext")
// Output: (occupation:doctor and country:US)
// Successfully recovered plaintext
}
65 changes: 65 additions & 0 deletions abe/cpabe/tkn20/format_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package tkn20

import (
"os"
"testing"
)

func TestPublicKeyFormat(t *testing.T) {
paramsData, err := os.ReadFile("testdata/publicKey")
if err != nil {
t.Fatalf("Unable to read public key")
}
pp := &PublicKey{}
err = pp.UnmarshalBinary(paramsData)
if err != nil {
t.Fatalf("unable to parse public key")
}
}

func TestSystemSecretKeyFormat(t *testing.T) {
secret, err := os.ReadFile("testdata/secretKey")
if err != nil {
t.Fatalf("Unable to read secret key")
}
sk := &SystemSecretKey{}
err = sk.UnmarshalBinary(secret)
if err != nil {
t.Fatalf("unable to parse system secret key")
}
}

func TestAttributeKeyFormat(t *testing.T) {
attributeKey, err := os.ReadFile("testdata/attributeKey")
if err != nil {
t.Fatalf("Unable to read secret key")
}
sk := &AttributeKey{}
err = sk.UnmarshalBinary(attributeKey)
if err != nil {
t.Fatalf("unable to parse secret key")
}
}

func TestCiphertext(t *testing.T) {
ciphertext, err := os.ReadFile("testdata/ciphertext")
if err != nil {
t.Fatalf("Unable to read ciphertext data")
}
policyKey, err := os.ReadFile("testdata/attributeKey")
if err != nil {
t.Fatalf("Unable to read secret key")
}
sk := AttributeKey{}
err = sk.UnmarshalBinary(policyKey)
if err != nil {
t.Fatalf("unable to parse secret key")
}
msg, err := sk.Decrypt(ciphertext)
if err != nil {
t.Fatal("unable to decrypt message")
}
if string(msg) != "Be sure to drink your ovaltine!" {
t.Fatal("message incorrect")
}
}
94 changes: 94 additions & 0 deletions abe/cpabe/tkn20/internal/dsl/ast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package dsl

import (
"fmt"

"github.com/cloudflare/circl/abe/cpabe/tkn20/internal/tkn"
)

var operators = map[string]int{
"and": tkn.Andgate,
"or": tkn.Orgate,
}

type attrValue struct {
value string
positive bool
}

type attr struct {
key string
id int
}

type gate struct {
op string
in1 attr
in2 attr
out attr
}

type Ast struct {
wires map[attr]attrValue
gates []gate
}

func (t *Ast) RunPasses() (*tkn.Policy, error) {
inputs, err := t.hashAttrValues()
if err != nil {
return nil, fmt.Errorf("attribute values could not be hashed: %s", err)
}

gates, err := t.transformGates()
if err != nil {
return nil, fmt.Errorf("gates could not be converted into a formula: %s", err)
}

return &tkn.Policy{
Inputs: inputs,
F: tkn.Formula{Gates: gates},
}, nil
}

func (t *Ast) hashAttrValues() ([]tkn.Wire, error) {
wires := make([]tkn.Wire, len(t.wires))
for k, v := range t.wires {
value := tkn.HashStringToScalar(AttrHashKey, v.value)
if value == nil {
return nil, fmt.Errorf("error on hashing")
}
wire := tkn.Wire{
Label: k.key,
RawValue: v.value,
Value: value,
Positive: v.positive,
}
wires[k.id] = wire
}
return wires, nil
}

func (t *Ast) transformGates() ([]tkn.Gate, error) {
lenGates := len(t.gates)
gates := make([]tkn.Gate, lenGates)
for i, g := range t.gates {
class, ok := operators[g.op]
if !ok {
return nil, fmt.Errorf("invalid operator %s", g.op)
}
wireIDs := [3]int{g.in1.id, g.in2.id, g.out.id}
for j, wireID := range wireIDs {
if wireID < 0 {
wireIDs[j] = -1*wireID + lenGates
}
}
gate := tkn.Gate{
Class: class,
In0: wireIDs[0],
In1: wireIDs[1],
Out: wireIDs[2],
}
gates[i] = gate
}
return gates, nil
}
19 changes: 19 additions & 0 deletions abe/cpabe/tkn20/internal/dsl/dsl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dsl

import "github.com/cloudflare/circl/abe/cpabe/tkn20/internal/tkn"

var AttrHashKey = []byte("attribute value hashing")

func Run(source string) (*tkn.Policy, error) {
l := newLexer(source)
err := l.scanTokens()
if err != nil {
return nil, err
}
p := newParser(l.tokens)
ast, err := p.parse()
if err != nil {
return nil, err
}
return ast.RunPasses()
}
Loading

0 comments on commit 34cd12b

Please sign in to comment.