Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

removed internal jose library, structured project a wee bit nicer, added e2e test #5

Merged
merged 5 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Option to provide "additional validation" for sd-jwt validation
- Option to provide "additional validation" for kb-jwt validation
- Function to retrieve kb-jwt contents as map
- KB JWT hash validation


Signing:
Expand Down
130 changes: 130 additions & 0 deletions disclosure/disclosure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package disclosure

import (
"encoding/base64"
"encoding/json"
"fmt"
e "github.com/MichaelFraser99/go-sd-jwt/internal/error"
s "github.com/MichaelFraser99/go-sd-jwt/internal/salt"
"hash"
)

// Disclosure this object represents a single disclosure in a SD-JWT.
// Salt the base64url encoded cryptographically secure string used during generation
// Key the key of the disclosed value. Only present for disclosed object values and not set when for an array element
// Value the value being disclosed
// EncodedValue the resulting base64url encoded disclosure array
type Disclosure struct {
Salt string
Key *string
Value any
EncodedValue string
}

func (d *Disclosure) Hash(hash hash.Hash) []byte {
hash.Write([]byte(d.EncodedValue))
hashedBytes := hash.Sum(nil)

b64Hash := make([]byte, base64.RawURLEncoding.EncodedLen(len(hashedBytes)))
base64.RawURLEncoding.Encode(b64Hash, hashedBytes)
return b64Hash
}

func NewFromObject(key string, value any, salt *string) (*Disclosure, error) {
if key == "" || key == "_sd" || key == "..." {
return nil, fmt.Errorf("%winvalid key value provided, must not be empty, '_sd', or '...'", e.InvalidDisclosure)
}

var saltValue string
if salt == nil {
newSalt, err := s.NewSalt()
if err != nil {
return nil, err
}
saltValue = *newSalt
} else {
saltValue = *salt
}

disclosureArray := []any{saltValue, key, value}
dBytes, err := json.Marshal(disclosureArray)
if err != nil {
return nil, fmt.Errorf("error encoding disclosure array as bytes: %w", err)
}

encodedDisclosureArray := make([]byte, base64.RawURLEncoding.EncodedLen(len(dBytes)))
base64.RawURLEncoding.Encode(encodedDisclosureArray, dBytes)

disclosure := &Disclosure{
Salt: saltValue,
Key: &key,
Value: value,
EncodedValue: string(encodedDisclosureArray),
}

return disclosure, nil
}

func NewFromArrayElement(element any, salt *string) (*Disclosure, error) {
var saltValue string
if salt == nil {
newSalt, err := s.NewSalt()
if err != nil {
return nil, err
}
saltValue = *newSalt
} else {
saltValue = *salt
}

disclosureArray := []any{saltValue, element}
dBytes, err := json.Marshal(disclosureArray)
if err != nil {
return nil, fmt.Errorf("error encoding disclosure array as bytes: %w", err)
}

encodedDisclosureArray := make([]byte, base64.RawURLEncoding.EncodedLen(len(dBytes)))
base64.RawURLEncoding.Encode(encodedDisclosureArray, dBytes)

disclosure := &Disclosure{
Salt: saltValue,
Value: element,
EncodedValue: string(encodedDisclosureArray),
}

return disclosure, nil
}

func NewFromDisclosure(disclosure string) (*Disclosure, error) {
d := &Disclosure{
EncodedValue: disclosure,
}

decoded, err := base64.RawURLEncoding.DecodeString(disclosure)
if err != nil {
return nil, fmt.Errorf("%werror base64url decoding provided disclosure: %s", e.InvalidDisclosure, err.Error())
}

var dArray []any
err = json.Unmarshal(decoded, &dArray)
if err != nil {
return nil, fmt.Errorf("%werror parsing decoded disclosure as array: %s", e.InvalidDisclosure, err.Error())
}

if len(dArray) == 2 {
d.Salt = dArray[0].(string)
d.Value = dArray[1]
} else if len(dArray) == 3 {
d.Salt = dArray[0].(string)
d.Key = String(dArray[1].(string))
d.Value = dArray[2]
} else {
return nil, fmt.Errorf("%winvalid disclosure contents: %s", e.InvalidDisclosure, string(decoded))
}

return d, nil
}

func String(s string) *string {
return &s
}
140 changes: 140 additions & 0 deletions disclosure/disclosure_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package disclosure

import (
"crypto/sha256"
"fmt"
"testing"
)

func TestNewFromObject(t *testing.T) {
disclosure, err := NewFromObject("family_name", "Möbius", String("_26bc4LT-ac6q2KI6cBW5es"))
if err != nil {
t.Fatalf("no error expected: %s", err.Error())
}

if disclosure.Key == nil {
t.Fatalf("key should not be nil")
}
if *disclosure.Key != "family_name" {
t.Errorf("key should be family_name is: %s", *disclosure.Key)
}
if disclosure.Salt != "_26bc4LT-ac6q2KI6cBW5es" {
t.Errorf("unexpected salt value returned: %s", disclosure.Salt)
}
strValue, ok := disclosure.Value.(string)
if !ok {
t.Fatalf("Returned value should be a string")
}
if strValue != "Möbius" {
t.Errorf("unexpected disclosure value returned: %s", disclosure.Value)
}
if disclosure.EncodedValue != "WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsImZhbWlseV9uYW1lIiwiTcO2Yml1cyJd" {
t.Errorf("unexpected encoded value produced: %s", disclosure.EncodedValue)
}
}

func TestNewFromArrayElement(t *testing.T) {
disclosure, err := NewFromArrayElement("FR", String("lklxF5jMYlGTPUovMNIvCA"))
if err != nil {
t.Fatalf("no error expected: %s", err.Error())
}

if disclosure.Key != nil {
t.Fatalf("key should not be nil, is: %s", *disclosure.Key)
}
if disclosure.Salt != "lklxF5jMYlGTPUovMNIvCA" {
t.Errorf("unexpected salt value returned: %s", disclosure.Salt)
}
if string(disclosure.Value.(string)) != "FR" {
t.Errorf("unexpected disclosure value returned: %s", disclosure.Value)
}
if disclosure.EncodedValue != "WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwiRlIiXQ" {
t.Errorf("unexpected encoded value produced: %s", disclosure.EncodedValue)
}
}

func TestNewFromDisclosureObject(t *testing.T) {
disclosures := []string{
"WwoiXzI2YmM0TFQtYWM2cTJLSTZjQlc1ZXMiLAoiZmFtaWx5X25hbWUiLAoiTcO2Yml1cyIKXQ",
"WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsICJmYW1pbHlfbmFtZSIsICJNXHUwMGY2Yml1cyJd",
"WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsImZhbWlseV9uYW1lIiwiTcO2Yml1cyJd",
}

for i, d := range disclosures {
t.Run(fmt.Sprintf("disclosure-%d", i), func(t *testing.T) {
disclosure, err := NewFromDisclosure(d)
if err != nil {
t.Fatalf("no error expected: %s", err.Error())
}

if disclosure.Key == nil {
t.Fatalf("key should not be nil")
}
if *disclosure.Key != "family_name" {
t.Errorf("key should be family_name is: %s", *disclosure.Key)
}
if disclosure.Salt != "_26bc4LT-ac6q2KI6cBW5es" {
t.Errorf("unexpected salt value returned: %s", disclosure.Salt)
}
if disclosure.Value.(string) != "Möbius" {
t.Errorf("unexpected disclosure value returned: %s", disclosure.Value)
}
if disclosure.EncodedValue != d {
t.Errorf("unexpected encoded value produced: %s", disclosure.EncodedValue)
}
})
}
}

func TestNewFromDisclosureElementObject(t *testing.T) {
disclosures := []string{
"WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIkZSIl0",
"WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwiRlIiXQ",
}

for i, d := range disclosures {
t.Run(fmt.Sprintf("disclosure-%d", i), func(t *testing.T) {
disclosure, err := NewFromDisclosure(d)
if err != nil {
t.Fatalf("no error expected: %s", err.Error())
}

if disclosure.Key != nil {
t.Fatalf("key should not be nil, is: %s", *disclosure.Key)
}
if disclosure.Salt != "lklxF5jMYlGTPUovMNIvCA" {
t.Errorf("unexpected salt value returned: %s", disclosure.Salt)
}
if disclosure.Value.(string) != "FR" {
t.Errorf("unexpected disclosure value returned: %s", disclosure.Value)
}
if disclosure.EncodedValue != d {
t.Errorf("unexpected encoded value produced: %s", disclosure.EncodedValue)
}
})
}
}

func TestDisclosure_Hash(t *testing.T) {
objectDisclosure, err := NewFromDisclosure("WyI2cU1RdlJMNWhhaiIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0")
if err != nil {
t.Fatalf("no error expected: %s", err.Error())
}

arrayElementDisclosure, err := NewFromDisclosure("WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIkZSIl0")
if err != nil {
t.Fatalf("no error expected: %s", err.Error())
}

hash := sha256.New()
objectHash := objectDisclosure.Hash(hash)
if string(objectHash) != "uutlBuYeMDyjLLTpf6Jxi7yNkEF35jdyWMn9U7b_RYY" {
t.Errorf("unexpected hash result: %s", string(objectHash))
}

hash.Reset()
arrayHash := arrayElementDisclosure.Hash(hash)
if string(arrayHash) != "w0I8EKcdCtUPkGCNUrfwVp2xEgNjtoIDlOxc9-PlOhs" {
t.Errorf("unexpected hash result: %s", string(arrayHash))
}
}
Loading
Loading