Skip to contentSkip to conversation

revocation

PATCH In review Davide Brini 5 participants 10 messages Apr 22, 2010 – Apr 23, 2010 3 messages not in the archive

lore
Share & tools
lore

A message that is not in the archive (sent off-list or not yet received): [email protected]...

A message that is not in the archive (sent off-list or not yet received): [email protected]...

A message that is not in the archive (sent off-list or not yet received): [email protected]...

#3 lore raw

PATCH 1/1 revocation

(moving to -devel as this is obviously pertains there more than -users)

Quoted 16 lines · Davide Brini
Quoted 16 lines
Quoted 6 lines

RFC 5280 says that "certificate users MUST be able to handle serialNumber values up to 20 octets", so a 16-byte value looks valid to me. I would say (without looking at the code) that it's OpenVPN that can't handle it and gives -1 to the user. However I'm performing some further
verification, and will report back if I find something.

Please do! However, beware that this RFC separates 'certificate serial number' and 'serial number'. Which is probably why the confusion is all present. The specification also uses the ASN1 INTEGER definition, AFAICU. But I don't recall the expected size of those integer values, but I believe it is 32bit in OpenSSL (that's what ASN1_INTEGER_get() returns). Going to the extreme, I don't think that ASN1 sets any boundary on the min/max values of INTEGER - which makes it quite interesting.

Quoted 8 lines
Quoted 8 lines

You might want to try to patch ssl.c with a better suitable type in ssl.c. Have a look around line 850. You could try to use 'unsigned long int' and change the %d to %ld, or whatever the compiler accepts for unsigned long int. From what I can understand, ASN1_INTEGER_get() should be correct.

long is usually 32bit (4 bytes). Making it unsigned gives you the 32th
bit.

Ok, so I'm looking into the sources of openssl. ASN1_INTEGER is a typedef for "struct asn1_string_st"; and the comments in the code for that structure are quite explicit:

-------------------------------------------------
/* This is the base type that holds just about everything :-) */ typedef struct asn1_string_st

    {
    int length;
    int type;
    unsigned char *data;
    /* The value of the following field depends on the type being
     * held.  It is mostly being used for BIT_STRING so if the
     * input data has a non-zero 'unused bits' value, it will be
     * handled correctly */
    long flags;
    } ASN1_STRING;

-------------------------------------------------

So an ASN1_INTEGER in OpenSSL can really contain arbitrarily long values (many other types are typedef'd to "struct asn1_string_st").

More to the point, when one runs "openssl x509 -text", it turns out that it explicitly checks for the serial number's length. If it's up to 4 bytes, it's printed like

        Serial Number: 927650371 (0x374ad243)

if instead it's longer, it's printed in the form

        Serial Number:
            05:40:69:bf:0f:c2:4f:f4:b0:05:ee:95:3f:18:71:17

(crypto/asn1/t_x509.c, line 138 and following), which confirms my initial guess. So in fact it's just a different output format, and the serial number is just an (almost) arbitrarily large number. Why would a CA choose such a serial number? I don't know, it's their business but we should be able to handle it.

Sorry for the - somewhat - OT introduction, but I felt it was necessary to clarify the puzzling output I was seeing earlier. Now let's turn to OpenVPN.

ssl.c, line 789:

-------------------------------------------------
/* export serial number as environmental variable */
const int serial = (int) ASN1_INTEGER_get (X509_get_serialNumber (ctx-

Quoted 1 line

current_cert));

-------------------------------------------------

Here is ASN1_INTEGER_get (from openssl's crypto/asn1/a_int.c, line 376):

-------------------------------------------------
long ASN1_INTEGER_get(ASN1_INTEGER *a)

    {
    int neg=0,i;
    long r=0;

    if (a == NULL) return(0L);
    i=a->type;
    if (i == V_ASN1_NEG_INTEGER)
        neg=1;
    else if (i != V_ASN1_INTEGER)
        return -1;

    if (a->length > (int)sizeof(long))
        {
        /* hmm... a bit ugly */
        return(0xffffffffL);
        }
    if (a->data == NULL)
        return 0;

    for (i=0; i<a->length; i++)
        {
        r<<=8;
        r|=(unsigned char)a->data[i];
        }
    if (neg) r= -r;
    return(r);
    }

-------------------------------------------------

and that explains why we get -1 with big serial numbers on 32 bit.

Now, it seems that the "right" fix for OpenVPN would be to use a memory BIO object as explained in the docs. That way, the serial number is "printed" to the BIO object and can be read back into an array of characters. In that form, it could be exported into the environment using setenv_str() instead of setenv_int() (environment variables are all strings anyway). Also, due to the way i2a_ASN1_INTEGER() prints stuff, it will be a long hexadecimal string, which integrates nicely with OCSP, so it is ready to use when doing OCSP queries (by doing '-serial "0x${tls_serial_0}"' etc.)

So here's a patch that does exactly that; it's quite straightforward in fact. I have tested it on two Gentoo boxes and it seems to work fine, although of course more testing never hurts; here's a dump of the serial numbers in a full certificate chain:

054069bf0fc24ff4b005ee953f187117
0851F959814145CABDE124E232C9D20E
428740A5
374AD243

I produced that with a simple tls-verify script that dumps $tls_serial_{0,1,2,3}.

Well, enough blathering (sorry!); here's the patch. It's also attached separately in the likely case that line wrapping mess up the inline version.

Description: correctly manage serial numbers of any size (as long as OpenSSL can manage them) to be exported as environment variables for children scripts.

ssl.c 2010-04-22 21:26:45.000000000 +0100 renamed from ssl.c 2010-02-28 22:17:45.000000000 +0000 +0 -1
@@ -788,9 +788,25 @@ verify_callback (int preverify_ok, X509_
 
   /* export serial number as environmental variable */
   {
-    const int serial = (int) ASN1_INTEGER_get (X509_get_serialNumber (ctx-
Quoted 1 line

current_cert));

+    BIO *bio = NULL;
+    char serial[1024] = {0};
+    int n;
+
+    if ((bio = BIO_new (BIO_s_mem ())) == NULL) {
+      msg (M_FATAL, "CALLBACK: Cannot create BIO");
+    }
+    /* "prints" the serial number onto the BIO */
+    i2a_ASN1_INTEGER(bio, X509_get_serialNumber (ctx->current_cert));
+    n = BIO_read (bio, serial, sizeof (serial)-1);
+    if (n < 0) {

+ serial[0] = '\x0';
+ }
+ else {
+ serial[n] = 0;
+ }
+
openvpn_snprintf (envname, sizeof(envname), "tls_serial_%d", ctx-

Quoted 1 line

error_depth);

- setenv_int (opt->es, envname, serial);
+ setenv_str (opt->es, envname, serial);
}

/* export current untrusted IP */

Signature · 1 line
D.
ssl.c +18 -2
@@ -788,9 +788,25 @@ verify_callback (int preverify_ok, X509_
 
   /* export serial number as environmental variable */
   {
-    const int serial = (int) ASN1_INTEGER_get (X509_get_serialNumber (ctx->current_cert));
+    BIO *bio = NULL;
+    char serial[1024] = {0};
+    int n;
+
+    if ((bio = BIO_new (BIO_s_mem ())) == NULL) {
+      msg (M_FATAL, "CALLBACK: Cannot create BIO");
+    }
+    /* "prints" the serial number onto the BIO */
+    i2a_ASN1_INTEGER(bio, X509_get_serialNumber (ctx->current_cert));
+    n = BIO_read (bio, serial, sizeof (serial)-1);
+    if (n < 0) {
+      serial[0] = '\x0';
+    }
+    else {
+      serial[n] = 0;
+    }
+    
     openvpn_snprintf (envname, sizeof(envname), "tls_serial_%d", ctx->error_depth);
-    setenv_int (opt->es, envname, serial);
+    setenv_str (opt->es, envname, serial);
   }
 
   /* export current untrusted IP */
#4 lore raw
Quoted 1 line · Davide Brini

(moving to -devel as this is obviously pertains there more than -users)

Sorry, too quick! I posted an incomplete version of the patch. The attached one should be better.

The only doubt I have is about error handling; in this case, if the allocation of the BIO fails, an error message is logged and nothing is done. Is this the right thing to do?

Signature · 1 line
D.
ssl.c +20 -2
@@ -788,9 +788,27 @@ verify_callback (int preverify_ok, X509_
 
   /* export serial number as environmental variable */
   {
-    const int serial = (int) ASN1_INTEGER_get (X509_get_serialNumber (ctx->current_cert));
+    BIO *bio = NULL;
+    char serial[1024] = {0};
+    int n;
+
+    if ((bio = BIO_new (BIO_s_mem ())) == NULL) {
+      msg (M_FATAL, "CALLBACK: Cannot create BIO");
+    }
+    else {
+      /* "prints" the serial number onto the BIO */
+      i2a_ASN1_INTEGER(bio, X509_get_serialNumber (ctx->current_cert));
+      n = BIO_read (bio, serial, sizeof (serial)-1);
+      if (n < 0) {
+        serial[0] = '\x0';
+      }
+      else {
+        serial[n] = 0;
+      }
+    
     openvpn_snprintf (envname, sizeof(envname), "tls_serial_%d", ctx->error_depth);
-    setenv_int (opt->es, envname, serial);
+      setenv_str (opt->es, envname, serial);
+    }
   }
 
   /* export current untrusted IP */
#5 lore raw
Quoted 10 lines · Davide Brini
Quoted 2 lines · Davide Brini

(moving to -devel as this is obviously pertains there more than -users)

Sorry, too quick! I posted an incomplete version of the patch. The attached one should be better.

The only doubt I have is about error handling; in this case, if the allocation of the BIO fails, an error message is logged and nothing is done. Is this the right thing to do?

I don't know if a FATAL error is such a good thing - not being able to set an env var does not warrant a server crash , I'd say. Also, you're not freeing the BIO as far as I can tell.

apart from that: nice patch !

cheers,

JJK

#6 lore raw
Quoted 6 lines · Jan Just Keijser
Quoted 3 lines

The only doubt I have is about error handling; in this case, if the allocation of the BIO fails, an error message is logged and nothing is done. Is this the right thing to do?

I don't know if a FATAL error is such a good thing - not being able to set an env var does not warrant a server crash , I'd say.

Ok, thanks. It's now a warning (with a clearer message as well).

Quoted 1 line

Also, you're not freeing the BIO as far as I can tell.

Right, well spotted! The variables are in a block which makes them local, but of course the memory pointed to by bio must be freed. Thanks for the heads-up.

Please find attached the revised patch.

Signature · 1 line
D.
ssl.c +21 -2
@@ -788,9 +788,28 @@ verify_callback (int preverify_ok, X509_
 
   /* export serial number as environmental variable */
   {
-    const int serial = (int) ASN1_INTEGER_get (X509_get_serialNumber (ctx->current_cert));
+    BIO *bio = NULL;
+    char serial[1024] = {0};
+    int n;
+
+    if ((bio = BIO_new (BIO_s_mem ())) == NULL) {
+      msg (M_WARN, "CALLBACK: Cannot create BIO (for tls_serial_%d)", ctx->error_depth);
+    }
+    else {
+      /* "prints" the serial number onto the BIO */
+      i2a_ASN1_INTEGER(bio, X509_get_serialNumber (ctx->current_cert));
+      n = BIO_read (bio, serial, sizeof (serial)-1);
+      if (n < 0) {
+        serial[0] = '\x0';
+      }
+      else {
+        serial[n] = 0;
+      }
+    
     openvpn_snprintf (envname, sizeof(envname), "tls_serial_%d", ctx->error_depth);
-    setenv_int (opt->es, envname, serial);
+      setenv_str (opt->es, envname, serial);
+      BIO_free(bio);
+    }
   }
 
   /* export current untrusted IP */
#7 lore raw

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi Davide!

Thanks a lot! This is looking pretty well, I have a few comments, though.

ssl.c verify_callback (int preverify_ok, X509_ · line 792

Quoted 10 lines · Davide Brini
ssl.c +2 -1
@@ -788,9 +788,28 @@ verify_callback (int preverify_ok, X509_
 
   /* export serial number as environmental variable */
   {
-    const int serial = (int) ASN1_INTEGER_get (X509_get_serialNumber (ctx->current_cert));
+    BIO *bio = NULL;
+    char serial[1024] = {0};

Do we need 1024 bytes? RFC5280 indicates 20 octets (read: bytes) as the expected maximum size. To print 20 octets as hex, you need 40 bytes, and with delimiters for each octet it will be 59 bytes.

The initialisation is also rather bogus, as it will only put \0 in the first byte, not the complete string. It's better to instead use CLEAR (serial) afterwards (which is a wrapper for memset()).

Quoted 5 lines
ssl.c +5 -0
+    int n;
+
+    if ((bio = BIO_new (BIO_s_mem ())) == NULL) {
+      msg (M_WARN, "CALLBACK: Cannot create BIO (for tls_serial_%d)", ctx->error_depth);
+    }

ssl.c verify_callback (int preverify_ok, X509_ · line 807

Quoted 10 lines
ssl.c +10 -0
+    else {
+      /* "prints" the serial number onto the BIO */
+      i2a_ASN1_INTEGER(bio, X509_get_serialNumber (ctx->current_cert));
+      n = BIO_read (bio, serial, sizeof (serial)-1);
+      if (n < 0) {
+        serial[0] = '\x0';
+      }
+      else {
+        serial[n] = 0;
+      }

This if-block is really not needed if you memset() serial first, unless there are known issues with OpenSSL that it might place garbage after the n'th byte. I don't think that is the case, though.

It should also be some error checking here as well.

Another thing is if BIO_read() returns 0, the serial number should not result in an empty string. I'm wondering if it would be appropriate for us to return "0" zero as a string) or "-1" as a string to the environmental variable. The former implementation would probably return 0, but I'm wondering if -1 would be even better to indicate an error.

Maybe even consider to check the return value of i2a_ASN1_INTEGER() as well.

ssl.c verify_callback (int preverify_ok, X509_ · line 813

Quoted 6 lines
ssl.c +3 -1
     openvpn_snprintf (envname, sizeof(envname), "tls_serial_%d", ctx->error_depth);
-    setenv_int (opt->es, envname, serial);
+      setenv_str (opt->es, envname, serial);
+      BIO_free(bio);
+    }
   }

kind regards,

David Sommerseth
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org/

iEYEARECAAYFAkvRcr4ACgkQDC186MBRfrqraQCggpyJ+DKcmzyK1uhmodj1cLIT LEMAniCuXp1HZ5WM8lhrGZ9F+kyDrka7
=kgsa
-----END PGP SIGNATURE-----

#8 lore raw
Quoted 5 lines · On Friday 23 Apr 2010 11:13:21 David Sommerseth wrote:
Quoted 4 lines · Davide Brini
ssl.c +0 -0
@@ -788,9 +788,28 @@ verify_callback (int preverify_ok, X509_
 
Quoted 9 lines

ssl.c verify_callback (int preverify_ok, X509_ · line 792

Quoted 5 lines
ssl.c +1 -1
   /* export serial number as environmental variable */
   {
-    const int serial = (int) ASN1_INTEGER_get (X509_get_serialNumber
 (ctx->current_cert)); +    BIO *bio = NULL;
+    char serial[1024] = {0};

Do we need 1024 bytes? RFC5280 indicates 20 octets (read: bytes) as the expected maximum size. To print 20 octets as hex, you need 40 bytes, and with delimiters for each octet it will be 59 bytes.

Right. Even if the variable is short-lived, and even anticipating future needs...I suppose 100 bytes will do for now.

Quoted 3 lines

The initialisation is also rather bogus, as it will only put \0 in the first byte, not the complete string. It's better to instead use CLEAR (serial) afterwards (which is a wrapper for memset()).

Ok, will do that. But anyway, since it's meant to be a string, I thought that initializing it to '\0' would mean "empty string".

Quoted 16 lines
Quoted 5 lines
ssl.c +4 -0
+    int n;
+
+    if ((bio = BIO_new (BIO_s_mem ())) == NULL) {
+      msg (M_WARN, "CALLBACK: Cannot create BIO (for tls_serial_%d)",
 ctx->error_depth); +    }

ssl.c verify_callback (int preverify_ok, X509_ · line 807

Quoted 10 lines
ssl.c +10 -0
+    else {
+      /* "prints" the serial number onto the BIO */
+      i2a_ASN1_INTEGER(bio, X509_get_serialNumber (ctx->current_cert));
+      n = BIO_read (bio, serial, sizeof (serial)-1);
+      if (n < 0) {
+        serial[0] = '\x0';
+      }
+      else {
+        serial[n] = 0;
+      }
Quoted 3 lines

This if-block is really not needed if you memset() serial first, unless there are known issues with OpenSSL that it might place garbage after the n'th byte. I don't think that is the case, though.

Neither do I, but I thought that "better safe then sorry". And btw, I saw that other OpenVPN code is /already/ doing the same thing (in fact, I wrote my code modeled after that): see pkcs11.c, from line 935.

Quoted 7 lines

It should also be some error checking here as well.

Another thing is if BIO_read() returns 0, the serial number should not result in an empty string. I'm wondering if it would be appropriate for us to return "0" zero as a string) or "-1" as a string to the environmental variable. The former implementation would probably return 0, but I'm wondering if -1 would be even better to indicate an error.

Unfortunately, both 0 and negative numbers are all possible serial numbers (although they are now forbidden and not found in practice, but the openssl routines DO handle them). The RFC also says

"CAs MUST force the serialNumber to be a non-negative integer.
...
Note: Non-conforming CAs may issue certificates with serial numbers that are negative or zero. Certificate users SHOULD be prepared to gracefully handle such certificates."

So I maintain that an empty string is the right thing to do. It will then be up to whatever code uses the environment variable to check that it's not empty (or negative, etc.).

Quoted 2 lines

Maybe even consider to check the return value of i2a_ASN1_INTEGER() as well.

See above. Since the other code fragment doesn't do that, neither did I. But right, I will add a check and a warning if it returns a value < 0, which will result in an empty string put into the environment variable.

Let me know what you think, and I'll send a revised patch (yes, with summary, Signed-off-by and all that; I'm learning, slowly).
Thanks for reviewing!

Signature · 1 line
D.
#9 lore raw

A minor nitpick, but...

Quoted 11 lines · Davide Brini
Quoted 10 lines · On Friday 23 Apr 2010 11:13:21 David Sommerseth wrote:
Quoted 9 lines · Davide Brini
ssl.c +1 -1
@@ -788,9 +788,28 @@ verify_callback (int preverify_ok, X509_
 
   /* export serial number as environmental variable */
   {
-    const int serial = (int) ASN1_INTEGER_get (X509_get_serialNumber
 (ctx->current_cert)); +    BIO *bio = NULL;
+    char serial[1024] = {0};

[...]

Quoted 6 lines
Quoted 3 lines

The initialisation is also rather bogus, as it will only put \0 in the first byte, not the complete string. It's better to instead use CLEAR (serial) afterwards (which is a wrapper for memset()).

Ok, will do that. But anyway, since it's meant to be a string, I thought that initializing it to '\0' would mean "empty string".

The initialization will, in fact, set all bytes of "serial" to zero. Quoting the C standard (well, C89 draft is the closest I have):

"If there are fewer initializers in a list than there are members of an aggregate, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration."

Which means filling with zeroes:

"If an object that has static storage duration is not initialized explicitly, it is initialized implicitly as if every member that has arithmetic type were assigned 0 and [--]"

I.e. you cannot "partially" initialize an object: the array elements not mentioned in the initializer are implicitly initialized to zero. You only get indeterminate values by omitting the initializer completely. I guess it's concievable some compiler could get this wrong, though...

Signature · 2 lines
Heikki Kallasjoki
[email protected]...
#10 lore raw

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Quoted 9 lines · Heikki Kallasjoki

A minor nitpick, but...

Quoted 6 lines · Davide Brini
Quoted 5 lines · On Friday 23 Apr 2010 11:13:21 David Sommerseth wrote:
Quoted 4 lines · Davide Brini
ssl.c +0 -0
@@ -788,9 +788,28 @@ verify_callback (int preverify_ok, X509_
 
Quoted 12 lines
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
ssl.c +1 -1
   /* export serial number as environmental variable */
   {
-    const int serial = (int) ASN1_INTEGER_get (X509_get_serialNumber
 (ctx->current_cert)); +    BIO *bio = NULL;
+    char serial[1024] = {0};

[...]

Quoted 6 lines
Quoted 3 lines

The initialisation is also rather bogus, as it will only put \0 in the first byte, not the complete string. It's better to instead use CLEAR (serial) afterwards (which is a wrapper for memset()).

Ok, will do that. But anyway, since it's meant to be a string, I thought that initializing it to '\0' would mean "empty string".

Yes, that is kind of right. The '\0' do give the needed NULL termination which tells most string functions when to stop reading further. But if you do printf("%s", serial+1) you'll "escape" that single NULL termination. If that memory region is not cleared, you're going to get a lot of garbage out.

Quoted 6 lines

The initialization will, in fact, set all bytes of "serial" to zero. Quoting the C standard (well, C89 draft is the closest I have):

"If there are fewer initializers in a list than there are members of an aggregate, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration."

On modern systems, this is most often the case.

Quoted 10 lines

Which means filling with zeroes:

"If an object that has static storage duration is not initialized explicitly, it is initialized implicitly as if every member that has arithmetic type were assigned 0 and [--]"

I.e. you cannot "partially" initialize an object: the array elements not mentioned in the initializer are implicitly initialized to zero. You only get indeterminate values by omitting the initializer completely. I guess it's concievable some compiler could get this wrong, though...

That is as my main concern. Most GCC compilers nowadays, do give 0'ed values on statically allocated variables, at least on Linux kernel. IIRC, those local variables are allocated on the stack, which gets the memory allocation via the kernel - so it's also up to the kernel if it returns a zero'd memory buffer. But this code might be built on less "clever" systems as well.

And the OpenVPN code uses CLEAR() and even memset() a lot of places throughout the code. This is merely to align with the coding "standard" in OpenVPN, and to follow more a defensive coding style.

kind regards,

David Sommerseth
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org/

iEYEARECAAYFAkvRkvQACgkQDC186MBRfrrU/QCcCk/JxpcAxtb//ZGD1vSGNEwi H/AAn0tMN5Ov3kydEDStoif7p6WKBqCT
=ONp1
-----END PGP SIGNATURE-----

#12 lore raw
Quoted 10 lines · On Friday 23 Apr 2010 00:34:38 Peter Stuge wrote:
Quoted 2 lines · Davide Brini

the serial number is just an (almost) arbitrarily large number. Why would a CA choose such a serial number?

In order to avoid a chosen-prefix collision that works among other things by predicting the serial number of certificates generated by the CA.

http://www.win.tue.nl/hashclash/rogue-ca/
https://documents.epfl.ch/users/l/le/lenstra/public/papers/lat.pdf

Ah, that is interesting. I haven't seen an MD5 certificate in a long time, but I guess being safe doesn't hurt, as sooner or later SHA might become vulnerable as well (and people should start using the newer, longer SHA-256,etc. hashes anyway - not just for certificates).

Thanks!

Signature · 1 line
D.