Skip to content

Commit

Permalink
vp: some initial start and added debug support to almost all services
Browse files Browse the repository at this point in the history
  • Loading branch information
matskramer committed Jan 24, 2025
1 parent 5699879 commit afb5a01
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 1 deletion.
20 changes: 20 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ docker-build-verifier:
$(info Docker Building verifier with tag: $(VERSION))
docker build --build-arg SERVICE_NAME=verifier --tag $(DOCKER_TAG_VERIFIER) --file dockerfiles/worker .

docker-build-verifier-debug:
$(info Docker Building verifier with tag: $(VERSION))
docker build --build-arg SERVICE_NAME=verifier --tag $(DOCKER_TAG_VERIFIER) --file dockerfiles/worker_debug .

docker-build-registry:
$(info Docker Building registry with tag: $(VERSION))
docker build --build-arg SERVICE_NAME=registry --tag $(DOCKER_TAG_REGISTRY) --file dockerfiles/worker .
Expand All @@ -94,18 +98,34 @@ docker-build-persistent:
$(info Docker Building persistent with tag: $(VERSION))
docker build --build-arg SERVICE_NAME=persistent --tag $(DOCKER_TAG_PERSISTENT) --file dockerfiles/worker .

docker-build-persistent-debug:
$(info Docker Building persistent with tag: $(VERSION))
docker build --build-arg SERVICE_NAME=persistent --tag $(DOCKER_TAG_PERSISTENT) --file dockerfiles/worker_debug .

docker-build-mockas:
$(info Docker Building mockas with tag: $(VERSION))
docker build --build-arg SERVICE_NAME=mockas --tag $(DOCKER_TAG_MOCKAS) --file dockerfiles/worker .

docker-build-mockas-debug:
$(info Docker Building mockas with tag: $(VERSION))
docker build --build-arg SERVICE_NAME=mockas --tag $(DOCKER_TAG_MOCKAS) --file dockerfiles/worker_debug .

docker-build-apigw:
$(info Docker building apigw with tag: $(VERSION))
docker build --build-arg SERVICE_NAME=apigw --build-arg VERSION=$(VERSION) --tag $(DOCKER_TAG_APIGW) --file dockerfiles/worker .

docker-build-apigw-debug:
$(info Docker building apigw with tag: $(VERSION))
docker build --build-arg SERVICE_NAME=apigw --build-arg VERSION=$(VERSION) --tag $(DOCKER_TAG_APIGW) --file dockerfiles/worker_debug .

docker-build-issuer:
$(info Docker building issuer with tag: $(VERSION))
docker build --build-arg SERVICE_NAME=issuer --tag $(DOCKER_TAG_ISSUER) --file dockerfiles/worker .

docker-build-issuer-debug:
$(info Docker building issuer with tag: $(VERSION))
docker build --build-arg SERVICE_NAME=issuer --tag $(DOCKER_TAG_ISSUER) --file dockerfiles/worker_debug .

docker-build-ui:
$(info Docker building ui with tag: $(VERSION))
docker build --build-arg SERVICE_NAME=ui --tag $(DOCKER_TAG_UI) --file dockerfiles/web_worker .
Expand Down
50 changes: 50 additions & 0 deletions dockerfiles/worker_debug
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Compile
FROM docker.sunet.se/dc4eu/gobuild:latest AS builder

# Build Delve
RUN go install github.com/go-delve/delve/cmd/dlv@latest

COPY . .
ARG SERVICE_NAME

RUN make swagger
RUN make proto

RUN --mount=type=cache,target=/root/.cache/go-build GOOS=linux GOARCH=amd64 go build -v -o bin/vc_$SERVICE_NAME -ldflags \
"-X vc/pkg/model.BuildVariableGitCommit=$(git rev-list -1 HEAD) \
-X vc/pkg/model.BuildVariableGitBranch=$(git rev-parse --abbrev-ref HEAD) \
-X vc/pkg/model.BuildVariableTimestamp=$(date +'%F:T%TZ') \
-X vc/pkg/model.BuildVariableGoVersion=$(go version|awk '{print $3}') \
-X vc/pkg/model.BuildVariableGoArch=$(go version|awk '{print $4}') \
-X vc/pkg/model.BuildVersion=$(git tag |tail -1) \
--extldflags '-static'" ./cmd/$SERVICE_NAME/main.go

# Deploy
FROM debian:bookworm-slim

ARG SERVICE_NAME

WORKDIR /

RUN apt-get update && apt-get install -y curl procps iputils-ping less
RUN rm -rf /var/lib/apt/lists/*

COPY --from=builder /go/bin/dlv /
COPY --from=builder /go/src/app/bin/vc_${SERVICE_NAME} /vc_service
COPY --from=builder /go/src/app/docs /docs
COPY --from=builder /go/src/app/standards /standards
COPY --from=builder /go/src/app/users_paris.xlsx /users_paris.xlsx

EXPOSE 8080

HEALTHCHECK --interval=20s --timeout=10s CMD curl --connect-timeout 5 http://localhost:8080/health | grep -q STATUS_OK

# vars in CMD and ENTRYPOINT are evaluated at runtime, that's why we use a static name on the binary.
CMD [ "./vc_service" ]

EXPOSE 8080 40000

HEALTHCHECK --interval=20s --timeout=10s CMD curl --connect-timeout 5 http://localhost:8080/health | grep -q STATUS_OK

# vars in CMD and ENTRYPOINT are evaluated at runtime, that's why we use a static name on the binary.
CMD ["/dlv", "--listen=:40000", "--headless=true", "--api-version=2", "--accept-multiclient", "exec", "./vc_service"]
5 changes: 5 additions & 0 deletions internal/ui/apiv1/vc_base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func NewClient(serviceName string, baseUrl string, tracer *trace.Tracer, logger
serviceName: serviceName,
baseUrl: baseUrl,
httpClient: &http.Client{
//TODO(mk): set timeout in config
Timeout: 10 * time.Second,
},
logger: logger,
Expand All @@ -31,6 +32,7 @@ func NewClient(serviceName string, baseUrl string, tracer *trace.Tracer, logger
return client
}

// TODO(mk): DEPRECATED USE GENERIC POST FUNC
func (c *VCBaseClient) DoPostJSON(endpoint string, reqBody any) (*map[string]any, error) {
url := c.url(endpoint)

Expand Down Expand Up @@ -82,10 +84,13 @@ func DoPostJSONGeneric[T any](c *VCBaseClient, endpoint string, reqBody any) (*T

resp, err := c.httpClient.Do(req)
if err != nil {
//TODO(mk): also return resp.StatusCode if resp and code not nil
return nil, err
}
defer c.closeBody(resp)

//TODO(mk): also return resp.StatusCode in all returns below

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
</div>
<div class="control">
<input id="identity-schema-name"
class="input" type="text" placeholder="identity schema name" value="SE">
class="input" type="text" placeholder="identity schema name" value="DefaultSchema">
</div>
<div class="control">
<button id="create-mock-btn" onclick="createMock()" class="button" disabled>
Expand Down
171 changes: 171 additions & 0 deletions internal/verifier/apiv1/verifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package apiv1

import (
"encoding/base64"
"encoding/json"
"errors"
"strings"
)

// VPToken represents the structure for validating a Verifiable Presentation token.
type VPToken struct {
RawToken string // The raw input token
Header map[string]interface{} // Decoded JWT header
Payload map[string]interface{} // Decoded JWT payload
Signature string // Extracted JWT signature
//TODO(mk): gör en struct för nedan
DecodedCredentials []map[string]interface{} // Decoded Verifiable Credentials
//TODO(mk): gör en struct för nedan
DisclosedClaims []string // Claims disclosed by the Holder
//TODO(mk): gör en struct för nedan
ValidationResults map[string]bool // Validation results for different steps
}

// NewVPToken initializes a new VPToken instance from a raw token.
func NewVPToken(rawToken string) (*VPToken, error) {
if rawToken == "" {
return nil, errors.New("empty vp_token provided")
}

vp := &VPToken{
RawToken: rawToken,
DecodedCredentials: make([]map[string]interface{}, 0),
DisclosedClaims: make([]string, 0),
ValidationResults: make(map[string]bool),
}

return vp, nil
}

// Validate runs the full validation process including extract and decode.
func (vp *VPToken) Validate() error {

// 1. Extracts And decodes the VP token into its components
if err := vp.extractAndDecode(); err != nil {
return err
}

// 2. Verify Holder's Signature
// Verify the signature of the outer JWT using the Holder's public key.
if err := vp.validateHolderSignature(); err != nil {
return err
}

// 3. Validate Issuer's Signatures on Embedded VC's
// Extract and verify the signatures of all Verifiable Credentials using the Issuer's public key.
if err := vp.validateIssuerSignatures(); err != nil {
return err
}

// 4. Check Credential Validity for each VC
// Ensure that credentials are not expired, revoked, or issued by untrusted issuers.
if err := vp.validateCredentials(); err != nil {
return err
}

// 5. Verify Selective Disclosure Claims
// Validate disclosed claims against the hashed values in the original credential.
if err := vp.verifySelectiveDisclosure(); err != nil {
return err
}

// 6. Validate Holder Binding
// Ensure the Holder is correctly bound to the credential.
if err := vp.validateHolderBinding(); err != nil {
return err
}

// 7. Validate Presentation Requirements
// Ensure the VP matches the verifier's requirements.
return vp.validatePresentationRequirements()
}

// extractAndDecode extracts and decodes the VP token into its components: header, payload, and signature.
// Validates its basic structure to ensure it conforms to the JWT standard.
func (vp *VPToken) extractAndDecode() error {
tokenParts := strings.Split(vp.RawToken, ".")
if len(tokenParts) != 3 {
return errors.New("invalid token structure")
}

header, payload, signature := tokenParts[0], tokenParts[1], tokenParts[2]

headerDecoded, err := decodeBase64URL(header)
if err != nil {
return err
}

payloadDecoded, err := decodeBase64URL(payload)
if err != nil {
return err
}

headerMap := make(map[string]interface{})
payloadMap := make(map[string]interface{})

if err := json.Unmarshal([]byte(headerDecoded), &headerMap); err != nil {
return err
}

if err := json.Unmarshal([]byte(payloadDecoded), &payloadMap); err != nil {
return err
}

vp.Header = headerMap
vp.Payload = payloadMap
vp.Signature = signature
return nil
}

// validateHolderSignature verifies the signature of the outer JWT.
func (vp *VPToken) validateHolderSignature() error {
// Placeholder for holder signature validation logic.
// Typically involves extracting JWK from payload and verifying signature.
vp.ValidationResults["HolderSignature"] = true
return nil
}

// validateIssuerSignatures validates signatures of all embedded Verifiable Credentials.
func (vp *VPToken) validateIssuerSignatures() error {
// Placeholder for issuer signature validation logic.
// Extract VCs and validate their signatures using Issuer public keys.
vp.ValidationResults["IssuerSignatures"] = true
return nil
}

// validateCredentials checks the validity of the credentials.
func (vp *VPToken) validateCredentials() error {
// Placeholder for checking credential validity (e.g., expiration, revocation).
vp.ValidationResults["Credentials"] = true
return nil
}

// verifySelectiveDisclosure validates selective disclosure claims.
func (vp *VPToken) verifySelectiveDisclosure() error {
// Placeholder for validating _sd claims in the payload.
vp.ValidationResults["SelectiveDisclosure"] = true
return nil
}

// validateHolderBinding ensures the Holder is bound to the credential.
func (vp *VPToken) validateHolderBinding() error {
// Placeholder for validating Holder binding logic.
vp.ValidationResults["HolderBinding"] = true
return nil
}

// validatePresentationRequirements ensures the VP matches the verifier's requirements.
func (vp *VPToken) validatePresentationRequirements() error {
// Placeholder for matching claims with verifier requirements.
vp.ValidationResults["PresentationRequirements"] = true
return nil
}

// decodeBase64URL decodes a Base64URL-encoded string.
func (vp *VPToken) decodeBase64URL(input string) ([]byte, error) {
decoded, err := base64.RawURLEncoding.DecodeString(input)
if err != nil {
return nil, err
}
return decoded, nil
}
77 changes: 77 additions & 0 deletions internal/verifier/apiv1/verifier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package apiv1

import "testing"

func TestVPToken_validateHolderSignature(t *testing.T) {
type fields struct {
RawToken string
Header map[string]interface{}
Payload map[string]interface{}
Signature string
DecodedCredentials []map[string]interface{}
DisclosedClaims []string
ValidationResults map[string]bool
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{
name: "Valid Holder Signature",
fields: fields{
RawToken: `eyJhbGciOiAiRVMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAiaG9sZGVyIiwgImF1ZCI6ICJ2ZXJpZmllciIsICJpYXQiOiAxNjgwODk3ODU1fQ.VALID_SIGNATURE`,
Header: map[string]interface{}{
"alg": "ES256",
"typ": "JWT",
},
Payload: map[string]interface{}{
"sub": "holder",
"aud": "verifier",
"iat": 1680897855,
},
Signature: "VALID_SIGNATURE",
ValidationResults: map[string]bool{
"HolderSignature": false,
},
},
wantErr: false,
},
{
name: "Invalid Holder Signature",
fields: fields{
RawToken: `eyJhbGciOiAiRVMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAiaG9sZGVyIiwgImF1ZCI6ICJ2ZXJpZmllciIsICJpYXQiOiAxNjgwODk3ODU1fQ.INVALID_SIGNATURE`,
Header: map[string]interface{}{
"alg": "ES256",
"typ": "JWT",
},
Payload: map[string]interface{}{
"sub": "holder",
"aud": "verifier",
"iat": 1680897855,
},
Signature: "INVALID_SIGNATURE",
ValidationResults: map[string]bool{
"HolderSignature": false,
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
vp := &VPToken{
RawToken: tt.fields.RawToken,
Header: tt.fields.Header,
Payload: tt.fields.Payload,
Signature: tt.fields.Signature,
DecodedCredentials: tt.fields.DecodedCredentials,
DisclosedClaims: tt.fields.DisclosedClaims,
ValidationResults: tt.fields.ValidationResults,
}
if err := vp.validateHolderSignature(); (err != nil) != tt.wantErr {
t.Errorf("validateHolderSignature() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

0 comments on commit afb5a01

Please sign in to comment.