Skip to content

Commit 34cd12b

Browse files
tanyav2armfazh
authored andcommitted
Add TKN20 ciphertext-policy attribute based encryption scheme
1 parent 6ab4dfe commit 34cd12b

38 files changed

+6098
-0
lines changed

abe/cpabe/tkn20/example_test.go

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package tkn20_test
2+
3+
import (
4+
"bytes"
5+
"crypto/rand"
6+
"fmt"
7+
"log"
8+
"strconv"
9+
10+
cpabe "github.com/cloudflare/circl/abe/cpabe/tkn20"
11+
)
12+
13+
func checkPolicy(in map[string][]string) bool {
14+
possiblePairs := map[string][]string{
15+
"occupation": {"wizard", "doctor", "ghost"},
16+
"country": {"US", "croatia"},
17+
"age": {},
18+
}
19+
isValid := func(key string, value string) bool {
20+
vs, ok := possiblePairs[key]
21+
if !ok {
22+
return false
23+
}
24+
if key == "age" {
25+
age, err := strconv.Atoi(value)
26+
if err != nil {
27+
return false
28+
}
29+
if age < 13 || age > 100 {
30+
return false
31+
}
32+
} else {
33+
for _, v := range vs {
34+
if value == v {
35+
return true
36+
}
37+
}
38+
}
39+
return false
40+
}
41+
for k, v := range in {
42+
for _, value := range v {
43+
if !isValid(k, value) {
44+
return false
45+
}
46+
}
47+
}
48+
return true
49+
}
50+
51+
func Example() {
52+
policyStr := `(occupation: doctor) and (country: US)`
53+
invalidPolicyStr := `(ocupation: doctor) and (country: pacific)`
54+
msgStr := `must have the precious 🎃`
55+
wrongAttrsMap := map[string]string{"occupation": "doctor", "country": "croatia"}
56+
rightAttrsMap := map[string]string{"occupation": "doctor", "country": "US", "age": "16"}
57+
58+
publicKey, systemSecretKey, err := cpabe.Setup(rand.Reader)
59+
if err != nil {
60+
log.Fatalf("%s", err)
61+
}
62+
63+
policy := cpabe.Policy{}
64+
err = policy.FromString(policyStr)
65+
if err != nil {
66+
log.Fatal(err)
67+
}
68+
if !checkPolicy(policy.ExtractAttributeValuePairs()) {
69+
log.Fatalf("policy check failed for valid policy")
70+
}
71+
72+
fmt.Println(policy.String())
73+
invalidPolicy := cpabe.Policy{}
74+
err = invalidPolicy.FromString(invalidPolicyStr)
75+
if err != nil {
76+
log.Fatal(err)
77+
}
78+
if checkPolicy(invalidPolicy.ExtractAttributeValuePairs()) {
79+
log.Fatalf("policy check should fail for invalid policy")
80+
}
81+
82+
// encrypt the secret message for a given policy
83+
ct, err := publicKey.Encrypt(rand.Reader, policy, []byte(msgStr))
84+
if err != nil {
85+
log.Fatalf("%s", err)
86+
}
87+
88+
// generate secret key for certain set of attributes
89+
wrongAttrs := cpabe.Attributes{}
90+
wrongAttrs.FromMap(wrongAttrsMap)
91+
rightAttrs := cpabe.Attributes{}
92+
rightAttrs.FromMap(rightAttrsMap)
93+
94+
wrongSecretKey, _ := systemSecretKey.KeyGen(rand.Reader, wrongAttrs)
95+
rightSecretKey, _ := systemSecretKey.KeyGen(rand.Reader, rightAttrs)
96+
97+
wrongSat := policy.Satisfaction(wrongAttrs)
98+
if wrongSat {
99+
log.Fatalf("wrong attributes should not satisfy policy")
100+
}
101+
rightSat := policy.Satisfaction(rightAttrs)
102+
if !rightSat {
103+
log.Fatalf("right attributes should satisfy policy")
104+
}
105+
106+
// wrong attrs should not satisfy ciphertext
107+
wrongCtSat := wrongAttrs.CouldDecrypt(ct)
108+
if wrongCtSat {
109+
log.Fatalf("wrong attrs should not satisfy ciphertext")
110+
}
111+
rightCtSat := rightAttrs.CouldDecrypt(ct)
112+
if rightCtSat == false {
113+
log.Fatalf("right attrs should satisfy ciphertext")
114+
}
115+
116+
// attempt to decrypt with wrong attributes should fail
117+
pt, err := wrongSecretKey.Decrypt(ct)
118+
if err == nil {
119+
log.Fatalf("decryption using wrong attrs should have failed, plaintext: %s", pt)
120+
}
121+
122+
pt, err = rightSecretKey.Decrypt(ct)
123+
if err != nil {
124+
log.Fatalf("decryption using right attrs should have succeeded, plaintext: %s", pt)
125+
}
126+
if !bytes.Equal(pt, []byte(msgStr)) {
127+
log.Fatalf("recoverd plaintext: %s is not equal to original msg: %s", pt, msgStr)
128+
}
129+
fmt.Println("Successfully recovered plaintext")
130+
// Output: (occupation:doctor and country:US)
131+
// Successfully recovered plaintext
132+
}

abe/cpabe/tkn20/format_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package tkn20
2+
3+
import (
4+
"os"
5+
"testing"
6+
)
7+
8+
func TestPublicKeyFormat(t *testing.T) {
9+
paramsData, err := os.ReadFile("testdata/publicKey")
10+
if err != nil {
11+
t.Fatalf("Unable to read public key")
12+
}
13+
pp := &PublicKey{}
14+
err = pp.UnmarshalBinary(paramsData)
15+
if err != nil {
16+
t.Fatalf("unable to parse public key")
17+
}
18+
}
19+
20+
func TestSystemSecretKeyFormat(t *testing.T) {
21+
secret, err := os.ReadFile("testdata/secretKey")
22+
if err != nil {
23+
t.Fatalf("Unable to read secret key")
24+
}
25+
sk := &SystemSecretKey{}
26+
err = sk.UnmarshalBinary(secret)
27+
if err != nil {
28+
t.Fatalf("unable to parse system secret key")
29+
}
30+
}
31+
32+
func TestAttributeKeyFormat(t *testing.T) {
33+
attributeKey, err := os.ReadFile("testdata/attributeKey")
34+
if err != nil {
35+
t.Fatalf("Unable to read secret key")
36+
}
37+
sk := &AttributeKey{}
38+
err = sk.UnmarshalBinary(attributeKey)
39+
if err != nil {
40+
t.Fatalf("unable to parse secret key")
41+
}
42+
}
43+
44+
func TestCiphertext(t *testing.T) {
45+
ciphertext, err := os.ReadFile("testdata/ciphertext")
46+
if err != nil {
47+
t.Fatalf("Unable to read ciphertext data")
48+
}
49+
policyKey, err := os.ReadFile("testdata/attributeKey")
50+
if err != nil {
51+
t.Fatalf("Unable to read secret key")
52+
}
53+
sk := AttributeKey{}
54+
err = sk.UnmarshalBinary(policyKey)
55+
if err != nil {
56+
t.Fatalf("unable to parse secret key")
57+
}
58+
msg, err := sk.Decrypt(ciphertext)
59+
if err != nil {
60+
t.Fatal("unable to decrypt message")
61+
}
62+
if string(msg) != "Be sure to drink your ovaltine!" {
63+
t.Fatal("message incorrect")
64+
}
65+
}

abe/cpabe/tkn20/internal/dsl/ast.go

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package dsl
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/cloudflare/circl/abe/cpabe/tkn20/internal/tkn"
7+
)
8+
9+
var operators = map[string]int{
10+
"and": tkn.Andgate,
11+
"or": tkn.Orgate,
12+
}
13+
14+
type attrValue struct {
15+
value string
16+
positive bool
17+
}
18+
19+
type attr struct {
20+
key string
21+
id int
22+
}
23+
24+
type gate struct {
25+
op string
26+
in1 attr
27+
in2 attr
28+
out attr
29+
}
30+
31+
type Ast struct {
32+
wires map[attr]attrValue
33+
gates []gate
34+
}
35+
36+
func (t *Ast) RunPasses() (*tkn.Policy, error) {
37+
inputs, err := t.hashAttrValues()
38+
if err != nil {
39+
return nil, fmt.Errorf("attribute values could not be hashed: %s", err)
40+
}
41+
42+
gates, err := t.transformGates()
43+
if err != nil {
44+
return nil, fmt.Errorf("gates could not be converted into a formula: %s", err)
45+
}
46+
47+
return &tkn.Policy{
48+
Inputs: inputs,
49+
F: tkn.Formula{Gates: gates},
50+
}, nil
51+
}
52+
53+
func (t *Ast) hashAttrValues() ([]tkn.Wire, error) {
54+
wires := make([]tkn.Wire, len(t.wires))
55+
for k, v := range t.wires {
56+
value := tkn.HashStringToScalar(AttrHashKey, v.value)
57+
if value == nil {
58+
return nil, fmt.Errorf("error on hashing")
59+
}
60+
wire := tkn.Wire{
61+
Label: k.key,
62+
RawValue: v.value,
63+
Value: value,
64+
Positive: v.positive,
65+
}
66+
wires[k.id] = wire
67+
}
68+
return wires, nil
69+
}
70+
71+
func (t *Ast) transformGates() ([]tkn.Gate, error) {
72+
lenGates := len(t.gates)
73+
gates := make([]tkn.Gate, lenGates)
74+
for i, g := range t.gates {
75+
class, ok := operators[g.op]
76+
if !ok {
77+
return nil, fmt.Errorf("invalid operator %s", g.op)
78+
}
79+
wireIDs := [3]int{g.in1.id, g.in2.id, g.out.id}
80+
for j, wireID := range wireIDs {
81+
if wireID < 0 {
82+
wireIDs[j] = -1*wireID + lenGates
83+
}
84+
}
85+
gate := tkn.Gate{
86+
Class: class,
87+
In0: wireIDs[0],
88+
In1: wireIDs[1],
89+
Out: wireIDs[2],
90+
}
91+
gates[i] = gate
92+
}
93+
return gates, nil
94+
}

abe/cpabe/tkn20/internal/dsl/dsl.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package dsl
2+
3+
import "github.com/cloudflare/circl/abe/cpabe/tkn20/internal/tkn"
4+
5+
var AttrHashKey = []byte("attribute value hashing")
6+
7+
func Run(source string) (*tkn.Policy, error) {
8+
l := newLexer(source)
9+
err := l.scanTokens()
10+
if err != nil {
11+
return nil, err
12+
}
13+
p := newParser(l.tokens)
14+
ast, err := p.parse()
15+
if err != nil {
16+
return nil, err
17+
}
18+
return ast.RunPasses()
19+
}

0 commit comments

Comments
 (0)