web-dev-qa-db-fra.com

Fournisseur de protection des données sur Asp.NET Core et Framework (générer un lien de réinitialisation de mot de passe)

Je rencontre ce problème lié au DataProtectionProvider , d'abord nous n'avions que 2 projets .NET Framework, maintenant un projet .NET Core est ajouté, ce qui m'embrouille sur la façon de faire ce qui suit: Générer un lien de réinitialisation de mot de passe à partir d'un projet .NET Framework et utilisez-le dans le projet .NET Core. Les deux utilisent la même base de données et la même table utilisateur, qui ont été rendues compatibles entre elles. .NET Framework est toujours le principal projet de génération de base de données Code-First.

Dans les deux projets .NET Frameworks, j'utilise une base de code partagée, qui a le code suivant:

//not sure where I got this from but it is part of the solution for solving
//password link generating and using in two different applications.
public class MachineKeyProtectionProvider : IDataProtectionProvider
{
    public IDataProtector Create(params string[] purposes)
    {
        return new MachineKeyDataProtector(purposes);
    }
}

public class MachineKeyDataProtector : IDataProtector
{
    private readonly string[] _purposes;

    public MachineKeyDataProtector(string[] purposes)
    {
        _purposes = purposes;
    }

    public byte[] Protect(byte[] userData)
    {
        return MachineKey.Protect(userData, _purposes);
    }

    public byte[] Unprotect(byte[] protectedData)
    {
        return MachineKey.Unprotect(protectedData, _purposes);
    }
}

Puis à l'intérieur du référentiel d'utilisateurs:

    private readonly UserManager<ApplicationUser> _userManager = null;
    private readonly RoleManager<IdentityRole> _roleManager = null;

    internal static IDataProtectionProvider DataProtectionProvider { get; private set; }
    public UserRepository(DatabaseContext dbContext)
    {
        _userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(dbContext));
        _roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(dbContext));
        _userManager.UserValidator = new UserValidator<ApplicationUser>(_userManager) { AllowOnlyAlphanumericUserNames = false };
        if (DataProtectionProvider == null)
        {
            DataProtectionProvider = new MachineKeyProtectionProvider();
        }
        _userManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, string>(DataProtectionProvider.Create("Identity"));
    }

Dans les deux projets .NET Framework, j'ai un <machineKey> ensemble. Après cela, je peux utiliser simplement:

    public string GeneratePasswordResetCode(string userId)
    {
        return _userManager.GeneratePasswordResetToken(userId);
    }

    public void ChangeUserPassword(string oldPassword, string newPassword)
    {
        string id = InfrastructureUserHelper.User.GetUserId();
        IdentityResult result = _userManager.ChangePassword(id, oldPassword, newPassword);
        ...
    }

Donc, maintenant un projet .NET Core est ajouté, qui possède déjà son propre mécanisme de réinitialisation de mot de passe, mais tous les travaux automatisés sont envoyés à partir de l'un des projets .NET Framework. La raison étant qu'un utilisateur doit définir un mot de passe pour un compte créé automatiquement.

Comment puis-je faire cela? J'ai regardé ceci: https://docs.Microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?tabs=aspnetcore2x et ceci: - Comment implémenter machineKey dans ASP.NET Core 2.

Mais je ne peux pas vraiment comprendre ce qu'est une solution simple et facile . Je préfère éviter de créer un serveur redis supplémentaire. Quelque chose de similaire à la clé machine ferait l'affaire. J'ai essayé de commencer avec cette documentation mais je n'ai pas vraiment pu comprendre quelle partie va dans le projet .NET Core et quelle partie va dans le projet .NET Framework.

J'ai essayé de jouer avec cette partie sans chance jusqu'à présent:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDataProtection().SetApplicationName("Identity")
            .SetDefaultKeyLifetime(TimeSpan.FromDays(60))
            .ProtectKeysWithDpapi();

        services.AddIdentity<ApplicationUser, ApplicationRole>().AddDefaultTokenProviders();
    }

EDIT: Je pourrais éventuellement utiliser notre stockage Blob pour cela, voir cette page: https://docs.Microsoft.com/en- us/aspnet/core/security/data-protection/configuration/overview? tabs = aspnetcore2x

L'ajout de cela semble aider un peu, les deux applications fonctionnent correctement mais toujours dans le projet .NET Framework n'utilise pas le DataProtectionProvider correct. Le UserManager ne le trouve pas (pas d'erreur ITokenProvider).

Finalement, j'ai en quelque sorte abandonné et stocké le jeton dans la base de données utilisateur, ce qui n'est pas idéal, mais trop de temps passé à essayer de résoudre quelque chose de trop peu documenté. -1 pour Microsoft.

11
CularBytes

L'API de protection des données fonctionnera de la même manière dans .NET Framework et .NET Core.

Au lieu d'une clé machine, chiffrez les clés avec un certificat X509 plutôt qu'avec l'ancienne approche par clé machine. Un certificat auto-généré est très bien car le cryptage est purement à usage interne.

Important: Si vous contrôlez les serveurs, le certificat doit être installé dans le magasin de certificats. Il y a un hic ridicule et non documenté à utiliser les surcharges ProtectWithCertificate qui acceptent une instance de certificat X509: il ne peut utiliser cette instance que pour - encrypt. Le déchiffrement échoue si le certificat n'est pas dans le magasin. Microsoft affirme qu'il s'agit d'une limitation du "cadre sous-jacent", quoi que cela signifie, mais des solutions de contournement sont disponibles (et pas compliquées). J'utilise une variation sur this et le certificat est sérialisé vers Azure Key Vault afin que nous n'ayons pas à toucher chaque serveur individuel.

Vous devez également spécifier l'une des options de persistance DPAPI pour garantir que les données sont accessibles à tous les serveurs. Puisque nous utilisons Azure, mon code ci-dessous utilise le stockage d'objets blob, mais si vous exécutez vos propres serveurs, cela pourrait être un partage réseau via PersistKeysToFileSystem.

Ma configuration dans ConfigureServices ressemble à ceci:

var x509 = GetDpApiCert(); // library utility
var container = GetBlobStorageRef(); // library

services.AddDataProtection()
    .SetApplicationName(appconfig["DpapiSiteName"])
    .ProtectKeysWithProvidedCertificate(x509)
    .PersistKeysToAzureBlobStorage(container, appconfig["DpapiFileName"]);

Voici mon script Powershell pour générer un certificat:

[CmdletBinding()]
param(
    [Parameter(Mandatory=$true)][string]$password = "",
    [Parameter(Mandatory=$true)][string]$rootDomain = ""
)

$cwd = Convert-Path .
$CerFile = "$cwd\aspnet_dpapi.cer"
$PfxFile = "$cwd\aspnet_dpapi.pfx"

# abort if files exist
if((Test-Path($PfxFile)) -or (Test-Path($CerFile)))
{
    Write-Warning "Failed, aspnet_dpapi already exists in $cwd"
    Exit
}

$cert = New-SelfSignedCertificate `
        -Subject $rootDomain `
        -DnsName $rootDomain `
        -FriendlyName "ASP.NET Data Protection $rootDomain" `
        -NotBefore (Get-Date) `
        -NotAfter (Get-Date).AddYears(10) `
        -CertStoreLocation "cert:CurrentUser\My" `
        -KeyAlgorithm RSA `
        -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" `
        -KeyLength 2048 `
        -KeyUsage KeyEncipherment, DataEncipherment
        # -HashAlgorithm SHA256 `
        # -Type Custom,DocumentEncryptionCert `
        # -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1")

$store = 'Cert:\CurrentUser\My\' + ($cert.ThumbPrint)  
$securePass = ConvertTo-SecureString -String $password -Force -AsPlainText

Export-Certificate -Cert $store -FilePath $CerFile
Export-PfxCertificate -Cert $store -FilePath $PfxFile -Password $securePass
8
McGuireV10