├── .gitignore ├── .gitlab-ci.yml ├── .taplo.toml ├── Cargo.toml ├── LICENSE ├── NOTICE ├── README.md ├── deny.toml ├── docs ├── assets │ ├── logo_dark_mode_Open_One_Core.svg │ ├── logo_dark_mode_Procivis.svg │ ├── logo_light_mode_Open_One_Core.svg │ ├── logo_light_mode_Procivis.svg │ └── procivis_favicon.svg ├── cache.md ├── capabilities.md ├── docker │ ├── Dockerfile │ ├── README.md │ └── nginx.conf └── one-core-docs-chart │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── ingress.yaml │ ├── service.yaml │ ├── serviceaccount.yaml │ └── tests │ │ └── test-connection.yaml │ ├── values.yaml │ └── values │ └── docs.procivis-one.yaml ├── examples ├── credential_example │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── did_resolution_example │ ├── Cargo.toml │ └── src │ │ └── main.rs └── signature_example │ ├── Cargo.toml │ └── src │ └── main.rs ├── one-crypto ├── Cargo.toml └── src │ ├── imp │ ├── encryption.rs │ ├── hasher │ │ ├── mod.rs │ │ └── sha256.rs │ ├── mod.rs │ ├── password.rs │ ├── signer │ │ ├── bbs.rs │ │ ├── crydi3.rs │ │ ├── eddsa.rs │ │ ├── error.rs │ │ ├── es256.rs │ │ └── mod.rs │ ├── test.rs │ └── utilities.rs │ └── lib.rs ├── one-open-core ├── Cargo.toml └── src │ ├── config.rs │ ├── lib.rs │ ├── model.rs │ └── service │ ├── credential_service.rs │ ├── did_service.rs │ ├── error.rs │ ├── mod.rs │ └── signature_service.rs ├── one-providers ├── Cargo.toml └── src │ ├── caching_loader.rs │ ├── common_dto │ └── mod.rs │ ├── common_mappers.rs │ ├── common_models │ ├── claim.rs │ ├── claim_schema.rs │ ├── credential.rs │ ├── credential_schema.rs │ ├── did.rs │ ├── interaction.rs │ ├── key.rs │ ├── macros.rs │ ├── mod.rs │ ├── organisation.rs │ ├── proof.rs │ └── proof_schema.rs │ ├── credential_formatter │ ├── error.rs │ ├── imp │ │ ├── common.rs │ │ ├── json_ld │ │ │ ├── context │ │ │ │ ├── caching_loader │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── test.rs │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ ├── model.rs │ │ │ ├── test.rs │ │ │ └── test_utilities.rs │ │ ├── json_ld_bbsplus │ │ │ ├── base_proof.rs │ │ │ ├── derived_proof.rs │ │ │ ├── mapper.rs │ │ │ ├── mod.rs │ │ │ ├── model.rs │ │ │ ├── remove_undisclosed_keys.rs │ │ │ ├── test.rs │ │ │ └── verify_proof.rs │ │ ├── jwt │ │ │ ├── mapper.rs │ │ │ ├── mod.rs │ │ │ ├── model.rs │ │ │ └── test.rs │ │ ├── jwt_formatter │ │ │ ├── mapper.rs │ │ │ ├── mod.rs │ │ │ ├── model.rs │ │ │ └── test.rs │ │ ├── mod.rs │ │ ├── provider.rs │ │ └── sdjwt_formatter │ │ │ ├── disclosures.rs │ │ │ ├── mapper.rs │ │ │ ├── mod.rs │ │ │ ├── model.rs │ │ │ ├── test.rs │ │ │ └── verifier.rs │ ├── mod.rs │ ├── model.rs │ └── provider.rs │ ├── did │ ├── error.rs │ ├── imp │ │ ├── common.rs │ │ ├── dto.rs │ │ ├── jwk │ │ │ ├── jwk_helpers.rs │ │ │ ├── mod.rs │ │ │ └── test.rs │ │ ├── key │ │ │ ├── mod.rs │ │ │ └── test.rs │ │ ├── key_helpers.rs │ │ ├── mapper.rs │ │ ├── mod.rs │ │ ├── provider.rs │ │ ├── resolver.rs │ │ ├── universal │ │ │ ├── mod.rs │ │ │ └── test.rs │ │ └── web │ │ │ ├── mod.rs │ │ │ └── test.rs │ ├── keys.rs │ ├── mod.rs │ ├── model.rs │ └── provider.rs │ ├── exchange_protocol │ ├── imp │ │ ├── mod.rs │ │ └── provider.rs │ ├── mod.rs │ ├── openid4vc │ │ ├── error.rs │ │ ├── imp │ │ │ ├── mappers.rs │ │ │ ├── mdoc.rs │ │ │ ├── mod.rs │ │ │ ├── test.rs │ │ │ └── utils.rs │ │ ├── mapper.rs │ │ ├── mod.rs │ │ ├── model.rs │ │ ├── proof_formatter.rs │ │ ├── service.rs │ │ ├── test.rs │ │ └── validator.rs │ └── provider.rs │ ├── http_client │ ├── imp │ │ ├── mod.rs │ │ └── reqwest_client.rs │ └── mod.rs │ ├── key_algorithm │ ├── error.rs │ ├── imp │ │ ├── bbs │ │ │ ├── mod.rs │ │ │ └── test.rs │ │ ├── eddsa │ │ │ ├── mod.rs │ │ │ └── test.rs │ │ ├── es256 │ │ │ ├── mod.rs │ │ │ └── test.rs │ │ ├── mod.rs │ │ └── provider.rs │ ├── mod.rs │ ├── model.rs │ └── provider.rs │ ├── key_storage │ ├── error.rs │ ├── imp │ │ ├── azure_vault │ │ │ ├── dto.rs │ │ │ ├── mapper.rs │ │ │ ├── mod.rs │ │ │ └── test.rs │ │ ├── internal │ │ │ ├── mod.rs │ │ │ └── test.rs │ │ ├── mod.rs │ │ └── provider.rs │ ├── mod.rs │ ├── model.rs │ └── provider.rs │ ├── lib.rs │ ├── remote_entity_storage │ ├── in_memory.rs │ └── mod.rs │ ├── revocation │ ├── error.rs │ ├── imp │ │ ├── bitstring_status_list │ │ │ ├── jwt_formatter.rs │ │ │ ├── mod.rs │ │ │ ├── model.rs │ │ │ ├── resolver.rs │ │ │ └── util.rs │ │ ├── lvvc │ │ │ ├── dto.rs │ │ │ ├── mapper.rs │ │ │ ├── mod.rs │ │ │ └── test.rs │ │ ├── mod.rs │ │ └── provider.rs │ ├── mod.rs │ ├── model.rs │ └── provider.rs │ └── util │ ├── key_verification.rs │ └── mod.rs └── sonar-project.properties /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | 4 | # IDE settings 5 | .idea 6 | .vscode 7 | .DS_Store 8 | 9 | # Reports file 10 | report.json 11 | coverage.xml 12 | cobertura.xml 13 | lcov.info 14 | clippy.json 15 | deny.json 16 | sonar-issues.json 17 | 18 | # Sonar scanner 19 | .sonar 20 | .scannerwork 21 | sonar.yml 22 | -------------------------------------------------------------------------------- /.taplo.toml: -------------------------------------------------------------------------------- 1 | include = ["**/*.toml"] 2 | 3 | [formatting] 4 | align_entries = true 5 | column_width = 120 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace.package] 2 | version = "0.1.0" 3 | edition = "2021" 4 | license = "Apache-2.0" 5 | 6 | [workspace] 7 | resolver = "2" 8 | default-members = ["one-providers", "one-open-core", "one-crypto"] 9 | members = [ 10 | "examples/did_resolution_example", 11 | "examples/credential_example", 12 | "examples/signature_example", 13 | "one-providers", 14 | "one-crypto", 15 | "one-open-core", 16 | ] 17 | 18 | [workspace.dependencies] 19 | reqwest = { version = "0.12", default-features = false, features = ["native-tls-vendored", "json"] } 20 | thiserror = { version = "1.0" } 21 | time = { version = "0.3", features = ["formatting", "macros", "parsing"] } 22 | serde = { version = "1.0" } 23 | serde_json = { version = "1.0" } 24 | zeroize = { version = "1.8" } 25 | 26 | [patch.crates-io] 27 | json-ld = { git = "https://github.com/strozynskiw/json-ld-rustls", rev = "cfab4c341b4c2514aa3c5799a0494c5b399692a6" } 28 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Procivis One Open Core 2 | Copyright 2024 Procivis AG 3 | 4 | This product includes software developed at 5 | Procivis AG (https://www.procivis.ch/). 6 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # Cargo deny options 2 | [graph] 3 | targets = [ 4 | # Linux 5 | { triple = "x86_64-unknown-linux-gnu" }, 6 | # Windows 7 | { triple = "x86_64-pc-windows-gnu" }, 8 | # Mobile (Android) 9 | { triple = "i686-linux-android" }, 10 | { triple = "armv7-linux-androideabi" }, 11 | { triple = "aarch64-linux-android" }, 12 | # Mobile (iOS) 13 | { triple = "aarch64-apple-ios" }, 14 | ] 15 | 16 | all-features = false 17 | no-default-features = false 18 | 19 | 20 | # This section is considered when running `cargo deny check advisories` 21 | # More documentation for the advisories section can be found here: 22 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 23 | [advisories] 24 | version = 2 25 | db-path = "~/.cargo/advisory-db" 26 | db-urls = ["https://github.com/rustsec/advisory-db"] 27 | yanked = "deny" 28 | 29 | ignore = [ 30 | { id = "RUSTSEC-2023-0055", reason = "no safe upgrade available, hard to replace dependency of JSON-LD" }, 31 | { id = "RUSTSEC-2023-0071", reason = "no safe upgrade available, can be avoided by not using RSA in mysql server" }, 32 | ] 33 | 34 | # This section is considered when running `cargo deny check licenses` 35 | # More documentation for the licenses section can be found here: 36 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 37 | [licenses] 38 | version = 2 39 | allow = [ 40 | "Apache-2.0", 41 | "BSD-2-Clause", 42 | "BSD-3-Clause", 43 | "CC0-1.0", 44 | "CECILL-B", 45 | "ISC", 46 | "MIT", 47 | "MIT-0", 48 | "Unicode-DFS-2016", 49 | "Zlib", 50 | "BSL-1.0", 51 | ] 52 | confidence-threshold = 0.8 53 | 54 | exceptions = [ 55 | # Allow MPL-2.0 for libraries we do not modify 56 | { allow = ["MPL-2.0"], name = "webpki-roots", version = "*" }, 57 | { allow = ["MPL-2.0"], name = "resiter", version = "*" }, 58 | { allow = ["MPL-2.0"], name = "uniffi", version = "*" }, 59 | { allow = ["MPL-2.0"], name = "uniffi_bindgen", version = "*" }, 60 | { allow = ["MPL-2.0"], name = "uniffi_build", version = "*" }, 61 | { allow = ["MPL-2.0"], name = "uniffi_checksum_derive", version = "*" }, 62 | { allow = ["MPL-2.0"], name = "uniffi_core", version = "*" }, 63 | { allow = ["MPL-2.0"], name = "uniffi_macros", version = "*" }, 64 | { allow = ["MPL-2.0"], name = "uniffi_meta", version = "*" }, 65 | { allow = ["MPL-2.0"], name = "uniffi_testing", version = "*" }, 66 | { allow = ["MPL-2.0"], name = "uniffi_udl", version = "*" }, 67 | 68 | # Allow OpenSSL for ring 69 | { allow = ["OpenSSL"], name = "ring", version = "*" }, 70 | ] 71 | 72 | [licenses.private] 73 | ignore = true 74 | 75 | # Clarify ring crate license (LICENSE file not properly detected) 76 | [[licenses.clarify]] 77 | name = "ring" 78 | expression = "MIT AND ISC AND OpenSSL" 79 | license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] 80 | 81 | # This section is considered when running `cargo deny check bans`. 82 | # More documentation about the 'bans' section can be found here: 83 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 84 | [bans] 85 | multiple-versions = "allow" 86 | wildcards = "warn" 87 | workspace-default-features = "allow" 88 | external-default-features = "allow" 89 | allow = [] 90 | deny = [] 91 | 92 | 93 | # This section is considered when running `cargo deny check sources`. 94 | # More documentation about the 'sources' section can be found here: 95 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 96 | [sources] 97 | unknown-registry = "deny" 98 | unknown-git = "deny" 99 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 100 | private = ["https://gitlab.procivis.ch"] 101 | -------------------------------------------------------------------------------- /docs/cache.md: -------------------------------------------------------------------------------- 1 | # Caching 2 | 3 | Caching is helpful for mobile devices with intermittent internet 4 | connectivity, as well as for system optimization with entities not 5 | expected to change (i.e. JSON-LD contexts). See the [caching][cac] 6 | docs for more information on cached entities. 7 | 8 | [cac]: https://docs.procivis.ch/api/caching -------------------------------------------------------------------------------- /docs/capabilities.md: -------------------------------------------------------------------------------- 1 | # Capabilities 2 | 3 | Capabilities are reports that reflect certain properties of instances. 4 | 5 | These reports inform of what is and is not possible with different kinds of entities, 6 | setting the scope of operations and compatibilities. 7 | -------------------------------------------------------------------------------- /docs/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:mainline-alpine 2 | 3 | COPY docs/docker/nginx.conf /etc/nginx/conf.d/default.conf 4 | COPY target/doc /usr/share/nginx/html 5 | 6 | # Make nginx user as owner 7 | RUN chown -R nginx:nginx /etc/nginx/ /var/log/nginx/ /var/cache/nginx /var/run/ /run/ /usr/share/nginx 8 | 9 | USER nginx 10 | -------------------------------------------------------------------------------- /docs/docker/README.md: -------------------------------------------------------------------------------- 1 | # Docker Rust documentation 2 | 3 | * Build image 4 | ```shell 5 | docker build -t open-core-docs -f docs/docker/Dockerfile . 6 | ``` 7 | 8 | * Run image 9 | ```shell 10 | docker run -p 80:8000 -it --rm open-core-docs 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | include /etc/nginx/mime.types; 3 | server_tokens off; 4 | gzip on; 5 | gzip_disable "msie6"; 6 | gzip_vary on; 7 | gzip_proxied any; 8 | gzip_comp_level 6; 9 | gzip_buffers 16 8k; 10 | gzip_http_version 1.1; 11 | gzip_min_length 512; 12 | gzip_types 13 | application/javascript 14 | application/x-javascript 15 | application/json 16 | application/xml 17 | font/eot 18 | font/otf 19 | font/ttf 20 | image/svg+xml 21 | text/css 22 | text/javascript 23 | text/plain 24 | text/xml 25 | text/html; 26 | 27 | listen 8000 default_server; 28 | server_name _; 29 | root /usr/share/nginx/html; 30 | 31 | add_header X-Frame-Options SAMEORIGIN; 32 | add_header X-Content-Type-Options nosniff; 33 | add_header X-XSS-Protection "1; mode=block"; 34 | add_header Referrer-Policy strict-origin-when-cross-origin; 35 | 36 | add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: blob: *; style-src 'self' 'unsafe-inline'"; 37 | 38 | location /one_open_core/ { 39 | index index.html; 40 | add_header Expires "Thu, 01 Jan 1970 00:00:01 GMT"; 41 | add_header Cache-Control "no-store, max-age=0"; 42 | try_files $uri $uri/ =404; 43 | } 44 | 45 | location = / { 46 | return 301 $scheme://$http_host/one_open_core/index.html; 47 | } 48 | 49 | error_page 404 /404.html; 50 | location = /404.html { 51 | internal; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /docs/one-core-docs-chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /docs/one-core-docs-chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: open-core-chart 3 | description: Open Core chart 4 | type: application 5 | version: 0.1.0 6 | appVersion: "1.16.0" 7 | -------------------------------------------------------------------------------- /docs/one-core-docs-chart/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "one-core-docs-chart.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "one-core-docs-chart.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "one-core-docs-chart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "one-core-docs-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /docs/one-core-docs-chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "one-core-docs-chart.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "one-core-docs-chart.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "one-core-docs-chart.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "one-core-docs-chart.labels" -}} 37 | helm.sh/chart: {{ include "one-core-docs-chart.chart" . }} 38 | {{ include "one-core-docs-chart.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "one-core-docs-chart.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "one-core-docs-chart.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "one-core-docs-chart.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "one-core-docs-chart.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /docs/one-core-docs-chart/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "one-core-docs-chart.fullname" . }} 5 | labels: 6 | {{- include "one-core-docs-chart.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "one-core-docs-chart.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "one-core-docs-chart.labels" . | nindent 8 }} 22 | {{- with .Values.podLabels }} 23 | {{- toYaml . | nindent 8 }} 24 | {{- end }} 25 | spec: 26 | {{- with .Values.imagePullSecrets }} 27 | imagePullSecrets: 28 | {{- toYaml . | nindent 8 }} 29 | {{- end }} 30 | serviceAccountName: {{ include "one-core-docs-chart.serviceAccountName" . }} 31 | securityContext: 32 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 33 | containers: 34 | - name: {{ .Chart.Name }} 35 | securityContext: 36 | {{- toYaml .Values.securityContext | nindent 12 }} 37 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 38 | imagePullPolicy: {{ .Values.image.pullPolicy }} 39 | ports: 40 | - name: http 41 | containerPort: {{ .Values.service.targetPort }} 42 | protocol: TCP 43 | livenessProbe: 44 | {{- toYaml .Values.livenessProbe | nindent 12 }} 45 | readinessProbe: 46 | {{- toYaml .Values.readinessProbe | nindent 12 }} 47 | resources: 48 | {{- toYaml .Values.resources | nindent 12 }} 49 | {{- with .Values.volumeMounts }} 50 | volumeMounts: 51 | {{- toYaml . | nindent 12 }} 52 | {{- end }} 53 | {{- with .Values.volumes }} 54 | volumes: 55 | {{- toYaml . | nindent 8 }} 56 | {{- end }} 57 | {{- with .Values.nodeSelector }} 58 | nodeSelector: 59 | {{- toYaml . | nindent 8 }} 60 | {{- end }} 61 | {{- with .Values.affinity }} 62 | affinity: 63 | {{- toYaml . | nindent 8 }} 64 | {{- end }} 65 | {{- with .Values.tolerations }} 66 | tolerations: 67 | {{- toYaml . | nindent 8 }} 68 | {{- end }} 69 | -------------------------------------------------------------------------------- /docs/one-core-docs-chart/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "one-core-docs-chart.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "one-core-docs-chart.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /docs/one-core-docs-chart/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "one-core-docs-chart.fullname" . }} 5 | labels: 6 | {{- include "one-core-docs-chart.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "one-core-docs-chart.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /docs/one-core-docs-chart/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "one-core-docs-chart.serviceAccountName" . }} 6 | labels: 7 | {{- include "one-core-docs-chart.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | automountServiceAccountToken: {{ .Values.serviceAccount.automount }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /docs/one-core-docs-chart/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "one-core-docs-chart.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "one-core-docs-chart.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "one-core-docs-chart.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /docs/one-core-docs-chart/values.yaml: -------------------------------------------------------------------------------- 1 | replicaCount: 1 2 | 3 | image: 4 | repository: registry.gitlab.procivis.ch/procivis/one/one-open-core/docs 5 | pullPolicy: Always 6 | tag: "latest" 7 | 8 | imagePullSecrets: 9 | - name: gitlab-credentials-demo-cluster 10 | 11 | nameOverride: "" 12 | fullnameOverride: "" 13 | 14 | serviceAccount: 15 | create: false 16 | automount: true 17 | annotations: {} 18 | name: "" 19 | 20 | podAnnotations: {} 21 | podLabels: {} 22 | 23 | podSecurityContext: {} 24 | # fsGroup: 2000 25 | 26 | securityContext: 27 | runAsUser: 101 28 | runAsGroup: 101 29 | runAsNonRoot: true 30 | allowPrivilegeEscalation: false 31 | 32 | service: 33 | type: ClusterIP 34 | port: 80 35 | targetPort: 8000 36 | 37 | ingress: 38 | enabled: true 39 | className: "" 40 | annotations: 41 | traefik.ingress.kubernetes.io/router.tls.certresolver: le 42 | traefik.ingress.kubernetes.io/router.tls: "true" 43 | hosts: [] 44 | tls: [] 45 | 46 | resources: 47 | limits: 48 | cpu: 15m 49 | memory: 10Mi 50 | 51 | livenessProbe: 52 | httpGet: 53 | path: / 54 | port: http 55 | readinessProbe: 56 | httpGet: 57 | path: / 58 | port: http 59 | 60 | autoscaling: 61 | enabled: false 62 | minReplicas: 1 63 | maxReplicas: 100 64 | targetCPUUtilizationPercentage: 80 65 | # targetMemoryUtilizationPercentage: 80 66 | 67 | # Additional volumes on the output Deployment definition. 68 | volumes: [] 69 | # - name: foo 70 | # secret: 71 | # secretName: mysecret 72 | # optional: false 73 | 74 | # Additional volumeMounts on the output Deployment definition. 75 | volumeMounts: [] 76 | # - name: foo 77 | # mountPath: "/etc/foo" 78 | # readOnly: true 79 | 80 | nodeSelector: {} 81 | 82 | tolerations: [] 83 | 84 | affinity: {} 85 | -------------------------------------------------------------------------------- /docs/one-core-docs-chart/values/docs.procivis-one.yaml: -------------------------------------------------------------------------------- 1 | ingress: 2 | hosts: 3 | - host: docs.procivis-one.com 4 | paths: 5 | - path: / 6 | pathType: ImplementationSpecific 7 | -------------------------------------------------------------------------------- /examples/credential_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "credential-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | 7 | [dependencies] 8 | one-open-core = { path = "../../one-open-core" } 9 | one-providers = { path = "../../one-providers" } 10 | reqwest = { workspace = true } 11 | tokio = { version = "1.38.1", features = ["full"] } 12 | time = { version = "0.3", features = ["formatting", "macros", "parsing"] } 13 | uuid = { version = "1.10.0", features = ["v4"] } 14 | -------------------------------------------------------------------------------- /examples/did_resolution_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "did-resolution-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | 7 | [dependencies] 8 | one-open-core = { path = "../../one-open-core" } 9 | one-providers = { path = "../../one-providers" } 10 | reqwest = { workspace = true } 11 | tokio = { version = "1.38.1", features = ["full"] } 12 | -------------------------------------------------------------------------------- /examples/did_resolution_example/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use one_open_core::OneOpenCore; 4 | use one_providers::common_models::did::DidValue; 5 | use one_providers::did::{ 6 | error::DidMethodError, 7 | imp::universal::{Params as UniversalDidMethodParams, UniversalDidMethod}, 8 | DidMethod, 9 | }; 10 | use one_providers::http_client::imp::reqwest_client::ReqwestClient; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<(), DidMethodError> { 14 | let client = Arc::new(ReqwestClient::default()); 15 | 16 | let core = OneOpenCore::new(None, client.clone()).unwrap(); 17 | let did_service = core.did_service; 18 | 19 | let example_did_values_implemented = vec![ 20 | // did:key 21 | DidValue::from("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string()), 22 | // did:jwk 23 | DidValue::from("did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9".to_string()), 24 | // did:web 25 | DidValue::from("did:web:core.trial.procivis-one.com:ssi:did-web:v1:bcbfef61-cfd4-4d31-ae46-82f0a121463e".to_string()), 26 | ]; 27 | let example_did_value_unimplemented = DidValue::from("did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJzaWdfNzJiZDE2ZDYiLCJwdWJsaWNLZXlKd2siOnsiY3J2Ijoic2VjcDI1NmsxIiwia3R5IjoiRUMiLCJ4IjoiS2JfMnVOR3Nyd1VOdkh2YUNOckRGdW14VXlQTWZZd3kxNEpZZmphQUhmayIsInkiOiJhSFNDZDVEOFh0RUxvSXBpN1A5eDV1cXBpeEVxNmJDenQ0QldvUVk1UUFRIn0sInB1cnBvc2VzIjpbImF1dGhlbnRpY2F0aW9uIiwiYXNzZXJ0aW9uTWV0aG9kIl0sInR5cGUiOiJFY2RzYVNlY3AyNTZrMVZlcmlmaWNhdGlvbktleTIwMTkifV0sInNlcnZpY2VzIjpbeyJpZCI6ImxpbmtlZGRvbWFpbnMiLCJzZXJ2aWNlRW5kcG9pbnQiOnsib3JpZ2lucyI6WyJodHRwczovL3d3dy52Y3NhdG9zaGkuY29tLyJdfSwidHlwZSI6IkxpbmtlZERvbWFpbnMifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUR4SWxJak9xQk5NTGZjdzZndWpHNEdFVDM3UjBIRWM2Z20xclNZTjlMOF9RIn0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlBLXV3TWo3RVFheURmWTRJS3pfSE9LdmJZQ05td19Tb1lhUmhOcWhFSWhudyIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQ0czQ1M5RFJpeU1JRVoxRl9sSjZnRVRMZWVHREwzZnpuQUViMVRGdFZXNEEifX0".to_string()); 28 | 29 | // 30 | // Resolving DIDs using the core DID service 31 | // 32 | 33 | for did in example_did_values_implemented.into_iter() { 34 | // resolve DID using service without allowing fallback provider 35 | let result = did_service.resolve_did(&did, false).await; 36 | assert!(result.is_ok(), "expected to resolve DID {}", did); 37 | println!("Resolved {} into:\n{:#?}\n", did, result); 38 | } 39 | 40 | // resolving an unimplemented DID method with fallback provider disabled will fail 41 | let result = did_service 42 | .resolve_did(&example_did_value_unimplemented, false) 43 | .await; 44 | assert!(result.is_err(), "expected not to resolve DID"); 45 | 46 | // when enabling the fallback to an universal resolver, DID resolution should succeed however 47 | let result = did_service 48 | .resolve_did(&example_did_value_unimplemented, true) 49 | .await; 50 | assert!(result.is_ok(), "expected to resolve DID"); 51 | 52 | // 53 | // Resolving DIDs using the DID method impolementation directly 54 | // 55 | 56 | let example_did_key = 57 | DidValue::from("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string()); 58 | let did_key = did_service.get_did_method("KEY").unwrap(); 59 | let result = did_key.resolve(&example_did_key).await; 60 | assert!(result.is_ok(), "expected to resolve DID"); 61 | 62 | // 63 | // Resolving DIDs without initializing core - if desired, the DID methods 64 | // can also be instantiated and used directly 65 | // 66 | 67 | let universal_resolver = UniversalDidMethod::new( 68 | UniversalDidMethodParams { 69 | resolver_url: "https://dev.uniresolver.io".to_string(), 70 | }, 71 | client, 72 | ); 73 | let result = universal_resolver.resolve(&example_did_key).await; 74 | assert!(result.is_ok(), "expected to resolve DID"); 75 | 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /examples/signature_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "signature-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | 7 | [dependencies] 8 | one-open-core = { path = "../../one-open-core" } 9 | one-providers = { path = "../../one-providers" } 10 | hex-literal = "0.4.1" 11 | reqwest = { workspace = true } 12 | zeroize = "1.8.1" 13 | -------------------------------------------------------------------------------- /examples/signature_example/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use hex_literal::hex; 4 | use one_open_core::{model::KeyAlgorithmType, OneOpenCore}; 5 | use one_providers::http_client::imp::reqwest_client::ReqwestClient; 6 | use zeroize::Zeroizing; 7 | 8 | fn main() { 9 | let core = OneOpenCore::new(None, Arc::new(ReqwestClient::default())).unwrap(); 10 | 11 | let key_pair = core 12 | .signature_service 13 | .get_key_pair(&KeyAlgorithmType::Es256) 14 | .expect("Key pair creation failed"); 15 | 16 | let bytes = hex!("d14ccebdae5153c916d82168c1e2a9e39ab056cfd197c64242151773ce1c61f8"); 17 | 18 | let signature = core 19 | .signature_service 20 | .sign( 21 | &KeyAlgorithmType::Es256, 22 | &key_pair.public, 23 | Zeroizing::new(key_pair.private), 24 | &bytes, 25 | ) 26 | .expect("Signing failed"); 27 | 28 | let verification = core.signature_service.verify( 29 | &KeyAlgorithmType::Es256, 30 | &key_pair.public, 31 | &signature, 32 | &bytes, 33 | ); 34 | 35 | match verification { 36 | Ok(_) => println!("Successfully verified"), 37 | Err(_) => println!("Signature is incorrect"), 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /one-crypto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "one-crypto" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | license = "Apache-2.0" 7 | 8 | [dependencies] 9 | thiserror = { workspace = true } 10 | serde = { workspace = true } 11 | serde_json = { workspace = true } 12 | ed25519-compact = { version = "2.1" } 13 | p256 = { version = "0.13", features = ["jwk"] } 14 | pairing_crypto = { version = "0.4", git = "https://github.com/Iskander508/pairing_crypto", rev = "517a424a989b57b987aacb3642bd7cd2c60b94d1" } 15 | mockall = { version = "0.13", optional = true } 16 | hmac = { version = "0.12" } 17 | rand = { version = "0.8" } 18 | rand_chacha = { version = "0.3" } 19 | sha2 = { version = "0.10" } 20 | pbkdf2 = { version = "0.12", features = ["simple"] } 21 | chacha20poly1305 = { version = "0.10" } 22 | pqc_dilithium = { version = "0.2.0", git = "https://github.com/ihor-rud/dilithium", rev = "e02a683" } 23 | ct-codecs = { version = "1.1.1" } 24 | 25 | 26 | [dev-dependencies] 27 | mockall = { version = "0.13" } 28 | 29 | [features] 30 | mock = ["mockall"] 31 | -------------------------------------------------------------------------------- /one-crypto/src/imp/encryption.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{Cursor, Read, Seek, SeekFrom, Write}, 4 | }; 5 | 6 | use chacha20poly1305::{ 7 | aead::{Aead, Nonce}, 8 | AeadCore, ChaCha20Poly1305, KeyInit, 9 | }; 10 | use rand::rngs::OsRng; 11 | 12 | use super::password::{derive_key, derive_key_with_salt}; 13 | 14 | #[derive(Debug, thiserror::Error)] 15 | pub enum EncryptionError { 16 | #[error("file system error: {0}")] 17 | FsError(#[from] std::io::Error), 18 | #[error("crypto error: {0}")] 19 | Crypto(String), 20 | } 21 | 22 | pub fn encrypt_file( 23 | password: &str, 24 | output_path: &str, 25 | mut input_file: impl Read, 26 | ) -> Result<(), EncryptionError> { 27 | let key = derive_key(password); 28 | 29 | let cipher = ChaCha20Poly1305::new_from_slice(&key.key) 30 | .map_err(|err| EncryptionError::Crypto(err.to_string()))?; 31 | 32 | let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng); 33 | 34 | let mut content = vec![]; 35 | input_file.read_to_end(&mut content)?; 36 | 37 | let ciphertext = cipher 38 | .encrypt(&nonce, content.as_slice()) 39 | .map_err(|err| EncryptionError::Crypto(err.to_string()))?; 40 | 41 | let mut file = File::create(output_path)?; 42 | 43 | file.write_all(&key.salt)?; 44 | file.write_all(&nonce)?; 45 | file.write_all(&ciphertext)?; 46 | 47 | Ok(()) 48 | } 49 | 50 | pub fn decrypt_file( 51 | password: &str, 52 | mut encrypted_file: impl Read, 53 | output_file: &mut T, 54 | ) -> Result<(), EncryptionError> { 55 | let mut key_salt = [0; 32]; 56 | encrypted_file.read_exact(&mut key_salt)?; 57 | 58 | let key = derive_key_with_salt(password, &key_salt); 59 | 60 | let cipher = ChaCha20Poly1305::new_from_slice(&key) 61 | .map_err(|err| EncryptionError::Crypto(err.to_string()))?; 62 | 63 | let mut nonce = Nonce::::default(); 64 | encrypted_file.read_exact(&mut nonce)?; 65 | 66 | let mut content = vec![]; 67 | encrypted_file.read_to_end(&mut content)?; 68 | 69 | let decrypted = cipher 70 | .decrypt(&nonce, content.as_slice()) 71 | .map_err(|err| EncryptionError::Crypto(err.to_string()))?; 72 | 73 | std::io::copy(&mut Cursor::new(decrypted), output_file)?; 74 | output_file.seek(SeekFrom::Start(0))?; 75 | 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /one-crypto/src/imp/hasher/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod sha256; 2 | -------------------------------------------------------------------------------- /one-crypto/src/imp/hasher/sha256.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use ct_codecs::{Base64UrlSafeNoPadding, Encoder}; 4 | use sha2::{Digest, Sha256}; 5 | 6 | use crate::{Hasher, HasherError}; 7 | 8 | pub struct SHA256 {} 9 | 10 | impl SHA256 { 11 | pub fn hash_reader(reader: &mut impl Read) -> Result, HasherError> { 12 | let mut hasher = Sha256::new(); 13 | std::io::copy(reader, &mut hasher).map_err(|_| HasherError::CouldNotHash)?; 14 | Ok(hasher.finalize().to_vec()) 15 | } 16 | } 17 | 18 | impl Hasher for SHA256 { 19 | fn hash_base64(&self, input: &[u8]) -> Result { 20 | let mut hasher = Sha256::new(); 21 | hasher.update(input); 22 | let result = hasher.finalize(); 23 | 24 | Base64UrlSafeNoPadding::encode_to_string(result).map_err(|_| HasherError::CouldNotHash) 25 | } 26 | 27 | fn hash(&self, input: &[u8]) -> Result, HasherError> { 28 | let mut hasher = Sha256::new(); 29 | hasher.update(input); 30 | Ok(hasher.finalize().to_vec()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /one-crypto/src/imp/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the Crypto provider, for hashing and signing. 2 | 3 | use std::{collections::HashMap, sync::Arc}; 4 | 5 | use hmac::Hmac; 6 | use sha2::Sha256; 7 | 8 | use super::{CryptoProvider, CryptoProviderError, Hasher, Signer}; 9 | 10 | pub mod encryption; 11 | pub mod hasher; 12 | pub mod signer; 13 | pub mod utilities; 14 | 15 | mod password; 16 | 17 | type HmacSha256 = Hmac; 18 | 19 | #[cfg(test)] 20 | mod test; 21 | 22 | #[derive(Clone)] 23 | pub struct CryptoProviderImpl { 24 | hashers: HashMap>, 25 | signers: HashMap>, 26 | } 27 | 28 | impl CryptoProviderImpl { 29 | pub fn new( 30 | hashers: HashMap>, 31 | signers: HashMap>, 32 | ) -> Self { 33 | Self { hashers, signers } 34 | } 35 | } 36 | 37 | impl CryptoProvider for CryptoProviderImpl { 38 | fn get_hasher(&self, hasher: &str) -> Result, CryptoProviderError> { 39 | Ok(self 40 | .hashers 41 | .get(hasher) 42 | .ok_or(CryptoProviderError::MissingHasher(hasher.to_owned()))? 43 | .clone()) 44 | } 45 | 46 | fn get_signer(&self, signer: &str) -> Result, CryptoProviderError> { 47 | Ok(self 48 | .signers 49 | .get(signer) 50 | .ok_or(CryptoProviderError::MissingHasher(signer.to_owned()))? 51 | .clone()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /one-crypto/src/imp/password.rs: -------------------------------------------------------------------------------- 1 | use pbkdf2::{password_hash::rand_core::OsRng, pbkdf2_hmac}; 2 | use rand::Rng; 3 | use sha2::Sha256; 4 | 5 | pub struct Key { 6 | pub key: [u8; 32], 7 | pub salt: [u8; 32], 8 | } 9 | 10 | pub fn derive_key(password: &str) -> Key { 11 | let mut key = [0u8; 32]; 12 | let salt: [u8; 32] = OsRng.gen(); 13 | 14 | pbkdf2_hmac::(password.as_bytes(), &salt, 600_000, &mut key); 15 | Key { key, salt } 16 | } 17 | 18 | pub fn derive_key_with_salt(password: &str, salt: &[u8; 32]) -> [u8; 32] { 19 | let mut key = [0u8; 32]; 20 | pbkdf2_hmac::(password.as_bytes(), salt, 600_000, &mut key); 21 | key 22 | } 23 | -------------------------------------------------------------------------------- /one-crypto/src/imp/signer/crydi3.rs: -------------------------------------------------------------------------------- 1 | use pqc_dilithium::*; 2 | 3 | use crate::{Signer, SignerError}; 4 | pub struct CRYDI3Signer {} 5 | 6 | impl Signer for CRYDI3Signer { 7 | fn sign( 8 | &self, 9 | input: &[u8], 10 | public_key: &[u8], 11 | private_key: &[u8], 12 | ) -> Result, SignerError> { 13 | let key_pair = Keypair::new(public_key.to_vec(), private_key.to_vec()) 14 | .map_err(|_| SignerError::CouldNotExtractKeyPair)?; 15 | 16 | Ok(key_pair.sign(input).to_vec()) 17 | } 18 | 19 | fn verify(&self, input: &[u8], signature: &[u8], public_key: &[u8]) -> Result<(), SignerError> { 20 | verify(signature, input, public_key).map_err(|_| SignerError::InvalidSignature) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /one-crypto/src/imp/signer/eddsa.rs: -------------------------------------------------------------------------------- 1 | use crate::{Signer, SignerError}; 2 | 3 | pub struct EDDSASigner {} 4 | 5 | impl Signer for EDDSASigner { 6 | fn sign( 7 | &self, 8 | input: &[u8], 9 | public_key: &[u8], 10 | private_key: &[u8], 11 | ) -> Result, SignerError> { 12 | let ed25519_kp = ed25519_compact::KeyPair::from_slice(private_key) 13 | .map_err(|_| SignerError::CouldNotExtractKeyPair)?; 14 | 15 | if ed25519_kp.pk.as_slice() != public_key { 16 | return Err(SignerError::CouldNotExtractKeyPair); 17 | } 18 | 19 | Ok(ed25519_kp.sk.sign(input, None).to_vec()) 20 | } 21 | 22 | fn verify(&self, input: &[u8], signature: &[u8], public_key: &[u8]) -> Result<(), SignerError> { 23 | let ed25519_pk = ed25519_compact::PublicKey::from_slice(public_key) 24 | .map_err(|_| SignerError::CouldNotExtractKeyPair)?; 25 | 26 | let ed25519_signature = ed25519_compact::Signature::from_slice(signature) 27 | .map_err(|e| SignerError::CouldNotVerify(e.to_string()))?; 28 | 29 | ed25519_pk 30 | .verify(input, &ed25519_signature) 31 | .map_err(|_| SignerError::InvalidSignature)?; 32 | Ok(()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /one-crypto/src/imp/signer/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::error::CryptoProviderError; 4 | 5 | #[derive(Debug, PartialEq, Eq, Error)] 6 | pub enum SignerError { 7 | #[error("Crypto provider error: `{0}`")] 8 | CryptoError(#[from] CryptoProviderError), 9 | #[error("Could not sign: `{0}`")] 10 | CouldNotSign(String), 11 | #[error("Could not extract keypair")] 12 | CouldNotExtractKeyPair, 13 | #[error("Could not extract public key: `{0}`")] 14 | CouldNotExtractPublicKey(String), 15 | #[error("Could not verify: `{0}`")] 16 | CouldNotVerify(String), 17 | #[error("Invalid signature")] 18 | InvalidSignature, 19 | #[error("Missing algorithm `{0}`")] 20 | MissingAlgorithm(String), 21 | #[error("Missing key")] 22 | MissingKey, 23 | } 24 | -------------------------------------------------------------------------------- /one-crypto/src/imp/signer/es256.rs: -------------------------------------------------------------------------------- 1 | use p256::{ 2 | ecdsa::{ 3 | signature::{Signer as _, Verifier as _}, 4 | Signature, SigningKey, VerifyingKey, 5 | }, 6 | EncodedPoint, 7 | }; 8 | use rand::thread_rng; 9 | 10 | use crate::{Signer, SignerError}; 11 | 12 | pub struct ES256Signer {} 13 | 14 | impl ES256Signer { 15 | fn from_bytes(public_key: &[u8]) -> Result { 16 | let point = EncodedPoint::from_bytes(public_key).map_err(|err| { 17 | SignerError::CouldNotExtractPublicKey(format!( 18 | "couldn't initialize verifying key: {err}" 19 | )) 20 | })?; 21 | VerifyingKey::from_encoded_point(&point).map_err(|err| { 22 | SignerError::CouldNotExtractPublicKey(format!( 23 | "couldn't initialize verifying key: {err}" 24 | )) 25 | }) 26 | } 27 | 28 | pub fn to_bytes(public_key: &[u8]) -> Result, SignerError> { 29 | let vk = Self::from_bytes(public_key)?; 30 | Ok(vk.to_encoded_point(true).to_bytes().into()) 31 | } 32 | 33 | pub fn random() -> (Vec, Vec) { 34 | let sk = SigningKey::random(&mut thread_rng()); 35 | let pk = VerifyingKey::from(&sk); 36 | ( 37 | sk.to_bytes().to_vec(), 38 | pk.to_encoded_point(true).to_bytes().into(), 39 | ) 40 | } 41 | } 42 | 43 | impl Signer for ES256Signer { 44 | fn sign( 45 | &self, 46 | input: &[u8], 47 | public_key: &[u8], 48 | private_key: &[u8], 49 | ) -> Result, SignerError> { 50 | let sk = SigningKey::from_bytes(private_key.into()).map_err(|err| { 51 | SignerError::CouldNotExtractPublicKey(format!("couldn't initialize secret key: {err}")) 52 | })?; 53 | let pk = VerifyingKey::from(&sk); 54 | 55 | if pk.to_encoded_point(true).as_bytes() != public_key { 56 | return Err(SignerError::CouldNotExtractKeyPair); 57 | } 58 | let signature: Signature = sk.sign(input); 59 | Ok(signature.to_vec()) 60 | } 61 | 62 | fn verify(&self, input: &[u8], signature: &[u8], public_key: &[u8]) -> Result<(), SignerError> { 63 | let vk = Self::from_bytes(public_key)?; 64 | 65 | let signature = 66 | Signature::try_from(signature).map_err(|_| SignerError::InvalidSignature)?; 67 | 68 | vk.verify(input, &signature) 69 | .map_err(|err| SignerError::CouldNotVerify(format!("couldn't verify: {err}"))) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /one-crypto/src/imp/signer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bbs; 2 | pub mod crydi3; 3 | pub mod eddsa; 4 | pub mod es256; 5 | -------------------------------------------------------------------------------- /one-crypto/src/imp/test.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn test_base64_salt() { 3 | let result = super::utilities::generate_salt_base64_16(); 4 | 5 | // set for Base64 url no padding 6 | let allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; 7 | 8 | assert!(result.chars().all(|c| allowed_characters.contains(c))); 9 | } 10 | 11 | #[test] 12 | fn test_alphanumeric() { 13 | let expected_len = 254; 14 | 15 | let result = super::utilities::generate_alphanumeric(expected_len); 16 | 17 | // alphanumeric 18 | let allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 19 | 20 | assert!(result.chars().all(|c| allowed_characters.contains(c))); 21 | assert_eq!(result.len(), expected_len); 22 | } 23 | -------------------------------------------------------------------------------- /one-crypto/src/imp/utilities.rs: -------------------------------------------------------------------------------- 1 | use ct_codecs::{Base64UrlSafeNoPadding, Decoder, Encoder}; 2 | use hmac::Mac; 3 | use rand::{ 4 | distributions::{Alphanumeric, DistString}, 5 | rngs::ThreadRng, 6 | Rng, RngCore, SeedableRng, 7 | }; 8 | use rand_chacha::ChaCha20Rng; 9 | use serde::{Deserialize, Deserializer}; 10 | 11 | use super::HmacSha256; 12 | 13 | pub fn generate_salt_base64_16() -> String { 14 | let seed = generate_random_seed_16(); 15 | 16 | //This operation should be safe as we control the input. 17 | Base64UrlSafeNoPadding::encode_to_string(seed).unwrap_or_default() 18 | } 19 | 20 | pub fn generate_alphanumeric(length: usize) -> String { 21 | Alphanumeric.sample_string(&mut rand::thread_rng(), length) 22 | } 23 | 24 | pub fn create_hmac(key: &[u8], message: &[u8]) -> Option> { 25 | let mut mac = HmacSha256::new_from_slice(key).ok()?; 26 | mac.update(message); 27 | let result = mac.finalize(); 28 | Some(result.into_bytes().to_vec()) 29 | } 30 | 31 | pub fn generate_random_seed_32() -> [u8; 32] { 32 | let mut rng = ChaCha20Rng::from_entropy(); 33 | let mut seed = [0u8; 32]; 34 | rng.fill_bytes(&mut seed); 35 | seed 36 | } 37 | 38 | pub fn generate_random_seed_16() -> [u8; 16] { 39 | let mut rng = ChaCha20Rng::from_entropy(); 40 | let mut seed = [0u8; 16]; 41 | rng.fill_bytes(&mut seed); 42 | seed 43 | } 44 | 45 | // TODO Try to use ChaCha20Rng here 46 | pub fn get_rng() -> ThreadRng { 47 | rand::thread_rng() 48 | } 49 | 50 | pub fn generate_nonce() -> String { 51 | let mut rng = ChaCha20Rng::from_entropy(); 52 | rng.gen::<[u8; 32]>().map(char::from).into_iter().collect() 53 | } 54 | 55 | pub fn deserialize_base64<'de, D>(deserializer: D) -> Result, D::Error> 56 | where 57 | D: Deserializer<'de>, 58 | { 59 | let s = String::deserialize(deserializer)?; 60 | 61 | Base64UrlSafeNoPadding::decode_to_vec(s, None).map_err(serde::de::Error::custom) 62 | } 63 | -------------------------------------------------------------------------------- /one-crypto/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Signing and verifying of raw bytes. 2 | //! 3 | //! This module provides utilities for hashing and direct signatures and verifications 4 | //! of raw bytes. It has been separated into its own directory to enable future 5 | //! certification, e.g. in the [NIST Cryptographic Module Validation Program (CMVP)][cmvp]. 6 | //! 7 | //! [cmvp]: https://csrc.nist.gov/Projects/Cryptographic-Module-Validation-Program 8 | 9 | use std::sync::Arc; 10 | 11 | use thiserror::Error; 12 | 13 | pub mod imp; 14 | 15 | #[derive(Debug, PartialEq, Eq, Error)] 16 | pub enum CryptoProviderError { 17 | #[error("Missing hasher: `{0}`")] 18 | MissingHasher(String), 19 | #[error("Missing signer: `{0}`")] 20 | MissingSigner(String), 21 | } 22 | 23 | #[derive(Debug, PartialEq, Eq, Error)] 24 | pub enum HasherError { 25 | #[error("Could not hash")] 26 | CouldNotHash, 27 | #[error("Crypto provider error: `{0}`")] 28 | CryptoError(#[from] CryptoProviderError), 29 | } 30 | 31 | #[derive(Debug, PartialEq, Eq, Error)] 32 | pub enum SignerError { 33 | #[error("Crypto provider error: `{0}`")] 34 | CryptoError(#[from] CryptoProviderError), 35 | #[error("Could not sign: `{0}`")] 36 | CouldNotSign(String), 37 | #[error("Could not extract keypair")] 38 | CouldNotExtractKeyPair, 39 | #[error("Could not extract public key: `{0}`")] 40 | CouldNotExtractPublicKey(String), 41 | #[error("Could not verify: `{0}`")] 42 | CouldNotVerify(String), 43 | #[error("Invalid signature")] 44 | InvalidSignature, 45 | #[error("Missing algorithm `{0}`")] 46 | MissingAlgorithm(String), 47 | #[error("Missing key")] 48 | MissingKey, 49 | } 50 | 51 | /// Provides hashing. 52 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 53 | pub trait Hasher: Send + Sync { 54 | /// Hasher. 55 | fn hash_base64(&self, input: &[u8]) -> Result; 56 | 57 | /// Hasher. 58 | fn hash(&self, input: &[u8]) -> Result, HasherError>; 59 | } 60 | 61 | /// Generally the [key storage][ks] module or [credential formatter][cf] module is used for safe signing, 62 | /// but direct signing and verification is possible here. 63 | /// 64 | /// [ks]: ../../one_providers/key_storage/index.html 65 | /// [cf]: ../../one_providers/credential_formatter/index.html 66 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 67 | pub trait Signer: Send + Sync { 68 | /// Direct signing. 69 | fn sign( 70 | &self, 71 | input: &[u8], 72 | public_key: &[u8], 73 | private_key: &[u8], 74 | ) -> Result, SignerError>; 75 | 76 | /// Direct signature verification. 77 | fn verify(&self, input: &[u8], signature: &[u8], public_key: &[u8]) -> Result<(), SignerError>; 78 | } 79 | 80 | /// Return hasher or signer instances. Not supported for all key storage types. 81 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 82 | pub trait CryptoProvider: Send + Sync { 83 | /// Returns hasher instance. 84 | fn get_hasher(&self, hasher: &str) -> Result, CryptoProviderError>; 85 | 86 | /// Returns signer instance. 87 | fn get_signer(&self, signer: &str) -> Result, CryptoProviderError>; 88 | } 89 | -------------------------------------------------------------------------------- /one-open-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "one-open-core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | license = "Apache-2.0" 7 | 8 | [lib] 9 | doctest = false 10 | 11 | [dependencies] 12 | one-providers = { version = "0.1.0", path = "../one-providers" } 13 | one-crypto = { version = "0.1.0", path = "../one-crypto" } 14 | reqwest = { workspace = true } 15 | thiserror = { workspace = true } 16 | zeroize = { workspace = true } 17 | strum = { version = "0.26" } 18 | strum_macros = { version = "0.26" } 19 | time = { workspace = true } 20 | 21 | [package.metadata.cargo-machete] 22 | ignored = ["strum"] 23 | -------------------------------------------------------------------------------- /one-open-core/src/config.rs: -------------------------------------------------------------------------------- 1 | pub struct OneCoreConfig { 2 | pub caching_config: CachingConfig, 3 | pub did_method_config: DidMethodConfig, 4 | pub formatter_config: FormatterConfig, 5 | } 6 | 7 | pub struct CachingLoaderConfig { 8 | pub cache_size: usize, 9 | pub cache_refresh_timeout: time::Duration, 10 | pub refresh_after: time::Duration, 11 | } 12 | 13 | pub struct CachingConfig { 14 | pub did: CachingLoaderConfig, 15 | pub json_ld_context: CachingLoaderConfig, 16 | } 17 | 18 | pub struct DidMethodConfig { 19 | pub universal_resolver_url: String, 20 | pub key_count_range: (usize, usize), 21 | } 22 | 23 | pub struct FormatterConfig { 24 | pub leeway: u64, 25 | pub embed_layout_properties: bool, 26 | } 27 | 28 | impl Default for OneCoreConfig { 29 | fn default() -> Self { 30 | Self { 31 | caching_config: CachingConfig { 32 | did: CachingLoaderConfig { 33 | cache_size: 100, 34 | cache_refresh_timeout: time::Duration::days(1), 35 | refresh_after: time::Duration::minutes(5), 36 | }, 37 | json_ld_context: CachingLoaderConfig { 38 | cache_size: 100, 39 | cache_refresh_timeout: time::Duration::days(10), 40 | refresh_after: time::Duration::days(1), 41 | }, 42 | }, 43 | did_method_config: DidMethodConfig { 44 | universal_resolver_url: "https://dev.uniresolver.io".to_string(), 45 | key_count_range: (1, 1), 46 | }, 47 | formatter_config: FormatterConfig { 48 | leeway: 60, 49 | embed_layout_properties: false, 50 | }, 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /one-open-core/src/model.rs: -------------------------------------------------------------------------------- 1 | use strum_macros::{Display, EnumString}; 2 | 3 | #[derive(Debug, Copy, Clone, Display, EnumString, PartialEq, Eq, PartialOrd, Ord)] 4 | pub enum KeyAlgorithmType { 5 | #[strum(serialize = "EDDSA")] 6 | Eddsa, 7 | #[strum(serialize = "BBS_PLUS")] 8 | BbsPlus, 9 | #[strum(serialize = "ES256")] 10 | Es256, 11 | } 12 | 13 | #[derive(Debug, Copy, Clone, Display, EnumString, PartialEq, Eq, PartialOrd, Ord)] 14 | pub enum CredentialFormat { 15 | #[strum(serialize = "JWT")] 16 | Jwt, 17 | #[strum(serialize = "SDJWT")] 18 | SdJwt, 19 | #[strum(serialize = "JSON_LD_BBSPLUS")] 20 | JsonLdBbsPlus, 21 | } 22 | 23 | #[derive(Debug, Copy, Clone, Display, EnumString, PartialEq, Eq, PartialOrd, Ord)] 24 | pub enum StorageType { 25 | #[strum(serialize = "INTERNAL")] 26 | Internal, 27 | } 28 | 29 | #[derive(Debug, Copy, Clone, Display, EnumString, PartialEq, Eq, PartialOrd, Ord)] 30 | pub enum DidMethodType { 31 | #[strum(serialize = "JWK")] 32 | Jwk, 33 | #[strum(serialize = "KEY")] 34 | Key, 35 | #[strum(serialize = "WEB")] 36 | Web, 37 | } 38 | -------------------------------------------------------------------------------- /one-open-core/src/service/credential_service.rs: -------------------------------------------------------------------------------- 1 | //! A service for issuing credentials, creating and signing presentations as a holder, 2 | //! and parsing and verifying credentials as a verifier. 3 | //! 4 | //! See the **/examples** directory in the [repository][repo] for an 5 | //! example implementation. 6 | //! 7 | //! [repo]: https://github.com/procivis/one-open-core 8 | 9 | use std::sync::Arc; 10 | 11 | use one_providers::{ 12 | common_models::{ 13 | did::{DidValue, KeyRole}, 14 | key::OpenKey, 15 | }, 16 | credential_formatter::{ 17 | model::{CredentialData, CredentialPresentation, DetailCredential}, 18 | provider::CredentialFormatterProvider, 19 | }, 20 | did::provider::DidMethodProvider, 21 | key_algorithm::provider::KeyAlgorithmProvider, 22 | key_storage::provider::KeyProvider, 23 | }; 24 | 25 | use one_providers::util::key_verification::KeyVerification; 26 | 27 | use crate::{ 28 | model::{CredentialFormat, KeyAlgorithmType}, 29 | service::error::CredentialServiceError, 30 | }; 31 | 32 | pub struct CredentialService { 33 | key_storage_provider: Arc, 34 | credential_formatter_provider: Arc, 35 | key_algorithm_provider: Arc, 36 | did_method_provider: Arc, 37 | } 38 | 39 | impl CredentialService { 40 | pub fn new( 41 | key_storage_provider: Arc, 42 | credential_formatter_provider: Arc, 43 | key_algorithm_provider: Arc, 44 | did_method_provider: Arc, 45 | ) -> Self { 46 | Self { 47 | key_storage_provider, 48 | credential_formatter_provider, 49 | key_algorithm_provider, 50 | did_method_provider, 51 | } 52 | } 53 | 54 | pub async fn format_credential( 55 | &self, 56 | credential_data: CredentialData, 57 | format: CredentialFormat, 58 | algorithm: KeyAlgorithmType, 59 | holder_did: DidValue, 60 | issuer_key: OpenKey, 61 | ) -> Result { 62 | let auth_fn = self 63 | .key_storage_provider 64 | .get_signature_provider(&issuer_key, None)?; 65 | 66 | let token = self 67 | .credential_formatter_provider 68 | .get_formatter(&format.to_string()) 69 | .ok_or(CredentialServiceError::MissingFormat(format.to_string()))? 70 | .format_credentials( 71 | credential_data, 72 | &holder_did, 73 | &algorithm.to_string(), 74 | vec![], 75 | vec![], 76 | auth_fn, 77 | None, 78 | None, 79 | ) 80 | .await?; 81 | 82 | Ok(token) 83 | } 84 | 85 | pub async fn format_credential_presentation( 86 | &self, 87 | format: CredentialFormat, 88 | credential: CredentialPresentation, 89 | ) -> Result { 90 | let token = self 91 | .credential_formatter_provider 92 | .get_formatter(&format.to_string()) 93 | .ok_or(CredentialServiceError::MissingFormat(format.to_string()))? 94 | .format_credential_presentation(credential) 95 | .await?; 96 | 97 | Ok(token) 98 | } 99 | 100 | pub async fn extract_credential( 101 | &self, 102 | format: CredentialFormat, 103 | credential: &str, 104 | ) -> Result { 105 | let key_verification = Box::new(KeyVerification { 106 | key_algorithm_provider: self.key_algorithm_provider.clone(), 107 | did_method_provider: self.did_method_provider.clone(), 108 | key_role: KeyRole::AssertionMethod, 109 | }); 110 | 111 | let details = self 112 | .credential_formatter_provider 113 | .get_formatter(&format.to_string()) 114 | .ok_or(CredentialServiceError::MissingFormat(format.to_string()))? 115 | .extract_credentials(credential, key_verification) 116 | .await?; 117 | 118 | Ok(details) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /one-open-core/src/service/did_service.rs: -------------------------------------------------------------------------------- 1 | //! A service for creating DIDs and resolving DIDs to their DID document. 2 | //! 3 | //! See the **/examples** directory in the [repository][repo] for an 4 | //! example implementation. 5 | //! 6 | //! [repo]: https://github.com/procivis/one-open-core 7 | 8 | use std::sync::Arc; 9 | 10 | use one_providers::{ 11 | common_models::did::DidValue, 12 | did::{ 13 | error::DidMethodProviderError, model::DidDocument, provider::DidMethodProvider, DidMethod, 14 | }, 15 | }; 16 | 17 | #[derive(Clone)] 18 | pub struct DidService { 19 | pub did_provider: Arc, 20 | pub fallback_method: Option>, 21 | } 22 | 23 | impl DidService { 24 | pub fn new( 25 | did_provider: Arc, 26 | fallback_method: Option>, 27 | ) -> Self { 28 | Self { 29 | did_provider, 30 | fallback_method, 31 | } 32 | } 33 | 34 | pub fn get_did_method(&self, did_method_id: &str) -> Option> { 35 | self.did_provider.get_did_method(did_method_id) 36 | } 37 | 38 | pub async fn resolve_did( 39 | &self, 40 | did: &DidValue, 41 | allow_fallback_resolver: bool, 42 | ) -> Result { 43 | let resolution = self.did_provider.resolve(did).await; 44 | 45 | if !allow_fallback_resolver { 46 | return resolution; 47 | } 48 | 49 | match resolution { 50 | Ok(x) => Ok(x), 51 | Err(DidMethodProviderError::MissingProvider(x)) => { 52 | if let Some(fallback) = &self.fallback_method { 53 | Ok(fallback.resolve(did).await?) 54 | } else { 55 | Err(DidMethodProviderError::MissingProvider(x)) 56 | } 57 | } 58 | Err(x) => Err(x), 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /one-open-core/src/service/error.rs: -------------------------------------------------------------------------------- 1 | //! Enumerates errors for services. 2 | 3 | use one_crypto::{CryptoProviderError, SignerError}; 4 | use one_providers::{ 5 | credential_formatter::error::FormatterError, key_storage::error::KeyStorageProviderError, 6 | }; 7 | use thiserror::Error; 8 | 9 | #[derive(Debug, Error)] 10 | pub enum SignatureServiceError { 11 | #[error("Missing algorithm `{0}`")] 12 | MissingAlgorithm(String), 13 | #[error("Could not sign")] 14 | CouldNotSign, 15 | #[error("Could not verify")] 16 | CouldNotVerify, 17 | #[error("Crypto provider error: `{0}`")] 18 | CryptoProviderError(#[from] CryptoProviderError), 19 | #[error("Signer error: `{0}`")] 20 | SignerError(#[from] SignerError), 21 | } 22 | 23 | #[derive(Debug, Error)] 24 | pub enum CredentialServiceError { 25 | #[error("Missing algorithm `{0}`")] 26 | MissingFormat(String), 27 | #[error(transparent)] 28 | KeyStorageProviderError(#[from] KeyStorageProviderError), 29 | #[error(transparent)] 30 | FormatterError(#[from] FormatterError), 31 | } 32 | -------------------------------------------------------------------------------- /one-open-core/src/service/mod.rs: -------------------------------------------------------------------------------- 1 | //! Services for orchestrating providers. 2 | 3 | pub mod credential_service; 4 | pub mod did_service; 5 | pub mod error; 6 | pub mod signature_service; 7 | -------------------------------------------------------------------------------- /one-open-core/src/service/signature_service.rs: -------------------------------------------------------------------------------- 1 | //! A service for signing and verifying bytes. 2 | //! 3 | //! See the **/examples** directory in the [repository][repo] for an 4 | //! example implementation. 5 | //! 6 | //! [repo]: https://github.com/procivis/one-open-core 7 | 8 | use std::sync::Arc; 9 | 10 | use one_crypto::CryptoProvider; 11 | 12 | use one_providers::key_algorithm::{model::GeneratedKey, provider::KeyAlgorithmProvider}; 13 | use zeroize::Zeroizing; 14 | 15 | use super::error::SignatureServiceError; 16 | use crate::model::KeyAlgorithmType; 17 | 18 | pub struct SignatureService { 19 | pub crypto_provider: Arc, 20 | pub key_algorithm_provider: Arc, 21 | } 22 | 23 | impl SignatureService { 24 | pub fn new( 25 | crypto_provider: Arc, 26 | key_algorithm_provider: Arc, 27 | ) -> Self { 28 | Self { 29 | crypto_provider, 30 | key_algorithm_provider, 31 | } 32 | } 33 | 34 | pub fn get_key_pair( 35 | &self, 36 | algorithm: &KeyAlgorithmType, 37 | ) -> Result { 38 | let selected_algorithm = self 39 | .key_algorithm_provider 40 | .get_key_algorithm(&algorithm.to_string()) 41 | .ok_or(SignatureServiceError::MissingAlgorithm( 42 | algorithm.to_string(), 43 | ))?; 44 | 45 | Ok(selected_algorithm.generate_key_pair()) 46 | } 47 | 48 | pub fn sign( 49 | &self, 50 | algorithm: &KeyAlgorithmType, 51 | public_key: &[u8], 52 | private_key: Zeroizing>, 53 | data: &[u8], 54 | ) -> Result, SignatureServiceError> { 55 | let algorithm = self 56 | .key_algorithm_provider 57 | .get_key_algorithm(&algorithm.to_string()) 58 | .ok_or(SignatureServiceError::MissingAlgorithm( 59 | algorithm.to_string(), 60 | ))?; 61 | 62 | let signer_algorithm_id = algorithm.get_signer_algorithm_id(); 63 | 64 | let signer = self.crypto_provider.get_signer(&signer_algorithm_id)?; 65 | 66 | Ok(signer.sign(data, public_key, private_key.as_slice())?) 67 | } 68 | 69 | pub fn verify( 70 | &self, 71 | algorithm: &KeyAlgorithmType, 72 | public_key: &[u8], 73 | signature: &[u8], 74 | data: &[u8], 75 | ) -> Result<(), SignatureServiceError> { 76 | let algorithm = self 77 | .key_algorithm_provider 78 | .get_key_algorithm(&algorithm.to_string()) 79 | .ok_or(SignatureServiceError::MissingAlgorithm( 80 | algorithm.to_string(), 81 | ))?; 82 | 83 | let signer_algorithm_id = algorithm.get_signer_algorithm_id(); 84 | 85 | let signer = self.crypto_provider.get_signer(&signer_algorithm_id)?; 86 | 87 | Ok(signer.verify(data, signature, public_key)?) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /one-providers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "one-providers" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | license = "Apache-2.0" 7 | 8 | [dependencies] 9 | thiserror = { workspace = true } 10 | serde = { workspace = true } 11 | serde_json = { workspace = true } 12 | serde_with = { version = "3.8", features = ["json", "time_0_3"] } 13 | zeroize = { workspace = true, features = ["serde"] } 14 | ed25519-compact = { version = "2.1" } 15 | p256 = { version = "0.13", features = ["jwk"] } 16 | bs58 = { version = "0.5" } 17 | blstrs = { version = "0.7" } 18 | josekit = { version = "0.8", features = ["vendored"] } 19 | pairing_crypto = { version = "0.4", git = "https://github.com/Iskander508/pairing_crypto", rev = "517a424a989b57b987aacb3642bd7cd2c60b94d1" } 20 | mockall = { version = "0.13", optional = true } 21 | sha2 = { version = "0.10" } 22 | anyhow = { version = "1.0.86" } 23 | ct-codecs = { version = "1.1.1" } 24 | uuid = { version = "1.10.0", features = ["v4", "serde"] } 25 | async-trait = { version = "0.1.81" } 26 | cocoon = { version = "0.4" } 27 | url = { version = "2.5", features = ["serde"] } 28 | tokio = { version = "1.38", features = ["macros"] } 29 | reqwest = { workspace = true } 30 | futures = { version = "0.3" } 31 | json-syntax = { version = "0.9" } 32 | locspan = { version = "0.7" } 33 | mime = { version = "0.3" } 34 | rdf-types = { version = "0.15" } 35 | convert_case = { version = "0.6" } 36 | ciborium = { version = "0.2" } 37 | strum = { version = "0.26", features = ["derive"] } 38 | sophia_jsonld = { version = "0.8", features = ["http_client"] } 39 | sophia_api = { version = "0.8" } 40 | sophia_c14n = { version = "0.8" } 41 | urlencoding = { version = "2.1" } 42 | itertools = { version = "0.13" } 43 | json-ld = { version = "0.15" } 44 | jsonptr = { version = "0.5" } 45 | bit-vec = { version = "0.8" } 46 | flate2 = { version = "1.0" } 47 | serde_urlencoded = { version = "0.7" } 48 | serde_qs = { version = "0.13" } 49 | one-crypto = { version = "0.1.0", path = "../one-crypto" } 50 | 51 | time.workspace = true 52 | 53 | [dev-dependencies] 54 | mockall = { version = "0.13" } 55 | wiremock = { version = "0.6" } 56 | maplit = { version = "1.0" } 57 | one-crypto = { version = "0.1.0", path = "../one-crypto", features = ["mock"] } 58 | 59 | [features] 60 | mock = ["mockall"] 61 | -------------------------------------------------------------------------------- /one-providers/src/common_dto/mod.rs: -------------------------------------------------------------------------------- 1 | //! DTOs shared across providers. 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] 6 | #[serde(rename_all = "camelCase")] 7 | #[serde(tag = "kty")] 8 | pub enum PublicKeyJwkDTO { 9 | #[serde(rename = "EC")] 10 | Ec(PublicKeyJwkEllipticDataDTO), 11 | #[serde(rename = "RSA")] 12 | Rsa(PublicKeyJwkRsaDataDTO), 13 | #[serde(rename = "OKP")] 14 | Okp(PublicKeyJwkEllipticDataDTO), 15 | #[serde(rename = "oct")] 16 | Oct(PublicKeyJwkOctDataDTO), 17 | #[serde(rename = "MLWE")] 18 | Mlwe(PublicKeyJwkMlweDataDTO), 19 | } 20 | 21 | impl PublicKeyJwkDTO { 22 | pub fn get_use(&self) -> &Option { 23 | match self { 24 | PublicKeyJwkDTO::Ec(val) => &val.r#use, 25 | PublicKeyJwkDTO::Rsa(val) => &val.r#use, 26 | PublicKeyJwkDTO::Okp(val) => &val.r#use, 27 | PublicKeyJwkDTO::Oct(val) => &val.r#use, 28 | PublicKeyJwkDTO::Mlwe(val) => &val.r#use, 29 | } 30 | } 31 | } 32 | 33 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] 34 | pub struct PublicKeyJwkRsaDataDTO { 35 | #[serde(default, skip_serializing_if = "Option::is_none")] 36 | pub r#use: Option, 37 | pub e: String, 38 | pub n: String, 39 | } 40 | 41 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] 42 | pub struct PublicKeyJwkOctDataDTO { 43 | #[serde(default, skip_serializing_if = "Option::is_none")] 44 | pub r#use: Option, 45 | pub k: String, 46 | } 47 | 48 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] 49 | pub struct PublicKeyJwkMlweDataDTO { 50 | #[serde(default, skip_serializing_if = "Option::is_none")] 51 | pub r#use: Option, 52 | pub alg: String, 53 | pub x: String, 54 | } 55 | 56 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] 57 | pub struct PublicKeyJwkEllipticDataDTO { 58 | #[serde(default, skip_serializing_if = "Option::is_none")] 59 | pub r#use: Option, 60 | pub crv: String, 61 | pub x: String, 62 | #[serde(default, skip_serializing_if = "Option::is_none")] 63 | pub y: Option, 64 | } 65 | -------------------------------------------------------------------------------- /one-providers/src/common_mappers.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Deserializer}; 2 | 3 | pub(crate) fn deserialize_with_serde_json<'de, D, T>(deserializer: D) -> Result 4 | where 5 | D: Deserializer<'de>, 6 | T: for<'a> Deserialize<'a>, 7 | { 8 | let value = serde_json::Value::deserialize(deserializer)?; 9 | match value.as_str() { 10 | None => serde_json::from_value(value).map_err(serde::de::Error::custom), 11 | Some(buffer) => serde_json::from_str(buffer).map_err(serde::de::Error::custom), 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /one-providers/src/common_models/claim.rs: -------------------------------------------------------------------------------- 1 | use time::OffsetDateTime; 2 | use uuid::Uuid; 3 | 4 | use super::claim_schema::OpenClaimSchema; 5 | use crate::common_models::{ 6 | credential::CredentialId, 7 | macros::{impl_display, impl_from, impl_into}, 8 | }; 9 | 10 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 11 | pub struct ClaimId(Uuid); 12 | impl_display!(ClaimId); 13 | impl_from!(ClaimId; Uuid); 14 | impl_into!(ClaimId; Uuid); 15 | 16 | #[derive(Clone, Debug, Eq, PartialEq)] 17 | pub struct OpenClaim { 18 | pub id: ClaimId, 19 | pub credential_id: CredentialId, 20 | pub created_date: OffsetDateTime, 21 | pub last_modified: OffsetDateTime, 22 | pub value: String, 23 | pub path: String, 24 | 25 | // Relations 26 | pub schema: Option, 27 | } 28 | -------------------------------------------------------------------------------- /one-providers/src/common_models/claim_schema.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use time::OffsetDateTime; 3 | use uuid::Uuid; 4 | 5 | use crate::common_models::macros::{impl_display, impl_from, impl_into}; 6 | 7 | #[derive(Debug, Clone, Copy, Eq, Serialize, Deserialize, PartialEq, Hash)] 8 | pub struct ClaimSchemaId(Uuid); 9 | impl_display!(ClaimSchemaId); 10 | impl_from!(ClaimSchemaId; Uuid); 11 | impl_into!(ClaimSchemaId; Uuid); 12 | 13 | #[derive(Clone, Debug, Eq, PartialEq)] 14 | pub struct OpenClaimSchema { 15 | pub id: ClaimSchemaId, 16 | pub key: String, 17 | pub data_type: String, 18 | pub created_date: OffsetDateTime, 19 | pub last_modified: OffsetDateTime, 20 | pub array: bool, 21 | } 22 | -------------------------------------------------------------------------------- /one-providers/src/common_models/credential.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use strum::Display; 3 | use time::OffsetDateTime; 4 | use uuid::Uuid; 5 | 6 | use super::{ 7 | claim::OpenClaim, 8 | credential_schema::OpenCredentialSchema, 9 | did::DidId, 10 | interaction::{InteractionId, OpenInteraction}, 11 | key::KeyId, 12 | }; 13 | use crate::common_models::{ 14 | did::OpenDid, 15 | key::OpenKey, 16 | macros::{impl_display, impl_from, impl_into}, 17 | }; 18 | 19 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] 20 | pub struct CredentialId(Uuid); 21 | impl_display!(CredentialId); 22 | impl_from!(CredentialId; Uuid); 23 | impl_into!(CredentialId; Uuid); 24 | 25 | #[derive(Clone, Debug, Eq, PartialEq)] 26 | pub struct OpenCredential { 27 | pub id: CredentialId, 28 | pub created_date: OffsetDateTime, 29 | pub issuance_date: OffsetDateTime, 30 | pub last_modified: OffsetDateTime, 31 | pub deleted_at: Option, 32 | pub credential: Vec, 33 | pub exchange: String, 34 | pub redirect_uri: Option, 35 | pub role: OpenCredentialRole, 36 | 37 | // Relations: 38 | pub state: Option>, 39 | pub claims: Option>, 40 | pub issuer_did: Option, 41 | pub holder_did: Option, 42 | pub schema: Option, 43 | pub key: Option, 44 | pub interaction: Option, 45 | } 46 | 47 | #[derive(Clone, Debug, Eq, PartialEq)] 48 | pub struct OpenCredentialState { 49 | pub created_date: OffsetDateTime, 50 | pub state: OpenCredentialStateEnum, 51 | pub suspend_end_date: Option, 52 | } 53 | 54 | #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display)] 55 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 56 | pub enum OpenCredentialStateEnum { 57 | Created, 58 | Pending, 59 | Offered, 60 | Accepted, 61 | Rejected, 62 | Revoked, 63 | Suspended, 64 | Error, 65 | } 66 | 67 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 68 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 69 | pub enum OpenCredentialRole { 70 | Holder, 71 | Issuer, 72 | Verifier, 73 | } 74 | 75 | #[derive(Clone, Debug, Eq, PartialEq)] 76 | pub struct OpenUpdateCredentialRequest { 77 | pub id: CredentialId, 78 | 79 | pub credential: Option>, 80 | pub holder_did_id: Option, 81 | pub issuer_did_id: Option, 82 | pub state: Option, 83 | pub interaction: Option, 84 | pub key: Option, 85 | pub redirect_uri: Option>, 86 | } 87 | -------------------------------------------------------------------------------- /one-providers/src/common_models/credential_schema.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use time::OffsetDateTime; 3 | use uuid::Uuid; 4 | 5 | use super::{claim_schema::OpenClaimSchema, organisation::OpenOrganisation}; 6 | use crate::common_models::macros::{impl_display, impl_from, impl_into}; 7 | 8 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] 9 | pub struct CredentialSchemaId(Uuid); 10 | impl_display!(CredentialSchemaId); 11 | impl_from!(CredentialSchemaId; Uuid); 12 | impl_into!(CredentialSchemaId; Uuid); 13 | 14 | pub type CredentialSchemaName = String; 15 | pub type CredentialFormat = String; 16 | pub type RevocationMethod = String; 17 | 18 | #[derive(Clone, Debug, Eq, PartialEq)] 19 | pub struct OpenCredentialSchema { 20 | pub id: CredentialSchemaId, 21 | pub deleted_at: Option, 22 | pub created_date: OffsetDateTime, 23 | pub last_modified: OffsetDateTime, 24 | pub name: CredentialSchemaName, 25 | pub format: CredentialFormat, 26 | pub revocation_method: RevocationMethod, 27 | pub wallet_storage_type: Option, 28 | pub layout_type: OpenLayoutType, 29 | pub layout_properties: Option, 30 | pub schema_id: String, 31 | pub schema_type: String, 32 | 33 | // Relations 34 | pub claim_schemas: Option>, 35 | pub organisation: Option, 36 | } 37 | 38 | #[derive(Clone, Debug, Eq, PartialEq)] 39 | pub struct OpenCredentialSchemaClaim { 40 | pub schema: OpenClaimSchema, 41 | pub required: bool, 42 | } 43 | 44 | #[derive(Clone, Debug, Eq, Serialize, Deserialize, PartialEq)] 45 | #[serde(rename_all = "UPPERCASE")] 46 | pub enum OpenWalletStorageTypeEnum { 47 | Hardware, 48 | Software, 49 | } 50 | 51 | #[derive(Clone, Debug, Eq, Serialize, Deserialize, PartialEq)] 52 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 53 | pub enum OpenLayoutType { 54 | Card, 55 | Document, 56 | SingleAttribute, 57 | } 58 | 59 | #[derive(Clone, Debug, Eq, Serialize, Deserialize, PartialEq)] 60 | #[serde(rename_all = "camelCase")] 61 | pub struct OpenLayoutProperties { 62 | #[serde(skip_serializing_if = "Option::is_none")] 63 | pub background: Option, 64 | #[serde(skip_serializing_if = "Option::is_none")] 65 | pub logo: Option, 66 | #[serde(skip_serializing_if = "Option::is_none")] 67 | pub primary_attribute: Option, 68 | #[serde(skip_serializing_if = "Option::is_none")] 69 | pub secondary_attribute: Option, 70 | #[serde(skip_serializing_if = "Option::is_none")] 71 | pub picture_attribute: Option, 72 | #[serde(skip_serializing_if = "Option::is_none")] 73 | pub code: Option, 74 | } 75 | 76 | #[derive(Clone, Debug, Eq, Serialize, Deserialize, PartialEq)] 77 | #[serde(rename_all = "camelCase")] 78 | pub struct OpenBackgroundProperties { 79 | #[serde(skip_serializing_if = "Option::is_none")] 80 | pub color: Option, 81 | #[serde(skip_serializing_if = "Option::is_none")] 82 | pub image: Option, 83 | } 84 | 85 | #[derive(Clone, Debug, Eq, Serialize, Deserialize, PartialEq)] 86 | #[serde(rename_all = "camelCase")] 87 | pub struct OpenLogoProperties { 88 | #[serde(skip_serializing_if = "Option::is_none")] 89 | pub font_color: Option, 90 | #[serde(skip_serializing_if = "Option::is_none")] 91 | pub background_color: Option, 92 | #[serde(skip_serializing_if = "Option::is_none")] 93 | pub image: Option, 94 | } 95 | 96 | #[derive(Clone, Debug, Eq, Serialize, Deserialize, PartialEq)] 97 | #[serde(rename_all = "camelCase")] 98 | pub struct OpenCodeProperties { 99 | pub attribute: String, 100 | pub r#type: OpenCodeTypeEnum, 101 | } 102 | 103 | #[derive(Clone, Debug, Eq, Deserialize, Serialize, PartialEq)] 104 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 105 | pub enum OpenCodeTypeEnum { 106 | Barcode, 107 | Mrz, 108 | QrCode, 109 | } 110 | 111 | #[derive(Clone, Debug, Eq, PartialEq)] 112 | pub struct OpenUpdateCredentialSchemaRequest { 113 | pub id: CredentialSchemaId, 114 | pub revocation_method: Option, 115 | pub format: Option, 116 | pub claim_schemas: Option>, 117 | } 118 | -------------------------------------------------------------------------------- /one-providers/src/common_models/did.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use strum::Display; 3 | use time::OffsetDateTime; 4 | use uuid::Uuid; 5 | 6 | use crate::common_models::{ 7 | key::OpenKey, 8 | macros::{impl_display, impl_from, impl_into}, 9 | }; 10 | 11 | use super::organisation::OpenOrganisation; 12 | 13 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] 14 | pub struct DidId(Uuid); 15 | impl_display!(DidId); 16 | impl_from!(DidId; uuid::Uuid); 17 | impl_into!(DidId; uuid::Uuid); 18 | 19 | #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] 20 | #[serde(transparent)] 21 | #[repr(transparent)] 22 | pub struct DidValue(String); 23 | impl_display!(DidValue); 24 | impl_from!(DidValue; String); 25 | impl_into!(DidValue; String); 26 | 27 | impl DidValue { 28 | pub fn as_str(&self) -> &str { 29 | self.0.as_str() 30 | } 31 | } 32 | 33 | #[derive(Clone, Debug, Eq, PartialEq)] 34 | pub struct OpenDid { 35 | pub id: DidId, 36 | pub created_date: OffsetDateTime, 37 | pub last_modified: OffsetDateTime, 38 | pub name: String, 39 | pub did: DidValue, 40 | pub did_type: DidType, 41 | pub did_method: String, 42 | pub deactivated: bool, 43 | 44 | // Relations: 45 | pub keys: Option>, 46 | pub organisation: Option, 47 | } 48 | 49 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 50 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 51 | pub enum DidType { 52 | Remote, 53 | Local, 54 | } 55 | 56 | #[derive(Clone, Debug, Eq, PartialEq, Display)] 57 | pub enum KeyRole { 58 | Authentication, 59 | AssertionMethod, 60 | KeyAgreement, 61 | CapabilityInvocation, 62 | CapabilityDelegation, 63 | } 64 | 65 | #[derive(Clone, Debug, Eq, PartialEq)] 66 | pub struct RelatedKey { 67 | pub role: KeyRole, 68 | pub key: OpenKey, 69 | } 70 | -------------------------------------------------------------------------------- /one-providers/src/common_models/interaction.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use time::OffsetDateTime; 3 | use url::Url; 4 | use uuid::Uuid; 5 | 6 | use super::macros::{impl_display, impl_from, impl_into}; 7 | 8 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] 9 | pub struct InteractionId(Uuid); 10 | impl_display!(InteractionId); 11 | impl_from!(InteractionId; Uuid); 12 | impl_into!(InteractionId; Uuid); 13 | 14 | #[derive(Clone, Debug, Eq, PartialEq)] 15 | pub struct OpenInteraction { 16 | pub id: InteractionId, 17 | pub created_date: OffsetDateTime, 18 | pub host: Option, 19 | pub data: Option>, 20 | } 21 | -------------------------------------------------------------------------------- /one-providers/src/common_models/key.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use time::OffsetDateTime; 3 | use uuid::Uuid; 4 | 5 | use super::{ 6 | macros::{impl_display, impl_from, impl_into}, 7 | organisation::OpenOrganisation, 8 | }; 9 | 10 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] 11 | pub struct KeyId(Uuid); 12 | impl_display!(KeyId); 13 | impl_from!(KeyId; Uuid); 14 | impl_into!(KeyId; Uuid); 15 | 16 | #[derive(Debug, Clone, Eq, PartialEq)] 17 | pub struct OpenKey { 18 | pub id: KeyId, 19 | pub created_date: OffsetDateTime, 20 | pub last_modified: OffsetDateTime, 21 | pub public_key: Vec, 22 | pub name: String, 23 | pub key_reference: Vec, 24 | pub storage_type: String, 25 | pub key_type: String, 26 | 27 | // Relations: 28 | pub organisation: Option, 29 | } 30 | -------------------------------------------------------------------------------- /one-providers/src/common_models/macros.rs: -------------------------------------------------------------------------------- 1 | /// Implements [`std::fmt::Display`] for a newtype, assuming that the inner type implements Display. 2 | macro_rules! impl_display { 3 | ($newtype: ty) => { 4 | impl std::fmt::Display for $newtype { 5 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 6 | std::fmt::Display::fmt(&self.0, f) 7 | } 8 | } 9 | }; 10 | } 11 | pub(crate) use impl_display; 12 | 13 | /// Implements [`std::convert::From`] 14 | macro_rules! impl_from { 15 | ($newtype: ty; $inner: ty) => { 16 | impl std::convert::From<$inner> for $newtype { 17 | fn from(value: $inner) -> Self { 18 | Self(value) 19 | } 20 | } 21 | }; 22 | } 23 | pub(crate) use impl_from; 24 | 25 | /// Implements [`std::convert::Into`] 26 | macro_rules! impl_into { 27 | ($newtype: ty; $inner: ty) => { 28 | impl std::convert::From<$newtype> for $inner { 29 | fn from(value: $newtype) -> Self { 30 | value.0 31 | } 32 | } 33 | }; 34 | } 35 | pub(crate) use impl_into; 36 | -------------------------------------------------------------------------------- /one-providers/src/common_models/mod.rs: -------------------------------------------------------------------------------- 1 | //! Models shared across providers. See the [API resources][api] for more on 2 | //! entities found here. 3 | //! 4 | //! [api]: https://docs.procivis.ch/guides/api/overview 5 | 6 | pub mod claim; 7 | pub mod claim_schema; 8 | pub mod credential; 9 | pub mod credential_schema; 10 | pub mod did; 11 | pub mod interaction; 12 | pub mod key; 13 | pub mod macros; 14 | pub mod organisation; 15 | pub mod proof; 16 | pub mod proof_schema; 17 | 18 | pub const NESTED_CLAIM_MARKER: char = '/'; 19 | 20 | #[derive(Clone, Debug, PartialEq, Eq)] 21 | pub enum OpenPublicKeyJwk { 22 | Ec(OpenPublicKeyJwkEllipticData), 23 | Rsa(OpenPublicKeyJwkRsaData), 24 | Okp(OpenPublicKeyJwkEllipticData), 25 | Oct(OpenPublicKeyJwkOctData), 26 | Mlwe(OpenPublicKeyJwkMlweData), 27 | } 28 | 29 | #[derive(Clone, Debug, PartialEq, Eq)] 30 | pub struct OpenPublicKeyJwkRsaData { 31 | pub r#use: Option, 32 | pub e: String, 33 | pub n: String, 34 | } 35 | 36 | #[derive(Clone, Debug, PartialEq, Eq)] 37 | pub struct OpenPublicKeyJwkOctData { 38 | pub r#use: Option, 39 | pub k: String, 40 | } 41 | 42 | #[derive(Clone, Debug, PartialEq, Eq)] 43 | pub struct OpenPublicKeyJwkMlweData { 44 | pub r#use: Option, 45 | pub alg: String, 46 | pub x: String, 47 | } 48 | 49 | #[derive(Clone, Debug, PartialEq, Eq)] 50 | pub struct OpenPublicKeyJwkEllipticData { 51 | pub r#use: Option, 52 | pub crv: String, 53 | pub x: String, 54 | pub y: Option, 55 | } 56 | -------------------------------------------------------------------------------- /one-providers/src/common_models/organisation.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use uuid::Uuid; 3 | 4 | use super::macros::{impl_display, impl_from, impl_into}; 5 | 6 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] 7 | pub struct OrganisationId(Uuid); 8 | impl_display!(OrganisationId); 9 | impl_from!(OrganisationId; Uuid); 10 | impl_into!(OrganisationId; Uuid); 11 | 12 | #[derive(Clone, Debug, Eq, PartialEq)] 13 | pub struct OpenOrganisation { 14 | pub id: OrganisationId, 15 | } 16 | -------------------------------------------------------------------------------- /one-providers/src/common_models/proof.rs: -------------------------------------------------------------------------------- 1 | use super::claim::OpenClaim; 2 | use super::credential::OpenCredential; 3 | use super::did::{DidId, OpenDid}; 4 | use super::interaction::{InteractionId, OpenInteraction}; 5 | use super::proof_schema::OpenProofSchema; 6 | use crate::common_models::key::OpenKey; 7 | use crate::common_models::macros::{impl_display, impl_from, impl_into}; 8 | use strum::Display; 9 | use time::OffsetDateTime; 10 | use uuid::Uuid; 11 | 12 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 13 | pub struct ProofId(Uuid); 14 | impl_display!(ProofId); 15 | impl_from!(ProofId; Uuid); 16 | impl_into!(ProofId; Uuid); 17 | 18 | #[derive(Clone, Debug, Eq, PartialEq)] 19 | pub struct OpenProof { 20 | pub id: ProofId, 21 | pub created_date: OffsetDateTime, 22 | pub last_modified: OffsetDateTime, 23 | pub issuance_date: OffsetDateTime, 24 | pub exchange: String, 25 | pub transport: String, 26 | pub redirect_uri: Option, 27 | 28 | // Relations 29 | pub state: Option>, 30 | pub schema: Option, 31 | pub claims: Option>, 32 | pub verifier_did: Option, 33 | pub holder_did: Option, 34 | pub verifier_key: Option, 35 | pub interaction: Option, 36 | } 37 | 38 | #[derive(Clone, Debug, Eq, PartialEq, Display)] 39 | pub enum OpenProofStateEnum { 40 | Created, 41 | Pending, 42 | Requested, 43 | Accepted, 44 | Rejected, 45 | Error, 46 | } 47 | 48 | #[derive(Clone, Debug, Eq, PartialEq)] 49 | pub struct OpenProofState { 50 | pub created_date: OffsetDateTime, 51 | pub last_modified: OffsetDateTime, 52 | pub state: OpenProofStateEnum, 53 | } 54 | 55 | #[derive(Clone, Debug, Eq, PartialEq)] 56 | pub struct OpenProofClaim { 57 | pub claim: OpenClaim, 58 | // Relations 59 | pub credential: Option, 60 | } 61 | 62 | #[derive(Clone, Debug, Eq, PartialEq)] 63 | pub struct OpenUpdateProofRequest { 64 | pub id: ProofId, 65 | 66 | pub holder_did_id: Option, 67 | pub verifier_did_id: Option, 68 | pub state: Option, 69 | pub interaction: Option>, 70 | pub redirect_uri: Option>, 71 | } 72 | -------------------------------------------------------------------------------- /one-providers/src/common_models/proof_schema.rs: -------------------------------------------------------------------------------- 1 | use time::OffsetDateTime; 2 | use uuid::Uuid; 3 | 4 | use super::{ 5 | claim_schema::OpenClaimSchema, credential_schema::OpenCredentialSchema, 6 | organisation::OpenOrganisation, 7 | }; 8 | use crate::common_models::macros::{impl_display, impl_from, impl_into}; 9 | 10 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 11 | pub struct ProofSchemaId(Uuid); 12 | impl_display!(ProofSchemaId); 13 | impl_from!(ProofSchemaId; Uuid); 14 | impl_into!(ProofSchemaId; Uuid); 15 | 16 | #[derive(Clone, Debug, Eq, PartialEq)] 17 | pub struct OpenProofSchema { 18 | pub id: ProofSchemaId, 19 | pub created_date: OffsetDateTime, 20 | pub last_modified: OffsetDateTime, 21 | pub deleted_at: Option, 22 | pub name: String, 23 | pub expire_duration: u32, 24 | 25 | // Relations 26 | pub input_schemas: Option>, 27 | pub organisation: Option, 28 | } 29 | 30 | #[derive(Clone, Debug, Eq, PartialEq, Default)] 31 | pub struct OpenProofInputSchema { 32 | pub validity_constraint: Option, 33 | 34 | // Relations 35 | pub claim_schemas: Option>, 36 | pub credential_schema: Option, 37 | } 38 | 39 | #[derive(Clone, Debug, Eq, PartialEq)] 40 | pub struct OpenProofInputClaimSchema { 41 | pub schema: OpenClaimSchema, 42 | pub required: bool, 43 | pub order: u32, 44 | } 45 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/error.rs: -------------------------------------------------------------------------------- 1 | //! Enumerates errors for credential formatter provider. 2 | 3 | use thiserror::Error; 4 | 5 | use one_crypto::CryptoProviderError; 6 | 7 | #[derive(Debug, PartialEq, Error)] 8 | pub enum FormatterError { 9 | #[error("Failed: `{0}`")] 10 | Failed(String), 11 | #[error("Could not sign: `{0}`")] 12 | CouldNotSign(String), 13 | #[error("Could not verify: `{0}`")] 14 | CouldNotVerify(String), 15 | #[error("Could not format: `{0}`")] 16 | CouldNotFormat(String), 17 | #[error("Could not extract credentials: `{0}`")] 18 | CouldNotExtractCredentials(String), 19 | #[error("Could not extract presentation: `{0}`")] 20 | CouldNotExtractPresentation(String), 21 | #[error("Could not extract claims from presentation: `{0}`")] 22 | CouldNotExtractClaimsFromPresentation(String), 23 | #[error("Incorrect signature")] 24 | IncorrectSignature, 25 | #[error("Missing part")] 26 | MissingPart, 27 | #[error("Missing disclosure")] 28 | MissingDisclosure, 29 | #[error("Missing issuer")] 30 | MissingIssuer, 31 | #[error("Missing claim")] 32 | MissingClaim, 33 | #[error("Only BBS is allowed")] 34 | BBSOnly, 35 | #[error("Crypto library error: `{0}`")] 36 | CryptoError(#[from] CryptoProviderError), 37 | #[error("{formatter} formatter missing missing base url")] 38 | MissingBaseUrl { formatter: &'static str }, 39 | #[error("JSON mapping error: `{0}`")] 40 | JsonMapping(String), 41 | #[error("Jsonptr assign error: `{0}`")] 42 | JsonPtrAssignError(#[from] jsonptr::assign::AssignError), 43 | #[error("Jsonptr parse error: `{0}`")] 44 | JsonPtrParseError(#[from] jsonptr::ParseError), 45 | #[error("Float value is NaN")] 46 | FloatValueIsNaN, 47 | } 48 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/common.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::credential_formatter::{error::FormatterError, model::PublishedClaim}; 4 | 5 | pub fn nest_claims( 6 | claims: impl IntoIterator, 7 | ) -> Result, FormatterError> { 8 | let mut data = serde_json::Value::Object(Default::default()); 9 | 10 | let mut claims = claims.into_iter().collect::>(); 11 | claims.sort_unstable_by(|a, b| a.key.cmp(&b.key)); 12 | 13 | for claim in claims { 14 | let path = format!("/{}", claim.key); 15 | let pointer = jsonptr::Pointer::parse(&path)?; 16 | let value: serde_json::Value = claim.value.try_into()?; 17 | pointer.assign(&mut data, value)?; 18 | } 19 | 20 | Ok(data 21 | .as_object() 22 | .ok_or(FormatterError::JsonMapping( 23 | "data is not an Object".to_string(), 24 | ))? 25 | .into_iter() 26 | .map(|(k, v)| (k.to_owned(), v.to_owned())) 27 | .collect()) 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use serde_json::json; 33 | 34 | use super::*; 35 | 36 | #[test] 37 | fn test_format_nested_vc_jwt() { 38 | let claims = vec![ 39 | PublishedClaim { 40 | key: "name".into(), 41 | value: "John".into(), 42 | datatype: None, 43 | array_item: false, 44 | }, 45 | PublishedClaim { 46 | key: "location/x".into(), 47 | value: "1".into(), 48 | datatype: None, 49 | array_item: false, 50 | }, 51 | PublishedClaim { 52 | key: "location/y".into(), 53 | value: "2".into(), 54 | datatype: None, 55 | array_item: false, 56 | }, 57 | ]; 58 | let expected = HashMap::from([ 59 | ( 60 | "location".to_string(), 61 | json!({ 62 | "x": "1", 63 | "y": "2" 64 | }), 65 | ), 66 | ("name".to_string(), json!("John")), 67 | ]); 68 | 69 | assert_eq!(expected, nest_claims(claims).unwrap()); 70 | } 71 | 72 | #[test] 73 | fn test_format_nested_vc_jwt_array() { 74 | let claims = vec![ 75 | PublishedClaim { 76 | key: "name".into(), 77 | value: "John".into(), 78 | datatype: None, 79 | array_item: false, 80 | }, 81 | PublishedClaim { 82 | key: "location/0".into(), 83 | value: "1".into(), 84 | datatype: None, 85 | array_item: true, 86 | }, 87 | PublishedClaim { 88 | key: "location/1".into(), 89 | value: "2".into(), 90 | datatype: None, 91 | array_item: true, 92 | }, 93 | ]; 94 | let expected = HashMap::from([ 95 | ("location".to_string(), json!(["1", "2"])), 96 | ("name".to_string(), json!("John")), 97 | ]); 98 | 99 | assert_eq!(expected, nest_claims(claims).unwrap()); 100 | } 101 | } 102 | 103 | #[cfg(any(test, feature = "mock"))] 104 | #[derive(Clone)] 105 | pub struct MockAuth Vec + Send + Sync>(pub F); 106 | 107 | #[cfg(any(test, feature = "mock"))] 108 | pub use crate::credential_formatter::model::SignatureProvider; 109 | #[cfg(any(test, feature = "mock"))] 110 | pub use one_crypto::SignerError; 111 | 112 | #[cfg(any(test, feature = "mock"))] 113 | #[async_trait::async_trait] 114 | impl Vec + Send + Sync> SignatureProvider for MockAuth { 115 | async fn sign(&self, message: &[u8]) -> Result, SignerError> { 116 | Ok(self.0(message)) 117 | } 118 | fn get_key_id(&self) -> Option { 119 | Some("#key0".to_owned()) 120 | } 121 | fn get_public_key(&self) -> Vec { 122 | vec![] 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/json_ld/context/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod caching_loader; 2 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/json_ld/model.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use serde_with::{serde_as, OneOrMany}; 5 | use time::OffsetDateTime; 6 | 7 | use crate::{ 8 | common_models::did::DidValue, 9 | credential_formatter::model::{CredentialSchema, CredentialStatus}, 10 | }; 11 | 12 | pub type VerifiableCredential = Vec>; 13 | 14 | // The main credential 15 | #[serde_as] 16 | #[derive(Debug, Serialize, Deserialize, Clone)] 17 | #[serde(rename_all = "camelCase")] 18 | pub struct LdCredential { 19 | #[serde(rename = "@context")] 20 | pub context: Vec, 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | pub id: Option, 23 | pub r#type: Vec, 24 | pub issuer: DidValue, 25 | // we keep this field for backwards compatibility with VCDM v1.1 26 | #[serde(with = "time::serde::rfc3339::option")] 27 | #[serde(default, skip_serializing_if = "Option::is_none")] 28 | pub issuance_date: Option, 29 | #[serde(with = "time::serde::rfc3339::option")] 30 | #[serde(default, skip_serializing_if = "Option::is_none")] 31 | pub valid_from: Option, 32 | #[serde(with = "time::serde::rfc3339::option")] 33 | #[serde(default, skip_serializing_if = "Option::is_none")] 34 | pub valid_until: Option, 35 | pub credential_subject: LdCredentialSubject, 36 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 37 | #[serde_as(as = "OneOrMany<_>")] 38 | pub credential_status: Vec, 39 | #[serde(skip_serializing_if = "Option::is_none")] 40 | pub proof: Option, 41 | #[serde(skip_serializing_if = "Option::is_none")] 42 | pub credential_schema: Option, 43 | } 44 | 45 | #[derive(Debug, Clone, Serialize, Deserialize)] 46 | #[serde(rename_all = "camelCase")] 47 | pub struct LdCredentialSubject { 48 | #[serde(skip_serializing_if = "Option::is_none")] 49 | pub id: Option, 50 | #[serde(flatten)] 51 | pub subject: HashMap, 52 | } 53 | 54 | pub type Claims = HashMap; 55 | 56 | #[derive(Debug, Clone, Serialize, Deserialize)] 57 | #[serde(rename_all = "camelCase")] 58 | pub struct LdProof { 59 | #[serde(rename = "@context")] 60 | #[serde(skip_serializing_if = "Option::is_none")] 61 | pub context: Option>, 62 | pub r#type: String, 63 | #[serde(skip_serializing_if = "Option::is_none")] 64 | #[serde(default, with = "time::serde::rfc3339::option")] 65 | pub created: Option, 66 | pub cryptosuite: String, 67 | pub verification_method: String, 68 | pub proof_purpose: String, 69 | #[serde(skip_serializing_if = "Option::is_none")] 70 | pub proof_value: Option, 71 | #[serde(skip_serializing_if = "Option::is_none")] 72 | pub nonce: Option, 73 | #[serde(skip_serializing_if = "Option::is_none")] 74 | pub challenge: Option, 75 | #[serde(skip_serializing_if = "Option::is_none")] 76 | pub domain: Option, 77 | } 78 | 79 | // The main presentation 80 | #[derive(Debug, Serialize, Deserialize, Clone)] 81 | #[serde(rename_all = "camelCase")] 82 | pub struct LdPresentation { 83 | #[serde(rename = "@context")] 84 | pub context: Vec, 85 | pub r#type: String, 86 | #[serde(with = "time::serde::rfc3339")] 87 | pub issuance_date: OffsetDateTime, 88 | pub verifiable_credential: VerifiableCredential, 89 | pub holder: DidValue, 90 | #[serde(skip_serializing_if = "Option::is_none")] 91 | pub nonce: Option, 92 | #[serde(skip_serializing_if = "Option::is_none")] 93 | pub proof: Option, 94 | } 95 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/json_ld_bbsplus/mapper.rs: -------------------------------------------------------------------------------- 1 | use super::model::TransformedEntry; 2 | use crate::credential_formatter::{ 3 | error::FormatterError, 4 | imp::{json_ld::model::LdCredential, json_ld_bbsplus::model::GroupEntry}, 5 | model::{CredentialSubject, DetailCredential}, 6 | }; 7 | 8 | pub fn to_grouped_entry(entries: Vec<(usize, String)>) -> TransformedEntry { 9 | TransformedEntry { 10 | data_type: "Map".to_owned(), 11 | value: entries 12 | .into_iter() 13 | .map(|(index, triple)| GroupEntry { 14 | index, 15 | entry: triple, 16 | }) 17 | .collect(), 18 | } 19 | } 20 | 21 | impl TryFrom for DetailCredential { 22 | type Error = FormatterError; 23 | 24 | fn try_from(value: LdCredential) -> Result { 25 | Ok(Self { 26 | id: value.id, 27 | valid_from: value.valid_from.or(value.issuance_date), 28 | valid_until: None, 29 | update_at: None, 30 | invalid_before: None, 31 | issuer_did: Some(value.issuer), 32 | subject: value.credential_subject.id, 33 | claims: CredentialSubject { 34 | values: value 35 | .credential_subject 36 | .subject 37 | .values() 38 | .next() 39 | .ok_or(FormatterError::JsonMapping( 40 | "subject is not defined".to_string(), 41 | ))? 42 | .as_object() 43 | .ok_or(FormatterError::JsonMapping( 44 | "subject is not an Object".to_string(), 45 | ))? 46 | .into_iter() 47 | .map(|(k, v)| (k.to_owned(), v.to_owned())) 48 | .collect(), 49 | }, 50 | status: value.credential_status, 51 | credential_schema: value.credential_schema, 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/json_ld_bbsplus/remove_undisclosed_keys.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::credential_formatter::{error::FormatterError, imp::json_ld::model::LdCredential}; 4 | 5 | pub(super) fn remove_undisclosed_keys( 6 | revealed_ld: &mut LdCredential, 7 | disclosed_keys: &[String], 8 | ) -> Result<(), FormatterError> { 9 | let mut result: HashMap = HashMap::new(); 10 | 11 | for (key, value) in &revealed_ld.credential_subject.subject { 12 | let mut object = serde_json::Value::Object(Default::default()); 13 | 14 | for key in disclosed_keys { 15 | let full_path = format!("/{key}"); 16 | if let Some(value) = value.pointer(&full_path) { 17 | let pointer = jsonptr::Pointer::parse(&full_path)?; 18 | pointer.assign(&mut object, value.to_owned())?; 19 | } 20 | } 21 | 22 | result.insert(key.to_owned(), object); 23 | } 24 | 25 | revealed_ld.credential_subject.subject = result; 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/jwt/mapper.rs: -------------------------------------------------------------------------------- 1 | use ct_codecs::{Base64UrlSafeNoPadding, Encoder}; 2 | 3 | use crate::credential_formatter::error::FormatterError; 4 | 5 | pub fn bin_to_b64url_string(bin: &[u8]) -> Result { 6 | Base64UrlSafeNoPadding::encode_to_string(bin) 7 | .map_err(|e| FormatterError::CouldNotFormat(e.to_string())) 8 | } 9 | 10 | pub fn string_to_b64url_string(string: &str) -> Result { 11 | Base64UrlSafeNoPadding::encode_to_string(string) 12 | .map_err(|e| FormatterError::CouldNotFormat(e.to_string())) 13 | } 14 | 15 | pub fn json_from_decoded(decoded: Vec) -> Result { 16 | let result = String::from_utf8(decoded) 17 | .map_err(|e| FormatterError::CouldNotExtractCredentials(e.to_string()))?; 18 | Ok(result) 19 | } 20 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/jwt/model.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use time::OffsetDateTime; 3 | 4 | #[derive(Debug, Clone, Serialize, Deserialize)] 5 | pub struct JWTHeader { 6 | #[serde(rename = "alg")] 7 | pub algorithm: String, 8 | 9 | #[serde(rename = "kid", default, skip_serializing_if = "Option::is_none")] 10 | pub key_id: Option, 11 | 12 | #[serde(rename = "typ", default, skip_serializing_if = "Option::is_none")] 13 | pub signature_type: Option, 14 | } 15 | 16 | #[derive(Debug, Clone, Serialize, Deserialize, Default)] 17 | pub struct JWTPayload { 18 | #[serde( 19 | rename = "iat", 20 | default, 21 | skip_serializing_if = "Option::is_none", 22 | with = "time::serde::timestamp::option" 23 | )] 24 | pub issued_at: Option, 25 | 26 | #[serde( 27 | rename = "exp", 28 | default, 29 | skip_serializing_if = "Option::is_none", 30 | with = "time::serde::timestamp::option" 31 | )] 32 | pub expires_at: Option, 33 | 34 | #[serde( 35 | rename = "nbf", 36 | default, 37 | skip_serializing_if = "Option::is_none", 38 | with = "time::serde::timestamp::option" 39 | )] 40 | pub invalid_before: Option, 41 | 42 | #[serde(rename = "iss", default, skip_serializing_if = "Option::is_none")] 43 | pub issuer: Option, 44 | 45 | #[serde(rename = "sub", default, skip_serializing_if = "Option::is_none")] 46 | pub subject: Option, 47 | 48 | #[serde(rename = "jti", default, skip_serializing_if = "Option::is_none")] 49 | pub jwt_id: Option, 50 | 51 | #[serde(rename = "nonce", default, skip_serializing_if = "Option::is_none")] 52 | pub nonce: Option, 53 | 54 | #[serde(flatten)] 55 | pub custom: CustomPayload, 56 | } 57 | 58 | #[derive(Debug)] 59 | pub struct DecomposedToken { 60 | pub header: JWTHeader, 61 | pub header_json: String, 62 | pub payload: JWTPayload, 63 | pub payload_json: String, 64 | pub signature: Vec, 65 | } 66 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/jwt/test.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use serde::{Deserialize, Serialize}; 3 | use time::{macros::datetime, OffsetDateTime}; 4 | 5 | use super::{model::JWTPayload, Jwt, TokenVerifier}; 6 | use crate::{ 7 | common_models::did::DidValue, 8 | credential_formatter::imp::common::{MockAuth, SignerError}, 9 | }; 10 | 11 | #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] 12 | struct Payload { 13 | test_field: String, 14 | } 15 | 16 | pub fn get_dummy_date() -> OffsetDateTime { 17 | datetime!(2005-04-02 21:37 +1) 18 | } 19 | 20 | pub struct TestVerify { 21 | issuer_did_value: Option, 22 | algorithm: String, 23 | token: String, 24 | signature: Vec, 25 | } 26 | 27 | #[async_trait] 28 | impl TokenVerifier for TestVerify { 29 | async fn verify<'a>( 30 | &self, 31 | issuer_did_value: Option, 32 | _issuer_key_id: Option<&'a str>, 33 | algorithm: &'a str, 34 | token: &'a [u8], 35 | signature: &'a [u8], 36 | ) -> Result<(), SignerError> { 37 | assert_eq!( 38 | issuer_did_value.map(|v| v.to_string()), 39 | self.issuer_did_value 40 | ); 41 | assert_eq!(algorithm, self.algorithm); 42 | assert_eq!(token, self.token.as_bytes()); 43 | 44 | if signature == self.signature { 45 | Ok(()) 46 | } else { 47 | Err(SignerError::InvalidSignature) 48 | } 49 | } 50 | } 51 | 52 | fn prepare_test_json() -> (Jwt, String) { 53 | let now = get_dummy_date(); 54 | 55 | let custom_payload = Payload { 56 | test_field: "test".to_owned(), 57 | }; 58 | 59 | let payload = JWTPayload { 60 | issued_at: Some(now), 61 | expires_at: Some(now), 62 | invalid_before: Some(now), 63 | issuer: Some("DID".to_owned()), 64 | subject: Some("DID".to_owned()), 65 | jwt_id: Some("ID".to_owned()), 66 | custom: custom_payload, 67 | nonce: None, 68 | }; 69 | let jwt: Jwt = Jwt::new( 70 | "Signature1".to_owned(), 71 | "Algorithm1".to_owned(), 72 | None, 73 | payload, 74 | ); 75 | 76 | (jwt, "eyJhbGciOiJBbGdvcml0aG0xIiwidHlwIjoiU2lnbmF0dXJlMSJ9.eyJpYXQiOjExMTI0NzQyMjAsImV4cCI6MTExMjQ3NDIyMCwibmJmIjoxMTEyNDc0MjIwLCJpc3MiOiJESUQiLCJzdWIiOiJESUQiLCJqdGkiOiJJRCIsInRlc3RfZmllbGQiOiJ0ZXN0In0.AQID".to_string()) 77 | } 78 | 79 | #[tokio::test] 80 | async fn test_tokenize() { 81 | let (json, reference_token) = prepare_test_json(); 82 | 83 | let reference_token_moved = reference_token.clone(); 84 | 85 | let auth_fn = MockAuth(move |data: &[u8]| { 86 | let jwt = extract_jwt_part(reference_token_moved.clone()); 87 | assert_eq!(data, jwt.as_bytes()); 88 | 89 | vec![1u8, 2, 3] 90 | }); 91 | 92 | let token = json.tokenize(Box::new(auth_fn)).await.unwrap(); 93 | 94 | assert_eq!(token, reference_token); 95 | } 96 | 97 | fn extract_jwt_part(token: String) -> String { 98 | let token_parts: Vec<&str> = token.split('.').collect(); 99 | if let Some(result) = token_parts.get(..token_parts.len() - 1) { 100 | result.join(".") 101 | } else { 102 | panic!("Incorrect input data"); 103 | } 104 | } 105 | 106 | #[tokio::test] 107 | async fn test_build_from_token() { 108 | let (json, reference_token) = prepare_test_json(); 109 | 110 | let jwt_part = extract_jwt_part(reference_token.clone()); 111 | let jwt: Jwt = Jwt::build_from_token( 112 | &reference_token, 113 | Some(Box::new(TestVerify { 114 | issuer_did_value: Some(String::from("DID")), 115 | algorithm: String::from("Algorithm1"), 116 | token: jwt_part, 117 | signature: vec![1, 2, 3], 118 | })), 119 | ) 120 | .await 121 | .unwrap(); 122 | 123 | assert_eq!(jwt.header.algorithm, json.header.algorithm); 124 | assert_eq!(jwt.header.signature_type, json.header.signature_type); 125 | 126 | assert_eq!(jwt.payload.custom, json.payload.custom); 127 | assert_eq!(jwt.payload.issuer, json.payload.issuer); 128 | assert_eq!(jwt.payload.jwt_id, json.payload.jwt_id); 129 | } 130 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/jwt_formatter/mapper.rs: -------------------------------------------------------------------------------- 1 | use super::model::{Issuer, VCContent, VC, VP}; 2 | use crate::{ 3 | common_models::did::DidValue, 4 | credential_formatter::{ 5 | error::FormatterError, 6 | imp::{common::nest_claims, jwt::Jwt}, 7 | model::{ 8 | Context, CredentialData, CredentialSchema, CredentialSchemaData, CredentialSubject, 9 | DetailCredential, Presentation, 10 | }, 11 | }, 12 | }; 13 | 14 | impl From for Option { 15 | fn from(credential_schema: CredentialSchemaData) -> Self { 16 | match credential_schema { 17 | CredentialSchemaData { 18 | id: Some(id), 19 | r#type: Some(r#type), 20 | metadata, 21 | .. 22 | } => Some(CredentialSchema::new(id, r#type, metadata)), 23 | _ => None, 24 | } 25 | } 26 | } 27 | 28 | pub(super) fn format_vc( 29 | credential: CredentialData, 30 | issuer: String, 31 | additional_context: Vec, 32 | additional_types: Vec, 33 | embed_layout_properties: bool, 34 | ) -> Result { 35 | let context = vec![Context::CredentialsV2.to_string()] 36 | .into_iter() 37 | .chain(additional_context) 38 | .collect(); 39 | 40 | let types = vec!["VerifiableCredential".to_owned()] 41 | .into_iter() 42 | .chain(additional_types) 43 | .collect(); 44 | 45 | // Strip layout (whole metadata as it only contains layout) 46 | let mut credential_schema: Option = credential.schema.into(); 47 | if let Some(schema) = &mut credential_schema { 48 | if !embed_layout_properties { 49 | schema.metadata = None; 50 | } 51 | } 52 | 53 | Ok(VC { 54 | vc: VCContent { 55 | context, 56 | r#type: types, 57 | id: credential.id, 58 | issuer: Some(Issuer::Url(issuer)), 59 | credential_subject: CredentialSubject { 60 | values: nest_claims(credential.claims)?, 61 | }, 62 | credential_status: credential.status, 63 | credential_schema, 64 | valid_from: None, 65 | valid_until: None, 66 | }, 67 | }) 68 | } 69 | 70 | impl From> for DetailCredential { 71 | fn from(jwt: Jwt) -> Self { 72 | DetailCredential { 73 | id: jwt.payload.jwt_id, 74 | valid_from: jwt.payload.issued_at, 75 | valid_until: jwt.payload.expires_at, 76 | update_at: None, 77 | invalid_before: jwt.payload.invalid_before, 78 | issuer_did: jwt.payload.issuer.map(DidValue::from), 79 | subject: jwt.payload.subject.map(DidValue::from), 80 | claims: jwt.payload.custom.vc.credential_subject, 81 | status: jwt.payload.custom.vc.credential_status, 82 | credential_schema: jwt.payload.custom.vc.credential_schema, 83 | } 84 | } 85 | } 86 | 87 | impl From> for Presentation { 88 | fn from(jwt: Jwt) -> Self { 89 | Presentation { 90 | id: jwt.payload.jwt_id, 91 | issued_at: jwt.payload.issued_at, 92 | expires_at: jwt.payload.expires_at, 93 | issuer_did: jwt.payload.issuer.map(DidValue::from), 94 | nonce: jwt.payload.nonce, 95 | credentials: jwt.payload.custom.vp.verifiable_credential, 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/jwt_formatter/model.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_with::{serde_as, OneOrMany}; 3 | use time::OffsetDateTime; 4 | 5 | use crate::credential_formatter::model::{CredentialSchema, CredentialStatus, CredentialSubject}; 6 | 7 | #[serde_as] 8 | #[derive(Debug, Serialize, Deserialize)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct VCContent { 11 | #[serde(rename = "@context")] 12 | pub context: Vec, 13 | pub r#type: Vec, 14 | #[serde(skip_serializing_if = "Option::is_none")] 15 | pub id: Option, 16 | pub credential_subject: CredentialSubject, 17 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 18 | #[serde_as(as = "OneOrMany<_>")] 19 | pub credential_status: Vec, 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub credential_schema: Option, 22 | #[serde(default, skip_serializing_if = "Option::is_none")] 23 | pub issuer: Option, 24 | #[serde(with = "time::serde::rfc3339::option")] 25 | #[serde(default, skip_serializing_if = "Option::is_none")] 26 | pub valid_from: Option, 27 | #[serde(with = "time::serde::rfc3339::option")] 28 | #[serde(default, skip_serializing_if = "Option::is_none")] 29 | pub valid_until: Option, 30 | } 31 | 32 | #[derive(Debug, Serialize, Deserialize)] 33 | #[serde(rename_all = "camelCase")] 34 | pub struct VPContent { 35 | #[serde(rename = "@context")] 36 | pub context: Vec, 37 | #[serde(rename = "type")] 38 | pub r#type: Vec, 39 | pub verifiable_credential: Vec, 40 | } 41 | 42 | #[derive(Debug, Serialize, Deserialize)] 43 | #[serde(rename_all = "camelCase")] 44 | pub struct VC { 45 | pub vc: VCContent, 46 | } 47 | 48 | #[derive(Debug, Serialize, Deserialize)] 49 | #[serde(rename_all = "camelCase")] 50 | pub struct VP { 51 | pub vp: VPContent, 52 | } 53 | 54 | #[derive(Debug, Serialize, Deserialize)] 55 | #[serde(untagged)] 56 | pub enum Issuer { 57 | Object(IssuerObject), 58 | Url(String), 59 | } 60 | 61 | impl Issuer { 62 | pub fn issuer(&self) -> &str { 63 | match self { 64 | Issuer::Object(object) => &object.id, 65 | Issuer::Url(s) => s, 66 | } 67 | } 68 | } 69 | 70 | #[derive(Debug, Serialize, Deserialize)] 71 | pub struct IssuerObject { 72 | id: String, 73 | #[serde(flatten)] 74 | rest: Option, 75 | } 76 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/mod.rs: -------------------------------------------------------------------------------- 1 | //! Credential format provider implementation. 2 | 3 | pub mod common; 4 | pub mod json_ld; 5 | pub mod json_ld_bbsplus; 6 | pub mod jwt; 7 | pub mod jwt_formatter; 8 | pub mod provider; 9 | pub mod sdjwt_formatter; 10 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/provider.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Arc}; 2 | 3 | use crate::credential_formatter::{provider::CredentialFormatterProvider, CredentialFormatter}; 4 | 5 | pub struct CredentialFormatterProviderImpl { 6 | formatters: HashMap>, 7 | } 8 | 9 | impl CredentialFormatterProviderImpl { 10 | pub fn new(formatters: HashMap>) -> Self { 11 | Self { formatters } 12 | } 13 | } 14 | 15 | impl CredentialFormatterProvider for CredentialFormatterProviderImpl { 16 | fn get_formatter(&self, format: &str) -> Option> { 17 | self.formatters.get(format).cloned() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/sdjwt_formatter/model.rs: -------------------------------------------------------------------------------- 1 | use crate::credential_formatter::model::{CredentialSchema, CredentialStatus}; 2 | use serde::{Deserialize, Serialize}; 3 | use serde_with::{serde_as, OneOrMany}; 4 | use time::OffsetDateTime; 5 | 6 | #[serde_as] 7 | #[derive(Debug, Serialize, Deserialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct VCContent { 10 | #[serde(rename = "@context")] 11 | pub context: Vec, 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub id: Option, 14 | pub r#type: Vec, 15 | pub credential_subject: SDCredentialSubject, 16 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 17 | #[serde_as(as = "OneOrMany<_>")] 18 | pub credential_status: Vec, 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub credential_schema: Option, 21 | #[serde(default, skip_serializing_if = "Option::is_none")] 22 | pub issuer: Option, 23 | #[serde(with = "time::serde::rfc3339::option")] 24 | #[serde(default, skip_serializing_if = "Option::is_none")] 25 | pub valid_from: Option, 26 | #[serde(with = "time::serde::rfc3339::option")] 27 | #[serde(default, skip_serializing_if = "Option::is_none")] 28 | pub valid_until: Option, 29 | } 30 | 31 | // TODO: remove the presentation models, since only JWT formatted presentations are used 32 | #[derive(Debug, Serialize, Deserialize)] 33 | #[serde(rename_all = "camelCase")] 34 | pub struct VPContent { 35 | #[serde(rename = "@context")] 36 | pub context: Vec, 37 | #[serde(rename = "type")] 38 | pub r#type: Vec, 39 | #[serde(rename = "_sd_jwt")] 40 | pub verifiable_credential: Vec, 41 | } 42 | 43 | #[derive(Debug, Serialize, Deserialize)] 44 | #[serde(rename_all = "camelCase")] 45 | pub struct Sdvc { 46 | pub vc: VCContent, 47 | /// Hash algorithm 48 | /// https://www.iana.org/assignments/named-information/named-information.xhtml 49 | #[serde(rename = "_sd_alg", default, skip_serializing_if = "Option::is_none")] 50 | pub hash_alg: Option, 51 | } 52 | 53 | #[derive(Debug, Serialize, Deserialize)] 54 | #[serde(rename_all = "camelCase")] 55 | pub struct Sdvp { 56 | pub vp: VPContent, 57 | } 58 | 59 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 60 | pub(crate) struct Disclosure { 61 | pub salt: String, 62 | pub key: String, 63 | pub value: serde_json::Value, 64 | pub original_disclosure: String, 65 | pub base64_encoded_disclosure: String, 66 | } 67 | 68 | #[derive(Debug, Serialize, Deserialize)] 69 | #[serde(rename_all = "camelCase")] 70 | pub struct SDCredentialSubject { 71 | #[serde(rename = "_sd")] 72 | pub claims: Vec, 73 | } 74 | 75 | pub struct DecomposedToken<'a> { 76 | pub jwt: &'a str, 77 | pub deserialized_disclosures: Vec, 78 | } 79 | 80 | #[derive(Debug, Serialize, Deserialize)] 81 | #[serde(untagged)] 82 | pub enum Issuer { 83 | Object(IssuerObject), 84 | Url(String), 85 | } 86 | 87 | impl Issuer { 88 | pub fn issuer(&self) -> &str { 89 | match self { 90 | Issuer::Object(object) => &object.id, 91 | Issuer::Url(s) => s, 92 | } 93 | } 94 | } 95 | 96 | #[derive(Debug, Serialize, Deserialize)] 97 | pub struct IssuerObject { 98 | id: String, 99 | #[serde(flatten)] 100 | rest: Option, 101 | } 102 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/imp/sdjwt_formatter/verifier.rs: -------------------------------------------------------------------------------- 1 | use one_crypto::Hasher; 2 | 3 | use crate::credential_formatter::error::FormatterError; 4 | 5 | use super::disclosures::{gather_hashes_from_disclosures, gather_hashes_from_hashed_claims}; 6 | use super::model::Disclosure; 7 | 8 | pub(super) fn verify_claims( 9 | hashed_claims: &[String], 10 | disclosures: &[Disclosure], 11 | hasher: &dyn Hasher, 12 | ) -> Result<(), FormatterError> { 13 | let mut hashes_used_by_disclosures = gather_hashes_from_disclosures(disclosures, hasher)?; 14 | 15 | let mut hashes_found_in_hashed_claims = 16 | gather_hashes_from_hashed_claims(hashed_claims, disclosures, hasher)?; 17 | 18 | hashes_used_by_disclosures.sort_unstable(); 19 | hashes_found_in_hashed_claims.sort_unstable(); 20 | 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /one-providers/src/credential_formatter/provider.rs: -------------------------------------------------------------------------------- 1 | //! Credential format provider. 2 | 3 | use std::sync::Arc; 4 | 5 | use super::CredentialFormatter; 6 | 7 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 8 | pub trait CredentialFormatterProvider: Send + Sync { 9 | fn get_formatter(&self, formatter_id: &str) -> Option>; 10 | } 11 | -------------------------------------------------------------------------------- /one-providers/src/did/error.rs: -------------------------------------------------------------------------------- 1 | //! Enumerates errors related to DID method provider. 2 | 3 | use thiserror::Error; 4 | 5 | use crate::caching_loader::CachingLoaderError; 6 | use crate::remote_entity_storage::RemoteEntityStorageError; 7 | 8 | #[derive(Debug, Error)] 9 | pub enum DidMethodError { 10 | #[error("Key algorithm not found")] 11 | KeyAlgorithmNotFound, 12 | #[error("Could not resolve: `{0}`")] 13 | ResolutionError(String), 14 | #[error("Could not create: `{0}`")] 15 | CouldNotCreate(String), 16 | #[error("Not supported")] 17 | NotSupported, 18 | } 19 | 20 | #[derive(Debug, Error)] 21 | pub enum DidMethodProviderError { 22 | #[error("Did method error: `{0}`")] 23 | DidMethod(#[from] DidMethodError), 24 | #[error("Failed to resolve did: `{0}`")] 25 | FailedToResolve(String), 26 | #[error("Missing did method name in did value")] 27 | MissingDidMethodNameInDidValue, 28 | #[error("Missing did provider: `{0}`")] 29 | MissingProvider(String), 30 | 31 | #[error("Other: `{0}`")] 32 | Other(String), 33 | 34 | #[error("Caching loader error: `{0}`")] 35 | CachingLoader(#[from] CachingLoaderError), 36 | #[error("JSON parse error: `{0}`")] 37 | JsonParse(#[from] serde_json::Error), 38 | #[error("Remote entity storage error: `{0}`")] 39 | RemoteEntityStorage(#[from] RemoteEntityStorageError), 40 | } 41 | -------------------------------------------------------------------------------- /one-providers/src/did/imp/common.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | 3 | use crate::{ 4 | common_models::{did::DidValue, OpenPublicKeyJwk}, 5 | did::model::DidVerificationMethod, 6 | }; 7 | 8 | pub const ENC: &str = "enc"; 9 | pub const SIG: &str = "sig"; 10 | 11 | pub fn jwk_context() -> serde_json::Value { 12 | json!([ 13 | "https://www.w3.org/ns/did/v1", 14 | "https://w3id.org/security/suites/jws-2020/v1", 15 | ]) 16 | } 17 | 18 | pub fn jwk_verification_method( 19 | id: String, 20 | did: &DidValue, 21 | jwk: OpenPublicKeyJwk, 22 | ) -> DidVerificationMethod { 23 | DidVerificationMethod { 24 | id, 25 | r#type: "JsonWebKey2020".into(), 26 | controller: did.to_string(), 27 | public_key_jwk: jwk, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /one-providers/src/did/imp/dto.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::{common_dto::PublicKeyJwkDTO, common_models::did::DidValue}; 4 | 5 | #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] 6 | #[serde(rename_all = "camelCase")] 7 | pub struct DidDocumentDTO { 8 | #[serde(rename = "@context")] 9 | pub context: serde_json::Value, 10 | pub id: DidValue, 11 | pub verification_method: Vec, 12 | #[serde(default, skip_serializing_if = "Option::is_none")] 13 | pub authentication: Option>, 14 | #[serde(default, skip_serializing_if = "Option::is_none")] 15 | pub assertion_method: Option>, 16 | #[serde(default, skip_serializing_if = "Option::is_none")] 17 | pub key_agreement: Option>, 18 | #[serde(default, skip_serializing_if = "Option::is_none")] 19 | pub capability_invocation: Option>, 20 | #[serde(default, skip_serializing_if = "Option::is_none")] 21 | pub capability_delegation: Option>, 22 | #[serde(flatten)] 23 | pub rest: serde_json::Value, 24 | } 25 | 26 | #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] 27 | #[serde(rename_all = "camelCase")] 28 | pub struct DidVerificationMethodDTO { 29 | pub id: String, 30 | pub r#type: String, 31 | pub controller: String, 32 | pub public_key_jwk: PublicKeyJwkDTO, 33 | } 34 | -------------------------------------------------------------------------------- /one-providers/src/did/imp/jwk/jwk_helpers.rs: -------------------------------------------------------------------------------- 1 | use ct_codecs::{Base64UrlSafeNoPadding, Decoder, Encoder}; 2 | 3 | use crate::{ 4 | common_dto::PublicKeyJwkDTO, 5 | common_models::did::DidValue, 6 | did::{ 7 | error::DidMethodError, 8 | imp::common::{jwk_context, jwk_verification_method, ENC, SIG}, 9 | model::DidDocument, 10 | }, 11 | }; 12 | 13 | pub fn extract_jwk(did: &DidValue) -> Result { 14 | let tail = did 15 | .as_str() 16 | .strip_prefix("did:jwk:") 17 | .ok_or_else(|| DidMethodError::ResolutionError("Invalid jwk did prefix".into()))?; 18 | 19 | let bytes = Base64UrlSafeNoPadding::decode_to_vec(tail, None).map_err(|err| { 20 | DidMethodError::ResolutionError(format!("Failed to decode base64url from jwk did: {err}")) 21 | })?; 22 | 23 | serde_json::from_slice(&bytes) 24 | .map_err(|err| DidMethodError::ResolutionError(format!("Failed to deserialize jwk: {err}"))) 25 | } 26 | 27 | pub fn generate_document(did: &DidValue, jwk: PublicKeyJwkDTO) -> DidDocument { 28 | let did_url = format!("{}#0", did); 29 | let urls = Some(vec![did_url.clone()]); 30 | let verification_method = jwk_verification_method(did_url, did, jwk.clone().into()); 31 | 32 | let mut template = DidDocument { 33 | context: jwk_context(), 34 | id: did.clone(), 35 | verification_method: vec![verification_method], 36 | authentication: None, 37 | assertion_method: None, 38 | key_agreement: None, 39 | capability_invocation: None, 40 | capability_delegation: None, 41 | rest: Default::default(), 42 | }; 43 | 44 | match jwk.get_use() { 45 | Some(val) if val == SIG => { 46 | template.authentication.clone_from(&urls); 47 | template.assertion_method.clone_from(&urls); 48 | template.capability_invocation.clone_from(&urls); 49 | template.capability_delegation = urls; 50 | } 51 | Some(val) if val == ENC => { 52 | template.key_agreement = urls; 53 | } 54 | _ => { 55 | template.authentication.clone_from(&urls); 56 | template.assertion_method.clone_from(&urls); 57 | template.key_agreement.clone_from(&urls); 58 | template.capability_invocation.clone_from(&urls); 59 | template.capability_delegation = urls; 60 | } 61 | } 62 | 63 | template 64 | } 65 | 66 | pub fn encode_to_did(jwk: &PublicKeyJwkDTO) -> Result { 67 | let jwk = serde_json::to_string(jwk) 68 | .map_err(|err| DidMethodError::CouldNotCreate(format!("Failed to serialize jwk: {err}")))?; 69 | 70 | let encoded = Base64UrlSafeNoPadding::encode_to_string(jwk).map_err(|err| { 71 | DidMethodError::CouldNotCreate(format!("Failed to base64 encode jwk: {err}")) 72 | })?; 73 | 74 | Ok(DidValue::from(format!("did:jwk:{encoded}"))) 75 | } 76 | -------------------------------------------------------------------------------- /one-providers/src/did/imp/jwk/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of did:jwk. 2 | 3 | use std::sync::Arc; 4 | 5 | use async_trait::async_trait; 6 | 7 | mod jwk_helpers; 8 | 9 | use crate::{ 10 | common_models::{ 11 | did::{DidId, DidValue}, 12 | key::OpenKey, 13 | }, 14 | did::{ 15 | error::DidMethodError, 16 | imp::jwk::jwk_helpers::{encode_to_did, extract_jwk, generate_document}, 17 | keys::Keys, 18 | model::{AmountOfKeys, DidCapabilities, DidDocument, Operation}, 19 | DidMethod, 20 | }, 21 | key_algorithm::provider::KeyAlgorithmProvider, 22 | }; 23 | 24 | pub struct JWKDidMethod { 25 | key_algorithm_provider: Arc, 26 | } 27 | 28 | impl JWKDidMethod { 29 | #[allow(clippy::new_without_default)] 30 | pub fn new(key_algorithm_provider: Arc) -> Self { 31 | Self { 32 | key_algorithm_provider, 33 | } 34 | } 35 | } 36 | 37 | #[async_trait] 38 | impl DidMethod for JWKDidMethod { 39 | async fn create( 40 | &self, 41 | _id: Option, 42 | _params: &Option, 43 | keys: Option>, 44 | ) -> Result { 45 | let keys = keys.ok_or(DidMethodError::ResolutionError("Missing keys".to_string()))?; 46 | 47 | let key = match keys.as_slice() { 48 | [key] => key, 49 | [] => return Err(DidMethodError::CouldNotCreate("Missing key".to_string())), 50 | _ => return Err(DidMethodError::CouldNotCreate("Too many keys".to_string())), 51 | }; 52 | 53 | let key_algorithm = self 54 | .key_algorithm_provider 55 | .get_key_algorithm(&key.key_type) 56 | .ok_or(DidMethodError::KeyAlgorithmNotFound)?; 57 | let jwk = key_algorithm 58 | .bytes_to_jwk(&key.public_key, None) 59 | .map_err(|e| DidMethodError::CouldNotCreate(e.to_string()))?; 60 | 61 | encode_to_did(&jwk.into()) 62 | } 63 | 64 | async fn resolve(&self, did: &DidValue) -> Result { 65 | let jwk = extract_jwk(did)?; 66 | Ok(generate_document(did, jwk)) 67 | } 68 | 69 | fn update(&self) -> Result<(), DidMethodError> { 70 | Err(DidMethodError::NotSupported) 71 | } 72 | 73 | fn can_be_deactivated(&self) -> bool { 74 | false 75 | } 76 | 77 | fn get_capabilities(&self) -> DidCapabilities { 78 | DidCapabilities { 79 | operations: vec![Operation::RESOLVE, Operation::CREATE], 80 | key_algorithms: vec![ 81 | "ES256".to_string(), 82 | "EDDSA".to_string(), 83 | "BBS_PLUS".to_string(), 84 | "DILITHIUM".to_string(), 85 | ], 86 | } 87 | } 88 | 89 | fn validate_keys(&self, keys: AmountOfKeys) -> bool { 90 | Keys::default().validate_keys(keys) 91 | } 92 | 93 | fn get_keys(&self) -> Option { 94 | Some(Keys::default()) 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod test; 100 | -------------------------------------------------------------------------------- /one-providers/src/did/imp/key/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of did:key. 2 | 3 | use std::sync::Arc; 4 | 5 | use async_trait::async_trait; 6 | 7 | use crate::{ 8 | common_models::{ 9 | did::{DidId, DidValue}, 10 | key::OpenKey, 11 | }, 12 | did::{ 13 | error::DidMethodError, 14 | imp::{ 15 | key_helpers, 16 | key_helpers::{decode_did, generate_document}, 17 | }, 18 | keys::Keys, 19 | model::{AmountOfKeys, DidCapabilities, DidDocument, Operation}, 20 | DidMethod, 21 | }, 22 | key_algorithm::provider::KeyAlgorithmProvider, 23 | }; 24 | 25 | pub struct KeyDidMethod { 26 | pub key_algorithm_provider: Arc, 27 | } 28 | 29 | impl KeyDidMethod { 30 | pub fn new(key_algorithm_provider: Arc) -> Self { 31 | Self { 32 | key_algorithm_provider, 33 | } 34 | } 35 | } 36 | 37 | #[async_trait] 38 | impl DidMethod for KeyDidMethod { 39 | async fn create( 40 | &self, 41 | _id: Option, 42 | _params: &Option, 43 | keys: Option>, 44 | ) -> Result { 45 | let keys = keys.ok_or(DidMethodError::ResolutionError("Missing keys".to_string()))?; 46 | 47 | let key = match keys.as_slice() { 48 | [key] => key, 49 | [] => return Err(DidMethodError::CouldNotCreate("Missing key".to_string())), 50 | _ => return Err(DidMethodError::CouldNotCreate("Too many keys".to_string())), 51 | }; 52 | 53 | let key_algorithm = self 54 | .key_algorithm_provider 55 | .get_key_algorithm(&key.key_type) 56 | .ok_or(DidMethodError::KeyAlgorithmNotFound)?; 57 | let multibase = key_algorithm 58 | .get_multibase(&key.public_key) 59 | .map_err(|e| DidMethodError::ResolutionError(e.to_string()))?; 60 | Ok(format!("did:key:{}", multibase).into()) 61 | } 62 | 63 | async fn resolve(&self, did_value: &DidValue) -> Result { 64 | let decoded = decode_did(did_value)?; 65 | let key_type = match decoded.type_ { 66 | key_helpers::DidKeyType::Eddsa => "EDDSA", 67 | key_helpers::DidKeyType::Ecdsa => "ES256", 68 | key_helpers::DidKeyType::Bbs => "BBS_PLUS", 69 | }; 70 | 71 | let jwk = self 72 | .key_algorithm_provider 73 | .get_key_algorithm(key_type) 74 | .ok_or(DidMethodError::KeyAlgorithmNotFound)? 75 | .bytes_to_jwk(&decoded.decoded_multibase, None) 76 | .map_err(|_| { 77 | DidMethodError::ResolutionError("Could not create jwk representation".to_string()) 78 | })?; 79 | 80 | generate_document(decoded, did_value, jwk) 81 | } 82 | 83 | fn update(&self) -> Result<(), DidMethodError> { 84 | Err(DidMethodError::NotSupported) 85 | } 86 | 87 | fn can_be_deactivated(&self) -> bool { 88 | false 89 | } 90 | 91 | fn get_capabilities(&self) -> DidCapabilities { 92 | DidCapabilities { 93 | operations: vec![Operation::RESOLVE, Operation::CREATE], 94 | key_algorithms: vec![ 95 | "ES256".to_string(), 96 | "EDDSA".to_string(), 97 | "BBS_PLUS".to_string(), 98 | ], 99 | } 100 | } 101 | 102 | fn validate_keys(&self, keys: AmountOfKeys) -> bool { 103 | Keys::default().validate_keys(keys) 104 | } 105 | 106 | fn get_keys(&self) -> Option { 107 | Some(Keys::default()) 108 | } 109 | } 110 | 111 | #[cfg(test)] 112 | mod test; 113 | -------------------------------------------------------------------------------- /one-providers/src/did/imp/key_helpers.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | common_models::{did::DidValue, OpenPublicKeyJwk}, 3 | did::{ 4 | error::DidMethodError, 5 | imp::common::{jwk_context, jwk_verification_method}, 6 | model::DidDocument, 7 | }, 8 | }; 9 | 10 | #[derive(Debug, Eq, PartialEq)] 11 | pub enum DidKeyType { 12 | Eddsa, 13 | Ecdsa, 14 | Bbs, 15 | } 16 | 17 | pub struct DecodedDidKey { 18 | pub multibase: String, 19 | pub decoded_multibase: Vec, 20 | pub type_: DidKeyType, 21 | } 22 | 23 | pub fn decode_did(did: &DidValue) -> Result { 24 | let tail = did 25 | .as_str() 26 | .strip_prefix("did:key:") 27 | .ok_or_else(|| DidMethodError::ResolutionError("Invalid did key prefix".into()))?; 28 | 29 | let type_ = if tail.starts_with("z6Mk") { 30 | DidKeyType::Eddsa 31 | } else if tail.starts_with("zDn") { 32 | DidKeyType::Ecdsa 33 | } else if tail.starts_with("zUC7") { 34 | DidKeyType::Bbs 35 | } else { 36 | return Err(DidMethodError::ResolutionError( 37 | "Unsupported key algorithm".to_string(), 38 | )); 39 | }; 40 | 41 | let decoded = bs58::decode(&tail[1..]).into_vec().map_err(|err| { 42 | DidMethodError::ResolutionError(format!("Invalid did key multibase suffix: {err}")) 43 | })?; 44 | 45 | // currently all supported key algorithms have a multicodec prefix 2 bytes long 46 | let decoded_without_multibase_prefix = decoded[2..].into(); 47 | 48 | Ok(DecodedDidKey { 49 | multibase: tail.into(), 50 | decoded_multibase: decoded_without_multibase_prefix, 51 | type_, 52 | }) 53 | } 54 | 55 | pub fn generate_document( 56 | decoded: DecodedDidKey, 57 | did: &DidValue, 58 | public_key_jwk: OpenPublicKeyJwk, 59 | ) -> Result { 60 | let verification_method = jwk_verification_method( 61 | format!("{}#{}", did, decoded.multibase), 62 | did, 63 | public_key_jwk, 64 | ); 65 | 66 | Ok(DidDocument { 67 | context: jwk_context(), 68 | id: did.clone(), 69 | authentication: Some(vec![verification_method.id.clone()]), 70 | assertion_method: Some(vec![verification_method.id.clone()]), 71 | capability_invocation: Some(vec![verification_method.id.clone()]), 72 | capability_delegation: Some(vec![verification_method.id.clone()]), 73 | key_agreement: Some(vec![verification_method.id.clone()]), 74 | verification_method: vec![verification_method], 75 | rest: Default::default(), 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /one-providers/src/did/imp/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of DID method provider. 2 | 3 | pub mod common; 4 | pub mod jwk; 5 | pub mod key; 6 | pub mod key_helpers; 7 | pub mod provider; 8 | pub mod resolver; 9 | pub mod universal; 10 | pub mod web; 11 | 12 | mod dto; 13 | mod mapper; 14 | -------------------------------------------------------------------------------- /one-providers/src/did/imp/provider.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Arc}; 2 | 3 | use crate::{ 4 | common_models::did::DidValue, 5 | did::{ 6 | error::DidMethodProviderError, 7 | imp::{dto::DidDocumentDTO, resolver::DidCachingLoader}, 8 | model::DidDocument, 9 | provider::DidMethodProvider, 10 | DidMethod, 11 | }, 12 | }; 13 | 14 | use super::resolver::DidResolver; 15 | 16 | pub struct DidMethodProviderImpl { 17 | caching_loader: DidCachingLoader, 18 | did_methods: HashMap>, 19 | resolver: Arc, 20 | } 21 | 22 | impl DidMethodProviderImpl { 23 | pub fn new( 24 | caching_loader: DidCachingLoader, 25 | did_methods: HashMap>, 26 | ) -> Self { 27 | let resolver = DidResolver { 28 | did_methods: did_methods.clone(), 29 | }; 30 | 31 | Self { 32 | caching_loader, 33 | did_methods, 34 | resolver: Arc::new(resolver), 35 | } 36 | } 37 | } 38 | 39 | #[async_trait::async_trait] 40 | impl DidMethodProvider for DidMethodProviderImpl { 41 | fn get_did_method(&self, did_method_id: &str) -> Option> { 42 | self.did_methods.get(did_method_id).cloned() 43 | } 44 | 45 | async fn resolve(&self, did: &DidValue) -> Result { 46 | let result = self 47 | .caching_loader 48 | .get(did.as_str(), self.resolver.clone()) 49 | .await?; 50 | let dto: DidDocumentDTO = serde_json::from_slice(&result)?; 51 | Ok(dto.into()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /one-providers/src/did/imp/resolver.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | 4 | use async_trait::async_trait; 5 | use time::OffsetDateTime; 6 | 7 | use crate::{ 8 | caching_loader::{CachingLoader, ResolveResult, Resolver}, 9 | common_models::did::DidValue, 10 | did::{error::DidMethodProviderError, imp::dto::DidDocumentDTO, DidMethod}, 11 | }; 12 | 13 | pub struct DidResolver { 14 | pub did_methods: HashMap>, 15 | } 16 | 17 | pub type DidCachingLoader = CachingLoader; 18 | 19 | #[async_trait] 20 | impl Resolver for DidResolver { 21 | type Error = DidMethodProviderError; 22 | 23 | async fn do_resolve( 24 | &self, 25 | did_value: &str, 26 | _previous: Option<&OffsetDateTime>, 27 | ) -> Result { 28 | let did_method_id = did_method_id_from_value(did_value)?; 29 | 30 | let method = self 31 | .did_methods 32 | .get(&did_method_id) 33 | .ok_or(DidMethodProviderError::MissingProvider(did_method_id))?; 34 | 35 | let did_value = DidValue::from(did_value.to_string()); 36 | let document = method.resolve(&did_value).await?; 37 | let dto: DidDocumentDTO = document.into(); 38 | 39 | Ok(ResolveResult::NewValue(serde_json::to_vec(&dto)?)) 40 | } 41 | } 42 | 43 | fn did_method_id_from_value(did_value: &str) -> Result { 44 | let mut parts = did_value.splitn(3, ':'); 45 | 46 | let did_method = parts 47 | .nth(1) 48 | .ok_or(DidMethodProviderError::MissingDidMethodNameInDidValue)?; 49 | Ok(did_method.to_uppercase()) 50 | } 51 | -------------------------------------------------------------------------------- /one-providers/src/did/imp/universal/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of DID Universal Resolver. 2 | 3 | use async_trait::async_trait; 4 | use serde::Deserialize; 5 | use std::sync::Arc; 6 | 7 | use crate::http_client::HttpClient; 8 | use crate::{ 9 | common_models::{ 10 | did::{DidId, DidValue}, 11 | key::OpenKey, 12 | }, 13 | did::{ 14 | error::DidMethodError, 15 | imp::dto::DidDocumentDTO, 16 | keys::Keys, 17 | model::{AmountOfKeys, DidCapabilities, DidDocument, Operation}, 18 | DidMethod, 19 | }, 20 | }; 21 | 22 | #[derive(Debug, Deserialize)] 23 | #[serde(rename_all = "camelCase")] 24 | struct ResolutionResponse { 25 | did_document: DidDocumentDTO, 26 | } 27 | 28 | #[derive(Debug)] 29 | pub struct Params { 30 | pub resolver_url: String, 31 | } 32 | 33 | pub struct UniversalDidMethod { 34 | pub params: Params, 35 | pub client: Arc, 36 | } 37 | 38 | impl UniversalDidMethod { 39 | pub fn new(params: Params, client: Arc) -> Self { 40 | Self { params, client } 41 | } 42 | } 43 | 44 | #[async_trait] 45 | impl DidMethod for UniversalDidMethod { 46 | async fn create( 47 | &self, 48 | _id: Option, 49 | _params: &Option, 50 | _keys: Option>, 51 | ) -> Result { 52 | Err(DidMethodError::NotSupported) 53 | } 54 | 55 | async fn resolve(&self, did_value: &DidValue) -> Result { 56 | let url = format!("{}/1.0/identifiers/{}", self.params.resolver_url, did_value); 57 | 58 | let response = self 59 | .client 60 | .get(&url) 61 | .send() 62 | .await 63 | .and_then(|resp| resp.error_for_status()) 64 | .map_err(|e| { 65 | DidMethodError::ResolutionError(format!("Could not fetch did document: {e}")) 66 | })?; 67 | 68 | Ok(response 69 | .json::() 70 | .map(|resp| resp.did_document) 71 | .map_err(|e| { 72 | DidMethodError::ResolutionError(format!("Could not deserialize response: {e}")) 73 | })? 74 | .into()) 75 | } 76 | 77 | fn update(&self) -> Result<(), DidMethodError> { 78 | Err(DidMethodError::NotSupported) 79 | } 80 | 81 | fn can_be_deactivated(&self) -> bool { 82 | false 83 | } 84 | 85 | fn get_capabilities(&self) -> DidCapabilities { 86 | DidCapabilities { 87 | operations: vec![Operation::RESOLVE], 88 | key_algorithms: vec![], 89 | } 90 | } 91 | 92 | fn validate_keys(&self, _keys: AmountOfKeys) -> bool { 93 | unimplemented!() 94 | } 95 | 96 | fn get_keys(&self) -> Option { 97 | None 98 | } 99 | } 100 | 101 | #[cfg(test)] 102 | mod test; 103 | -------------------------------------------------------------------------------- /one-providers/src/did/imp/universal/test.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::did::{ 4 | imp::universal::{Params, UniversalDidMethod}, 5 | model::Operation, 6 | DidMethod, 7 | }; 8 | use crate::http_client::MockHttpClient; 9 | 10 | #[test] 11 | fn test_get_capabilities() { 12 | let provider = UniversalDidMethod::new( 13 | Params { 14 | resolver_url: "".into(), 15 | }, 16 | Arc::new(MockHttpClient::new()), 17 | ); 18 | 19 | assert_eq!( 20 | vec![Operation::RESOLVE], 21 | provider.get_capabilities().operations 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /one-providers/src/did/keys.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of keys validation of DIDs. 2 | 3 | use crate::did::model::AmountOfKeys; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct MinMax { 7 | pub min: usize, 8 | pub max: usize, 9 | } 10 | 11 | impl MinMax { 12 | fn contains(&self, number: usize) -> bool { 13 | (&self.min..=&self.max).contains(&&number) 14 | } 15 | } 16 | 17 | impl Default for MinMax { 18 | fn default() -> Self { 19 | Self { min: 1, max: 1 } 20 | } 21 | } 22 | 23 | #[derive(Debug, Clone, Default)] 24 | pub struct Keys { 25 | pub global: MinMax<1>, 26 | pub authentication: MinMax<0>, 27 | pub assertion_method: MinMax<0>, 28 | pub key_agreement: MinMax<0>, 29 | pub capability_invocation: MinMax<0>, 30 | pub capability_delegation: MinMax<0>, 31 | } 32 | 33 | impl Keys { 34 | pub fn validate_keys(&self, keys: AmountOfKeys) -> bool { 35 | self.global.contains(keys.global) 36 | && self.authentication.contains(keys.authentication) 37 | && self.assertion_method.contains(keys.assertion_method) 38 | && self.key_agreement.contains(keys.key_agreement) 39 | && self 40 | .capability_invocation 41 | .contains(keys.capability_invocation) 42 | && self 43 | .capability_delegation 44 | .contains(keys.capability_delegation) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /one-providers/src/did/mod.rs: -------------------------------------------------------------------------------- 1 | //! Tools for DID method operations and metadata. 2 | //! 3 | //! Decentralized identifiers (DIDs) are a type of globally unique identifier 4 | //! for a resource. The DID is similar to a URL and can be resolved to a DID 5 | //! document which offers metadata about the identified resource. 6 | //! 7 | //! Use this module to perform all operations associated with the relevant 8 | //! DID method. 9 | 10 | use async_trait::async_trait; 11 | 12 | use crate::{ 13 | common_models::{ 14 | did::{DidId, DidValue}, 15 | key::OpenKey, 16 | }, 17 | did::{ 18 | error::DidMethodError, 19 | keys::Keys, 20 | model::{AmountOfKeys, DidCapabilities, DidDocument}, 21 | }, 22 | }; 23 | 24 | pub mod error; 25 | pub mod imp; 26 | pub mod keys; 27 | pub mod model; 28 | pub mod provider; 29 | 30 | /// Performs operations on DIDs and provides DID utilities. 31 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 32 | #[async_trait] 33 | pub trait DidMethod: Send + Sync { 34 | /// Creates a DID. 35 | async fn create( 36 | &self, 37 | id: Option, 38 | params: &Option, 39 | keys: Option>, 40 | ) -> Result; 41 | 42 | /// Resolve a DID to its DID document. 43 | async fn resolve(&self, did: &DidValue) -> Result; 44 | 45 | /// Deactivates a DID. Note that DID deactivation is permanent. 46 | fn update(&self) -> Result<(), DidMethodError>; 47 | 48 | /// Informs whether a DID can be deactivated or not. 49 | /// 50 | /// DID deactivation is useful if, for instance, a private key is leaked. 51 | fn can_be_deactivated(&self) -> bool; 52 | 53 | #[doc = include_str!("../../../docs/capabilities.md")] 54 | /// 55 | /// See the [API docs][dmc] for a complete list of credential format capabilities. 56 | /// 57 | /// [dmc]: https://docs.procivis.ch/api/resources/dids#did-method-capabilities 58 | fn get_capabilities(&self) -> DidCapabilities; 59 | 60 | /// Validates whether the number of keys assigned is supported by the DID method. 61 | /// 62 | /// Different DID methods support different numbers of keys for verification relationships. 63 | /// This method validates whether the method of the DID supports the keys associated with it. 64 | fn validate_keys(&self, keys: AmountOfKeys) -> bool; 65 | /// Returns the keys associated with a DID. 66 | fn get_keys(&self) -> Option; 67 | } 68 | -------------------------------------------------------------------------------- /one-providers/src/did/model.rs: -------------------------------------------------------------------------------- 1 | //! `struct`s and `enum`s for DID method provider. 2 | 3 | use crate::common_models::{did::DidValue, OpenPublicKeyJwk}; 4 | 5 | #[derive(Debug, PartialEq, Eq, Clone)] 6 | pub enum Operation { 7 | RESOLVE, 8 | CREATE, 9 | DEACTIVATE, 10 | } 11 | 12 | #[derive(Clone, Default)] 13 | pub struct DidCapabilities { 14 | pub operations: Vec, 15 | pub key_algorithms: Vec, 16 | } 17 | 18 | #[derive(Clone, Default)] 19 | pub struct DidKey { 20 | pub key_type: String, 21 | pub public_key: Vec, 22 | } 23 | 24 | #[derive(Clone, Debug, PartialEq, Eq)] 25 | pub struct DidDocument { 26 | pub context: serde_json::Value, 27 | pub id: DidValue, 28 | pub verification_method: Vec, 29 | pub authentication: Option>, 30 | pub assertion_method: Option>, 31 | pub key_agreement: Option>, 32 | pub capability_invocation: Option>, 33 | pub capability_delegation: Option>, 34 | 35 | pub rest: serde_json::Value, 36 | } 37 | 38 | #[derive(Clone, Debug, PartialEq, Eq)] 39 | pub struct DidVerificationMethod { 40 | pub id: String, 41 | pub r#type: String, 42 | pub controller: String, 43 | pub public_key_jwk: OpenPublicKeyJwk, 44 | } 45 | 46 | #[derive(Debug, Clone)] 47 | pub struct AmountOfKeys { 48 | pub global: usize, 49 | pub authentication: usize, 50 | pub assertion_method: usize, 51 | pub key_agreement: usize, 52 | pub capability_invocation: usize, 53 | pub capability_delegation: usize, 54 | } 55 | -------------------------------------------------------------------------------- /one-providers/src/did/provider.rs: -------------------------------------------------------------------------------- 1 | //! DID method provider. 2 | 3 | use std::sync::Arc; 4 | 5 | use crate::{ 6 | common_models::did::DidValue, 7 | did::{error::DidMethodProviderError, model::DidDocument, DidMethod}, 8 | }; 9 | 10 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 11 | #[async_trait::async_trait] 12 | pub trait DidMethodProvider: Send + Sync { 13 | fn get_did_method(&self, did_method_id: &str) -> Option>; 14 | 15 | async fn resolve(&self, did: &DidValue) -> Result; 16 | } 17 | -------------------------------------------------------------------------------- /one-providers/src/exchange_protocol/imp/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of exchange protocol provider. 2 | 3 | pub mod provider; 4 | -------------------------------------------------------------------------------- /one-providers/src/exchange_protocol/mod.rs: -------------------------------------------------------------------------------- 1 | //! Tools for exchange protocols, to govern the direct exchange of credentials. 2 | 3 | pub mod imp; 4 | pub mod openid4vc; 5 | pub mod provider; 6 | -------------------------------------------------------------------------------- /one-providers/src/exchange_protocol/openid4vc/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::common_models::credential::OpenCredentialStateEnum; 4 | use crate::common_models::proof::OpenProofStateEnum; 5 | use crate::revocation::error::RevocationError; 6 | 7 | #[derive(Clone, Debug, Error)] 8 | pub enum OpenID4VCIError { 9 | #[error("unsupported_grant_type")] 10 | UnsupportedGrantType, 11 | #[error("invalid_grant")] 12 | InvalidGrant, 13 | #[error("invalid_request")] 14 | InvalidRequest, 15 | #[error("invalid_token")] 16 | InvalidToken, 17 | #[error("invalid_or_missing_proof")] 18 | InvalidOrMissingProof, 19 | #[error("unsupported_credential_format")] 20 | UnsupportedCredentialFormat, 21 | #[error("unsupported_credential_type")] 22 | UnsupportedCredentialType, 23 | #[error("vp_formats_not_supported")] 24 | VPFormatsNotSupported, 25 | #[error("vc_formats_not_supported")] 26 | VCFormatsNotSupported, 27 | #[error("oidc runtime error: `{0}`")] 28 | RuntimeError(String), 29 | } 30 | 31 | #[derive(Debug, Error)] 32 | pub enum OpenID4VCError { 33 | #[error("Credential is revoked or suspended")] 34 | CredentialIsRevokedOrSuspended, 35 | #[error("Invalid credential state: `{state}`")] 36 | InvalidCredentialState { state: OpenCredentialStateEnum }, 37 | #[error("Invalid proof state: `{state}`")] 38 | InvalidProofState { state: OpenProofStateEnum }, 39 | #[error("Mapping error: `{0}`")] 40 | MappingError(String), 41 | #[error("Missing claim schemas")] 42 | MissingClaimSchemas, 43 | #[error("Missing revocation provider for type: `{0}`")] 44 | MissingRevocationProviderForType(String), 45 | #[error("Other: `{0}`")] 46 | Other(String), 47 | #[error("Validation error: `{0}`")] 48 | ValidationError(String), 49 | 50 | #[error("OpenID4VCI error: `{0}`")] 51 | OpenID4VCI(#[from] OpenID4VCIError), 52 | #[error("Revocation error: `{0}`")] 53 | Revocation(#[from] RevocationError), 54 | } 55 | -------------------------------------------------------------------------------- /one-providers/src/exchange_protocol/openid4vc/imp/mdoc.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, bail, Context}; 2 | use josekit::jwe::alg::ecdh_es::{EcdhEsJweAlgorithm, EcdhEsJweEncrypter}; 3 | use josekit::jwe::JweHeader; 4 | use josekit::jwk::Jwk; 5 | 6 | use crate::common_dto::PublicKeyJwkDTO; 7 | use crate::exchange_protocol::openid4vc::model::{ 8 | AuthorizationEncryptedResponseAlgorithm, 9 | AuthorizationEncryptedResponseContentEncryptionAlgorithm, JwePayload, OpenID4VPClientMetadata, 10 | OpenID4VPClientMetadataJwkDTO, 11 | }; 12 | use crate::key_algorithm::imp::eddsa::JwkEddsaExt; 13 | 14 | pub(crate) fn build_jwe( 15 | payload: JwePayload, 16 | client_metadata: OpenID4VPClientMetadata, 17 | mdoc_generated_nonce: &str, 18 | nonce: &str, // nonce from the authorization request object 19 | ) -> anyhow::Result { 20 | let payload = payload.try_into_json_base64_encode()?; 21 | 22 | let (header, encrypter) = build_ecdh_es_encrypter(client_metadata, mdoc_generated_nonce, nonce) 23 | .context("Failed to build ecdh-es encrypter")?; 24 | 25 | josekit::jwe::serialize_compact(payload.as_bytes(), &header, &encrypter) 26 | .context("JWE serialization failed") 27 | } 28 | 29 | fn build_ecdh_es_encrypter( 30 | verifier_metadata: OpenID4VPClientMetadata, 31 | mdoc_generated_nonce: &str, 32 | nonce: &str, 33 | ) -> anyhow::Result<(JweHeader, EcdhEsJweEncrypter)> { 34 | match verifier_metadata 35 | .authorization_encrypted_response_alg 36 | .as_ref() 37 | .zip(verifier_metadata.authorization_encrypted_response_enc.as_ref()) 38 | { 39 | None => return Err(anyhow!("Verifier must provide `authorization_encrypted_response_alg` and `authorization_encrypted_response_enc` parameters when for encrypted authorization response")), 40 | Some((AuthorizationEncryptedResponseAlgorithm::EcdhEs, AuthorizationEncryptedResponseContentEncryptionAlgorithm::A256GCM)) => {} 41 | } 42 | 43 | let key = key_from_verifier_metadata(verifier_metadata)?; 44 | 45 | let mut header = JweHeader::new(); 46 | header.set_key_id(key.key_id.to_string()); 47 | header.set_content_encryption( 48 | AuthorizationEncryptedResponseContentEncryptionAlgorithm::A256GCM.to_string(), 49 | ); 50 | // apu param 51 | header.set_agreement_partyuinfo(mdoc_generated_nonce); 52 | // apv param 53 | header.set_agreement_partyvinfo(nonce); 54 | 55 | let jwk = build_jwk(key)?; 56 | 57 | // the encrypter will set the correct "alg" and "epk" parameters when constructing the JWE 58 | let encrypter = EcdhEsJweAlgorithm::EcdhEs.encrypter_from_jwk(&jwk)?; 59 | 60 | Ok((header, encrypter)) 61 | } 62 | 63 | fn build_jwk(key: OpenID4VPClientMetadataJwkDTO) -> anyhow::Result { 64 | match key.jwk { 65 | PublicKeyJwkDTO::Rsa(_) | PublicKeyJwkDTO::Oct(_) | PublicKeyJwkDTO::Mlwe(_) => { 66 | bail!("Unsupported key type for MDOC proof verification, must be EC or OKP") 67 | } 68 | PublicKeyJwkDTO::Ec(ec) => { 69 | let mut jwk = Jwk::new("EC"); 70 | jwk.set_curve(ec.crv); 71 | jwk.set_parameter("x", Some(ec.x.into()))?; 72 | jwk.set_parameter("y", ec.y.map(Into::into))?; 73 | 74 | Ok(jwk) 75 | } 76 | PublicKeyJwkDTO::Okp(okp) => { 77 | let mut jwk = Jwk::new("OKP"); 78 | jwk.set_curve(okp.crv); 79 | jwk.set_parameter("x", Some(okp.x.into()))?; 80 | 81 | if let Some("Ed25519") = jwk.curve() { 82 | jwk = jwk 83 | .into_x25519() 84 | .context("Cannot convert Ed25519 into X25519")?; 85 | } 86 | 87 | Ok(jwk) 88 | } 89 | } 90 | } 91 | 92 | fn key_from_verifier_metadata( 93 | metadata: OpenID4VPClientMetadata, 94 | ) -> anyhow::Result { 95 | metadata 96 | .jwks 97 | .into_iter() 98 | .find(|key| { 99 | matches!(&key.jwk, 100 | PublicKeyJwkDTO::Ec(key) | PublicKeyJwkDTO::Okp(key) if key.r#use.as_deref() == Some("enc") 101 | ) 102 | }) 103 | .ok_or(anyhow!( 104 | "verifier metadata is missing EC or OKP key with `enc=use` parameter", 105 | )) 106 | } 107 | -------------------------------------------------------------------------------- /one-providers/src/exchange_protocol/openid4vc/proof_formatter.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use time::OffsetDateTime; 3 | 4 | use crate::{ 5 | common_models::did::OpenDid, 6 | credential_formatter::{ 7 | error::FormatterError, 8 | imp::jwt::{model::JWTPayload, Jwt}, 9 | model::AuthenticationFn, 10 | }, 11 | }; 12 | 13 | #[derive(Debug, Serialize, Deserialize)] 14 | pub struct ProofContent { 15 | #[serde(rename = "aud")] 16 | pub audience: String, 17 | } 18 | 19 | pub struct OpenID4VCIProofJWTFormatter {} 20 | 21 | impl OpenID4VCIProofJWTFormatter { 22 | pub async fn verify_proof(content: &str) -> Result, FormatterError> { 23 | Jwt::build_from_token(content, None).await 24 | } 25 | pub async fn format_proof( 26 | issuer_url: String, 27 | holder_did: &OpenDid, 28 | algorithm: String, 29 | auth_fn: AuthenticationFn, 30 | ) -> Result { 31 | let content = ProofContent { 32 | audience: issuer_url, 33 | }; 34 | 35 | let payload = JWTPayload { 36 | issuer: None, 37 | jwt_id: None, 38 | subject: None, 39 | custom: content, 40 | issued_at: Some(OffsetDateTime::now_utc()), 41 | expires_at: None, 42 | invalid_before: None, 43 | nonce: None, 44 | }; 45 | 46 | let jwt = Jwt::new( 47 | "openid4vci-proof+jwt".to_owned(), 48 | algorithm, 49 | Some(holder_did.did.to_string()), 50 | payload, 51 | ); 52 | 53 | jwt.tokenize(auth_fn).await 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /one-providers/src/exchange_protocol/provider.rs: -------------------------------------------------------------------------------- 1 | //! Exchange protocol provider. 2 | 3 | use std::sync::Arc; 4 | 5 | use url::Url; 6 | 7 | use super::openid4vc::ExchangeProtocolImpl; 8 | 9 | pub trait ExchangeProtocol: 10 | ExchangeProtocolImpl< 11 | VCInteractionContext = serde_json::Value, 12 | VPInteractionContext = serde_json::Value, 13 | > 14 | { 15 | } 16 | 17 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 18 | #[async_trait::async_trait] 19 | pub trait ExchangeProtocolProvider: Send + Sync { 20 | fn get_protocol(&self, protocol_id: &str) -> Option>; 21 | fn detect_protocol(&self, url: &Url) -> Option>; 22 | } 23 | -------------------------------------------------------------------------------- /one-providers/src/http_client/imp/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod reqwest_client; 2 | -------------------------------------------------------------------------------- /one-providers/src/http_client/imp/reqwest_client.rs: -------------------------------------------------------------------------------- 1 | use crate::http_client::{ 2 | Error, Headers, HttpClient, Method, RequestBuilder, Response, StatusCode, 3 | }; 4 | use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; 5 | use std::collections::HashMap; 6 | use std::str::FromStr; 7 | use std::sync::Arc; 8 | 9 | #[derive(Clone)] 10 | pub struct ReqwestClient { 11 | pub client: reqwest::Client, 12 | } 13 | 14 | impl ReqwestClient { 15 | pub fn new(client: reqwest::Client) -> Self { 16 | Self { client } 17 | } 18 | } 19 | 20 | impl Default for ReqwestClient { 21 | fn default() -> Self { 22 | Self::new(Default::default()) 23 | } 24 | } 25 | 26 | #[async_trait::async_trait] 27 | impl HttpClient for ReqwestClient { 28 | fn get(&self, url: &str) -> RequestBuilder { 29 | RequestBuilder::new(Arc::new(self.clone()), Method::Get, url) 30 | } 31 | 32 | fn post(&self, url: &str) -> RequestBuilder { 33 | RequestBuilder::new(Arc::new(self.clone()), Method::Post, url) 34 | } 35 | 36 | async fn send( 37 | &self, 38 | url: &str, 39 | body: Option>, 40 | headers: Option, 41 | method: Method, 42 | ) -> Result { 43 | let mut builder = match method { 44 | Method::Get => self.client.get(url), 45 | Method::Post => self.client.post(url), 46 | }; 47 | 48 | if let Some(headers) = headers { 49 | builder = builder.headers(to_header_map(headers)?); 50 | } 51 | if let Some(body) = body { 52 | builder = builder.body(body); 53 | } 54 | 55 | do_send(builder).await 56 | } 57 | } 58 | 59 | fn to_header_map(headers: HashMap) -> Result { 60 | headers 61 | .into_iter() 62 | .map(|(k, v)| { 63 | let name = HeaderName::from_str(k.as_str()).map_err(|e| Error::Other(e.to_string()))?; 64 | let value = 65 | HeaderValue::from_str(v.as_str()).map_err(|e| Error::Other(e.to_string()))?; 66 | 67 | Ok((name, value)) 68 | }) 69 | .collect::>() 70 | } 71 | 72 | async fn do_send(builder: reqwest::RequestBuilder) -> Result { 73 | let response = builder 74 | .send() 75 | .await 76 | .map_err(|e| Error::HttpError(e.to_string()))?; 77 | 78 | let headers = response 79 | .headers() 80 | .iter() 81 | .map(|(k, v)| { 82 | let value = v.to_str().map_err(|e| Error::Other(e.to_string()))?; 83 | 84 | Ok((k.to_string(), value.to_string())) 85 | }) 86 | .collect::>()?; 87 | let status_code = response.status().as_u16(); 88 | let body = response 89 | .bytes() 90 | .await 91 | .map_err(|e| Error::HttpError(e.to_string()))?; 92 | 93 | Ok(Response { 94 | body: body.to_vec(), 95 | headers, 96 | status: StatusCode(status_code), 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /one-providers/src/key_algorithm/error.rs: -------------------------------------------------------------------------------- 1 | //! Enumerates errors related to the key algorithm provider. 2 | 3 | use thiserror::Error; 4 | 5 | use one_crypto::SignerError; 6 | 7 | #[derive(Debug, Error)] 8 | pub enum KeyAlgorithmProviderError { 9 | #[error("Cannot find key algorithm `{0}`")] 10 | MissingAlgorithmImplementation(String), 11 | #[error("Cannot find signer `{0}`")] 12 | MissingSignerImplementation(String), 13 | } 14 | 15 | #[derive(Debug, Error)] 16 | pub enum KeyAlgorithmError { 17 | #[error("Key algorithm error: `{0}`")] 18 | Failed(String), 19 | #[error("Signer error: `{0}`")] 20 | SignerError(#[from] SignerError), 21 | #[error("Not supported for type: `{0}`")] 22 | NotSupported(String), 23 | } 24 | -------------------------------------------------------------------------------- /one-providers/src/key_algorithm/imp/bbs/mod.rs: -------------------------------------------------------------------------------- 1 | use ct_codecs::{Base64UrlSafeNoPadding, Decoder, Encoder}; 2 | use pairing_crypto::bbs::ciphersuites::bls12_381::KeyPair; 3 | 4 | use one_crypto::imp::utilities::get_rng; 5 | 6 | use crate::{ 7 | common_models::{OpenPublicKeyJwk, OpenPublicKeyJwkEllipticData}, 8 | key_algorithm::{error::KeyAlgorithmError, model::GeneratedKey, KeyAlgorithm}, 9 | }; 10 | 11 | pub struct BBS; 12 | 13 | #[cfg(test)] 14 | mod test; 15 | 16 | impl KeyAlgorithm for BBS { 17 | fn get_signer_algorithm_id(&self) -> String { 18 | "BBS".to_string() 19 | } 20 | 21 | fn get_multibase(&self, public_key: &[u8]) -> Result { 22 | let codec = &[0xeb, 0x01]; 23 | let data = [codec, public_key].concat(); 24 | Ok(format!("z{}", bs58::encode(data).into_string())) 25 | } 26 | 27 | fn generate_key_pair(&self) -> GeneratedKey { 28 | // There is not much to break hence default on failure should be good enough. 29 | let key_pair = KeyPair::random(&mut get_rng(), b"").unwrap_or_default(); 30 | let private = key_pair.secret_key.to_bytes().to_vec(); 31 | let public = key_pair.public_key.to_octets().to_vec(); 32 | GeneratedKey { public, private } 33 | } 34 | 35 | fn bytes_to_jwk( 36 | &self, 37 | bytes: &[u8], 38 | r#use: Option, 39 | ) -> Result { 40 | let public = blstrs::G2Affine::from_compressed( 41 | bytes 42 | .try_into() 43 | .map_err(|_| KeyAlgorithmError::Failed("Couldn't parse public key".to_string()))?, 44 | ); 45 | let public = if public.is_some().into() { 46 | public.unwrap() 47 | } else { 48 | return Err(KeyAlgorithmError::Failed( 49 | "Couldn't parse public key".to_string(), 50 | )); 51 | }; 52 | let pk_uncompressed = public.to_uncompressed(); 53 | let x = &pk_uncompressed[..96]; 54 | let y = &pk_uncompressed[96..]; 55 | Ok(OpenPublicKeyJwk::Okp(OpenPublicKeyJwkEllipticData { 56 | r#use, 57 | crv: "Bls12381G2".to_string(), 58 | x: Base64UrlSafeNoPadding::encode_to_string(x) 59 | .map_err(|e| KeyAlgorithmError::Failed(e.to_string()))?, 60 | y: Some( 61 | Base64UrlSafeNoPadding::encode_to_string(y) 62 | .map_err(|e| KeyAlgorithmError::Failed(e.to_string()))?, 63 | ), 64 | })) 65 | } 66 | 67 | fn jwk_to_bytes(&self, jwk: &OpenPublicKeyJwk) -> Result, KeyAlgorithmError> { 68 | if let OpenPublicKeyJwk::Okp(data) = jwk { 69 | let x = Base64UrlSafeNoPadding::decode_to_vec(&data.x, None) 70 | .map_err(|e| KeyAlgorithmError::Failed(e.to_string()))?; 71 | let y = Base64UrlSafeNoPadding::decode_to_vec( 72 | data.y 73 | .as_ref() 74 | .ok_or(KeyAlgorithmError::Failed("Y is missing".to_string()))?, 75 | None, 76 | ) 77 | .map_err(|e| KeyAlgorithmError::Failed(e.to_string()))?; 78 | 79 | let uncompressed: [u8; 192] = [x, y] 80 | .concat() 81 | .try_into() 82 | .map_err(|_| KeyAlgorithmError::Failed("Couldn't parse public key".to_string()))?; 83 | let public = blstrs::G2Affine::from_uncompressed(&uncompressed); 84 | let public = if public.is_some().into() { 85 | public.unwrap() 86 | } else { 87 | return Err(KeyAlgorithmError::Failed( 88 | "Couldn't parse public key".to_string(), 89 | )); 90 | }; 91 | 92 | Ok(public.to_compressed().to_vec()) 93 | } else { 94 | Err(KeyAlgorithmError::Failed("invalid kty".to_string())) 95 | } 96 | } 97 | 98 | fn public_key_from_der(&self, _public_key_der: &[u8]) -> Result, KeyAlgorithmError> { 99 | unimplemented!() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /one-providers/src/key_algorithm/imp/bbs/test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::common_models::{OpenPublicKeyJwk, OpenPublicKeyJwkEllipticData}; 3 | 4 | struct TestData { 5 | jwk: OpenPublicKeyJwk, 6 | serialized: Vec, 7 | } 8 | fn get_test_key() -> TestData { 9 | TestData { 10 | jwk: OpenPublicKeyJwk::Okp(OpenPublicKeyJwkEllipticData { 11 | r#use: None, 12 | crv: "Bls12381G2".to_owned(), 13 | x: "Ajs8lstTgoTgXMF6QXdyh3m8k2ixxURGYLMaYylVK_x0F8HhE8zk0YWiGV3CHwpQEa2sH4PBZLaYCn8se-1clmCORDsKxbbw3Js_Alu4OmkV9gmbJsy1YF2rt7Vxzs6S".to_owned(), 14 | y: Some("BVkkrVEib-P_FMPHNtqxJymP3pV-H8fCdvPkoWInpFfM9tViyqD8JAmwDf64zU2hBV_vvCQ632ScAooEExXuz1IeQH9D2o-uY_dAjZ37YHuRMEyzh8Tq-90JHQvicOqx".to_owned()), 15 | }), 16 | serialized: vec![ 17 | 130, 59, 60, 150, 203, 83, 130, 132, 224, 92, 193, 122, 65, 119, 114, 135, 121, 188, 18 | 147, 104, 177, 197, 68, 70, 96, 179, 26, 99, 41, 85, 43, 252, 116, 23, 193, 225, 19, 19 | 204, 228, 209, 133, 162, 25, 93, 194, 31, 10, 80, 17, 173, 172, 31, 131, 193, 100, 182, 20 | 152, 10, 127, 44, 123, 237, 92, 150, 96, 142, 68, 59, 10, 197, 182, 240, 220, 155, 63, 21 | 2, 91, 184, 58, 105, 21, 246, 9, 155, 38, 204, 181, 96, 93, 171, 183, 181, 113, 206, 22 | 206, 146 23 | ] 24 | } 25 | } 26 | 27 | #[test] 28 | fn test_jwk_to_bytes() { 29 | let TestData { jwk, serialized } = get_test_key(); 30 | let alg = BBS; 31 | assert_eq!(serialized, alg.jwk_to_bytes(&jwk).unwrap()) 32 | } 33 | 34 | #[test] 35 | fn test_bytes_to_jwk() { 36 | let TestData { jwk, serialized } = get_test_key(); 37 | let alg = BBS; 38 | assert_eq!(alg.bytes_to_jwk(&serialized, None).unwrap(), jwk) 39 | } 40 | -------------------------------------------------------------------------------- /one-providers/src/key_algorithm/imp/eddsa/test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_jwk_to_bytes() { 5 | let jwk = OpenPublicKeyJwk::Okp(OpenPublicKeyJwkEllipticData { 6 | r#use: None, 7 | crv: "Ed25519".to_owned(), 8 | x: "m7AE5UQdjLuCOnZHB1gCFfo2uvhM6W_4xFmpJK02r7s".to_owned(), 9 | y: None, 10 | }); 11 | 12 | let alg = Eddsa::new(EddsaParams { 13 | algorithm: Algorithm::Ed25519, 14 | }); 15 | 16 | assert_eq!( 17 | vec![ 18 | 155, 176, 4, 229, 68, 29, 140, 187, 130, 58, 118, 71, 7, 88, 2, 21, 250, 54, 186, 248, 19 | 76, 233, 111, 248, 196, 89, 169, 36, 173, 54, 175, 187, 20 | ], 21 | alg.jwk_to_bytes(&jwk).unwrap() 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /one-providers/src/key_algorithm/imp/es256/test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_jwk_to_bytes() { 5 | let jwk = OpenPublicKeyJwk::Ec(OpenPublicKeyJwkEllipticData { 6 | r#use: None, 7 | crv: "P-256".to_owned(), 8 | x: "CQKO9r8IF7mEYhZImiOoLqw70WYLAohqT3JkomZW3x4".to_owned(), 9 | y: Some("khCene-e-_GAeE8N-aWUUucY_dVGRGCqpQmVhPwDHUM".to_owned()), 10 | }); 11 | 12 | let alg = Es256::new(Es256Params { 13 | algorithm: Algorithm::Es256, 14 | }); 15 | 16 | assert_eq!( 17 | vec![ 18 | 3, 9, 2, 142, 246, 191, 8, 23, 185, 132, 98, 22, 72, 154, 35, 168, 46, 172, 59, 209, 19 | 102, 11, 2, 136, 106, 79, 114, 100, 162, 102, 86, 223, 30 20 | ], 21 | alg.jwk_to_bytes(&jwk).unwrap() 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /one-providers/src/key_algorithm/imp/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementations of key algorithms. 2 | 3 | pub mod provider; 4 | 5 | pub mod bbs; 6 | pub mod eddsa; 7 | pub mod es256; 8 | -------------------------------------------------------------------------------- /one-providers/src/key_algorithm/imp/provider.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Arc}; 2 | 3 | use crate::{ 4 | common_models::OpenPublicKeyJwk, 5 | key_algorithm::{ 6 | error::KeyAlgorithmProviderError, model::ParsedPublicKeyJwk, 7 | provider::KeyAlgorithmProvider, KeyAlgorithm, 8 | }, 9 | }; 10 | 11 | use one_crypto::{CryptoProvider, Signer}; 12 | 13 | pub struct KeyAlgorithmProviderImpl { 14 | algorithms: HashMap>, 15 | crypto: Arc, 16 | } 17 | 18 | impl KeyAlgorithmProviderImpl { 19 | pub fn new( 20 | algorithms: HashMap>, 21 | crypto: Arc, 22 | ) -> Self { 23 | Self { algorithms, crypto } 24 | } 25 | } 26 | 27 | impl KeyAlgorithmProvider for KeyAlgorithmProviderImpl { 28 | fn get_key_algorithm(&self, algorithm: &str) -> Option> { 29 | self.algorithms.get(algorithm).cloned() 30 | } 31 | 32 | fn get_signer(&self, algorithm: &str) -> Result, KeyAlgorithmProviderError> { 33 | let key_algorithm = self.get_key_algorithm(algorithm).ok_or( 34 | KeyAlgorithmProviderError::MissingAlgorithmImplementation(algorithm.to_owned()), 35 | )?; 36 | let signer_algorithm = key_algorithm.get_signer_algorithm_id(); 37 | self.crypto 38 | .get_signer(&signer_algorithm) 39 | .map_err(|e| KeyAlgorithmProviderError::MissingSignerImplementation(e.to_string())) 40 | } 41 | 42 | fn parse_jwk( 43 | &self, 44 | key: &OpenPublicKeyJwk, 45 | ) -> Result { 46 | for algorithm in self.algorithms.values() { 47 | if let Ok(public_key_bytes) = algorithm.jwk_to_bytes(key) { 48 | return Ok(ParsedPublicKeyJwk { 49 | public_key_bytes, 50 | signer_algorithm_id: algorithm.get_signer_algorithm_id(), 51 | }); 52 | } 53 | } 54 | 55 | Err(KeyAlgorithmProviderError::MissingAlgorithmImplementation( 56 | "None of the algorithms supports given key".to_string(), 57 | )) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /one-providers/src/key_algorithm/mod.rs: -------------------------------------------------------------------------------- 1 | //! Tools for key algorithm representations and finding signer IDs. 2 | //! 3 | //! This module provides utilities for generating cryptographic key pairs, 4 | //! finding signer IDs and getting public and private keys in different representations. 5 | //! 6 | //! The [key storage][ks] module acts as a wrapper for this module, enabling key 7 | //! generation and usage safely via key reference. 8 | //! 9 | //! [ks]: ../../one_providers/key_storage/index.html 10 | 11 | use error::KeyAlgorithmError; 12 | use model::GeneratedKey; 13 | use zeroize::Zeroizing; 14 | 15 | use crate::common_models::OpenPublicKeyJwk; 16 | 17 | pub mod error; 18 | pub mod imp; 19 | pub mod model; 20 | pub mod provider; 21 | 22 | /// Find signer IDs and convert key representations. 23 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 24 | pub trait KeyAlgorithm: Send + Sync { 25 | /// Finds related crypto signer ID. 26 | fn get_signer_algorithm_id(&self) -> String; 27 | 28 | /// Returns base58-btc representation of a public key. 29 | fn get_multibase(&self, public_key: &[u8]) -> Result; 30 | 31 | /// Generates a new in-memory key-pair. 32 | fn generate_key_pair(&self) -> GeneratedKey; 33 | 34 | /// Converts public key bytes to JWK. 35 | fn bytes_to_jwk( 36 | &self, 37 | bytes: &[u8], 38 | r#use: Option, 39 | ) -> Result; 40 | 41 | /// Converts JWK to key bytes. 42 | fn jwk_to_bytes(&self, jwk: &OpenPublicKeyJwk) -> Result, KeyAlgorithmError>; 43 | 44 | /// Converts a private key to JWK. **Use carefully.** 45 | /// 46 | /// This can be useful for certain APIs that require JWK. Not supported by all 47 | /// storage methods. 48 | /// 49 | /// Zeroize is used to ensure memory erasure. 50 | fn private_key_as_jwk( 51 | &self, 52 | _secret_key: Zeroizing>, 53 | ) -> Result, KeyAlgorithmError> { 54 | Err(KeyAlgorithmError::NotSupported( 55 | std::any::type_name::().to_string(), 56 | )) 57 | } 58 | 59 | /// Converts a public key from DER to bytes. 60 | fn public_key_from_der(&self, public_key_der: &[u8]) -> Result, KeyAlgorithmError>; 61 | } 62 | -------------------------------------------------------------------------------- /one-providers/src/key_algorithm/model.rs: -------------------------------------------------------------------------------- 1 | //! `struct`s for key algorithm provider. 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct ParsedPublicKeyJwk { 5 | pub public_key_bytes: Vec, 6 | pub signer_algorithm_id: String, 7 | } 8 | 9 | #[derive(Clone, Debug, PartialEq, Eq)] 10 | pub struct GeneratedKey { 11 | pub public: Vec, 12 | pub private: Vec, 13 | } 14 | -------------------------------------------------------------------------------- /one-providers/src/key_algorithm/provider.rs: -------------------------------------------------------------------------------- 1 | //! Key algorithm provider. 2 | 3 | use std::sync::Arc; 4 | 5 | use one_crypto::Signer; 6 | 7 | use super::{error::KeyAlgorithmProviderError, model::ParsedPublicKeyJwk, KeyAlgorithm}; 8 | use crate::common_models::OpenPublicKeyJwk; 9 | 10 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 11 | pub trait KeyAlgorithmProvider: Send + Sync { 12 | fn get_key_algorithm(&self, algorithm: &str) -> Option>; 13 | 14 | fn get_signer(&self, algorithm: &str) -> Result, KeyAlgorithmProviderError>; 15 | 16 | fn parse_jwk( 17 | &self, 18 | key: &OpenPublicKeyJwk, 19 | ) -> Result; 20 | } 21 | -------------------------------------------------------------------------------- /one-providers/src/key_storage/error.rs: -------------------------------------------------------------------------------- 1 | //! Enumerates errors related to key storage provider. 2 | 3 | use thiserror::Error; 4 | 5 | use one_crypto::SignerError; 6 | 7 | use crate::key_algorithm::error::KeyAlgorithmError; 8 | 9 | #[derive(Debug, Error)] 10 | pub enum KeyStorageProviderError { 11 | #[error("Invalid key storage `{0}`")] 12 | InvalidKeyStorage(String), 13 | } 14 | 15 | #[derive(Debug, Error)] 16 | pub enum KeyStorageError { 17 | #[error("Key algorithm error: `{0}`")] 18 | Failed(String), 19 | #[error("Signer error: `{0}`")] 20 | SignerError(#[from] SignerError), 21 | #[error("Not supported for type: `{0}`")] 22 | NotSupported(String), 23 | #[error("Unsupported key type: {key_type}")] 24 | UnsupportedKeyType { key_type: String }, 25 | #[error("Mapping error: `{0}`")] 26 | MappingError(String), 27 | #[error("Transport error: `{0}`")] 28 | Transport(anyhow::Error), 29 | #[error("Key algorithm error: `{0}`")] 30 | KeyAlgorithmError(#[from] KeyAlgorithmError), 31 | #[error("Password decryption failure")] 32 | PasswordDecryptionFailure, 33 | #[error("Invalid key algorithm `{0}`")] 34 | InvalidKeyAlgorithm(String), 35 | } 36 | -------------------------------------------------------------------------------- /one-providers/src/key_storage/imp/azure_vault/dto.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Serialize)] 6 | pub(super) struct AzureHsmGenerateKeyRequest { 7 | #[serde(rename = "kty")] 8 | pub key_type: String, 9 | #[serde(rename = "crv")] 10 | pub curve_name: String, 11 | #[serde(rename = "key_ops")] 12 | pub key_operations: Vec, 13 | } 14 | 15 | #[allow(dead_code)] 16 | #[derive(Deserialize)] 17 | pub(super) struct AzureHsmGenerateKeyResponse { 18 | pub key: AzureHsmGenerateKeyResponseKey, 19 | pub attributes: AzureHsmGenerateKeyResponseAttributes, 20 | pub tags: Option>, 21 | } 22 | 23 | #[allow(dead_code)] 24 | #[derive(Deserialize)] 25 | pub(super) struct AzureHsmGenerateKeyResponseKey { 26 | #[serde(rename = "kid")] 27 | pub key_id: String, 28 | #[serde(rename = "kty")] 29 | pub key_type: String, 30 | #[serde(rename = "key_ops")] 31 | pub key_operations: Vec, 32 | #[serde(rename = "x")] 33 | pub x_component: String, 34 | #[serde(rename = "y")] 35 | pub y_component: String, 36 | 37 | #[serde(flatten)] 38 | pub extra: HashMap, 39 | } 40 | 41 | #[allow(dead_code)] 42 | #[derive(Deserialize)] 43 | #[serde(rename_all = "camelCase")] 44 | pub(super) struct AzureHsmGenerateKeyResponseAttributes { 45 | pub enabled: bool, 46 | pub created: u64, 47 | pub updated: u64, 48 | pub recovery_level: String, 49 | } 50 | 51 | #[derive(Serialize)] 52 | pub(super) struct AzureHsmGetTokenRequest { 53 | pub client_id: String, 54 | pub client_secret: String, 55 | pub grant_type: String, 56 | pub scope: String, 57 | } 58 | 59 | #[derive(Deserialize, Serialize)] 60 | pub(super) struct AzureHsmGetTokenResponse { 61 | pub token_type: String, 62 | pub expires_in: i64, 63 | pub access_token: String, 64 | } 65 | 66 | #[derive(Debug, Serialize)] 67 | pub(super) struct AzureHsmSignRequest { 68 | #[serde(rename = "alg")] 69 | pub algorithm: String, 70 | pub value: String, 71 | } 72 | 73 | #[allow(dead_code)] 74 | #[derive(Deserialize)] 75 | pub(super) struct AzureHsmSignResponse { 76 | #[serde(rename = "kid")] 77 | pub key_id: String, 78 | pub value: String, 79 | } 80 | -------------------------------------------------------------------------------- /one-providers/src/key_storage/imp/azure_vault/mapper.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::anyhow; 4 | use ct_codecs::{Base64UrlSafeNoPadding, Decoder}; 5 | 6 | use one_crypto::{CryptoProvider, SignerError}; 7 | 8 | use super::dto::{ 9 | AzureHsmGenerateKeyRequest, AzureHsmGenerateKeyResponseKey, AzureHsmGetTokenRequest, 10 | AzureHsmSignRequest, 11 | }; 12 | use crate::key_storage::error::KeyStorageError; 13 | 14 | pub(super) fn create_get_token_request( 15 | client_id: &str, 16 | client_secret: &str, 17 | ) -> AzureHsmGetTokenRequest { 18 | AzureHsmGetTokenRequest { 19 | client_id: client_id.to_string(), 20 | client_secret: client_secret.to_string(), 21 | grant_type: "client_credentials".to_string(), 22 | scope: "https://vault.azure.net/.default".to_string(), 23 | } 24 | } 25 | 26 | pub(super) fn create_generate_key_request() -> AzureHsmGenerateKeyRequest { 27 | AzureHsmGenerateKeyRequest { 28 | key_type: "EC-HSM".to_string(), 29 | curve_name: "P-256".to_string(), 30 | key_operations: vec!["sign".to_string(), "verify".to_string()], 31 | } 32 | } 33 | 34 | pub(super) fn create_sign_request( 35 | value: &[u8], 36 | crypto: Arc, 37 | ) -> Result { 38 | let hasher = crypto 39 | .get_hasher("sha-256") 40 | .map_err(SignerError::CryptoError)?; 41 | let value = hasher 42 | .hash_base64(value) 43 | .map_err(|e| SignerError::CouldNotSign(e.to_string()))?; 44 | 45 | Ok(AzureHsmSignRequest { 46 | algorithm: "ES256".to_string(), 47 | value, 48 | }) 49 | } 50 | 51 | pub(super) fn public_key_from_components( 52 | key: &AzureHsmGenerateKeyResponseKey, 53 | ) -> Result, KeyStorageError> { 54 | let mut x_component = 55 | Base64UrlSafeNoPadding::decode_to_vec(&key.x_component, None).map_err(|e| { 56 | KeyStorageError::Failed( 57 | anyhow!(e) 58 | .context("could not decode x component") 59 | .to_string(), 60 | ) 61 | })?; 62 | let mut y_component = 63 | Base64UrlSafeNoPadding::decode_to_vec(&key.y_component, None).map_err(|e| { 64 | KeyStorageError::Failed( 65 | anyhow!(e) 66 | .context("could not decode y component") 67 | .to_string(), 68 | ) 69 | })?; 70 | 71 | const PUBLIC_KEY_FULL: u8 = 0x04; 72 | let mut result = vec![PUBLIC_KEY_FULL]; 73 | result.append(&mut x_component); 74 | result.append(&mut y_component); 75 | 76 | Ok(result) 77 | } 78 | -------------------------------------------------------------------------------- /one-providers/src/key_storage/imp/internal/test.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use time::OffsetDateTime; 4 | use uuid::Uuid; 5 | 6 | use super::InternalKeyProvider; 7 | use crate::{ 8 | common_models::key::OpenKey, 9 | key_algorithm::{model::GeneratedKey, provider::MockKeyAlgorithmProvider, MockKeyAlgorithm}, 10 | key_storage::{imp::internal::Params, KeyStorage}, 11 | }; 12 | 13 | use one_crypto::MockSigner; 14 | 15 | #[tokio::test] 16 | async fn test_internal_generate() { 17 | let mut mock_key_algorithm = MockKeyAlgorithm::default(); 18 | mock_key_algorithm 19 | .expect_generate_key_pair() 20 | .times(1) 21 | .returning(|| GeneratedKey { 22 | public: vec![1], 23 | private: vec![1, 2, 3], 24 | }); 25 | 26 | let arc = Arc::new(mock_key_algorithm); 27 | 28 | let mut mock_key_algorithm_provider = MockKeyAlgorithmProvider::default(); 29 | mock_key_algorithm_provider 30 | .expect_get_key_algorithm() 31 | .times(1) 32 | .returning(move |_| Some(arc.clone())); 33 | 34 | let provider = InternalKeyProvider::new( 35 | Arc::new(mock_key_algorithm_provider), 36 | Params { encryption: None }, 37 | ); 38 | 39 | let result = provider.generate(None, "").await.unwrap(); 40 | assert_eq!(3, result.key_reference.len()); 41 | } 42 | 43 | #[tokio::test] 44 | async fn test_internal_generate_with_encryption() { 45 | let mut mock_key_algorithm = MockKeyAlgorithm::default(); 46 | mock_key_algorithm 47 | .expect_generate_key_pair() 48 | .times(1) 49 | .returning(|| GeneratedKey { 50 | public: vec![1], 51 | private: vec![1, 2, 3], 52 | }); 53 | 54 | let arc = Arc::new(mock_key_algorithm); 55 | 56 | let mut mock_key_algorithm_provider = MockKeyAlgorithmProvider::default(); 57 | mock_key_algorithm_provider 58 | .expect_get_key_algorithm() 59 | .times(1) 60 | .returning(move |_| Some(arc.clone())); 61 | 62 | let provider = InternalKeyProvider::new( 63 | Arc::new(mock_key_algorithm_provider), 64 | Params { 65 | encryption: Some("password".to_string()), 66 | }, 67 | ); 68 | 69 | let result = provider.generate(None, "").await.unwrap(); 70 | assert_eq!(result.key_reference.len(), 39); 71 | } 72 | 73 | #[tokio::test] 74 | async fn test_internal_sign_with_encryption() { 75 | let expected_signed_response = vec![1u8]; 76 | 77 | let mut mock_key_algorithm = MockKeyAlgorithm::default(); 78 | mock_key_algorithm 79 | .expect_generate_key_pair() 80 | .times(1) 81 | .returning(|| GeneratedKey { 82 | public: vec![1], 83 | private: vec![1, 2, 3], 84 | }); 85 | let mut mock_signer = MockSigner::default(); 86 | mock_signer 87 | .expect_sign() 88 | .times(1) 89 | .returning(move |_, _, _| Ok(expected_signed_response.clone())); 90 | 91 | let arc_key_algorithm = Arc::new(mock_key_algorithm); 92 | let arc_signer = Arc::new(mock_signer); 93 | 94 | let mut mock_key_algorithm_provider = MockKeyAlgorithmProvider::default(); 95 | mock_key_algorithm_provider 96 | .expect_get_key_algorithm() 97 | .times(1) 98 | .returning(move |_| Some(arc_key_algorithm.clone())); 99 | mock_key_algorithm_provider 100 | .expect_get_signer() 101 | .times(1) 102 | .returning(move |_| Ok(arc_signer.clone())); 103 | 104 | let provider = InternalKeyProvider::new( 105 | Arc::new(mock_key_algorithm_provider), 106 | Params { 107 | encryption: Some("password".to_string()), 108 | }, 109 | ); 110 | 111 | let generated_key = provider.generate(None, "").await.unwrap(); 112 | 113 | let key = OpenKey { 114 | id: Uuid::new_v4().into(), 115 | created_date: OffsetDateTime::now_utc(), 116 | last_modified: OffsetDateTime::now_utc(), 117 | public_key: generated_key.public_key, 118 | name: "".to_string(), 119 | key_reference: generated_key.key_reference, 120 | storage_type: "".to_string(), 121 | key_type: "".to_string(), 122 | organisation: None, 123 | }; 124 | 125 | provider.sign(&key, "message".as_bytes()).await.unwrap(); 126 | } 127 | -------------------------------------------------------------------------------- /one-providers/src/key_storage/imp/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of key storage provider. 2 | 3 | pub mod azure_vault; 4 | pub mod internal; 5 | pub mod provider; 6 | -------------------------------------------------------------------------------- /one-providers/src/key_storage/imp/provider.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Arc}; 2 | 3 | use one_crypto::SignerError; 4 | 5 | use crate::{ 6 | common_models::key::OpenKey, 7 | credential_formatter::model::SignatureProvider, 8 | key_storage::{provider::KeyProvider, KeyStorage}, 9 | }; 10 | 11 | pub struct KeyProviderImpl { 12 | storages: HashMap>, 13 | } 14 | 15 | impl KeyProviderImpl { 16 | pub fn new(storages: HashMap>) -> Self { 17 | Self { storages } 18 | } 19 | } 20 | 21 | impl KeyProvider for KeyProviderImpl { 22 | fn get_key_storage(&self, format: &str) -> Option> { 23 | self.storages.get(format).cloned() 24 | } 25 | } 26 | 27 | pub(crate) struct SignatureProviderImpl { 28 | pub storage: Arc, 29 | pub key: OpenKey, 30 | pub jwk_key_id: Option, 31 | } 32 | 33 | #[async_trait::async_trait] 34 | impl SignatureProvider for SignatureProviderImpl { 35 | async fn sign(&self, message: &[u8]) -> Result, SignerError> { 36 | self.storage.sign(&self.key, message).await 37 | } 38 | 39 | fn get_key_id(&self) -> Option { 40 | self.jwk_key_id.to_owned() 41 | } 42 | 43 | fn get_public_key(&self) -> Vec { 44 | self.key.public_key.to_owned() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /one-providers/src/key_storage/mod.rs: -------------------------------------------------------------------------------- 1 | //! Tools for safe generation and usage of keys. 2 | //! 3 | //! This module wraps the key algorithm provider, providing a middle 4 | //! layer for interacting with private keys via key references. Generate 5 | //! key pairs and sign via key reference. 6 | //! 7 | //! [ks]: ../../one_providers/key_algorithm/index.html 8 | 9 | use async_trait; 10 | use zeroize::Zeroizing; 11 | 12 | use one_crypto::SignerError; 13 | 14 | use crate::common_models::key::{KeyId, OpenKey}; 15 | 16 | pub mod error; 17 | pub mod imp; 18 | pub mod model; 19 | pub mod provider; 20 | 21 | /// Generate key pairs and sign via key references. 22 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 23 | #[async_trait::async_trait] 24 | pub trait KeyStorage: Send + Sync { 25 | /// Generates a key pair and returns the key reference. Does not expose the private key. 26 | async fn generate( 27 | &self, 28 | key_id: Option, 29 | key_type: &str, 30 | ) -> Result; 31 | 32 | /// Sign with a private key via the key reference. 33 | async fn sign(&self, key: &OpenKey, message: &[u8]) -> Result, SignerError>; 34 | 35 | /// Converts a private key to JWK (thus exposing it). 36 | /// 37 | /// **Use carefully.** 38 | /// 39 | /// May not be implemented for some storage providers (e.g. Azure Key Vault). 40 | fn secret_key_as_jwk(&self, key: &OpenKey) 41 | -> Result, error::KeyStorageError>; 42 | 43 | #[doc = include_str!("../../../docs/capabilities.md")] 44 | /// 45 | /// See the [API docs][ksc] for a complete list of credential format capabilities. 46 | /// 47 | /// [ksc]: https://docs.procivis.ch/api/resources/keys#key-storage-capabilities 48 | fn get_capabilities(&self) -> model::KeyStorageCapabilities; 49 | } 50 | -------------------------------------------------------------------------------- /one-providers/src/key_storage/model.rs: -------------------------------------------------------------------------------- 1 | //! `struct`s and `enum`s for key storage provider. 2 | 3 | #[derive(Clone, Debug, Eq, PartialEq)] 4 | pub enum KeySecurity { 5 | Hardware, 6 | Software, 7 | } 8 | 9 | #[derive(Clone, Debug, Default)] 10 | pub struct KeyStorageCapabilities { 11 | pub features: Vec, 12 | pub algorithms: Vec, 13 | pub security: Vec, 14 | } 15 | 16 | pub struct StorageGeneratedKey { 17 | pub public_key: Vec, 18 | pub key_reference: Vec, 19 | } 20 | -------------------------------------------------------------------------------- /one-providers/src/key_storage/provider.rs: -------------------------------------------------------------------------------- 1 | //! Key storage provider. 2 | 3 | use std::sync::Arc; 4 | 5 | use super::{error::KeyStorageProviderError, imp::provider::SignatureProviderImpl, KeyStorage}; 6 | use crate::{common_models::key::OpenKey, credential_formatter::model::AuthenticationFn}; 7 | 8 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 9 | pub trait KeyProvider: Send + Sync { 10 | fn get_key_storage(&self, key_provider_id: &str) -> Option>; 11 | 12 | fn get_signature_provider( 13 | &self, 14 | key: &OpenKey, 15 | jwk_key_id: Option, 16 | ) -> Result { 17 | let storage = self.get_key_storage(&key.storage_type).ok_or( 18 | KeyStorageProviderError::InvalidKeyStorage(key.storage_type.clone()), 19 | )?; 20 | 21 | Ok(Box::new(SignatureProviderImpl { 22 | key: key.to_owned(), 23 | storage, 24 | jwk_key_id, 25 | })) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /one-providers/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Implementations of functionality and traits for interfaces. 2 | //! 3 | //! Where the [Core][cor] provides developer APIs for quick implementations 4 | //! of supported functionalities through [services][serv], the providers 5 | //! implement the actual functioning (i.e. generating key pairs, signing, 6 | //! verifying, exchanging credentials, etc.). 7 | //! 8 | //! ## Provider structure 9 | //! 10 | //! Each provider is contained within a module below and structured in a 11 | //! similar pattern, each containing some subset of: 12 | //! 13 | //! - `imp`: Implements the functionality. Within this directory, each 14 | //! technology (e.g. each credential format, each key algorithm, each 15 | //! DID method) is implemented within its own directory. 16 | //! - `error`: Enumerates errors of the provider. 17 | //! - `mod`: Provides the traits used in the implementation. 18 | //! - `model`: `struct`s and `enum`s of the provider. 19 | //! - `provider`: The provider implementation. 20 | //! 21 | //! Some providers may include additional elements of implementation. 22 | //! 23 | //! There are additional modules in the **Providers** crate containing, 24 | //! for example, shared resources such as DTOs as well as utilities such 25 | //! as bitstring list handling and key verification of DIDs. 26 | //! 27 | //! ## Extension 28 | //! 29 | //! The library can be extended by adding additional implementations in the 30 | //! relevant provider. 31 | //! 32 | //! [cor]: ..//one_open_core/index.html 33 | //! [serv]: ..//one_open_core/service/index.html 34 | 35 | #![doc(html_favicon_url = "https://docs.procivis.ch/img/favicon.svg")] 36 | 37 | pub mod common_dto; 38 | pub mod common_models; 39 | pub mod credential_formatter; 40 | pub mod did; 41 | pub mod http_client; 42 | pub mod key_algorithm; 43 | pub mod key_storage; 44 | pub mod revocation; 45 | pub mod util; 46 | 47 | pub mod common_mappers; 48 | 49 | pub mod caching_loader; 50 | pub mod exchange_protocol; 51 | pub mod remote_entity_storage; 52 | -------------------------------------------------------------------------------- /one-providers/src/remote_entity_storage/in_memory.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | 4 | use async_trait::async_trait; 5 | use tokio::sync::Mutex; 6 | 7 | use crate::remote_entity_storage::{ 8 | RemoteEntity, RemoteEntityStorage, RemoteEntityStorageError, RemoteEntityType, 9 | }; 10 | 11 | pub struct InMemoryStorage { 12 | storage: Arc>>, 13 | } 14 | 15 | impl InMemoryStorage { 16 | pub fn new(storage: HashMap) -> Self { 17 | Self { 18 | storage: Arc::new(Mutex::new(storage)), 19 | } 20 | } 21 | } 22 | 23 | #[async_trait] 24 | impl RemoteEntityStorage for InMemoryStorage { 25 | async fn delete_oldest( 26 | &self, 27 | entity_type: RemoteEntityType, 28 | ) -> Result<(), RemoteEntityStorageError> { 29 | let mut hash_map_handle = self.storage.lock().await; 30 | 31 | if let Some(key) = hash_map_handle 32 | .iter() 33 | .filter(|(_, entity)| entity.entity_type == entity_type) 34 | .min() 35 | .map(|(k, _)| k.to_owned()) 36 | { 37 | hash_map_handle.remove(&key); 38 | } 39 | 40 | Ok(()) 41 | } 42 | 43 | async fn get_by_key( 44 | &self, 45 | key: &str, 46 | ) -> Result, RemoteEntityStorageError> { 47 | let hash_map_handle = self.storage.lock().await; 48 | 49 | Ok(hash_map_handle.get(key).map(|v| v.to_owned())) 50 | } 51 | 52 | async fn get_storage_size( 53 | &self, 54 | entity_type: RemoteEntityType, 55 | ) -> Result { 56 | let hash_map_handle = self.storage.lock().await; 57 | 58 | Ok(hash_map_handle 59 | .iter() 60 | .filter(|(_, entity)| entity.entity_type == entity_type) 61 | .count()) 62 | } 63 | 64 | async fn insert(&self, request: RemoteEntity) -> Result<(), RemoteEntityStorageError> { 65 | let mut hash_map_handle = self.storage.lock().await; 66 | 67 | hash_map_handle.insert(request.key.to_owned(), request); 68 | 69 | Ok(()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /one-providers/src/remote_entity_storage/mod.rs: -------------------------------------------------------------------------------- 1 | //! Storage provider for caching. 2 | //! 3 | //! In-memory storage is supported natively; this can be extended with another storage 4 | //! provider. 5 | //! 6 | //! # Caching 7 | //! 8 | //! Some entities are cached. This is helpful for mobile devices with intermittent 9 | //! internet connectivity, as well as for system optimization with entities not 10 | //! expected to change (i.e. JSON-LD contexts). See the [caching][cac] 11 | //! docs for more information on cached entities. 12 | //! 13 | //! [cac]: https://docs.procivis.ch/api/caching 14 | 15 | use std::cmp::Ordering; 16 | 17 | use thiserror::Error; 18 | use time::OffsetDateTime; 19 | 20 | pub mod in_memory; 21 | 22 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 23 | #[async_trait::async_trait] 24 | pub trait RemoteEntityStorage: Send + Sync { 25 | async fn delete_oldest( 26 | &self, 27 | entity_type: RemoteEntityType, 28 | ) -> Result<(), RemoteEntityStorageError>; 29 | 30 | async fn get_by_key(&self, key: &str) 31 | -> Result, RemoteEntityStorageError>; 32 | 33 | async fn get_storage_size( 34 | &self, 35 | entity_type: RemoteEntityType, 36 | ) -> Result; 37 | 38 | async fn insert(&self, request: RemoteEntity) -> Result<(), RemoteEntityStorageError>; 39 | } 40 | 41 | #[derive(Clone, Debug, Eq, PartialEq)] 42 | pub struct RemoteEntity { 43 | pub last_modified: OffsetDateTime, 44 | 45 | pub entity_type: RemoteEntityType, 46 | pub key: String, 47 | pub value: Vec, 48 | 49 | pub hit_counter: u32, 50 | } 51 | 52 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 53 | pub enum RemoteEntityType { 54 | DidDocument, 55 | JsonLdContext, 56 | StatusListCredential, 57 | } 58 | 59 | #[derive(Clone, Error, Debug)] 60 | pub enum RemoteEntityStorageError { 61 | #[error("Delete error: `{0}`")] 62 | Delete(String), 63 | #[error("Get by key error: `{0}`")] 64 | GetByKey(String), 65 | #[error("Get storage size: `{0}`")] 66 | GetStorageSize(String), 67 | #[error("Insert: `{0}`")] 68 | Insert(String), 69 | } 70 | 71 | impl PartialOrd for RemoteEntity { 72 | fn partial_cmp(&self, other: &Self) -> Option { 73 | Some(self.cmp(other)) 74 | } 75 | } 76 | 77 | impl Ord for RemoteEntity { 78 | fn cmp(&self, other: &Self) -> Ordering { 79 | match self.hit_counter.cmp(&other.hit_counter) { 80 | Ordering::Equal => self.last_modified.cmp(&other.last_modified), 81 | value => value, 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /one-providers/src/revocation/error.rs: -------------------------------------------------------------------------------- 1 | //! Enumerates errors related to revocation method provider. 2 | 3 | use thiserror::Error; 4 | 5 | use crate::{ 6 | caching_loader::CachingLoaderError, 7 | common_models::{ 8 | credential::{CredentialId, OpenCredentialStateEnum}, 9 | did::{DidId, KeyRole}, 10 | }, 11 | credential_formatter::error::FormatterError, 12 | did::error::DidMethodProviderError, 13 | http_client, 14 | key_storage::error::KeyStorageProviderError, 15 | remote_entity_storage::RemoteEntityStorageError, 16 | revocation::imp::bitstring_status_list::util::BitstringError, 17 | }; 18 | 19 | #[derive(Debug, Error)] 20 | pub enum RevocationError { 21 | #[error("Credential not found: `{0}`")] 22 | CredentialNotFound(CredentialId), 23 | #[error("Formatter not found: `{0}`")] 24 | FormatterNotFound(String), 25 | #[error("Invalid credential state: `{0}`")] 26 | InvalidCredentialState(OpenCredentialStateEnum), 27 | #[error("Key with role `{0}` not found`")] 28 | KeyWithRoleNotFound(KeyRole), 29 | #[error("Mapping error: `{0}`")] 30 | MappingError(String), 31 | #[error("Missing credential index `{0}` on revocation list for did id `{1}`")] 32 | MissingCredentialIndexOnRevocationList(CredentialId, DidId), 33 | #[error("Operation not supported: `{0}`")] 34 | OperationNotSupported(String), 35 | #[error("Validation error: `{0}`")] 36 | ValidationError(String), 37 | 38 | #[error("Bitstring error: `{0}`")] 39 | BitstringError(#[from] BitstringError), 40 | #[error("Caching loader error: `{0}`")] 41 | CachingLoader(#[from] CachingLoaderError), 42 | #[error("Did method provider error: `{0}`")] 43 | DidMethodProviderError(#[from] DidMethodProviderError), 44 | #[error("Formatter error: `{0}`")] 45 | FormatterError(#[from] FormatterError), 46 | #[error("Key storage provider error: `{0}`")] 47 | KeyStorageProviderError(#[from] KeyStorageProviderError), 48 | #[error("Remote entity storage error: `{0}`")] 49 | RemoteEntityStorageError(#[from] RemoteEntityStorageError), 50 | 51 | #[error("From UTF-8 error: `{0}`")] 52 | FromUtf8Error(#[from] std::string::FromUtf8Error), 53 | #[error("HTTP client error: `{0}`")] 54 | HttpClientError(#[from] http_client::Error), 55 | #[error("JSON error: `{0}`")] 56 | JsonError(#[from] serde_json::Error), 57 | } 58 | -------------------------------------------------------------------------------- /one-providers/src/revocation/imp/bitstring_status_list/model.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use strum::Display; 3 | use time::OffsetDateTime; 4 | 5 | use crate::{ 6 | common_models::did::DidValue, 7 | credential_formatter::model::Context, 8 | revocation::{ 9 | imp::bitstring_status_list::jwt_formatter::{from_timestamp_opt, into_timestamp_opt}, 10 | model::RevocationListId, 11 | }, 12 | }; 13 | 14 | #[derive(Debug, Serialize, Deserialize)] 15 | pub enum ContentType { 16 | VerifiableCredential, 17 | BitstringStatusListCredential, 18 | } 19 | 20 | #[derive(Debug, Serialize, Deserialize)] 21 | #[serde(rename_all = "camelCase")] 22 | pub struct VCContent { 23 | #[serde(rename = "@context")] 24 | pub context: Vec, 25 | pub id: Option, 26 | pub r#type: Vec, 27 | pub issuer: DidValue, 28 | // we keep this field for backwards compatibility with VCDM v1.1 29 | #[serde( 30 | serialize_with = "into_timestamp_opt", 31 | deserialize_with = "from_timestamp_opt" 32 | )] 33 | #[serde(default, skip_serializing_if = "Option::is_none")] 34 | pub issued: Option, 35 | #[serde( 36 | serialize_with = "into_timestamp_opt", 37 | deserialize_with = "from_timestamp_opt" 38 | )] 39 | #[serde(default, skip_serializing_if = "Option::is_none")] 40 | pub valid_from: Option, 41 | #[serde( 42 | serialize_with = "into_timestamp_opt", 43 | deserialize_with = "from_timestamp_opt" 44 | )] 45 | #[serde(default, skip_serializing_if = "Option::is_none")] 46 | pub valid_until: Option, 47 | pub credential_subject: CredentialSubject, 48 | } 49 | 50 | #[derive(Debug, Serialize, Deserialize)] 51 | pub enum SubjectType { 52 | BitstringStatusList, 53 | } 54 | 55 | #[derive(Debug, Serialize, Deserialize)] 56 | #[serde(rename_all = "camelCase")] 57 | pub struct CredentialSubject { 58 | pub id: String, 59 | pub r#type: SubjectType, 60 | pub status_purpose: StatusPurpose, 61 | pub encoded_list: String, 62 | } 63 | 64 | #[derive(Debug, Serialize, Deserialize)] 65 | #[serde(rename_all = "camelCase")] 66 | pub struct VC { 67 | pub vc: VCContent, 68 | } 69 | 70 | #[derive(Debug, Serialize, Deserialize)] 71 | #[serde(rename_all = "camelCase")] 72 | pub enum StatusPurpose { 73 | Revocation, 74 | Suspension, 75 | } 76 | 77 | #[derive(Debug, Serialize, Deserialize)] 78 | pub struct RevocationUpdateData { 79 | pub id: RevocationListId, 80 | pub value: Vec, 81 | } 82 | 83 | #[derive(Clone, Debug, Eq, PartialEq, Serialize)] 84 | pub struct RevocationList { 85 | pub id: RevocationListId, 86 | pub created_date: OffsetDateTime, 87 | pub last_modified: OffsetDateTime, 88 | pub credentials: Vec, 89 | pub purpose: RevocationListPurpose, 90 | } 91 | 92 | #[derive(Clone, Debug, Eq, PartialEq, Display, Serialize)] 93 | pub enum RevocationListPurpose { 94 | Revocation, 95 | Suspension, 96 | } 97 | -------------------------------------------------------------------------------- /one-providers/src/revocation/imp/bitstring_status_list/resolver.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use std::sync::Arc; 3 | use time::OffsetDateTime; 4 | 5 | use crate::{ 6 | caching_loader::{CachingLoader, ResolveResult, Resolver}, 7 | http_client::HttpClient, 8 | revocation::error::RevocationError, 9 | }; 10 | 11 | pub struct StatusListResolver { 12 | pub client: Arc, 13 | } 14 | 15 | pub type StatusListCachingLoader = CachingLoader; 16 | 17 | #[async_trait] 18 | impl Resolver for StatusListResolver { 19 | type Error = RevocationError; 20 | 21 | async fn do_resolve( 22 | &self, 23 | url: &str, 24 | _previous: Option<&OffsetDateTime>, 25 | ) -> Result { 26 | Ok(ResolveResult::NewValue( 27 | self.client.get(url).send().await?.error_for_status()?.body, 28 | )) 29 | } 30 | } 31 | 32 | impl StatusListResolver { 33 | pub fn new(client: Arc) -> Self { 34 | Self { client } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /one-providers/src/revocation/imp/bitstring_status_list/util.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for Bitstring Status List. 2 | 3 | use std::io::{Read, Write}; 4 | 5 | use bit_vec::BitVec; 6 | use ct_codecs::{Base64UrlSafeNoPadding, Decoder, Encoder}; 7 | use flate2::{bufread::GzDecoder, write::GzEncoder}; 8 | use thiserror::Error; 9 | 10 | #[derive(Debug, Error)] 11 | pub enum BitstringError { 12 | #[error("Bitstring encoding error: `{0}`")] 13 | Base64Encoding(ct_codecs::Error), 14 | #[error("Bitstring decoding error: `{0}`")] 15 | Base64Decoding(ct_codecs::Error), 16 | #[error("Bitstring compression error: `{0}`")] 17 | Compression(std::io::Error), 18 | #[error("Bitstring decompression error: `{0}`")] 19 | Decompression(std::io::Error), 20 | #[error("Index `{index}` out of bounds for provided bitstring")] 21 | IndexOutOfBounds { index: usize }, 22 | } 23 | 24 | pub fn extract_bitstring_index(input: String, index: usize) -> Result { 25 | let compressed = Base64UrlSafeNoPadding::decode_to_vec(input, None) 26 | .map_err(BitstringError::Base64Decoding)?; 27 | 28 | let bytes = gzip_decompress(compressed, index).map_err(|err| { 29 | if err.kind() == std::io::ErrorKind::UnexpectedEof { 30 | BitstringError::IndexOutOfBounds { index } 31 | } else { 32 | BitstringError::Decompression(err) 33 | } 34 | })?; 35 | 36 | let bits = BitVec::from_bytes(&bytes); 37 | Ok(bits[index]) 38 | } 39 | 40 | pub(super) fn generate_bitstring(input: Vec) -> Result { 41 | let size = calculate_bitstring_size(input.len()); 42 | let mut bits = BitVec::from_elem(size, false); 43 | input.into_iter().enumerate().for_each(|(index, state)| { 44 | if state { 45 | bits.set(index, true) 46 | } 47 | }); 48 | 49 | let bytes = bits.to_bytes(); 50 | let compressed = gzip_compress(bytes).map_err(BitstringError::Compression)?; 51 | 52 | Base64UrlSafeNoPadding::encode_to_string(compressed).map_err(BitstringError::Base64Encoding) 53 | } 54 | 55 | fn gzip_compress(input: Vec) -> Result, std::io::Error> { 56 | let mut encoder = GzEncoder::new(Vec::new(), Default::default()); 57 | encoder.write_all(&input)?; 58 | encoder.finish() 59 | } 60 | 61 | fn gzip_decompress(input: Vec, index: usize) -> Result, std::io::Error> { 62 | let mut decoder = GzDecoder::new(&input[..]); 63 | let mut result: Vec = vec![0; index / 8 + 1]; 64 | decoder.read_exact(&mut result)?; 65 | Ok(result) 66 | } 67 | 68 | fn calculate_bitstring_size(input_size: usize) -> usize { 69 | const MINIMUM_INPUT_SIZE: usize = 131072; 70 | std::cmp::max(input_size, MINIMUM_INPUT_SIZE) 71 | } 72 | 73 | #[cfg(test)] 74 | mod test { 75 | use super::*; 76 | 77 | // test vector, no revocations, taken from: https://www.w3.org/TR/vc-status-list/#example-example-statuslist2021credential-0 78 | const BITSTRING_NO_REVOCATIONS: &str = 79 | "H4sIAAAAAAAAA-3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAIC3AYbSVKsAQAAA"; 80 | 81 | // test vector, one revocation on index 1 82 | const BITSTRING_ONE_REVOCATION: &str = 83 | "H4sIAAAAAAAA_-3AsQAAAAACsNDypwqjZ2sAAAAAAAAAAAAAAAAAAACAtwE3F1_NAEAAAA"; 84 | 85 | #[test] 86 | fn test_generate_bitstring() { 87 | assert_eq!( 88 | generate_bitstring(vec![false, true, false, false]).unwrap(), 89 | BITSTRING_ONE_REVOCATION 90 | ); 91 | } 92 | 93 | #[test] 94 | fn test_extract_bitstring_index_success() { 95 | assert!(!extract_bitstring_index(BITSTRING_ONE_REVOCATION.to_owned(), 0).unwrap()); 96 | assert!(extract_bitstring_index(BITSTRING_ONE_REVOCATION.to_owned(), 1).unwrap()); 97 | 98 | assert!(!extract_bitstring_index(BITSTRING_NO_REVOCATIONS.to_owned(), 0).unwrap()); 99 | assert!(!extract_bitstring_index(BITSTRING_NO_REVOCATIONS.to_owned(), 1).unwrap()); 100 | } 101 | 102 | #[test] 103 | fn test_extract_bitstring_invalid_base64() { 104 | let result = extract_bitstring_index("invalid".to_owned(), 1000000); 105 | assert!(matches!(result, Err(BitstringError::Base64Decoding(_)))); 106 | } 107 | 108 | #[test] 109 | fn test_extract_bitstring_index_out_of_bounds() { 110 | let result = extract_bitstring_index(BITSTRING_ONE_REVOCATION.to_owned(), 1000000); 111 | assert!(matches!( 112 | result, 113 | Err(BitstringError::IndexOutOfBounds { index: 1000000 }) 114 | )); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /one-providers/src/revocation/imp/lvvc/dto.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use time::OffsetDateTime; 3 | use uuid::Uuid; 4 | 5 | #[derive(PartialEq, Debug, strum::Display)] 6 | pub enum LvvcStatus { 7 | #[strum(serialize = "ACCEPTED")] 8 | Accepted, 9 | #[strum(serialize = "REVOKED")] 10 | Revoked, 11 | #[strum(serialize = "SUSPENDED")] 12 | Suspended { 13 | suspend_end_date: Option, 14 | }, 15 | } 16 | 17 | #[derive(Clone, Debug, Deserialize)] 18 | pub(super) struct IssuerResponseDTO { 19 | pub credential: String, 20 | pub format: String, 21 | } 22 | 23 | #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] 24 | #[serde(rename_all = "camelCase")] 25 | pub struct Lvvc { 26 | pub id: Uuid, 27 | pub created_date: OffsetDateTime, 28 | pub credential: Vec, 29 | pub linked_credential_id: Uuid, 30 | } 31 | -------------------------------------------------------------------------------- /one-providers/src/revocation/imp/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of revocation method provider. 2 | 3 | pub mod bitstring_status_list; 4 | pub mod lvvc; 5 | pub mod provider; 6 | -------------------------------------------------------------------------------- /one-providers/src/revocation/imp/provider.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Arc}; 2 | 3 | use crate::revocation::{provider::RevocationMethodProvider, RevocationMethod}; 4 | 5 | pub struct RevocationMethodProviderImpl { 6 | revocation_methods: HashMap>, 7 | } 8 | 9 | impl RevocationMethodProviderImpl { 10 | pub fn new(revocation_methods: HashMap>) -> Self { 11 | Self { revocation_methods } 12 | } 13 | } 14 | 15 | impl RevocationMethodProvider for RevocationMethodProviderImpl { 16 | fn get_revocation_method( 17 | &self, 18 | revocation_method_id: &str, 19 | ) -> Option> { 20 | self.revocation_methods.get(revocation_method_id).cloned() 21 | } 22 | 23 | fn get_revocation_method_by_status_type( 24 | &self, 25 | credential_status_type: &str, 26 | ) -> Option<(Arc, String)> { 27 | let result = self 28 | .revocation_methods 29 | .iter() 30 | .find(|(_id, method)| method.get_status_type() == credential_status_type)?; 31 | 32 | Some((result.1.to_owned(), result.0.to_owned())) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /one-providers/src/revocation/mod.rs: -------------------------------------------------------------------------------- 1 | //! Tools for suspending and revoking credentials, and checking the status of credentials. 2 | //! 3 | //! Credentials issued with no revocation method cannot be revoked and remain valid 4 | //! indefinitely. Suspended credentials can be made valid again. Revoked credentials 5 | //! are rendered permanently invalid. 6 | //! 7 | //! This module provides tools for changing the suspension or revocation status of a 8 | //! credential and for retrieving the validity status of a credential. 9 | 10 | use crate::common_models::credential::OpenCredential; 11 | use crate::common_models::did::DidValue; 12 | use crate::credential_formatter::model::CredentialStatus; 13 | use crate::revocation::error::RevocationError; 14 | use crate::revocation::model::{ 15 | CredentialAdditionalData, CredentialDataByRole, CredentialRevocationInfo, 16 | CredentialRevocationState, JsonLdContext, RevocationMethodCapabilities, RevocationUpdate, 17 | }; 18 | 19 | pub mod error; 20 | pub mod imp; 21 | pub mod model; 22 | pub mod provider; 23 | 24 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 25 | #[async_trait::async_trait] 26 | pub trait RevocationMethod: Send + Sync { 27 | /// Returns the revocation method as a string for the `credentialStatus` field of the VC. 28 | fn get_status_type(&self) -> String; 29 | 30 | /// Creates the `credentialStatus` field of the VC. 31 | /// 32 | /// For BitstringStatusList, this method creates the entry in revocation and suspension lists. 33 | /// 34 | /// For LVVC, the URL used by the holder to obtain a new LVVC is returned. 35 | async fn add_issued_credential( 36 | &self, 37 | credential: &OpenCredential, 38 | additional_data: Option, 39 | ) -> Result<(Option, Vec), RevocationError>; 40 | 41 | /// Change a credential's status to valid, revoked, or suspended. 42 | /// 43 | /// For list-based revocation methods, use `additional_data` to specify the ID of the associated list. 44 | async fn mark_credential_as( 45 | &self, 46 | credential: &OpenCredential, 47 | new_state: CredentialRevocationState, 48 | additional_data: Option, 49 | ) -> Result; 50 | 51 | /// Checks the revocation status of a credential. 52 | async fn check_credential_revocation_status( 53 | &self, 54 | credential_status: &CredentialStatus, 55 | issuer_did: &DidValue, 56 | additional_credential_data: Option, 57 | ) -> Result; 58 | 59 | #[doc = include_str!("../../../docs/capabilities.md")] 60 | /// 61 | /// Revocation method capabilities include the operations possible for each revocation 62 | /// method. 63 | fn get_capabilities(&self) -> RevocationMethodCapabilities; 64 | 65 | /// For credentials with LVVC revocation method, this method creates the URL 66 | /// where the JSON-LD @context is hosted. 67 | fn get_json_ld_context(&self) -> Result; 68 | } 69 | -------------------------------------------------------------------------------- /one-providers/src/revocation/model.rs: -------------------------------------------------------------------------------- 1 | //! `struct`s and `enum`s for revocation method provider. 2 | 3 | use serde::Serialize; 4 | use strum::Display; 5 | use time::OffsetDateTime; 6 | use uuid::Uuid; 7 | 8 | use crate::{ 9 | common_models::{credential::OpenCredential, proof_schema::OpenProofInputSchema}, 10 | credential_formatter::model::{CredentialStatus, DetailCredential}, 11 | }; 12 | 13 | pub type RevocationListId = Uuid; 14 | 15 | pub struct CredentialAdditionalData { 16 | pub credentials_by_issuer_did: Vec, 17 | pub revocation_list_id: RevocationListId, 18 | pub suspension_list_id: RevocationListId, 19 | } 20 | 21 | #[derive(Clone)] 22 | pub enum CredentialDataByRole { 23 | Holder(OpenCredential), 24 | Issuer(OpenCredential), 25 | Verifier(Box), 26 | } 27 | 28 | #[derive(Debug, Clone)] 29 | pub struct VerifierCredentialData { 30 | pub credential: DetailCredential, 31 | pub extracted_lvvcs: Vec, 32 | pub proof_input: OpenProofInputSchema, 33 | } 34 | 35 | pub struct CredentialRevocationInfo { 36 | pub credential_status: CredentialStatus, 37 | } 38 | 39 | #[derive(Clone, Debug, Display, PartialEq)] 40 | pub enum CredentialRevocationState { 41 | Valid, 42 | Revoked, 43 | Suspended { 44 | suspend_end_date: Option, 45 | }, 46 | } 47 | 48 | #[derive(Debug, Default)] 49 | pub struct JsonLdContext { 50 | pub revokable_credential_type: String, 51 | pub revokable_credential_subject: String, 52 | pub url: Option, 53 | } 54 | 55 | #[derive(Clone, Default, Serialize)] 56 | pub struct RevocationMethodCapabilities { 57 | pub operations: Vec, 58 | } 59 | 60 | #[derive(Debug)] 61 | pub struct RevocationUpdate { 62 | pub status_type: String, 63 | pub data: Vec, 64 | } 65 | -------------------------------------------------------------------------------- /one-providers/src/revocation/provider.rs: -------------------------------------------------------------------------------- 1 | //! Revocation method provider. 2 | 3 | use std::sync::Arc; 4 | 5 | use crate::revocation::RevocationMethod; 6 | 7 | #[cfg_attr(any(test, feature = "mock"), mockall::automock)] 8 | pub trait RevocationMethodProvider: Send + Sync { 9 | fn get_revocation_method( 10 | &self, 11 | revocation_method_id: &str, 12 | ) -> Option>; 13 | 14 | fn get_revocation_method_by_status_type( 15 | &self, 16 | credential_status_type: &str, 17 | ) -> Option<(Arc, String)>; 18 | } 19 | -------------------------------------------------------------------------------- /one-providers/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for signature verification. 2 | 3 | pub mod key_verification; 4 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=procivis_one_one-open-core_4a1a1170-ebc8-46e4-8db8-daac061088f9 2 | sonar.qualitygate.wait=true 3 | sonar.sources=one-open-core,one-providers,examples 4 | sonar.exclusions=sonar.yml 5 | sonar.test.inclusions=**/tests/** 6 | 7 | community.rust.clippy.reportPaths=clippy.json 8 | community.rust.cpd.ignoretests=true 9 | community.rust.lcov.reportPaths=lcov.info 10 | --------------------------------------------------------------------------------