/* ** @(#) pemout.c - PEM encoding ** @(#) $Id: pemout.c,v 1.10 2003/12/08 07:15:17 lucio Exp $ */ /* ** ================================================================== ** ** $Logfile:$ ** $RCSfile: pemout.c,v $ ** $Revision: 1.10 $ ** $Date: 2003/12/08 07:15:17 $ ** $Author: lucio $ ** ** ================================================================== ** ** $Log: pemout.c,v $ ** Revision 1.10 2003/12/08 07:15:17 lucio ** Weekend developments - mostly OID related ** ** Revision 1.9 2003/12/04 16:13:31 lucio ** Checkpoint - OID rethink (incomplete) ** ** Revision 1.8 2003/12/04 11:31:43 lucio ** Streamlined - specially OID management ** ** Revision 1.7 2003/11/30 19:05:27 lucio ** Advanced - plenty to go still, of course. ** ** Revision 1.6 2003/11/27 19:19:37 lucio ** Checkpoint - DNs almost complete ** ** Revision 1.5 2003/11/27 18:39:27 lucio ** Checkpoint - some progress with DNs ** ** Revision 1.4 2003/11/25 14:35:29 lucio ** Checkpoint to take home. ** ** Revision 1.3 2003/11/25 08:43:19 lucio ** On return from home ** ** Revision 1.2 2003/11/24 17:45:59 lucio ** Checkpoint after some progress ** ** Revision 1.1.1.1 2003/11/10 10:34:31 lucio ** ASN.1 developments. ** ** ================================================================== */ #include #include #include #include #include #include <9p.h> #include #include #include "b64.h" #include "../devel/ber.h" #include "../devel/oid.h" static void copyright (void) { print ("@(#) pemout: PEM (RFC 1424) encoding\n"); print ("@(#) $Id: pemout.c,v 1.10 2003/12/08 07:15:17 lucio Exp $\n"); print ("@(#) Copyright (C) 2003 Lucio De Re.\n"); print ("@(#) Author (A) Lucio De Re, 2003.\n"); } static char *use[] = { "usage: %s [-h|H] [-p PEMtype] [-f originator]... [-t recipient]...\n", "\n", "opts: -h|H: this message\n", " -V: show version and copyright notice\n", "\n", " -p PEMtype: Processing type\n", " -f originator: Originator mail\n", " -t recipient: Recipient mail\n", nil }; static void usage (char *argv0, char *use) { fprint (2, use, argv0); exits ("usage"); } static void help (char *argv0, char **use) { print (*use++, argv0); while (*use) { print (*use++); } } enum { MIC_CLEAR, MIC_ONLY, ENCRYPTED, CRL, }; static char *proctype[] = { [MIC_CLEAR] "MIC-CLEAR", [MIC_ONLY] "MIC-ONLY", [ENCRYPTED] "ENCRYPTED", [CRL] "CRL", nil, }; enum { DES_CBC, }; static char *dekinfo[] = { [DES_CBC] "DES-CBC", }; enum { RSA, }; static char *keyinfo[] = { [RSA] "RSA", }; /* We could use upas/fs to fragment the message into the components we require, namely the originator(s) and recipient(s) and the message body. We may however prefer to allow the explicit specification of the parties involved on the command line as possibly multiple entries. In any case, the headers, if supplied, will be left unmodified. */ static OidHier *hierarchy; /* Search the database for the given address, return the information required to process the message as specified by the "ptype" argument. The type of returned information varies with the party, rather than the processing type: the PEM RFCs only expand on asymmetric processing, where an X.509 certificate is used, although they provide the infrastructure for other approaches. Like them, we'll probably only consider X.509 certificates, but ensure that the procedures can be extended to cater for alternatives. We need to return not only the certificate, but also the available encryption methods, possibly other details. RFC 1422 refers. ber_obj * getauth (char *addr, int ptype) { } */ typedef struct target Target; struct target { char *addr; char *name; int nname; int *cap; uchar *cert; int ncert; uchar *icert; int nicert; RSApub* pub; int ktype; uchar *key; long keysz; Target *next; }; static char *ldr_cert[] = { "MIIBazCB1QIBADANBgkqhkiG9w0BAQQFABAAMB4XDTAzMTEzMDE2MjgwNFoXDTA2", "MTIwMjE2MjgwNFoQADCBnDANBgkqhkiG9w0BAQEFAAOBigAwgYYCgYCgVhPJm6Qj", "Zt0X9XNfzd+60c6f7/P2M7b0DZQCQ9T8/xS/mL+LeBbkyVFIkZnbXTHZu6VauKMM", "29NudWVytZLDuUlFJYM7cdY20N6+hLHMBmyZJubWZ1xEoFWi+zi8VZU9wiB/kbJp", "Hxd/RXZtlb/6v9idljj8TKZnDF13ob427QIBIzANBgkqhkiG9w0BAQQFAAOBgQAD", "TtReORNcCfVCTb5ikzehOhimEwn1Wz+LJ5l0tnaGs0ulpOEJTsSSxMW+LUHOdTGm", "4kgI18+PomN/Z+LjsWm5Bq9zlrIi1l41urevU+ozTxCia5rZOQahJbR9MeWYkGQd", "LwG+cf8Q9OoJ7oquNKLjQOWyZ6a5fMke2ScOBLO4DA==", nil, }; static char *o_cert[] = { "MIIBlTCCAScCAWUwDQYJKoZIhvcNAQECBQAwUTELMAkGA1UEBhMCVVMxIDAeBgNV", "BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDzAN", "BgNVBAsTBk5PVEFSWTAeFw05MTA5MDQxODM4MTdaFw05MzA5MDMxODM4MTZaMEUx", "CzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5jLjEU", "MBIGA1UEAxMLVGVzdCBVc2VyIDEwWTAKBgRVCAEBAgICAANLADBIAkEAwHZHl7i+", "yJcqDtjJCowzTdBJrdAiLAnSC+CnnjOJELyuQiBgkGrgIh3j8/x0fM+YrsyF1u3F", "LZPVtzlndhYFJQIDAQABMA0GCSqGSIb3DQEBAgUAA1kACKr0PqphJYw1j+YPtcIq", "iWlFPuN5jJ79Khfg7ASFxskYkEMjRNZV/HZDZQEhtVaU7Jxfzs2wfX5byMp2X3U/", "5XUXGx7qusDgHQGs7Jk9W8CW1fuSWUgN4w==", nil, }; static char *i_cert[] = { "MIIB3DCCAUgCAQowDQYJKoZIhvcNAQECBQAwTzELMAkGA1UEBhMCVVMxIDAeBgNV", "BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDTAL", "BgNVBAsTBFRMQ0EwHhcNOTEwOTAxMDgwMDAwWhcNOTIwOTAxMDc1OTU5WjBRMQsw", "CQYDVQQGEwJVUzEgMB4GA1UEChMXUlNBIERhdGEgU2VjdXJpdHksIEluYy4xDzAN", "BgNVBAsTBkJldGEgMTEPMA0GA1UECxMGTk9UQVJZMHAwCgYEVQgBAQICArwDYgAw", "XwJYCsnp6lQCxYykNlODwutF/jMJ3kL+3PjYyHOwk+/9rLg6X65B/LD4bJHtO5XW", "cqAz/7R7XhjYCm0PcqbdzoACZtIlETrKrcJiDYoP+DkZ8k1gCk7hQHpbIwIDAQAB", "MA0GCSqGSIb3DQEBAgUAA38AAICPv4f9Gx/tY4+p+4DB7MV+tKZnvBoy8zgoMGOx", "dD2jMZ/3HsyWKWgSF0eH/AJB3qr9zosG47pyMnTf3aSy2nBO7CMxpUWRBcXUpE+x", "EREZd9++32ofGBIXaialnOgVUn0OzSYgugiQ077nJLDUj0hQehCizEs5wUJ35a5h", nil, }; static void dcert (Target *fp) { int w, n = 0; char *t, *t0, **t1; t = malloc (w = 512); for (t1 = ldr_cert; *t1; t1++) { if (n + strlen (*t1) > w) { w *= 2; t = realloc (t, w); } t0 = t + n; n += db64v ((uchar *) t0, *t1); } fp->cert = realloc (t, fp->ncert = n); n = 0; t = malloc (w = 512); for (t1 = i_cert; *t1; t1++) { if (n + strlen (*t1) > w) { w *= 2; t = realloc (t, w); } t0 = t + n; n += db64v ((uchar *) t0, *t1); } fp->icert = realloc (t, fp->nicert = n); fp->name = malloc (fp->nname = 128); fp->ktype = RSA; t = malloc (128); n = db64v ((uchar *) t, "I3rRIGXUGWAF8js5wCzRTkdhO34PTHdRZY9Tuvm03M+NM7fx6qc5udixps2Lng0+"); t0 = t + n; n += db64v ((uchar *) t0, "wGrtiUm/ovtKdinz6ZQ/aQ=="); fp->key = realloc (t, n); fp->keysz = n; } static void cert_analyse (BerObj *op) { BerObj *opd, *op1; OidObj aug; if (!op) return; ber_print (op, 1); print ("Certificate\n"); if (opd = op->down) { // constructed record if (opd->tag == BER_TAG_INTEGER) { if (opd->next->tag == BER_TAG_INTEGER) { print ("\tVersion = %I\n", opd); opd = opd->next; } else { print ("\tVersion = 0\n"); } print ("\tSerial = %I\n", opd); } else { print ("Unrecognised BER object (tag = %d)\n", opd->tag); return; } opd = opd->next; print ("\tSignature:\n"); op1 = opd->down; print ("\t\tAlgorithm: %O\n", op1); /* if (memcmp (op1->buf, BER_MD2RSAENCRYPTION_OBJECTID, op1->len) == 0) { print ("\t\tParameters: NULL\n"); } else { print ("Unrecognised algorithm\n"); return; } This should read: if (oid_isobj (op1->buf, BER_MD2RSAENCRYPTION_OBJECTID)) { } ... where the constant is somehow obtained from our OID database. Alternatively, we could search the database for the corresponding object name: n = oid_name (h, op1->buf); if (n != nil && strcmp (n, BER_MD2RSAENCRYPTION_OBJECTNAME) == 0) { } ... Neither seems convenient, but one of them will have to do unless we choose to implement everything in C++ (where operator overloading will make things considerably simpler, if more risky). What we actually need is an opaque mechanism to retrieve/return the algorithm and parameters in the format most appropriate for our needs. Right now, this is for printing purposes, but in the long term we will probably need the details to perform some operation on the associated text. All along, there seems to be a need to parse the ASN.1 object in terms of the objectives rather than in a generic fashion if one is to avoid redundant processing, but that way leads to producing specialised utilities that do not adjust readily to changing conditions. In particular, I'm hoping to entend the PEM encoder/decoder into its equivalent S/MIME processor without writing an entirely new parser. Certainly, it is necessary to proceed with the immediate requirements before being able to generalise the needs: trying to establish from theory and first principles what is required is not an option because the specifications are too difficult to interpret in a vacuum. Alternatively to all of the above, memcmp() may still be the best approach. After all, we do have the parsed object and its BER representation and what is missing - that we used to have, one iteration back - is a record of the object we generated (at the time it was being generated on purpose, now we make better use of it). There are other considerations that need investigating... Presently, the hierarchy/database (all right, then, it's a hierarchical database!) is constructed by private functions oid_build() and oid_append() (great deal of details in the oid.c module file) which ensure by devious means that the handle to the database is kept up to date even when a new root entry is added. Lower down in the functionality layers, a more orthodox mechanism (by some measurement of orthodox) is used, making it possible to keep the hierarchy accessible _and_ return a pointer to the node entry being operated upon. It seems necessary to extend the latter mechanism to oid_build() and oid_append() so that here we can also enhance the return capabilities. In particular, right now, we want to be able to retrieve the BER representation of an OID (we may even make good use of this facility in constructing OID objects later) either at creation time or on demand when we want to identify an OID as we do here. In practice, we're currently looking at algorithm oIDs as well as the accompanying, required parameters. We can't just yet use the OID database to determine what parameters to associate with a particular algorithm, but we certainly want to be able to establish whether the algorithm is what we desire/expect it to be. I guess it is up to us then to decide what to do if it is or isn't. In some fashion it ought to be possible to put into an extended database the actual semantics of certificates, but that has to be a specialised function rather than a generic solution. Something to give serious attention to, certainly. Another option? switch (oid_algid (op1->buf, algs)) { case OID_MD2RSAENCRYPTION: do_MD2(); break; ... default: fprint (2, "Not a valid algorithm\n"); exits ("alg"); } where algs[] is a set of descriptors for valid algorithms and the return value from oid_algid() is the index into algs[] or -1 if not found. This does take care of determining what algorithms are valid and which one of them was specified. The actual semantics can then be obtained via the chosen algs[] element and will have been established, possibly separately from some configuration information retrieved at startup time. Mind you, whenever I think startup, I wonder how it can be turned into a file system. I don't think it's in Bell Labs' philosophy, but one benefit of using a files system to provide services is the advantage that it needs only occasional initialisation, unlike procedures that need to read their configuration information whenever they are utilised. In this vein, figuring out how to install the certificate extraction/creation facility as a file server may not be easy, but it ought to be well worth the effort. Tentatively, one would submit the necessary certificate(s) (to factotum, really!) and then pipe the data through the server to obtain the desired result. */ opd = opd->next; aug.obj = opd->down; aug.hier = hierarchy; print ("\tIssuer: %N\n", &aug); opd = opd->next; print ("\tValidity:\n"); print ("\t\tnotBefore: %T\n", op1 = opd->down); print ("\t\tnotAfter: %T\n", op1->next); opd = opd->next; aug.obj = opd->down; aug.hier = hierarchy; print ("\tSubject: %N\n", &aug); opd = opd->next; print ("\tPublicKey:\n"); op1 = opd->down; print ("\t\tAlgorithm: %O\n", op1->down); /* print ("\t\t\tAlgorithm: %O\n", opd->down); print ("\t\t\tParameters: %d\n", o->...); // dependent on above */ } else { print ("Unrecognisable certificate\n"); return; } } static Target * append (Target *t, char *name) { Target *t0 = malloc (sizeof (Target)); Target *t1, *t2; t0->addr = strdup (name); t0->cap = nil; t0->next = nil; dcert (t0); // fake certificate if (t == nil) { return t0; } for (t1 = t; t1; t1 = t1->next) t2 = t1; t2->next = t0; return t; } void main (int argc, char *argv[]) { int ptype = ENCRYPTED, dektype = DES_CBC, x; uchar dekval[8]; char *optarg, **p0, *msg; Target *from = nil, *to = nil, *fp; BerObj *o, *o0; ARGBEGIN { case 'p': // transformation type (proctype[]) optarg = EARGF(usage (argv0, *use)); for (ptype = 0, p0 = proctype; *p0; p0++, ptype++) { if (cistrncmp (optarg, *p0, strlen (*p0)) == 0) break; } if (*p0 != nil) { fprint (2, "%s: Invalid processing type: %s\n", argv0, optarg); exits ("proctype"); } break; case 'f': // one or more originators from = append (from, EARGF(usage (argv0, *use))); break; case 't': // one or more recipients to = append (to, EARGF(usage (argv0, *use))); break; default: break; } ARGEND quotefmtinstall(); doquote = needsrcquote; hierarchy = oid_initdb (nil); print ("-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\n"); print ("Proc-Type: %d,%s\r\n", 4, proctype[ptype]); print ("Content-Domain: RFC822\r\n"); switch (ptype) { case ENCRYPTED: switch (dektype) { case DES_CBC: des56to64 ((uchar *) "abcdefg", dekval); print ("DEK-Info: %s,", dekinfo[dektype]); for (x = 0; x < sizeof (dekval); x++) { print ("%02X", dekval[x] & 0xFF); } print ("\r\n"); for (fp = from; fp; fp = fp->next) { // originator(s) print ("Mail from: %s <%s>\r\n", fp->name, fp->addr); print ("Originator-Certificate:\r\n"); feb64n (" %s\r\n", fp->cert, fp->ncert, 48); print ("Key-Info: %s,\r\n", keyinfo[fp->ktype]); feb64n (" %s\r\n", fp->key, fp->keysz, 48); // check signature of certificate // extract and display elements of certificate // report on validity of certificate fmtinstall ('I', ber_fmtint); fmtinstall ('N', oid_fmtdn); fmtinstall ('O', ber_fmtoid); fmtinstall ('T', ber_fmtdate); //asn1dump (fp->cert, fp->ncert); if (o = ber_parse (fp->cert, fp->cert + fp->ncert)) { fp->pub = X509toRSApub ((uchar *) (o->down->buf), o->down->len, nil, 0); if (msg = X509verify (fp->cert, fp->ncert, fp->pub)) { fprint (2, "Originator certificate does not verify:\n\t%s\n", msg); // exits ("orig cert"); } // ber_print (o, 1); cert_analyse (o->down); // ber_free (o); } else { fprint (2, "Originator certificate cannot be parsed\n"); exits ("origcert"); } print ("Issuer-Certificate:\r\n"); feb64n (" %s\r\n", fp->icert, fp->nicert, 48); //asn1dump (fp->icert, fp->nicert); if (o0 = ber_parse (fp->icert, fp->icert + fp->nicert)) { fp->pub = X509toRSApub ((uchar *) (o0->down->buf), o0->down->len, nil, 0); if (msg = X509verify ((uchar *) (o0->down->buf), o0->down->len, fp->pub)) { fprint (2, "Issuer certificate does not verify:\n\t%s\n", msg); // exits ("iss cert"); } cert_analyse (o0->down); // ber_free (o0); } else { fprint (2, "Originator certificate cannot be parsed\n"); exits ("origcert"); } } for (fp = to; fp; fp = fp->next) { // recipient(s) print ("Rcpt to: %s <%s>\r\n", fp->name, fp->addr); print ("Originator-Certificate:\r\n"); feb64n (" %s\r\n", fp->cert, fp->ncert, 48); print ("Key-Info: %s,\r\n", keyinfo[fp->ktype]); feb64n (" %s\r\n", fp->key, fp->keysz, 48); print ("Issuer-Certificate:\r\n"); } break; default: fprint (2, "%s: invalid encryption algorithm: %d\n", argv0, dektype); break; } if (0) { /* generate a 56-bit (plus odd parity bits) key for DES-CBC (RFC-1423 does not consider other options), */ /* ** MIIBlTCCAScCAWUwDQYJKoZIhvcNAQECBQAwUTELMAkGA1UEBhMCVVMxIDAeBgNV ** BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDzAN ** BgNVBAsTBk5PVEFSWTAeFw05MTA5MDQxODM4MTdaFw05MzA5MDMxODM4MTZaMEUx ** CzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5jLjEU ** MBIGA1UEAxMLVGVzdCBVc2VyIDEwWTAKBgRVCAEBAgICAANLADBIAkEAwHZHl7i+ ** yJcqDtjJCowzTdBJrdAiLAnSC+CnnjOJELyuQiBgkGrgIh3j8/x0fM+YrsyF1u3F ** LZPVtzlndhYFJQIDAQABMA0GCSqGSIb3DQEBAgUAA1kACKr0PqphJYw1j+YPtcIq ** iWlFPuN5jJ79Khfg7ASFxskYkEMjRNZV/HZDZQEhtVaU7Jxfzs2wfX5byMp2X3U/ ** 5XUXGx7qusDgHQGs7Jk9W8CW1fuSWUgN4w== */ /* ** I3rRIGXUGWAF8js5wCzRTkdhO34PTHdRZY9Tuvm03M+NM7fx6qc5udixps2Lng0+ ** wGrtiUm/ovtKdinz6ZQ/aQ== */ /* ** MIIB3DCCAUgCAQowDQYJKoZIhvcNAQECBQAwTzELMAkGA1UEBhMCVVMxIDAeBgNV ** BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDTAL ** BgNVBAsTBFRMQ0EwHhcNOTEwOTAxMDgwMDAwWhcNOTIwOTAxMDc1OTU5WjBRMQsw ** CQYDVQQGEwJVUzEgMB4GA1UEChMXUlNBIERhdGEgU2VjdXJpdHksIEluYy4xDzAN ** BgNVBAsTBkJldGEgMTEPMA0GA1UECxMGTk9UQVJZMHAwCgYEVQgBAQICArwDYgAw ** XwJYCsnp6lQCxYykNlODwutF/jMJ3kL+3PjYyHOwk+/9rLg6X65B/LD4bJHtO5XW ** cqAz/7R7XhjYCm0PcqbdzoACZtIlETrKrcJiDYoP+DkZ8k1gCk7hQHpbIwIDAQAB ** MA0GCSqGSIb3DQEBAgUAA38AAICPv4f9Gx/tY4+p+4DB7MV+tKZnvBoy8zgoMGOx ** dD2jMZ/3HsyWKWgSF0eH/AJB3qr9zosG47pyMnTf3aSy2nBO7CMxpUWRBcXUpE+x ** EREZd9++32ofGBIXaialnOgVUn0OzSYgugiQ077nJLDUj0hQehCizEs5wUJ35a5h */ } } print ("\r\n"); if (0) { char *s = nil; while (*s) { // encode to base64 } } print ("-----END PRIVACY-ENHANCED MESSAGE-----\r\n"); exits (0); } #ifdef LATER /* Retrieve from "the" database the details that make it possible to process the message to suit each recipient as well as originator. Other than an explicit preference, we may need to calculate an implicit operation based on the intersection of facilities available to the originator and recipient. It may be most logical to start with the originators and establish the most suitable "method" shared amongst them (I think this is essential - it probably will need to be a list, in some preference sequence), then establish for each recipient what is the method to be applied. It is important not to weaken the security in the quest to accommodate originators or recipients. In particular, the requested level of processing has to be obeyed; even exceeding it is not permissible. */ for (fp = *from; fp; fp = fp->m_next) { if ((cp = getauth (fp->addr, ptype)) { } } while (*mp) { // - encode message to canonical form // - apply the requested transformation // - output } #endif