Files
at-container-registry/pkg/hold/pds/did.go
T

136 lines
3.7 KiB
Go

package pds
import (
"encoding/json"
"fmt"
"net/url"
)
// DIDDocument represents a did:web document
type DIDDocument struct {
Context []string `json:"@context"`
ID string `json:"id"`
AlsoKnownAs []string `json:"alsoKnownAs,omitempty"`
VerificationMethod []VerificationMethod `json:"verificationMethod"`
Authentication []string `json:"authentication,omitempty"`
AssertionMethod []string `json:"assertionMethod,omitempty"`
Service []Service `json:"service,omitempty"`
}
// VerificationMethod represents a public key in a DID document
type VerificationMethod struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
PublicKeyMultibase string `json:"publicKeyMultibase"`
}
// Service represents a service endpoint in a DID document
type Service struct {
ID string `json:"id"`
Type string `json:"type"`
ServiceEndpoint string `json:"serviceEndpoint"`
}
// GenerateDIDDocument creates a DID document for a did:web identity
func (p *HoldPDS) GenerateDIDDocument(publicURL string) (*DIDDocument, error) {
// Parse URL to extract host and port
u, err := url.Parse(publicURL)
if err != nil {
return nil, fmt.Errorf("failed to parse public URL: %w", err)
}
hostname := u.Hostname()
port := u.Port()
// Build host string (include non-standard ports per did:web spec)
host := hostname
if port != "" && port != "80" && port != "443" {
host = fmt.Sprintf("%s:%s", hostname, port)
}
did := fmt.Sprintf("did:web:%s", host)
// Get public key in multibase format using indigo's crypto
pubKey, err := p.signingKey.PublicKey()
if err != nil {
return nil, fmt.Errorf("failed to get public key: %w", err)
}
publicKeyMultibase := pubKey.Multibase()
doc := &DIDDocument{
Context: []string{
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/multikey/v1",
"https://w3id.org/security/suites/secp256k1-2019/v1",
},
ID: did,
AlsoKnownAs: []string{
fmt.Sprintf("at://%s", host),
},
VerificationMethod: []VerificationMethod{
{
ID: fmt.Sprintf("%s#atproto", did),
Type: "Multikey",
Controller: did,
PublicKeyMultibase: publicKeyMultibase,
},
},
Authentication: []string{
fmt.Sprintf("%s#atproto", did),
},
Service: []Service{
{
ID: "#atproto_pds",
Type: "AtprotoPersonalDataServer",
ServiceEndpoint: publicURL,
},
{
ID: "#atcr_hold",
Type: "AtcrHoldService",
ServiceEndpoint: publicURL,
},
},
}
return doc, nil
}
// MarshalDIDDocument converts a DID document to JSON using the stored public URL
func (p *HoldPDS) MarshalDIDDocument() ([]byte, error) {
doc, err := p.GenerateDIDDocument(p.PublicURL)
if err != nil {
return nil, err
}
return json.MarshalIndent(doc, "", " ")
}
// GenerateDIDFromURL creates a did:web identifier from a public URL
// Example: "http://hold1.example.com:8080" -> "did:web:hold1.example.com:8080"
// Note: Per did:web spec, non-standard ports (not 80/443) are included in the DID
func GenerateDIDFromURL(publicURL string) string {
// Parse URL
u, err := url.Parse(publicURL)
if err != nil {
// Fallback: assume it's just a hostname
return fmt.Sprintf("did:web:%s", publicURL)
}
// Get hostname
hostname := u.Hostname()
if hostname == "" {
hostname = "localhost"
}
// Get port
port := u.Port()
// Include port in DID if it's non-standard (not 80 for http, not 443 for https)
if port != "" && port != "80" && port != "443" {
return fmt.Sprintf("did:web:%s:%s", hostname, port)
}
return fmt.Sprintf("did:web:%s", hostname)
}