Browse Source

* Start of ECDSA

Michaël Van Canneyt 3 years ago
parent
commit
6ae8840ece

+ 9 - 0
packages/hash/fpmake.pp

@@ -42,6 +42,15 @@ begin
     T.Dependencies.AddUnit('hashutils');
     T:=P.Targets.AddUnit('src/sha512.pp');
     T.Dependencies.AddUnit('hashutils');
+    T:=P.Targets.AddUnit('src/asn.pp');
+    T.Dependencies.AddUnit('hashutils');
+    T:=P.Targets.AddUnit('src/pem.pp');
+    T.Dependencies.AddUnit('hashutils');
+    T.Dependencies.AddUnit('asn');
+    T:=P.Targets.AddUnit('src/ecdsa.pp');
+    T.Dependencies.AddUnit('hashutils');
+    T.Dependencies.AddUnit('ecc');
+    T.Dependencies.AddUnit('sha256');
     T:=P.Targets.AddUnit('src/onetimepass.pp');
     T:=P.Targets.AddUnit('src/crc.pas');
     T:=P.Targets.AddUnit('src/ntlm.pas');

+ 805 - 0
packages/hash/src/asn.pp

@@ -0,0 +1,805 @@
+unit asn;
+
+{$mode ObjFPC}{$H+}
+{$modeswitch advancedrecords}
+interface
+
+uses
+  Classes, SysUtils, hashutils;
+
+const
+  ASN1_BOOL       = $01;
+  ASN1_INT        = $02;
+  ASN1_BITSTR     = $03;
+  ASN1_OCTSTR     = $04;
+  ASN1_NULL       = $05;
+  ASN1_OBJID      = $06;
+  ASN1_ENUM       = $0A;
+  ASN1_UTF8STRING = $0C;
+  ASN1_PRINTABLESTRING = $13;
+  ASN1_IA5STRING  = $16;
+  ASN1_UTCTIME    = $17;
+  ASN1_SEQ        = $30;
+  ASN1_SETOF      = $31;
+  ASN1_IPADDR     = $40;
+  ASN1_COUNTER    = $41;
+  ASN1_GAUGE      = $42;
+  ASN1_TIMETICKS  = $43;
+  ASN1_OPAQUE     = $44;
+  ASN1_COUNTER64  = $46;
+
+  ASN_emailAddress = '1.2.840.113549.1.9.1';
+  ASN_commonName = '2.5.4.3';
+  ASN_subjectAltName = '2.5.29.17';
+  // ASN_organizationName = '2.5.4.10';
+  // ASN_organizationalUnitName = '2.5.4.11';
+  // ASN_countryName = '2.5.4.6';
+  // ASN_stateOrProvinceName = '2.5.4.8';
+  // ASN_localityName = '2.5.4.7';
+  ASN_ecPublicKey = '1.2.840.10045.2.1';
+  // ASN_prime256v1 = '1.2.840.10045.3.1.7';
+  ASN_secp256r1 = '1.2.840.10045.3.1.7';
+  ASN_ecdsa_with_SHA256 = '1.2.840.10045.4.3.2';
+  ASN_ecdsa_with_SHA512 = '1.2.840.10045.4.3.4';
+  ASN_ecdsa_with_SHA384 = '1.2.840.10045.4.3.3';
+  ASN_ecdsa_with_SHA224 = '1.2.840.10045.4.3.1';
+
+//------------------------------------------------------------------------------
+// ASN
+//------------------------------------------------------------------------------
+procedure ASNEncodeOID(const Value: Int64; var Result: AnsiString);
+function ASNDecodeOID(var Start: Integer; const S: AnsiString): Int64;
+function ASNGetEncodedLen(const Len: Integer): Integer;
+procedure ASNEncodeLen(const Len: Integer; var Buffer: TBytes);
+function ASNReadLen(const Buffer: TBytes; var Offset: Int32): Int32;
+procedure ASNEncodeInt(Value: Int64; var Result: TBytes);
+procedure ASNEncodeUInt(Value: Integer; var Result: TBytes);
+// Encodes ASN.1 object to binary form
+procedure ASNObject(const Data: Ansistring; const ASNType: Integer; var Buffer: TBytes);
+// Encodes an MIB OID Ansistring to binary form
+procedure MibToId(Mib: Ansistring; var Result: AnsiString);
+// Decodes MIB OID from binary form to Ansistring form.
+procedure IdToMib(const Id: Ansistring; var Result: Ansistring);
+procedure ASNDebug(const Buffer: TBytes; var Output: TBytes);
+procedure ASNParse(const Buffer: TBytes; List: TStrings);
+procedure PemToDER(const PEM: AnsiString; const BeginTag, EndTag: Ansistring; Out DER: TBytes); overload;
+procedure PemToDER(PEM: TBytes; const BeginTag, EndTag: Ansistring; Out DER: TBytes); overload;
+procedure ASNParsePemSection(const PEM: TBytes; List: TStrings; const BeginTag, EndTag: Ansistring);
+procedure ASNParsePemSection(const PEM: AnsiString; List: TStrings; const BeginTag, EndTag: Ansistring);
+
+
+implementation
+
+Uses basenenc;
+
+//------------------------------------------------------------------------------
+// ASN
+//------------------------------------------------------------------------------
+
+procedure ASNEncodeOID(const Value: Int64; var Result: Ansistring);
+var
+  B: Boolean;
+  I: Integer;
+  x: Int64;
+  Modulo: Byte;
+  S: Ansistring;
+
+begin
+  S:='';
+  X := Value;
+  B := False;
+  repeat
+    Modulo := X mod 128;
+    X := X div 128;
+    if B then
+      Modulo := Modulo or $80;
+    if x > 0 then
+      B := True;
+    S:=S+Char(Modulo);
+  until x = 0;
+  for I:=Length(S) downto 1 do
+    Result:=Result+S[I];
+end;
+
+// @Start=0
+function ASNDecodeOID(var Start: Integer; const S: AnsiString): Int64;
+var
+  x: Integer;
+begin
+  Result := 0;
+  repeat
+    x := Ord(S[Start]);
+    Inc(Start);
+    Result := (Result shl 7) + (x and $7F);
+  until (x and $80) = 0;
+end;
+
+procedure ASNEncodeLen(const Len: Integer; var Buffer: TBytes);
+var
+  x, y: Integer;
+  S: AnsiString;
+
+begin
+  if Len < $80 then
+  begin
+    Buffer:=Concat(Buffer,[Len]);
+    Exit;
+  end;
+  S:='';
+  x := Len;
+  repeat
+    y := x mod 256;
+    x := x div 256;
+    S:=S+Char(y);
+  until x = 0;
+  y := Length(S);
+  y := y or $80;
+  S:=S+Char(y);
+  for x := Length(S) downto 1 do
+    Buffer:=Concat(Buffer,[Ord(S[x])]);
+end;
+
+function ASNGetEncodedLen(const Len: Integer): Integer;
+var
+  x: Integer;
+begin
+  Result := 1;
+  if Len < $80 then
+    Exit;
+  x := Len;
+  while x > 0 do
+  begin
+    x := x div 256;
+    Inc(Result);
+  end;
+end;
+
+function ASNReadLen(const Buffer: TBytes; var Offset: Int32): Int32;
+var
+  Len: Integer;
+begin
+  Result := Buffer[Offset];
+  Inc(Offset);
+  if Result < $80 then
+    Exit;
+  Len := Result and $7F;
+  Result := 0;
+  while Len > 0 do
+  begin
+    Result := Result*256 + Buffer[Offset];
+    Inc(Offset);
+    Dec(Len);
+  end;
+end;
+
+procedure ASNEncodeInt(Value: Int64; var Result: TBytes);
+
+var
+  x: Int64;
+  y: byte;
+  neg: Boolean;
+  S : AnsiString;
+begin
+  S:='';
+  neg := Value < 0;
+  x := Abs(Value);
+  if neg then
+    x := x - 1;
+  repeat
+    y := x mod 256;
+    x := x div 256;
+    if neg then
+      y := not y;
+    S:=S+AnsiChar(y);
+  until x = 0;
+  if (not neg) and (S[Length(S)] > #$7F) then
+    S:=S+#0
+  else if neg and (S[Length(S)] < #$80) then
+    S:=S+#$FF;
+  for y := S.Length downto 1 do
+    Result:=Concat(Result,[Ord(S[y])]);
+end;
+
+procedure ASNEncodeUInt(Value: Integer; var Result: TBytes);
+var
+  x, y: Integer;
+  neg: Boolean;
+  S : AnsiString;
+
+begin
+  neg := Value < 0;
+  x := Value;
+  if neg then
+    x := x and $7FFFFFFF;
+  S:='';
+  repeat
+    y := x mod 256;
+    x := x div 256;
+    S:=AnsiChar(y);
+  until x = 0;
+  if neg then
+    S[Length(S)]:=AnsiChar(Ord(S[Length(S)]) or $80);
+  for y := Length(S) downto 1 do
+    Result:=Concat(Result,[Ord(S[y])]);
+end;
+
+Procedure AppendStringToBuffer(var Buffer: TBytes; const aString : AnsiString);
+
+Var
+  Buflen,sLen : integer;
+
+begin
+  bufLen:=Length(Buffer);
+  sLen:=Length(aString);
+  SetLength(Buffer,BufLen+sLen);
+  If (sLen>0) then
+    Move(aString[1],Buffer[Buflen],sLen);
+end;
+
+procedure ASNObject(const Data: AnsiString; const ASNType: Integer; var Buffer: TBytes);
+
+
+begin
+  Buffer:=Concat(Buffer,[ASNType]);
+  ASNEncodeLen(Length(Data), Buffer);
+  AppendStringToBuffer(Buffer,Data);
+end;
+
+procedure DumpExStr(const S: AnsiString; var Output: TBytes);
+var
+  I: Integer;
+  x: Byte;
+begin
+  for I := 1 to Length(S) do
+  begin
+    x := Ord(S[I]);
+    if x in [65..90, 97..122] then
+    begin
+      AppendStringToBuffer(Output, ' +''');
+      AppendStringToBuffer(Output, AnsiChar(x)+'''');
+    end else
+    begin
+      AppendStringToBuffer(Output, ' +#$');
+      AppendStringToBuffer(Output, HexStr(X,2));
+    end;
+  end;
+end;
+
+procedure OutputHexa(var Output: TBytes; const S: AnsiString);
+
+var
+  I: Integer;
+  P: PByte;
+
+begin
+  P := PByte(PChar(S));
+  for I := 1 to Length(S) do
+  begin
+    AppendStringToBuffer(Output, HexStr(P^,2));
+    Inc(P);
+  end;
+end;
+
+// @Result[256]
+procedure MibToId(Mib: AnsiString; var Result: AnsiString);
+
+  function WalkInt(var S: AnsiString): Integer;
+  var
+    P : Integer;
+
+  begin
+    P:=Pos('.',S);
+    If P=0 then
+      P:=Length(S)+1;
+    Result:=StrToIntDef(Copy(S,1,P-1),0);
+    S:=Copy(S,Pos('.',S)+1,Length(S));
+  end;
+
+var
+  x: Integer;
+begin
+  x := WalkInt(Mib);
+  x := x*40 + WalkInt(Mib);
+  ASNEncodeOID(x, Result);
+  while (Mib<>'') do
+  begin
+    x := WalkInt(Mib);
+    ASNEncodeOID(x, Result);
+  end;
+end;
+
+// @Result[256]
+procedure IdToMib(const ID: AnsiString; var Result: AnsiString);
+var
+  x, y, Index: Integer;
+begin
+  Index := 1;
+  while Index <= Length(ID) do
+  begin
+    x := ASNDecodeOID(Index, ID);
+    if Index = 2 then
+    begin
+      y := x div 40;
+      x := x mod 40;
+      Result:=IntToStr(y);
+    end;
+    Result:=Result+'.';
+    Result:=Result+IntToStr(x);
+  end;
+end;
+
+function ASNParseInt(const Buffer: TBytes; var Start: Integer; const ASNSize: Integer): Int64;
+var
+  I: Integer;
+  Negative: Boolean;
+  X: Byte;
+begin
+  Result := 0;
+  Negative := False;
+  for I := 1 to ASNSize do
+  begin
+    X := Buffer[Start];
+    if (I = 1) and (X > $7F) then
+      Negative := True;
+    if Negative then
+      X := not X;
+    Result := Result*256 + X;
+    Inc(Start);
+  end;
+  if Negative then
+    Result := -(Result + 1);
+end;
+
+function ASNParseUInt(const Buffer: TBytes; var Start: Integer; const ASNSize: Integer): Int64;
+var
+  I: Integer;
+begin
+  Result := 0;
+  for I := 1 to ASNSize do
+  begin
+    Result := Result*256 + Buffer[Start];
+    Inc(Start);
+  end;
+end;
+
+// Beginning with the @Start position, decode the ASN.1 item of the next element in @Buffer. Type of item is stored in @ASNType
+procedure ASNDebugItem(const Buffer: TBytes; var Start: Integer; Out ASNType, ASNSize: Integer; var Output: TBytes);
+
+  procedure BufToString(out S : AnsiString);
+
+  begin
+    S:='';
+    SetLength(S,ASNSize);
+    if ASNSize>0 then
+      Move(Buffer[Start],S[1],ASNSize);
+  end;
+
+
+var
+  l, n: Integer;
+  S, S2: AnsiString;
+  y: Int64;
+begin
+  S:='';
+  S2:='';
+  ASNType := ASN1_NULL;
+  l := Length(Buffer);
+  if Start > l then
+    Exit;
+  ASNType := Buffer[Start];
+  Inc(Start);
+  ASNSize := ASNReadLen(Buffer, Start);
+  if (Start + ASNSize) > l then
+    Exit;
+  AppendStringToBuffer(Output,'$');
+  AppendStringToBuffer(Output, HexStr(ASNType,2));
+  if (ASNType and $20) > 0 then
+  begin
+//    XBufferAppend(Output, Buffer, Start, ASNSize)
+    if ASNType = ASN1_SEQ then
+      AppendStringToBuffer(Output, ' SEQUENCE: length ')
+    else if ASNType = ASN1_SETOF then
+      AppendStringToBuffer(Output, ' SET: length ')
+    else
+      AppendStringToBuffer(Output, ' constructed: length ');
+    AppendStringToBuffer(Output, IntToStr(ASNSize));
+    Exit;
+  end;
+  case ASNType of
+    ASN1_INT, ASN1_ENUM, ASN1_BOOL:
+      begin
+        if ASNType = ASN1_BOOL then
+          AppendStringToBuffer(Output, ' BOOL: ')
+        else if ASNType = ASN1_INT then
+          AppendStringToBuffer(Output, ' INT: ')
+        else if ASNType = ASN1_ENUM then
+          AppendStringToBuffer(Output, ' ENUM: ');
+        if ASNSize < 8 then
+        begin
+          y := ASNParseInt(Buffer, Start, ASNSize);
+          AppendStringToBuffer(Output, IntToStr(y));
+        end else
+        begin
+          BufToString(S);
+          if S[1] = Char(#00) then
+            Delete(S,1,1);
+          AppendStringToBuffer(Output, '$');
+          OutputHexa(Output, S);
+          Inc(Start, ASNSize);
+        end;
+      end;
+    ASN1_COUNTER, ASN1_GAUGE, ASN1_TIMETICKS, ASN1_COUNTER64:
+      begin
+        if ASNType = ASN1_COUNTER then
+          AppendStringToBuffer(Output, ' COUNTER: ')
+        else if ASNType = ASN1_GAUGE then
+          AppendStringToBuffer(Output, ' GAUGE: ')
+        else if ASNType = ASN1_TIMETICKS then
+          AppendStringToBuffer(Output, ' TIMETICKS: ')
+        else if ASNType = ASN1_COUNTER64 then
+          AppendStringToBuffer(Output, ' COUNTER64: ');
+        if ASNSize < 8 then
+        begin
+          y := ASNParseUInt(Buffer, Start, ASNSize);
+          AppendStringToBuffer(Output, IntToStr(y));
+        end else
+        begin
+          BufToString(S);
+          AppendStringToBuffer(Output, '$');
+          OutputHexa(Output, S);
+          Inc(Start, ASNSize);
+        end;
+      end;
+    ASN1_OCTSTR, ASN1_OPAQUE:
+      begin
+        if ASNType = ASN1_OCTSTR then
+          AppendStringToBuffer(Output, ' OCTSTR: ')
+        else if ASNType = ASN1_OPAQUE then
+          AppendStringToBuffer(Output, ' OPAQUE: ');
+        BufToString(S);
+        OutputHexa(Output, S);
+        Inc(Start, ASNSize);
+      end;
+    ASN1_UTCTIME:
+      begin // 180131123456Z -> 2018-01-31 12:34:56
+        AppendStringToBuffer(Output, ' UTCTIME: ');
+        BufToString(S);
+        AppendStringToBuffer(Output, S);
+        Inc(Start, ASNSize);
+      end;
+    ASN1_BITSTR:
+      begin
+        AppendStringToBuffer(Output, ' BITSTR: ');
+        Inc(Start); // this is the Trailing Length in bits
+        Dec(ASNSize);
+        BufToString(S);
+        OutputHexa(Output, S);
+        if (ASNType = ASN1_BITSTR) and (Buffer[Start] = ASN1_SEQ) then
+        begin
+          // continue to decode the bitstring as ASN.1 formatted content
+        end else
+          Inc(Start, ASNSize);
+      end;
+    ASN1_UTF8STRING, ASN1_PRINTABLESTRING, ASN1_IA5STRING:
+      begin
+        if ASNType = ASN1_UTF8STRING then
+          AppendStringToBuffer(Output, ' UTF8STRING: ')
+        else if ASNType = ASN1_PRINTABLESTRING then
+          AppendStringToBuffer(Output, ' PRINTABLESTRING: ')
+        else if ASNType = ASN1_IA5STRING then
+          AppendStringToBuffer(Output, ' IA5STRING: ');
+        BufToString(S);
+        AppendStringToBuffer(Output, S);
+        Inc(Start, ASNSize);
+      end;
+    ASN1_OBJID:
+      begin
+        AppendStringToBuffer(Output, ' OBJID: ');
+        BufToString(S2);
+        S:='';
+        IdToMib(S2, S);
+        AppendStringToBuffer(Output, S);
+        Inc(Start, ASNSize);
+      end;
+    ASN1_IPADDR:
+      begin
+        AppendStringToBuffer(Output, ' IPADDR: ');
+        for n := 1 to ASNSize do
+        begin
+          if n <> 1 then
+            AppendStringToBuffer(Output, '.');
+          y := Buffer[Start];
+          Inc(Start);
+          AppendStringToBuffer(Output, IntToStr(y));
+        end;
+      end;
+    ASN1_NULL:
+      begin
+        AppendStringToBuffer(Output, ' NULL: ');
+        Inc(Start, ASNSize);
+      end;
+  else // unknown
+    begin
+      AppendStringToBuffer(Output, ' unknown: ');
+      BufToString(S);
+      OutputHexa(Output, S);
+      Inc(Start, ASNSize);
+    end;
+  end;
+end;
+
+// Convert ASN.1 DER encoded buffer to human readable form for debugging
+
+procedure ASNDebug(const Buffer: TBytes; var Output: TBytes);
+
+const
+  SSpaces: AnsiString = '                                                                     ';
+
+var
+  ASNSize, ASNType, Index, n: Integer;
+  Indent: Integer;
+  IndentList: Array of Integer;
+
+begin
+  IndentList:=[];
+  Indent:=0;
+  Index := 0;
+  while Index < Length(Buffer) do
+  begin
+    for n := Length(IndentList)-1 downto 0 do
+    begin
+      ASNSize := IndentList[n];
+      if ASNSize <= Index then
+      begin
+        Delete(IndentList,n,1);
+        Dec(Indent, 2);
+      end;
+    end;
+    AppendStringToBuffer(Output, Copy(SSpaces,1,Indent));
+    ASNDebugItem(Buffer, Index, ASNType, ASNSize, Output);
+    if (ASNType and $20) > 0 then
+    begin
+      Inc(Indent, 2);
+      IndentList:=Concat(IndentList,[ASNSize+Index-1]);
+    end;
+    AppendStringToBuffer(Output, #13#10);
+  end;
+end;
+
+procedure ASNParseAdd(List: TStrings; const S: AnsiString; const ASNType, ASNSize: Integer);
+begin
+  List.AddObject(S, TObject(PtrInt (ASNType shl 16) or (ASNSize)));
+end;
+
+procedure ASNParseAddInt(const Buffer: TBytes; var Start: Integer; List: TStrings; const ASNType, ASNSize: Integer);
+
+  procedure BufToString(var S : AnsiString);
+
+  begin
+    SetLength(S,ASNSize);
+    if ASNSize>0 then
+      Move(Buffer[Start],S[1],ASNSize);
+  end;
+
+var
+  S, S2: AnsiString;
+  y: Int64;
+begin
+  S:='';
+  S2:='';
+  if ASNSize < 8 then
+  begin
+    y := ASNParseInt(Buffer, Start, ASNSize);
+    S:=IntToStr(y);
+  end else
+  begin
+    BufToString(S2);
+    if S2[1] = Char(#00) then
+      Delete(S2,1,1);
+    BytesToHexStr(S,TEncoding.UTF8.GetAnsiBytes(S2));
+    Inc(Start, ASNSize);
+  end;
+  ASNParseAdd(List, S, ASNType, ASNSize);
+end;
+
+procedure ASNParseAddUInt(const Buffer: TBytes; var Start: Integer; List: TStrings; const ASNType, ASNSize: Integer);
+
+  procedure BufToString(out S : AnsiString);
+
+  begin
+    S:='';
+    SetLength(S,ASNSize);
+    if ASNSize>0 then
+      Move(Buffer[Start],S[1],ASNSize);
+  end;
+var
+  S, S2: AnsiString;
+  y: Int64;
+begin
+  S:='';
+  S2:='';
+  if ASNSize < 8 then
+  begin
+    y := ASNParseUInt(Buffer, Start, ASNSize);
+    S:=IntToStr(y);
+  end else
+  begin
+    BufToString(S2);
+    if S2[1] = Char(#00) then
+      Delete(S2,1,1);
+    BytesToHexStr(S,TEncoding.UTF8.GetAnsiBytes(S2));
+    Inc(Start, ASNSize);
+  end;
+  ASNParseAdd(List, S, ASNType, ASNSize);
+end;
+
+function ASNFetch(const Buffer: TBytes; var Offset: Int32; Out ASNType, ASNSize: Int32): Boolean;
+var
+  Len: Int32;
+begin
+  Result := False;
+  Len := Length(Buffer);
+  if Offset > Len then
+    Exit;
+  ASNType := Buffer[Offset];
+  Inc(Offset);
+  ASNSize := ASNReadLen(Buffer, Offset);
+  if (Offset + ASNSize) > Len then
+    Exit;
+  Result := True;
+end;
+
+// Beginning with the @Start position, decode the ASN.1 item of the next element in @Buffer. Type of item is stored in @ASNType
+// @Offset starts at 0
+
+procedure ASNParseItem(const Buffer: TBytes; var Offset: Int32; List: TStrings);
+
+  procedure BufToString(var S : AnsiString; P : PByte; Len : Integer);
+
+  begin
+    SetLength(S,Len);
+    if Len>0 then
+      Move(P^,S[1],Len);
+  end;
+
+var
+  ASNType, ASNSize: Integer;
+  n: Integer;
+  S, S2: AnsiString;
+  y: Int64;
+
+begin
+  if not ASNFetch(Buffer, Offset, ASNType, ASNSize) then
+    Exit;
+  if (ASNType and $20) > 0 then
+  begin // constructed
+    ASNParseAdd(List, '', ASNType, ASNSize);
+    Exit;
+  end;
+  S:='';
+  S2:='';
+  case ASNType of
+    ASN1_INT, ASN1_ENUM, ASN1_BOOL:
+      begin
+        ASNParseAddInt(Buffer, Offset, List, ASNType, ASNSize);
+      end;
+    ASN1_COUNTER, ASN1_GAUGE, ASN1_TIMETICKS, ASN1_COUNTER64:
+      begin
+        ASNParseAddUInt(Buffer, Offset, List, ASNType, ASNSize);
+      end;
+    ASN1_BITSTR, ASN1_OCTSTR, ASN1_OPAQUE:
+      begin
+        if ASNType = ASN1_BITSTR then
+        begin // this is the Trailing Length in bits
+          Inc(Offset);
+          Dec(ASNSize);
+        end;
+        BufToString(S2, @Buffer[Offset], ASNSize);
+        S:=BytesToHexStr(S2);
+        ASNParseAdd(List, S, ASNType, ASNSize);
+        if (ASNType = ASN1_BITSTR) and (Buffer[Offset] = ASN1_SEQ) then
+        begin
+          // continue to decode the bitstring as ASN.1 formatted content
+        end else
+          Inc(Offset, ASNSize);
+      end;
+    ASN1_UTF8STRING, ASN1_PRINTABLESTRING, ASN1_IA5STRING:
+      begin
+        BufToString(S2, @Buffer[Offset], ASNSize);
+        ASNParseAdd(List, S2, ASNType, ASNSize);
+        Inc(Offset, ASNSize);
+      end;
+    ASN1_UTCTIME:
+      begin // 180131123456Z -> 2018-01-31 12:34:56
+        BufToString(S2, @Buffer[Offset], ASNSize);
+        ASNParseAdd(List, S2, ASNType, ASNSize);
+        Inc(Offset, ASNSize);
+      end;
+    ASN1_OBJID:
+      begin
+        BufToString(S2, @Buffer[Offset], ASNSize);
+        IdToMib(S2, S);
+        ASNParseAdd(List, S, ASNType, ASNSize);
+        Inc(Offset, ASNSize);
+      end;
+    ASN1_IPADDR:
+      begin
+        for n := 1 to ASNSize do
+        begin
+          if n <> 1 then
+            S:=S+'.';
+          y := Buffer[Offset];
+          Inc(Offset);
+          S:=S+IntToStr(y);
+        end;
+        ASNParseAdd(List, S, ASNType, ASNSize);
+      end;
+    ASN1_NULL:
+      begin
+        ASNParseAdd(List, '', ASNType, ASNSize);
+        Inc(Offset, ASNSize);
+      end;
+  else // unknown
+    begin
+      BufToString(S2, @Buffer[Offset], ASNSize);
+      S:=BytesToHexStr(S2);
+      ASNParseAdd(List, S, ASNType, ASNSize);
+      Inc(Offset, ASNSize);
+    end;
+  end;
+end;
+
+// Convert ASN.1 DER encoded buffer to human readable form for debugging
+procedure ASNParse(const Buffer: TBytes; List: TStrings);
+var
+  Index: integer;
+begin
+  Index := 0;
+  while Index < Length(Buffer) do
+    ASNParseItem(Buffer, Index, List);
+end;
+
+procedure PemToDER(PEM: TBytes; const BeginTag, EndTag: AnsiString; out DER: TBytes);
+
+begin
+  PemToDER(TEncoding.UTF8.GetAnsiString(PEM),BeginTag,EndTag,DER);
+end;
+
+procedure PemToDER(Const PEM: AnsiString; const BeginTag, EndTag: AnsiString; Out DER: TBytes);
+
+var
+  Content: AnsiString;
+
+begin
+  DER:=[];
+  Content:=ExtractBetween(Pem, BeginTag, EndTag);
+  Content:=Trim(Content);
+  if Length(Content) = 0 then
+    Exit;
+  DER:=Base64.Decode(Content,True);
+end;
+
+procedure ASNParsePemSection(const PEM: TBytes; List: TStrings; const BeginTag, EndTag: AnsiString);
+
+begin
+  ASNParsePemSection(TEncoding.UTF8.GetAnsiString(PEM),List,BeginTag,EndTag);
+end;
+
+procedure ASNParsePemSection(const PEM: AnsiString; List: TStrings; const BeginTag, EndTag: AnsiString);
+
+var
+  BufferSection,res: TBytes;
+//  S : AnsiString;
+
+begin
+  List.Clear;
+  PemToDER(PEM, BeginTag, EndTag, BufferSection);
+  {ASNDebug(BufferSection,Res);
+  S:=TEncoding.UTF8.GetAnsiString(Res);
+  Writeln('ASN Debug: ',S);}
+  ASNParse(BufferSection, List);
+end;
+
+
+end.
+

+ 97 - 0
packages/hash/src/ecdsa.pp

@@ -0,0 +1,97 @@
+unit ecdsa;
+
+{$mode ObjFPC}{$H+}
+{$modeswitch advancedrecords}
+interface
+
+uses
+  sysutils, ecc, sha256;
+
+Type
+  { TECDSA }
+
+  TECDSA = record
+    Class Function SignSHA256(aPayLoad : String; aPrivateKey : TECCPrivateKey; out aSignature : TEccSignature) : Boolean; static; overload;
+    Class Function SignSHA256(aPayLoad : TBytes; aPrivateKey : TECCPrivateKey; out aSignature : TEccSignature) : Boolean; static; overload;
+    Class Function VerifySHA256(aPayLoad : String; aPrivateKey : TECCPrivateKey; const aSignature : TEccSignature) : Boolean; static; overload;
+    Class Function VerifySHA256(aPayLoad : TBytes; aPrivateKey : TECCPrivateKey; const aSignature : TEccSignature) : Boolean; static; overload;
+    Class Function VerifySHA256(aPayLoad : String; aPublicKey : TECCPublicKey; const aSignature : TEccSignature) : Boolean; static; overload;
+    Class Function VerifySHA256(aPayLoad : TBytes; aPublicKey : TECCPublicKey; const aSignature : TEccSignature) : Boolean; static;overload;
+   end;
+
+implementation
+
+
+
+{ TECDSA }
+
+class Function TECDSA.SignSHA256(aPayLoad: String; aPrivateKey: TECCPrivateKey; out aSignature: TEccSignature) : boolean;
+begin
+  Result:=SignSha256(TEncoding.UTF8.GetAnsiBytes(aPayload),aPrivateKey,aSignature);
+end;
+
+class Function TECDSA.SignSHA256(aPayLoad: TBytes; aPrivateKey: TECCPrivateKey; out aSignature: TEccSignature) : Boolean;
+
+var
+  lSHA256: TSHA256;
+  eccHash : TECCHash;
+  Pubkey : TECCpublicKey;
+
+begin
+  aSignature:=Default(TEccSignature);
+  lSHA256.Init;
+  lSHA256.Update(aPayload);
+  lSHA256.Final;
+  Move(lSHA256.Digest[0], eccHash, SizeOf(eccHash));
+  Result:=EcdsaSign(aPrivateKey,eccHash,aSignature);
+  if Result then
+    begin
+    EccPublicKeyFromPrivateKey(PubKey,aPrivateKey);
+    Result:=EcdsaVerify(Pubkey,eccHash,aSignature);
+    end;
+end;
+
+Class Function TECDSA.VerifySHA256(aPayLoad : String; aPrivateKey : TECCPrivateKey; const aSignature : TEccSignature) : Boolean; static;
+
+var
+  Pubkey : TECCpublicKey;
+
+begin
+  EccPublicKeyFromPrivateKey(PubKey,aPrivateKey);
+  Result:=VerifySHA256(aPayLoad,PubKey,aSignature);
+end;
+
+Class Function TECDSA.VerifySHA256(aPayLoad : TBytes; aPrivateKey : TECCPrivateKey; const aSignature : TEccSignature) : Boolean; static;
+
+var
+  Pubkey : TECCpublicKey;
+
+begin
+  EccPublicKeyFromPrivateKey(PubKey,aPrivateKey);
+  Result:=VerifySHA256(aPayLoad,PubKey,aSignature);
+end;
+
+Class Function TECDSA.VerifySHA256(aPayLoad : String; aPublicKey : TECCPublicKey; const aSignature : TEccSignature) : Boolean; static;
+
+begin
+  Result:=VerifySHA256(TEncoding.UTF8.GetAnsiBytes(aPayload),aPublicKey,aSignature);
+end;
+
+Class Function TECDSA.VerifySHA256(aPayLoad : TBytes; aPublicKey : TECCPublicKey; const aSignature : TEccSignature) : Boolean; static;
+
+var
+  lSHA256: TSHA256;
+  eccHash : TECCHash;
+
+begin
+  lSHA256.Init;
+  lSHA256.Update(aPayload);
+  lSHA256.Final;
+  Move(lSHA256.Digest[0], eccHash, SizeOf(eccHash));
+  Result:=EcdsaVerify(aPublickey,eccHash,aSignature);
+end;
+
+
+
+end.
+

+ 172 - 0
packages/hash/src/pem.pp

@@ -0,0 +1,172 @@
+unit pem;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, ecc;
+
+const
+  _BEGIN_CERTIFICATE = '-----BEGIN CERTIFICATE-----';
+  _END_CERTIFICATE = '-----END CERTIFICATE-----';
+  _BEGIN_EC_PARAMETERS = '-----BEGIN EC PARAMETERS-----';
+  _END_EC_PARAMETERS = '-----END EC PARAMETERS-----';
+  _BEGIN_EC_PRIVATE_KEY = '-----BEGIN EC PRIVATE KEY-----';
+  _END_EC_PRIVATE_KEY = '-----END EC PRIVATE KEY-----';
+  _BEGIN_RSA_PRIVATE_KEY = '-----BEGIN RSA PRIVATE KEY-----';
+  _END_RSA_PRIVATE_KEY = '-----END RSA PRIVATE KEY-----';
+  _BEGIN_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----';
+  _END_PRIVATE_KEY = '-----END PRIVATE KEY-----';
+
+
+Function PemIsECDSA(const aStream : TStream; List: TStrings): Boolean;
+Function PemIsECDSA(const FileName: String; List: TStrings): Boolean;
+
+procedure PemLoadPublicKey64FromList(List: TStrings; out PrivateKey: TEccPrivateKey; out PublicKey: TEccPublicKey; out PublicKeyX64, PublicKeyY64, ThumbPrint: AnsiString);
+procedure PemLoadPublicKey64FromList(List: TStrings; out PrivateKey: TEccPrivateKey; out PublicKey: TEccPublicKey; out PublicKeyX64, PublicKeyY64: AnsiString);
+
+Function PemLoadECDSA(const FileName: String; out PrivateKey: TEccPrivateKey; out PublicKey: TEccPublicKey; out PublicKeyX64, PublicKeyY64, ThumbPrint: AnsiString) : Boolean;
+Function PemLoadECDSA(const FileName: String; out PrivateKey: TEccPrivateKey; out PublicKey: TEccPublicKey; out PublicKeyX64, PublicKeyY64: AnsiString): Boolean;
+Function PemLoadECDSA(const aStream : TStream; out PrivateKey: TEccPrivateKey; out PublicKey: TEccPublicKey; out PublicKeyX64, PublicKeyY64, ThumbPrint: AnsiString) : Boolean;
+Function PemLoadECDSA(const aStream : TStream; out PrivateKey: TEccPrivateKey; out PublicKey: TEccPublicKey; out PublicKeyX64, PublicKeyY64: AnsiString): Boolean;
+
+
+implementation
+
+uses basenenc, sha256, asn, hashutils;
+
+procedure PemLoadPublicKey64FromList(List: TStrings; out PrivateKey: TEccPrivateKey; out PublicKey: TEccPublicKey; out PublicKeyX64, PublicKeyY64, ThumbPrint: AnsiString);
+
+Var
+  S : String;
+
+begin
+  PemLoadPublicKey64FromList(List,PrivateKey,PublicKey,PublicKeyX64,PublicKeyY64);
+  // Thumbprint
+  S:='{"crv":"P-256","kty":"EC","x":"' + PublicKeyX64 + '","y":"' + PublicKeyY64 + '"}';
+  TSHA256.DigestBase64(TEncoding.UTF8.GetAnsiBytes(S),True,ThumbPrint);
+end;
+
+procedure PemLoadPublicKey64FromList(List: TStrings; out PrivateKey: TEccPrivateKey; out PublicKey: TEccPublicKey; out PublicKeyX64, PublicKeyY64: AnsiString);
+
+var
+  SPrivateKeyHexa, SPublicKeyHexa: String;
+  DecompressedPublicKey, SPrivateKey : TBytes;
+
+begin
+  SPrivateKeyHexa := List.Strings[2];
+  HexStrToBytes(SPrivateKeyHexa, SPrivateKey);
+  if Length(SPrivateKey)>=SizeOf(PrivateKey) then
+    BytesToVar(SPrivateKey,Privatekey,SizeOf(Privatekey))
+  else
+    Raise Exception.CreateFmt('Wrong private key length, got %d, expected %d',[Length(SPrivateKey),SizeOf(TPrivateKey)]);
+  SPublicKeyHexa := List.Strings[6]; // 132 chars
+  if Length(SPublicKeyHexa) = 2+2*(2*ECC_BYTES) then
+    Delete(SPublicKeyHexa,1,2); // skip the leading $04 for uncompressed publickey
+  DecompressedPublicKey:=HexStrToBytes(SPublicKeyHexa);
+  EccPublicKeyFromHexa(PublicKey, SPublicKeyHexa);
+  PublicKeyX64:=base64URL.Encode(Copy(DecompressedPublicKey,0,32),False);
+  PublicKeyY64:=base64URL.Encode(Copy(DecompressedPublicKey, 32, 32),False);
+end;
+
+function PemIsECDSA(const FileName: String; List: TStrings): Boolean;
+
+var
+  F : TFileStream;
+begin
+  F:=TFileStream.Create(FileName,fmOpenRead or fmShareDenyNone);
+  try
+    Result:=PemIsECDSA(F,List);
+  finally
+    F.Free;
+  end;
+end;
+
+
+Function PemLoadECDSA(const FileName: String; Out PrivateKey: TEccPrivateKey; Out PublicKey: TEccPublicKey; Out PublicKeyX64, PublicKeyY64: AnsiString) : Boolean;
+
+var
+  List: TStrings;
+
+begin
+  List := TStringList.Create;
+  try
+    Result:=FileExists(FileName) and PemIsECDSA(FileName, List);
+    if Result then
+      PemLoadPublicKey64FromList(List, PrivateKey, PublicKey, PublicKeyX64, PublicKeyY64);
+  finally
+    List.Free;
+  end;
+end;
+
+
+Function PemLoadECDSA(const FileName: String; out PrivateKey: TEccPrivateKey; out PublicKey: TEccPublicKey; out PublicKeyX64, PublicKeyY64, ThumbPrint: AnsiString) : Boolean;
+
+var
+  List: TStrings;
+
+begin
+  List := TStringList.Create;
+  try
+    Result:=FileExists(FileName) and PemIsECDSA(FileName, List);
+    if Result then
+      PemLoadPublicKey64FromList(List, PrivateKey, PublicKey, PublicKeyX64, PublicKeyY64,ThumbPrint);
+  finally
+    List.Free;
+  end;
+end;
+
+function PemIsECDSA(const aStream : TStream; List: TStrings): Boolean;
+
+var
+  Buffer: TBytes;
+  CurveOID: String;
+
+begin
+  Result := False;
+  Buffer:=[];
+  SetLength(Buffer,aStream.Size);
+  aStream.ReadBuffer(Buffer,aStream.Size);
+  ASNParsePemSection(Buffer, List, _BEGIN_EC_PRIVATE_KEY, _END_EC_PRIVATE_KEY);
+  if List.Count < 7 then
+    Exit;
+  CurveOID := List.Strings[4];
+  Result := (CurveOID=ASN_secp256r1);
+end;
+
+Function PemLoadECDSA(const aStream : TStream; out PrivateKey: TEccPrivateKey; out PublicKey: TEccPublicKey; out PublicKeyX64, PublicKeyY64, ThumbPrint: AnsiString) : Boolean;
+
+var
+  List: TStrings;
+
+begin
+  List := TStringList.Create;
+  try
+    Result:=PemIsECDSA(aStream, List);
+    if Result then
+      PemLoadPublicKey64FromList(List, PrivateKey, PublicKey, PublicKeyX64, PublicKeyY64, ThumbPrint);
+  finally
+    List.Free;
+  end;
+end;
+
+Function PemLoadECDSA(const aStream: Tstream; Out PrivateKey: TEccPrivateKey; Out PublicKey: TEccPublicKey; Out PublicKeyX64, PublicKeyY64: AnsiString) : Boolean;
+
+var
+  List: TStrings;
+
+begin
+  List := TStringList.Create;
+  try
+    Result:=PemIsECDSA(aStream, List);
+    if Result then
+      PemLoadPublicKey64FromList(List, PrivateKey, PublicKey, PublicKeyX64, PublicKeyY64);
+  finally
+    List.Free;
+  end;
+end;
+
+
+end.
+

+ 5 - 0
packages/hash/tests/private-key.pem

@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEII2NxhoAa/ToxQis/eszHjhedjtsTNUrkYxDjVsYBKWpoAoGCCqGSM49
+AwEHoUQDQgAEZ1yhvV1Gb17jDOyS/ezLCDSmRn5wE8rwXHtoZI99uTyQHOmUdaE6
+WnvWViM5qgroF+j1ytVPeZsIQBaor7UVWw==
+-----END EC PRIVATE KEY-----

+ 4 - 0
packages/hash/tests/public-key.pem

@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZ1yhvV1Gb17jDOyS/ezLCDSmRn5w
+E8rwXHtoZI99uTyQHOmUdaE6WnvWViM5qgroF+j1ytVPeZsIQBaor7UVWw==
+-----END PUBLIC KEY-----

+ 77 - 0
packages/hash/tests/testpem.pp

@@ -0,0 +1,77 @@
+unit testpem;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testregistry, asn, pem;
+
+Type
+
+  { TTestPEM }
+
+  TTestPEM = Class(TTestCase)
+  Published
+    Procedure TestLoad;
+  end;
+
+implementation
+
+uses basenenc, hashutils, ecc;
+
+
+{ TTestPEM }
+Const
+  PrivateKeyFile = 'private-key.pem';
+
+procedure TTestPEM.TestLoad;
+
+Const
+  // Hex encoded keys, Obtained using XMLRAD
+  // Private key matched openssl
+  // openssl ec -text -in private-key.pem -noout
+  resprivatekey = '8D8DC61A006BF4E8C508ACFDEB331E385E763B6C4CD52B918C438D5B1804A5A9';
+  respublickey  = '03675CA1BD5D466F5EE30CEC92FDECCB0834A6467E7013CAF05C7B68648F7DB93C';
+  ResX = 'Z1yhvV1Gb17jDOyS_ezLCDSmRn5wE8rwXHtoZI99uTw';
+  ResY = 'kBzplHWhOlp71lYjOaoK6Bfo9crVT3mbCEAWqK-1FVs';
+  // Obtained through OpenSSL: public key as X&Y (DER 04 prefix removed)
+  ResXY = {04}
+          '675ca1bd5d466f5ee30cec92fdec'+
+          'cb0834a6467e7013caf05c7b68648f'+
+          '7db93c901ce99475a13a5a7bd65623'+
+          '39aa0ae817e8f5cad54f799b084016'+
+          'a8afb5155b';
+
+var
+  List: TStrings;
+  PrivateKey  : TEccPrivateKey;
+  PublicKey : TEccPublicKey;
+  XHex,YHex,PublicKeyX64, PublicKeyY64 : Ansistring;
+  Res : Boolean;
+
+begin
+  List := TStringList.Create;
+  try
+    Res:=FileExists(PrivateKeyFile) and PemIsECDSA(PrivateKeyFile, List);
+    if Res then
+       PemLoadPublicKey64FromList(List, PrivateKey, PublicKey, PublicKeyX64, PublicKeyY64);
+    AssertEquals('Private key',resprivatekey,BytesToHexStr(BytesFromVar(@PrivateKey,Sizeof(PrivateKey))));
+    AssertEquals('Public  key',respublickey,BytesToHexStr(BytesFromVar(@PublicKey,Sizeof(PublicKey))));
+    AssertEquals('X',resX,PublicKeyX64);
+    AssertEquals('Y',resY,PublicKeyY64);
+    XHex:=base16.Encode(base64url.Decode(PublicKeyX64),False);
+    YHex:=base16.Encode(base64url.Decode(PublicKeyY64),False);
+    AssertEquals('Public as X,Y',ResXY,LowerCase(XHex+YHex));
+
+//    Writeln('X ', PublicKeyX64,' -> ',XHex);
+//    Writeln('Y ', PublicKeyY64,' -> ',YHex);
+  finally
+    List.Free;
+  end;
+end;
+
+initialization
+  RegisterTest(TTestPEM);
+end.
+

+ 1 - 1
packages/hash/tests/tests.pp

@@ -5,7 +5,7 @@ program tests;
 {$mode objfpc}
 
 uses
-  consoletestrunner, TestsHMAC, testsha256, testonetimepass, sha512, testsha512;
+  consoletestrunner, TestsHMAC, testsha256, testonetimepass, sha512, testsha512, asn, ecc, pem, testpem, ecdsa;
 
 var
   Application: TTestRunner;