Skip to content

Commit

Permalink
Address misc spec updates (#146)
Browse files Browse the repository at this point in the history
* spec updates

* add thumbprint checking

* signed

* sign

* update vector to handle excluded jwk thumbprint id from vm id
  • Loading branch information
decentralgabe authored Mar 22, 2024
1 parent e2db0ba commit 5176956
Show file tree
Hide file tree
Showing 13 changed files with 103 additions and 71 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,6 @@ dist

# spec stuff
spec/build
spec/node_modules
spec/node_modules

coverage.out
12 changes: 6 additions & 6 deletions impl/concurrencytest/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import (
"sync"
"time"

"github.com/sirupsen/logrus"

"github.com/TBD54566975/did-dht-method/internal/did"
"github.com/TBD54566975/did-dht-method/pkg/dht"
"github.com/sirupsen/logrus"
)

var (
Expand All @@ -22,7 +23,7 @@ var (
func main() {
logrus.SetLevel(logrus.DebugLevel)

programstart := time.Now()
programStart := time.Now()

var wg sync.WaitGroup
for _, server := range servers {
Expand All @@ -43,7 +44,7 @@ func main() {
}

getStart := time.Now()
if err := get(s, suffix); err != nil {
if err = get(s, suffix); err != nil {
log = log.WithError(err)
}
log.WithField("time", time.Since(getStart)).Info("GET request completed")
Expand All @@ -55,7 +56,7 @@ func main() {

wg.Wait()

logrus.WithField("time", time.Since(programstart)).Info("concurrency test completed")
logrus.WithField("time", time.Since(programStart)).Info("concurrency test completed")
}

func put(server string) (string, error) {
Expand Down Expand Up @@ -99,8 +100,7 @@ func get(server, suffix string) error {
}
defer resp.Body.Close()

_, err = io.ReadAll(resp.Body)
if err != nil {
if _, err = io.ReadAll(resp.Body); err != nil {
return err
}

Expand Down
4 changes: 2 additions & 2 deletions impl/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.22

require (
github.com/BurntSushi/toml v0.3.1
github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240109225800-c9f99e5db02a
github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240321215515-97ccd06a631d
github.com/allegro/bigcache/v3 v3.1.0
github.com/anacrolix/dht/v2 v2.20.0
github.com/anacrolix/log v0.14.0
Expand Down Expand Up @@ -75,7 +75,7 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
Expand Down
8 changes: 4 additions & 4 deletions impl/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrX
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240109225800-c9f99e5db02a h1:xvPnLvpf6zCNkgA5brq38wFOJBWoq3rMxXRH8xHrHp4=
github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240109225800-c9f99e5db02a/go.mod h1:v3JouHTB++xJi6zTC+hbtTeBZkfdp/orSHF/+inJozU=
github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240321215515-97ccd06a631d h1:lEekCCpwjxtQBNNUoUmPiDg35t3quQzDgtetug5xbx4=
github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240321215515-97ccd06a631d/go.mod h1:UoNlAhXuPb1VxsAkNbLyr4XYeyHhLvcwSbkmsaOeGjM=
github.com/alecthomas/assert/v2 v2.0.0-alpha3 h1:pcHeMvQ3OMstAWgaeaXIAL8uzB9xMm2zlxt+/4ml8lk=
github.com/alecthomas/assert/v2 v2.0.0-alpha3/go.mod h1:+zD0lmDXTeQj7TgDgCt0ePWxb0hMC1G+PGTsTCv1B9o=
github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8=
Expand Down Expand Up @@ -328,8 +328,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
Expand Down
27 changes: 9 additions & 18 deletions impl/internal/cli/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ package cli
import (
"os"

"github.com/TBD54566975/ssi-sdk/util"
"github.com/goccy/go-json"

"github.com/sirupsen/logrus"

"github.com/TBD54566975/did-dht-method/internal"
)

Expand All @@ -20,15 +19,13 @@ func Read() (internal.Identities, error) {
homeDir, _ := os.UserHomeDir()
diddhtFile := homeDir + diddhtPath
if _, err := os.Stat(diddhtFile); os.IsNotExist(err) {
logrus.WithError(err).Error("failed to find diddht file")
return nil, nil
return nil, util.LoggingErrorMsg(err, "failed to find diddht file")
}
f, _ := os.Open(diddhtFile)
defer f.Close()
var identities internal.Identities
if err := json.NewDecoder(f).Decode(&identities); err != nil {
logrus.WithError(err).Error("failed to decode diddht file")
return nil, err
return nil, util.LoggingErrorMsg(err, "failed to decode diddht file")
}
return identities, nil
}
Expand All @@ -41,37 +38,31 @@ func Write(id string, identity internal.Identity) error {
var err error
if _, err = os.Stat(diddhtFile); os.IsNotExist(err) {
if err = os.Mkdir(homeDir+diddhtDir, 0700); err != nil {
logrus.WithError(err).Error("failed to create diddht directory")
return err
return util.LoggingErrorMsg(err, "failed to create diddht directory")
}
if _, err = os.Create(homeDir + diddhtPath); err != nil {
logrus.WithError(err).Error("failed to create diddht file")
return err
return util.LoggingErrorMsg(err, "failed to create diddht file")
}
identities = internal.Identities{id: identity}
} else {
identities, err = Read()
if err != nil {
logrus.WithError(err).Error("failed to read diddht file")
return err
return util.LoggingErrorMsg(err, "failed to read diddht file")
}
if _, ok := identities[identity.Base58PublicKey]; ok {
logrus.WithError(err).Error("identity already exists")
return err
return util.LoggingErrorMsg(err, "identity already exists")
}
identities[id] = identity
}

identitiesBytes, err := json.Marshal(identities)
if err != nil {
logrus.WithError(err).Error("failed to marshal identities")
return err
return util.LoggingErrorMsg(err, "failed to marshal identities")
}

f, _ := os.OpenFile(diddhtFile, os.O_WRONLY, os.ModeAppend)
if _, err = f.WriteString(string(identitiesBytes)); err != nil {
logrus.WithError(err).Error("failed to write identities to diddht file")
return err
return util.LoggingErrorMsg(err, "failed to write identities to diddht file")
}
return f.Close()
}
3 changes: 1 addition & 2 deletions impl/internal/did/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"net/url"

"github.com/TBD54566975/ssi-sdk/did"
"github.com/TBD54566975/ssi-sdk/util"
"github.com/anacrolix/dht/v2/bep44"
"github.com/miekg/dns"
"github.com/pkg/errors"
Expand Down Expand Up @@ -55,7 +54,7 @@ func (c *GatewayClient) GetDIDDocument(id string) (*did.Document, []TypeIndex, e
}
msg := new(dns.Msg)
if err = msg.Unpack(body[72:]); err != nil {
return nil, nil, util.LoggingErrorMsg(err, "failed to unpack records")
return nil, nil, errors.Wrap(err, "failed to unpack records")
}
return d.FromDNSPacket(msg)
}
Expand Down
67 changes: 54 additions & 13 deletions impl/internal/did/did.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ const (
DHTMethod did.Method = "dht"
JSONWebKeyType cryptosuite.LDKeyType = "JsonWebKey"

// Version corresponds to the version fo the specification https://did-dht.com/#dids-as-dns-records
Version int = 0

Discoverable TypeIndex = 0
Organization TypeIndex = 1
GovernmentOrganization TypeIndex = 2
Expand Down Expand Up @@ -144,15 +147,8 @@ func CreateDIDDHTDID(pubKey ed25519.PublicKey, opts CreateDIDDHTOpts) (*did.Docu
// mark as seen
seenIDs[vm.VerificationMethod.ID] = true

// update ID and controller in place, setting to thumbprint if none is provided

// e.g. nothing -> did:dht:123456789abcdefghi#<jwk key id>
if vm.VerificationMethod.ID == "" {
vm.VerificationMethod.ID = id + "#" + vm.VerificationMethod.PublicKeyJWK.KID
} else {
// make sure the verification method ID and KID match
vm.VerificationMethod.PublicKeyJWK.KID = vm.VerificationMethod.ID
}
// make sure the verification method JWK KID is set to its thumbprint
vm.VerificationMethod.ID = id + "#" + vm.VerificationMethod.PublicKeyJWK.KID

// e.g. #key-1 -> did:dht:123456789abcdefghi#key-1
if strings.HasPrefix(vm.VerificationMethod.ID, "#") {
Expand Down Expand Up @@ -242,6 +238,9 @@ func (d DHT) ToDNSPacket(doc did.Document, types []TypeIndex) (*dns.Msg, error)
var rootRecord []string
keyLookup := make(map[string]string)

// first append the version to the root record
rootRecord = append(rootRecord, fmt.Sprintf("v=%d", Version))

// build controller and aka records
if doc.Controller != nil {
var controllerTxt string
Expand Down Expand Up @@ -298,26 +297,38 @@ func (d DHT) ToDNSPacket(doc did.Document, types []TypeIndex) (*dns.Msg, error)
if err != nil {
return nil, err
}
pubKeyBytes, err := crypto.PubKeyToBytes(pubKey)

// as per the spec's guidance DNS representations use compressed keys, so we must marshal them as such
pubKeyBytes, err := crypto.PubKeyToBytes(pubKey, crypto.ECDSAMarshalCompressed)
if err != nil {
return nil, err
}
keyBase64Url := base64.RawURLEncoding.EncodeToString(pubKeyBytes)

keyBase64URL := base64.RawURLEncoding.EncodeToString(pubKeyBytes)
vmKeyFragment := vm.ID[strings.LastIndex(vm.ID, "#")+1:]
txtRecord := fmt.Sprintf("id=%s;t=%d;k=%s", vmKeyFragment, keyType, keyBase64URL)
// note the controller if it differs from the DID
if vm.Controller != doc.ID {
// handle the case where the controller of the identity key is not the DID itself
if vm.ID == doc.ID+"#0" && (vm.Controller != "" || vm.Controller != doc.ID) {
return nil, fmt.Errorf("controller of identity key must be the DID itself, instead it is: %s", vm.Controller)
}
txtRecord += fmt.Sprintf(";c=%s", vm.Controller)
}
keyRecord := dns.TXT{
Hdr: dns.RR_Header{
Name: fmt.Sprintf("_%s._did.", recordIdentifier),
Rrtype: dns.TypeTXT,
Class: dns.ClassINET,
Ttl: 7200,
},
Txt: []string{fmt.Sprintf("id=%s;t=%d;k=%s", vmKeyFragment, keyType, keyBase64Url)},
Txt: []string{txtRecord},
}

records = append(records, &keyRecord)
vmIDs = append(vmIDs, recordIdentifier)
}

// add verification methods to the root record
rootRecord = append(rootRecord, fmt.Sprintf("vm=%s", strings.Join(vmIDs, ",")))

Expand Down Expand Up @@ -482,13 +493,20 @@ func (d DHT) FromDNSPacket(msg *dns.Msg) (*did.Document, []TypeIndex, error) {
vmID := data["id"]
keyType := keyTypeLookUp(data["t"])
keyBase64URL := data["k"]
controller := data["c"]

// set the controller to the DID if it's not provided
if controller == "" {
controller = d.String()
}

// Convert keyBase64URL back to PublicKeyJWK
pubKeyBytes, err := base64.RawURLEncoding.DecodeString(keyBase64URL)
if err != nil {
return nil, nil, err
}
pubKey, err := crypto.BytesToPubKey(pubKeyBytes, keyType)
// as per the spec's guidance DNS representations use compressed keys, so we must unmarshall them as such
pubKey, err := crypto.BytesToPubKey(pubKeyBytes, keyType, crypto.ECDSAUnmarshalCompressed)
if err != nil {
return nil, nil, err
}
Expand All @@ -497,6 +515,20 @@ func (d DHT) FromDNSPacket(msg *dns.Msg) (*did.Document, []TypeIndex, error) {
return nil, nil, err
}

// make sure the controller of the identity key matches the DID
if vmID == "0" && controller != d.String() {
return nil, nil, fmt.Errorf("controller of identity key must be the DID itself, instead it is: %s", controller)
}

// if the verification method ID is not set, set it to the thumbprint
if vmID == "" {
vmID = pubKeyJWK.KID
}

if vmID != "0" && pubKeyJWK.KID != vmID {
return nil, nil, fmt.Errorf("verification method JWK KID must be set to its thumbprint")
}

vm := did.VerificationMethod{
ID: d.String() + "#" + vmID,
Type: JSONWebKeyType,
Expand Down Expand Up @@ -555,6 +587,7 @@ func (d DHT) FromDNSPacket(msg *dns.Msg) (*did.Document, []TypeIndex, error) {
rootData := strings.Join(record.Txt, ";")
rootItems := strings.Split(rootData, ";")

seenVersion := false
for _, item := range rootItems {
kv := strings.Split(item, "=")
if len(kv) != 2 {
Expand All @@ -565,6 +598,11 @@ func (d DHT) FromDNSPacket(msg *dns.Msg) (*did.Document, []TypeIndex, error) {
valueItems := strings.Split(values, ",")

switch key {
case "v":
if len(valueItems) != 1 || valueItems[0] != strconv.Itoa(Version) {
return nil, nil, fmt.Errorf("invalid version: %s", values)
}
seenVersion = true
case "auth":
for _, valueItem := range valueItems {
doc.Authentication = append(doc.Authentication, doc.ID+"#"+keyLookup[valueItem])
Expand All @@ -587,6 +625,9 @@ func (d DHT) FromDNSPacket(msg *dns.Msg) (*did.Document, []TypeIndex, error) {
}
}
}
if !seenVersion {
return nil, nil, fmt.Errorf("root record missing version identifier")
}
}
}
}
Expand Down
8 changes: 3 additions & 5 deletions impl/internal/did/did_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/TBD54566975/ssi-sdk/crypto/jwx"
"github.com/TBD54566975/ssi-sdk/cryptosuite"
"github.com/TBD54566975/ssi-sdk/did"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -221,7 +220,6 @@ func TestToDNSPacket(t *testing.T) {
}

func TestVectors(t *testing.T) {

type testVectorDNSRecord struct {
RecordType string `json:"type"`
TTL string `json:"ttl"`
Expand Down Expand Up @@ -347,7 +345,7 @@ func TestMisc(t *testing.T) {
var secpJWK jwx.PublicKeyJWK
retrieveTestVectorAs(t, vector2PublicKeyJWK2, &secpJWK)

t.Run("single aka", func(t *testing.T) {
t.Run("single aka field", func(t *testing.T) {
doc, err := CreateDIDDHTDID(pubKey.(ed25519.PublicKey), CreateDIDDHTOpts{
Controller: []string{"did:example:abcd"},
AlsoKnownAs: []string{"did:example:efgh"},
Expand Down Expand Up @@ -554,7 +552,7 @@ func TestCreationFailures(t *testing.T) {
{
VerificationMethod: did.VerificationMethod{
ID: secpJWK.KID,
Type: cryptosuite.LDKeyType("fake"),
Type: "fake",
PublicKeyJWK: &secpJWK,
},
Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation},
Expand Down Expand Up @@ -609,7 +607,7 @@ func TestCreationFailures(t *testing.T) {
Type: JSONWebKeyType,
PublicKeyJWK: &secpJWK,
},
Purposes: []did.PublicKeyPurpose{did.PublicKeyPurpose("fake purpose")},
Purposes: []did.PublicKeyPurpose{"fake purpose"},
},
},
Services: []did.Service{
Expand Down
2 changes: 1 addition & 1 deletion impl/internal/did/testdata/vector-2-dns-records.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"_k1._did.": {
"type": "TXT",
"ttl": "7200",
"rdata": "id=0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw;t=1;k=Atf6NCChxjWpnrfPt1WDVE4ipYVSvi4pXCq4SUjx0jT9"
"rdata": "t=1;k=Atf6NCChxjWpnrfPt1WDVE4ipYVSvi4pXCq4SUjx0jT9"
},
"_s0._did.": {
"type": "TXT",
Expand Down
Loading

0 comments on commit 5176956

Please sign in to comment.