Zum Hauptinhalt springen

4. Presentation Language (Darstellungssprache)

Dieses Dokument befasst sich mit der Formatierung von Daten in einer externen Darstellung. Die folgende sehr grundlegende und etwas locker definierte Präsentationssyntax wird verwendet. Die Syntax bezieht ihre Struktur aus mehreren Quellen. Obwohl sie in ihrer Syntax der Programmiersprache „C" und in Syntax und Absicht XDR [XDR] ähnelt, wäre es riskant, zu viele Parallelen zu ziehen. Der Zweck dieser Präsentationssprache besteht ausschließlich darin, TLS zu dokumentieren; sie hat keine allgemeine Anwendung über dieses spezielle Ziel hinaus.

4.1. Basic Block Size (Grundlegende Blockgröße)

Die Darstellung aller Datenelemente ist explizit spezifiziert. Die grundlegende Datenblockgröße beträgt ein Byte (d.h. 8 Bit). Mehrbytige Datenelemente sind Verkettungen von Bytes, von links nach rechts, von oben nach unten. Aus dem Byte-Stream wird ein Mehrbytige-Element (eine Zahl im Beispiel) folgendermaßen gebildet (unter Verwendung der C-Notation):

value = (byte[0] << 8*(n-1)) | (byte[1] << 8*(n-2)) |
... | byte[n-1];

Diese Byte-Reihenfolge für mehrbytige Werte ist die übliche Netzwerk-Byte-Reihenfolge oder Big-Endian-Format.

4.2. Miscellaneous (Verschiedenes)

Kommentare beginnen mit /* und enden mit */.

Optionale Komponenten werden durch Einschließen in doppelte Klammern [[ ]] gekennzeichnet.

Einzelbyte-Entitäten, die nicht interpretierte Daten enthalten, sind vom Typ opaque.

4.3. Vectors (Vektoren)

Ein Vektor (eindimensionales Array) ist ein Strom homogener Datenelemente. Die Größe des Vektors kann zur Dokumentationszeit spezifiziert oder bis zur Laufzeit unspezifiziert gelassen werden. In jedem Fall deklariert die Länge die Anzahl der Bytes, nicht die Anzahl der Elemente im Vektor. Die Syntax zum Spezifizieren eines neuen Typs T', der ein Vektor fester Länge vom Typ T ist, lautet:

T T'[n];

Hier belegt T' n Bytes im Datenstrom, wobei n ein Vielfaches der Größe von T ist. Die Länge des Vektors ist nicht im codierten Strom enthalten.

Im folgenden Beispiel ist Datum als drei aufeinanderfolgende Bytes definiert, die das Protokoll nicht interpretiert, während Data drei aufeinanderfolgende Datum sind, die insgesamt neun Bytes verbrauchen.

opaque Datum[3];      /* three uninterpreted bytes */
Datum Data[9]; /* 3 consecutive 3 byte vectors */

Vektoren variabler Länge werden definiert, indem ein Unterbereich legaler Längen, einschließlich, unter Verwendung der Notation <floor..ceiling> spezifiziert wird. Wenn diese codiert werden, geht die tatsächliche Länge dem Inhalt des Vektors im Byte-Stream voraus. Die Länge hat die Form einer Zahl, die so viele Bytes verbraucht, wie erforderlich sind, um die spezifizierte maximale (ceiling) Länge des Vektors zu halten. Ein Vektor variabler Länge mit einem tatsächlichen Längenfeld von Null wird als leerer Vektor bezeichnet.

T T'<floor..ceiling>;

Im folgenden Beispiel ist mandatory ein Vektor, der zwischen 300 und 400 Bytes vom Typ opaque enthalten muss. Er kann niemals leer sein. Das tatsächliche Längenfeld verbraucht zwei Bytes, ein uint16, was ausreicht, um den Wert 400 darzustellen (siehe Abschnitt 4.4). Andererseits kann longer bis zu 800 Bytes Daten oder 400 uint16-Elemente darstellen, und er kann leer sein. Seine Codierung wird ein vorangestelltes zweibytiges tatsächliches Längenfeld vor dem Vektor enthalten. Die Länge eines codierten Vektors muss ein gerades Vielfaches der Länge eines einzelnen Elements sein (zum Beispiel wäre ein 17-Byte-Vektor von uint16 illegal).

opaque mandatory<300..400>;
/* length field is 2 bytes, cannot be empty */
uint16 longer<0..800>;
/* zero to 400 16-bit unsigned integers */

4.4. Numbers (Zahlen)

Der grundlegende numerische Datentyp ist ein vorzeichenloses Byte (uint8). Alle größeren numerischen Datentypen werden aus Serien von Bytes fester Länge gebildet, die wie in Abschnitt 4.1 beschrieben verkettet werden, und sind ebenfalls vorzeichenlos. Die folgenden numerischen Typen sind vordefiniert.

uint8 uint16[2];
uint8 uint24[3];
uint8 uint32[4];
uint8 uint64[8];

Alle Werte, hier und anderswo in der Spezifikation, werden in Netzwerk-Byte-Reihenfolge (Big-Endian) gespeichert; das durch die Hex-Bytes 01 02 03 04 dargestellte uint32 entspricht dem Dezimalwert 16909060.

Beachten Sie, dass es in einigen Fällen (z.B. DH-Parameter) notwendig ist, Ganzzahlen als opaque-Vektoren darzustellen. In solchen Fällen werden sie als vorzeichenlose Ganzzahlen dargestellt (d.h. führende Null-Oktette sind nicht erforderlich, auch wenn das höchstwertige Bit gesetzt ist).

4.5. Enumerateds (Aufzählungen)

Ein zusätzlicher spärlicher Datentyp ist verfügbar, der als enum bezeichnet wird. Ein Feld vom Typ enum kann nur die in der Definition deklarierten Werte annehmen. Jede Definition ist ein anderer Typ. Nur Aufzählungen desselben Typs können zugewiesen oder verglichen werden. Jedem Element einer Aufzählung muss ein Wert zugewiesen werden, wie im folgenden Beispiel demonstriert. Da die Elemente der Aufzählung nicht geordnet sind, können ihnen beliebige eindeutige Werte in beliebiger Reihenfolge zugewiesen werden.

enum { e1(v1), e2(v2), ... , en(vn) [[, (n)]] } Te;

Eine Aufzählung belegt im Byte-Stream so viel Platz wie ihr maximal definierter Ordnungswert. Die folgende Definition würde dazu führen, dass ein Byte zum Transport von Feldern vom Typ Color verwendet wird.

enum { red(3), blue(5), white(7) } Color;

Man kann optional einen Wert ohne sein zugehöriges Tag spezifizieren, um die Breitendefinition zu erzwingen, ohne ein überflüssiges Element zu definieren.

Im folgenden Beispiel wird Taste zwei Bytes im Datenstrom verbrauchen, kann aber nur die Werte 1, 2 oder 4 annehmen.

enum { sweet(1), sour(2), bitter(4), (32000) } Taste;

Die Namen der Elemente einer Aufzählung sind auf den definierten Typ beschränkt. Im ersten Beispiel wäre eine vollständig qualifizierte Referenz auf das zweite Element der Aufzählung Color.blue. Eine solche Qualifikation ist nicht erforderlich, wenn das Ziel der Zuweisung gut spezifiziert ist.

Color color = Color.blue;     /* overspecified, legal */
Color color = blue; /* correct, type implicit */

Für Aufzählungen, die niemals in eine externe Darstellung konvertiert werden, kann die numerische Information weggelassen werden.

enum { low, medium, high } Amount;

4.6. Constructed Types (Konstruierte Typen)

Strukturtypen können aus primitiven Typen zur Vereinfachung konstruiert werden. Jede Spezifikation deklariert einen neuen, eindeutigen Typ. Die Syntax für die Definition ist der von C sehr ähnlich.

struct {
T1 f1;
T2 f2;
...
Tn fn;
} [[T]];

Die Felder innerhalb einer Struktur können unter Verwendung des Typnamens qualifiziert werden, mit einer Syntax, die der für Aufzählungen verfügbaren sehr ähnlich ist. Zum Beispiel bezieht sich T.f2 auf das zweite Feld der vorherigen Deklaration. Strukturdefinitionen können verschachtelt werden.

4.6.1. Variants (Varianten)

Definierte Strukturen können Varianten haben, die auf etwas Wissen basieren, das in der Umgebung verfügbar ist. Der Selektor muss ein Aufzählungstyp sein, der die möglichen Varianten definiert, die die Struktur definiert. Es muss einen case-Arm für jedes Element der im select deklarierten Aufzählung geben. Case-Arme haben ein begrenztes Fall-through: Wenn zwei case-Arme unmittelbar aufeinander folgen, ohne Felder dazwischen, dann enthalten beide dieselben Felder. Daher enthalten im folgenden Beispiel sowohl „orange" als auch „banana" V2. Beachten Sie, dass dies ein neues Syntaxelement in TLS 1.2 ist.

Der Körper der Variantenstruktur kann eine Bezeichnung für Referenzen erhalten. Der Mechanismus, durch den die Variante zur Laufzeit ausgewählt wird, ist nicht durch die Präsentationssprache vorgeschrieben.

struct {
T1 f1;
T2 f2;
....
Tn fn;
select (E) {
case e1: Te1;
case e2: Te2;
case e3: case e4: Te3;
....
case en: Ten;
} [[fv]];
} [[Tv]];

Zum Beispiel:

enum { apple, orange, banana } VariantTag;

struct {
uint16 number;
opaque string<0..10>; /* variable length */
} V1;

struct {
uint32 number;
opaque string[10]; /* fixed length */
} V2;

struct {
select (VariantTag) { /* value of selector is implicit */
case apple:
V1; /* VariantBody, tag = apple */
case orange:
case banana:
V2; /* VariantBody, tag = orange or banana */
} variant_body; /* optional label on variant */
} VariantRecord;

4.7. Cryptographic Attributes (Kryptografische Attribute)

Fünf kryptografische Operationen — digitales Signieren, Stream-Cipher-Verschlüsselung, Block-Cipher-Verschlüsselung, authentifizierte Verschlüsselung mit zusätzlichen Daten (AEAD) und Public-Key-Verschlüsselung — werden als digitally-signed, stream-ciphered, block-ciphered, aead-ciphered bzw. public-key-encrypted bezeichnet. Die kryptografische Verarbeitung eines Felds wird spezifiziert, indem eine entsprechende Schlüsselwortbezeichnung vor die Typspezifikation des Felds gesetzt wird. Kryptografische Schlüssel sind durch den aktuellen Sitzungszustand impliziert (siehe Abschnitt 6.1).

Ein digital signiertes Element wird als struct DigitallySigned codiert:

struct {
SignatureAndHashAlgorithm algorithm;
opaque signature<0..2^16-1>;
} DigitallySigned;

Das algorithm-Feld spezifiziert den verwendeten Algorithmus (siehe Abschnitt 7.4.1.4.1 für die Definition dieses Felds). Beachten Sie, dass die Einführung des algorithm-Felds eine Änderung gegenüber früheren Versionen darstellt. Die signature ist eine digitale Signatur unter Verwendung dieser Algorithmen über den Inhalt des Elements. Der Inhalt selbst erscheint nicht auf der Leitung, sondern wird einfach berechnet. Die Länge der Signatur wird durch den Signaturalgorithmus und den Schlüssel spezifiziert.

Bei RSA-Signaturen enthält der opaque-Vektor die mit dem in [PKCS1] definierten RSASSA-PKCS1-v1_5-Signaturschema erzeugte Signatur. Wie in [PKCS1] diskutiert, MUSS das DigestInfo DER-codiert sein [X680] [X690]. Für Hash-Algorithmen ohne Parameter (einschließlich SHA-1) MUSS das Feld DigestInfo.AlgorithmIdentifier.parameters NULL sein, aber Implementierungen MÜSSEN sowohl ohne Parameter als auch mit NULL-Parametern akzeptieren. Beachten Sie, dass frühere Versionen von TLS ein anderes RSA-Signaturschema verwendeten, das keine DigestInfo-Codierung enthielt.

Bei DSA werden die 20 Bytes des SHA-1-Hashs direkt durch den digitalen Signaturalgorithmus ohne zusätzliches Hashing geleitet. Dies erzeugt zwei Werte, r und s. Die DSA-Signatur ist ein opaque-Vektor, wie oben, dessen Inhalt die DER-Codierung von folgendem ist:

Dss-Sig-Value ::= SEQUENCE {
r INTEGER,
s INTEGER
}

Hinweis: In der aktuellen Terminologie bezieht sich DSA auf den Digital Signature Algorithm und DSS auf den NIST-Standard. In den ursprünglichen SSL- und TLS-Spezifikationen wurde „DSS" universell verwendet. Dieses Dokument verwendet „DSA" für den Algorithmus, „DSS" für den Standard und verwendet „DSS" in den Code-Point-Definitionen zur historischen Kontinuität.

Bei Stream-Cipher-Verschlüsselung wird der Klartext mit einer identischen Menge an Ausgabe exklusiv-ODER-verknüpft, die von einem kryptografisch sicheren, schlüsselbasierten Pseudozufallszahlengenerator erzeugt wird.

Bei Block-Cipher-Verschlüsselung wird jeder Block von Klartext zu einem Block von Chiffretext verschlüsselt. Alle Block-Cipher-Verschlüsselung erfolgt im CBC-Modus (Cipher Block Chaining), und alle Elemente, die block-ciphered sind, werden ein exaktes Vielfaches der Cipher-Blocklänge sein.

Bei AEAD-Verschlüsselung wird der Klartext gleichzeitig verschlüsselt und integritätsgeschützt. Die Eingabe kann beliebige Länge haben, und die aead-ciphered-Ausgabe ist im Allgemeinen größer als die Eingabe, um den Integritätsprüfwert aufzunehmen.

Bei Public-Key-Verschlüsselung wird ein Public-Key-Algorithmus verwendet, um Daten so zu verschlüsseln, dass sie nur mit dem passenden privaten Schlüssel entschlüsselt werden können. Ein public-key-encrypted-Element wird als opaque-Vektor <0..2^16-1> codiert, wobei die Länge durch den Verschlüsselungsalgorithmus und den Schlüssel spezifiziert wird.

RSA-Verschlüsselung erfolgt unter Verwendung des in [PKCS1] definierten RSAES-PKCS1-v1_5-Verschlüsselungsschemas.

Im folgenden Beispiel:

stream-ciphered struct {
uint8 field1;
uint8 field2;
digitally-signed opaque {
uint8 field3<0..255>;
uint8 field4;
};
} UserType;

Der Inhalt der inneren Struktur (field3 und field4) wird als Eingabe für den Signatur-/Hash-Algorithmus verwendet, und dann wird die gesamte Struktur mit einem Stream-Cipher verschlüsselt. Die Länge dieser Struktur in Bytes wäre gleich zwei Bytes für field1 und field2, plus zwei Bytes für den Signatur- und Hash-Algorithmus, plus zwei Bytes für die Länge der Signatur, plus die Länge der Ausgabe des Signaturalgorithmus. Die Länge der Signatur ist bekannt, weil der für die Signatur verwendete Algorithmus und Schlüssel vor dem Codieren oder Decodieren dieser Struktur bekannt sind.

4.8. Constants (Konstanten)

Typisierte Konstanten können für Spezifikationszwecke definiert werden, indem ein Symbol des gewünschten Typs deklariert und ihm Werte zugewiesen werden.

Unterspezifizierte Typen (opaque, Vektoren variabler Länge und Strukturen, die opaque enthalten) können keine Werte zugewiesen bekommen. Keine Felder einer mehrelementigen Struktur oder eines Vektors können weggelassen werden.

Zum Beispiel:

struct {
uint8 f1;
uint8 f2;
} Example1;

Example1 ex1 = {1, 4}; /* assigns f1 = 1, f2 = 4 */