lib: Make async_sock includable on its own
[kai/samba.git] / lib / util / asn1.c
index a2665ed5397ddabe5adb5b241190f2bc7f870964..70637a3e065c48d5063a95d22bea392559f35622 100644 (file)
@@ -205,32 +205,46 @@ bool asn1_write_Integer(struct asn1_data *data, int i)
        return asn1_pop_tag(data);
 }
 
-bool ber_write_OID_String(DATA_BLOB *blob, const char *OID)
+/* write a BIT STRING */
+bool asn1_write_BitString(struct asn1_data *data, const void *p, size_t length, uint8_t padding)
 {
-       uint_t v, v2;
+       if (!asn1_push_tag(data, ASN1_BIT_STRING)) return false;
+       if (!asn1_write_uint8(data, padding)) return false;
+       if (!asn1_write(data, p, length)) return false;
+       return asn1_pop_tag(data);
+}
+
+bool ber_write_OID_String(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, const char *OID)
+{
+       unsigned int v, v2;
        const char *p = (const char *)OID;
        char *newp;
        int i;
 
+       if (!isdigit(*p)) return false;
        v = strtoul(p, &newp, 10);
        if (newp[0] != '.') return false;
        p = newp + 1;
 
+       if (!isdigit(*p)) return false;
        v2 = strtoul(p, &newp, 10);
        if (newp[0] != '.') return false;
        p = newp + 1;
 
        /*the ber representation can't use more space then the string one */
-       *blob = data_blob(NULL, strlen(OID));
+       *blob = data_blob_talloc(mem_ctx, NULL, strlen(OID));
        if (!blob->data) return false;
 
        blob->data[0] = 40*v + v2;
 
        i = 1;
        while (*p) {
+               if (!isdigit(*p)) return false;
                v = strtoul(p, &newp, 10);
                if (newp[0] == '.') {
                        p = newp + 1;
+                       /* check for empty last component */
+                       if (!*p) return false;
                } else if (newp[0] == '\0') {
                        p = newp;
                } else {
@@ -249,6 +263,45 @@ bool ber_write_OID_String(DATA_BLOB *blob, const char *OID)
        return true;
 }
 
+/**
+ * Serialize partial OID string.
+ * Partial OIDs are in the form:
+ *   1:2.5.6:0x81
+ *   1:2.5.6:0x8182
+ */
+bool ber_write_partial_OID_String(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, const char *partial_oid)
+{
+       TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+       char *oid = talloc_strdup(tmp_ctx, partial_oid);
+       char *p;
+
+       /* truncate partial part so ber_write_OID_String() works */
+       p = strchr(oid, ':');
+       if (p) {
+               *p = '\0';
+               p++;
+       }
+
+       if (!ber_write_OID_String(mem_ctx, blob, oid)) {
+               talloc_free(tmp_ctx);
+               return false;
+       }
+
+       /* Add partially encoded sub-identifier */
+       if (p) {
+               DATA_BLOB tmp_blob = strhex_to_data_blob(tmp_ctx, p);
+               if (!data_blob_append(mem_ctx, blob, tmp_blob.data,
+                                     tmp_blob.length)) {
+                       talloc_free(tmp_ctx);
+                       return false;
+               }
+       }
+
+       talloc_free(tmp_ctx);
+
+       return true;
+}
+
 /* write an object ID to a ASN1 buffer */
 bool asn1_write_OID(struct asn1_data *data, const char *OID)
 {
@@ -256,12 +309,13 @@ bool asn1_write_OID(struct asn1_data *data, const char *OID)
 
        if (!asn1_push_tag(data, ASN1_OID)) return false;
 
-       if (!ber_write_OID_String(&blob, OID)) {
+       if (!ber_write_OID_String(NULL, &blob, OID)) {
                data->has_error = true;
                return false;
        }
 
        if (!asn1_write(data, blob.data, blob.length)) {
+               data_blob_free(&blob);
                data->has_error = true;
                return false;
        }
@@ -332,6 +386,29 @@ bool asn1_read_BOOLEAN(struct asn1_data *data, bool *v)
        return !data->has_error;
 }
 
+/* write a BOOLEAN in a simple context */
+bool asn1_write_BOOLEAN_context(struct asn1_data *data, bool v, int context)
+{
+       asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(context));
+       asn1_write_uint8(data, v ? 0xFF : 0);
+       asn1_pop_tag(data);
+       return !data->has_error;
+}
+
+bool asn1_read_BOOLEAN_context(struct asn1_data *data, bool *v, int context)
+{
+       uint8_t tmp = 0;
+       asn1_start_tag(data, ASN1_CONTEXT_SIMPLE(context));
+       asn1_read_uint8(data, &tmp);
+       if (tmp == 0xFF) {
+               *v = true;
+       } else {
+               *v = false;
+       }
+       asn1_end_tag(data);
+       return !data->has_error;
+}
+
 /* check a BOOLEAN */
 bool asn1_check_BOOLEAN(struct asn1_data *data, bool v)
 {
@@ -421,6 +498,77 @@ bool asn1_peek_tag(struct asn1_data *data, uint8_t tag)
        return (b == tag);
 }
 
+/*
+ * just get the needed size the tag would consume
+ */
+bool asn1_peek_tag_needed_size(struct asn1_data *data, uint8_t tag, size_t *size)
+{
+       off_t start_ofs = data->ofs;
+       uint8_t b;
+       size_t taglen = 0;
+
+       if (data->has_error) {
+               return false;
+       }
+
+       if (!asn1_read_uint8(data, &b)) {
+               data->ofs = start_ofs;
+               data->has_error = false;
+               return false;
+       }
+
+       if (b != tag) {
+               data->ofs = start_ofs;
+               data->has_error = false;
+               return false;
+       }
+
+       if (!asn1_read_uint8(data, &b)) {
+               data->ofs = start_ofs;
+               data->has_error = false;
+               return false;
+       }
+
+       if (b & 0x80) {
+               int n = b & 0x7f;
+               if (!asn1_read_uint8(data, &b)) {
+                       data->ofs = start_ofs;
+                       data->has_error = false;
+                       return false;
+               }
+               if (n > 4) {
+                       /*
+                        * We should not allow more than 4 bytes
+                        * for the encoding of the tag length.
+                        *
+                        * Otherwise we'd overflow the taglen
+                        * variable on 32 bit systems.
+                        */
+                       data->ofs = start_ofs;
+                       data->has_error = false;
+                       return false;
+               }
+               taglen = b;
+               while (n > 1) {
+                       if (!asn1_read_uint8(data, &b)) {
+                               data->ofs = start_ofs;
+                               data->has_error = false;
+                               return false;
+                       }
+                       taglen = (taglen << 8) | b;
+                       n--;
+               }
+       } else {
+               taglen = b;
+       }
+
+       *size = (data->ofs - start_ofs) + taglen;
+
+       data->ofs = start_ofs;
+       data->has_error = false;
+       return true;
+}
+
 /* start reading a nested asn1 structure */
 bool asn1_start_tag(struct asn1_data *data, uint8_t tag)
 {
@@ -510,12 +658,17 @@ int asn1_tag_remaining(struct asn1_data *data)
        return remaining;
 }
 
-/* read an object ID from a data blob */
-bool ber_read_OID_String(TALLOC_CTX *mem_ctx, DATA_BLOB blob, const char **OID)
+/**
+ * Internal implementation for reading binary OIDs
+ * Reading is done as far in the buffer as valid OID
+ * till buffer ends or not valid sub-identifier is found.
+ */
+static bool _ber_read_OID_String_impl(TALLOC_CTX *mem_ctx, DATA_BLOB blob,
+                                     char **OID, size_t *bytes_eaten)
 {
        int i;
        uint8_t *b;
-       uint_t v;
+       unsigned int v;
        char *tmp_oid = NULL;
 
        if (blob.length < 2) return false;
@@ -527,29 +680,78 @@ bool ber_read_OID_String(TALLOC_CTX *mem_ctx, DATA_BLOB blob, const char **OID)
        tmp_oid = talloc_asprintf_append_buffer(tmp_oid, ".%u",  b[0]%40);
        if (!tmp_oid) goto nomem;
 
+       if (bytes_eaten != NULL) {
+               *bytes_eaten = 0;
+       }
+
        for(i = 1, v = 0; i < blob.length; i++) {
                v = (v<<7) | (b[i]&0x7f);
                if ( ! (b[i] & 0x80)) {
                        tmp_oid = talloc_asprintf_append_buffer(tmp_oid, ".%u",  v);
                        v = 0;
+                       if (bytes_eaten)
+                               *bytes_eaten = i+1;
                }
                if (!tmp_oid) goto nomem;
        }
 
-       if (v != 0) {
-               talloc_free(tmp_oid);
+       *OID = tmp_oid;
+       return true;
+
+nomem:
+       return false;
+}
+
+/* read an object ID from a data blob */
+bool ber_read_OID_String(TALLOC_CTX *mem_ctx, DATA_BLOB blob, char **OID)
+{
+       size_t bytes_eaten;
+
+       if (!_ber_read_OID_String_impl(mem_ctx, blob, OID, &bytes_eaten))
+               return false;
+
+       return (bytes_eaten == blob.length);
+}
+
+/**
+ * Deserialize partial OID string.
+ * Partial OIDs are in the form:
+ *   1:2.5.6:0x81
+ *   1:2.5.6:0x8182
+ */
+bool ber_read_partial_OID_String(TALLOC_CTX *mem_ctx, DATA_BLOB blob,
+                                char **partial_oid)
+{
+       size_t bytes_left;
+       size_t bytes_eaten;
+       char *identifier = NULL;
+       char *tmp_oid = NULL;
+
+       if (!_ber_read_OID_String_impl(mem_ctx, blob, &tmp_oid, &bytes_eaten))
                return false;
+
+       if (bytes_eaten < blob.length) {
+               bytes_left = blob.length - bytes_eaten;
+               identifier = hex_encode_talloc(mem_ctx, &blob.data[bytes_eaten], bytes_left);
+               if (!identifier)        goto nomem;
+
+               *partial_oid = talloc_asprintf_append_buffer(tmp_oid, ":0x%s", identifier);
+               if (!*partial_oid)      goto nomem;
+               TALLOC_FREE(identifier);
+       } else {
+               *partial_oid = tmp_oid;
        }
 
-       *OID = tmp_oid;
        return true;
 
-nomem: 
+nomem:
+       TALLOC_FREE(identifier);
+       TALLOC_FREE(tmp_oid);
        return false;
 }
 
 /* read an object ID from a ASN1 buffer */
-bool asn1_read_OID(struct asn1_data *data, TALLOC_CTX *mem_ctx, const char **OID)
+bool asn1_read_OID(struct asn1_data *data, TALLOC_CTX *mem_ctx, char **OID)
 {
        DATA_BLOB blob;
        int len;
@@ -588,16 +790,16 @@ bool asn1_read_OID(struct asn1_data *data, TALLOC_CTX *mem_ctx, const char **OID
 /* check that the next object ID is correct */
 bool asn1_check_OID(struct asn1_data *data, const char *OID)
 {
-       const char *id;
+       char *id;
 
        if (!asn1_read_OID(data, data, &id)) return false;
 
        if (strcmp(id, OID) != 0) {
-               talloc_free(discard_const(id));
+               talloc_free(id);
                data->has_error = true;
                return false;
        }
-       talloc_free(discard_const(id));
+       talloc_free(id);
        return true;
 }
 
@@ -642,7 +844,7 @@ bool asn1_read_OctetString(struct asn1_data *data, TALLOC_CTX *mem_ctx, DATA_BLO
                return false;
        }
        *blob = data_blob_talloc(mem_ctx, NULL, len+1);
-       if (!blob->data) {
+       if (!blob->data || blob->length < len) {
                data->has_error = true;
                return false;
        }
@@ -653,7 +855,7 @@ bool asn1_read_OctetString(struct asn1_data *data, TALLOC_CTX *mem_ctx, DATA_BLO
        
        if (data->has_error) {
                data_blob_free(blob);
-               *blob = data_blob(NULL, 0);
+               *blob = data_blob_null;
                return false;
        }
        return true;
@@ -683,10 +885,19 @@ bool asn1_read_ContextSimple(struct asn1_data *data, uint8_t num, DATA_BLOB *blo
 bool asn1_read_implicit_Integer(struct asn1_data *data, int *i)
 {
        uint8_t b;
+       bool first_byte = true;
        *i = 0;
 
        while (!data->has_error && asn1_tag_remaining(data)>0) {
                if (!asn1_read_uint8(data, &b)) return false;
+               if (first_byte) {
+                       if (b & 0x80) {
+                               /* Number is negative.
+                                  Set i to -1 for sign extend. */
+                               *i = -1;
+                       }
+                       first_byte = false;
+               }
                *i = (*i << 8) + b;
        }
        return !data->has_error;        
@@ -703,6 +914,39 @@ bool asn1_read_Integer(struct asn1_data *data, int *i)
        return asn1_end_tag(data);      
 }
 
+/* read a BIT STRING */
+bool asn1_read_BitString(struct asn1_data *data, TALLOC_CTX *mem_ctx, DATA_BLOB *blob, uint8_t *padding)
+{
+       int len;
+       ZERO_STRUCTP(blob);
+       if (!asn1_start_tag(data, ASN1_BIT_STRING)) return false;
+       len = asn1_tag_remaining(data);
+       if (len < 0) {
+               data->has_error = true;
+               return false;
+       }
+       if (!asn1_read_uint8(data, padding)) return false;
+
+       *blob = data_blob_talloc(mem_ctx, NULL, len+1);
+       if (!blob->data || blob->length < len) {
+               data->has_error = true;
+               return false;
+       }
+       if (asn1_read(data, blob->data, len - 1)) {
+               blob->length--;
+               blob->data[len] = 0;
+               asn1_end_tag(data);
+       }
+
+       if (data->has_error) {
+               data_blob_free(blob);
+               *blob = data_blob_null;
+               *padding = 0;
+               return false;
+       }
+       return true;
+}
+
 /* read an integer */
 bool asn1_read_enumerated(struct asn1_data *data, int *v)
 {
@@ -740,6 +984,32 @@ bool asn1_write_enumerated(struct asn1_data *data, uint8_t v)
        return !data->has_error;
 }
 
+/*
+  Get us the data just written without copying
+*/
+bool asn1_blob(const struct asn1_data *asn1, DATA_BLOB *blob)
+{
+       if (asn1->has_error) {
+               return false;
+       }
+       if (asn1->nesting != NULL) {
+               return false;
+       }
+       blob->data = asn1->data;
+       blob->length = asn1->length;
+       return true;
+}
+
+/*
+  Fill in an asn1 struct without making a copy
+*/
+void asn1_load_nocopy(struct asn1_data *data, uint8_t *buf, size_t len)
+{
+       ZERO_STRUCTP(data);
+       data->data = buf;
+       data->length = len;
+}
+
 /*
   check if a ASN.1 blob is a full tag
 */
@@ -763,6 +1033,30 @@ NTSTATUS asn1_full_tag(DATA_BLOB blob, uint8_t tag, size_t *packet_size)
 
        if (size > blob.length) {
                return STATUS_MORE_ENTRIES;
+       }
+
+       *packet_size = size;
+       return NT_STATUS_OK;
+}
+
+NTSTATUS asn1_peek_full_tag(DATA_BLOB blob, uint8_t tag, size_t *packet_size)
+{
+       struct asn1_data asn1;
+       size_t size;
+       bool ok;
+
+       ZERO_STRUCT(asn1);
+       asn1.data = blob.data;
+       asn1.length = blob.length;
+
+       ok = asn1_peek_tag_needed_size(&asn1, tag, &size);
+       if (!ok) {
+               return NT_STATUS_INVALID_BUFFER_SIZE;
+       }
+
+       if (size > blob.length) {
+               *packet_size = size;
+               return STATUS_MORE_ENTRIES;
        }               
 
        *packet_size = size;