@denismaster

Как красиво реализовать возможность подписания блока данных?

Добрый день!
Есть класс:
public class MyClass<TData>
{
    // хеш от результата применения эцп
    public ISignature Signature {get;set}
    // ПАРАМЕТРЫ
    public long Param1 {get; set}
    public string Param2 {get; set}
    // и куча других полей
    // ТУТ ДАННЫЕ
    public TData InnerData {get;set}
}

Нужно реализовать поддержку подписи объектов этого класса.
Однако необходимо дать возможность пользователю создание своего класса.
При этом Signature может быть создана различным алгоритмом ЭЦП, который будет использован (есть ISignatureService).
TData - внутренние данные, они также могут быть заменены пользователем, поэтому стоит generic.
И наибольшая сложность - это блок параметров, есть определенные заданные параметры - long Param1, string Param2.
Общее количество этих параметров может быть изменено пользователем (например, добавлен int Param3 и т.д.)
Signature вычисляется от этих параметров(всех, и дополнительных тоже)
Потом нужно будет добавить сервисы-валидаторы для таких объектов, а также сервисы-фабрики.
Как правильно оформить иерархию классов, интерфейсов, добавить туда generics чтобы оно все было красиво??
Я уже накинул пару вариантов, как это сделать, но я не совсем еще уверен, какой код окажется в итоге лучше и проще.

Вариант 1
public interface IClassHeader{
  long Param1 {get;}
  string Param2 {get;}
}

public interface IMyObj<THeader, TData> where THeader: IClassHeader{
  ISignature Signature { get; }
  THeader Header {get;}
  TData InnerData {get;}
} 
public class MyHeader: IClassHeader { ...} 
public class MyObj: IMyObj<MyHeader, string> { ... }

public interface IMyObjValidator<TObj, THeader, TData> 
  where TObj: IMyObj<THeader, TData>
  where THeader: IClassHeader { 
 bool CheckSignature(TObj inputObject);
}

Вариант 2
public interface IObjectWithParams{  //пользователь расширяет именно его
  ISignature Signature {get;}
  long Param1 {get;}
  string Param2 {get;}
}
public interface ICheckableObject { 
  bool CheckSignature(IMyObjValidationAlgorithm algorithm);
}
public interface ISignableObject { 
  void SignObject(ISignatureService service);
}
public interface IDataObject<TData> : IObjectWithParams { 
  TData Data {get;} 
}
public interface IMyObject<TData>: IDataObject<TData>, ICheckableObject, ISignableObject {}
public class MyObject: IMyObject<string> {
  ...
 private ISignature _signature = null;
 void ISignableObject.Sign(ISignatureService service) { 
   var sign = service.Sign(this);
   _signature = sign;
  }
 public ISignature Signature => _signature
}



Соответственно в первом варианте будет специальный класс, который принимает IClassHeader и вычисляет хеш от подписанного блока с параметрами, а во втором - по аналогии с ICheckableObject, будет реализовано заполнение поля Signature специальным классом. Есть еще мысля что второй вариант напоминает мне пример паттерна Visitor.

Как будет лучше?
  • Вопрос задан
  • 315 просмотров
Решения вопроса 1
lam0x86
@lam0x86
Как я понял задачу: у вас есть две сущности, реализация которых неизвестна:
  • подписываемый объект
  • сервис генерации подписи

Объект ничего не знает, каким сервисом он будет подписан, а сервис не знает, какая структура у подписываемого объекта.
В этом случае логично ввести некую промежуточную сущность, в которую объект может себя сериализовать, а сервис будет знать, как её подписать. В простейшем случае это может быть массив, например, byte[]. Можно придумать более изощренный способ с потоковой генерацией подписи, чтобы не создавать промежуточный объект.

Может, я неправильно понял задачу, но вот, что у меня вышло.

Интерфейсы:
public interface ISignable
{
    ISignature Signature { get; set; }
    byte[] Serialize();
}

public interface ISignature
{
}

public interface ISignatureService
{
    bool Validate(ISignable signable);
    void Sign(ISignable signable);
}


Простейшая имплементация (в качестве подписи используется просто хеш-код):
class MyObject : ISignable
{
    public ISignature Signature { get; set; }
    public long Param1 { get; set; }
    public string Param2 { get; set; }
    public string InnerData { get; set; }

    public byte[] Serialize()
    {
        return Encoding.UTF8.GetBytes(Param1 + Param2 + InnerData);
    }
}

public class HashCodeSignatureService : ISignatureService
{
    public void Sign(ISignable signable)
    {
        var signature = CalculateSignature(signable);
        signable.Signature = signature;
    }

    public bool Validate(ISignable signable)
    {
        var s1 = CalculateSignature(signable);
        var s2 = signable.Signature as SimpleHashCodeSignature;
        return s1?.HashCode == s2?.HashCode;
    }

    private static SimpleHashCodeSignature CalculateSignature(ISignable signable)
    {
        var body = signable.Serialize();
        var signature = new SimpleHashCodeSignature(body.Aggregate(0, (a, b) => a + b.GetHashCode()));
        return signature;
    }
}

public class SimpleHashCodeSignature : ISignature
{
    public int HashCode { get; }

    public SimpleHashCodeSignature(int hashCode)
    {
        HashCode = hashCode;
    }
}


А так это можно использовать:
class Program
{
    static void Main(string[] args)
    {
        var obj = new MyObject {Param1 = 1, Param2 = "asd", InnerData = "some data"};
        var signatureService = new HashCodeSignatureService();
        signatureService.Sign(obj);
        // Passing the object across untrusted boundary
        signatureService.Validate(obj);
    }
}
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы