< Summary

Information
Class: LGDXRobotCloud.API.Services.Administration.RobotCertificateService
Assembly: LGDXRobotCloud.API
File(s): /builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/Administration/RobotCertificateService.cs
Line coverage
100%
Covered lines: 118
Uncovered lines: 0
Coverable lines: 118
Total lines: 166
Line coverage: 100%
Branch coverage
87%
Covered branches: 7
Total branches: 8
Branch coverage: 87.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/Administration/RobotCertificateService.cs

#LineLine coverage
 1using System.Security.Cryptography;
 2using System.Security.Cryptography.X509Certificates;
 3using LGDXRobotCloud.API.Configurations;
 4using LGDXRobotCloud.API.Exceptions;
 5using LGDXRobotCloud.Data.DbContexts;
 6using LGDXRobotCloud.Data.Entities;
 7using LGDXRobotCloud.Data.Models.Business.Administration;
 8using LGDXRobotCloud.Utilities.Helpers;
 9using Microsoft.EntityFrameworkCore;
 10using Microsoft.Extensions.Options;
 11
 12namespace LGDXRobotCloud.API.Services.Administration;
 13
 14public interface IRobotCertificateService
 15{
 16  Task<(IEnumerable<RobotCertificateListBusinessModel>, PaginationHelper)> GetRobotCertificatesAsync(int pageNumber, int
 17  Task<RobotCertificateBusinessModel> GetRobotCertificateAsync(Guid robotCertificateId);
 18  RobotCertificateIssueBusinessModel IssueRobotCertificate(Guid robotId);
 19  Task<RobotCertificateRenewBusinessModel> RenewRobotCertificateAsync(RobotCertificateRenewRequestBusinessModel robotCer
 20
 21  RootCertificateBusinessModel? GetRootCertificate();
 22}
 23
 824public class RobotCertificateService(
 825    LgdxContext context,
 826    IOptionsSnapshot<LgdxRobotCloudConfiguration> options
 827  ) : IRobotCertificateService
 28{
 829  private readonly LgdxContext _context = context;
 830  private readonly LgdxRobotCloudConfiguration _lgdxRobotCloudConfiguration = options.Value;
 31
 32  private record CertificateDetail
 33  {
 634    required public string RootCertificate { get; set; }
 635    required public string RobotCertificatePrivateKey { get; set; }
 636    required public string RobotCertificatePublicKey { get; set; }
 637    required public string RobotCertificateThumbprint { get; set; }
 638    required public DateTime RobotCertificateNotBefore { get; set; }
 639    required public DateTime RobotCertificateNotAfter { get; set; }
 40  }
 41
 42  public async Task<(IEnumerable<RobotCertificateListBusinessModel>, PaginationHelper)> GetRobotCertificatesAsync(int pa
 143  {
 144    var query = _context.RobotCertificates as IQueryable<RobotCertificate>;
 145    var itemCount = await query.CountAsync();
 146    var PaginationHelper = new PaginationHelper(itemCount, pageNumber, pageSize);
 147    var robotCertificates = await query.AsNoTracking()
 148      .OrderBy(a => a.Id)
 149      .Skip(pageSize * (pageNumber - 1))
 150      .Take(pageSize)
 151      .Select(a => new RobotCertificateListBusinessModel {
 152        Id = a.Id,
 153        Thumbprint = a.Thumbprint,
 154        ThumbprintBackup = a.ThumbprintBackup,
 155        NotBefore = a.NotBefore,
 156        NotAfter = a.NotAfter
 157      })
 158      .ToListAsync();
 159    return (robotCertificates, PaginationHelper);
 160  }
 61
 62  public async Task<RobotCertificateBusinessModel> GetRobotCertificateAsync(Guid robotCertificateId)
 263  {
 264    return await _context.RobotCertificates.AsNoTracking()
 265      .Where(a => a.Id == robotCertificateId)
 266      .Include(a => a.Robot)
 267      .Select(a => new RobotCertificateBusinessModel {
 268        Id = a.Id,
 269        RobotId = a.Robot.Id,
 270        RobotName = a.Robot.Name,
 271        Thumbprint = a.Thumbprint,
 272        ThumbprintBackup = a.ThumbprintBackup,
 273        NotBefore = a.NotBefore,
 274        NotAfter = a.NotAfter
 275      })
 276      .FirstOrDefaultAsync()
 277        ?? throw new LgdxNotFound404Exception();
 178  }
 79
 80  private CertificateDetail GenerateRobotCertificate(Guid robotId)
 381  {
 382    X509Store store = new(StoreName.My, StoreLocation.CurrentUser);
 383    store.Open(OpenFlags.OpenExistingOnly);
 1184    X509Certificate2 rootCertificate = store.Certificates.First(c => c.SerialNumber == _lgdxRobotCloudConfiguration.Root
 85
 386    var certificateNotBefore = DateTime.UtcNow;
 387    var certificateNotAfter = DateTimeOffset.UtcNow.AddDays(_lgdxRobotCloudConfiguration.RobotCertificateValidDay);
 88
 389    var rsa = RSA.Create();
 390    var certificateRequest = new CertificateRequest("CN=LGDXRobot Cloud Robot Certificate for " + robotId.ToString() + "
 391    var certificate = certificateRequest.Create(rootCertificate, certificateNotBefore, certificateNotAfter, RandomNumber
 92
 393    return new CertificateDetail {
 394      RootCertificate = rootCertificate.ExportCertificatePem(),
 395      RobotCertificatePrivateKey = new string(PemEncoding.Write("PRIVATE KEY", rsa.ExportPkcs8PrivateKey())),
 396      RobotCertificatePublicKey = certificate.ExportCertificatePem(),
 397      RobotCertificateThumbprint = certificate.Thumbprint,
 398      RobotCertificateNotBefore = certificateNotBefore,
 399      RobotCertificateNotAfter = certificateNotAfter.DateTime
 3100    };
 3101  }
 102
 103  public RobotCertificateIssueBusinessModel IssueRobotCertificate(Guid robotId)
 1104  {
 1105    var certificate = GenerateRobotCertificate(robotId);
 1106    return new RobotCertificateIssueBusinessModel {
 1107      RootCertificate = certificate.RootCertificate,
 1108      RobotCertificatePrivateKey = certificate.RobotCertificatePrivateKey,
 1109      RobotCertificatePublicKey = certificate.RobotCertificatePublicKey,
 1110      RobotCertificateThumbprint = certificate.RobotCertificateThumbprint,
 1111      RobotCertificateNotBefore = certificate.RobotCertificateNotBefore,
 1112      RobotCertificateNotAfter = certificate.RobotCertificateNotAfter
 1113    };
 1114  }
 115
 116  public async Task<RobotCertificateRenewBusinessModel> RenewRobotCertificateAsync(RobotCertificateRenewRequestBusinessM
 3117  {
 3118    var certificate = _context.RobotCertificates
 3119      .Where(c => c.Id == robotCertificateRenewRequestBusinessModel.CertificateId)
 3120      .FirstOrDefault()
 3121        ?? throw new LgdxNotFound404Exception();
 122
 2123      var newCertificate = GenerateRobotCertificate(certificate.RobotId);
 2124      if (robotCertificateRenewRequestBusinessModel.RevokeOldCertificate)
 1125      {
 1126        certificate.ThumbprintBackup = null;
 1127      }
 128      else
 1129      {
 1130        certificate.ThumbprintBackup = certificate.Thumbprint;
 1131      }
 2132      certificate.Thumbprint = newCertificate.RobotCertificateThumbprint;
 2133      certificate.NotBefore = DateTime.SpecifyKind(newCertificate.RobotCertificateNotBefore, DateTimeKind.Utc);
 2134      certificate.NotAfter = DateTime.SpecifyKind(newCertificate.RobotCertificateNotAfter, DateTimeKind.Utc);
 2135      await _context.SaveChangesAsync();
 136
 2137      var robot = await _context.Robots.AsNoTracking()
 2138        .Where(r => r.Id == certificate.RobotId)
 2139        .Select(r => new {
 2140          r.Id,
 2141          r.Name
 2142        })
 2143        .FirstOrDefaultAsync()
 2144          ?? throw new LgdxNotFound404Exception();
 145
 2146      return new RobotCertificateRenewBusinessModel {
 2147        RobotId = robot.Id,
 2148        RobotName = robot.Name,
 2149        RootCertificate = newCertificate.RootCertificate,
 2150        RobotCertificatePrivateKey = newCertificate.RobotCertificatePrivateKey,
 2151        RobotCertificatePublicKey = newCertificate.RobotCertificatePublicKey
 2152      };
 2153    }
 154
 155  public RootCertificateBusinessModel? GetRootCertificate()
 1156  {
 1157    X509Store store = new(StoreName.My, StoreLocation.CurrentUser);
 1158    store.Open(OpenFlags.OpenExistingOnly);
 3159    X509Certificate2 rootCertificate = store.Certificates.First(c => c.SerialNumber == _lgdxRobotCloudConfiguration.Root
 1160    return new RootCertificateBusinessModel {
 1161      NotBefore = rootCertificate.NotBefore.ToUniversalTime(),
 1162      NotAfter = rootCertificate.NotAfter.ToUniversalTime(),
 1163      PublicKey = rootCertificate.ExportCertificatePem()
 1164    };
 1165  }
 166}