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();
}
}