Index: creddy/attr.c
===================================================================
--- creddy/attr.c	(revision aa33ad991a001be2a9dd44feb3fabfd87a93ba41)
+++ creddy/attr.c	(revision aa33ad991a001be2a9dd44feb3fabfd87a93ba41)
@@ -0,0 +1,225 @@
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+#include "creddy.h"
+#include "id.h"
+#include "libcreddy.h"
+
+#define ROLE_SEPARATOR " <- "
+#define INTERSECTION_SEP " & "
+#define SHA1_LENGTH 40
+
+struct _creddy_attribute_t {
+    creddy_id_t *issuer;
+    char *role;
+    int validity;
+
+    char **subject;
+    int num_subjects;
+    int size_subjects;
+    int total_len; // total length of all subject strings
+
+    certificate_t *cert; // NULL until baked
+};
+
+static char *_validate_principal(char *keyid);
+static void _append(creddy_attribute_t *attr, char *subject);
+char *_get_role_encoding(creddy_attribute_t *attr);
+
+int creddy_attribute_create(creddy_attribute_t **ret, creddy_id_t *issuer, char *role, int validity) {
+    if (creddy_id_privkey(issuer) == NULL)
+        return CREDDY_ATTRIBUTE_ISSUER_NOKEY;
+    if (!clean_name(role))
+        return CREDDY_ATTRIBUTE_INVALID_ROLE;
+    if (validity < 0)
+        return CREDDY_ATTRIBUTE_INVALID_VALIDITY;
+
+    creddy_attribute_t *attr = xmalloc(sizeof(creddy_attribute_t));
+    attr->issuer = issuer;
+    attr->role = xstrdup(role);
+    attr->validity = validity;
+
+    attr->num_subjects = 0;
+    attr->size_subjects = 4;
+    attr->subject = xmalloc(sizeof(char *) * attr->size_subjects);
+    attr->total_len = 0;
+
+    // NULL until baked
+    attr->cert = NULL;
+
+    *ret = attr;
+    return CREDDY_SUCCESS;
+}
+
+int creddy_attribute_principal(creddy_attribute_t *attr, char *keyid) {
+    char *copy = _validate_principal(keyid);
+    if (copy == NULL)
+        return 0;
+
+    _append(attr, copy);
+    return 1;
+}
+
+int creddy_attribute_role(creddy_attribute_t *attr, char *keyid, char *role) {
+    if (!clean_name(role))
+        return 0;
+
+    char *copy = _validate_principal(keyid);
+    if (copy == NULL)
+        return 0;
+
+    int len = strlen(copy) + strlen(role) + strlen(ROLE_SEPARATOR) + 1;
+    copy = xrealloc(copy, len);
+    strcat(copy, ".");
+    strcat(copy, role);
+
+    _append(attr, copy);
+    return 1;
+}
+
+int creddy_attribute_linking_role(creddy_attribute_t *attr, char *keyid, char *role, char *linked) {
+    if (!clean_name(role) || !clean_name(linked))
+        return 0;
+
+    char *copy = _validate_principal(keyid);
+    if (copy == NULL)
+        return 0;
+
+    int len = strlen(copy) + strlen(role) + strlen(ROLE_SEPARATOR) + 2;
+    copy = xrealloc(copy, len);
+    strcat(copy, ".");
+    strcat(copy, role);
+    strcat(copy, ".");
+    strcat(copy, linked);
+
+    _append(attr, copy);
+    return 1;
+}
+
+int creddy_attribute_bake(creddy_attribute_t *attr) {
+    if (attr->num_subjects == 0)
+        return 0;
+
+    char *role_encoding = _get_role_encoding(attr);
+
+    // create attribute cert
+    time_t not_before = time(NULL);
+    time_t not_after = not_before + attr->validity * 3600 * 60 * 60;
+    chunk_t serial = creddy_generate_serial();
+
+    certificate_t *attr_cert = lib->creds->create(lib->creds,
+        CRED_CERTIFICATE, CERT_X509_AC,
+        BUILD_CERT, creddy_id_cert(attr->issuer),
+        BUILD_NOT_BEFORE_TIME, not_before,
+        BUILD_NOT_AFTER_TIME, not_after,
+        BUILD_SERIAL, serial,
+        BUILD_IETF_GROUP_ATTR, role_encoding,
+        BUILD_SIGNING_CERT, creddy_id_cert(attr->issuer),
+        BUILD_SIGNING_KEY, creddy_id_privkey(attr->issuer),
+        BUILD_END
+    );
+    if (attr_cert == NULL)
+        return 0;
+
+    attr->cert = attr_cert;
+
+    free(role_encoding);
+    free(serial.ptr);
+    return 1;
+}
+
+// returns 1 if the cert hasn't been baked
+int creddy_attribute_write(creddy_attribute_t *attr, FILE *out) {
+    assert(attr != NULL);
+
+    if (attr->cert == NULL)
+        return 0;
+
+    // write to file
+    chunk_t encoding = attr->cert->get_encoding(attr->cert);
+    fwrite(encoding.ptr, encoding.len, 1, out);
+    free(encoding.ptr);
+
+    return 1;
+}
+
+void creddy_attribute_free(creddy_attribute_t *attr) {
+    int i;
+
+    if (attr == NULL)
+        return;
+
+    creddy_id_free(attr->issuer);
+    free(attr->role);
+    DESTROY_IF(attr->cert);
+
+    // free all the subjects
+    for (i = 0; i < attr->num_subjects; ++i)
+        free(attr->subject[i]);
+    free(attr->subject);
+
+    free(attr);
+}
+
+//
+// Helper functions below
+//
+
+// validate a princpal's name
+// makes sure it's a valid SHA1 identifier
+// return values:
+//  success: malloc'd copy with all hex digits lowercase
+//  fail: NULL
+static char *_validate_principal(char *keyid) {
+    int i;
+    char *copy = NULL;
+
+    if (strlen(keyid) != SHA1_LENGTH)
+        return NULL;
+
+    copy = xstrdup(keyid);
+    for (i = 0; i < SHA1_LENGTH; ++i) {
+        copy[i] = tolower(copy[i]);
+        if (!isxdigit(copy[i]))
+            goto error;
+    }
+
+    return copy;
+
+error:
+    free(copy);
+    return NULL;
+}
+
+static void _append(creddy_attribute_t *attr, char *subject) {
+    // expand the array if necessary
+    if (attr->num_subjects == attr->size_subjects) {
+        attr->size_subjects *= 2;
+        attr->subject = xrealloc(attr->subject, attr->size_subjects * sizeof(creddy_attribute_t));
+    }
+
+    attr->subject[attr->num_subjects++] = subject;
+    attr->total_len += strlen(subject);
+}
+
+char *_get_role_encoding(creddy_attribute_t *attr) {
+    int i, num_subjects;
+
+    num_subjects = attr->num_subjects;
+
+    int len = SHA1_LENGTH + 1 + strlen(attr->role) + strlen(ROLE_SEPARATOR);
+    len += attr->total_len + num_subjects * strlen(INTERSECTION_SEP);
+
+    char *encoding = xmalloc(len);
+    sprintf(encoding, "%s.%s" ROLE_SEPARATOR, creddy_id_keyid(attr->issuer), attr->role);
+
+    for (i = 0; i < num_subjects; ++i) {
+        strcat(encoding, attr->subject[i]);
+        if (i < num_subjects - 1)
+            strcat(encoding, INTERSECTION_SEP);
+    }
+
+    return encoding;
+}
