Configurar notificaciones de cambio que incluyan datos del recursoSet up change notifications that include resource data

Microsoft Graph permite a las aplicaciones suscribirse a las notificaciones de cambio de recursos a través de webhooks.Microsoft Graph allows apps to subscribe to change notifications for resources via webhooks. Puede configurar suscripciones para incluir los datos de recursos cambiados (como el contenido de un mensaje de chat de Microsoft Teams o información de presencia de Microsoft Teams) en las notificaciones de cambios.You can set up subscriptions to include the changed resource data (such as the content of a Microsoft Teams chat message or Microsoft Teams presence information) in change notifications. Su aplicación puede entonces ejecutar su lógica de negocio sin tener que hacer una llamada a la API por separado para obtener el recurso cambiado.Your app can then run its business logic without having to make a separate API call to fetch the changed resource. Como resultado, la aplicación funciona mejor haciendo menos llamadas a la API, lo que es beneficioso en escenarios de gran escala.As a result, the app performs better by making fewer API calls, which is beneficial in large scale scenarios.

La inclusión de datos de recursos como parte de las notificaciones de cambios requiere que implemente la siguiente lógica adicional para satisfacer los requisitos de acceso a los datos y de seguridad:Including resource data as part of change notifications requires you to implement the following additional logic to satisfy data access and security requirements:

  • Administrar las notificaciones especiales del ciclo de vida de suscripción (versión preliminar) para mantener un flujo ininterrumpido de datos.Handle special subscription lifecycle notifications (preview) to maintain an uninterrupted flow of data. Microsoft Graph envía notificaciones del ciclo de vida de vez en cuando para requerir que una aplicación vuelva a autorizar, para asegurarse de que no hayan surgido problemas de acceso inesperados para incluir datos de recursos en las notificaciones de cambios.Microsoft Graph sends lifecycle notifications from time to time to require an app to re-authorize, to make sure access issues have not unexpectedly cropped up for including resource data in change notifications.
  • Validar la autenticidad de las notificaciones de cambios como procedentes de Microsoft Graph.Validate the authenticity of change notifications as having originated from Microsoft Graph.
  • Proporcionar una clave de encriptación pública y utilizar una clave privada para descifrar los datos de los recursos recibidos a través de las notificaciones de cambios.Provide a public encryption key and use a private key to decrypt resource data received through change notifications.

Datos de recurso en la carga útil de notificaciónResource data in notification payload

En general, este tipo de notificaciones de cambio incluye los siguientes datos de recursos en la carga útil:In general, this type of change notifications include the following resource data in the payload:

  • ID y tipo de la instancia de recurso modificada, devuelta en la propiedad resourceData.ID and type of the changed resource instance, returned in the resourceData property.
  • Todos los valores de propiedad de esa instancia de recurso, cifrados como se especifica en la suscripción, se devuelven en la propiedadencryptedContent.All the property values of that resource instance, encrypted as specified in the subscription, returned in the encryptedContent property.
  • O, dependiendo del recurso, propiedades específicas devueltas en la propiedad resourceData.Or, depending on the resource, specific properties returned in the resourceData property. Para obtener sólo propiedades específicas, especifíquelas como parte de la URL del recurso en la suscripción, utilizando un $select parámetro.To get only specific properties, specify them as part of the resource URL in the subscription, using a $select parameter.

Recursos admitidosSupported resources

Actualmente, los recursos chatMessage y presencia (versión preliminar) de Microsoft Teams admiten notificaciones de cambios que incluyen datos del recurso.Currently, the Microsoft Teams chatMessage as well as the Microsoft Teams presence (preview) resources supports change notifications that include resource data. Específicamente, puede configurar una suscripción que se aplique a uno de los siguientes aspectos:Specifically, you can set up a subscription that applies to one of the following:

  • Mensajes nuevos o modificados en un canal de Teams específico:/teams/{id}/channels/{id}/messagesNew or changed messages in a specific Teams channel: /teams/{id}/channels/{id}/messages
  • Mensajes nuevos o modificados en un chat de Teams específico: /chats/{id}/messagesNew or changed messages in a specific Teams chat: /chats/{id}/messages
  • Actualización de la información de presencia del usuario: /communications/presences/{id}User's presence information update: /communications/presences/{id}

Los recursos chatMessage y presencia (versión preliminar) admiten la inclusión de todas las propiedades de una instancia modificada en una notificación de cambios.The chatMessage and the presence (preview) resources support including all the properties of a changed instance in a change notification. No permiten devolver solo propiedades selectivas de la instancia.They do not support returning only selective properties of the instance.

Este artículo muestra un ejemplo de cómo suscribirse a notificaciones de cambios de mensajes en un canal de Teams, con cada notificaciones de cambios incluyendo los datos de recursos completos de la instancia modificada de chatMessage.This article walks through an example of subscribing to change notifications of messages in a Teams channel, with each change notification including the full resource data of the changed chatMessage instance.

Crear una suscripciónCreating a subscription

Para que los datos de recursos se incluyan en las notificaciones de modificación, debe especificar las siguientes propiedades, además de las que se especifican normalmente alcrear una suscripción:To have resource data included in change notifications, you must specify the following properties, in addition to those that are usually specified when creating a subscription:

  • includeResourceData que debería establecerse en true para solicitar explícitamente datos de recursos.includeResourceData which should be set to true to explicitly request resource data.
  • encryptionCertificate que contiene sólo la clave pública que utiliza Microsoft Graph para cifrar los datos de recursos.encryptionCertificate which contains only the public key that Microsoft Graph uses to encrypt resource data. Mantenga la clave privada correspondiente endescifrar el contenido.Keep the corresponding private key to decrypt the content.
  • encryptionCertificateId el cual es su propio identificador para el certificado.encryptionCertificateId which is your own identifier for the certificate. Utilice este ID. para que coincida en cada notificación de cambios, qué certificado utilizar para el descifrado.Use this ID to match in each change notification, which certificate to use for decryption.

Tenga en cuenta lo siguiente:Keep the following in mind:

Ejemplo de solicitud de suscripciónSubscription request example

El siguiente ejemplo se suscribe a los mensajes de canal que se crean o actualizan en Microsoft Teams.The following example subscribes to channel messages being created or updated in Microsoft Teams.

POST https://graph.microsoft.com/v1.0/subscriptions
Content-Type: application/json
{
  "changeType": "created,updated",
  "notificationUrl": "https://webhook.azurewebsites.net/api/resourceNotifications",
  "resource": "/teams/{id}/channels/{id}/messages",
  "includeResourceData": true,
  "encryptionCertificate": "{base64encodedCertificate}",
  "encryptionCertificateId": "{customId}",
  "expirationDateTime": "2019-09-19T11:00:00.0000000Z",
  "clientState": "{secretClientState}"
}

Respuesta a la suscripciónSubscription response

HTTP/1.1 201 Created
Content-Type: application/json

{
  "changeType": "created,updated",
  "notificationUrl": "https://webhook.azurewebsites.net/api/resourceNotifications",
  "resource": "/teams/{id}/channels/{id}/messages",
  "includeResourceData": true,
  "encryptionCertificateId": "{custom ID}",
  "expirationDateTime": "2019-09-19T11:00:00.0000000Z",
  "clientState": "{secret client state}"
}

Notificaciones del ciclo de vida de la suscripción (versión preliminar)Subscription lifecycle notifications (preview)

Algunos eventos pueden interferir con el flujo de notificaciones de cambios en una suscripción existente.Certain events can interfere with change notification flow in an existing subscription. La suscripción a las notificaciones del ciclo de vida le informan de las acciones que debe realizar para mantener un flujo ininterrumpido.Subscription lifecycle notifications inform you actions to take in order to maintain an uninterrupted flow. A diferencia de una notificación de cambio de recurso que informa de un cambio en una instancia de recurso, una notificación de ciclo de vida se refiere a la suscripción en sí misma y a su estado actual en el ciclo de vida.Unlike a resource change notification which informs a change to a resource instance, a lifecycle notification is about the subscription itself, and its current state in the lifecycle.

Para obtener más información sobre cómo recibir y responder las notificaciones del ciclo de vida (vista previa), consulte Reducir suscripciones faltantes y notificaciones de cambios (versión preliminar)For more information about how to receive, and respond to, lifecycle notifications (preview), see Reduce missing subscriptions and change notifications (preview)

Validación de la autenticidad de las notificacionesValidating the authenticity of notifications

Las aplicaciones a menudo ejecutan la lógica de negocios basada en los datos de los recursos que se incluyen en las notificaciones de cambios.Apps often run business logic based on resource data included in change notifications. Es importante haber verificado primero la autenticidad de cada notificación de cambios.Verifying the authenticity of each change notification first is important. De lo contrario, un tercero podría suplantar electrónicamente su aplicación con notificaciones de cambios falsas y hacer que se ejecute incorrectamente la lógica de negocio, lo cual puede provocar un incidente de seguridad.Otherwise, a third party can spoof your app with false change notifications and make it run its business logic incorrectly, and this can lead to a security incident.

Para las notificaciones de cambios básicas que no contienen datos del recurso, puede confirmarlas basándose simplemente en el valor clientState como se describe en Procesamiento de las notificaciones de cambios.For basic change notifications that do not contain resource data, simply validate them based on the clientState value as described in Processing the change notification. Esto es aceptable, ya que puede realizar llamadas de confianza posteriores a Microsoft Graph para obtener acceso a los datos de los recursos y, por lo tanto, el impacto de cualquier intento de falsificación es limitado.This is acceptable, as you can make subsequent trusted Microsoft Graph calls to get access to resource data, and therefore the impact of any spoofing attempts is limited.

Para las notificaciones de cambios que suministran datos de recursos, realice una validación más completa antes de procesar los datos.For change notifications that deliver resource data, perform a more thorough validation before processing the data.

En esta sección:In this section:

Token de validación en la notificación de cambiosValidation tokens in the change notification

Una notificación de cambios con datos de recursos contiene una propiedad adicional, validationTokens, que contiene una matriz de tokens JWT generados por Microsoft Graph.A change notification with resource data contains an additional property, validationTokens, which contains an array of JWT tokens generated by Microsoft Graph. Microsoft Graph genera un único token para cada par de aplicación y espacio empresarial distinto para los que hay un elemento en la matriz de valores.Microsoft Graph generates a single token for each distinct app and tenant pair for whom there is an item in the value array. Tenga en cuenta que las notificaciones de cambios pueden contener una mezcla de elementos para varias aplicaciones y espacios empresariales que se suscribieron utilizando la misma notificationUrl.Keep in mind that change notifications may contain a mix of items for various apps and tenants that subscribed using the same notificationUrl.

En el siguiente ejemplo, la notificación de cambios contiene dos elementos para la misma aplicación y para dos espacios empresariales diferentes, por lo que la matriz validationTokens contiene dos tokens que necesitan ser validados.In the following example, the change notification contains two items for the same app, and for two different tenants, therefore the validationTokens array contains two tokens that need to be validated.

{
    "value": [
          {
            "subscriptionId": "76619225-ff6b-4489-96ca-4ef547e78b22",
      "tenantId": "84bd8158-6d4d-4958-8b9f-9d6445542f95",
            "changeType": "created",
            ...
          },
      {
            "subscriptionId": "e990d58f-fd93-40af-acf7-a7c907c5d8ea",
      "tenantId": "46d9e3bd-6309-4177-a016-b256a411e30f",
            "changeType": "created",
            ...
            }
    ],
    "validationTokens": [
        "eyJ0eXAiOiJKV1QiLCJhb...",
    "cGlkYWNyIjoiMiIsImlkc..."
    ]
}

Nota: para obtener una descripción completa de los datos que se envían cuando se entregan las notificaciones de cambios, vea changeNotificationCollection.Note: for a full description of the data sent when change notifications are delivered, see changeNotificationCollection.

Cómo validarHow to validate

Si no está familiarizado con la validación de tokens, vea Principios de validación de tokens para obtener información general.If you're new to token validation, see Principles of Token Validation for an overview. Utilice un SDK, como la biblioteca System.IdentityModel.Tokens.Jwt para .NET, o una biblioteca de terceros para una plataforma diferente.Use an SDK, such as the System.IdentityModel.Tokens.Jwt library for .NET, or a third-party library for a different platform.

Sea consciente de lo siguiente:Be mindful of the following:

  • Asegúrese de enviar siempre un código de estado HTTP 202 Accepted como parte de la respuesta a la notificación de cambios.Make sure to always send an HTTP 202 Accepted status code as part of the response to the change notification.
  • Responda antes de validar la notificación de cambios (por ejemplo, si almacena notificaciones de cambios en colas para su procesamiento posterior) o después (si los procesa sobre la marcha), incluso si la validación falló.Respond before validating the change notification (for example, if you store change notifications in queues for later processing) or after (if you process them on the fly), even if validation failed.
  • Aceptar una notificación de cambios evita reintentos de entrega innecesarios y también evita que cualquier posible agente deshonesto descubra si han superado o no la validación.Accepting a change notification prevents unnecessary delivery retries and it also prevents any potential rogue actors from finding out if they passed or failed validation. Siempre puede optar por ignorar una notificación de cambios no válida después de haberla aceptado.You can always choose to ignore an invalid change notification after you have accepted it.

En particular, realice la validación en todos los tokens JWT de la colección validationTokens.In particular, perform validation on every JWT token in the validationTokens collection. Si algún token falla, considere la notificación de cambios sospechosa e investigue más a fondo.If any tokens fail, consider the change notification suspicious and investigate further.

Utilice los siguientes pasos para validar los tokens y las aplicaciones que generan tokens:Use the following steps to validate tokens and apps that generate tokens:

  1. Validar que el token no ha caducado.Validate that the token has not expired.

  2. Validar el token que no ha fue manipulado y fue emitido por la autoridad esperada, Plataforma de identidad de Microsoft:Validate the token has not been tampered with and was issued by the expected authority, Microsoft identity platform:

    • Obtenga las claves de firma desde el punto final de la configuración común:https://login.microsoftonline.com/common/.well-known/openid-configuration.Obtain the signing keys from the common configuration endpoint: https://login.microsoftonline.com/common/.well-known/openid-configuration. Esta configuración es almacenada en caché por su aplicación durante un período de tiempo.This configuration is cached by your app for a period of time. Tenga en cuenta que la configuración se actualiza con frecuencia ya que las claves de firma se giran diariamente.Be aware that the configuration is updated frequently as signing keys are rotated daily.
    • Verifique la firma del token JWT usando esas teclas.Verify the signature of the JWT token using those keys.

    No acepte token emitidos por ninguna otra autoridad.Do not accept tokens issued by any other authority.

  3. Valide que el token fue emitido para su aplicación que se está suscribiendo a notificaciones de cambio.Validate that the token was issued for your app that is subscribing to change notifications.

    Los siguientes pasos forman parte de la lógica de validación estándar en las bibliotecas de tokens de JWT y normalmente se pueden ejecutar como una llamada de función única.The following steps are part of standard validation logic in JWT token libraries and can typically be executed as a single function call.

    • Valida que el "público" del token coincida con el ID de tu aplicación.Validate the "audience" in the token matches your app ID.
    • Si tiene más de una aplicación recibiendo notificaciones de cambios, asegúrese de comprobar si hay varios ID.If you have more than one app receiving change notifications, make sure to check for multiple IDs.
  4. Crítico: Validar que la aplicación que generó el token representa al editor de notificación de cambios de Microsoft Graph.Critical: Validate that the app that generated the token represents the Microsoft Graph change notification publisher.

    • Compruebe que la propiedad appid en el token coincide con el valor esperado de0bf30f3b-4a52-48df-9a82-234910c4a086.Check that the appid property in the token matches the expected value of 0bf30f3b-4a52-48df-9a82-234910c4a086.
    • Esto garantiza que las notificaciones de cambios no se envíen desde una aplicación diferente que no sea Microsoft Graph.This ensures that change notifications are not sent by a different app that is not Microsoft Graph.

Ejemplo de token JWTExample JWT token

El siguiente es un ejemplo de las propiedades incluidas en el token JWT que se necesitan para la validación.The following is an example of the properties included in the JWT token that are needed for validation.

{
  // aud is your app's id 
  "aud": "8e460676-ae3f-4b1e-8790-ee0fb5d6148f",                           
  "iss": "https://sts.windows.net/84bd8158-6d4d-4958-8b9f-9d6445542f95/",
  "iat": 1565046813,
  "nbf": 1565046813,
  // Expiration date 
  "exp": 1565075913,                                                        
  "aio": "42FgYKhZ+uOZrHa7p+7tfruauq1HAA==",
  // appid represents the notification publisher and must always be the same value of 0bf30f3b-4a52-48df-9a82-234910c4a086 
  "appid": "0bf30f3b-4a52-48df-9a82-234910c4a086",                          
  "appidacr": "2",
  "idp": "https://sts.windows.net/84bd8158-6d4d-4958-8b9f-9d6445542f95/",
  "tid": "84bd8158-6d4d-4958-8b9f-9d6445542f95",
  "uti": "-KoJHevhgEGnN4kwuixpAA",
  "ver": "1.0"
}

Ejemplo: comprobar los tokens de validaciónExample: Verifying validation tokens

// add Microsoft.IdentityModel.Protocols.OpenIdConnect and System.IdentityModel.Tokens.Jwt nuget packages to your project
public async Task<bool> ValidateToken(string token, string tenantId, IEnumerable<string> appIds)
{
    var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
    var openIdConfig = await configurationManager.GetConfigurationAsync();
    var handler = new JwtSecurityTokenHandler();
    try
    {
    handler.ValidateToken(token, new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateIssuerSigningKey = true,
        ValidateLifetime = true,
        ValidIssuer = $"https://sts.windows.net/{tenantId}/",
        ValidAudiences = appIds,
        IssuerSigningKeys = openIdConfig.SigningKeys
    }, out _);
    return true;
    }
    catch (Exception ex)
    {
    Trace.TraceError($"{ex.Message}:{ex.StackTrace}");
    return false;
    }
}
private boolean IsValidationTokenValid(String[] appIds, String tenantId, String serializedToken) {
    try {
        JwkKeyResolver jwksResolver = new JwkKeyResolver();
        Jws<Claims> token = Jwts.parserBuilder()
        .setSigningKeyResolver(jwksResolver)
        .build()
        .parseClaimsJws(serializedToken);
        Claims body = token.getBody();
        String audience = body.getAudience();
        boolean isAudienceValid = false;
        for(String appId : appIds) {
        isAudienceValid = isAudienceValid || appId.equals(audience);
        }
        boolean isTenantValid = body.getIssuer().endsWith(tenantId + "/");
        return isAudienceValid  && isTenantValid; //nbf,exp and signature are already validated by library
    } catch (Exception e) {
        LOGGER.error("could not validate token");
        LOGGER.error(e.getMessage());
        return false;
    }
}
import jwt from 'jsonwebtoken';
import jkwsClient from 'jwks-rsa';

const client = jkwsClient({
  jwksUri: 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
});

export function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    var signingKey = key.publicKey || key.rsaPublicKey;
    callback(null, signingKey);
  });
}

export function isTokenValid(token, appId, tenantId) {
  return new Promise((resolve) => {
    const options = {
      audience: [appId],
      issuer: [`https://sts.windows.net/${tenantId}/`]
    };
    jwt.verify(token, getKey, options, (err) => {
      if (err) {
        // eslint-disable-next-line no-console
        console.error(err);
        resolve(false);
      } else {
        resolve(true);
      }
    });
  });
}

Para que funcione el ejemplo de Java, también tendrá que implementar JwkKeyResolver.For the Java sample to work, you will also need to implement the JwkKeyResolver.

package com.example.restservice;

import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.UrlJwkProvider;
import com.auth0.jwk.Jwk;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.SigningKeyResolverAdapter;
import java.security.Key;
import java.net.URI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JwkKeyResolver extends SigningKeyResolverAdapter {
    private JwkProvider keyStore;
    private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
    public JwkKeyResolver() throws java.net.URISyntaxException, java.net.MalformedURLException {
        this.keyStore = new UrlJwkProvider((new URI("https://login.microsoftonline.com/common/discovery/keys").toURL()));
    }
    @Override
    public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims) {
        try {
            String keyId = jwsHeader.getKeyId();
            Jwk pub = keyStore.get(keyId);
            return pub.getPublicKey();
        } catch (Exception e) {
            LOGGER.error(e.getMessage());
            return null;
        }
    }
}

Descifrar datos de recursos de notificaciones de cambiosDecrypting resource data from change notifications

La propiedad resourceData de una notificación de cambios incluye sólo el ID. básico y la información de tipo de una instancia de recurso.The resourceData property of a change notification includes only the basic ID and type information of a resource instance. La propiedad encryptedData contiene todos los datos de recursos, cifrados por Microsoft Graph utilizando la clave pública proporcionada en la suscripción.The encryptedData property contains the full resource data, encrypted by Microsoft Graph using the public key provided in the subscription. La propiedad también contiene los valores necesarios para la verificación y descifrado.The property also contains values required for verification and decryption. Esto se hace para aumentar la seguridad de los datos de los clientes a los que se accede mediante notificaciones de cambios.This is done to increase the security of customer data accessed via change notifications. Es su responsabilidad asegurar la clave privada para asegurar que los datos de los clientes no puedan ser descifrados por un tercero, incluso si logran interceptar las notificaciones de cambios originales.It is your responsibility to secure the private key to ensure that customer data cannot be decrypted by a third party, even if they manage to intercept the original change notifications.

En esta sección:In this section:

Administración de claves de cifradoManaging encryption keys

  1. Obtener un certificado con un par de claves asimétricas.Obtain a certificate with a pair of asymmetric keys.

    • Puede autofirmar el certificado, ya que Microsoft Graph no comprueba el emisor del certificado y usa la clave pública solo para el cifrado.You can self-sign the certificate, since Microsoft Graph does not verify the certificate issuer, and uses the public key for only encryption.

    • Utilice Azure Key Vault como la solución para crear, rotar y gestionar certificados de forma segura.Use Azure Key Vault as the solution to create, rotate, and securely manage certificates. Asegúrese de que las teclas cumplen los siguientes criterios:Make sure the keys satisfy the following criteria:

      • La clave debe ser de tipo RSAThe key must be of type RSA
      • El tamaño de la clave debe estar entre 2048 y 4096 bitsThe key size must be between 2048 and 4096 bits
  2. Exportar el certificado en formato X.509 con codificación base64, y incluir sólo la clave pública.Export the certificate in base64-encoded X.509 format, and include only the public key.

  3. Al crear una suscripción:When creating a subscription:

    • Proporcione el certificado en la propiedad encryptionCertificate, utilizando el contenido con codificación base64 en el que se exportó el certificado.Provide the certificate in the encryptionCertificate property, using the base64-encoded content that the certificate was exported in.

    • Proporcione su propio identificador en la propiedad encryptionCertificateId.Provide your own identifier in the encryptionCertificateId property.

      Este identificador le permite hacer coincidir sus certificados con las notificaciones de cambios que recibe y recuperar los certificados de su almacén de certificados.This identifier allows you to match your certificates to the change notifications you receive, and to retrieve certificates from your certificate store. El identificador puede tener hasta 128 caracteres.The identifier can be up to 128 characters.

  4. Administre la clave privada de forma segura, de modo que el código de procesamiento de notificaciones de cambios pueda acceder a la clave privada para descifrar los datos de los recursos.Manage the private key securely, so that your change notification processing code can access the private key to decrypt resource data.

Claves rotativasRotating keys

Para minimizar el riesgo de que una clave privada se vea comprometida, cambie periódicamente sus claves asimétricas.To minimize the risk of a private key becoming compromised, periodically change your asymmetric keys. Siga estos pasos para introducir un nuevo par de claves:Follow these steps to introduce a new pair of keys:

  1. Obtener un nuevo certificado con un nuevo par de claves asimétricas.Obtain a new certificate with a new pair of asymmetric keys. Utilícelo para todas las suscripciones nuevas que se estén creando.Use it for all new subscriptions being created.

  2. Actualice las suscripciones existentes con la nueva clave de certificado.Update existing subscriptions with the new certificate key.

    • Haga esto como parte de la renovación regular de la suscripción.Do this as part of regular subscription renewal.
    • O bien, enumere todas las suscripciones y proporcione la clave.Or, enumerate all subscriptions and provide the key. Utilice la operación revisión en la suscripción y actualice las propiedades encryptionCertificate y encryptionCertificateId.Use the PATCH operation on the subscription and update the encryptionCertificate and encryptionCertificateId properties.
  3. Tenga en cuenta lo siguiente:Keep in mind the following:

    • Durante un tiempo, el certificado antiguo puede seguir utilizándose para el cifrado.For a period of time, the old certificate may still be used for encryption. Su aplicación debe tener acceso tanto a los certificados antiguos como a los nuevos para poder descifrar el contenido.Your app must have access to both old and new certificates to be able to decrypt content.
    • Utilice la propiedad encryptionCertificateId en cada notificación de cambios para identificar la clave correcta a utilizar.Use the encryptionCertificateId property in each change notification to identify the correct key to use.
    • Descarte el certificado antiguo sólo cuando no haya visto ninguna notificación de cambios reciente que haga referencia al mismo.Discard of the old certificate only when you have seen no recent change notifications referencing it.

Descifrar los datos de recursosDecrypting resource data

Para optimizar el rendimiento, Microsoft Graph utiliza un proceso de cifrado de dos pasos:To optimize performance, Microsoft Graph uses a two-step encryption process:

  • Genera una clave simétrica de un solo uso y la utiliza para encriptar datos de recursos.It generates a single use symmetric key, and uses it to encrypt resource data.
  • Utiliza la clave pública asimétrica (que usted proporcionó al suscribirse) para cifrar la clave simétrica y la incluye en cada notificación de cambios de esa suscripción.It uses the public asymmetric key (that you provided when subscribing) to encrypt the symmetric key and includes it in each change notification of that subscription.

Suponga siempre que la clave simétrica es diferente para cada elemento de la notificación de cambios.Always assume that the symmetric key is different for each item in the change notification.

Para descifrar los datos de recursos, su aplicación debería realizar los pasos invertidos, utilizando las propiedades bajo encryptedContent en cada notificación de cambios:To decrypt resource data, your app should perform the reverse steps, using the properties under encryptedContent in each change notification:

  1. Utilice la propiedad encryptionCertificateId para identificar el certificado a utilizar.Use the encryptionCertificateId property to identify the certificate to use.

  2. Inicialice un componente criptográfico RSA (como el .NET RSACryptoServiceProvider) con la clave privada.Initialize an RSA cryptographic component (such as the .NET RSACryptoServiceProvider) with the private key.

  3. Descifre la clave simétrica entregada en la propiedad dataKey de cada elemento de la notificación de cambios.Decrypt the symmetric key delivered in the dataKey property of each item in the change notification.

    Utilice Optimal Asymmetric Encryption Padding (OAEP) para el algoritmo de descifrado.Use Optimal Asymmetric Encryption Padding (OAEP) for the decryption algorithm.

  4. Utilizar la clave simétrica para calcular la firma HMAC-SHA256 del valor en los datos.Use the symmetric key to calculate the HMAC-SHA256 signature of the value in data.

    Compárelo con el valor en dataSignature.Compare it to the value in dataSignature. Si no coinciden, asuma que la carga útil ha sido manipulada y no la descifre.If they do not match, assume the payload has been tampered with and do not decrypt it.

  5. Utilice la clave simétrica con un Advanced Encryption Standard (AES) (como el .NET AesCryptoServiceProvider) para descifrar el contenido de los datos.Use the symmetric key with an Advanced Encryption Standard (AES) (such as the .NET AesCryptoServiceProvider) to decrypt the content in data.

    • Utilice los siguientes parámetros de descifrado para el algoritmo AES:Use the following decryption parameters for the AES algorithm:

      • Relleno: PKCS7Padding: PKCS7
      • Modo de cifrado: CSCCipher mode: CBC
    • Ajuste el "vector de inicialización" copiando los primeros 16 bytes de la clave simétrica utilizada para el descifrado.Set the "initialization vector" by copying the first 16 bytes of the symmetric key used for decryption.

  6. El valor descifrado es una cadena JSON que representa la instancia del recurso en la notificación de cambios.The decrypted value is a JSON string that represents the resource instance in the change notification.

Ejemplo: descifrar una notificación con datos de recursos cifradosExample: decrypting a notification with encrypted resource data

El siguiente es un ejemplo de notificación de cambios que incluye valores de propiedad cifrados de una instancia de chatMessage en un mensaje de canal.The following is an example change notification that includes encrypted property values of a chatMessage instance in a channel message. La instancia se especifica por el valor @odata.id.The instance is specified by the @odata.id value.

{
    "value": [
        {
            "subscriptionId": "76222963-cc7b-42d2-882d-8aaa69cb2ba3",
            "changeType": "created",
            // Other properties typical in a resource change notification
            "resource": "teams('d29828b8-c04d-4e2a-b2f6-07da6982f0f0')/channels('19:f127a8c55ad949d1a238464d22f0f99e@thread.skype')/messages('1565045424600')/replies('1565047490246')",
            "resourceData": {
                "id": "1565293727947",
                "@odata.type": "#Microsoft.Graph.ChatMessage",
                "@odata.id": "teams('88cbc8fc-164b-44f0-b6a6-b59b4a1559d3')/channels('19:8d9da062ec7647d4bb1976126e788b47@thread.tacv2')/messages('1565293727947')/replies('1565293727947')"
            },
            "encryptedContent": {
                "data": "{encrypted data that produces a full resource}",
        "dataSignature": "<HMAC-SHA256 hash>",
                "dataKey": "{encrypted symmetric key from Microsoft Graph}",
                "encryptionCertificateId": "MySelfSignedCert/DDC9651A-D7BC-4D74-86BC-A8923584B0AB",
                "encryptionCertificateThumbprint": "07293748CC064953A3052FB978C735FB89E61C3D"
            }
        }
    ],
    "validationTokens": [
        "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU..."
    ]
}

Nota: para obtener una descripción completa de los datos que se envían cuando se entregan las notificaciones de cambios, vea changeNotificationCollection.Note: for a full description of the data sent when change notifications are delivered, see changeNotificationCollection.

Esta sección contiene algunos fragmentos de código útiles que utilizan C# y .NET para cada etapa de desencriptación.This section contains some useful code snippets that use C# and .NET for each stage of decryption.

Descifrar la clave simétricaDecrypt the symmetric key

// Initialize with the private key that matches the encryptionCertificateId.
RSACryptoServiceProvider rsaProvider = ...;        
byte[] encryptedSymmetricKey = Convert.FromBase64String(<value from dataKey property>);

// Decrypt using OAEP padding.
byte[] decryptedSymmetricKey = rsaProvider.Decrypt(encryptedSymmetricKey, fOAEP: true);

// Can now use decryptedSymmetricKey with the AES algorithm.
String storename = ""; //name/path of the jks store
String storepass = ""; //password used to open the jks store
String alias = ""; //alias of the certificate when store in the jks store, should be passed as encryptionCertificateId when subscribing and retrieved from the notification
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(storename), storepass.toCharArray());
Key asymmetricKey = ks.getKey(alias, storepass.toCharArray());
byte[] encryptedSymetricKey = Base64.decodeBase64("<value from dataKey property>");
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, asymmetricKey);
byte[] decryptedSymmetricKey = cipher.doFinal(encryptedSymetricKey);
// Can now use decryptedSymmetricKey with the AES algorithm.
const base64encodedKey = 'base 64 encoded dataKey value';
const asymetricPrivateKey = 'pem encoded private key';
const decodedKey = Buffer.from(base64encodedKey, 'base64');
const decryptedSymetricKey = crypto.privateDecrypt(asymetricPrivateKey, decodedKey);
// Can now use decryptedSymmetricKey with the AES algorithm.

Comparar la firma de datos utilizando HMAC-SHA256Compare data signature using HMAC-SHA256

byte[] decryptedSymmetricKey = <the aes key decrypted in the previous step>;
byte[] encryptedPayload = <the value from the data property, still encrypted>;
byte[] expectedSignature = <the value from the dataSignature property>;
byte[] actualSignature;

using (HMACSHA256 hmac = new HMACSHA256(decryptedSymmetricKey))
{
    actualSignature = hmac.ComputeHash(encryptedPayload);
}
if (actualSignature.SequenceEqual(expectedSignature))
{
    // Continue with decryption of the encryptedPayload.
}
else
{
    // Do not attempt to decrypt encryptedPayload. Assume notification payload has been tampered with and investigate.
}
byte[] decryptedSymmetricKey = "<the aes key decrypted in the previous step>";
byte[] decodedEncryptedData = Base64.decodeBase64("data property from encryptedContent object");
Mac mac = Mac.getInstance("HMACSHA256");
SecretKey skey = new SecretKeySpec(decryptedSymmetricKey, "HMACSHA256");
mac.init(skey);
byte[] hashedData = mac.doFinal(decodedEncryptedData);
String encodedHashedData = new String(Base64.encodeBase64(hashedData));
if (comparisonSignature.equals(encodedHashedData))
{
    // Continue with decryption of the encryptedPayload.
}
else
{
    // Do not attempt to decrypt encryptedPayload. Assume notification payload has been tampered with and investigate.
}
const decryptedSymetricKey = []; //Buffer provided by previous step
const base64encodedSignature = 'base64 encodded value from the dataSignature property';
const hmac = crypto.createHmac('sha256', decryptedSymetricKey);
hmac.write(base64encodedPayload, 'base64');
if(base64encodedSignature === hmac.digest('base64'))
{
    // Continue with decryption of the encryptedPayload.
}
else
{
    // Do not attempt to decrypt encryptedPayload. Assume notification payload has been tampered with and investigate.
}

Descifrar el contenido de los datos del recursoDecrypt the resource data content

AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider();
aesProvider.Key = decryptedSymmetricKey;
aesProvider.Padding = PaddingMode.PKCS7;
aesProvider.Mode = CipherMode.CBC;

// Obtain the intialization vector from the symmetric key itself.
int vectorSize = 16;
byte[] iv = new byte[vectorSize];
Array.Copy(decryptedSymmetricKey, iv, vectorSize);
aesProvider.IV = iv;

byte[] encryptedPayload = Convert.FromBase64String(<value from dataKey property>);

string decryptedResourceData;
// Decrypt the resource data content.
using (var decryptor = aesProvider.CreateDecryptor())
{
  using (MemoryStream msDecrypt = new MemoryStream(encryptedPayload))
  {
      using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
      {
          using (StreamReader srDecrypt = new StreamReader(csDecrypt))
          {
              decryptedResourceData = srDecrypt.ReadToEnd();
          }
      }
  }
}

// decryptedResourceData now contains a JSON string that represents the resource.
SecretKey skey = new SecretKeySpec(decryptedSymmetricKey, "AES");
IvParameterSpec ivspec = new IvParameterSpec(Arrays.copyOf(decryptedSymmetricKey, 16));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skey, ivspec);
String decryptedResourceData = new String(cipher.doFinal(Base64.decodeBase64(encryptedData)));
const base64encodedPayload = 'base64 encoded value from data property';
const decryptedSymetricKey = []; //Buffer provided by previous step
const iv = Buffer.alloc(16, 0);
decryptedSymetricKey.copy(iv, 0, 0, 16);
const decipher = crypto.createDecipheriv('aes-256-cbc', decryptedSymetricKey, iv);
let decryptedPayload = decipher.update(base64encodedPayload, 'base64', 'utf8');
decryptedPayload += decipher.final('utf8');

Consulte tambiénSee also