diff options
Diffstat (limited to 'usr/src/lib/libkmf/ber_der/common/encode.c')
-rw-r--r-- | usr/src/lib/libkmf/ber_der/common/encode.c | 744 |
1 files changed, 744 insertions, 0 deletions
diff --git a/usr/src/lib/libkmf/ber_der/common/encode.c b/usr/src/lib/libkmf/ber_der/common/encode.c new file mode 100644 index 0000000000..bf78d8e719 --- /dev/null +++ b/usr/src/lib/libkmf/ber_der/common/encode.c @@ -0,0 +1,744 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "NPL"); you may not use this file except in + * compliance with the NPL. You may obtain a copy of the NPL at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the NPL is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL + * for the specific language governing rights and limitations under the + * NPL. + * + * The Initial Developer of this code under the NPL is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998 Netscape Communications Corporation. All Rights + * Reserved. + */ + +/* + * Copyright (c) 1990 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <netinet/in.h> +#include <inttypes.h> + +#include <ber_der.h> +#include "kmfber_int.h" + +/* the following constants are used in kmfber_calc_lenlen */ + +#define LENMASK1 0xFF +#define LENMASK2 0xFFFF +#define LENMASK3 0xFFFFFF +#define LENMASK4 0xFFFFFFFF +#define _MASK 0x80 + +int +kmfber_calc_taglen(ber_tag_t tag) +{ + int i; + ber_int_t mask; + + /* find the first non-all-zero byte in the tag */ + for (i = sizeof (ber_int_t) - 1; i > 0; i--) { + mask = (LENMASK3 << (i * 8)); + /* not all zero */ + if (tag & mask) + break; + } + + return (i + 1); +} + +static int +ber_put_tag(BerElement *ber, ber_tag_t tag, int nosos) +{ + ber_int_t taglen; + ber_tag_t ntag; + + taglen = kmfber_calc_taglen(tag); + + ntag = htonl(tag); + + return (kmfber_write(ber, + ((char *) &ntag) + sizeof (ber_int_t) - taglen, + taglen, nosos)); +} + +int +kmfber_calc_lenlen(ber_int_t len) +{ + /* + * short len if it's less than 128 - one byte giving the len, + * with bit 8 0. + */ + + if (len <= 0x7F) + return (1); + + /* + * long len otherwise - one byte with bit 8 set, giving the + * length of the length, followed by the length itself. + */ + + if (len <= LENMASK1) + return (2); + if (len <= LENMASK2) + return (3); + if (len <= LENMASK3) + return (4); + + return (5); +} + +int +kmfber_put_len(BerElement *ber, ber_int_t len, int nosos) +{ + int i; + char lenlen; + ber_int_t mask, netlen; + + /* + * short len if it's less than 128 - one byte giving the len, + * with bit 8 0. + */ + if (len <= 127) { + netlen = htonl(len); + return (kmfber_write(ber, + (char *)&netlen + sizeof (ber_int_t) - 1, + 1, nosos)); + } + + /* + * long len otherwise - one byte with bit 8 set, giving the + * length of the length, followed by the length itself. + */ + + /* find the first non-all-zero byte */ + for (i = sizeof (ber_int_t) - 1; i > 0; i--) { + mask = (LENMASK1 << (i * 8)); + /* not all zero */ + if (len & mask) + break; + } + lenlen = ++i; + if (lenlen > 4) + return (-1); + lenlen |= 0x80; + + /* write the length of the length */ + if (kmfber_write(ber, &lenlen, 1, nosos) != 1) + return (-1); + + /* write the length itself */ + netlen = htonl(len); + if (kmfber_write(ber, + (char *) &netlen + (sizeof (ber_int_t) - i), i, nosos) != i) + return (-1); + + return (i + 1); +} + +static int +ber_put_int_or_enum(BerElement *ber, ber_int_t num, ber_tag_t tag) +{ + int i, sign; + ber_int_t len, lenlen, taglen, netnum, mask; + + sign = (num < 0); + + /* + * high bit is set - look for first non-all-one byte + * high bit is clear - look for first non-all-zero byte + */ + for (i = sizeof (ber_int_t) - 1; i > 0; i--) { + mask = (LENMASK1 << (i * 8)); + + if (sign) { + /* not all ones */ + if ((num & mask) != mask) + break; + } else { + /* not all zero */ + if (num & mask) + break; + } + } + + /* + * we now have the "leading byte". if the high bit on this + * byte matches the sign bit, we need to "back up" a byte. + */ + mask = (num & (_MASK << (i * 8))); + if ((mask && !sign) || (sign && !mask)) + i++; + + len = i + 1; + + if ((taglen = ber_put_tag(ber, tag, 0)) == -1) + return (-1); + + if ((lenlen = kmfber_put_len(ber, len, 0)) == -1) + return (-1); + i++; + netnum = htonl(num); + if (kmfber_write(ber, + (char *) &netnum + (sizeof (ber_int_t) - i), i, 0) == i) + /* length of tag + length + contents */ + return (taglen + lenlen + i); + + return (-1); +} + +static int +kmfber_put_enum(BerElement *ber, ber_int_t num, ber_tag_t tag) +{ + if (tag == KMFBER_DEFAULT) + tag = BER_ENUMERATED; + + return (ber_put_int_or_enum(ber, num, tag)); +} + +int +ber_put_int(BerElement *ber, ber_int_t num, ber_tag_t tag) +{ + if (tag == KMFBER_DEFAULT) + tag = BER_INTEGER; + + return (ber_put_int_or_enum(ber, num, tag)); +} + +int +ber_put_oid(BerElement *ber, struct berval *oid, ber_tag_t tag) +{ + ber_int_t taglen, lenlen, rc, len; + + if (tag == KMFBER_DEFAULT) + tag = 0x06; /* TODO: Add new OID constant to header */ + + if ((taglen = ber_put_tag(ber, tag, 0)) == -1) + return (-1); + + len = (ber_int_t)oid->bv_len; + if ((lenlen = kmfber_put_len(ber, len, 0)) == -1 || + kmfber_write(ber, oid->bv_val, oid->bv_len, 0) != + (ber_int_t)oid->bv_len) { + rc = -1; + } else { + /* return length of tag + length + contents */ + rc = taglen + lenlen + oid->bv_len; + } + return (rc); +} + +int +ber_put_big_int(BerElement *ber, ber_tag_t tag, char *data, + ber_len_t len) +{ + ber_int_t taglen, lenlen, ilen, rc; + + if (tag == KMFBER_DEFAULT) + tag = BER_INTEGER; + + if ((taglen = ber_put_tag(ber, tag, 0)) == -1) + return (-1); + + ilen = (ber_int_t)len; + if ((lenlen = kmfber_put_len(ber, ilen, 0)) == -1 || + kmfber_write(ber, data, len, 0) != (ber_int_t)len) { + rc = -1; + } else { + /* return length of tag + length + contents */ + rc = taglen + lenlen + len; + } + return (rc); +} + +static int +kmfber_put_ostring(BerElement *ber, char *str, ber_len_t len, + ber_tag_t tag) +{ + ber_int_t taglen, lenlen, ilen, rc; +#ifdef STR_TRANSLATION + int free_str; +#endif /* STR_TRANSLATION */ + + if (tag == KMFBER_DEFAULT) + tag = BER_OCTET_STRING; + + if ((taglen = ber_put_tag(ber, tag, 0)) == -1) + return (-1); + +#ifdef STR_TRANSLATION + if (len > 0 && (ber->ber_options & KMFBER_OPT_TRANSLATE_STRINGS) != 0 && + ber->ber_encode_translate_proc != NULL) { + if ((*(ber->ber_encode_translate_proc))(&str, &len, 0) + != 0) { + return (-1); + } + free_str = 1; + } else { + free_str = 0; + } +#endif /* STR_TRANSLATION */ + + /* + * Note: below is a spot where we limit ber_write + * to signed long (instead of unsigned long) + */ + ilen = (ber_int_t)len; + if ((lenlen = kmfber_put_len(ber, ilen, 0)) == -1 || + kmfber_write(ber, str, len, 0) != (ber_int_t)len) { + rc = -1; + } else { + /* return length of tag + length + contents */ + rc = taglen + lenlen + len; + } + +#ifdef STR_TRANSLATION + if (free_str) { + free(str); + } +#endif /* STR_TRANSLATION */ + + return (rc); +} + +static int +kmfber_put_string(BerElement *ber, char *str, ber_tag_t tag) +{ + return (kmfber_put_ostring(ber, str, (ber_len_t)strlen(str), tag)); +} + +static int +kmfber_put_bitstring(BerElement *ber, char *str, + ber_len_t blen /* in bits */, ber_tag_t tag) +{ + ber_int_t taglen, lenlen, len; + unsigned char unusedbits; + + if (tag == KMFBER_DEFAULT) + tag = BER_BIT_STRING; + + if ((taglen = ber_put_tag(ber, tag, 0)) == -1) + return (-1); + + len = (blen + 7) / 8; + unusedbits = (unsigned char) (len * 8 - blen); + if ((lenlen = kmfber_put_len(ber, len + 1, 0)) == -1) + return (-1); + + if (kmfber_write(ber, (char *)&unusedbits, 1, 0) != 1) + return (-1); + + if (kmfber_write(ber, str, len, 0) != len) + return (-1); + + /* return length of tag + length + unused bit count + contents */ + return (taglen + 1 + lenlen + len); +} + +static int +kmfber_put_null(BerElement *ber, ber_tag_t tag) +{ + int taglen; + + if (tag == KMFBER_DEFAULT) + tag = BER_NULL; + + if ((taglen = ber_put_tag(ber, tag, 0)) == -1) + return (-1); + + if (kmfber_put_len(ber, 0, 0) != 1) + return (-1); + + return (taglen + 1); +} + +static int +kmfber_put_boolean(BerElement *ber, int boolval, ber_tag_t tag) +{ + int taglen; + unsigned char trueval = 0xff; + unsigned char falseval = 0x00; + + if (tag == KMFBER_DEFAULT) + tag = BER_BOOLEAN; + + if ((taglen = ber_put_tag(ber, tag, 0)) == -1) + return (-1); + + if (kmfber_put_len(ber, 1, 0) != 1) + return (-1); + + if (kmfber_write(ber, (char *)(boolval ? &trueval : &falseval), 1, 0) + != 1) + return (-1); + + return (taglen + 2); +} + +#define FOUR_BYTE_LEN 5 + + +/* + * The idea here is roughly this: we maintain a stack of these Seqorset + * structures. This is pushed when we see the beginning of a new set or + * sequence. It is popped when we see the end of a set or sequence. + * Since we don't want to malloc and free these structures all the time, + * we pre-allocate a small set of them within the ber element structure. + * thus we need to spot when we've overflowed this stack and fall back to + * malloc'ing instead. + */ +static int +ber_start_seqorset(BerElement *ber, ber_tag_t tag) +{ + Seqorset *new_sos; + + /* can we fit into the local stack ? */ + if (ber->ber_sos_stack_posn < SOS_STACK_SIZE) { + /* yes */ + new_sos = &ber->ber_sos_stack[ber->ber_sos_stack_posn]; + } else { + /* no */ + if ((new_sos = (Seqorset *)malloc(sizeof (Seqorset))) + == NULLSEQORSET) { + return (-1); + } + } + ber->ber_sos_stack_posn++; + + if (ber->ber_sos == NULLSEQORSET) + new_sos->sos_first = ber->ber_ptr; + else + new_sos->sos_first = ber->ber_sos->sos_ptr; + + /* Set aside room for a 4 byte length field */ + new_sos->sos_ptr = new_sos->sos_first + kmfber_calc_taglen(tag) + + FOUR_BYTE_LEN; + new_sos->sos_tag = tag; + + new_sos->sos_next = ber->ber_sos; + new_sos->sos_clen = 0; + + ber->ber_sos = new_sos; + if (ber->ber_sos->sos_ptr > ber->ber_end) { + (void) realloc(ber, ber->ber_sos->sos_ptr - ber->ber_end); + } + return (0); +} + +static int +kmfber_start_seq(BerElement *ber, ber_tag_t tag) +{ + if (tag == KMFBER_DEFAULT) + tag = BER_CONSTRUCTED_SEQUENCE; + + return (ber_start_seqorset(ber, tag)); +} + +static int +kmfber_start_set(BerElement *ber, ber_tag_t tag) +{ + if (tag == KMFBER_DEFAULT) + tag = BER_CONSTRUCTED_SET; + + return (ber_start_seqorset(ber, tag)); +} + +static int +ber_put_seqorset(BerElement *ber) +{ + ber_int_t netlen, len, taglen, lenlen; + unsigned char ltag = 0x80 + FOUR_BYTE_LEN - 1; + Seqorset *next; + Seqorset **sos = &ber->ber_sos; + + /* + * If this is the toplevel sequence or set, we need to actually + * write the stuff out. Otherwise, it's already been put in + * the appropriate buffer and will be written when the toplevel + * one is written. In this case all we need to do is update the + * length and tag. + */ + + len = (*sos)->sos_clen; + netlen = (ber_len_t)htonl(len); + + if (ber->ber_options & KMFBER_OPT_USE_DER) { + lenlen = kmfber_calc_lenlen(len); + } else { + lenlen = FOUR_BYTE_LEN; + } + + if ((next = (*sos)->sos_next) == NULLSEQORSET) { + /* write the tag */ + if ((taglen = ber_put_tag(ber, (*sos)->sos_tag, 1)) == -1) + return (-1); + + if (ber->ber_options & KMFBER_OPT_USE_DER) { + /* Write the length in the minimum # of octets */ + if (kmfber_put_len(ber, len, 1) == -1) + return (-1); + + if (lenlen != FOUR_BYTE_LEN) { + /* + * We set aside FOUR_BYTE_LEN bytes for + * the length field. Move the data if + * we don't actually need that much + */ + (void) memmove((*sos)->sos_first + taglen + + lenlen, (*sos)->sos_first + taglen + + FOUR_BYTE_LEN, len); + } + } else { + /* Fill FOUR_BYTE_LEN bytes for length field */ + /* one byte of length length */ + if (kmfber_write(ber, (char *)<ag, 1, 1) != 1) + return (-1); + + /* the length itself */ + if (kmfber_write(ber, + (char *)&netlen + sizeof (ber_int_t) + - (FOUR_BYTE_LEN - 1), FOUR_BYTE_LEN - 1, 1) != + FOUR_BYTE_LEN - 1) + return (-1); + } + /* The ber_ptr is at the set/seq start - move it to the end */ + ber->ber_ptr += len; + } else { + ber_tag_t ntag; + + /* the tag */ + taglen = kmfber_calc_taglen((*sos)->sos_tag); + ntag = htonl((*sos)->sos_tag); + (void) memmove((*sos)->sos_first, (char *)&ntag + + sizeof (ber_int_t) - taglen, taglen); + + if (ber->ber_options & KMFBER_OPT_USE_DER) { + ltag = (lenlen == 1) ? (unsigned char)len : + (unsigned char) (0x80 + (lenlen - 1)); + } + + /* one byte of length length */ + (void) memmove((*sos)->sos_first + 1, <ag, 1); + + if (ber->ber_options & KMFBER_OPT_USE_DER) { + if (lenlen > 1) { + /* Write the length itself */ + (void) memmove((*sos)->sos_first + 2, + (char *)&netlen + sizeof (ber_uint_t) - + (lenlen - 1), + lenlen - 1); + } + if (lenlen != FOUR_BYTE_LEN) { + /* + * We set aside FOUR_BYTE_LEN bytes for + * the length field. Move the data if + * we don't actually need that much + */ + (void) memmove((*sos)->sos_first + taglen + + lenlen, (*sos)->sos_first + taglen + + FOUR_BYTE_LEN, len); + } + } else { + /* the length itself */ + (void) memmove((*sos)->sos_first + taglen + 1, + (char *) &netlen + sizeof (ber_int_t) - + (FOUR_BYTE_LEN - 1), FOUR_BYTE_LEN - 1); + } + + next->sos_clen += (taglen + lenlen + len); + next->sos_ptr += (taglen + lenlen + len); + } + + /* we're done with this seqorset, so free it up */ + /* was this one from the local stack ? */ + if (ber->ber_sos_stack_posn > SOS_STACK_SIZE) { + free((char *)(*sos)); + } + ber->ber_sos_stack_posn--; + *sos = next; + + return (taglen + lenlen + len); +} + +/* VARARGS */ +int +kmfber_printf(BerElement *ber, const char *fmt, ...) +{ + va_list ap; + char *s, **ss; + struct berval **bv, *oid; + int rc, i, t; + ber_int_t len; + + va_start(ap, fmt); + +#ifdef KMFBER_DEBUG + if (lber_debug & 64) { + char msg[80]; + sprintf(msg, "kmfber_printf fmt (%s)\n", fmt); + ber_err_print(msg); + } +#endif + + for (rc = 0; *fmt && rc != -1; fmt++) { + switch (*fmt) { + case 'b': /* boolean */ + i = va_arg(ap, int); + rc = kmfber_put_boolean(ber, i, ber->ber_tag); + break; + + case 'i': /* int */ + i = va_arg(ap, int); + rc = ber_put_int(ber, (ber_int_t)i, ber->ber_tag); + break; + + case 'D': /* Object ID */ + if ((oid = va_arg(ap, struct berval *)) == NULL) + break; + rc = ber_put_oid(ber, oid, ber->ber_tag); + break; + case 'I': /* int */ + s = va_arg(ap, char *); + len = va_arg(ap, ber_int_t); + rc = ber_put_big_int(ber, ber->ber_tag, s, len); + break; + + case 'e': /* enumeration */ + i = va_arg(ap, int); + rc = kmfber_put_enum(ber, (ber_int_t)i, ber->ber_tag); + break; + + case 'l': + t = va_arg(ap, int); + rc = kmfber_put_len(ber, t, 0); + break; + case 'n': /* null */ + rc = kmfber_put_null(ber, ber->ber_tag); + break; + + case 'o': /* octet string (non-null terminated) */ + s = va_arg(ap, char *); + len = va_arg(ap, int); + rc = kmfber_put_ostring(ber, s, len, ber->ber_tag); + break; + + case 's': /* string */ + s = va_arg(ap, char *); + rc = kmfber_put_string(ber, s, ber->ber_tag); + break; + + case 'B': /* bit string */ + s = va_arg(ap, char *); + len = va_arg(ap, int); /* in bits */ + rc = kmfber_put_bitstring(ber, s, len, ber->ber_tag); + break; + + case 't': /* tag for the next element */ + ber->ber_tag = va_arg(ap, ber_tag_t); + ber->ber_usertag = 1; + break; + + case 'T': /* Write an explicit tag, but don't change current */ + t = va_arg(ap, int); + rc = ber_put_tag(ber, t, 0); + break; + + case 'v': /* vector of strings */ + if ((ss = va_arg(ap, char **)) == NULL) + break; + for (i = 0; ss[i] != NULL; i++) { + if ((rc = kmfber_put_string(ber, ss[i], + ber->ber_tag)) == -1) + break; + } + break; + + case 'V': /* sequences of strings + lengths */ + if ((bv = va_arg(ap, struct berval **)) == NULL) + break; + for (i = 0; bv[i] != NULL; i++) { + if ((rc = kmfber_put_ostring(ber, bv[i]->bv_val, + bv[i]->bv_len, ber->ber_tag)) == -1) + break; + } + break; + + case '{': /* begin sequence */ + rc = kmfber_start_seq(ber, ber->ber_tag); + break; + + case '}': /* end sequence */ + rc = ber_put_seqorset(ber); + break; + + case '[': /* begin set */ + rc = kmfber_start_set(ber, ber->ber_tag); + break; + + case ']': /* end set */ + rc = ber_put_seqorset(ber); + break; + + default: { +#ifdef KMFBER_DEBUG + char msg[80]; + sprintf(msg, "unknown fmt %c\n", *fmt); + ber_err_print(msg); +#endif + rc = -1; + break; + } + } + + if (ber->ber_usertag == 0) + ber->ber_tag = KMFBER_DEFAULT; + else + ber->ber_usertag = 0; + } + + va_end(ap); + + return (rc); +} |