Table of Contents

References

High Level System Architecture

App Architecture

Generic Data Flow

This is the data flow for both registration and assertion (and message sign):

Frontend API

iOS

Start Request

Start request via ASAuthorizationPlatformPublicKeyCredentialProvider

Create the object with a relying party identifier:
init(relyingPartyIdentifier: String)

Create a registration request with a challenge, name, and user ID:
func createCredentialRegistrationRequest(challenge: Data, name: String, userID: Data)
-> ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest
Note that challenge is binary and we may need to convert it from Base64URL encoding when received from the Auth Svr.

Registration Request

ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest
implements ASAuthorizationPublicKeyCredentialRegistrationRequest which has

var attestationPreference: ASAuthorizationPublicKeyCredentialAttestationKind - The type of attestation you're requesting.

static let none: ASAuthorizationPublicKeyCredentialAttestationKind - An attestation kind of none.

static let direct: ASAuthorizationPublicKeyCredentialAttestationKind - An attestation kind of direct.

static let enterprise: ASAuthorizationPublicKeyCredentialAttestationKind - An attestation kind of enterprise.

static let indirect: ASAuthorizationPublicKeyCredentialAttestationKind - An attestation kind of indirect.

var challenge: Data - Arbitrary data that the client signs as proof of a valid registration or attestation.

var displayName: String? - A user-visible name for the credential, such as the account's user name.

var name: String - A user-visible name that identifies a credential.

var relyingPartyIdentifier: String - The domain name of the website for the credential.

var userID: Data - Data that the relying party associates with the credential.

var userVerificationPreference: ASAuthorizationPublicKeyCredentialUserVerificationPreference - A preference for whether the authenticator attempts to verify the user at the time of registration.

static let discouraged: ASAuthorizationPublicKeyCredentialUserVerificationPreference - The relying party discourages user verification.

static let preferred: ASAuthorizationPublicKeyCredentialUserVerificationPreference - The relying party prefers user verification.

static let required: ASAuthorizationPublicKeyCredentialUserVerificationPreference - The relying party requires user verification.

Sign-in (Assertion) Request

Create an assertion request with a challenge:
func createCredentialAssertionRequest(challenge: Data)
-> ASAuthorizationPlatformPublicKeyCredentialAssertionRequest
Note that challenge is binary and we may need to convert it from Base64URL encoding when received from the Auth Svr.

ASAuthorizationPlatformPublicKeyCredentialAssertionRequest implements
ASAuthorizationPublicKeyCredentialAssertionRequest which has

var challenge: Data - The challenge to sign.

var relyingPartyIdentifier: String - The domain name of the website for the credential.

var allowedCredentials: [ASAuthorizationPublicKeyCredentialDescriptor] - A list of allowed credential descriptors the user attempts to sign in with.

var credentialID: Data - An identifier the authenticator generates during registration to uniquely identify a specific credential.

var userVerificationPreference: ASAuthorizationPublicKeyCredentialUserVerificationPreference - A preference that indicates whether the authenticator attempts to verify the user at the time of sign-in.

static let discouraged: ASAuthorizationPublicKeyCredentialUserVerificationPreference - The relying party discourages user verification.

static let preferred: ASAuthorizationPublicKeyCredentialUserVerificationPreference - The relying party prefers user verification.

static let required: ASAuthorizationPublicKeyCredentialUserVerificationPreference - The relying party requires user verification.

Process Reply

Reply is received via implementing some functions in ASAuthorizationControllerDelegate

Tells the delegate when authorization completes successfully:
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization: ASAuthorization)
The parameter didCompleteWithAuthorization.credential can be ASAuthorizationPlatformPublicKeyCredentialRegistration or ASAuthorizationPlatformPublicKeyCredentialAssertion

Registration Reply

ASAuthorizationPlatformPublicKeyCredentialRegistration
implements ASAuthorizationPublicKeyCredentialRegistration which has

var rawAttestationObject: Data? - a WebAuthn attestation object (in CBOR format). Note that this is binary and may need to be converted to Base64URL to be transmitted to the Auth Srvr.
(reference: https://cbor.io/spec.html)

ASAuthorizationPublicKeyCredentialRegistration
implements ASPublicKeyCredential which has

var rawClientDataJSON: Data - Raw data that contains a JSON-compatible encoding of the client data.
var credentialID: Data - An identifier that the authenticator generated during registration to uniquely identify a specific credential.

Sign-in (Assertion) Reply

ASAuthorizationPlatformPublicKeyCredentialAssertion
implements ASAuthorizationPublicKeyCredentialAssertion which has

var signature: Data! - The signature for the assertion
var userID: Data! - A user identifier for the assertion.
var rawAuthenticatorData: Data! - A byte sequence that contains additional information about the credential.

Error Handling

Tells the delegate when authorization fails, and provides an error explaining why:
func authorizationController(controller: ASAuthorizationController, didCompleteWithError: Error)

Change or reset a key

Use the same createCredentialRegistrationRequest as above. Registering a passkey with the same userID as an existing one overwrites the existing passkey on the user's devices.

Backend API - auth-service

Deployed at https://auth-1.sandbox.safibank.online/

GET /api/v1

  • info about available endpoints, in JSON format

GET /api/v1/version

  • info about deployed code version, etc.

POST /api/v1/register

  • initial call to be issued when registering a new username

  • request:

    • add non-resident credential

      {
        "username":"Norbi6",
        "displayName":"Norbi6",
        "credentialNickname":"",
        "sessionToken":null
      }
    • add resident credential

      {
         "username":"norbi22",
         "displayName":"norbi22",
         "credentialNickname":"",
         "requireResidentKey":true,
         "sessionToken":null
      }
  • response:

    • success

      • non-resident credential: returns credential ID, challenge string and continuation action URL (among others)

        {
           "success":true,
           "request":{
              "username":"Norbi6",
              "credentialNickname":"",
              "requestId":"WpaCANAUFJ42JdcZgDsUqr4pmUCD6G0cYFjmKfJR_eg",
              "publicKeyCredentialCreationOptions":{
                 "rp":{
                    "name":"auth PoC",
                    "id":"safibank.online"
                 },
                 "user":{
                    "name":"Norbi6",
                    "displayName":"Norbi6",
                    "id":"HVb6FEbU4haJAqJALbf0vboSmqKiPbZqFmXtpuvISCw"
                 },
                 "challenge":"nUGbR2MALTnEVmoK4BgVOOgODVbldpnhc9hME4TSEfI",
                 "pubKeyCredParams":[
                    { "alg":-7, "type":"public-key" },
                    { "alg":-257, "type":"public-key" }
                 ],
                 "excludeCredentials":[ ],
                 "authenticatorSelection":{
                    "residentKey":"discouraged"
                 },
                 "attestation":"direct",
                 "extensions":{
                    "appidExclude":"https://auth-1.sandbox.safibank.online:8443",
                    "credProps":true
                 }
              },
              "sessionToken":"Rng4gnX6Teduc_-4qVK_UEAizoXsIfKlZxznUhz5VrE"
           },
           "actions":{
              "finish":"https://auth-1.sandbox.safibank.online/api/v1/register/finish"
           }
        }
      • resident credential: returns credential ID, challenge string and continuation action URL (among others)
        Note: the only semantic difference compared to the previous one is the value of authenticatorSelection.residentKey is required

        {
           "success":true,
           "request":{
              "username":"norbi22",
              "credentialNickname":"",
              "requestId":"2DnHO2YZdF06Lud2gYgBND09u8ok5yPV3mNHn70bZCY",
              "publicKeyCredentialCreationOptions":{
                 "rp":{
                    "name":"auth PoC",
                    "id":"auth-1.sandbox.safibank.online"
                 },
                 "user":{
                    "name":"norbi22",
                    "displayName":"norbi22",
                    "id":"cDNuvwcp5pkgfwU0y5kK_-yJutLtK5t2PJP_8Ct5vkw"
                 },
                 "challenge":"cb7PqQqJrJmJAhqVtIsqkXLVJWhnWBT8iZ0kvg10mKs",
                 "pubKeyCredParams":[
                    { "alg":-7, "type":"public-key" },
                    { "alg":-257, "type":"public-key" }
                 ],
                 "excludeCredentials":[ ],
                 "authenticatorSelection":{
                    "residentKey":"required"
                 },
                 "attestation":"direct",
                 "extensions":{
                    "appidExclude":"https://auth-1.sandbox.safibank.online:8443",
                    "credProps":true
                 }
              },
              "sessionToken":"czKWyCQxQ9AvWOtjzj3k3B9LIqxMIpYfTSsvTsdc6MU"
           },
           "actions":{
              "finish":"https://auth-1.sandbox.safibank.online/api/v1/register/finish"
           }
        }
    • returns error if username is already in use
      {"messages":["The username \"Norbi6\" is already registered."]}

POST /api/v1/register/finish

  • request: to be issued after the client received success response from POST /api/v1/register and after client generated keypair and payload will contain public key to be stored by server

    • non-resident credential:

      {
         "requestId":"WpaCANAUFJ42JdcZgDsUqr4pmUCD6G0cYFjmKfJR_eg",
         "credential":{
            "type":"public-key",
            "id":"lWDLU2GjHm4D6-MNys7lNpKuBMPSNGY_UDSEwhTHdUbwZX0jzHN0Fqr_6uj6UC_yd9ZCxfgcf5WooqtxNXYtIQta4zhRoxwADEHR9zHO",
            "rawId":"lWDLU2GjHm4D6-MNys7lNpKuBMPSNGY_UDSEwhTHdUbwZX0jzHN0Fqr_6uj6UC_yd9ZCxfgcf5WooqtxNXYtIQta4zhRoxwADEHR9zHO",
            "response":{
               "clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiblVHYlIyTUFMVG5FVm1vSzRCZ1ZPT2dPRFZibGRwbmhjOWhNRTRUU0VmSSIsIm9yaWdpbiI6Imh0dHBzOi8vYXV0aC0xLnNhbmRib3guc2FmaWJhbmsub25saW5lIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ",
               "attestationObject":"o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEYwRAIgRKMU3Sxg46Mx1uXymFZnfikCzzb4ksgdnFjjX-Z3ARICIAZh_0sUm6dYYHTiOwhRbTJMJzo_d7fXcKyiEaJieB5oaGF1dGhEYXRhWNJe3ul-V0R7-KBuTV13Q9hjEiWmASzJaXm2INyHcopXSkUAAAAArc4AAjW8xgpkiwsl8fBVAwBOlWDLU2GjHm4D6-MNys7lNpKuBMPSNGY_UDSEwhTHdUbwZX0jzHN0Fqr_6uj6UC_yd9ZCxfgcf5WooqtxNXYtIQta4zhRoxwADEHR9zHOpQECAyYgASFYILZ0Vw60L-ahKs-HuhAw1mGTO1SQuncnIkzSfJlLF4x9Ilgg_wLEEQj1VROa-v3ITWURwjgK8aePzF2KVEq4WDV0mGM",
               "transports":[
                  "internal"
               ]
            },
            "clientExtensionResults":{
               "credProps":{
                  "rk":false
               }
            }
         },
         "sessionToken":"Rng4gnX6Teduc_-4qVK_UEAizoXsIfKlZxznUhz5VrE"
      }
    • resident credential (the only difference compared to the previous one is the value of clientExtensionResults.credProps.rk)

      {
         "requestId":"2DnHO2YZdF06Lud2gYgBND09u8ok5yPV3mNHn70bZCY",
         "credential":{
            "type":"public-key",
            "id":"fkB33Lg5CGJutWwI2sCx4zuOkc3k69FUs4XDPOibpa7U3CTPyL55VbJm5gm0lCFpd4Ltz8Gp-7zFIJr6vV3K6PxLc0PchHFhXjGOeME-G44",
            "rawId":"fkB33Lg5CGJutWwI2sCx4zuOkc3k69FUs4XDPOibpa7U3CTPyL55VbJm5gm0lCFpd4Ltz8Gp-7zFIJr6vV3K6PxLc0PchHFhXjGOeME-G44",
            "response":{
               "clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiY2I3UHFRcUpySm1KQWhxVnRJc3FrWExWSldobldCVDhpWjBrdmcxMG1LcyIsIm9yaWdpbiI6Imh0dHBzOi8vYXV0aC0xLnNhbmRib3guc2FmaWJhbmsub25saW5lIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ",
               "attestationObject":"o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIgMI7G84xGaF_S3ZnW3W7B2pfU85MyAwYuDh0YJf_IysICIQCFIxIL7tDAnc6_gMPqa0vcFj86wfATbTYcW4IzskP8bmhhdXRoRGF0YVjURxYUfbcZhfKoSTmiUwejao264CviCUGv2c1ik6XbL9JFAAAAAK3OAAI1vMYKZIsLJfHwVQMAUH5Ad9y4OQhibrVsCNrAseM7jpHN5OvRVLOFwzzom6Wu1Nwkz8i-eVWyZuYJtJQhaXeC7c_Bqfu8xSCa-r1dyuj8S3ND3IRxYV4xjnjBPhuOpQECAyYgASFYIExNEiVXRgcghsYkHfWX7V7MmdX6BMzSWJwXHuZdSKnmIlggiHvLLjyBkpEyHlMbPaG7qqPQJfibtyeQFq5kP_nESlk",
               "transports":[
                  "internal"
               ]
            },
            "clientExtensionResults":{
               "credProps":{
                  "rk":true
               }
            }
         },
         "sessionToken":"czKWyCQxQ9AvWOtjzj3k3B9LIqxMIpYfTSsvTsdc6MU"
      }
  • response: all the details about the registration procedure (most probably just to display it on the web UI)

    • non-resident credential:

      {
         "success":true,
         "request":{
           // very same structure that is in the response
           // for the POST /api/v1/register call
         },
         "response":{
            // very same structure that is in the request 
            // for the POST /api/v1/register/finish call
         },
         "registration":{
            "userIdentity":{
               "name":"Norbi6",
               "displayName":"Norbi6",
               "id":"HVb6FEbU4haJAqJALbf0vboSmqKiPbZqFmXtpuvISCw"
            },
            "credentialNickname":"",
            "transports":[
               "internal"
            ],
            "credential":{
               "credentialId":"lWDLU2GjHm4D6-MNys7lNpKuBMPSNGY_UDSEwhTHdUbwZX0jzHN0Fqr_6uj6UC_yd9ZCxfgcf5WooqtxNXYtIQta4zhRoxwADEHR9zHO",
               "userHandle":"HVb6FEbU4haJAqJALbf0vboSmqKiPbZqFmXtpuvISCw",
               "publicKeyCose":"pQECAyYgASFYILZ0Vw60L-ahKs-HuhAw1mGTO1SQuncnIkzSfJlLF4x9Ilgg_wLEEQj1VROa-v3ITWURwjgK8aePzF2KVEq4WDV0mGM",
               "signatureCount":0
            },
            "username":"Norbi6",
            "registrationTime":"2022-07-01T17:58:10.554237Z"
         },
         "attestationTrusted":false,
         "authData":{
            "rpIdHash":"5edee97e57447bf8a06e4d5d7743d8631225a6012cc96979b620dc87728a574a",
            "flags":{
               "value":69,
               "UP":true,
               "UV":true,
               "AT":true,
               "ED":false
            },
            "signatureCounter":0,
            "attestedCredentialData":{
               "aaguid":"adce000235bcc60a648b0b25f1f05503",
               "credentialId":"9560cb5361a31e6e03ebe30dcacee53692ae04c3d234663f503484c214c77546f0657d23cc737416aaffeae8fa502ff277d642c5f81c7f95a8a2ab7135762d210b5ae33851a31c000c41d1f731ce",
               "publicKey":"a5010203262001215820b674570eb42fe6a12acf87ba1030d661933b5490ba7727224cd27c994b178c7d225820ff02c41108f555139afafdc84d6511c2380af1a78fcc5d8a544ab85835749863"
            },
            "extensions":null
         },
         "username":"Norbi6",
         "sessionToken":"Rng4gnX6Teduc_-4qVK_UEAizoXsIfKlZxznUhz5VrE"
      }
    • resident credential:

      {
         "success":true,
         "request":{
           // very same structure that is in the response
           // for the POST /api/v1/register call
         },
         "response":{
            // very same structure that is in the request 
            // for the POST /api/v1/register/finish call
         },
         "registration":{
            "userIdentity":{
               "name":"norbi22",
               "displayName":"norbi22",
               "id":"cDNuvwcp5pkgfwU0y5kK_-yJutLtK5t2PJP_8Ct5vkw"
            },
            "credentialNickname":"",
            "transports":[
               "internal"
            ],
            "credential":{
               "credentialId":"fkB33Lg5CGJutWwI2sCx4zuOkc3k69FUs4XDPOibpa7U3CTPyL55VbJm5gm0lCFpd4Ltz8Gp-7zFIJr6vV3K6PxLc0PchHFhXjGOeME-G44",
               "userHandle":"cDNuvwcp5pkgfwU0y5kK_-yJutLtK5t2PJP_8Ct5vkw",
               "publicKeyCose":"pQECAyYgASFYIExNEiVXRgcghsYkHfWX7V7MmdX6BMzSWJwXHuZdSKnmIlggiHvLLjyBkpEyHlMbPaG7qqPQJfibtyeQFq5kP_nESlk",
               "signatureCount":0
            },
            "username":"norbi22",
            "registrationTime":"2022-07-07T17:31:39.151539Z"
         },
         "attestationTrusted":false,
         "authData":{
            "rpIdHash":"4716147db71985f2a84939a25307a36a8dbae02be20941afd9cd6293a5db2fd2",
            "flags":{
               "value":69,
               "UP":true,
               "UV":true,
               "AT":true,
               "ED":false
            },
            "signatureCounter":0,
            "attestedCredentialData":{
               "aaguid":"adce000235bcc60a648b0b25f1f05503",
               "credentialId":"7e4077dcb83908626eb56c08dac0b1e33b8e91cde4ebd154b385c33ce89ba5aed4dc24cfc8be7955b266e609b49421697782edcfc1a9fbbcc5209afabd5dcae8fc4b7343dc8471615e318e78c13e1b8e",
               "publicKey":"a50102032620012158204c4d12255746072086c6241df597ed5ecc99d5fa04ccd2589c171ee65d48a9e6225820887bcb2e3c819291321e531b3da1bbaaa3d025f89bb7279016ae643ff9c44a59"
            },
            "extensions":null
         },
         "username":"norbi22",
         "sessionToken":"czKWyCQxQ9AvWOtjzj3k3B9LIqxMIpYfTSsvTsdc6MU"
      }

POST /api/v1/authenticate

  • initial call to be issued when authenticating the user

  • request

    • non-resident credential: { "username" : "Norbi6" }

    • resident credential: {}

  • response: returns success if username is already registered on server and contains challenge to be encrypted by client with private key

    • non-resident credential:

      {
         "success":true,
         "request":{
            "requestId":"RhcCrGu5ZJyoC5UlAStvoFyyvwIUtbc9LJAtmE3blhk",
            "publicKeyCredentialRequestOptions":{
               "challenge":"3435vQ1fxdciVGRCpB4yY9JzB9YK8WrbM702FSLHWVs",
               "rpId":"safibank.online",
               "allowCredentials":[
                  {
                     "type":"public-key",
                     "id":"lWDLU2GjHm4D6-MNys7lNpKuBMPSNGY_UDSEwhTHdUbwZX0jzHN0Fqr_6uj6UC_yd9ZCxfgcf5WooqtxNXYtIQta4zhRoxwADEHR9zHO",
                     "transports":[
                        "internal"
                     ]
                  }
               ],
               "extensions":{
                  "appid":"https://auth-1.sandbox.safibank.online:8443"
               }
            },
            "username":"Norbi6"
         },
         "actions":{
            "finish":"https://auth-1.sandbox.safibank.online/api/v1/authenticate/finish"
         }
      }
    • resident credential:

      {
         "success":true,
         "request":{
            "requestId":"jOBtVwb2qeNBXXxZqJw87EB2rnpZuiDnfiuO48vSbmU",
            "publicKeyCredentialRequestOptions":{
               "challenge":"kW9eHrQkJboPyO7sNO0GVUV_sgrsokQJIItUlZ0YeqY",
               "rpId":"auth-1.sandbox.safibank.online",
               "extensions":{
                  "appid":"https://auth-1.sandbox.safibank.online:8443"
               }
            }
         },
         "actions":{
            "finish":"https://auth-1.sandbox.safibank.online/api/v1/authenticate/finish"
         }
      }

POST /api/v1/authenticate/finish

  • to be issued after the client received success response from POST /api/v1/authenticate and after client encrypted the challenge with the private key

  • request: payload will contain encrypted challenge, to be decrypted on server with the stored public key corresponding to that user

    • non-resident credential:

      {
         "requestId":"RhcCrGu5ZJyoC5UlAStvoFyyvwIUtbc9LJAtmE3blhk",
         "credential":{
            "type":"public-key",
            "id":"lWDLU2GjHm4D6-MNys7lNpKuBMPSNGY_UDSEwhTHdUbwZX0jzHN0Fqr_6uj6UC_yd9ZCxfgcf5WooqtxNXYtIQta4zhRoxwADEHR9zHO",
            "rawId":"lWDLU2GjHm4D6-MNys7lNpKuBMPSNGY_UDSEwhTHdUbwZX0jzHN0Fqr_6uj6UC_yd9ZCxfgcf5WooqtxNXYtIQta4zhRoxwADEHR9zHO",
            "response":{
               "clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiMzQzNXZRMWZ4ZGNpVkdSQ3BCNHlZOUp6QjlZSzhXcmJNNzAyRlNMSFdWcyIsIm9yaWdpbiI6Imh0dHBzOi8vYXV0aC0xLnNhbmRib3guc2FmaWJhbmsub25saW5lIiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUgaHR0cHM6Ly9nb28uZ2wveWFiUGV4In0",
               "authenticatorData":"Xt7pfldEe_igbk1dd0PYYxIlpgEsyWl5tiDch3KKV0oFAAAAAA",
               "signature":"MEYCIQDMzAjUmVyvK4ubc2fPqno_y0hFfTI4kFbQd9_47RQZjAIhAKLMJWm8DE3buizE-llz-QIuaUMcmBIwD69kf0XYTbj7",
               "userHandle":"HVb6FEbU4haJAqJALbf0vboSmqKiPbZqFmXtpuvISCw"
            },
            "clientExtensionResults":{
               "appid":false
            }
         },
         "sessionToken":"Rng4gnX6Teduc_-4qVK_UEAizoXsIfKlZxznUhz5VrE"
      }
    • non-resident credential:

      {
         "requestId":"jOBtVwb2qeNBXXxZqJw87EB2rnpZuiDnfiuO48vSbmU",
         "credential":{
            "type":"public-key",
            "id":"fkB33Lg5CGJutWwI2sCx4zuOkc3k69FUs4XDPOibpa7U3CTPyL55VbJm5gm0lCFpd4Ltz8Gp-7zFIJr6vV3K6PxLc0PchHFhXjGOeME-G44",
            "rawId":"fkB33Lg5CGJutWwI2sCx4zuOkc3k69FUs4XDPOibpa7U3CTPyL55VbJm5gm0lCFpd4Ltz8Gp-7zFIJr6vV3K6PxLc0PchHFhXjGOeME-G44",
            "response":{
               "clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoia1c5ZUhyUWtKYm9QeU83c05PMEdWVVZfc2dyc29rUUpJSXRVbFowWWVxWSIsIm9yaWdpbiI6Imh0dHBzOi8vYXV0aC0xLnNhbmRib3guc2FmaWJhbmsub25saW5lIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ",
               "authenticatorData":"RxYUfbcZhfKoSTmiUwejao264CviCUGv2c1ik6XbL9IFAAAAAA",
               "signature":"MEUCIEKX-ai9OE5gnH5hHQT3ndC9B7kPU4n12Scj2tni4zQYAiEAokQZwfHNvXGVw1Ems0iIDW6rxMs9LVrXgtBPc8PL8cc",
               "userHandle":"cDNuvwcp5pkgfwU0y5kK_-yJutLtK5t2PJP_8Ct5vkw"
            },
            "clientExtensionResults":{
               "appid":false
            }
         },
         "sessionToken":null
      }
  • response:

    • non-resident credential:

      {
         "success":true,
         "request":{
           // very same structure that is in the response
           // for the POST /api/v1/authenticate call
         },
         "response":{
            // very same structure that is in the request 
            // for the POST /api/v1/authenticate/finish call
         },
         "registrations":[
            {
               "userIdentity":{
                  "name":"Norbi6",
                  "displayName":"Norbi6",
                  "id":"HVb6FEbU4haJAqJALbf0vboSmqKiPbZqFmXtpuvISCw"
               },
               "credentialNickname":"",
               "transports":[
                  "internal"
               ],
               "credential":{
                  "credentialId":"lWDLU2GjHm4D6-MNys7lNpKuBMPSNGY_UDSEwhTHdUbwZX0jzHN0Fqr_6uj6UC_yd9ZCxfgcf5WooqtxNXYtIQta4zhRoxwADEHR9zHO",
                  "userHandle":"HVb6FEbU4haJAqJALbf0vboSmqKiPbZqFmXtpuvISCw",
                  "publicKeyCose":"pQECAyYgASFYILZ0Vw60L-ahKs-HuhAw1mGTO1SQuncnIkzSfJlLF4x9Ilgg_wLEEQj1VROa-v3ITWURwjgK8aePzF2KVEq4WDV0mGM",
                  "signatureCount":0
               },
               "username":"Norbi6",
               "registrationTime":"2022-07-01T17:58:10.554237Z"
            }
         ],
         "authData":{
            "rpIdHash":"5edee97e57447bf8a06e4d5d7743d8631225a6012cc96979b620dc87728a574a",
            "flags":{
               "value":5,
               "UP":true,
               "UV":true,
               "AT":false,
               "ED":false
            },
            "signatureCounter":0,
            "extensions":null
         },
         "username":"Norbi6",
         "sessionToken":"5rFaWptHgn2fkEaeoyluK3JiBoHpG6B3bhvKOWoeJjI"
      }
    • resident credential:

      {
         "success":true,
         "request":{
           // very same structure that is in the response
           // for the POST /api/v1/authenticate call
         },
         "response":{
            // very same structure that is in the request 
            // for the POST /api/v1/authenticate/finish call
         },
         "registrations":[
            {
               "userIdentity":{
                  "name":"norbi22",
                  "displayName":"norbi22",
                  "id":"cDNuvwcp5pkgfwU0y5kK_-yJutLtK5t2PJP_8Ct5vkw"
               },
               "credentialNickname":"",
               "transports":[
                  "internal"
               ],
               "credential":{
                  "credentialId":"fkB33Lg5CGJutWwI2sCx4zuOkc3k69FUs4XDPOibpa7U3CTPyL55VbJm5gm0lCFpd4Ltz8Gp-7zFIJr6vV3K6PxLc0PchHFhXjGOeME-G44",
                  "userHandle":"cDNuvwcp5pkgfwU0y5kK_-yJutLtK5t2PJP_8Ct5vkw",
                  "publicKeyCose":"pQECAyYgASFYIExNEiVXRgcghsYkHfWX7V7MmdX6BMzSWJwXHuZdSKnmIlggiHvLLjyBkpEyHlMbPaG7qqPQJfibtyeQFq5kP_nESlk",
                  "signatureCount":0
               },
               "username":"norbi22",
               "registrationTime":"2022-07-07T17:31:39.151539Z"
            }
         ],
         "authData":{
            "rpIdHash":"4716147db71985f2a84939a25307a36a8dbae02be20941afd9cd6293a5db2fd2",
            "flags":{
               "value":5,
               "UP":true,
               "UV":true,
               "AT":false,
               "ED":false
            },
            "signatureCounter":0,
            "extensions":null
         },
         "username":"norbi22",
         "sessionToken":"LAlt2lPOsbk7vR4xfFnnOBIWbl3jpfvObToo_AkwvMg"
      }

POST /api/v1/action/deregister

  • deregister user (remove user from db)

  • user is identified with its credential ID

DELETE /api/v1/delete-account

  • similar to POST /api/v1/action/deregister, but user is identified by username

POST /api/v1/fetch-user-keys

  • returns a list of all public keys associated to the username

  • request:

{ "username" : "myName"}

  • response - list of tuples in the format {credentialId, publicKeyCose}

POST /api/v1/fetch-key

  • returns the public key associated with the username and credentialId

  • request:

{
  "userHandle": "PRHXZ9HMwvl3eRSuzZ16XY7gr0AOfl6OuGr0geOxGY0",
  "credentialId": "Zl1TeirsX-G_i2n5TfDfMA5w4_k9d5AL-t9YtdifItA"
}
  • response - tuple in the format {credentialId, publicKeyCose}

Backend API - protected-service

Deployed at https://auth-1-abc.sandbox.safibank.online/

POST /api/v1/send-message-webauthn

  • send an arbitrary text message, signed using the client authenticator, from a particular username (and credentialId) to the server

  • leverages signature mechanism of client-side WebAuthn, and verification on server-side

  • the text message to be sent is fed into the authenticator as the “challenge” string

  • request - the structure should exactly match the output of the authenticator:

{
  "credential": {
    "type": "public-key",
    "id": "Zl1TeirsX-G_i2n5TfDfMA5w4_k9d5AL-t9YtdifItA",
    "rawId": "Zl1TeirsX-G_i2n5TfDfMA5w4_k9d5AL-t9YtdifItA",
    "response": {
      "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiWlhsS2FHSkhZMmxQYVVwVFZYcEpNVTVwU1hOSmJVNTVZVmhSYVU5c2MybGtXRTVzWTIwMWFHSlhWV2xNUTBwcVkyMVdhMXBYTlRCaFYwWnpVMWRSYVZoVGQybGtXRTVzWTIwMWFHSlhWV2xQYVVwM1lqTkNhR041U1hOSmJVNTVXbGRTYkdKdVVuQlpWM2hLV2tOSk5rbHNjSE5OVmxKc1lWaEtlbGRETVVoWU1tdDVZbXBXVlZwclVtMVVWVVV4WkhwU1ptRjZiR3RPVlVaTlRGaFJOVmRZVW10aFYxcEtaRVZGYVdaUlBUMHVaWGxLZEZwWVRucFpWMlJzU1dwdmFXUXlWbTFqYlZWcFpsRTlQUSIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMiLCJjcm9zc09yaWdpbiI6ZmFsc2V9",
      "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAw",
      "signature": "uaTxv9YB1usmsz1ID_ZA5609fo_09ds5NZ7J0x99e0jw8FvOw3pPFTsI9aa4SO2Au7mSMAtrKv3FkAPUnL1LExWXHUHu_UCiDwjxqL48H0NY5f4N0U7W57PsDepDVR0NGt4N7MZGEkqgMz0qM0eeTqv3Q6unLKKi5lPX55WU2fiOrVUWsTUuE2Sd22bXXmBqtORBT-N-x8JmnLGqkAzPaOTFywvmoRqMk6HdffZbKPyRTAnmT32k360OzghBCI1v8FIyGiGb_Qkg2Mjq6CE6NB4Fltcnte0FfW2TIx_O8UyPwA9ryBGWro3qucIshmI6c7Prih-ScL4eVI9aJjYbqA",
      "userHandle": "PRHXZ9HMwvl3eRSuzZ16XY7gr0AOfl6OuGr0geOxGY0"
    },
    "clientExtensionResults": {
      "appid": false
    }
  }
}
  • response:

{
  "success": true,      -> the server was able to decode the message
  "verified": true,     -> the signature verification was successful
  "message": "the_string_sent_from_the_client_side",     -> the actual message string
  "error": ""       -> any errors encountered; empty string means no error
}

Attachments:

~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus arch.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus arch.drawio (application/vnd.jgraph.mxfile)
Portunus arch.drawio.png (image/png)
Portunus arch.drawio-f70fdfb13757fb92cd1904b501095668eff9a5f3.png (image/png)
Portunus arch.drawio-f70fdfb13757fb92cd1904b501095668eff9a5f3.png (image/png)
Portunus Architecture.drawio (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio.png (image/png)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio.png (image/png)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio.png (image/png)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio.png (image/png)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus data flow.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~6203b4b4e5caff0070e2aa9c~Portunus data flow.drawio.tmp (application/vnd.jgraph.mxfile)
plantuml_1657027776791.svg (image/svg+xml)
plantuml_1657027776791 (text/plain)
plantuml_1657027776791.png (image/png)
plantuml_1657027776791 (text/plain)
plantuml_1657027776791.svg (image/svg+xml)
plantuml_1657027776791.png (image/png)
plantuml_1657027776791.svg (image/svg+xml)
plantuml_1657027776791 (text/plain)
plantuml_1657027776791.png (image/png)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio.png (image/png)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio.png (image/png)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio.png (image/png)
Portunus PoC System Diagram.drawio (application/vnd.jgraph.mxfile)
Portunus PoC System Diagram.drawio.png (image/png)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus PoC System Diagram.drawio (application/vnd.jgraph.mxfile)
Portunus PoC System Diagram.drawio.png (image/png)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus PoC System Diagram.drawio (application/vnd.jgraph.mxfile)
Portunus PoC System Diagram.drawio.png (image/png)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus PoC System Diagram.drawio (application/vnd.jgraph.mxfile)
Portunus PoC System Diagram.drawio.png (image/png)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus PoC System Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus PoC System Diagram.drawio (application/vnd.jgraph.mxfile)
Portunus PoC System Diagram.drawio.png (image/png)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio.png (image/png)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio.png (image/png)
~Portunus Architecture.drawio.tmp (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio (application/vnd.jgraph.mxfile)
Portunus Architecture.drawio.png (image/png)