Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/shared/Core/Authentication/StructuredToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace GitCredentialManager.Authentication
{
public abstract class StructuredToken
{
private class JwtHeader
{
[JsonRequired]
[JsonInclude]
[JsonPropertyName("typ")]
public string Type { get; private set; }
}
private class JwtPayload : StructuredToken
{
[JsonRequired]
[JsonInclude]
[JsonPropertyName("exp")]
public long Expiry { get; private set; }

public override bool IsExpired
{
get
{
return Expiry < DateTimeOffset.Now.ToUnixTimeSeconds();
}
}
}

public abstract bool IsExpired { get; }

public static bool TryCreate(string value, out StructuredToken token)
{
try
{
// elements of JWT structure "<header>.<payload>.<signature>"
var parts = value.Split('.');
if (parts.Length == 3)
{
var header = JsonSerializer.Deserialize<JwtHeader>(Base64UrlConvert.Decode(parts[0]));
if ("JWT".Equals(header.Type, StringComparison.OrdinalIgnoreCase))
{
token = JsonSerializer.Deserialize<JwtPayload>(Base64UrlConvert.Decode(parts[1]));
return true;
}
}
}
catch { }

// invalid token data on content mismatch or deserializer exception
token = null;
return false;
}
}
}
39 changes: 30 additions & 9 deletions src/shared/Core/Base64UrlConvert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,43 @@ namespace GitCredentialManager
{
public static class Base64UrlConvert
{

// The base64url format is the same as regular base64 format except:
// 1. character 62 is "-" (minus) not "+" (plus)
// 2. character 63 is "_" (underscore) not "/" (slash)
// 3. padding is optional
private const char base64PadCharacter = '=';
private const char base64Character62 = '+';
private const char base64Character63 = '/';
private const char base64UrlCharacter62 = '-';
private const char base64UrlCharacter63 = '_';

public static string Encode(byte[] data, bool includePadding = true)
{
const char base64PadCharacter = '=';
const char base64Character62 = '+';
const char base64Character63 = '/';
const char base64UrlCharacter62 = '-';
const char base64UrlCharacter63 = '_';

// The base64url format is the same as regular base64 format except:
// 1. character 62 is "-" (minus) not "+" (plus)
// 2. character 63 is "_" (underscore) not "/" (slash)
string base64Url = Convert.ToBase64String(data)
.Replace(base64Character62, base64UrlCharacter62)
.Replace(base64Character63, base64UrlCharacter63);

return includePadding ? base64Url : base64Url.TrimEnd(base64PadCharacter);
}

public static byte[] Decode(string data)
{
string base64 = data
.Replace(base64UrlCharacter62, base64Character62)
.Replace(base64UrlCharacter63, base64Character63);

switch (base64.Length % 4)
{
case 2:
base64 += base64PadCharacter;
goto case 3;
case 3:
base64 += base64PadCharacter;
break;
}

return Convert.FromBase64String(base64);
}
}
}
13 changes: 8 additions & 5 deletions src/shared/Core/GenericHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,20 @@ public async Task<GetCredentialResult> GetCredentialAsync(InputArguments input)
if (credential == null)
{
_context.Trace.WriteLine("No existing credentials found.");

// No existing credential was found, create a new one
_context.Trace.WriteLine("Creating new credential...");
return await GenerateCredentialAsync(input);
}
else if (StructuredToken.TryCreate(credential.Password, out var token) && token.IsExpired)
{
_context.Trace.WriteLine("Credential is expired token.");
}
else
{
_context.Trace.WriteLine("Existing credential found.");
return new GetCredentialResult(credential);
}

return new GetCredentialResult(credential);
// No valid credential was found, create a new one
_context.Trace.WriteLine("Creating new credential...");
return await GenerateCredentialAsync(input);
}

public Task StoreCredentialAsync(InputArguments input)
Expand Down
Loading