diff --git a/Source/ProofOfConcept/Program.cs b/Source/ProofOfConcept/Program.cs index 6aae7a5..aad6a6f 100644 --- a/Source/ProofOfConcept/Program.cs +++ b/Source/ProofOfConcept/Program.cs @@ -51,6 +51,7 @@ if (app.Environment.IsDevelopment()) }); app.MapGet("/CheckRegisteredApplication", ([FromServices] TeslaAuthenticatorService service) => service.CheckApplicationRegistrationAsync()); app.MapGet("/RegisterApplication", ([FromServices] TeslaAuthenticatorService service) => service.RegisterApplicationAsync()); + app.MapGet("/Authorize", ([FromServices] TeslaAuthenticatorService service) => new RedirectResult(service.GetAplicationAuthorizationURL())); } //Map static assets diff --git a/Source/ProofOfConcept/ProofOfConcept.csproj b/Source/ProofOfConcept/ProofOfConcept.csproj index 5e55dab..d7a56a2 100644 --- a/Source/ProofOfConcept/ProofOfConcept.csproj +++ b/Source/ProofOfConcept/ProofOfConcept.csproj @@ -31,7 +31,7 @@ - Never + PreserveNewest diff --git a/Source/ProofOfConcept/Services/TeslaAuthenticatorService.cs b/Source/ProofOfConcept/Services/TeslaAuthenticatorService.cs index faecc32..81b4224 100644 --- a/Source/ProofOfConcept/Services/TeslaAuthenticatorService.cs +++ b/Source/ProofOfConcept/Services/TeslaAuthenticatorService.cs @@ -1,4 +1,6 @@ using System.Net.Http.Headers; +using System.Security.Cryptography; +using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.Extensions.Caching.Memory; @@ -220,12 +222,73 @@ public class TeslaAuthenticatorService httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", partnerToken.AccessToken); - //Send request - HttpResponseMessage response = await httpClient.GetAsync($"{euBaseURL}/api/1/partner_accounts/public_key?domain={this.configuration.Domain}", cancellationToken); - string responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - - logger.LogInformation("Application registration result: {Result}", responseBody); - return new Result(); + try + { + //Send request + HttpResponseMessage response = await httpClient.GetAsync($"{euBaseURL}/api/1/partner_accounts/public_key?domain={this.configuration.Domain}", cancellationToken); + string responseBody = await response.Content.ReadAsStringAsync(cancellationToken); + logger.LogInformation("Application registration result: {Result}", responseBody); + + //Parse response + using JsonDocument? doc = JsonDocument.Parse(responseBody); + string publicKeyHex = doc.RootElement.GetProperty("response").GetProperty("public_key").GetString() ?? throw new JsonException("Public key not found in response"); + + //Public key bytes + byte[] bytes = Convert.FromHexString(publicKeyHex); + + //Get bytes from PEM key + string pem = await File.ReadAllTextAsync("Resources/Signature/public-key.pem", cancellationToken: cancellationToken);; + string[] lines = pem.Split('\n') + .Select(l => l.Trim()) + .Where(l => !string.IsNullOrEmpty(l) && + !l.StartsWith("-----")) + .ToArray(); + + string base64 = string.Join("", lines); + byte[] pemBytes = Convert.FromBase64String(base64); + + // Parse the PEM with ECDsa to get the raw Q.X||Q.Y + using var ecdsa = ECDsa.Create(); + ecdsa.ImportSubjectPublicKeyInfo(pemBytes, out _); + ECParameters parameters = ecdsa.ExportParameters(false); + byte[]? x = parameters.Q.X; + byte[]? y = parameters.Q.Y; + + if (x is null || y is null) + throw new CryptographicException("Invalid PEM file"); + + // Assemble into uncompressed SEC1 format + byte[] pemKeyBytes = new byte[1 + x.Length + y.Length]; + pemKeyBytes[0] = 0x04; // uncompressed marker + Buffer.BlockCopy(x, 0, pemKeyBytes, 1, x.Length); + Buffer.BlockCopy(y, 0, pemKeyBytes, 1 + x.Length, y.Length); + + // Compare + bool match = bytes.SequenceEqual(pemKeyBytes); + + return Result.Success(match); + } + catch (Exception e) + { + logger.LogError(e, "Error while checking application registration"); + return Result.Fail(e); + } + } + + public string GetAplicationAuthorizationURL() + { + //https://auth.tesla.com/oauth2/v3/authorize?&client_id=$CLIENT_ID&locale=en-US&prompt=login&redirect_uri=$REDIRECT_URI&response_type=code&scope=openid%20vehicle_device_data%20offline_access&state=$STATE + StringBuilder sb = new StringBuilder(); + sb.Append("https://auth.tesla.com/oauth2/v3/authorize?response_type=code"); + sb.AppendFormat("&client_id={0}", this.configuration.ClientID); + sb.AppendFormat("&redirect_uri={0}"); + sb.AppendFormat("&scope=openid offline_access vehicle_device_data vehicle_location"); + sb.AppendFormat("&state=1234567890"); + sb.AppendFormat("&nonce=1234567890"); + sb.AppendFormat("&prompt_missing_scopes=true"); + sb.AppendFormat("&require_requested_scopes=true"); + sb.AppendFormat("&show_keypair_step=true"); + return sb.ToString(); } }