Hello,
While debugging our ASN.1/PER encoder, I noticed a problem with
the way ICV is calculated in H.225.0.
Briefly, the PER encodings change when a SEQUENCE type is further
extended, even if the new extension additions are not included in
the SEQUENCE value.
The issue prevents interoperability between software using H.225.0
version 2 and software using any version, it should be solved before
any version 2 devices are released.
Pekka Pessi
h2250-integrityCheckValue-problem.txt Pekka Pessi <pessi(a)research.nokia.com>
$Revision: 1.1 $ Nokia Research Center
June 1998
Comments on H.225.0 (1998):
Problems When Calculating integrityCheckValue
The current H.225.0 algorithm for calculating the ICV does not work well
with the PER extension mechanism. In order to interwork, each H.323
device needs a separate PER encoding function for each version of H.225.0
ASN.1 definitions.
Algorithm for Calculating integityCheckValue
According to the H.225.0 v2, integrityCheckValue is defined and
calculated as follows:
ICV ::= SEQUENCE
{
algorithmOID OBJECT IDENTIFIER, -- the algorithm used to compute the signature
icv BIT STRING -- the computed cryptographic integrity check
-- value or signature
}
integrityCheckValue - provides improved message integrity/message
authentication of the RAS messages. The cryptographically based
integrity check value is computed by the sender applying a
negotiated integrity algorithm and the secret key upon the entire
message. Prior to integrityCheckValue computation this field shall
be ignored and shall be empty. After computation, the sender puts
the computed integrity check value in the integrityCheckValue field
and transmits the message.
The above algorithm (AFAIK I have understood it correctly) assumes that
the sender and receiver can encode the PDU exactly in the same way. It is
a reasonable assumption, as PER produces only one possible (canonical)
encoding for given PDU. However, there is a serious problem: the
canonical representation changes if the ASN.1 SEQUENCE type is extended
later.
PER Encoding of an Extended ASN.1 SEQUENCE
In the PER encoding of extensions to SEQUENCE types, the encoder first
encodes the number of extensions as a normally small non-negative
integer, then a bit field with one bit for each extension followed by the
extensions itself (bit is one if the extension is present, zero if
not). The encoding of each extension is preceded by the length of their
encoding in octets.
Now, if the sender A uses following ASN.1 definition for a SEQUENCE
Foo ::= SEQUENCE
{
bar INTEGER (0..127),
...,
baz INTEGER (0..255) OPTIONAL,
integrityCheckValue ICV OPTIONAL
}
When encoding this, the length of extension bitfield is 2. However,
recipient B is using extended version of Foo like this:
Foo ::= SEQUENCE
{
bar INTEGER (0..127),
...,
baz INTEGER (0..255) OPTIONAL,
integrityCheckValue ICV OPTIONAL,
importantExtension SomeType OPTIONAL
}
B decodes the Foo, removes ICV and encodes the packet again. In the
resulting encoding, the length of extension bitfield is 3, and the ICV
that B regenerates is different that A generated and sent to B. B is not
able to interwork with systems using earlier version of the ASN.1 spec.
ASN.1 Compilers Don't Grok Unknown Extensions
There are also problems because the way some ASN.1 compilers behave.
Using our previous example, when A receives Foo with the
importantExtension field from B, A has somehow to include the value when
it is re-encoding Foo in order to calculate the ICV. However, it may be
very hard to present an value of an unknown extension to the ASN.1
encoding functions. As a possible solution the ICV calculation could be
included in the ASN.1 decoding process.
Problems with Non-OPTIONAL Extensions
Another problem with some ASN.1 compilers is inclusion of non-OPTIONAL
extensions. Let us assume that software C uses following ASN.1 definition
for Foo:
Foo ::= SEQUENCE
{
bar INTEGER (0..127),
...,
baz INTEGER (0..255) OPTIONAL,
integrityCheckValue ICV OPTIONAL,
importantExtension SomeType OPTIONAL,
criticalExtension OtherType
}
There are two kinds of problems with an extension like criticalExtension.
First, the encoder may try to ensure that all encoded PDUs conform to the
specification and signal an error when a PDU without criticalExtension is
encoded. Another problem is that the intermediate representation produced
by the ASN.1 compiler may not provide means for application to express
that criticalExtension is not present. (In other words, the produced
structure usually contains a flag telling whether an optional field is
present or not. Such flags are not included when the field is not
optional.)
The presense or absence of the OPTIONAL flag in an extension does not
change the PER encoding of the SEQUENCE. In order to avoid previously
mentioned problems, application may use a version of ASN.1 notation that
has extra OPTIONAL keyword after each extension.
Solution 1: Clarification to the PER Encoding Process
The text in PER document (X.691, 1994) is somewhat ambiguous how many
bits should be included in the extension present bitfield of SEQUENCE. To
quote verbatim: "Let the number of extension additions in the type being
encoded be "n", then a bit-field with "n" bits shall be produced for
addition to the field-list." (Is the "type being encoded" the abstract
syntax or an actual value like { bar 1, baz 2 }?) However, the 0 bits at
the end of extension present bitfield can be left out without changing
the resulting semantics: the corresponding extensions are not present. As
a result, the PER encoding does not change after a new extension is added
to the ASN.1 specification.
This solution, while leaving H.225.0 v2 protocol as it is, requires
however changes to some existing ASN.1 compilers, and in a pessimal case,
to the X.691 standard text, too.
Solution 2: Hack
The receiving application does not decode and the re-encode the PDU, but
rather removes the ICV from the encoded PDU. In practice, this requires that
application can identify PER-encoded fields within the PDU and it can
regenerate them, i.e. it has effectively the same functionality as a PER
encoder/decoder.
Solution 3: Calculating ICV Differently
The following algorithm for generating and checking the ICV makes it
possible to avoid all the previously mentioned problems. The problems are
avoided by breaking the protocol layering, the application changes directly
the PER-encoded PDU:
integrityCheckValue - provides improved message integrity/message
authentication of the RAS messages. The cryptographically based
integrity check value is computed by the sender applying a
negotiated integrity algorithm and the secret key upon the entire
message. Prior to integrityCheckValue computation an ICV with a
previously agreed magic value (or key when using MDC) will be
inserted to this field. The magic value will contain same
algorithmOID and exactly as many bits in the icv BIT STRING as the
computed value. After computation, the sender replaces the magic
value with the computed integrity check value and transmits
message. The receiver decodes message, replaces the received
integrity value with the magic value, calculates the ICV and
compares it with the received value.
NOTE: The sender or receiver can encode the ICV separately and replace
it directly within the encoded PDU, when the magic ICV and computed
ICV have exactly the same length. When replacing ICV value within
an encoded PDU, re-encoding the whole PDU can be avoided.
NOTE: The above algorithm does not require directly changing the
PER-encoded PDU. Like the current algorithm, ICV can be replaced
with a new value above presentation layer then PDU will be
re-encoded. It makes it easy, however: as the magic value is a very
unique octet-aligned bit pattern (random data of 64 bits or more,
probably), it should be easy to spot and replace it from the encoded
PDU.
NOTE: It is not advisable to use magic value as the key to MAC
algorithm.
Example code:
int icv_replace(
u_char *msg, size_t len,
u_char const *icv, u_char const *magic, size_t ilen)
{
u_char *m, *mim;
size_t i, j, ilen_in_octets, ilen_in_full_octets;
ilen_in_octets = (ilen + 7) >> 3;
ilen_in_full_octets = ilen >> 3;
/* find magic value from message */
for (m = msg, mim = NULL; m - msg < len - ilen_in_octets; m++) {
/* NOTE: the magic value/icv may not be integral number of octets */
for (i = 0; i < ilen_in_full_octets; i++) {
if (m[i] != magic[i])
break;
}
if (i == ilen_in_full_octets) {
if (mim)
return -2; /* failure: two or more magic values found */
mim = m;
}
}
if (mim == NULL) {
return -1; /* failure: no magic value found */
/* replace magic value with icv */
for (i = 0; i < ilen_in_octets; i++) {
mim[i] ^= magic[i] ^ icv[i];
}
return 0; /* success */
}
Solution 4:
Fourth alternative is to treat encoded RasMessage as octet string. IVC
can be calculated over that octet string and appended to the PDU on
separate layer. E.g., RAS PDU could be defined as follows:
RasMessage ::= CHOICE {
--8<----8<----8<----8<----8<----8<----8<----8<----8<----8<--
-- all previous RasMessage CHOICEs are here
--8<----8<----8<----8<----8<----8<----8<----8<----8<----8<--
authenticatedRasMessage SEQUENCE {
plainRasMessage OCTET STRING,
ivc IVC,
...
}
}