/*
** @(#) 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 <u.h>
#include <libc.h>
#include <auth.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <mp.h>
#include <libsec.h>
#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
|