{******************************************************************************} { } { Library: Fundamentals 5.00 } { File name: cASN1.pas } { File version: 5.05 } { Description: Abstract Syntax Notation One (ASN.1) } { BER (Basic Encoding Routines) } { } { Copyright: Copyright (c) 2010-2020, David J Butler } { All rights reserved. } { Redistribution and use in source and binary forms, with } { or without modification, are permitted provided that } { the following conditions are met: } { Redistributions of source code must retain the above } { copyright notice, this list of conditions and the } { following disclaimer. } { THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND } { CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED } { WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED } { WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A } { PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL } { THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, } { INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR } { CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, } { PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF } { USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) } { HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER } { IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING } { NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE } { USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE } { POSSIBILITY OF SUCH DAMAGE. } { } { Github: https://github.com/fundamentalslib } { E-mail: fundamentals.library at gmail.com } { } { References: } { } { ITU-T Rec. X.690 (07/2002) } { http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf } { http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One } { http://en.wikipedia.org/wiki/Basic_Encoding_Rules } { http://luca.ntop.org/Teaching/Appunti/asn1.html } { http://www.obj-sys.com/asn1tutorial/node10.html } { [OID registry] http://www.alvestrand.no/objectid/top.html } { [OID registry] http://oid-info.com/get/1 } { } { Revision history: } { } { 2010/11/08 0.01 Initial development: encoding routines. } { 2010/11/23 0.02 Initial development: decoding routines. } { 2011/04/02 0.03 Compilable with Delphi 5. } { 2018/07/17 5.04 Revised for Fundamentals 5. } { 2018/08/12 5.05 String type changes. } { } { Supported compilers: } { } { Delphi 2010-10.4 Win32/Win64 5.05 2020/06/02 } { Delphi 10.2-10.4 Linux64 5.05 2020/06/02 } { FreePascal 3.0.4 Win64 5.05 2020/06/02 } { } { Todo: } { - Parser to str repr } { - Parser to dom } {******************************************************************************} {$INCLUDE ..\flcInclude.inc} {$IFDEF DEBUG} {$IFDEF TEST} {$DEFINE ASN1_TEST} {$ENDIF} {$ENDIF} unit flcASN1; interface uses { System } SysUtils, { Fundamentals } flcStdTypes; { ASN.1 errors } type EASN1 = class(Exception); { Type identifier octet } { } { Class Bit 8 Bit 7 } { universal 0 0 } { application 0 1 } { context-specific 1 0 } { private 1 1 } { } { Bit 6 = P/C (Primitive/Constructed) } const ASN1_ID_END_OF_CONTENT = $00; ASN1_ID_BOOLEAN = $01; ASN1_ID_INTEGER = $02; ASN1_ID_BIT_STRING = $03; ASN1_ID_OCTET_STRING = $04; ASN1_ID_NULL = $05; ASN1_ID_OBJECT_IDENTIFIER = $06; ASN1_ID_OBJECT_DESCRIPTOR = $07; ASN1_ID_EXTERNAL = $08; ASN1_ID_REAL = $09; ASN1_ID_ENUMERATED = $0A; ASN1_ID_EMBEDDED_PDV = $0B; ASN1_ID_UTF8STRING = $0C; // ISO10646-1 (Unicode, UTF8) ASN1_ID_RELATIVE_OID = $0D; ASN1_ID_NUMERICSTRING = $12; // ASCII ASN1_ID_PRINTABLESTRING = $13; // ASCII (A..Z, a..z, 0..9, ' = ( ) + , - . / : ?) (Excludes @ & _) ASN1_ID_T61STRING = $14; // CCITT T.61 (Teletex) (ASCII, 8 bit, escape sequences) ASN1_ID_VIDEOTEXSTRING = $15; // CCITT T.100/T.101 ASN1_ID_IA5STRING = $16; // International Alphabet 5 (ASCII) ASN1_ID_UTCTIME = $17; ASN1_ID_GENERALIZEDTIME = $18; ASN1_ID_GRAPHICSTRING = $19; ASN1_ID_VISIBLESTRING = $1A; // ISO646 (ASCII) ASN1_ID_GENERALSTRING = $1B; ASN1_ID_UNIVERSALSTRING = $1C; // ISO10646-1 (Unicode, UCS4) ASN1_ID_CHARACTERSTRING = $1D; ASN1_ID_BMPSTRING = $1E; // ISO10646-1 (Unicode, UCS2) ASN1_ID_SEQUENCE = $30; ASN1_ID_SET = $31; ASN1_ID_CONSTRUCTED = $20; ASN1_ID_APPLICATION = $40; ASN1_ID_CONTEXT_SPECIFIC = $80; ASN1_ID_PRIVATE = $C0; ASN1_ID_CONSTR_APPLICATION = ASN1_ID_CONSTRUCTED or ASN1_ID_APPLICATION; ASN1_ID_CONSTR_CONTEXT_SPECIFIC = ASN1_ID_CONSTRUCTED or ASN1_ID_CONTEXT_SPECIFIC; ASN1_ID_CONSTR_PRIVATE = ASN1_ID_CONSTRUCTED or ASN1_ID_PRIVATE; { Object identifiers } type TASN1ObjectIdentifier = array of Integer; procedure ASN1OIDInit(var A: TASN1ObjectIdentifier; const B: array of Integer); function ASN1OIDToStrB(const A: TASN1ObjectIdentifier): RawByteString; function ASN1OIDToStr(const A: TASN1ObjectIdentifier): String; function ASN1OIDEqual(const A: TASN1ObjectIdentifier; const B: array of Integer): Boolean; { OIDs for common hash and cipher algorithms } const // PKCS-1 OID_PKCS_1 : array[0..5] of Integer = (1, 2, 840, 113549, 1, 1); OID_RSA : array[0..6] of Integer = (1, 2, 840, 113549, 1, 1, 1); OID_RSA_PKCS1_MD2 : array[0..6] of Integer = (1, 2, 840, 113549, 1, 1, 2); OID_RSA_PKCS1_MD5 : array[0..6] of Integer = (1, 2, 840, 113549, 1, 1, 4); OID_RSA_PKCS1_SHA1 : array[0..6] of Integer = (1, 2, 840, 113549, 1, 1, 5); OID_RSA_OAEP : array[0..6] of Integer = (1, 2, 840, 113549, 1, 1, 7); OID_RSA_MGF1 : array[0..6] of Integer = (1, 2, 840, 113549, 1, 1, 8); // PKCS#3 OID_PKCS_3 : array[0..5] of Integer = (1, 2, 840, 113549, 1, 3); OID_DHKeyAgreement : array[0..6] of Integer = (1, 2, 840, 113549, 1, 3, 1); // PKCS-9/Signatures/SMIME/alg OID_PKCS_9 : array[0..5] of Integer = (1, 2, 840, 113549, 1, 9); OID_3DES_wrap : array[0..8] of Integer = (1, 2, 840, 113549, 1, 9, 16, 3, 6); OID_RC2_wrap : array[0..8] of Integer = (1, 2, 840, 113549, 1, 9, 16, 3, 7); OID_HMAC_3DES_wrap : array[0..8] of Integer = (1, 2, 840, 113549, 1, 9, 16, 3, 11); OID_HMAC_AES_wrap : array[0..8] of Integer = (1, 2, 840, 113549, 1, 9, 16, 3, 12); // PKCS#2 OID_MD2 : array[0..5] of Integer = (1, 2, 840, 113549, 2, 2); OID_MD4 : array[0..5] of Integer = (1, 2, 840, 113549, 2, 4); OID_MD5 : array[0..5] of Integer = (1, 2, 840, 113549, 2, 5); OID_HMAC_SHA1 : array[0..5] of Integer = (1, 2, 840, 113549, 2, 7); OID_HMAC_SHA224 : array[0..5] of Integer = (1, 2, 840, 113549, 2, 8); OID_HMAC_SHA256 : array[0..5] of Integer = (1, 2, 840, 113549, 2, 9); OID_HMAC_SHA384 : array[0..5] of Integer = (1, 2, 840, 113549, 2, 10); OID_HMAC_SHA512 : array[0..5] of Integer = (1, 2, 840, 113549, 2, 11); // rsadsi/encryptionalgorithm OID_RC2_CBC : array[0..5] of Integer = (1, 2, 840, 113549, 3, 2); OID_RC2_ECB : array[0..5] of Integer = (1, 2, 840, 113549, 3, 3); OID_RC4 : array[0..5] of Integer = (1, 2, 840, 113549, 3, 4); OID_RC4_MAC : array[0..5] of Integer = (1, 2, 840, 113549, 3, 5); OID_DES_CBC : array[0..5] of Integer = (1, 2, 840, 113549, 3, 6); OID_DES_EDE3_CBC : array[0..5] of Integer = (1, 2, 840, 113549, 3, 7); OID_RC5_CBC : array[0..5] of Integer = (1, 2, 840, 113549, 3, 8); OID_RC5_CBCPad : array[0..5] of Integer = (1, 2, 840, 113549, 3, 9); // X9-57 OID_DSA : array[0..5] of Integer = (1, 2, 840, 10040, 4, 1); OID_DSA_SHA1 : array[0..5] of Integer = (1, 2, 840, 10040, 4, 3); // secsig OID_SHA1 : array[0..5] of Integer = (1, 3, 14, 3, 2, 26); { Encode } function ASN1EncodeLength(const Len: Integer): RawByteString; function ASN1EncodeObj(const TypeID: Byte; const Data: RawByteString): RawByteString; function ANS1EncodeEndOfContent: RawByteString; function ASN1EncodeNull: RawByteString; function ASN1EncodeBoolean(const A: Boolean): RawByteString; function ASN1EncodeDataInteger8(const A: ShortInt): RawByteString; function ASN1EncodeDataInteger16(const A: SmallInt): RawByteString; function ASN1EncodeDataInteger24(const A: Int32): RawByteString; function ASN1EncodeDataInteger32(const A: Int32): RawByteString; function ASN1EncodeDataInteger64(const A: Int64): RawByteString; function ASN1EncodeInteger8(const A: ShortInt): RawByteString; function ASN1EncodeInteger16(const A: SmallInt): RawByteString; function ASN1EncodeInteger24(const A: Int32): RawByteString; function ASN1EncodeInteger32(const A: Int32): RawByteString; function ASN1EncodeInteger64(const A: Int64): RawByteString; function ASN1EncodeIntegerBuf(const A; const Size: Integer): RawByteString; function ASN1EncodeIntegerBufStr(const A: RawByteString): RawByteString; function ASN1EncodeEnumerated(const A: Int64): RawByteString; function ASN1EncodeBitString(const A: RawByteString; const UnusedBits: Byte): RawByteString; function ASN1EncodeOctetString(const A: RawByteString): RawByteString; function ASN1EncodeInt32AsOctetString(const A: Int32): RawByteString; function ASN1EncodeUTF8String(const A: RawByteString): RawByteString; function ASN1EncodeIA5String(const A: RawByteString): RawByteString; function ASN1EncodeVisibleString(const A: RawByteString): RawByteString; function ASN1EncodeNumericString(const A: RawByteString): RawByteString; function ASN1EncodePrintableString(const A: RawByteString): RawByteString; function ASN1EncodeTeletexString(const A: RawByteString): RawByteString; function ASN1EncodeUniversalString(const A: UnicodeString): RawByteString; function ASN1EncodeBMPString(const A: UnicodeString): RawByteString; function ASN1EncodeUTCTime(const A: TDateTime): RawByteString; function ASN1EncodeGeneralizedTime(const A: TDateTime): RawByteString; function ASN1EncodeOID(const OID: array of Integer): RawByteString; function ASN1EncodeSequence(const A: RawByteString): RawByteString; function ASN1EncodeSet(const A: RawByteString): RawByteString; function ASN1EncodeContextSpecific(const I: Integer; const A: RawByteString): RawByteString; { Decode } function ASN1DecodeLength(const Buf; const Size: Integer; var Len: Integer): Integer; function ASN1DecodeObjHeader(const Buf; const Size: Integer; var TypeID: Byte; var Len: Integer; var Data: Pointer): Integer; function ASN1TypeIsConstructedType(const TypeID: Byte): Boolean; function ASN1TypeIsContextSpecific(const TypeID: Byte; var Idx: Integer): Boolean; function ASN1DecodeDataBoolean(const Buf; const Size: Integer; var A: Boolean): Integer; function ASN1DecodeDataInteger32(const Buf; const Size: Integer; var A: Int32): Integer; function ASN1DecodeDataInteger64(const Buf; const Size: Integer; var A: Int64): Integer; function ASN1DecodeDataIntegerBuf(const Buf; const Size: Integer; var A: RawByteString): Integer; function ASN1DecodeDataBitString(const Buf; const Size: Integer; var A: RawByteString; var UnusedBits: Byte): Integer; function ASN1DecodeDataRawByteString(const Buf; const Size: Integer; var A: RawByteString): Integer; function ASN1DecodeDataOctetString(const Buf; const Size: Integer; var A: RawByteString): Integer; function ASN1DecodeDataIA5String(const Buf; const Size: Integer; var A: RawByteString): Integer; function ASN1DecodeDataVisibleString(const Buf; const Size: Integer; var A: RawByteString): Integer; function ASN1DecodeDataNumericString(const Buf; const Size: Integer; var A: RawByteString): Integer; function ASN1DecodeDataPrintableString(const Buf; const Size: Integer; var A: RawByteString): Integer; function ASN1DecodeDataTeletexString(const Buf; const Size: Integer; var A: RawByteString): Integer; function ASN1DecodeDataUTF8String(const Buf; const Size: Integer; var A: RawByteString): Integer; function ASN1DecodeDataUniversalString(const Buf; const Size: Integer; var A: RawByteString): Integer; function ASN1DecodeDataBMPString(const Buf; const Size: Integer; var A: RawByteString): Integer; function ASN1DecodeDataOID(const Buf; const Size: Integer; var A: TASN1ObjectIdentifier): Integer; function ASN1DecodeDataUTCTime(const Buf; const Size: Integer; var A: TDateTime): Integer; function ASN1DecodeDataGeneralizedTime(const Buf; const Size: Integer; var A: TDateTime): Integer; function ASN1DecodeBoolean(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: Boolean): Integer; function ASN1DecodeInteger32(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: Int32): Integer; function ASN1DecodeInteger64(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: Int64): Integer; function ASN1DecodeIntegerBuf(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: RawByteString): Integer; function ASN1DecodeBitString(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: RawByteString; var UnusedBits: Byte): Integer; function ASN1DecodeString(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: RawByteString): Integer; function ASN1DecodeOID(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: TASN1ObjectIdentifier): Integer; function ASN1DecodeTime(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: TDateTime): Integer; type TASN1ParseProc = procedure (const TypeID: Byte; const DataBuf; const DataSize: Integer; const ObjectIdx: Integer; const CallerData: NativeInt); function ASN1Parse( const Buf; const Size: Integer; const ParseProc: TASN1ParseProc; const CallerData: NativeInt): Integer; { } { Test } { } {$IFDEF ASN1_TEST} procedure Test; {$ENDIF} implementation uses { Fundamentals } flcUtils, flcUTF, flcUnicodeCodecs; { Errors } const SErr_EncodeError = 'Encode error'; SErr_DecodeError = 'Decode error'; SErr_DecodeInvalidType = 'Decode error: Invalid type'; SErr_DecodeConversionError = 'Decode error: Invalid type conversion'; { Constants } const MinInt8 = -128; MaxInt8 = 127; MinInt16 = -32768; MaxInt16 = 32767; MinInt24 = -8388608; MaxInt24 = 8388607; MinInt32 = Low(Int32); // -2147483648; MaxInt32 = 2147483647; { Types } {$IFDEF DELPHI5} type PByte = ^Byte; {$ENDIF} { Utilities } {$IFDEF DELPHI5} function TryStrToInt(const S: AnsiString; var I: Integer): Boolean; var Error : Integer; begin Val(S, I, Error); Result := Error = 0; end; function TryStrToInt64(const S: AnsiString; var I: Int64): Boolean; var Error : Integer; begin Val(S, I, Error); Result := Error = 0; end; {$ENDIF} { Object identifiers } procedure ASN1OIDInit(var A: TASN1ObjectIdentifier; const B: array of Integer); var L, I : Integer; begin L := Length(B); SetLength(A, L); for I := 0 to L - 1 do A[I] := B[I]; end; function ASN1OIDToStrB(const A: TASN1ObjectIdentifier): RawByteString; var I : Integer; S : RawByteString; begin SetLength(S, 0); for I := 0 to Length(A) - 1 do begin if I > 0 then S := S + '.'; S := S + IntToStringB(A[I]); end; Result := S; end; function ASN1OIDToStr(const A: TASN1ObjectIdentifier): String; var I : Integer; S : String; begin SetLength(S, 0); for I := 0 to Length(A) - 1 do begin if I > 0 then S := S + '.'; S := S + IntToStr(A[I]); end; Result := S; end; function ASN1OIDEqual(const A: TASN1ObjectIdentifier; const B: array of Integer): Boolean; var L, M, I : Integer; begin L := Length(A); M := Length(B); Result := False; if L <> M then exit; for I := 0 to L - 1 do if A[I] <> B[I] then exit; Result := True; end; { Encode } function ASN1EncodeLength_Short(const Len: Integer): RawByteString; begin Assert(Len >= 0); Assert(Len <= 127); SetLength(Result, 1); Result := ByteChar(Len); end; function ASN1EncodeLength_Long(const Len: Integer): RawByteString; var N, I : Integer; B : array[0..3] of Byte; begin Assert(Len >= 0); // count bytes required to represent Len N := 0; I := Len; repeat B[N] := I mod $100; I := I div $100; Inc(N); until I = 0; // set result SetLength(Result, N + 1); Result[1] := AnsiChar(N or $80); for I := 0 to N - 1 do Result[2 + I] := AnsiChar(B[N - I - 1]); end; function ASN1EncodeLength(const Len: Integer): RawByteString; begin if Len < $80 then Result := ASN1EncodeLength_Short(Len) else Result := ASN1EncodeLength_Long(Len); end; function ASN1EncodeObj(const TypeID: Byte; const Data: RawByteString): RawByteString; begin Result := AnsiChar(TypeID) + ASN1EncodeLength(Length(Data)) + Data; end; function ANS1EncodeEndOfContent: RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_END_OF_CONTENT, ''); end; function ASN1EncodeNull: RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_NULL, ''); end; function ASN1EncodeBoolean(const A: Boolean): RawByteString; begin if A then Result := ASN1EncodeObj(ASN1_ID_BOOLEAN, #$FF) else Result := ASN1EncodeObj(ASN1_ID_BOOLEAN, #$00); end; function ASN1EncodeDataInteger8(const A: ShortInt): RawByteString; begin Result := AnsiChar(Byte(A)); end; function ASN1EncodeDataInteger16(const A: SmallInt): RawByteString; var D : RawByteString; begin if (A >= MinInt8) and (A <= MaxInt8) then Result := ASN1EncodeDataInteger8(A) else begin SetLength(D, 2); Move(A, D[1], 2); Result := D; end; end; function ASN1EncodeDataInteger24(const A: Int32): RawByteString; var D : RawByteString; begin if (A >= MinInt16) and (A <= MaxInt16) then Result := ASN1EncodeDataInteger16(A) else begin SetLength(D, 3); Move(A, D[1], 3); Result := D; end; end; function ASN1EncodeDataInteger32(const A: Int32): RawByteString; var D : RawByteString; begin if (A >= MinInt24) and (A <= MaxInt24) then Result := ASN1EncodeDataInteger24(A) else begin SetLength(D, 4); Move(A, D[1], 4); Result := D; end; end; function ASN1EncodeDataInteger64(const A: Int64): RawByteString; var D : RawByteString; F : Byte; B : array[0..7] of Byte; I, L : Integer; begin Move(A, B[0], 8); if B[7] and $80 <> 0 then F := $FF else F := $00; L := 0; for I := 7 downto 1 do if B[I] <> F then begin L := I; break; end; if ((F = $00) and (B[L] and $80 <> 0)) or ((F = $FF) and (B[L] and $80 = 0)) then Inc(L); Inc(L); SetLength(D, L); Move(B[0], D[1], L); Result := D; end; function ASN1EncodeInteger8(const A: ShortInt): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_INTEGER, ASN1EncodeDataInteger8(A)); end; function ASN1EncodeInteger16(const A: SmallInt): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_INTEGER, ASN1EncodeDataInteger16(A)); end; function ASN1EncodeInteger24(const A: Int32): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_INTEGER, ASN1EncodeDataInteger24(A)); end; function ASN1EncodeInteger32(const A: Int32): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_INTEGER, ASN1EncodeDataInteger32(A)); end; function ASN1EncodeInteger64(const A: Int64): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_INTEGER, ASN1EncodeDataInteger64(A)); end; function ASN1EncodeIntegerBuf(const A; const Size: Integer): RawByteString; var D : RawByteString; begin Assert(Size > 0); SetLength(D, Size); Move(A, D[1], Size); Result := ASN1EncodeObj(ASN1_ID_INTEGER, D); end; function ASN1EncodeIntegerBufStr(const A: RawByteString): RawByteString; begin Assert(A <> ''); Result := ASN1EncodeObj(ASN1_ID_INTEGER, A); end; function ASN1EncodeEnumerated(const A: Int64): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_ENUMERATED, ASN1EncodeDataInteger64(A)); end; function ASN1EncodeBitString(const A: RawByteString; const UnusedBits: Byte): RawByteString; begin if A = '' then Result := ASN1EncodeObj(ASN1_ID_BIT_STRING, AnsiChar(#0)) else Result := ASN1EncodeObj(ASN1_ID_BIT_STRING, AnsiChar(UnusedBits) + A); end; function ASN1EncodeOctetString(const A: RawByteString): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_OCTET_STRING, A); end; function ASN1EncodeInt32AsOctetString(const A: Int32): RawByteString; var S : RawByteString; I : Integer; F : Int32; begin SetLength(S, 4); F := A; for I := 0 to 3 do begin S[4 - I] := AnsiChar(F mod 256); F := F div 256; end; Result := ASN1EncodeOctetString(S); end; function ASN1EncodeUTF8String(const A: RawByteString): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_UTF8STRING, A); end; function ASN1EncodeIA5String(const A: RawByteString): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_IA5STRING, A); end; function ASN1EncodeVisibleString(const A: RawByteString): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_VISIBLESTRING, A); end; function ASN1EncodeNumericString(const A: RawByteString): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_NUMERICSTRING, A); end; function ASN1EncodePrintableString(const A: RawByteString): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_PRINTABLESTRING, A); end; function ASN1EncodeTeletexString(const A: RawByteString): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_T61STRING, A); end; function ASN1EncodeUniversalString(const A: UnicodeString): RawByteString; var S : RawByteString; begin S := UTF16ToEncodingU(TUCS4LECodec, A); Result := ASN1EncodeObj(ASN1_ID_UNIVERSALSTRING, S); end; function ASN1EncodeBMPString(const A: UnicodeString): RawByteString; var S : RawByteString; begin S := UTF16ToEncodingU(TUCS2Codec, A); Result := ASN1EncodeObj(ASN1_ID_UNIVERSALSTRING, S); end; function ASN1EncodeUTCTime(const A: TDateTime): RawByteString; begin {$IFDEF StringIsUnicode} Result := ASN1EncodeObj(ASN1_ID_UTCTIME, RawByteString(FormatDateTime('YYMMDDHHNNSS', A)) + 'Z'); {$ELSE} Result := ASN1EncodeObj(ASN1_ID_UTCTIME, FormatDateTime('YYMMDDHHNNSS', A) + 'Z'); {$ENDIF} end; function ASN1EncodeGeneralizedTime(const A: TDateTime): RawByteString; begin {$IFDEF StringIsUnicode} Result := ASN1EncodeObj(ASN1_ID_GENERALIZEDTIME, RawByteString(FormatDateTime('YYYYMMDDHHNNSS', A)) + 'Z'); {$ELSE} Result := ASN1EncodeObj(ASN1_ID_GENERALIZEDTIME, FormatDateTime('YYYYMMDDHHNNSS', A) + 'Z'); {$ENDIF} end; // OID implementation limits const MAX_BER_OIDPart_Len = 6; // 6 = max 42-bit part, 10 = max 70-bit part MAX_BER_OID_Length = 512; MAX_OID_Parts = 64; type TOIDPartEnc = record Len : Byte; Enc : array[0..MAX_BER_OIDPart_Len - 1] of Byte; end; procedure ASN1BEREncodeOIDPart(const OIDPart: Integer; var Enc: TOIDPartEnc); var N, A, B, C : Integer; begin N := 0; A := OIDPart; repeat B := A div 128; C := A mod 128; if N > 0 then C := C or 128; if N >= MAX_BER_OIDPart_Len then raise EASN1.Create(SErr_EncodeError); Enc.Enc[N] := Byte(C); Inc(N); A := B; until A = 0; Enc.Len := N; end; // BER Encode OID function ASN1EncodeOID(const OID: array of Integer): RawByteString; var L, I, N, J, K : Integer; A, B, C : Integer; E : TOIDPartEnc; Buf : array[0..MAX_BER_OID_Length - 1] of Byte; S : RawByteString; begin Result := ''; L := Length(OID); if L < 2 then raise EASN1.Create(SErr_EncodeError); A := OID[0]; B := OID[1]; C := A * 40 + B; if C > $FF then raise EASN1.Create(SErr_EncodeError); N := 1; Buf[0] := C; for I := 2 to L - 1 do begin A := OID[I]; ASN1BEREncodeOIDPart(A, E); K := E.Len; if N + K - 1 >= MAX_BER_OID_Length then raise EASN1.Create(SErr_EncodeError); for J := 0 to K - 1 do Buf[N + J] := E.Enc[K - J - 1]; Inc(N, K); end; SetLength(S, N); Move(Buf[0], S[1], N); Result := ASN1EncodeObj(ASN1_ID_OBJECT_IDENTIFIER, S); end; function ASN1EncodeSequence(const A: RawByteString): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_SEQUENCE, A); end; function ASN1EncodeSet(const A: RawByteString): RawByteString; begin Result := ASN1EncodeObj(ASN1_ID_SET, A); end; function ASN1EncodeContextSpecific(const I: Integer; const A: RawByteString): RawByteString; begin Assert(I >= 0); Assert(I <= 31); Result := ASN1EncodeObj(ASN1_ID_CONSTR_CONTEXT_SPECIFIC + I, A); end; { Decode } function ASN1DecodeLength(const Buf; const Size: Integer; var Len: Integer): Integer; var P : PByte; L, I : Byte; begin if Size <= 0 then raise EASN1.Create(SErr_DecodeError); // buffer too small P := @Buf; L := P^; if L < $80 then begin Len := L; Result := 1; exit; end; L := L and $7F; if Size < L + 1 then raise EASN1.Create(SErr_DecodeError); // buffer too small if L > 3 then raise EASN1.Create(SErr_DecodeError); // size too big Len := 0; for I := 0 to L - 1 do begin Inc(P); Len := (Len shl 8) or P^; end; Result := L + 1; end; function ASN1DecodeObjHeader(const Buf; const Size: Integer; var TypeID: Byte; var Len: Integer; var Data: Pointer): Integer; var P : PByte; L : Integer; begin Assert(Assigned(@Buf)); if Size < 2 then raise EASN1.Create(SErr_DecodeError); // buffer too small P := @Buf; TypeID := P^; Inc(P); L := ASN1DecodeLength(P^, Size - 1, Len); Inc(P, L); Data := P; Result := L + 1; end; function ASN1TypeIsConstructedType(const TypeID: Byte): Boolean; begin Result := TypeID and ASN1_ID_CONSTRUCTED <> 0; end; function ASN1TypeIsContextSpecific(const TypeID: Byte; var Idx: Integer): Boolean; begin if TypeID and ASN1_ID_CONTEXT_SPECIFIC <> 0 then begin Idx := TypeID and $1F; Result := True; end else begin Idx := -1; Result := False; end; end; function ASN1DecodeDataBoolean(const Buf; const Size: Integer; var A: Boolean): Integer; begin if Size <> 1 then raise EASN1.Create(SErr_DecodeError); A := PByte(@Buf)^ <> 0; Result := 1; end; function ASN1DecodeDataInteger32(const Buf; const Size: Integer; var A: Int32): Integer; var P : PByte; L, I : Integer; B : array[0..3] of Byte; E : Byte; begin P := @Buf; L := Size; if (L <= 0) or (L > 4) then raise EASN1.Create(SErr_DecodeError); // integer too big for Int32 Move(P^, B[0], L); if L < 4 then begin // extend sign if B[L - 1] >= $80 then E := $FF else E := $00; for I := L to 3 do B[I] := E; end; Move(B[0], A, 4); Result := L; end; function ASN1DecodeDataInteger64(const Buf; const Size: Integer; var A: Int64): Integer; var P : PByte; L, I : Integer; B : array[0..7] of Byte; E : Byte; begin P := @Buf; L := Size; if (L <= 0) or (L > 8) then raise EASN1.Create(SErr_DecodeError); // integer too big for Int64 Move(P^, B[0], L); if L < 8 then begin // extend sign if B[L - 1] >= $80 then E := $FF else E := $00; for I := L to 7 do B[I] := E; end; Move(B[0], A, 8); Result := L; end; function ASN1DecodeDataIntegerBuf(const Buf; const Size: Integer; var A: RawByteString): Integer; var L : Integer; begin L := Size; SetLength(A, L); if L > 0 then Move(Buf, A[1], L); Result := L; end; function ASN1DecodeDataBitString(const Buf; const Size: Integer; var A: RawByteString; var UnusedBits: Byte): Integer; var P : PByte; L : Integer; F : Byte; begin P := @Buf; L := Size; if L <= 0 then raise EASN1.Create(SErr_DecodeError); // invalid size F := P^; if F > 7 then raise EASN1.Create(SErr_DecodeError); // invalid UnusedBits if L = 1 then begin if F <> 0 then raise EASN1.Create(SErr_DecodeError); // invalid size A := ''; UnusedBits := 0; Result := 1; exit; end; Inc(P); Dec(L); SetLength(A, L); if L > 0 then Move(P^, A[1], L); UnusedBits := F; Result := L + 1; end; function ASN1DecodeDataRawByteString(const Buf; const Size: Integer; var A: RawByteString): Integer; var L : Integer; begin L := Size; SetLength(A, L); if L > 0 then Move(Buf, A[1], L); Result := L; end; function ASN1DecodeDataOctetString(const Buf; const Size: Integer; var A: RawByteString): Integer; begin Result := ASN1DecodeDataRawByteString(Buf, Size, A); end; function ASN1DecodeDataIA5String(const Buf; const Size: Integer; var A: RawByteString): Integer; begin Result := ASN1DecodeDataRawByteString(Buf, Size, A); end; function ASN1DecodeDataVisibleString(const Buf; const Size: Integer; var A: RawByteString): Integer; begin Result := ASN1DecodeDataRawByteString(Buf, Size, A); end; function ASN1DecodeDataNumericString(const Buf; const Size: Integer; var A: RawByteString): Integer; begin Result := ASN1DecodeDataRawByteString(Buf, Size, A); end; function ASN1DecodeDataPrintableString(const Buf; const Size: Integer; var A: RawByteString): Integer; begin Result := ASN1DecodeDataRawByteString(Buf, Size, A); end; function ASN1DecodeDataTeletexString(const Buf; const Size: Integer; var A: RawByteString): Integer; begin Result := ASN1DecodeDataRawByteString(Buf, Size, A); end; function ASN1DecodeDataUTF8String(const Buf; const Size: Integer; var A: RawByteString): Integer; begin Result := ASN1DecodeDataRawByteString(Buf, Size, A); end; function ASN1DecodeDataUniversalString(const Buf; const Size: Integer; var A: RawByteString): Integer; begin A := UnicodeStringToUTF8String(EncodingToUTF16U(TUCS4LECodec, @Buf, Size)); Result := Size; end; function ASN1DecodeDataBMPString(const Buf; const Size: Integer; var A: RawByteString): Integer; begin A := UnicodeStringToUTF8String(EncodingToUTF16U(TUCS2Codec, @Buf, Size)); Result := Size; end; function ASN1DecodeOIDPart(const Buf; const Size: Integer; var Part: Integer): Integer; var P : PByte; L, C : Integer; A : Byte; R : Boolean; V : Int64; begin P := @Buf; L := Size; C := 1; V := 0; repeat if L < 0 then raise EASN1.Create(SErr_DecodeError); if C > MAX_BER_OIDPart_Len then raise EASN1.Create(SErr_DecodeError); // too many bytes in this OID part A := P^; R := A and 128 = 0; V := (V shl 7) or (A and 127); Inc(P); Dec(L); Inc(C); until R; if V > MaxInt32 then raise EASN1.Create(SErr_DecodeError); // value too large Part := V; Result := Size - L; end; function ASN1DecodeDataOID(const Buf; const Size: Integer; var A: TASN1ObjectIdentifier): Integer; var P : PByte; L, N, C, I : Integer; F : Byte; Parts : array[0..MAX_OID_Parts - 1] of Integer; begin P := @Buf; L := Size; if L < 1 then raise EASN1.Create(SErr_DecodeError); F := P^; Parts[0] := F div 40; Parts[1] := F mod 40; Inc(P); Dec(L); N := 2; while L > 0 do begin if N >= MAX_OID_Parts then raise EASN1.Create(SErr_DecodeError); C := ASN1DecodeOIDPart(P^, L, Parts[N]); Inc(P, C); Dec(L, C); Inc(N); end; SetLength(A, N); for I := 0 to N - 1 do A[I] := Parts[I]; Result := Size - L; end; function ASN1DecodeDataUTCTime(const Buf; const Size: Integer; var A: TDateTime): Integer; var D : RawByteString; YYYY, YY, MM, DD, HH, NN, SS : Integer; begin if Size < 6 then raise EASN1.Create(SErr_DecodeError); SetLength(D, Size); Move(Buf, D[1], Size); if not TryStringToIntB(Copy(D, 1, 2), YY) then raise EASN1.Create(SErr_DecodeError); if not TryStringToIntB(Copy(D, 3, 2), MM) then raise EASN1.Create(SErr_DecodeError); if not TryStringToIntB(Copy(D, 5, 2), DD) then raise EASN1.Create(SErr_DecodeError); HH := 0; NN := 0; SS := 0; if Size >= 10 then begin if not TryStringToIntB(Copy(D, 7, 2), HH) then raise EASN1.Create(SErr_DecodeError); if not TryStringToIntB(Copy(D, 9, 2), NN) then raise EASN1.Create(SErr_DecodeError); if Size >= 12 then if not TryStringToIntB(Copy(D, 11, 2), SS) then raise EASN1.Create(SErr_DecodeError); end; if YY <= 49 then YYYY := 2000 + YY else YYYY := 1900 + YY; A := EncodeDate(YYYY, MM, DD) + EncodeTime(HH, NN, SS, 0); Result := Size; end; function ASN1DecodeDataGeneralizedTime(const Buf; const Size: Integer; var A: TDateTime): Integer; var D : RawByteString; YYYY, MM, DD, HH, NN, SS : Integer; begin if Size < 8 then raise EASN1.Create(SErr_DecodeError); SetLength(D, Size); Move(Buf, D[1], Size); if not TryStringToIntB(Copy(D, 1, 4), YYYY) then raise EASN1.Create(SErr_DecodeError); if not TryStringToIntB(Copy(D, 5, 2), MM) then raise EASN1.Create(SErr_DecodeError); if not TryStringToIntB(Copy(D, 7, 2), DD) then raise EASN1.Create(SErr_DecodeError); HH := 0; NN := 0; SS := 0; if Size >= 12 then begin if not TryStringToIntB(Copy(D, 9, 2), HH) then raise EASN1.Create(SErr_DecodeError); if not TryStringToIntB(Copy(D, 11, 2), NN) then raise EASN1.Create(SErr_DecodeError); if Size >= 14 then if not TryStringToIntB(Copy(D, 13, 2), SS) then raise EASN1.Create(SErr_DecodeError); end; A := EncodeDate(YYYY, MM, DD) + EncodeTime(HH, NN, SS, 0); Result := Size; end; function ASN1DecodeBoolean(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: Boolean): Integer; var I : Int64; begin case TypeID of ASN1_ID_BOOLEAN : Result := ASN1DecodeDataBoolean(DataBuf, DataSize, A); ASN1_ID_INTEGER : begin Result := ASN1DecodeDataInteger64(DataBuf, DataSize, I); A := I <> 0; end; else raise EASN1.Create(SErr_DecodeInvalidType); end; end; function ASN1DecodeInteger32(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: Int32): Integer; var I : Int64; begin Result := ASN1DecodeInteger64(TypeID, DataBuf, DataSize, I); if (I > High(Int32)) or (I < Low(Int32)) then raise EASN1.Create(SErr_DecodeError); A := I; end; function ASN1DecodeInteger64(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: Int64): Integer; var S : RawByteString; begin case TypeID of ASN1_ID_INTEGER : Result := ASN1DecodeDataInteger64(DataBuf, DataSize, A); ASN1_ID_OCTET_STRING, ASN1_ID_UTF8STRING, ASN1_ID_NUMERICSTRING, ASN1_ID_PRINTABLESTRING, ASN1_ID_VISIBLESTRING, ASN1_ID_T61STRING, ASN1_ID_UNIVERSALSTRING, ASN1_ID_BMPSTRING : begin Result := ASN1DecodeString(TypeID, DataBuf, DataSize, S); if not TryStringToInt64B(S, A) then raise EASN1.Create(SErr_DecodeConversionError); end; else raise EASN1.Create(SErr_DecodeInvalidType); end; end; function ASN1DecodeIntegerBuf(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: RawByteString): Integer; begin case TypeID of ASN1_ID_INTEGER : Result := ASN1DecodeDataIntegerBuf(DataBuf, DataSize, A); else raise EASN1.Create(SErr_DecodeInvalidType); end; end; function ASN1DecodeBitString(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: RawByteString; var UnusedBits: Byte): Integer; begin case TypeID of ASN1_ID_BIT_STRING : Result := ASN1DecodeDataBitString(DataBuf, DataSize, A, UnusedBits); else raise EASN1.Create(SErr_DecodeInvalidType); end; end; type TStringParseData = record Delim : RawByteString; Str : RawByteString; end; PStringParseData = ^TStringParseData; procedure StringParseProc(const TypeID: Byte; const DataBuf; const DataSize: Integer; const ObjectIdx: Integer; const CallerData: NativeInt); var S : RawByteString; D : PStringParseData; begin ASN1DecodeString(TypeID, DataBuf, DataSize, S); D := Pointer(CallerData); if (ObjectIdx > 0) and (D^.Delim <> '') then D^.Str := D^.Str + D^.Delim; D^.Str := D^.Str + S; end; function ASN1DecodeString(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: RawByteString): Integer; var I : Int64; B : TASN1ObjectIdentifier; D : TStringParseData; K : Byte; begin if ASN1TypeIsConstructedType(TypeID) then begin D.Delim := ' '; Result := ASN1Parse(DataBuf, DataSize, StringParseProc, NativeInt(@D)); A := D.Str; end else case TypeID of ASN1_ID_OCTET_STRING : Result := ASN1DecodeDataOctetString(DataBuf, DataSize, A); ASN1_ID_UTF8STRING : Result := ASN1DecodeDataUTF8String(DataBuf, DataSize, A); ASN1_ID_NUMERICSTRING : Result := ASN1DecodeDataNumericString(DataBuf, DataSize, A); ASN1_ID_PRINTABLESTRING : Result := ASN1DecodeDataPrintableString(DataBuf, DataSize, A); ASN1_ID_VISIBLESTRING : Result := ASN1DecodeDataVisibleString(DataBuf, DataSize, A); ASN1_ID_T61STRING : Result := ASN1DecodeDataTeletexString(DataBuf, DataSize, A); ASN1_ID_UNIVERSALSTRING : Result := ASN1DecodeDataUniversalString(DataBuf, DataSize, A); ASN1_ID_BMPSTRING : Result := ASN1DecodeDataBMPString(DataBuf, DataSize, A); ASN1_ID_BIT_STRING : Result := ASN1DecodeDataBitString(DataBuf, DataSize, A, K); ASN1_ID_IA5STRING : Result := ASN1DecodeDataIA5String(DataBuf, DataSize, A); ASN1_ID_INTEGER : begin Result := ASN1DecodeDataInteger64(DataBuf, DataSize, I); A := IntToStringB(I); end; ASN1_ID_OBJECT_IDENTIFIER : begin Result := ASN1DecodeDataOID(DataBuf, DataSize, B); A := ASN1OIDToStrB(B); end; else raise EASN1.Create(SErr_DecodeInvalidType); end; end; function ASN1DecodeOID(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: TASN1ObjectIdentifier): Integer; begin case TypeID of ASN1_ID_OBJECT_IDENTIFIER : Result := ASN1DecodeDataOID(DataBuf, DataSize, A); else raise EASN1.Create(SErr_DecodeInvalidType); end; end; function ASN1DecodeTime(const TypeID: Byte; const DataBuf; const DataSize: Integer; var A: TDateTime): Integer; begin case TypeID of ASN1_ID_UTCTIME : Result := ASN1DecodeDataUTCTime(DataBuf, DataSize, A); ASN1_ID_GENERALIZEDTIME : Result := ASN1DecodeDataGeneralizedTime(DataBuf, DataSize, A); else raise EASN1.Create(SErr_DecodeInvalidType); end; end; function ASN1Parse(const Buf; const Size: Integer; const ParseProc: TASN1ParseProc; const CallerData: NativeInt): Integer; var P : PByte; L, N, T : Integer; ObjIdx : Integer; TypeID : Byte; Len : Integer; Data : Pointer; begin P := @Buf; L := Size; if (L < 0) or not Assigned(P) then raise EASN1.Create(SErr_DecodeError); // invalid buffer if L = 0 then begin // nothing to parse Result := 0; exit; end; // iterate top level ASN.1 objects in buffer ObjIdx := 0; while L > 0 do begin N := ASN1DecodeObjHeader(P^, L, TypeID, Len, Data); T := N + Len; if T > L then raise EASN1.Create(SErr_DecodeError); // invalid length encoded in header ParseProc(TypeID, Data^, Len, ObjIdx, CallerData); Inc(P, T); Dec(L, T); Inc(ObjIdx); end; Result := Size - L; end; { } { Test } { } {$IFDEF ASN1_TEST} {$ASSERTIONS ON} procedure TestParseProc( const TypeID: Byte; const DataBuf; const DataSize: Integer; const ObjectIdx: Integer; const CallerData: NativeInt); var I : Int64; S : RawByteString; begin case CallerData of 0 : case ObjectIdx of 0 : begin Assert(TypeID = ASN1_ID_SEQUENCE); Assert(ASN1Parse(DataBuf, DataSize, TestParseProc, 1) = DataSize); end; else Assert(False); end; 1 : case ObjectIdx of 0 : begin Assert(TypeID = ASN1_ID_INTEGER); ASN1DecodeInteger64(TypeID, DataBuf, DataSize, I); Assert(I = 123); end; 1 : begin Assert(TypeID = ASN1_ID_PRINTABLESTRING); ASN1DecodeString(TypeID, DataBuf, DataSize, S); Assert(S = 'ABC'); end; else Assert(False); end; else Assert(False); end; end; procedure Test; var S : RawByteString; L, I, J : Integer; D : TASN1ObjectIdentifier; begin Assert(ASN1EncodeLength(0) = #$00); Assert(ASN1EncodeLength(1) = #$01); Assert(ASN1EncodeLength($7F) = #$7F); Assert(ASN1EncodeLength($80) = #$81#$80); Assert(ASN1EncodeLength($FF) = #$81#$FF); Assert(ASN1EncodeLength($100) = #$82#$01#$00); Assert(ASN1EncodeOID(OID_3DES_wrap) = #$06#$0b#$2a#$86#$48#$86#$f7#$0d#$01#$09#$10#$03#$06); Assert(ASN1EncodeOID(OID_RC2_wrap) = #$06#$0b#$2a#$86#$48#$86#$f7#$0d#$01#$09#$10#$03#$07); S := RawByteString(#$2a#$86#$48#$86#$f7#$0d#$01#$09#$10#$03#$06); L := Length(S); Assert(ASN1DecodeDataOID(S[1], L, D) = L); Assert(Length(D) = 9); Assert((D[0] = 1) and (D[1] = 2) and (D[2] = 840) and (D[3] = 113549) and (D[4] = 1) and (D[5] = 9) and (D[6] = 16) and (D[7] = 3) and (D[8] = 6)); Assert(ASN1OIDToStrB(D) = '1.2.840.113549.1.9.16.3.6'); S := RawByteString(#$2a#$86#$48#$86#$f7#$0d#$03#$06); L := Length(S); Assert(ASN1DecodeDataOID(S[1], L, D) = L); Assert(Length(D) = 6); Assert((D[0] = 1) and (D[1] = 2) and (D[2] = 840) and (D[3] = 113549) and (D[4] = 3) and (D[5] = 6)); Assert(ASN1EncodeInteger32(0) = #$02#$01#$00); Assert(ASN1EncodeInteger32(1) = #$02#$01#$01); Assert(ASN1EncodeInteger32(-1) = #$02#$01#$FF); Assert(ASN1EncodeInteger32(-$80) = #$02#$01#$80); Assert(ASN1EncodeInteger32(-$81) = #$02#$02#$7F#$FF); Assert(ASN1EncodeInteger32(-$FF) = #$02#$02#$01#$FF); Assert(ASN1EncodeInteger32($7F) = #$02#$01#$7F); Assert(ASN1EncodeInteger32($80) = #$02#$02#$80#$00); Assert(ASN1EncodeInteger32($FF) = #$02#$02#$FF#$00); for I := -512 to 512 do begin S := ASN1EncodeInteger32(I); Assert(S = ASN1EncodeInteger64(I)); L := Length(S); Assert(ASN1DecodeDataInteger32(S[3], L - 2, J) = L - 2); Assert(J = I); end; S := ASN1EncodeSequence( ASN1EncodeInteger32(123) + ASN1EncodePrintableString('ABC') ); L := Length(S); Assert(L > 0); Assert(ASN1Parse(S[1], L, @TestParseProc, 0) = L); end; {$ENDIF} end.