-
Notifications
You must be signed in to change notification settings - Fork 146
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TKN20 ciphertext-policy attribute based encryption scheme
- Loading branch information
Showing
38 changed files
with
6,098 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
Oops, something went wrong.