{ $Project$ $Workfile$ $Revision$ $DateUTC$ $Id$ This file is part of the Indy (Internet Direct) project, and is offered under the dual-licensing agreement described on the Indy website. (http://www.indyproject.org/) Copyright: (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved. } { $Log$ } { Rev 1.0 15/04/2005 7:25:02 AM GGrieve first ported to INdy } unit IdASN1Coder; interface {$i IdCompilerDefines.inc} uses Classes, Contnrs; type TIdASN1IdentifierType = (aitUnknown, aitSequence, aitBoolean, aitInteger, aitEnum, aitString, aitOID, aitReal); TIdASN1IdentifierClass = (aicUniversal, aicApplication, aicContextSpecific, aicPrivate); TIdASN1Identifier = record Position : Integer; IdClass : TIdASN1IdentifierClass; Constructed : Boolean; TagValue : Integer; TagType : TIdASN1IdentifierType; ContentLength : integer; end; TIdASN1Sequence = Class Private FIdClass : TIdASN1IdentifierClass; FTag : Integer; FContents : String; Public Property IdClass : TIdASN1IdentifierClass Read FIdClass Write FIdClass; Property Tag : integer Read FTag Write FTag; Property Contents : String Read FContents Write FContents; End; TIdASN1Sequences = Class(TObjectList) Private Function GetElement(Const iIndex : Integer) : TIdASN1Sequence; function GetLast: TIdASN1Sequence; Public Property LastElement : TIdASN1Sequence read GetLast; procedure Pop; Property Elements[Const iIndex : Integer] : TIdASN1Sequence Read GetElement; Default; End; TIdASN1Encoder = class private FSequences : TIdASN1Sequences; FReadyToWrite : Boolean; function FormatEncoding(aClass : TIdASN1IdentifierClass; bConstructed : Boolean; iTag : integer; const sContent : String) : String; procedure AddEncoding(const sContent : String); procedure WriteInt(iTag : integer; iValue : integer); function EncodeLength(iLen : Integer):String; protected // must call this as an outer wrapper Procedure StartWriting; Procedure StopWriting; // sequences procedure StartSequence; overload; procedure StartSequence(iTag : Integer); overload; procedure StartSequence(aClass : TIdASN1IdentifierClass; iTag : Integer); overload; procedure StopSequence; // primitives procedure WriteBoolean(bValue : Boolean); procedure WriteInteger(iValue : Integer); procedure WriteEnum(iValue : Integer); procedure WriteString(sValue : String); overload; procedure WriteString(iTag : integer; sValue : String); overload; public Constructor Create; destructor Destroy; override; procedure WriteToStream(Stream : TStream); end; TIntegerList = class (TList) private function GetValue(iIndex: integer): Integer; procedure SetValue(Index: integer; const Value: Integer); public procedure AddInt(value : integer); procedure InsertInt(Index, Value : integer); property Value[iIndex : integer]:Integer read GetValue write SetValue; default; end; TIdASN1Decoder = class private FLengths : TIntegerList; FPosition : Integer; FNextHeader : TIdASN1Identifier; FNextHeaderUsed : Boolean; FStream: TStream; function ReadHeader : TIdASN1Identifier; // -1 in length means that no definite length was specified function DescribeIdentifier(const aId : TIdASN1Identifier) : String; Function ReadByte : Byte; function ReadChar : Char; function ReadContentLength : Integer; protected procedure Check(bCondition : Boolean; const sMethod, sMessage : String); overload; virtual; // must call this as an outer wrapper Procedure StartReading; Procedure StopReading; // sequences and choices procedure ReadSequenceBegin; function SequenceEnded : Boolean; procedure ReadSequenceEnd; function NextTag : integer; function NextTagType : TIdASN1IdentifierType; // primitives function ReadBoolean : Boolean; Function ReadInteger : Integer; function ReadEnum : Integer; Function ReadString : String; public Constructor Create; destructor Destroy; override; property Stream : TStream read FStream write FStream; end; const NAMES_ASN1IDENTIFIERTYPE : array [TIdASN1IdentifierType] of String = ('Unknown', 'Sequence', 'Boolean', 'Integer', 'Enum', 'String', 'OID', 'Real'); TAGS_ASN1IDENTIFIERTYPE : array [TIdASN1IdentifierType] of Integer = (0, $10, $01, $02, $0A, $04, $06, 0 {?}); NAMES_ASN1IDENTIFIERCLASS : array [TIdASN1IdentifierClass] of String = ('Universal', 'Application', 'ContextSpecific', 'Private'); function ToIdentifierType(iTag : integer) : TIdASN1IdentifierType; implementation uses IdGlobal, IdException, SysUtils; function ToIdentifierType(iTag : integer) : TIdASN1IdentifierType; begin case iTag of $10 : result := aitSequence; $01 : result := aitBoolean; $02 : result := aitInteger; $04 : result := aitString; $06 : result := aitOID; $0A : result := aitEnum; else result := aitUnknown; end; end; { TIdASN1Encoder } constructor TIdASN1Encoder.Create; begin inherited Create; FSequences := TIdASN1Sequences.create; end; destructor TIdASN1Encoder.Destroy; begin FSequences.Free; inherited Destroy; end; procedure TIdASN1Encoder.WriteToStream(Stream : TStream); begin Assert(FReadyToWrite, 'not ready to write'); if Length(FSequences[0].Contents) <> 0 then WriteStringToStream(Stream, FSequences[0].Contents, IndyTextEncoding_8Bit); end; procedure TIdASN1Encoder.StartWriting; begin FSequences.Clear; StartSequence(aicUniversal, 0); end; procedure TIdASN1Encoder.StopWriting; begin assert(FSequences.Count = 1, 'Writing left an open Sequence'); FReadyToWrite := true; // todo - actually commit to stream Produce(Fsequences[0].Contents); end; procedure TIdASN1Encoder.StartSequence(aClass: TIdASN1IdentifierClass; iTag: Integer); var oSequence : TIdASN1Sequence; begin oSequence := TIdASN1Sequence.create; try oSequence.IdClass := aClass; oSequence.Tag := iTag; oSequence.Contents := ''; FSequences.add(oSequence); finally oSequence.Free; end; end; procedure TIdASN1Encoder.StartSequence(iTag: Integer); begin if iTag = -1 then StartSequence(aicUniversal, TAGS_ASN1IDENTIFIERTYPE[aitSequence]) else StartSequence(aicApplication, iTag); end; procedure TIdASN1Encoder.StartSequence; begin StartSequence(aicUniversal, TAGS_ASN1IDENTIFIERTYPE[aitSequence]); end; procedure TIdASN1Encoder.StopSequence; var sSequence : String; begin sSequence := FormatEncoding(FSequences.LastElement.IdClass, true, FSequences.LastElement.Tag, FSequences.LastElement.Contents); FSequences.Pop; AddEncoding(sSequence); end; procedure TIdASN1Encoder.WriteBoolean(bValue: Boolean); begin // RLebeau 1/7/09: using Char() for #128-#255 because in D2009, the compiler // may change characters >= #128 from their Ansi codepage value to their true // Unicode codepoint value, depending on the codepage used for the source code. // For instance, #128 may become #$20AC... if bValue then AddEncoding(FormatEncoding(aicUniversal, False, TAGS_ASN1IDENTIFIERTYPE[aitBoolean], Char($FF))) else AddEncoding(FormatEncoding(aicUniversal, False, TAGS_ASN1IDENTIFIERTYPE[aitBoolean], #$00)); end; procedure TIdASN1Encoder.WriteEnum(iValue: Integer); begin WriteInt(TAGS_ASN1IDENTIFIERTYPE[aitEnum], iValue); end; procedure TIdASN1Encoder.WriteInteger(iValue: Integer); begin WriteInt(TAGS_ASN1IDENTIFIERTYPE[aitInteger], iValue); end; procedure TIdASN1Encoder.WriteInt(iTag, iValue: integer); var sValue : String; x, y: Cardinal; bNeg: Boolean; begin bNeg := iValue < 0; x := Abs(iValue); if bNeg then x := not (x - 1); sValue := ''; {Do not Localize} repeat y := x mod 256; x := x div 256; sValue := Char(y) + sValue; until x = 0; if (not bNeg) and (sValue[1] > #$7F) then sValue := #0 + sValue; AddEncoding(FormatEncoding(aicUniversal, False, iTag, sValue)) end; procedure TIdASN1Encoder.WriteString(sValue: String); begin AddEncoding(FormatEncoding(aicUniversal, False, TAGS_ASN1IDENTIFIERTYPE[aitString], sValue)) end; procedure TIdASN1Encoder.WriteString(iTag : integer; sValue: String); begin AddEncoding(FormatEncoding(aicContextSpecific, False, iTag, sValue)) end; procedure TIdASN1Encoder.AddEncoding(const sContent: String); begin FSequences.LastElement.Contents := FSequences.LastElement.Contents + sContent; end; function TIdASN1Encoder.FormatEncoding(aClass: TIdASN1IdentifierClass; bConstructed : Boolean; iTag: integer; const sContent: String): String; begin if bConstructed then result := chr((ord(aClass) shl 6) or $20 or iTag) + EncodeLength(length(sContent)) + sContent else result := chr((ord(aClass) shl 6) or iTag) + EncodeLength(length(sContent)) + sContent; end; function TIdASN1Encoder.EncodeLength(iLen: Integer): String; var x, y: Integer; begin if iLen < $80 then Result := Char(iLen) else begin x := iLen; Result := ''; repeat y := x mod 256; x := x div 256; Result := Char(y) + Result; until x = 0; y := Length(Result); y := y or $80; Result := Char(y) + Result; end; end; { TIdASN1Sequences } function TIdASN1Sequences.GetElement(const iIndex: Integer): TIdASN1Sequence; begin result := TIdASN1Sequence(items[iIndex]); end; function TIdASN1Sequences.GetLast: TIdASN1Sequence; begin if Count = 0 then result := nil else result := GetElement(Count - 1); end; procedure TIdASN1Sequences.Pop; begin if Count > 0 then Delete(Count-1); end; { TIdASN1Decoder } Constructor TIdASN1Decoder.Create; begin inherited Create; FLengths := TIntegerList.create; end; destructor TIdASN1Decoder.Destroy; begin FLengths.Free; Inherited Destroy; end; procedure TIdASN1Decoder.Check(bCondition: Boolean; const sMethod, sMessage: String); begin if not bCondition then raise EIdException.create(sMessage); end; Procedure TIdASN1Decoder.StartReading; begin FLengths.Clear; FLengths.AddInt(-1); FNextHeaderUsed := False; FPosition := 0; end; Procedure TIdASN1Decoder.StopReading; begin Check(FLengths.Count = 1, 'StopReading', 'Reading was incomplete'); FLengths.Clear; end; function TIdASN1Decoder.DescribeIdentifier(const aId : TIdASN1Identifier) : String; begin result := '[Pos '+IntToStr(aId.Position)+', Type '+NAMES_ASN1IDENTIFIERTYPE[aId.TagType]+', '+ 'Tag '+IntToStr(aId.TagValue)+', Class '+NAMES_ASN1IDENTIFIERCLASS[aId.IdClass]+']'; end; Function TIdASN1Decoder.ReadByte : Byte; begin Check(FLengths[0] <> 0, 'ReadByte', 'Attempt to read past end of Sequence'); Stream.Read(result, 1); inc(FPosition); FLengths[0] := FLengths[0] - 1; end; function TIdASN1Decoder.ReadChar : Char; begin result := Chr(readByte); end; function TIdASN1Decoder.ReadContentLength: Integer; var iNext : Byte; iLoop: Integer; begin iNext := ReadByte; if iNext < $80 then Result := iNext else begin Result := 0; iNext := iNext and $7F; if iNext = 0 then raise EIdException.create('Indefinite lengths are not yet handled'); for iLoop := 1 to iNext do begin Result := Result * 256; iNext := ReadByte; Result := Result + iNext; end; end; end; function TIdASN1Decoder.ReadHeader : TIdASN1Identifier; var iNext : Byte; begin if FNextHeaderUsed then begin result := FNextHeader; FNextHeaderUsed := False; end else begin FillChar(result, sizeof(TIdASN1Identifier), #0); result.Position := FPosition; iNext := ReadByte; result.Constructed := iNext and $20 > 0; result.IdClass := TIdASN1IdentifierClass(iNext shr 6); if iNext and $1F = $1F then begin raise EIdException.create('Todo'); end else result.TagValue := iNext and $1F; result.TagType := ToIdentifierType(result.TagValue); result.ContentLength := ReadContentLength; end; end; function TIdASN1Decoder.NextTag: integer; begin if not FNextHeaderUsed then begin FNextHeader := ReadHeader; FNextHeaderUsed := true; end; result := FNextHeader.TagValue; end; function TIdASN1Decoder.NextTagType: TIdASN1IdentifierType; begin if not FNextHeaderUsed then begin FNextHeader := ReadHeader; FNextHeaderUsed := true; end; result := FNextHeader.TagType; end; function TIdASN1Decoder.ReadBoolean : Boolean; var aId : TIdASN1Identifier; begin aId := ReadHeader; Check((aId.IdClass = aicApplication) or (aId.TagType = aitBoolean), 'ReadBoolean', 'Found '+DescribeIdentifier(aId)+' expecting a Boolean'); Check(aId.ContentLength = 1, 'ReadBoolean', 'Boolean Length should be 1'); result := ReadByte <> 0; end; Function TIdASN1Decoder.ReadInteger : Integer; var aId : TIdASN1Identifier; iVal : Integer; iNext : Byte; bNeg : Boolean; iLoop : integer; begin aId := ReadHeader; Check((aId.IdClass = aicApplication) or (aId.TagType = aitInteger), 'ReadInteger', 'Found '+DescribeIdentifier(aId)+' expecting an Integer'); Check(aId.ContentLength >= 1, 'ReadInteger', 'Boolean Length should not be 0'); iVal := 0; bNeg := False; for iLoop := 1 to aId.ContentLength do begin iNext := ReadByte; if (iLoop = 1) and (iNext > $7F) then bNeg := True; if bNeg then iNext := not iNext; iVal := iVal * 256 + iNext; end; if bNeg then iVal := -(iVal + 1); Result := iVal; end; function TIdASN1Decoder.ReadEnum : Integer; var aId : TIdASN1Identifier; iVal : Integer; iNext : Byte; bNeg : Boolean; iLoop : integer; begin aId := ReadHeader; Check((aId.IdClass = aicApplication) or (aId.TagType = aitEnum), 'ReadEnum', 'Found '+DescribeIdentifier(aId)+' expecting an Enum'); Check(aId.ContentLength >= 1, 'ReadEnum', 'Boolean Length should not be 0'); iVal := 0; bNeg := False; for iLoop := 1 to aId.ContentLength do begin iNext := ReadByte; if (iLoop = 1) and (iNext > $7F) then bNeg := True; if bNeg then iNext := not iNext; iVal := iVal * 256 + iNext; end; if bNeg then iVal := -(iVal + 1); Result := iVal; end; Function TIdASN1Decoder.ReadString : String; var aId : TIdASN1Identifier; iLoop : integer; begin aId := ReadHeader; Check((aId.IdClass = aicApplication) or (aId.TagType in [aitUnknown, aitString]), 'ReadString', 'Found '+DescribeIdentifier(aId)+' expecting a String'); SetLength(result, aId.ContentLength); for iLoop := 1 to aId.ContentLength do result[iLoop] := ReadChar; end; procedure TIdASN1Decoder.ReadSequenceBegin; var aId : TIdASN1Identifier; begin aId := ReadHeader; Check((aId.IdClass = aicApplication) or (aId.TagType in [aitUnknown, aitSequence]), 'ReadSequenceBegin', 'Found '+DescribeIdentifier(aId)+' expecting a Sequence'); FLengths[0] := FLengths[0] - aId.ContentLength; FLengths.InsertInt(0, aId.ContentLength); end; function TIdASN1Decoder.SequenceEnded: Boolean; begin Check(FLengths.Count > 1, 'SequenceEnded', 'Not in a Sequence'); result := FLengths[0] <= 0; end; procedure TIdASN1Decoder.ReadSequenceEnd; begin Check(SequenceEnded, 'ReadSequenceEnd', 'Sequence has not ended'); FLengths.Delete(0); end; { TIntegerList } procedure TIntegerList.AddInt(value: integer); begin Add(pointer(value)); end; function TIntegerList.GetValue(iIndex: integer): Integer; begin result := integer(items[iIndex]); end; procedure TIntegerList.InsertInt(Index, Value: integer); begin insert(Index, pointer(value)); end; procedure TIntegerList.SetValue(Index: integer; const Value: Integer); begin items[Index] := pointer(value); end; end.