@@ -73,6 +73,7 @@ protected override async Task<OAuthTokenResponse> ExchangeCodeAsync([NotNull] OA
7373 using var document = await JsonDocument . ParseAsync ( stream ) ;
7474
7575 var mainElement = document . RootElement ;
76+
7677 if ( ! ValidateReturnCode ( mainElement , out var code ) )
7778 {
7879 return OAuthTokenResponse . Failed ( new Exception ( $ "An error (Code:{ code } ) occurred while retrieving an access token.") ) ;
@@ -86,21 +87,26 @@ private static string ComputeHmacSHA256(string key, string data)
8687 {
8788 var keyBytes = Encoding . UTF8 . GetBytes ( key ) ;
8889 var dataBytes = Encoding . UTF8 . GetBytes ( data ) ;
90+
8991 var hash = HMACSHA256 . HashData ( keyBytes , dataBytes ) ;
92+
9093 return Convert . ToHexStringLower ( hash ) ;
9194 }
9295
93- private static string BuildSignatureString ( HttpRequestMessage request , string appSecret )
96+ private static string BuildSignatureString ( SortedList < string , string > xbiliHeaders , string key )
9497 {
95- var headers = request . Headers
96- . Where ( h => h . Key . StartsWith ( "x-bili-" , StringComparison . OrdinalIgnoreCase ) )
97- . OrderBy ( h => h . Key )
98- . Select ( h => $ "{ h . Key } :{ string . Join ( "," , h . Value ) } ")
99- . ToList ( ) ;
98+ var builder = new StringBuilder ( 256 ) ; // 256 is an estimated size for the plain text
10099
101- var signature = string . Join ( '\n ' , headers ) ;
100+ foreach ( ( var name , var value ) in xbiliHeaders )
101+ {
102+ builder . Append ( name )
103+ . Append ( ':' )
104+ . Append ( value )
105+ . Append ( '\n ' ) ;
106+ }
102107
103- return ComputeHmacSHA256 ( appSecret , signature ) ;
108+ var data = builder . ToString ( 0 , builder . Length - 1 ) ; // Ignore the last '\n'
109+ return ComputeHmacSHA256 ( key , data ) ;
104110 }
105111
106112 protected override async Task < AuthenticationTicket > CreateTicketAsync (
@@ -110,19 +116,21 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(
110116 {
111117 using var request = new HttpRequestMessage ( HttpMethod . Get , Options . UserInformationEndpoint ) ;
112118 request . Headers . Add ( "access-token" , tokens . AccessToken ) ;
113- request . Headers . Add ( "x-bili-accesskeyid" , Options . ClientId ) ;
114- request . Headers . Add ( "x-bili-content-md5" , "d41d8cd98f00b204e9800998ecf8427e" ) ; // It's a GET request so there's no content, so we send the MD5 hash of an empty string
115- request . Headers . Add ( "x-bili-signature-method" , "HMAC-SHA256" ) ;
116- request . Headers . Add ( "x-bili-signature-nonce" , Base64Url . EncodeToString ( RandomNumberGenerator . GetBytes ( 256 / 8 ) ) ) ;
117- request . Headers . Add ( "x-bili-signature-version" , "2.0" ) ;
118- request . Headers . Add ( "x-bili-timestamp" , TimeProvider . GetUtcNow ( ) . ToUnixTimeSeconds ( ) . ToString ( CultureInfo . InvariantCulture ) ) ;
119-
120- var signature = BuildSignatureString ( request , Options . ClientSecret ) ;
119+
120+ var xbiliHeaders = BuildXBiliHeaders ( ) ;
121+
122+ foreach ( ( var name , var value ) in xbiliHeaders )
123+ {
124+ request . Headers . Add ( name , value ) ;
125+ }
126+
127+ var signature = BuildSignatureString ( xbiliHeaders , Options . ClientSecret ) ;
121128 request . Headers . Add ( "Authorization" , signature ) ;
122129
123130 request . Headers . Accept . Add ( new MediaTypeWithQualityHeaderValue ( "application/json" ) ) ;
124131
125132 using var response = await Backchannel . SendAsync ( request , HttpCompletionOption . ResponseHeadersRead , Context . RequestAborted ) ;
133+
126134 if ( ! response . IsSuccessStatusCode )
127135 {
128136 await Log . UserProfileErrorAsync ( Logger , response , Context . RequestAborted ) ;
@@ -132,6 +140,7 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(
132140 using var payload = JsonDocument . Parse ( await response . Content . ReadAsStringAsync ( Context . RequestAborted ) ) ;
133141
134142 var mainElement = payload . RootElement ;
143+
135144 if ( ! ValidateReturnCode ( mainElement , out var code ) )
136145 {
137146 throw new AuthenticationFailureException ( $ "An error (ErrorCode:{ code } ) occurred while retrieving user information.") ;
@@ -145,6 +154,23 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(
145154 return new AuthenticationTicket ( context . Principal ! , context . Properties , Scheme . Name ) ;
146155 }
147156
157+ private SortedList < string , string > BuildXBiliHeaders ( ) => new ( 6 , StringComparer . OrdinalIgnoreCase )
158+ {
159+ { "x-bili-accesskeyid" , Options . ClientId } ,
160+ { "x-bili-content-md5" , "d41d8cd98f00b204e9800998ecf8427e" } , // It's a GET request so there's no content, so we send the MD5 hash of an empty string
161+ { "x-bili-signature-method" , "HMAC-SHA256" } ,
162+ { "x-bili-signature-nonce" , GenerateNonce ( ) } ,
163+ { "x-bili-signature-version" , "2.0" } ,
164+ { "x-bili-timestamp" , TimeProvider . GetUtcNow ( ) . ToUnixTimeSeconds ( ) . ToString ( CultureInfo . InvariantCulture ) }
165+ } ;
166+
167+ private static string GenerateNonce ( )
168+ {
169+ Span < byte > bytes = stackalloc byte [ 256 / 8 ] ;
170+ RandomNumberGenerator . Fill ( bytes ) ;
171+ return Base64Url . EncodeToString ( bytes ) ;
172+ }
173+
148174 /// <summary>
149175 /// Check the code sent back by server for potential server errors.
150176 /// </summary>
@@ -154,14 +180,13 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(
154180 /// <returns>True if succeed, otherwise false.</returns>
155181 private static bool ValidateReturnCode ( JsonElement element , out int code )
156182 {
157- code = 0 ;
158183 if ( ! element . TryGetProperty ( "code" , out JsonElement errorCodeElement ) )
159184 {
185+ code = 0 ;
160186 return true ;
161187 }
162188
163189 code = errorCodeElement . GetInt32 ( ) ;
164-
165190 return code == 0 ;
166191 }
167192
0 commit comments