Configurar notificações de alteração que incluam dados de recursoSet up change notifications that include resource data

O Microsoft Graph permite que os aplicativos inscrevam-se para alterar as notificações de recursos através do webhooks.Microsoft Graph allows apps to subscribe to change notifications for resources via webhooks. É possíve configurar as assinaturas para incluir os dados alterados dos recursos (como o conteúdo de uma mensagem de bate-papo ou informações de presença do Microsoft Teams) nas notificações de alteração.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. Em seguida, seu aplicativo pode usar a lógica de negócios sem ter que fazer uma chamada à API separada para buscar o recurso alterado.Your app can then run its business logic without having to make a separate API call to fetch the changed resource. Como resultado, o aplicativo funciona melhor ao realizar menos chamadas da API, o que é benéfico em cenários de larga escala.As a result, the app performs better by making fewer API calls, which is beneficial in large scale scenarios.

A inclusão de dados de recursos como parte das notificações de alteração exige que, para atender aos requisitos de acesso e segurança de dados, você implemente a seguinte lógica adicional :Including resource data as part of change notifications requires you to implement the following additional logic to satisfy data access and security requirements:

  • Lide com as notificações especiais sobre o ciclo de vida da assinatura (visualização) para manter um fluxo ininterrupto de dados.Handle special subscription lifecycle notifications (preview) to maintain an uninterrupted flow of data. O Microsoft Graph envia notificações sobre o ciclo de vida de tempos em tempos para exigir que um aplicativo seja autorizado novamente e para garantir que os problemas de acesso não ocorram inesperadamente, incluindo dados de recursos nas notificações de alterações.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.
  • Confirme a autenticidade das notificações de alteração como originárias do Microsoft Graph.Validate the authenticity of change notifications as having originated from Microsoft Graph.
  • Forneça uma chave de criptografia pública e utilize uma chave privada para decriptar os dados de recursos recebidos por meio das notificações de alteração.Provide a public encryption key and use a private key to decrypt resource data received through change notifications.

Dados de recursos na carga de notificação Resource data in notification payload

Geralmente, esse tipo de notificação de alteração inclui os seguintes dados de recursos na carga:In general, this type of change notifications include the following resource data in the payload:

  • ID e tipo de instância de recurso alterado, retornados na propriedade resourceData.ID and type of the changed resource instance, returned in the resourceData property.
  • Todos os valores de propriedade da instância de recurso, criptografados conforme especificado na assinatura, retornados na propriedade encryptedContent.All the property values of that resource instance, encrypted as specified in the subscription, returned in the encryptedContent property.
  • Or, dependendo do recurso, propriedades específicas retornadas na propriedade resourceData.Or, depending on the resource, specific properties returned in the resourceData property. Para obter somente propriedades específicas, especifique-as como parte da URL do recurso na assinatura, usando um parâmetro $select.To get only specific properties, specify them as part of the resource URL in the subscription, using a $select parameter.

Recursos com suporteSupported resources

Atualmente, o Microsoft Teams chatMessage assim como os recursos de presença (visualização) do Microsoft Teams oferecem suporte a notificações de alterações que incluem dados de recursos.Currently, the Microsoft Teams chatMessage as well as the Microsoft Teams presence (preview) resources supports change notifications that include resource data. Especificamente, você pode configurar uma assinatura que se aplique a uma das seguintes opções:Specifically, you can set up a subscription that applies to one of the following:

  • Mensagens novas ou alteradas em um canal específico do Teams: /teams/{id}/channels/{id}/messagesNew or changed messages in a specific Teams channel: /teams/{id}/channels/{id}/messages
  • Mensagens novas ou alteradas em todos os canais do Teams em toda a organização (locatário): /teams/getAllMessagesNew or changed messages in all Teams channels: /teams/getAllMessages
  • Mensagens novas ou alteradas em um bate-papo específico do Teams: /chats/{id}/messagesNew or changed messages in a specific Teams chat: /chats/{id}/messages
  • Mensagens novas ou alteradas em todos os bate-papos em toda a organização (locatário): /chats/getAllMessagesNew or changed messages in all Teams chats: /chats/getAllMessages
  • Atualização das informações de presença do usuário: /communications/presences/{id}User's presence information update: /communications/presences/{id}

Os recursos chatMessage e presence (visualização) suportam, incluindo todas as propriedades de uma instância modificada em uma notificação de alteração.The chatMessage and the presence (preview) resources support including all the properties of a changed instance in a change notification. Eles não suportam o retorno de apenas propriedades seletivas da instância.They do not support returning only selective properties of the instance.

Este artigo mostra um exemplo de assinatura para alterar as notificações de mensagens em um canal do Teams, com cada notificação de alteração incluindo todos os dados do recurso da instância chatMessage alterada.This article walks through an example that shows you how to subscribe to change notifications for messages in a Teams channel, with each change notification including the full resource data of the changed chatMessage instance. Para obter mais detalhes sobre assinaturas baseadas em chatMessage, confira Obter notificações de alteração para mensagens de chat e canal.For more details about chatMessage-based subscriptions, see Get change notifications for chat and channel messages.

Criar uma assinaturaCreating a subscription

Para que os dados de recursos estejam incluídos nas notificações de alteração, você deve especificar as seguintes propriedades, além das que geralmente são especificadas ao criar uma assinatura: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 deve ser definido como true para solicitar explicitamente os dados de recursos.includeResourceData which should be set to true to explicitly request resource data.
  • encryptionCertificate que contém apenas a chave público que o Microsoft Graph usa para criptografar os dados de recursos.encryptionCertificate which contains only the public key that Microsoft Graph uses to encrypt resource data. Mantenha a chave privada correspondente para descriptografar o conteúdo.Keep the corresponding private key to decrypt the content.
  • encryptionCertificateId é o seu próprio identificador para o certificado.encryptionCertificateId which is your own identifier for the certificate. Use esse ID para corresponder a cada notificação de alteração cujo certificado foi utilizado para descriptografia.Use this ID to match in each change notification, which certificate to use for decryption.

Lembre-se do seguinte:Keep the following in mind:

  • Valide ambos os pontos de extremidade conforme descrito em Validação de ponto de extremidade de notificação.Validate both endpoints as described in Notification endpoint validation. Se você quiser usar a mesma URL para os dois pontos de extremidade, você receberá e responderá a duas solicitações de validação.If you choose to use the same URL for both endpoints, you will receive and respond to two validation requests.

Exemplo de solicitação de assinaturaSubscription request example

O exemplo a seguir assina as mensagens de canal que estão sendo criadas ou atualizadas no 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}"
}

Resposta de assinaturaSubscription 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}"
}

Notificações do ciclo de vida da assinatura (visualização)Subscription lifecycle notifications (preview)

Certos eventos podem interferir no fluxo de notificação de alterações em uma assinatura existente.Certain events can interfere with change notification flow in an existing subscription. As notificações sobre o ciclo de vida da assinatura informam as ações a serem tomadas para manter um fluxo ininterrupto.Subscription lifecycle notifications inform you actions to take in order to maintain an uninterrupted flow. Ao contrário de uma notificação de alteração de recurso que informa uma alteração em uma instância de recurso, uma notificação do ciclo de vida é sobre a própria assinatura e seu estado atual no 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 obter mais informações sobre como receber e responder a, notificações de ciclo de vida (visualização), confira Reduzir assinaturas ausentes e alterar as notificações (visualização)For more information about how to receive, and respond to, lifecycle notifications (preview), see Reduce missing subscriptions and change notifications (preview)

Validando a autenticidade das notificaçõesValidating the authenticity of notifications

Os aplicativos geralmente executam a lógica comercial com base nos dados de recursos incluídos nas notificações de alterações.Apps often run business logic based on resource data included in change notifications. Verificar a autenticidade de cada notificação de alteração primeiro é importante.Verifying the authenticity of each change notification first is important. Caso contrário, um terceiro poderá imitar seu aplicativo com notificações de alteração falsa e fazê-lo executar a lógica de negócios incorretamente, e isso pode levar a um incidente de segurança.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 notificações básicas de alterações que não contêm dados de recursos, simplesmente valide-as com base no valor clientState conforme descrito em Processando a notificação de alteração.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. Isso é aceitável, uma vez que você pode fazer chamadas confiáveis subsequentes do Microsoft Graph para obter acesso ao dados do recurso, portanto, o impacto das tentativas de falsificação é 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 notificações de alteração que entregam dados de recursos, execute uma validação mais completa antes de processar os dados.For change notifications that deliver resource data, perform a more thorough validation before processing the data.

Nesta seção:In this section:

Tokens de validação na notificação de alteraçãoValidation tokens in the change notification

Uma notificação de alteração com os dados do recurso contém uma propriedade adicional, validationTokens, com uma matriz de tokens JWT gerados pelo Microsoft Graph.A change notification with resource data contains an additional property, validationTokens, which contains an array of JWT tokens generated by Microsoft Graph. O Microsoft Graph gera um token único para cada aplicativo distinto e um par de locatários onde existe um item no conunto valor.Microsoft Graph generates a single token for each distinct app and tenant pair for whom there is an item in the value array. Tenha em mente que as notificações de alterações podem conter uma mistura de itens para vários aplicativos e locatários que fizeram assinatura usando o mesmo notificationUrl.Keep in mind that change notifications may contain a mix of items for various apps and tenants that subscribed using the same notificationUrl.

No exemplo a seguir, a notificação de alteração contém dois itens para o mesmo aplicativo e para dois locatários diferentes, portanto, o conjunto validationTokens contém dois tokens que precisam 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 obter uma descrição completa dos dados enviados quando as notificações de alterações são entregues, consulte changeNotificationCollection.Note: for a full description of the data sent when change notifications are delivered, see changeNotificationCollection.

Como validarHow to validate

Se você não conhece a validação de token, consulte Princípios de Validação de Token para obter uma visão geral.If you're new to token validation, see Principles of Token Validation for an overview. Use um SDK, como a System.IdentityModel.Tokens.Jwt biblioteca para .NET ou uma biblioteca de terceiros para uma plataforma diferente.Use an SDK, such as the System.IdentityModel.Tokens.Jwt library for .NET, or a third-party library for a different platform.

Fique atento ao seguinte:Be mindful of the following:

  • Certifique-se de sempre enviar um HTTP 202 Accepted código de status como parte da resposta à notificação de alteração.Make sure to always send an HTTP 202 Accepted status code as part of the response to the change notification.
  • Responda antes de validar a notificação de alteração (por exemplo, se você armazenar as notificações de alteração em filas para processamento posterior) ou depois (se você as processar instantaneamente), mesmo se a validação falhar.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.
  • A aceitação de uma notificação de alteração evita novas tentativas desnecessárias de entrega e também impede que possíveis atores invasores descubram se foram ou não aprovados na validação.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. Você sempre pode optar por ignorar uma notificação de alteração inválida após aceitá-la.You can always choose to ignore an invalid change notification after you have accepted it.

Especificamente, realize a validação em todos os tokens JWT na coleção validationTokens.In particular, perform validation on every JWT token in the validationTokens collection. Se algum token falhar, considere a notificação de alteração suspeita e investigue mais.If any tokens fail, consider the change notification suspicious and investigate further.

Use as etapas a seguir para validar tokens e aplicativos que geram tokens:Use the following steps to validate tokens and apps that generate tokens:

  1. Valide se o token não expirou.Validate that the token has not expired.

  2. Valide se o token não foi adulterado e foi emitido pela autoridade esperada, plataforma de identidade da Microsoft:Validate the token has not been tampered with and was issued by the expected authority, Microsoft identity platform:

    • Obtenha as chaves de assinatura do ponto de extremidade de configuração comum: 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. Essa configuração é armazenada em cache pelo aplicativo por um período de tempo.This configuration is cached by your app for a period of time. Lembre-se de que a configuração é atualizada frequentemente, uma vez que as chaves de assinatura são giradas diariamente.Be aware that the configuration is updated frequently as signing keys are rotated daily.
    • Verifique a assinatura do token JWT usando essas chaves.Verify the signature of the JWT token using those keys.

    Não aceite tokens emitidos por outra autoridade.Do not accept tokens issued by any other authority.

  3. Valide se o token foi emitido para o aplicativo que está inscrito para alterar as notificações.Validate that the token was issued for your app that is subscribing to change notifications.

    As etapas a seguir fazem parte da lógica de validação padrão nas bibliotecas de token JWT e podem ser tipicamente executadas como uma única chamada de função.The following steps are part of standard validation logic in JWT token libraries and can typically be executed as a single function call.

    • Valide a “audiência” no token que corresponde à ID do seu aplicativo.Validate the "audience" in the token matches your app ID.
    • Se você tiver mais de um aplicativo recebendo notificações de alterações, verifique a existencia de vários IDs.If you have more than one app receiving change notifications, make sure to check for multiple IDs.
  4. Crítico: valide se o aplicativo que gerou o token representa o distribuidor de notificação de alteração do Microsoft Graph.Critical: Validate that the app that generated the token represents the Microsoft Graph change notification publisher.

    • Verifique se a propriedade appid no token corresponde ao valor esperado de 0bf30f3b-4a52-48df-9a82-234910c4a086.Check that the appid property in the token matches the expected value of 0bf30f3b-4a52-48df-9a82-234910c4a086.
    • Isso garante que as notificações de alteração não sejam enviadas por um aplicativo diferente do Microsoft Graph.This ensures that change notifications are not sent by a different app that is not Microsoft Graph.

Exemplo de Token JWT Example JWT token

A seguir, é apresentado um exemplo das propriedades incluídas no token JWT que são necessárias para validação.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"
}

Exemplo: verificação de tokens de validaçãoExample: 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 o exemplo do Java funcione, também será necessário implementar o 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;
        }
    }
}

Descriptografar os dados de recursos de notificações de alteraçãoDecrypting resource data from change notifications

A propriedade resourceData de uma notificação de alteração inclui apenas o ID básico e as informações do tipo de uma instância de recurso.The resourceData property of a change notification includes only the basic ID and type information of a resource instance. A propriedade encryptedData contém os dados de recursos completos, criptografados pelo Microsoft Graph usando a chave pública fornecida na assinatura.The encryptedData property contains the full resource data, encrypted by Microsoft Graph using the public key provided in the subscription. A propriedade também contém valores necessários para verificação e descriptografia.The property also contains values required for verification and decryption. Isso é feito para aumentar a segurança dos dados do cliente acessados por meio de notificações de alterações.This is done to increase the security of customer data accessed via change notifications. É da sua responsabilidade proteger a chave privada para garantir que os dados do cliente não sejam decriptados por terceiros, mesmo que eles consigam interceptar as notificações de alteração originais.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.

Nesta seção:In this section:

Gerenciar as chaves de criptografiaManaging encryption keys

  1. Obtenha um certificado com um par de chaves assimétricas.Obtain a certificate with a pair of asymmetric keys.

    • Você pode assinatura o certificado sozinho, uma vez que o Microsoft Graph não verifica o emissor do certificado e usa a chave pública somente para criptografia.You can self-sign the certificate, since Microsoft Graph does not verify the certificate issuer, and uses the public key for only encryption.

    • Use o Cofre da Chave do Azure como a solução para criar, girar e gerenciar certificados com segurança.Use Azure Key Vault as the solution to create, rotate, and securely manage certificates. Cerifique-se de que as chaves atendem aos seguintes critérios:Make sure the keys satisfy the following criteria:

      • A chave deve ser do tipo RSAThe key must be of type RSA
      • O tamanho da chave deve estar entre 2048 e 4096 bitsThe key size must be between 2048 and 4096 bits
  2. Exportar o certificar no formato base64-encoded X.509 e incluir apenas a chave pública.Export the certificate in base64-encoded X.509 format, and include only the public key.

  3. Ao criar uma assinatura:When creating a subscription:

    • Forneça o certificado na propriedade encryptionCertificate usando o conteúdo base64-encoded no qual o certificado foi exportado.Provide the certificate in the encryptionCertificate property, using the base64-encoded content that the certificate was exported in.

    • Forneça seu próprio identificador na propriedade encryptionCertificateId.Provide your own identifier in the encryptionCertificateId property.

      Esse identificador permite corresponder seus certificados às notificações de alterações recebidas e recuperar certificados do seu repositório de certificados.This identifier allows you to match your certificates to the change notifications you receive, and to retrieve certificates from your certificate store. O identificador pode ter no máximo 128 caracteres.The identifier can be up to 128 characters.

  4. Gerencie com segurança a chave privada com para que seu código de processamento de notificação de alteração acesse a chave privada para decriptar os dados do recurso.Manage the private key securely, so that your change notification processing code can access the private key to decrypt resource data.

Chaves de rotaçãoRotating keys

Para minimizar o risco de uma chave privada ser comprometida, altere periodicamente suas chaves assimétricas.To minimize the risk of a private key becoming compromised, periodically change your asymmetric keys. Siga estas etapas para introduzir um novo par de chaves:Follow these steps to introduce a new pair of keys:

  1. Obtenha um novo certificado com um novo par de chaves assimétricas.Obtain a new certificate with a new pair of asymmetric keys. Use-o para todas as novas assinaturas sendo criadas.Use it for all new subscriptions being created.

  2. Atualize as assinaturas existentes com a nova chave de certificado.Update existing subscriptions with the new certificate key.

    • Faça isso como parte da renovação da assinatura regular.Do this as part of regular subscription renewal.
    • Ou enumere todas as assinaturas e forneça a chave.Or, enumerate all subscriptions and provide the key. Use a operação PATCH na assinatura e atualize as propriedades encryptionCertificate e encryptionCertificateId.Use the PATCH operation on the subscription and update the encryptionCertificate and encryptionCertificateId properties.
  3. Lembre-se do seguinte:Keep in mind the following:

    • Por um período de tempo, o certificado antigo ainda pode ser usado para criptografia.For a period of time, the old certificate may still be used for encryption. Seu aplicativo deve ter acesso a certificados novos e antigos para poder descriptografar o conteúdo.Your app must have access to both old and new certificates to be able to decrypt content.
    • Use a propriedade encryptionCertificateId em cada notificação de alteração para identificar a chave correta a ser utilizada.Use the encryptionCertificateId property in each change notification to identify the correct key to use.
    • Descarte o certificado antigo somente quando não houver notificações de alterações recentes fazendo referência a ele.Discard of the old certificate only when you have seen no recent change notifications referencing it.

Descriptografar dados de recursosDecrypting resource data

Para otimizar o desempenho, o Microsoft Graph usa um processo de criptografia de duas etapas:To optimize performance, Microsoft Graph uses a two-step encryption process:

  • Ele gera uma chave simétrica de uso único e a usa para criptografar os dados do recurso.It generates a single use symmetric key, and uses it to encrypt resource data.
  • Ele usa a chave pública assimétrica (que você forneceu ao fazer a assinatura) para criptografar a chave simétrica e a incluir em cada notificação de alteração desta assinatura.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.

Sempre assuma que a chave simétrica é diferente para cada item na notificação de alteração.Always assume that the symmetric key is different for each item in the change notification.

Para decriptar os dados de recursos, o seu aplicativo deve executar as etapas inversas, usando as propriedades encryptedContent em cada notificação de alteração:To decrypt resource data, your app should perform the reverse steps, using the properties under encryptedContent in each change notification:

  1. Use a propriedade encryptionCertificateId para identificar o certificado a ser usado.Use the encryptionCertificateId property to identify the certificate to use.

  2. Inicialize um componente criptográfico da RSA (como o .NET RSACryptoServiceProvider) com a chave privada.Initialize an RSA cryptographic component (such as the .NET RSACryptoServiceProvider) with the private key.

  3. Decripte a chave simétrica entregue na propriedade dataKey de cada item na notificação de alteração.Decrypt the symmetric key delivered in the dataKey property of each item in the change notification.

    Use o Preenchimento de Criptografia Assimétrica Ideal (OAEP) para o algoritmo de descriptografia.Use Optimal Asymmetric Encryption Padding (OAEP) for the decryption algorithm.

  4. Use a chave simétrica para calcular a assinatura HMAC-SHA256 do valor em dados.Use the symmetric key to calculate the HMAC-SHA256 signature of the value in data.

    Compare-o com o valor em dataSignature.Compare it to the value in dataSignature. Se eles não corresponderem, considere que a carga foi adulterada e não a descriptografe.If they do not match, assume the payload has been tampered with and do not decrypt it.

  5. Use a chave simétrica com a criptografia AES (como o .NET AesCryptoServiceProvider) para descriptografar o conteúdo em dados.Use the symmetric key with an Advanced Encryption Standard (AES) (such as the .NET AesCryptoServiceProvider) to decrypt the content in data.

    • Use os seguintes parâmetros de descriptografia para o algoritmo AES:Use the following decryption parameters for the AES algorithm:

      • Preenchimento: PKCS7Padding: PKCS7
      • Modo de criptografia: CBCCipher mode: CBC
    • Defina o “vetor de inicialização” copiando os primeiros 16 bytes da chave simétrica usada para descriptografia.Set the "initialization vector" by copying the first 16 bytes of the symmetric key used for decryption.

  6. O valor decriptado é uma sequência JSON que representa a instância do recurso na notificação de alteração.The decrypted value is a JSON string that represents the resource instance in the change notification.

Exemplo: decriptando uma notificação com dados de recurso criptografadosExample: decrypting a notification with encrypted resource data

A seguir, é apresentado um exemplo de notificação de alteração que inclui valores de propriedade criptografados de uma chatMessage instância em uma mensagem de canal.The following is an example change notification that includes encrypted property values of a chatMessage instance in a channel message. A instância é especificada pelo @odata.id valor.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 obter uma descrição completa dos dados enviados quando as notificações de alterações são entregues, consulte changeNotificationCollection.Note: for a full description of the data sent when change notifications are delivered, see changeNotificationCollection.

A seção contém alguns trechos de código úteis que usam C# e .NET em cada etapa da descriptografia.This section contains some useful code snippets that use C# and .NET for each stage of decryption.

Descriptografar a chave 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 assinatura de dados usando 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.
}

Descriptografar o conteúdo dos dados de recursosDecrypt 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');

Confira tambémSee also