Index: creddy/attribute.c
===================================================================
--- creddy/attribute.c	(revision 085f15950e1245032702e366285b3848d7715cae)
+++ creddy/attribute.c	(revision 085f15950e1245032702e366285b3848d7715cae)
@@ -0,0 +1,127 @@
+#include <credentials/keys/private_key.h>
+
+#include "creddy.h"
+
+#define ROLE_SEPARATOR " <- "
+
+void attribute_main(options_t *opts) {
+    if (
+        opts->issuer == NULL ||
+        opts->key == NULL ||
+        opts->role == NULL ||
+        opts->subject == NULL ||
+        opts->out == NULL
+    )
+        usage(opts);
+
+    if (!clean_name(opts->role)) {
+        printf("bad role name\n");
+        usage(opts);
+    }
+
+    // verify the subject role name if present
+    if (opts->subject_role) {
+        char *role = opts->subject_role;
+        char *start[3];
+        int name_parts = 0, i;
+
+        start[name_parts++] = role;
+
+        for (i = 0; role[i] != '\0'; ++i)
+            if (role[i] == '.') {
+                if (name_parts == 3) {
+                    printf("bad subject role name (too many dots)\n");
+                    usage(opts);
+                }
+                start[name_parts++] = &role[i+1];
+                role[i] = 0;
+            }
+
+        for (i = 0; i < name_parts; ++i)
+            if (!clean_name(start[i])) {
+                printf("bad subject role name\n");
+                usage(opts);
+            }
+
+        for (i = 1; i < name_parts; ++i)
+            *(start[i]-1) = '.'; // replace the dot
+
+        printf("good: %s\n", opts->subject_role);
+    }
+
+    if (opts->validity < 0) {
+        printf("Validity must be >= 1 day\n");
+        usage(opts);
+    }
+
+    // load signer key
+    private_key_t *key = lib->creds->create(lib->creds,
+        CRED_PRIVATE_KEY, KEY_RSA,
+        BUILD_FROM_FILE, opts->key,
+        BUILD_END
+    );
+    if (key == NULL)
+        errx(1, "can't open private key file %s", opts->key);
+
+    // get the keyids
+    certificate_t *issuer = cert_from_file(opts->issuer);
+    char *issuer_id = cert_keyid(issuer);
+
+    certificate_t *subject = cert_from_file(opts->subject);
+    char *subject_id = cert_keyid(subject);
+
+    // build the role encoding
+    int role_encoding_len = 1; // for nul terminator
+    role_encoding_len += strlen(issuer_id) + 1 + strlen(opts->role);
+    role_encoding_len += sizeof(ROLE_SEPARATOR) - 1;
+    role_encoding_len += strlen(subject_id);
+    if (opts->subject_role)
+        role_encoding_len += 1 + strlen(opts->subject_role);
+
+    char *role_encoding = xmalloc(role_encoding_len);
+    role_encoding[0] = '\0';
+
+    sprintf(role_encoding, "%s.%s" ROLE_SEPARATOR "%s", issuer_id, opts->role, subject_id);
+    if (opts->subject_role) {
+        strcat(role_encoding, ".");
+        strcat(role_encoding, opts->subject_role);
+    }
+
+    free(issuer_id);
+    free(subject_id);
+
+    // create attribute cert
+    time_t not_before = time(NULL);
+    time_t not_after = not_before + opts->validity * 3600 * 60 * 60;
+    chunk_t serial = generate_serial();
+
+    certificate_t *attr_cert = lib->creds->create(lib->creds,
+        CRED_CERTIFICATE, CERT_X509_AC,
+        BUILD_CERT, 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, issuer,
+        BUILD_SIGNING_KEY, key,
+        BUILD_END
+    );
+    if (attr_cert == NULL)
+        errx(1, "Couldn't build attribute cert");
+
+    // write to file
+    chunk_t encoding = attr_cert->get_encoding(attr_cert);
+
+    FILE *out = fopen(opts->out, "w");
+    if (out == NULL)
+        err(1, "Can't open attribute cert output file %s", opts->out);
+    fwrite(encoding.ptr, encoding.len, 1, out);
+    fclose(out);
+
+    free(role_encoding);
+    free(serial.ptr);
+    DESTROY_IF(attr_cert);
+    DESTROY_IF(issuer);
+    DESTROY_IF(subject);
+    DESTROY_IF(key);
+}
Index: creddy/creddy.c
===================================================================
--- creddy/creddy.c	(revision ef3b04959fff64b9d44dfe4c16e22b875f989d0f)
+++ creddy/creddy.c	(revision 085f15950e1245032702e366285b3848d7715cae)
@@ -10,10 +10,14 @@
 #define OPT_VALIDITY        2
 #define OPT_CERT            3
+#define OPT_ISSUER          4
+#define OPT_KEY             5
+#define OPT_ROLE            6
+#define OPT_SUBJECT         7
+#define OPT_SUBJECT_ROLE    8
+#define OPT_OUT             9 // the world oughta be opt-in only :(
 
 int main(int argc, char **argv) {
     int ret;
     options_t options = { 0, };
-
-    options.validity = 1080;
 
     struct option getopts[] = {
@@ -31,4 +35,12 @@
         { "validity",   1, 0, OPT_VALIDITY },
 
+        // attribute options
+        { "issuer",     1, 0, OPT_ISSUER },
+        { "key",        1, 0, OPT_KEY },
+        { "role",       1, 0, OPT_ROLE },
+        { "subject",    1, 0, OPT_SUBJECT },
+        { "subject_role", 1, 0, OPT_SUBJECT_ROLE },
+        { "out",          1, 0, OPT_OUT },
+
         { NULL },
     };
@@ -52,6 +64,26 @@
                 options.cn = xstrdup(optarg);
                 break;
-            case OPT_VALIDITY:
+            case OPT_VALIDITY: // also an attribute option
                 options.validity = atoi(optarg);
+                break;
+
+            // attribute options
+            case OPT_ISSUER:
+                options.issuer = xstrdup(optarg);
+                break;
+            case OPT_KEY:
+                options.key = xstrdup(optarg);
+                break;
+            case OPT_ROLE:
+                options.role = xstrdup(optarg);
+                break;
+            case OPT_SUBJECT:
+                options.subject = xstrdup(optarg);
+                break;
+            case OPT_SUBJECT_ROLE:
+                options.subject_role = xstrdup(optarg);
+                break;
+            case OPT_OUT:
+                options.out = xstrdup(optarg);
                 break;
 
@@ -87,4 +119,5 @@
     switch (options.mode) {
         case MODE_GENERATE:
+            if (options.validity == 0) options.validity = 1080;
             generate_main(&options);
             break;
@@ -92,4 +125,9 @@
         case MODE_KEYID:
             keyid_main(&options);
+            break;
+
+        case MODE_ATTRIBUTE:
+            if (options.validity == 0) options.validity = 365;
+            attribute_main(&options);
             break;
 
@@ -171,7 +209,7 @@
         return 0;
 
-    // rest must be alphanumeric
+    // rest must be alphanumeric or _
     for (i = 1; string[i] != '\0'; ++i)
-        if (!isalnum(string[i]))
+        if (!isalnum(string[i]) && string[i] != '_')
             return 0;
 
Index: creddy/creddy.h
===================================================================
--- creddy/creddy.h	(revision ef3b04959fff64b9d44dfe4c16e22b875f989d0f)
+++ creddy/creddy.h	(revision 085f15950e1245032702e366285b3848d7715cae)
@@ -15,4 +15,12 @@
     char *cn;
     int validity;
+
+    // attribute options
+    char *issuer;
+    char *key;
+    char *role;
+    char *subject;
+    char *subject_role;
+    char *out;
 } options_t;
 
@@ -32,4 +40,7 @@
 char *cert_keyid(certificate_t *cert);
 
+// generate a random serial
+chunk_t generate_serial();
+
 void usage(options_t *opts);
 void *xmalloc(size_t len);
@@ -39,4 +50,5 @@
 void generate_main(options_t *opts);
 void keyid_main(options_t *opts);
+void attribute_main(options_t *opts);
 
 #endif /* __CREDDY_H__ */
Index: creddy/generate.c
===================================================================
--- creddy/generate.c	(revision ef3b04959fff64b9d44dfe4c16e22b875f989d0f)
+++ creddy/generate.c	(revision 085f15950e1245032702e366285b3848d7715cae)
@@ -143,10 +143,6 @@
         errx(1, "no random number generator");
 
-    chunk_t serial = chunk_empty;
-    rng->allocate_bytes(rng, 8, &serial);
-    while (serial.ptr[0] == 0)
-        // don't get leading 0's
-        rng->get_bytes(rng, 1, serial.ptr);
-    rng->destroy(rng);
+    // random serial
+    chunk_t serial = generate_serial();
 
     // validity period
@@ -180,3 +176,21 @@
     DESTROY_IF(public);
     free(encoding.ptr);
+    free(serial.ptr);
 }
+
+chunk_t generate_serial() {
+    chunk_t serial = chunk_empty;
+
+    // create a serial (stolen from strongswan pki)
+    rng_t *rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
+    if (!rng)
+        errx(1, "no random number generator");
+
+    rng->allocate_bytes(rng, 8, &serial);
+    while (serial.ptr[0] == 0)
+        // don't get leading 0's
+        rng->get_bytes(rng, 1, serial.ptr);
+    rng->destroy(rng);
+
+    return serial;
+}
