Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System.Security.Cryptography.AesGcm - decryption allows truncated tags #71366

Closed
sdrapkin opened this issue Jun 28, 2022 · 17 comments · Fixed by #86211
Closed

System.Security.Cryptography.AesGcm - decryption allows truncated tags #71366

sdrapkin opened this issue Jun 28, 2022 · 17 comments · Fixed by #86211
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Security breaking-change Issue or PR that represents a breaking API or functional change over a prerelease.
Milestone

Comments

@sdrapkin
Copy link

sdrapkin commented Jun 28, 2022

Edited by @vcsjones

API Proposal:

As the issue outlines below, this proposes changes to AesGcm because it's current design makes it prone to misuse of allowing truncated authentication tags during decryption.

This proposes new constructors that accept the authentication tag size. During decryption or encryption, the tag parameter must match this specified exactly. This means the caller must decide at instantiation what tag size is correct, not what callers of Encrypt or Decrypt are provided.

The current constructors will continue to have the same existing behavior. However, since this is an issue around misuse, this also proposes obsoleting the existing constructors to steer people toward using constructors which take the authentication tag size.

namespace System.Security.Cryptography;

public partial class AesGcm {

+  public AesGcm(byte[] key, int tagSizeInBytes);
+  public AesGcm(ReadOnlySpan<byte> key, int tagSizeInBytes);

+  [Obsolete("AesGcm should indicate the required tag size for encryption and decryption. Use a constructor that accepts the tag size.", DiagnosticId = "SYSLIB9999")]
   public AesGcm(byte[] key);
+  [Obsolete("AesGcm should indicate the required tag size for encryption and decryption. Use a constructor that accepts the tag size.", DiagnosticId = "SYSLIB9999")]
   public AesGcm(ReadOnlySpan<byte> key);

+  public int? TagSizeInBytes { get; }
}

Original issue:

void Main()
{
	Span<byte> plaintext = Encoding.UTF8.GetBytes("[Hello World!]");
	Span<byte> key = new byte[32];
	Span<byte> nonce = new byte[12];
	Span<byte> ciphertext = new byte[plaintext.Length];
	Span<byte> tag = new byte[16];

	using var gcm = new AesGcm(key);

	Encoding.UTF8.GetString(plaintext).Dump();

	gcm.Encrypt(nonce, plaintext, ciphertext, tag);
	plaintext.Clear();
	gcm.Decrypt(nonce, ciphertext, tag.Slice(0, 12), plaintext);
	Encoding.UTF8.GetString(plaintext).Dump();
}

Scenario:
A naive (documentation-reading) user uses AesGcm to encrypt plaintext and produce ciphertext with the maximum allowed tag of 16 bytes (ie. naively expecting ~128 bits of tag strength). However the Decrypt API is quite happy to accept truncated 12-byte tags, which means that the attacker can truncate all tags to 12 bytes for ~96 bits of tag strength (assuming decryption oracle is readily available).

  • AesGcm n-bit tags provide only n-k bits of authentication security for messages that are 2^k blocks long
  • Large files at ~64GiB size are quite common, and it's plausible to assume that close to 2^32 blocks are routinely encrypted

Would that suggest that AesGcm tags as implemented in .NET realistically provide close to 96-32=64 bits of security? If yes - is anyone concerned about that? (both in terms of implementation/security and in terms of documentation). If no - please explain and help me understand what I'm missing. Thanks.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jun 28, 2022
@ghost
Copy link

ghost commented Jun 28, 2022

Tagging subscribers to this area: @dotnet/area-system-security, @vcsjones
See info in area-owners.md if you want to be subscribed.

Issue Details
void Main()
{
	Span<byte> plaintext = Encoding.UTF8.GetBytes("[Hello World!]");
	Span<byte> key = new byte[32];
	Span<byte> nonce = new byte[12];
	Span<byte> ciphertext = new byte[plaintext.Length];
	Span<byte> tag = new byte[16];

	using var gcm = new AesGcm(key);

	Encoding.UTF8.GetString(plaintext).Dump();

	gcm.Encrypt(nonce, plaintext, ciphertext, tag);
	plaintext.Clear();
	gcm.Decrypt(nonce, ciphertext, tag.Slice(0, 12), plaintext);
	Encoding.UTF8.GetString(plaintext).Dump();
}

Scenario:
A naive (documentation-reading) user uses AesGcm to encrypt plaintext and produce ciphertext with the maximum allowed tag of 16 bytes (ie. naively expecting ~128 bits of tag strength). However the Decrypt API is quite happy to accept truncated 12-byte tags, which means that the attacker can truncate all tags to 12 bytes for ~96 bits of tag strength (assuming decryption oracle is readily available).

  • AesGcm n-bit tags provide only n-k bits of authentication security for messages that are 2^k blocks long
  • Large files at ~64GiB size are quite common, and it's plausible to assume that close to 2^32 blocks are routinely encrypted

Would that suggest that AesGcm tags as implemented in .NET realistically provide close to 96-32=64 bits of security? If yes - is anyone concerned about that? (both in terms of implementation/security and in terms of documentation). If no - please explain and help me understand what I'm missing. Thanks.

Author: sdrapkin
Assignees: -
Labels:

area-System.Security

Milestone: -

@vcsjones
Copy link
Member

vcsjones commented Jun 28, 2022

Tag truncation is just how AES GCM supports short tags, as demonstrated by:

using System.Security.Cryptography;

AesGcm aes = new AesGcm(new byte[128 / 8]);
byte[] nonce = new byte[96 / 8];

byte[] tag16 = new byte[16];
aes.Encrypt(nonce, new byte[32], new byte[32], tag16);

byte[] tag12 = new byte[12];
aes.Encrypt(nonce, new byte[32], new byte[32], tag12);

Console.WriteLine(Convert.ToHexString(tag16));
Console.WriteLine(Convert.ToHexString(tag12));

// Prints:
// 40490AF4805606B2A3A2E793E3500066
// 40490AF4805606B2A3A2E793

This is not a .NET-ism, either. For example, Ruby:

require 'openssl'

cipher = OpenSSL::Cipher.new('aes-128-gcm').encrypt
cipher.key = "\0" * 16
cipher.iv = "\0" * 12
cipher.auth_data = ""

encrypted = cipher.update('a' * 32) + cipher.final
tag16 = cipher.auth_tag

cipher = OpenSSL::Cipher.new('aes-128-gcm').decrypt
cipher.key = "\0" * 16
cipher.iv = "\0" * 12
tag12 = tag16.slice(0, 12)
cipher.auth_tag = tag12
decrypted = cipher.update(encrypted) + cipher.final

puts decrypted

Ruby will even let you truncate it below 12 octets.

Since the AES-GCM implementation supports short tags, Decrypt can't tell the difference between a tag that was truncated legitimately or illegitimately.

Consumers of AesGcm should ensure that the tag length that is being supplied is the expected length.

@sdrapkin
Copy link
Author

sdrapkin commented Jun 28, 2022

Since the AES-GCM implementation supports short tags, Decrypt can't tell the difference between a tag that was truncated legitimately or illegitimately. Consumers of AesGcm should ensure that the tag length that is being supplied is the expected length.

Thanks for confirming that my understanding is correct.

I see 2 issues:

  1. Insufficient documentation, missing guidance on what AesGcm consumers are expected to ensure for tag lengths. The description for the tag parameter to .Decrypt states: The authentication tag produced for this message during encryption (which is clearly incorrect when truncated tags are accepted, since they were likely never produced by the Encryption).
  2. Let's assume documentation is updated (ex. the above quote is added to it). How are developers supposed to comply?
    • option-1: validate a fixed-tag-of-length-N expectation. This is practical, but already makes Decrypt unsafe for direct exposure (ie. requires a tag-length validator in front of it). N must become part of the caller-state/protocol, since it is not part of the Encrypt output.
    • option-2: variable-length-tags. This would be very challenging/unsafe/insecure to implement with AesGcm primitive API alone. Our key assumption is that ciphertext and tag are malleable (which is precisely what the tag is supposed to guard against). While the tag-length could be made non-malleable (option-1), ciphertext-length cannot be made non-malleable. While Microsoft (rightfully) does not prescribe how the tag and ciphertext are to be stored, in the vast majority of uses they are concatenated into one bytestream (order is not relevant). Thus, it is not possible to calculate the correct value of tag-length N from the (ciphertext+tag) bytestream. One approach is to call Decrypt 5 times for each of the 5 possible values of N (not an appealing idea for obvious reasons).

Given that it seems option-1 is the only practical/reasonable choice, it would also need to be added to AesGcm documentation (ie. guidance that N must be explicitly fixed for safe operation of Decrypt, which includes tag validation).

Finally, it might be a good idea to also document a caution that AesGcm tag of length N bytes should not be assumed to provide N*8 bits of authentication strength in all scenarios.

Thoughts?

@sdrapkin
Copy link
Author

What GCM specification does S.S.C.AesGcm actually implement?

  • RFC 5116 defines and AEAD interface as well as 2 GCM-mode algorithms implementing this interface: AEAD_AES_128_GCM and AEAD_AES_256_GCM. Both fix the tag-length to 16 bytes.
  • NIST SP800-38D specifies GCM-mode, and even states that GCM is a "mode of operation of AES algorithm" (thus making the "AES" part of AES-GCM or AesGcm redundant). More importantly, it states that "a single, fixed value for [tag length t] from among the supported choices shall be associated with each key." (which S.S.C.AesGcm does not do)
  • The original GCM-spec by McGrew and Viega defines T as the Encryption-step-produced tag, T' as the computed Decryption-step-produced tag, and states that "If the two tags match (in both length and value), then the ciphertext is returned. Otherwise, the special symbol FAIL is returned." (which is obviously not how S.S.C.AesGcm behaves)

@sdrapkin
Copy link
Author

sdrapkin commented Jun 28, 2022

Proposed improvements to AesGcm APIs (non-breaking, backwards-compatible):

public class AesGcm
{
	// existing ctors
	public AesGcm(ReadOnlySpan<byte> key) { }
	public AesGcm(byte[] key) { }

	// new field
	readonly int _fixedTagLength = 0;

	// new ctor
	public AesGcm(ReadOnlySpan<byte> key, int fixedTagLength)
	{
		_fixedTagLength = fixedTagLength; // also do [12-16] range check
	}

	// new ctor
	public AesGcm(byte[] key, int fixedTagLength)
	{
		_fixedTagLength = fixedTagLength; // also do [12-16] range check
	}

Change internal implementation for

static void CheckParameters(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> tag)

by adding the following check to it:

int fixedTagLength = _fixedTagLength;
if (fixedTagLength != 0 && fixedTagLength != tag.Length)
{
	throw new ArgumentException(System.SR.Cryptography_InvalidTagLength, "tag");
}

@sdrapkin
Copy link
Author

Example of a likely vulnerability in Microsoft's code due to incorrect assumptions of what AesGcm.Decrypt does/doesn't do:

MS domain: AzureAD
Repo: azure-activedirectory-identity-model-extensions-for-dotnet
Code in question

If I traced their call-paths right, the authenticationTag is coming straight from JWT token field, which is treated as any-length base64-encoded string. An attacker can be truncating all JWT tokens to 12-byte Auth-tags which reduces GCM tag strength to 96 bits of security at most. I doubt AzureAD team realizes this...

I'm sure there are many more cases like this one, where the authentication Tag lives separately from ciphertext (ie. not a mere take X fixed bytes from the end of ciphertext slicing).

AesGcm not having safe-by-default constructors (that default to fixed tag of 16 bytes) is going to be the cause of numerous security vulnerabilities. In the meantime I've created AesGcmStrict wrapper to help deal with this.

@bartonjs
Copy link
Member

We're exposing GCM in the same way the underlying providers are, which leaves the algorithms loose tag processing as an application problem. (I'd've included the tag length in the tag calculation, but I guess the GCM designers disagree)

Generally the application context should know what the tag size is. In the case of JWA, it's supposed to always be 128 bits (https://datatracker.ietf.org/doc/html/rfc7518#section-4.7), which is up to the implementer to verify.

Adding ctor overloads for the expected tag size is reasonable, and not limiting to anyone following the SP800-38D recommendation, and I guess helps provide a standardized exception message to anyone who chooses to utilize it.

@bartonjs bartonjs added this to the 8.0.0 milestone Jul 13, 2022
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Jul 13, 2022
@sdrapkin
Copy link
Author

Thanks for the response. I would encourage .NET team to consider adding tag-length-fixing constructor overloads to AesGcm, based on @bartonjs reply.

Do note that the AesCcm behavior does not have the same problem - ie. truncated AesCcm tags (that are still length-permitted based on AesCcm.TagByteSizes) will fail to decrypt. This creates 2 different (and undocumented) behaviors for AesGcm and AesCcm APIs in .NET.

Just do a simple sanity test: ask any of your esteemed cryptography-aware colleagues the following simple question:

I've got an AesGcm-encrypted ciphertext, and I'm trying to decrypt it with a truncated tag - will it work?

..and watch how many no's you're going to get.

I believe it would be somewhat irresponsible to take the position that "loose AesGcm tag processing" is application's problem. Arguing that .NET team is merely exposing the deficiencies of underlying GCM providers is a dodge at best (at the very least you cannot vouch for all possible GCM providers on all supported current and future .NET-running platforms). However let me provide a specification-based argument:

NIST SP 800-38D :

  • Section 5.2.1.2 Output Data
    • t is the bit-length of tag T
    • Quote: "A single, fixed value for t from among the supported choices shall be associated with each key."
  • Section 5.2.2 Authenticated Decryption Function
    • Quote: "Given the selection of an approved block cipher, key, and an associated tag length, the inputs to
      the authenticated decryption function are values for IV, A, C, and T, as described in Sec. 5.2.1 above."

What this means is that for a specific instance of AesGcm implemented to the spec:

var one_gcm = new AesGcm(key);

...one_gcm.Decrypt(...) MUST NOT accept tag-lengths that differ from those produced by one_gcm.Encrypt(...)

Ask any FIPS auditor/validator lab, and they should tell you that the GCM spec wording above is specifically intended to prevent tag truncations under a fixed key. The sister CCM spec (800-38C) which targets the same AEAD behavior has the same constraint:

  • (800-38C): "The MAC that is generated within CCM is an internal variable, denoted T. The bit length of T,
    denoted Tlen, is a parameter of the mode that shall be fixed for all invocations of CCM with the given key."

Thus it is quite likely that the current status of AesGcm in .NET is that it is not spec-compliant.

@vcsjones
Copy link
Member

vcsjones commented Jul 14, 2022

Thus it is quite likely that the current status of AesGcm in .NET is that it is not spec-compliant.

I'm not quite sure I follow this logic.

Given the selection of an approved block cipher, key, and an associated tag length, the inputs to

The "selected" tag length just happens to be whatever tag.Length is.

CCM does not have this problem because CCM bakes the MAC length in to the control information block:

The leading octet of the first block of the formatting, B0, contains four flags for control information: two single bits, called Reserved and Adata, and two strings of three bits, to encode the values t and q.

Where t is "The octet length of the MAC." CCM is an entirely different mode. While CCM and GCM are both AEADs I do not think it is entirely reasonable to consider their behaviors and design as interchangeable. GCM has no control information block.

By your logic then, is OpenSSL not "spec compliant" for GCM?

//tl;dr: OpenSSL encrypting with a 16 byte tag and decrypting with a 12 byte tag.

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>

void dump(unsigned char* data, int len)
{
    for (int i = 0; i < len; i++) {
        printf("%02x", data[i]);
    }
    putchar('\n');
}

int main(int argc, char *argv[])
{
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    
    unsigned char key[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
    unsigned char nonce[12] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
    unsigned char plaintext[6] = "hello";
    unsigned char decrypted[6] =  { 0 };
    unsigned char ciphertext[6] = { 0 };
    unsigned char tag[16] = { 0 };
    
    if (EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) {
        printf("Could not initialize");
        abort();
    }
    
    if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce) != 1) {
        printf("Could not set key or nonce");
        abort();
    }

    int len = 0, ciphertextLength = 0;
    
    if (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, 6) != 1) {
        printf("Encrypt failed.");
        abort();
    }
    
    ciphertextLength += len;
    
    if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) {
        printf("Final failed.");
        abort();
    }
    
    ciphertextLength += len;
    
    if (ciphertextLength != 6) {
        printf("6 bytes in should be 6 bytes out.");
        abort();
    }
    
    printf("Ciphertext: ");
    dump(ciphertext, ciphertextLength);
    
    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, tag) != 1) {
        printf("Could not get the tag.");
        abort();
    }
    
    printf("Tag: ");
    dump(tag, sizeof tag);
    
    unsigned char truncatedTag[12] = { 0 };
    memcpy(truncatedTag, tag, sizeof truncatedTag);
    
    printf("Truncated Tag: ");
    dump(truncatedTag, sizeof truncatedTag);
    
    EVP_CIPHER_CTX_free(ctx);
    
    ctx = EVP_CIPHER_CTX_new();

    if (EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) {
        printf("Could not initialize");
        abort();
    }
    
    if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce) != 1) {
        printf("Could not set key or nonce");
        abort();
    }
    
    assert(sizeof truncatedTag == 12);
    
    if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, sizeof truncatedTag, truncatedTag)) {
        printf("Could not set truncated authentication tag.");
        abort();
    }
    
    int plaintextLength = 0;
    
    if (EVP_DecryptUpdate(ctx, decrypted, &len, ciphertext, ciphertextLength) != 1) {
        printf("Decrypt failed.");
        abort();
    }

    plaintextLength += len;
    
    int result = EVP_DecryptFinal_ex(ctx, plaintext + plaintextLength, &len);
    
    
    if (result > 0) {
        printf("Decrypted successfully");
        plaintextLength += len;
    }
    else {
        printf("Decryption failed.");
    }
}

@sdrapkin
Copy link
Author

@vcsjones In your C/OpenSSL code you create one GCM context to encrypt (which produces 16-byte tag), then you free it, then you create another new GCM context to decrypt, then you use that 2nd context to successfully decrypt with a truncated 12-byte tag.

This is not the correct comparison. I'm not very familiar with low-level OpenSSL APIs - perhaps OpenSSL cannot use the same GCM context to encrypt and decrypt, and thus requires a new context for each operation. Your Ruby example (which seems to wrap OpenSSL) also uses 2 different GCM contexts to encrypt and decrypt.

The GCM spec is quite clear (Section-7 "GCM specification") that tag-length is a "prerequisite" (prerequisites are fixed across many invocations of Encrypt or Decrypt function), and must be associated with the key. The GCM decryption function specification (Section 7.2) is explicit in saying that one of its prerequisites is a "supported tag length t associated with the key".

OpenSSL low-level functions are a toolkit for "build-your-own-GCM" (here's AES, here's CTR-mode, here's GMAC, and here's XOR - go have fun).

This is how Golang does it. This should have as much relevance to .NET as how OpenSSL does it (ie. not relevant, but at least Golang does it correctly, by binding tag-size to GCM context and having a default of tag-size=16).

@bartonjs
Copy link
Member

@sdrapkin Your insistence that our implementation is wrong and/or against spec isn't productive.

We have the implementation we have, and we're not going to change it. The specific example that you cited from JWA nearly automatically requires separate contexts that are only tied together by a specification; and NIST often writes "this is how things are" which only means "this is the only acceptable use for US-Gov and impacted parties" (such as that a single RSA key should be PKCS1 xor PSS, or signing xor encryption... but our underlying providers have the same loosy-goosey model we have (which is what informed ours).

The usability concern behind "let me tell you what tag size I intend to use at construction time to save me the redundancy of checking tag.Length in my code" is valid. So some new API is justified to enhance that scenario.

The cryptography stack in .NET is also mostly intended as low-level primitives. It's a key. Use it however you want. We don't block duplicated nonces... that's an application problem. But many specs will tell you they MUST be unique. At some point things just become a caller/application problem.

@sdrapkin
Copy link
Author

@bartonjs I understand. Thanks for reviewing this issue. Let's do whatever is productive.

@owlstead
Copy link

owlstead commented Jul 15, 2022

Security check such as tag size checks should not be left to user of libraries. The fact that the possibility of tag size truncation is not even mentioned in the API's clearly shows a lack of responsibility by the creators the API. API's should contain indication on how to use them correctly and securely. Just brushing this off as something that OpenSSL does as well is dishonest - creators or API's are encouraged to think on their own and know their field. Given the track record of OpenSSL implementation and documentation it is not even a good argument by itself.

Note that the Java implementation defaults to a 128 bit tag size, and that you need to configure the tag size explicitly using GcmParameterSpec. So there are certainly implementations that handle this better. Of course, since the tag size is not mentioned explicitly in the list of Java supported algorithms, other providers within that eco-system could choose otherwise. There is also the issue that tag verification is not explicit in the Java API. The Java API is far from perfect, but it does show that other libraries do validate the complete tag.

That all said, I think adding the constructors with an explicit tag size is a necessity to create a secure API. Given that the current constructors do not indicate a tag size I would strongly suggest to both deprecate these constructors and provide a clear warning in the documentation. It might be worthwhile to think of a way (factory method?) that defaults to a 128 bit tag size. If the current constructors are not deprecated then devs will simply choose the easier, insecure option with fewer parameters.

I do agree with bartonjs that it does make sense to think in solutions; the API cannot be changed in such a way that backwards compatibility is harmed. It may be that the current API is used for e.g. 12 byte tags, where the tag size is validated.

@vcsjones
Copy link
Member

vcsjones commented May 5, 2023

The top post has been updated with an API proposal.

@bartonjs bartonjs added the api-ready-for-review API is ready for review, it is NOT ready for implementation label May 5, 2023
@bartonjs
Copy link
Member

bartonjs commented May 9, 2023

Video

Looks good as proposed.

namespace System.Security.Cryptography;

public partial class AesGcm {

+  public AesGcm(byte[] key, int tagSizeInBytes);
+  public AesGcm(ReadOnlySpan<byte> key, int tagSizeInBytes);

+  [Obsolete("AesGcm should indicate the required tag size for encryption and decryption. Use a constructor that accepts the tag size.", DiagnosticId = "SYSLIB9999")]
   public AesGcm(byte[] key);
+  [Obsolete("AesGcm should indicate the required tag size for encryption and decryption. Use a constructor that accepts the tag size.", DiagnosticId = "SYSLIB9999")]
   public AesGcm(ReadOnlySpan<byte> key);

+  public int? TagSizeInBytes { get; }
}

@bartonjs bartonjs added api-approved API was approved in API review, it can be implemented breaking-change Issue or PR that represents a breaking API or functional change over a prerelease. and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels May 9, 2023
@ghost ghost added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label May 9, 2023
@ghost
Copy link

ghost commented May 9, 2023

Added needs-breaking-change-doc-created label because this issue has the breaking-change label.

  1. Create and link to this issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.

Tagging @dotnet/compat for awareness of the breaking change.

@vcsjones vcsjones self-assigned this May 11, 2023
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label May 13, 2023
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label May 15, 2023
@vcsjones
Copy link
Member

Breaking change doc: dotnet/docs#35338

@vcsjones vcsjones removed the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label May 15, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Jun 14, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Security breaking-change Issue or PR that represents a breaking API or functional change over a prerelease.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants