Encryption in the OAuth 2.0 implementation

1,921 views
Skip to first unread message

Hjörtur Stefánsson

unread,
Jun 8, 2011, 10:45:24 AM6/8/11
to DotNetOpenAuth
Hi there.
I'm working on creating a single sign-on solution, using oauth 2.0.
It's basically an authorization server, that can issue tokens to
clients, and the clients use the tokens to fetch information about the
users and then log them in on their end.

I was looking at the dotnetopenauth library and it looks good for that
purpose. Looking through your examples (OAuthAuthorizationServer &
OAuthClient) I can see that it is required that the tokens be
generated using encryption. It was however my understanding of the
OAuth 2.0 spec, that it was no longer required to use any form for
encryption (except SSL in the http communication between the client
and the auth-server), and the tokens are basically just randomly
generated strings, that the authorization server can map to a user.

Am I just misunderstanding the spec or is this an added security
measure on your part?

Best regards and thanks for a great library,
Hjörtur

Andrew Arnott

unread,
Jun 8, 2011, 11:10:00 AM6/8/11
to dotnet...@googlegroups.com
Hi Hjörtur,

OAuth 1.0(a) was primarily around random strings used for tokens, whereas OAuth 2.0 enables these tokens carrying important data in order to reduce the load and synchronization requirements across a distributed authorization server network. 

For instance, if the tokens were truly random strings, then each of those strings would have to be stored in the authorization server's database (potentially many millions of them), cycled out when they expire, etc.  Perhaps even more significant, it would mean that the resource server would have to forward each HTTP request's access token to the authorization server through a backchannel to ask it to verify the token's validity.

What OAuth 2.0's slightly modified flow from OAuth 1.0's enables is that every token in the flow carry its own claims in an encoded form.  The tokens are self-describing.  Since they are also signed, the authorization server can be sure that refresh tokens and authorization codes are valid.  It also means that resource servers can verify the validity of access tokens without contacting the authorization server explicitly. 

Since these bearer tokens may also include confidential data (data that the client or others should not be able to read), encrypting the token is also a good and sometimes necessary security measure. 

From your app's point of view, implementing one approach vs. the other isn't much different.  If you used random strings for tokens, you'd have a table or two of tokens with several columns describing them.  But if you're willing to use cryptography, all you need is a table for your symmetric key secrets, which DotNetOpenAuth will completely manage for you.  This table will be very small (on the order of a dozen rows at most) compared to the unbounded rows required to store random string tokens.

Hopefully I've sold you on the idea.  I'd be interested in hearing if you have any lingering concerns.

Now, I mentioned a symmetric key table.  You probably don't see that in the samples right now.  That's a very new feature that hasn't yet been published in a CTP but will be soon.  The samples you have probably have a hard-coded symmetric key that you put into your web site.  The downside of this approach is it doesn't allow for seemless key rotation.  The recent enhancement to DNOA is support for built-in self-managing key rotation, so you don't have any hard-coded keys in your code any more, and you're mitigating against brute force attacks on your keys.

Hjörtur Stefánsson

unread,
Jun 8, 2011, 12:02:07 PM6/8/11
to DotNetOpenAuth
Hi Andrew.
Thanks for your excellent answer - it really clears things up for
me :)
Your approach with the symmetric key table sounds very interesting.
Any estimate on when the next CTP will be out? Are we talking days,
weeks or months?

Best regards,
Hjörtur

On Jun 8, 3:10 pm, Andrew Arnott <andrewarn...@gmail.com> wrote:
> Hi Hj�rtur,
>
> OAuth 1.0(a) was primarily around random strings used for tokens,
> whereas OAuth 2.0 enables these tokens carrying important data in order
> to reduce the load and synchronization requirements across a distributed
> authorization server network.
>
> For instance, if the tokens were truly random strings, then each of
> those strings would have to be stored in the authorization server's
> database (potentially many millions of them), cycled out when they
> expire, etc.  Perhaps even more significant, it would mean that the
> resource server would have to forward each HTTP request's access token
> to the authorization server through a backchannel to ask it to verify
> the token's validity.
>
> What OAuth 2.0's slightly modified flow from OAuth 1.0's enables is that
> every token in the flow carry its own claims in an encoded form.  The
> tokens are self-describing.  Since they are also signed, the
> authorization server can be sure that refresh tokens and authorization
> codes are valid.  It also means that resource servers can verify the
> validity of access tokens without contacting the authorization server
> explicitly.
>
> Since these bearer tokens may also include confidential data (data that
> the client or others should not be able to read), encrypting the token
> is also a good and sometimes necessary security measure.
>
> >From your app's point of view, implementing one approach vs. the other
>
> isn't /much /different.  If you used random strings for tokens, you'd
> have a table or two of tokens with several columns describing them.  But
> if you're willing to use cryptography, all you need is a table for your
> symmetric key secrets, which DotNetOpenAuth will completely manage for
> you.  This table will be /very/ small (on the order of a dozen rows at
> most) compared to the unbounded rows required to store random string tokens.
>
> Hopefully I've sold you on the idea.  I'd be interested in hearing if
> you have any lingering concerns.
>
> Now, I mentioned a symmetric key table.  You probably don't see that in
> the samples right now.  That's a very new feature that hasn't yet been
> published in a CTP but will be soon.  The samples you have probably have
> a hard-coded symmetric key that you put into your web site.  The
> downside of this approach is it doesn't allow for seemless key
> rotation.  The recent enhancement to DNOA is support for built-in
> self-managing key rotation, so you don't have any hard-coded keys in
> your code any more, and you're mitigating against brute force attacks on
> your keys.
>
> On 6/8/2011 7:45 AM, Hj�rtur Stef�nsson wrote:
>
>
>
>
>
>
>
> > Hi there.
> > I'm working on creating a single sign-on solution, using oauth 2.0.
> > It's basically an authorization server, that can issue tokens to
> > clients, and the clients use the tokens to fetch information about the
> > users and then log them in on their end.
>
> > I was looking at the dotnetopenauth library and it looks good for that
> > purpose. Looking through your examples (OAuthAuthorizationServer &
> > OAuthClient) I can see that it is required that the tokens be
> > generated using encryption. It was however my understanding of the
> > OAuth 2.0 spec, that it was no longer required to use any form for
> > encryption (except SSL in the http communication between the client
> > and the auth-server), and the tokens are basically just randomly
> > generated strings, that the authorization server can map to a user.
>
> > Am I just misunderstanding the spec or is this an added security
> > measure on your part?
>
> > Best regards and thanks for a great library,
> > Hj�rtur

Andrew Arnott

unread,
Jun 8, 2011, 12:05:03 PM6/8/11
to dotnet...@googlegroups.com
We're probably talking about a week... I just want to bring DNOA up to the latest spec (draft 16) before releasing the next CTP.

And for those interested, this same symmetric key table also enhances OpenID Providers: they no longer have to keep an unbounded association table because (like Yahoo!) associations are totally encoded into the association handles themselves using the same technique as is used in the OAuth 2.0 refresh tokens.

--
Andrew Arnott
"I [may] not agree with what you have to say, but I'll defend to the death your right to say it." - S. G. Tallentyre


2011/6/8 Hjörtur Stefánsson <hjor...@gmail.com>

--
You received this message because you are subscribed to the Google Groups "DotNetOpenAuth" group.
To post to this group, send email to dotnet...@googlegroups.com.
To unsubscribe from this group, send email to dotnetopenid...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/dotnetopenid?hl=en.


Hjörtur Stefánsson

unread,
Jun 14, 2011, 7:55:04 AM6/14/11
to DotNetOpenAuth
Hi Andrew.
Just to verify...is this change a part of the CTP3 that's just been
released?

/H

On Jun 8, 4:05 pm, Andrew Arnott <andrewarn...@gmail.com> wrote:
> We're probably talking about a week... I just want to bring DNOA up to the
> latest spec (draft 16) before releasing the next CTP.
>
> And for those interested, this same symmetric key table also enhances OpenID
> Providers: they no longer have to keep an unbounded association table
> because (like Yahoo!) associations are totally encoded into the association
> handles themselves using the same technique as is used in the OAuth 2.0
> refresh tokens.
>
> --
> Andrew Arnott
> "I [may] not agree with what you have to say, but I'll defend to the death
> your right to say it." - S. G. Tallentyre
>
> 2011/6/8 Hjörtur Stefánsson <hjortu...@gmail.com>

Andrew Arnott

unread,
Jun 14, 2011, 9:16:14 AM6/14/11
to dotnet...@googlegroups.com
Yes, although "just been released" might be a bit premature to say.  The files are available for download as of this morning from TeamCity, but I'm expecting to upload them to SourceForge later today and then post the link.

--
Andrew Arnott
"I [may] not agree with what you have to say, but I'll defend to the death your right to say it." - S. G. Tallentyre


2011/6/14 Hjörtur Stefánsson <hjor...@gmail.com>

Marcus

unread,
Jun 17, 2011, 3:20:06 AM6/17/11
to DotNetOpenAuth
Andrew,

Looking at CTP 3, it looks like you've implemented the above and while
I think there are merits to this approach, I do have some questions.

How do you revoke a token in your scenario? Say a user authorizes a
3rd party app and we issue a token with a relatively long lifespan.
Then later the user revokes that app's access but using the
cryptography method I see in CTP 3, since we are not going back to the
Authorization Server via a back channel on each request we wouldn't
know the token has been revoked and would still be granting access or
have I missed something?

I understand we could set a low lifespan and use the refresh token
procedure, but this would still allow a window of unauthorized
access...

Further to this (and I haven't examined the source in any detail), is
there a way to use DotNetOpenAuth without this cryptography
implementation? My personal feeling is that I don't like the idea of
self describing tokens as they incur cryptography CPU processing
overhead on both the signature check and the decryption while a token
look up can be implemented very efficiently with caching etc.

In all, I think the work you have done here is first class, but I
would like to be able to choose whether or not to go with self
describing tokens or not.

Regards,
Marcus



On Jun 14, 2:16 pm, Andrew Arnott <andrewarn...@gmail.com> wrote:
> Yes, although "just been released" might be a bit premature to say.  The
> files are available for download as of this morning from TeamCity, but I'm
> expecting to upload them to SourceForge later today and then post the link.
> --
> Andrew Arnott
> "I [may] not agree with what you have to say, but I'll defend to the death
> your right to say it." - S. G. Tallentyre
>
> 2011/6/14 Hjörtur Stefánsson <hjortu...@gmail.com>

Andrew Arnott

unread,
Jun 17, 2011, 9:54:41 AM6/17/11
to dotnet...@googlegroups.com
Hi Marcus,

First of all, thank you very much for your feedback and concerns.

I expressed the same concern (very gravely, actually) regarding the access tokens being useful after permission has been revoked on the OAuth mailing list when it was first being drafted.  There are two mitigations for this, and you've already named them: short access token lifetimes, and a backchannel for verification.  DotNetOpenAuth makes the first one easy: you always have to set a lifetime for the access token and you can just pass in a smaller value.  

The second one (using a backchannel to verify the access token upon each use of it) I do intend to build support into DotNetOpenAuth, but there is a bit more code to write.  Specifically, the resource server must implement the IAccessTokenAnalyzer interface and set the ResourceServer.AccessTokenAnalyzer property to an instance of your implementation.  Your custom class then could take an access token, then use a private HTTP request to the authorization server to verify the continued validity of the token.  In addition to validating, the auth server could also provide the authorizing username and scopes for the token as well, which would eliminate the need for cryptography on the resource server.

In order for the above to work, the resource server would (currently) have to decrypt the access token before forwarding it onto the auth server, which would then validate its own signature and then validate the authorization itself against what the user has and has not revoked.

Now regarding your desire to remove cryptography in favor of database access:  In OpenID you may have noticed that CTP3 removes the requirement for OpenID Providers to store association tables, but since it had a side-effect of making the association handle considerably longer (from ~20 characters to ~140) DNOA retains a switch that can revert to the old behavior of storing each individual association in a database.
So far there isn't an equivalent "turn off cryptography" switch on the OAuth 2.0 side of DNOA.  The OAuth 2.0 spec differs from OAuth 1.0 in a few very important ways deliberately to enable cryptographic utilization of tokens and auth codes themselves so they are self-describing.  Some large companies with massive scaling requirements needed this feature, so I can only imagine (and it makes sense) that cryptographic operations are cheaper than HTTP requests and perhaps even database lookups (depending on how the db is implemented).  Yahoo! in particular mentioned distribution issues, where because Yahoo's servers are distributed worldwide there would be timing issues where not all the servers had the latest tokens in their database and as a result would have caused unreliable access.  By making token self-describing, you not only eliminate the distribution problem entirely, but you no longer have to prepare for unbounded growth in your database tables.  Because so many OAuth 2 servers will utilize self-describing tokens, I expect many clients will consider the generation of these tokens as a "free" operation to the auth server and may therefore request tokens very frequently.  This would generate large tables, and possibly needless frequent scrubbing of your tables to remove expired tokens.  

Do you have numbers telling you that verifying cryptographic signatures and decryption is more expensive than HTTP requests or database lookups in a way that would impede your scalability?

--
Andrew Arnott
"I [may] not agree with what you have to say, but I'll defend to the death your right to say it." - S. G. Tallentyre


weblivz

unread,
Jul 5, 2012, 10:31:15 AM7/5/12
to dotnet...@googlegroups.com
Hi Andrew - I found this thread recently and also found this post  http://homakov.blogspot.ca/2012/07/saferweb-most-common-oauth2.html 

I like the use of the Symmetric Key in this library. I had a quick look and now have a decent idea of what is going on.

My question however is whether the code is also generated using the symmetric encryption key ... which i assume would mitigate the issue in the above blog post?

thanks,
steven

> > > > For more options, visit this group at
> > > >http://groups.google.com/group/dotnetopenid?hl=en.
>
> > --
> > You received this message because you are subscribed to the Google Groups
> > "DotNetOpenAuth" group.
> > To post to this group, send email to dotnet...@googlegroups.com.
> > To unsubscribe from this group, send email to

> > For more options, visit this group at
> >http://groups.google.com/group/dotnetopenid?hl=en.

--
You received this message because you are subscribed to the Google Groups "DotNetOpenAuth" group.
To post to this group, send email to dotnet...@googlegroups.com.
To unsubscribe from this group, send email to dotnetopenid+unsubscribe@googlegroups.com.

Andrew Arnott

unread,
Jul 5, 2012, 11:41:35 AM7/5/12
to dotnet...@googlegroups.com
Hi Steven,

I'm not sure I see the relevance between the use of encryption keys and the use of the state parameter.  In fact DNOA v4.1 does populate the state parameter, but it doesn't use encryption to do it at all.

--
Andrew Arnott
"I [may] not agree with what you have to say, but I'll defend to the death your right to say it." - S. G. Tallentyre


To view this discussion on the web visit https://groups.google.com/d/msg/dotnetopenid/-/Cpytmm-TJqkJ.

To post to this group, send email to dotnet...@googlegroups.com.
To unsubscribe from this group, send email to dotnetopenid...@googlegroups.com.

Steven Livingstone Pérez

unread,
Jul 5, 2012, 11:56:27 AM7/5/12
to dotnet...@googlegroups.com
Just trying to get my head around how it all works... in fact i was asked to document it so I can explain it to someone who is going to do some api work.

I had assumed that the ICryptoKeyStore implementation which in the sample is used to encrypt the access token (using a Symmetric key) which as i understand it may contain sensitive data - even signing it is probably a good thing.

Now, before you get an access token you need a code which you can later use to get an access token - per section 4 in the OAuth 2.0 draft.

From what I saw the link on that blog shows how you can replace that code on a secondary site to gain access to someone's account via oAuth ? At the mo' not entirely sure what goes into the generation of that code hash ... currently looking at that.

I had figured the symmetric key (stored in the db in the samples) was used for the access token to encrypt/sign the access token and was wondering whether the same approach was used by the code that was returned.

I may have some/all of this wrong ... simply trying to piece together what is going on as i'm finding doco hard to come by (no so much the code, but the flow) so using blogs, groups & step throughs to figure things out.

cheers,
/steven


From: andrew...@gmail.com
Date: Thu, 5 Jul 2012 08:41:35 -0700
Subject: Re: [dotnetopenauth] Re: Encryption in the OAuth 2.0 implementation
To: dotnet...@googlegroups.com

John Bradley

unread,
Jul 5, 2012, 12:35:59 PM7/5/12
to dotnet...@googlegroups.com
Steven,

There are perhaps implementation details, and OAuth vs openID Connect issues getting mixed up.

Passing a signed or signed and encrypted access token to enable the protected resource to be stateless as opposed to having to make a call back to the authorization endpoint to introspect the token is a implementation choice.

In OAuth 2 there are 2 types of clients, one with a secret password that it can use to authenticate itself "Confidential" and one that has no method to authenticate itself "Public".

The confidential client authenticates itself and based on that authentication and a "code" (Artifact in other protocols) it can get a access code (structured or not) from the token endpoint.

The problem is that with public clients using either the "code" or "token" OAuth flows there is no audience restriction on the token that can be inspected by the client.

The problem is with a public client if you make the assumption that the presenter of the access_token or code is the end-user you may well be wrong because there are lots of sites that have valid access tokens for the 
end-user's protected resource.

In DNOI the access token is audience restricted to the protected resource, however that doesn't help the client much. 

What is needed is a audience restriction to the client.  In openID Connect we introduced a separate id_token that is used for that.   The id_token contains a hash of the code or access_token it was issued with along with the client_id of the intended recipient signed by the Authorization server.   That is however not part of OAuth itself.

Some sites like Facebook use a similar signed_request format, or provide a token introspection endpoint, to prevent this attack on public clients.

One of the big problems is token use outside of OAuth flows.

If you were building a native app,  you might be tempted to just pass a access token over a backchannel to enable the backend to know who is logged into the app.
This has caused many sites and apps to be totally insecure.   Any site the user has visited can emulate the apps API and impersonate the user.

In the public client case using code I am going to change OAuth to require the client to send it's client_id to the token endpoint even if it is not authenticating.  
This prevents the code switch for public clients.

I know Andrew will not be happy with yet another OAuth change:)

The bottom line is that OAuth 2.0 is not a SSO protocol.  There are a lot of potential issues if you use out for one in the wrong way.

John B.

-- 
You received this message because you are subscribed to the Google Groups "DotNetOpenAuth" group.
To post to this group, send email to dotnet...@googlegroups.com.

Andrew Arnott

unread,
Jul 5, 2012, 12:38:01 PM7/5/12
to dotnet...@googlegroups.com
Hi Steven,

Responses inline...
On Thu, Jul 5, 2012 at 8:56 AM, Steven Livingstone Pérez <web...@hotmail.com> wrote:
I had assumed that the ICryptoKeyStore implementation which in the sample is used to encrypt the access token (using a Symmetric key) which as i understand it may contain sensitive data - even signing it is probably a good thing.
DNOA never encrypts access tokens using symmetric keys, which are the only keys stored by ICryptoKeyStore.  Access tokens are always encrypted using the resource server's asymmetric public key, and signed using the authorization server's asymmetric private key.
Where are you looking in the sample that makes it appear that DNOA is encrypting access tokens using the ICryptoKeyStore?
 

Now, before you get an access token you need a code which you can later use to get an access token - per section 4 in the OAuth 2.0 draft.
There are a couple of possible codes you may be referring to: refresh tokens or authorization code.  And actually it's not always true.  In the implicit grant type, the access token is the first and only code you get.
 

From what I saw the link on that blog shows how you can replace that code on a secondary site to gain access to someone's account via oAuth ? At the mo' not entirely sure what goes into the generation of that code hash ... currently looking at that.
On that blog they're talking about the authorization code grant type, and the auth code's format isn't specified in the spec.  It can be anything.  DNOA creates the auth code by completely describing the user-client-scope-date tuple for when authorization took place, and then uses the ICryptoKeyStore to symmetrically sign and encrypt the auth code before sending it to the client.  
 

I had figured the symmetric key (stored in the db in the samples) was used for the access token to encrypt/sign the access token and was wondering whether the same approach was used by the code that was returned.

To summarize then, the auth code does use symmetric keys to sign/encrypt. The access token does not -- it uses asymmetric keys instead.
 

I may have some/all of this wrong ... simply trying to piece together what is going on as i'm finding doco hard to come by (no so much the code, but the flow) so using blogs, groups & step throughs to figure things out.

I hope this helps.

Steven Livingstone Pérez

unread,
Jul 5, 2012, 1:34:57 PM7/5/12
to dotnet...@googlegroups.com
Yes Andrew - I think it does help. It's the flow in calling from the DNOI framework to the interface implementations I write that can be hard to follow.

I think I was on the right tracks (... if not doing a great impression of a slightly inebriated driver)  - I just need to look through the code and follow the flow. I think the calls to my ICryptoKeyStore implemented class come from the framework so i wasn't sure what they did once they got the response. What you are saying makes sense ... and combined with the  id_token mentioned by John I can see what I was thinking about was slightly different. However I can now see where and why the (a)symmetric encryption options are needed.

Now because DNOI symetrically encrypts the auth code - and i assume checks it when it is used to exchange for an access - the blog post i pointed at isn't a concern.

I need to do some debugging and step through some code but i think i'm almost there (with the ROCP grant which I have been asked to show first ... hoping to push for Auth Code Grant next).

thanks very much,
/steven


From: andrew...@gmail.com
Date: Thu, 5 Jul 2012 09:38:01 -0700

Subject: Re: [dotnetopenauth] Re: Encryption in the OAuth 2.0 implementation
To: dotnet...@googlegroups.com

--
You received this message because you are subscribed to the Google Groups "DotNetOpenAuth" group.
To post to this group, send email to dotnet...@googlegroups.com.

Steven Livingstone Pérez

unread,
Jul 5, 2012, 1:37:49 PM7/5/12
to dotnet...@googlegroups.com
Thanks John - that fills in a bit of the puzzle for me. I do need to do draw a few more flow diagrams but the audience restriction makes total sense.

The token over backchannel is something i'm going to read more about this evening!

thanks again
/steven


Subject: Re: [dotnetopenauth] Re: Encryption in the OAuth 2.0 implementation
Date: Thu, 5 Jul 2012 12:35:59 -0400
To: dotnet...@googlegroups.com

John Bradley

unread,
Jul 5, 2012, 2:44:39 PM7/5/12
to dotnet...@googlegroups.com
If you look at apps like foursquare the pattern is fairly typical.

The app gets you to login with your Facebook credential.   It then passes the access token to it's backend to link the information it keeps on you.
They may also have a web interface that uses Facebook authentication.

The problem was (at least foursquare is fixed now, I think) anyone with a access token to your Facebook graph API had access to all of your foursquare information as well,   Not something the user understood when they  logged in to foursquare or other accounts.

It is a sort of federated identity where any site or app you log into has your implicit permission to access any other site as you.

Perhaps a touch too much sharing:)

John B.

Steven Livingstone Pérez

unread,
Jul 5, 2012, 3:01:00 PM7/5/12
to dotnet...@googlegroups.com
Ahhh, so you could have N access tokens passed from N external services all linked to a single account on a local site which effectively allows token N1 obtained from Service1 to be used to sign you back into that local site and furthermore access via delegation etc all the other external services that uses an oAuth token linked to that account.

It's sharing enough that you could get back to one app using the token but once you'd auth'd in an app (eg. fb to 4sq) it could get quite crazy.

Interesting! I can see why the ... who i gave this authorization to .. is quite important in the bigger picture.

thanks,
/steven


From: ve7...@ve7jtb.com
Subject: Re: [dotnetopenauth] Re: Encryption in the OAuth 2.0 implementation
Date: Thu, 5 Jul 2012 14:44:39 -0400
To: dotnet...@googlegroups.com

John Bradley

unread,
Jul 5, 2012, 3:13:26 PM7/5/12
to dotnet...@googlegroups.com
The sites don't need you in the flow after they have the token,  any valid access token gets you in without the user.

log into site A using Facebook connect/OAuth.  Site A can log into site B as you by using the token they got from you.
You don't even need to have ever visited Site B as a user.  The attacker just logs in as you with the access token.

If you have never gone there it won't expose any additional personal information.

Though they can push information as you.  Perhaps sending libellous messages on sites that wind up looking like they come from you because they used your Facebook account to login.

OAuth was not designed for Authentication, just delegated authorization.   If it is used as part of Authentication extra steps need to be taken.

That is why openID Connect exists.

John B.

Steven Livingstone Pérez

unread,
Jul 5, 2012, 3:29:12 PM7/5/12
to dotnet...@googlegroups.com
Yeah - I can see how that could be a concern.

I need to get into OpenID Connect a bit more. I like that it builds on OpenID and oAuth 2. Makes sense.

/steven


From: ve7...@ve7jtb.com
Subject: Re: [dotnetopenauth] Re: Encryption in the OAuth 2.0 implementation
Date: Thu, 5 Jul 2012 15:13:26 -0400
To: dotnet...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages