/**
* https://tools.ietf.org/html/rfc4648
* https://tools.ietf.org/html/rfc4648#section-5
*/
using System.Text;
using System.Security.Cryptography;
using MaksIT.LetsEncrypt.Entities.Jws;
using MaksIT.Core.Extensions;
namespace MaksIT.LetsEncrypt.Services;
public interface IJwsService {
void SetKeyId(string location);
JwsMessage Encode(JwsHeader protectedHeader);
JwsMessage Encode(TPayload payload, JwsHeader protectedHeader);
string GetKeyAuthorization(string token);
string Base64UrlEncoded(string s);
string Base64UrlEncoded(byte[] arg);
}
public class JwsService : IJwsService {
public Jwk _jwk;
private RSA _rsa;
public JwsService(RSA rsa) {
_rsa = rsa ?? throw new ArgumentNullException(nameof(rsa));
var publicParameters = rsa.ExportParameters(false);
var exp = publicParameters.Exponent ?? throw new ArgumentNullException(nameof(publicParameters.Exponent));
var mod = publicParameters.Modulus ?? throw new ArgumentNullException(nameof(publicParameters.Modulus));
_jwk = new Jwk() {
KeyType = "RSA",
Exponent = Base64UrlEncoded(exp),
Modulus = Base64UrlEncoded(mod),
};
}
public void SetKeyId(string location) {
_jwk.KeyId = location;
}
public JwsMessage Encode(JwsHeader protectedHeader) =>
Encode(null, protectedHeader);
public JwsMessage Encode(T? payload, JwsHeader protectedHeader) {
protectedHeader.Algorithm = "RS256";
if (_jwk.KeyId != null) {
protectedHeader.KeyId = _jwk.KeyId;
}
else {
protectedHeader.Key = _jwk;
}
var message = new JwsMessage {
Payload = "",
Protected = Base64UrlEncoded(protectedHeader.ToJson())
};
if (payload != null) {
if (payload is string stringPayload)
message.Payload = Base64UrlEncoded(stringPayload);
else
message.Payload = Base64UrlEncoded(payload.ToJson());
}
message.Signature = Base64UrlEncoded(
_rsa.SignData(Encoding.ASCII.GetBytes($"{message.Protected}.{message.Payload}"),
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1));
return message;
}
public string GetKeyAuthorization(string token) =>
$"{token}.{GetSha256Thumbprint()}";
private string GetSha256Thumbprint() {
var json = "{\"e\":\"" + _jwk.Exponent + "\",\"kty\":\"RSA\",\"n\":\"" + _jwk.Modulus + "\"}";
return Base64UrlEncoded(SHA256.HashData(Encoding.UTF8.GetBytes(json)));
}
public string Base64UrlEncoded(string s) =>
Base64UrlEncoded(Encoding.UTF8.GetBytes(s));
// https://tools.ietf.org/html/rfc4648#section-5
public string Base64UrlEncoded(byte[] bytes) =>
Convert.ToBase64String(bytes) // Regular base64 encoder
.Split('=').First() // Remove any trailing '='s
.Replace('+', '-') // 62nd char of encoding
.Replace('/', '_'); // 63rd char of encoding
}