IdASN1Coder.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.0 15/04/2005 7:25:02 AM GGrieve
  18. first ported to INdy
  19. }
  20. unit IdASN1Coder;
  21. interface
  22. {$i IdCompilerDefines.inc}
  23. uses
  24. Classes,
  25. Contnrs;
  26. type
  27. TIdASN1IdentifierType = (aitUnknown, aitSequence, aitBoolean, aitInteger, aitEnum, aitString, aitOID, aitReal);
  28. TIdASN1IdentifierClass = (aicUniversal, aicApplication, aicContextSpecific, aicPrivate);
  29. TIdASN1Identifier = record
  30. Position : Integer;
  31. IdClass : TIdASN1IdentifierClass;
  32. Constructed : Boolean;
  33. TagValue : Integer;
  34. TagType : TIdASN1IdentifierType;
  35. ContentLength : integer;
  36. end;
  37. TIdASN1Sequence = Class
  38. Private
  39. FIdClass : TIdASN1IdentifierClass;
  40. FTag : Integer;
  41. FContents : String;
  42. Public
  43. Property IdClass : TIdASN1IdentifierClass Read FIdClass Write FIdClass;
  44. Property Tag : integer Read FTag Write FTag;
  45. Property Contents : String Read FContents Write FContents;
  46. End;
  47. TIdASN1Sequences = Class(TObjectList)
  48. Private
  49. Function GetElement(Const iIndex : Integer) : TIdASN1Sequence;
  50. function GetLast: TIdASN1Sequence;
  51. Public
  52. Property LastElement : TIdASN1Sequence read GetLast;
  53. procedure Pop;
  54. Property Elements[Const iIndex : Integer] : TIdASN1Sequence Read GetElement; Default;
  55. End;
  56. TIdASN1Encoder = class
  57. private
  58. FSequences : TIdASN1Sequences;
  59. FReadyToWrite : Boolean;
  60. function FormatEncoding(aClass : TIdASN1IdentifierClass; bConstructed : Boolean; iTag : integer; const sContent : String) : String;
  61. procedure AddEncoding(const sContent : String);
  62. procedure WriteInt(iTag : integer; iValue : integer);
  63. function EncodeLength(iLen : Integer):String;
  64. protected
  65. // must call this as an outer wrapper
  66. Procedure StartWriting;
  67. Procedure StopWriting;
  68. // sequences
  69. procedure StartSequence; overload;
  70. procedure StartSequence(iTag : Integer); overload;
  71. procedure StartSequence(aClass : TIdASN1IdentifierClass; iTag : Integer); overload;
  72. procedure StopSequence;
  73. // primitives
  74. procedure WriteBoolean(bValue : Boolean);
  75. procedure WriteInteger(iValue : Integer);
  76. procedure WriteEnum(iValue : Integer);
  77. procedure WriteString(sValue : String); overload;
  78. procedure WriteString(iTag : integer; sValue : String); overload;
  79. public
  80. Constructor Create;
  81. destructor Destroy; override;
  82. procedure WriteToStream(Stream : TStream);
  83. end;
  84. TIntegerList = class (TList)
  85. private
  86. function GetValue(iIndex: integer): Integer;
  87. procedure SetValue(Index: integer; const Value: Integer);
  88. public
  89. procedure AddInt(value : integer);
  90. procedure InsertInt(Index, Value : integer);
  91. property Value[iIndex : integer]:Integer read GetValue write SetValue; default;
  92. end;
  93. TIdASN1Decoder = class
  94. private
  95. FLengths : TIntegerList;
  96. FPosition : Integer;
  97. FNextHeader : TIdASN1Identifier;
  98. FNextHeaderUsed : Boolean;
  99. FStream: TStream;
  100. function ReadHeader : TIdASN1Identifier; // -1 in length means that no definite length was specified
  101. function DescribeIdentifier(const aId : TIdASN1Identifier) : String;
  102. Function ReadByte : Byte;
  103. function ReadChar : Char;
  104. function ReadContentLength : Integer;
  105. protected
  106. procedure Check(bCondition : Boolean; const sMethod, sMessage : String); overload; virtual;
  107. // must call this as an outer wrapper
  108. Procedure StartReading;
  109. Procedure StopReading;
  110. // sequences and choices
  111. procedure ReadSequenceBegin;
  112. function SequenceEnded : Boolean;
  113. procedure ReadSequenceEnd;
  114. function NextTag : integer;
  115. function NextTagType : TIdASN1IdentifierType;
  116. // primitives
  117. function ReadBoolean : Boolean;
  118. Function ReadInteger : Integer;
  119. function ReadEnum : Integer;
  120. Function ReadString : String;
  121. public
  122. Constructor Create;
  123. destructor Destroy; override;
  124. property Stream : TStream read FStream write FStream;
  125. end;
  126. const
  127. NAMES_ASN1IDENTIFIERTYPE : array [TIdASN1IdentifierType] of String = ('Unknown', 'Sequence', 'Boolean', 'Integer', 'Enum', 'String', 'OID', 'Real');
  128. TAGS_ASN1IDENTIFIERTYPE : array [TIdASN1IdentifierType] of Integer = (0, $10, $01, $02, $0A, $04, $06, 0 {?});
  129. NAMES_ASN1IDENTIFIERCLASS : array [TIdASN1IdentifierClass] of String = ('Universal', 'Application', 'ContextSpecific', 'Private');
  130. function ToIdentifierType(iTag : integer) : TIdASN1IdentifierType;
  131. implementation
  132. uses
  133. IdGlobal, IdException, SysUtils;
  134. function ToIdentifierType(iTag : integer) : TIdASN1IdentifierType;
  135. begin
  136. case iTag of
  137. $10 : result := aitSequence;
  138. $01 : result := aitBoolean;
  139. $02 : result := aitInteger;
  140. $04 : result := aitString;
  141. $06 : result := aitOID;
  142. $0A : result := aitEnum;
  143. else
  144. result := aitUnknown;
  145. end;
  146. end;
  147. { TIdASN1Encoder }
  148. constructor TIdASN1Encoder.Create;
  149. begin
  150. inherited Create;
  151. FSequences := TIdASN1Sequences.create;
  152. end;
  153. destructor TIdASN1Encoder.Destroy;
  154. begin
  155. FSequences.Free;
  156. inherited Destroy;
  157. end;
  158. procedure TIdASN1Encoder.WriteToStream(Stream : TStream);
  159. begin
  160. Assert(FReadyToWrite, 'not ready to write');
  161. if Length(FSequences[0].Contents) <> 0 then
  162. WriteStringToStream(Stream, FSequences[0].Contents, IndyTextEncoding_8Bit);
  163. end;
  164. procedure TIdASN1Encoder.StartWriting;
  165. begin
  166. FSequences.Clear;
  167. StartSequence(aicUniversal, 0);
  168. end;
  169. procedure TIdASN1Encoder.StopWriting;
  170. begin
  171. assert(FSequences.Count = 1, 'Writing left an open Sequence');
  172. FReadyToWrite := true;
  173. // todo - actually commit to stream Produce(Fsequences[0].Contents);
  174. end;
  175. procedure TIdASN1Encoder.StartSequence(aClass: TIdASN1IdentifierClass; iTag: Integer);
  176. var
  177. oSequence : TIdASN1Sequence;
  178. begin
  179. oSequence := TIdASN1Sequence.create;
  180. try
  181. oSequence.IdClass := aClass;
  182. oSequence.Tag := iTag;
  183. oSequence.Contents := '';
  184. FSequences.add(oSequence);
  185. except
  186. oSequence.Free;
  187. raise;
  188. end;
  189. end;
  190. procedure TIdASN1Encoder.StartSequence(iTag: Integer);
  191. begin
  192. if iTag = -1 then
  193. StartSequence(aicUniversal, TAGS_ASN1IDENTIFIERTYPE[aitSequence])
  194. else
  195. StartSequence(aicApplication, iTag);
  196. end;
  197. procedure TIdASN1Encoder.StartSequence;
  198. begin
  199. StartSequence(aicUniversal, TAGS_ASN1IDENTIFIERTYPE[aitSequence]);
  200. end;
  201. procedure TIdASN1Encoder.StopSequence;
  202. var
  203. sSequence : String;
  204. begin
  205. sSequence := FormatEncoding(FSequences.LastElement.IdClass, true, FSequences.LastElement.Tag, FSequences.LastElement.Contents);
  206. FSequences.Pop;
  207. AddEncoding(sSequence);
  208. end;
  209. procedure TIdASN1Encoder.WriteBoolean(bValue: Boolean);
  210. begin
  211. // RLebeau 1/7/09: using Char() for #128-#255 because in D2009, the compiler
  212. // may change characters >= #128 from their Ansi codepage value to their true
  213. // Unicode codepoint value, depending on the codepage used for the source code.
  214. // For instance, #128 may become #$20AC...
  215. if bValue then
  216. AddEncoding(FormatEncoding(aicUniversal, False, TAGS_ASN1IDENTIFIERTYPE[aitBoolean], Char($FF)))
  217. else
  218. AddEncoding(FormatEncoding(aicUniversal, False, TAGS_ASN1IDENTIFIERTYPE[aitBoolean], #$00));
  219. end;
  220. procedure TIdASN1Encoder.WriteEnum(iValue: Integer);
  221. begin
  222. WriteInt(TAGS_ASN1IDENTIFIERTYPE[aitEnum], iValue);
  223. end;
  224. procedure TIdASN1Encoder.WriteInteger(iValue: Integer);
  225. begin
  226. WriteInt(TAGS_ASN1IDENTIFIERTYPE[aitInteger], iValue);
  227. end;
  228. procedure TIdASN1Encoder.WriteInt(iTag, iValue: integer);
  229. var
  230. sValue : String;
  231. x, y: Cardinal;
  232. bNeg: Boolean;
  233. begin
  234. bNeg := iValue < 0;
  235. x := Abs(iValue);
  236. if bNeg then
  237. x := not (x - 1);
  238. sValue := ''; {Do not Localize}
  239. repeat
  240. y := x mod 256;
  241. x := x div 256;
  242. sValue := Char(y) + sValue;
  243. until x = 0;
  244. if (not bNeg) and (sValue[1] > #$7F) then
  245. sValue := #0 + sValue;
  246. AddEncoding(FormatEncoding(aicUniversal, False, iTag, sValue))
  247. end;
  248. procedure TIdASN1Encoder.WriteString(sValue: String);
  249. begin
  250. AddEncoding(FormatEncoding(aicUniversal, False, TAGS_ASN1IDENTIFIERTYPE[aitString], sValue))
  251. end;
  252. procedure TIdASN1Encoder.WriteString(iTag : integer; sValue: String);
  253. begin
  254. AddEncoding(FormatEncoding(aicContextSpecific, False, iTag, sValue))
  255. end;
  256. procedure TIdASN1Encoder.AddEncoding(const sContent: String);
  257. begin
  258. FSequences.LastElement.Contents := FSequences.LastElement.Contents + sContent;
  259. end;
  260. function TIdASN1Encoder.FormatEncoding(aClass: TIdASN1IdentifierClass; bConstructed : Boolean; iTag: integer; const sContent: String): String;
  261. begin
  262. if bConstructed then
  263. result := chr((ord(aClass) shl 6) or $20 or iTag) + EncodeLength(length(sContent)) + sContent
  264. else
  265. result := chr((ord(aClass) shl 6) or iTag) + EncodeLength(length(sContent)) + sContent;
  266. end;
  267. function TIdASN1Encoder.EncodeLength(iLen: Integer): String;
  268. var
  269. x, y: Integer;
  270. begin
  271. if iLen < $80 then
  272. Result := Char(iLen)
  273. else
  274. begin
  275. x := iLen;
  276. Result := '';
  277. repeat
  278. y := x mod 256;
  279. x := x div 256;
  280. Result := Char(y) + Result;
  281. until x = 0;
  282. y := Length(Result);
  283. y := y or $80;
  284. Result := Char(y) + Result;
  285. end;
  286. end;
  287. { TIdASN1Sequences }
  288. function TIdASN1Sequences.GetElement(const iIndex: Integer): TIdASN1Sequence;
  289. begin
  290. result := TIdASN1Sequence(items[iIndex]);
  291. end;
  292. function TIdASN1Sequences.GetLast: TIdASN1Sequence;
  293. begin
  294. if Count = 0 then
  295. result := nil
  296. else
  297. result := GetElement(Count - 1);
  298. end;
  299. procedure TIdASN1Sequences.Pop;
  300. begin
  301. if Count > 0 then
  302. Delete(Count-1);
  303. end;
  304. { TIdASN1Decoder }
  305. Constructor TIdASN1Decoder.Create;
  306. begin
  307. inherited Create;
  308. FLengths := TIntegerList.create;
  309. end;
  310. destructor TIdASN1Decoder.Destroy;
  311. begin
  312. FLengths.Free;
  313. Inherited Destroy;
  314. end;
  315. procedure TIdASN1Decoder.Check(bCondition: Boolean; const sMethod, sMessage: String);
  316. begin
  317. if not bCondition then
  318. raise EIdException.create(sMessage); // TODO: create a new Exception class for this
  319. end;
  320. Procedure TIdASN1Decoder.StartReading;
  321. begin
  322. FLengths.Clear;
  323. FLengths.AddInt(-1);
  324. FNextHeaderUsed := False;
  325. FPosition := 0;
  326. end;
  327. Procedure TIdASN1Decoder.StopReading;
  328. begin
  329. Check(FLengths.Count = 1, 'StopReading', 'Reading was incomplete');
  330. FLengths.Clear;
  331. end;
  332. function TIdASN1Decoder.DescribeIdentifier(const aId : TIdASN1Identifier) : String;
  333. begin
  334. result := '[Pos '+IntToStr(aId.Position)+', Type '+NAMES_ASN1IDENTIFIERTYPE[aId.TagType]+', '+
  335. 'Tag '+IntToStr(aId.TagValue)+', Class '+NAMES_ASN1IDENTIFIERCLASS[aId.IdClass]+']';
  336. end;
  337. Function TIdASN1Decoder.ReadByte : Byte;
  338. begin
  339. Check(FLengths[0] <> 0, 'ReadByte', 'Attempt to read past end of Sequence');
  340. Stream.Read(result, 1);
  341. inc(FPosition);
  342. FLengths[0] := FLengths[0] - 1;
  343. end;
  344. function TIdASN1Decoder.ReadChar : Char;
  345. begin
  346. result := Chr(readByte);
  347. end;
  348. function TIdASN1Decoder.ReadContentLength: Integer;
  349. var
  350. iNext : Byte;
  351. iLoop: Integer;
  352. begin
  353. iNext := ReadByte;
  354. if iNext < $80 then
  355. Result := iNext
  356. else
  357. begin
  358. Result := 0;
  359. iNext := iNext and $7F;
  360. if iNext = 0 then
  361. raise EIdException.create('Indefinite lengths are not yet handled'); {do not localize} // TODO: add a resource string, and create a new Exception class for this
  362. for iLoop := 1 to iNext do
  363. begin
  364. Result := Result * 256;
  365. iNext := ReadByte;
  366. Result := Result + iNext;
  367. end;
  368. end;
  369. end;
  370. function TIdASN1Decoder.ReadHeader : TIdASN1Identifier;
  371. var
  372. iNext : Byte;
  373. begin
  374. if FNextHeaderUsed then
  375. begin
  376. result := FNextHeader;
  377. FNextHeaderUsed := False;
  378. end
  379. else
  380. begin
  381. FillChar(result, sizeof(TIdASN1Identifier), #0);
  382. result.Position := FPosition;
  383. iNext := ReadByte;
  384. result.Constructed := iNext and $20 > 0;
  385. result.IdClass := TIdASN1IdentifierClass(iNext shr 6);
  386. if iNext and $1F = $1F then
  387. begin
  388. raise EIdException.create('Todo'); {do not localize} // TODO: add a resource string, and create a new Exception class for this
  389. end
  390. else
  391. result.TagValue := iNext and $1F;
  392. result.TagType := ToIdentifierType(result.TagValue);
  393. result.ContentLength := ReadContentLength;
  394. end;
  395. end;
  396. function TIdASN1Decoder.NextTag: integer;
  397. begin
  398. if not FNextHeaderUsed then
  399. begin
  400. FNextHeader := ReadHeader;
  401. FNextHeaderUsed := true;
  402. end;
  403. result := FNextHeader.TagValue;
  404. end;
  405. function TIdASN1Decoder.NextTagType: TIdASN1IdentifierType;
  406. begin
  407. if not FNextHeaderUsed then
  408. begin
  409. FNextHeader := ReadHeader;
  410. FNextHeaderUsed := true;
  411. end;
  412. result := FNextHeader.TagType;
  413. end;
  414. function TIdASN1Decoder.ReadBoolean : Boolean;
  415. var
  416. aId : TIdASN1Identifier;
  417. begin
  418. aId := ReadHeader;
  419. Check((aId.IdClass = aicApplication) or (aId.TagType = aitBoolean), 'ReadBoolean', 'Found '+DescribeIdentifier(aId)+' expecting a Boolean');
  420. Check(aId.ContentLength = 1, 'ReadBoolean', 'Boolean Length should be 1');
  421. result := ReadByte <> 0;
  422. end;
  423. Function TIdASN1Decoder.ReadInteger : Integer;
  424. var
  425. aId : TIdASN1Identifier;
  426. iVal : Integer;
  427. iNext : Byte;
  428. bNeg : Boolean;
  429. iLoop : integer;
  430. begin
  431. aId := ReadHeader;
  432. Check((aId.IdClass = aicApplication) or (aId.TagType = aitInteger), 'ReadInteger', 'Found '+DescribeIdentifier(aId)+' expecting an Integer');
  433. Check(aId.ContentLength >= 1, 'ReadInteger', 'Boolean Length should not be 0');
  434. iVal := 0;
  435. bNeg := False;
  436. for iLoop := 1 to aId.ContentLength do
  437. begin
  438. iNext := ReadByte;
  439. if (iLoop = 1) and (iNext > $7F) then
  440. bNeg := True;
  441. if bNeg then
  442. iNext := not iNext;
  443. iVal := iVal * 256 + iNext;
  444. end;
  445. if bNeg then
  446. iVal := -(iVal + 1);
  447. Result := iVal;
  448. end;
  449. function TIdASN1Decoder.ReadEnum : Integer;
  450. var
  451. aId : TIdASN1Identifier;
  452. iVal : Integer;
  453. iNext : Byte;
  454. bNeg : Boolean;
  455. iLoop : integer;
  456. begin
  457. aId := ReadHeader;
  458. Check((aId.IdClass = aicApplication) or (aId.TagType = aitEnum), 'ReadEnum', 'Found '+DescribeIdentifier(aId)+' expecting an Enum');
  459. Check(aId.ContentLength >= 1, 'ReadEnum', 'Boolean Length should not be 0');
  460. iVal := 0;
  461. bNeg := False;
  462. for iLoop := 1 to aId.ContentLength do
  463. begin
  464. iNext := ReadByte;
  465. if (iLoop = 1) and (iNext > $7F) then
  466. bNeg := True;
  467. if bNeg then
  468. iNext := not iNext;
  469. iVal := iVal * 256 + iNext;
  470. end;
  471. if bNeg then
  472. iVal := -(iVal + 1);
  473. Result := iVal;
  474. end;
  475. Function TIdASN1Decoder.ReadString : String;
  476. var
  477. aId : TIdASN1Identifier;
  478. iLoop : integer;
  479. begin
  480. aId := ReadHeader;
  481. Check((aId.IdClass = aicApplication) or (aId.TagType in [aitUnknown, aitString]), 'ReadString', 'Found '+DescribeIdentifier(aId)+' expecting a String');
  482. SetLength(result, aId.ContentLength);
  483. for iLoop := 1 to aId.ContentLength do
  484. result[iLoop] := ReadChar;
  485. end;
  486. procedure TIdASN1Decoder.ReadSequenceBegin;
  487. var
  488. aId : TIdASN1Identifier;
  489. begin
  490. aId := ReadHeader;
  491. Check((aId.IdClass = aicApplication) or (aId.TagType in [aitUnknown, aitSequence]), 'ReadSequenceBegin', 'Found '+DescribeIdentifier(aId)+' expecting a Sequence');
  492. FLengths[0] := FLengths[0] - aId.ContentLength;
  493. FLengths.InsertInt(0, aId.ContentLength);
  494. end;
  495. function TIdASN1Decoder.SequenceEnded: Boolean;
  496. begin
  497. Check(FLengths.Count > 1, 'SequenceEnded', 'Not in a Sequence');
  498. result := FLengths[0] <= 0;
  499. end;
  500. procedure TIdASN1Decoder.ReadSequenceEnd;
  501. begin
  502. Check(SequenceEnded, 'ReadSequenceEnd', 'Sequence has not ended');
  503. FLengths.Delete(0);
  504. end;
  505. { TIntegerList }
  506. procedure TIntegerList.AddInt(value: integer);
  507. begin
  508. Add(pointer(value));
  509. end;
  510. function TIntegerList.GetValue(iIndex: integer): Integer;
  511. begin
  512. result := integer(items[iIndex]);
  513. end;
  514. procedure TIntegerList.InsertInt(Index, Value: integer);
  515. begin
  516. insert(Index, pointer(value));
  517. end;
  518. procedure TIntegerList.SetValue(Index: integer; const Value: Integer);
  519. begin
  520. items[Index] := pointer(value);
  521. end;
  522. end.