Guida all'onboarding del registrar del verificatore

Panoramica

Questa sezione descrive la procedura passo passo per i registrar dei verificatori per l'onboarding con il servizio di identità di Google Wallet.

In qualità di registrar del verificatore (ad esempio, una società IDV che esegue la verifica per conto di altre entità), agisci come tua autorità di certificazione (CA), firmando le richieste di identità per le parti terze che fanno affidamento a valle che gestisci.

Procedura di onboarding

Passaggio 1: invia il modulo di acquisizione, i certificati radice e accetta i Termini di servizio

Compila e invia il modulo di accettazione per l'onboarding del registrar del verificatore. In questo modulo, fornirai sia i certificati root di produzione e sandbox. Inviando questo modulo di onboarding, accetti formalmente anche i Termini di servizio del registrar di Google Wallet Verifier.

Passaggio 2: attendibilità e test della sandbox

Dopo l'invio del modulo di acquisizione, Google aggiunge il certificato radice della sandbox all'archivio attendibilità della sandbox di Google Wallet e ti invia una notifica. Puoi quindi iniziare a testare l'integrazione nella sandbox utilizzando i certificati firmati dalla radice della sandbox.

Passaggio 3: registra la dimostrazione video end-to-end

Al termine del test sandbox, registra dimostrazioni video end-to-end del flusso di verifica per la tua prima Relying Party e inviale a Google.

  • Requisiti video:
    • Registra le dimostrazioni per i flussi ospitati dal verificatore (self-hosted) e ospitati dal commerciante (ospitati da RP), a seconda dei casi.
    • Utilizza gli asset di visualizzazione del commerciante effettivi (nome, logo, URL dei Termini di servizio) e gli asset di visualizzazione dell'aggregatore nei video.
    • Mostra chiaramente l'interfaccia utente e le schermate che avviano il flusso di verifica.

Passaggio 4: approvazione e attendibilità della radice di produzione

Una volta ricevute le dimostrazioni video end-to-end, Google avvia la procedura di revisione e approvazione dei video e, parallelamente, la procedura di attendibilità del certificato radice di produzione. Una volta completate e approvate entrambe le procedure, puoi iniziare a lanciare il servizio per i tuoi RP finali downstream.

Passaggio 5: onboarding continuo di End RP

Per ogni RP finale che firmi, devi:

  • Informa Google:utilizza il modulo di onboarding del client del registrar del verificatore per comunicare a Google il nuovo RP e il suo caso d'uso previsto.
  • Configura i metadati:compila le informazioni di visualizzazione del RP (nome, logo, URL delle norme sulla privacy) e imposta un nome distinto (soggetto) univoco a livello globale nel certificato.

Specifiche tecniche

A. Profilo certificato

Le richieste devono essere firmate da certificati X.509 v3 standard generati utilizzando P-256 / ECDSA e contenenti un'estensione Google personalizzata:

  • OID estensione personalizzata: 1.3.6.1.4.1.11129.10.1
  • Criticità: non critica.
  • Contenuto:un hash SHA256 di RelyingPartyMetadataBytes, codificato in un OCTET STRING ASN.1.

B. Schema dei metadati (CBOR)

I metadati devono essere codificati in formato CBOR.

; in CDDL for CBOR encoding
; schemaVersion = "v1"

RelyingPartyMetadataBytes = #6.24(bstr .cbor RelyingPartyMetadata)


RelyingPartyMetadata = {
  "schema_version": tstr,
  "display": DisplayInfo,
  "aggregator_info": DisplayInfo  ; Optional: include to show your branding alongside the RP
}

DisplayInfo = {
  "display_name": tstr,
  "logo_uri": tstr,             ; See brand guidelines link in following paragraph
  "privacy_policy_uri": tstr
}

Il logo_uri deve rispettare le linee guida per il branding di Google Wallet.

C. Integrazione di OpenID4VP

Quando formatti la richiesta di credenziali OpenID4VP firmate, includi i metadati con codifica base64url nel campo gw_rp_metadata_bytes all'interno dell'oggetto client_metadata (come mostrato nel codice di richiesta di esempio nella sezione seguente).

Conformità e revoca

  • Monitoraggio degli abusi:Google monitora le attività dannose del RP e ti avvisa in caso di abusi rilevati.
  • Revoca immediata:devi revocare immediatamente i certificati per le RP abusive e pubblicare un elenco revoche certificati (CRL) aggiornato.
  • Audit: Google conserva log anonimizzati per garantire che le richieste di RP corrispondano ai casi d'uso registrati.

Passaggi successivi

Per iniziare l'onboarding come registrar di verifica, compila e invia il modulo di acquisizione dell'onboarding del registrar di verifica. Per l'onboarding dei client downstream successivi, utilizza il modulo di onboarding del client di registrazione del verificatore.

Per le domande frequenti sull'onboarding e l'integrazione, consulta le domande frequenti su identità e credenziali digitali.

Dettagli dell'integrazione del registrar del verificatore

La sezione seguente illustra i dettagli tecnici dell'integrazione per i registrar dei verificatori che si integrano con l'API Digital Credentials (inclusi formattazione delle richieste, crittografia delle richieste, attivazione dell'API, convalida delle risposte e implementazione delle prove a conoscenza zero).

Formati e funzionalità supportati

Google Wallet supporta i documenti di identità digitali basati su ISO mdoc.

Formattazione della richiesta

Per richiedere le credenziali da qualsiasi wallet, devi formattare la richiesta utilizzando OpenID4VP. Puoi richiedere credenziali specifiche o più credenziali in un unico oggetto dcql_query.

Esempio di richiesta JSON

Di seguito è riportato un esempio di richiesta di documento digitale requestJson per ottenere le credenziali dell'identità da qualsiasi wallet su un dispositivo Android o sul web.

{
      "requests" : [
        {
          "protocol": "openid4vp-v1-signed",
          "data": {<signed_credential_request>} // This is an object, shouldn't be a string.
        }
      ]
}

Richiedi crittografia

client_metadata contiene la chiave pubblica di crittografia per ogni richiesta. Dovrai archiviare le chiavi private per ogni richiesta e utilizzarle per autenticare e autorizzare il token che ricevi dall'app del portafoglio.

Metadati OpenID4VP integrati

Quando formatti la richiesta di credenziali, devi includere il campo gw_rp_metadata_bytes all'interno dell'oggetto client_metadata (come mostrato nel codice di richiesta di esempio riportato di seguito). Questo campo contiene i metadati della relying party codificati in Base64URL richiesti da Google Wallet per verificare la tua identità e mostrare il tuo brand all'utente.

Il parametro credential_request in requestJson contiene i seguenti campi.

Qualifica specifica

{
  "response_type": "vp_token",
  "response_mode": "dc_api.jwt", // change this to dc_api if you want to demo with a non encrypted response.
  "nonce": "1234",
  "dcql_query": {
    "credentials": [
      {
        "id": "cred1",
        "format": "mso_mdoc",
        "meta": {
          "doctype_value": "org.iso.18013.5.1.mDL"  // this is for mDL. Use com.google.wallet.idcard.1 for ID pass
        },
        "claims": [
          {
            "path": [
              "org.iso.18013.5.1",
              "family_name"
            ],
            "intent_to_retain": false // set this to true if you are saving the value of the field
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ],
            "intent_to_retain": false
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ],
            "intent_to_retain": false
          }
        ]
      }
    ]
  },
  "client_metadata": {
    "jwks": {
      "keys": [ // sample request encryption key
        {
          "kty": "EC",
          "crv": "P-256",
          "x": "pDe667JupOe9pXc8xQyf_H03jsQu24r5qXI25x_n1Zs",
          "y": "w-g0OrRBN7WFLX3zsngfCWD3zfor5-NLHxJPmzsSvqQ",
          "use": "enc",
          "kid" : "1",  // This is required
          "alg" : "ECDH-ES",  // This is required
        }
      ]
    },
    "vp_formats_supported": {
      "mso_mdoc": {
        "deviceauth_alg_values": [
          -7
        ],
        "issuerauth_alg_values": [
          -7
        ]
      }
    },
    "gw_rp_metadata_bytes": "<base64url encoded metadata string>"
  }
}

Qualsiasi credenziale idonea

Ecco la richiesta di esempio per la patente di guida digitale e la tessera ID. L'utente può procedere con una delle due.

{
  "response_type": "vp_token",
  "response_mode": "dc_api.jwt", // change this to dc_api if you want to demo with a non encrypted response.
  "nonce": "1234",
  "dcql_query": {
    "credentials": [
      {
        "id": "mdl-request",
        "format": "mso_mdoc",
        "meta": {
          "doctype_value": "org.iso.18013.5.1.mDL"
        },
        "claims": [
          {
            "path": [
              "org.iso.18013.5.1",
              "family_name"
            ],
            "intent_to_retain": false // set this to true if you are saving the value of the field
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ],
            "intent_to_retain": false
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ],
            "intent_to_retain": false
          }
        ]
      },
      {  // Credential type 2
        "id": "id_pass-request",
        "format": "mso_mdoc",
        "meta": {
          "doctype_value": "com.google.wallet.idcard.1"
        },
        "claims": [
          {
            "path": [
              "org.iso.18013.5.1",
              "family_name"
            ],
            "intent_to_retain": false // set this to true if you are saving the value of the field
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ],
            "intent_to_retain": false
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ],
            "intent_to_retain": false
          }
        ]
      }
    ]
    credential_sets : [
      {
        "options": [
          [ "mdl-request" ],
          [ "id_pass-request" ]
        ]
      }
    ]
  },
  "client_metadata": {
    "jwks": {
      "keys": [ // sample request encryption key
        {
          "kty": "EC",
          "crv": "P-256",
          "x": "pDe667JupOe9pXc8xQyf_H03jsQu24r5qXI25x_n1Zs",
          "y": "w-g0OrRBN7WFLX3zsngfCWD3zfor5-NLHxJPmzsSvqQ",
          "use": "enc",
          "kid" : "1",  // This is required
          "alg" : "ECDH-ES",  // This is required
        }
      ]
    },
    "vp_formats_supported": {
      "mso_mdoc": {
        "deviceauth_alg_values": [
          -7
        ],
        "issuerauth_alg_values": [
          -7
        ]
      }
    },
    "gw_rp_metadata_bytes": "<base64url encoded metadata string>"
  }
}

Puoi richiedere un numero qualsiasi di attributi supportati da qualsiasi documento di identità memorizzato in Google Wallet.

Richieste firmate

Le richieste firmate (richieste di autorizzazione protette da JWT) incapsulano la tua richiesta di presentazione verificabile all'interno di un token web JSON (JWT) firmato crittograficamente utilizzando la tua infrastruttura PKI, garantendo l'integrità della richiesta e dimostrando la tua identità a Google Wallet.

Prerequisiti

Prima di implementare le modifiche al codice per la richiesta firmata, assicurati di:

  • Chiave privata:per firmare la richiesta, è necessaria una chiave privata (ad es. ES256 a curva ellittica) gestita nel server.
  • Certificato:devi disporre di un certificato X.509 standard derivato dalla tua coppia di chiavi.
  • Registrazione:assicurati che il tuo certificato pubblico sia registrato in Google Wallet.

Logica di costruzione delle richieste

Per creare una richiesta, devi utilizzare la chiave privata e racchiudere il payload in un JWS.

def construct_openid4vp_request(
    doctypes: list[str],
    requested_fields: list[dict],
    nonce_base64: str,
    jwe_encryption_public_jwk: jwk.JWK,
    is_zkp_request: bool,
    is_signed_request: bool,
    state: dict,
    origin: str
) -> dict:

    # ... [Existing logic to build 'presentation_definition' and basic 'request_payload'] ...

    # ------------------------------------------------------------------
    # SIGNED REQUEST IMPLEMENTATION (JAR)
    # ------------------------------------------------------------------
    if is_signed_request:
        try:
            # 1. Load the Verifier's Certificate
            # We must load the PEM string into a cryptography x509 object
            verifier_cert_obj = x509.load_pem_x509_certificate(
                CERTIFICATE.encode('utf-8'),
                backend=default_backend()
            )

            # 2. Calculate Client ID (x509_hash)
            # We calculate the SHA-256 hash of the DER-encoded certificate.
            cert_der = verifier_cert_obj.public_bytes(serialization.Encoding.DER)
            verifier_fingerprint_bytes = hashlib.sha256(cert_der).digest()

            # Create a URL-safe Base64 hash (removing padding '=')
            verifier_fingerprint_b64 = base64.urlsafe_b64encode(verifier_fingerprint_bytes).decode('utf-8').rstrip("=")

            # Format the client_id as required by the spec
            client_id = f'x509_hash:{verifier_fingerprint_b64}'

            # 3. Update Request Payload with JAR specific fields
            request_payload["client_id"] = client_id

            # Explicitly set expected origins to prevent relay attacks
            # Format for android origin: origin = android:apk-key-hash:<base64SHA256_ofAppSigningCert>
            # Format for web origin: origin = <origin_url>
            if origin:
                request_payload["expected_origins"] = [origin]

            # 4. Create Signed JWT (JWS)
            # Load the signing private key
            signing_key = jwk.JWK.from_pem(PRIVATE_KEY.encode('utf-8'))

            # Initialize JWS with the JSON payload
            jws_token = jws.JWS(json.dumps(request_payload).encode('utf-8'))

            # Construct the JOSE Header
            # 'x5c' (X.509 Certificate Chain) is critical: it allows the wallet
            # to validate your key against the one registered in the console.
            x5c_value = base64.b64encode(cert_der).decode('utf-8')

            protected_header = {
                "alg": "ES256",                 # Algorithm (e.g., ES256 or RS256)
                "typ": "oauth-authz-req+jwt",   # Standard type for JAR
                "kid": "1",                     # Key ID
                "x5c": [x5c_value]              # Embed the certificate
            }

            # Sign the token
            jws_token.add_signature(
                key=signing_key,
                alg=None,
                protected=json_encode(protected_header)
            )

            # 5. Return the Request Object
            # Instead of returning the raw JSON, we return the signed JWT string
            # under the 'request' key.
            return {"request": jws_token.serialize(compact=True)}

        except Exception as e:
            print(f"Error signing OpenID4VP request: {e}")
            return None

    # ... [Fallback for unsigned requests] ...
    return request_payload

Attivare l'API

L'intera richiesta API deve essere generata lato server. A seconda della piattaforma, passerai il JSON generato alle API della piattaforma.

In-app (Android)

Per richiedere le credenziali dell'identità dalle tue app per Android:

Aggiorna le dipendenze

In build.gradle del tuo progetto, aggiorna le dipendenze per utilizzare Gestore delle credenziali (beta):

dependencies {
    implementation("androidx.credentials:credentials:1.5.0-beta01")
    implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}

Configurare il Gestore delle credenziali

Per configurare e inizializzare un oggetto CredentialManager, aggiungi una logica simile alla seguente:

// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)

Richiedere attributi riguardanti l'identità

Invece di specificare singoli parametri per le richieste di identità, l'app li fornisce tutti insieme come stringa JSON all'interno di CredentialOption. Il Gestore delle credenziali trasmette questa stringa JSON ai portafogli digitali disponibili senza esaminarne i contenuti. Ogni wallet è quindi responsabile di: - Analizzare la stringa JSON per comprendere la richiesta di identità. - Determinare quali delle credenziali memorizzate, se presenti, soddisfano la richiesta.

Consigliamo ai partner di creare le richieste sul server anche per le integrazioni di app per Android.

Utilizzerai requestJson da Formato richiesta come request nella chiamata alla funzione GetDigitalCredentialOption().

// The request in the JSON format to conform with
// the JSON-ified Digital Credentials API request definition.
val requestJson = generateRequestFromServer()
val digitalCredentialOption =
    GetDigitalCredentialOption(requestJson = requestJson)

// Use the option from the previous step to build the `GetCredentialRequest`.
val getCredRequest = GetCredentialRequest(
    listOf(digitalCredentialOption)
)

coroutineScope.launch {
    try {
        val result = credentialManager.getCredential(
            context = activityContext,
            request = getCredRequest
        )
        verifyResult(result)
    } catch (e : GetCredentialException) {
        handleFailure(e)
    }
}

Gestire la risposta delle credenziali

Una volta ricevuta una risposta dal wallet, verifica se la risposta è positiva e contiene la risposta credentialJson.

// Handle the successfully returned credential.
fun verifyResult(result: GetCredentialResponse) {
    val credential = result.credential
    when (credential) {
        is DigitalCredential -> {
            val responseJson = credential.credentialJson
            validateResponseOnServer(responseJson) // make a server call to validate the response
        }
        else -> {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential ${credential.type}")
        }
    }
}

// Handle failure.
fun handleFailure(e: GetCredentialException) {
  when (e) {
        is GetCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to share the credential.
        }
        is GetCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is NoCredentialException -> {
            // No credential was available.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java}")
    }
}

La risposta credentialJson contiene un identityToken (JWT) criptato, definito dal W3C. L'app Wallet è responsabile della creazione di questa risposta.

Esempio:

{
  "protocol" : "openid4vp-v1-signed",
  "data" : {
    <encrpted_response>
  }
}

Passerai questa risposta al server per verificarne l'autenticità. Puoi trovare i passaggi per convalidare la risposta delle credenziali

Web

Per richiedere le credenziali dell'identità utilizzando l'API Digital Credentials su Chrome o altri browser supportati, invia la seguente richiesta.

const credentialResponse = await navigator.credentials.get({
          digital : {
          requests : [
            {
              protocol: "openid4vp-v1-signed",
              data: {<credential_request>} // This is an object, shouldn't be a string.
            }
          ]
        }
      })

Invia la risposta di questa API al tuo server per convalidare la risposta delle credenziali

Convalida la risposta

Una volta che il wallet restituisce il identityToken (JWT) criptato, devi eseguire una rigorosa convalida lato server prima di considerare attendibili i dati.

Decriptare la risposta

Utilizza la chiave privata corrispondente alla chiave pubblica inviata in client_metadata della richiesta per decriptare il JWE. In questo modo si ottiene un vp_token.

Esempio Python:

  from jwcrypto import jwe, jwk

  # Retrieve the Private Key from Datastore
  reader_private_jwk = jwk.JWK.from_json(jwe_private_key_json_str)
  # Save public key thumbprint for session transcript
  encryption_public_jwk_thumbprint = reader_private_jwk.thumbprint()


  # Decrypt the JWE encrypted response from Google Wallet
  jwe_object = jwe.JWE()
  jwe_object.deserialize(encrypted_jwe_response_from_wallet)
  jwe_object.decrypt(reader_private_jwk)
  decrypted_payload_bytes = jwe_object.payload
  decrypted_data = json.loads(decrypted_payload_bytes)

decrypted_data genererà un file JSON vp_token contenente le credenziali

  {
    "vp_token":
    {
      "cred1": ["<base64UrlNoPadding_encoded_credential>"] // This applies to OpenID4VP 1.0 spec.
    }
  }
  1. Creare la trascrizione della sessione

    Il passaggio successivo consiste nel creare SessionTranscript da ISO/IEC 18013-5:2021 con una struttura di trasferimento specifica per Android o il web:

    SessionTranscript = [
      null,                // DeviceEngagementBytes not available
      null,                // EReaderKeyBytes not available
      [
        "OpenID4VPDCAPIHandover",
        AndroidHandoverDataBytes   // BrowserHandoverDataBytes for Web
      ]
    ]
    

    Per i trasferimenti sia su Android che sul web, devi utilizzare lo stesso nonce che hai utilizzato per generare credential_request.

    Android Handover

        AndroidHandoverData = [
          origin,             // "android:apk-key-hash:<base64SHA256_ofAppSigningCert>",
          nonce,           // nonce that was used to generate credential request,
          encryption_public_jwk_thumbprint,  // Encryption public key (JWK) Thumbprint
        ]
    
        AndroidHandoverDataBytes = hashlib.sha256(cbor2.dumps(AndroidHandoverData)).digest()
        

    Trasferimento al browser

        BrowserHandoverData =[
          origin,               // Origin URL
          nonce,               //  nonce that was used to generate credential request
          encryption_public_jwk_thumbprint,  // Encryption public key (JWK) Thumbprint
        ]
    
        BrowserHandoverDataBytes = hashlib.sha256(cbor2.dumps(BrowserHandoverData)).digest()
        

    Utilizzando SessionTranscript, la risposta del dispositivo deve essere convalidata in base alla clausola 9 dello standard ISO/IEC 18013-5:2021.

    Questa convalida include diversi passaggi:

  2. Controlla il certificato dell'emittente:estrai la catena di certificati di firma dell'emittente da issuerAuth e convalidala rispetto ai certificati radice IACA attendibili. Consulta i certificati IACA dell'emittente supportato.

  3. Verifica della firma MSO (sezione 9.1.2 di 18013-5)

  4. Calcola e controlla ValueDigests per gli elementi di dati (18013-5 Sezione 9.1.2)

  5. Verifica della firma deviceSignature (sezione 9.1.3 di 18013-5)

{
  "version": "1.0",
  "documents": [
    {
      "docType": "org.iso.18013.5.1.mDL",
      "issuerSigned": {
        "nameSpaces": {...}, // contains data elements
        "issuerAuth": [...]  // COSE_Sign1 w/ issuer PK, mso + sig
      },
      "deviceSigned": {
        "nameSpaces": 24(<< {} >>), // empty
        "deviceAuth": {
          "deviceSignature": [...] // COSE_Sign1 w/ device signature
        }
      }
    }
  ],
  "status": 0
}

Verifica dell'età che tutela la privacy (ZKP)

Per supportare le prove a conoscenza zero (ad es. verificare che un utente abbia più di 18 anni senza visualizzare la sua data di nascita esatta), modifica il formato della richiesta in mso_mdoc_zk e fornisci la configurazione zk_system_type richiesta.

Per una panoramica generale di cosa sono le ZKP e delle loro funzionalità, consulta le domande frequenti.

  ...
  "dcql_query": {
    "credentials": [{
      "id": "cred1",
      "format": "mso_mdoc_zk",
      "meta": {
        "doctype_value": "org.iso.18013.5.1.mDL"
        "zk_system_type": [
        {
          "system": "longfellow-libzk-v1",
          "circuit_hash": "f88a39e561ec0be02bb3dfe38fb609ad154e98decbbe632887d850fc612fea6f", // This will differ if you need more than 1 attribute.
          "num_attributes": 1, // number of attributes (in claims) this has can support
          "version": 5,
          "block_enc_hash": 4096,
          "block_enc_sig": 2945,
        }
        {
          "system": "longfellow-libzk-v1",
          "circuit_hash": "137e5a75ce72735a37c8a72da1a8a0a5df8d13365c2ae3d2c2bd6a0e7197c7c6", // This will differ if you need more than 1 attribute.
          "num_attributes": 1, // number of attributes (in claims) this has can support
          "version": 6,
          "block_enc_hash": 4096,
          "block_enc_sig": 2945,
        }
       ],
       "verifier_message": "challenge"
      },
     "claims": [{
         ...
      "client_metadata": {
        "jwks": {
          "keys": [ // sample request encryption key
            {
              ...

Riceverai una prova a conoscenza zero criptata dal wallet. Puoi convalidare questa prova rispetto ai certificati IACA degli emittenti utilizzando la libreria longfellow-zk di Google.

Il verifier-service contiene un server basato su Docker pronto per il deployment che ti consente di convalidare la risposta in base a determinati certificati IACA dell'emittente.

Puoi modificare il file certs.pem per gestire i certificati dell'emittente IACA di cui vuoi fidarti.

Risorse e assistenza