D
D
denismaster2017-11-07 19:36:26
OOP
denismaster, 2017-11-07 19:36:26

How to beautifully implement the possibility of signing a data block?

Good afternoon!
There is a class:

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

It is necessary to implement support for signing objects of this class.
However, it is necessary to allow the user to create his own class.
In this case, the Signature can be created by a different digital signature algorithm that will be used (there is an ISignatureService).
TData - internal data, they can also be replaced by the user, so it's worth generic.
And the greatest complexity is the block of parameters, there are certain given parameters - long Param1, string Param2.
The total number of these parameters can be changed by the user (for example, int Param3 is added, etc.)
Signature is calculated from these parameters (all of them, and additional ones too).
Then you will need to add validator services for such objects, as well as factory services.
How to properly arrange the hierarchy of classes, interfaces, add generics there so that everything is beautiful??
I already threw in a couple of options on how to do this, but I'm not quite sure yet which code will turn out to be better and simpler in the end.
Option 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);
}

Option 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
}


Accordingly, in the first variant there will be a special class that accepts IClassHeader and calculates the hash from the signed block with parameters, and in the second, by analogy with ICheckableObject, the Signature field will be filled with a special class. There is another thought that the second option reminds me of an example of the Visitor pattern.
How will be better?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
L
lam0x86, 2017-11-07
@denismaster

As I understand the task: you have two entities, the implementation of which is unknown:
The object does not know what service it will be signed by, and the service does not know what structure the signed object has.
In this case, it is logical to introduce some intermediate entity into which the object can serialize itself, and the service will know how to sign it. In the simplest case, this can be an array, such as byte[]. You can come up with a more sophisticated way with streaming signature generation so as not to create an intermediate object.
Maybe I misunderstood the problem, but here's what I got.
Interfaces:

public interface ISignable
{
    ISignature Signature { get; set; }
    byte[] Serialize();
}

public interface ISignature
{
}

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

The simplest implementation (just a hash code is used as a signature):
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;
    }
}

And this is how you can use it:
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);
    }
}

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question