libsec: add X509reqtoRSApub() function and return subject alt names in X509to*pub() name buffer

We need a way to parse a rsa certificate request and return the public
key and subject names. The new function X509reqtoRSApub() works the
same way as X509toRSApub() but on a certificate request.

We also need to support certificates that are valid for multiple domain
names (as tlshand does not support certificate selection). For this
reason, a comma separated list is returned as the certificate subject,
making it symmetric to X509rsareq() handling.

A little helper is provided with this change (auth/x5092pub) that takes
a certificate (or a certificate request when -r flag is provided) and
outputs the RSA public key in plan 9 format appended with the subject
attribute.
This commit is contained in:
cinap_lenrek 2021-07-04 22:00:24 +00:00
parent 7010ad85c5
commit 88060e7501
6 changed files with 216 additions and 9 deletions

View file

@ -365,6 +365,7 @@ RSApriv* rsaprivalloc(void);
void rsaprivfree(RSApriv*);
RSApub* rsaprivtopub(RSApriv*);
RSApub* X509toRSApub(uchar*, int, char*, int);
RSApub* X509reqtoRSApub(uchar*, int, char*, int);
RSApriv* asn1toRSApriv(uchar*, int);
RSApub* asn1toRSApub(uchar*, int);
void asn1dump(uchar *der, int len);

View file

@ -357,6 +357,7 @@ RSApriv* rsaprivalloc(void);
void rsaprivfree(RSApriv*);
RSApub* rsaprivtopub(RSApriv*);
RSApub* X509toRSApub(uchar*, int, char*, int);
RSApub* X509reqtoRSApub(uchar*, int, char*, int);
RSApub* asn1toRSApub(uchar*, int);
RSApriv* asn1toRSApriv(uchar*, int);
void asn1dump(uchar *der, int len);

View file

@ -15,6 +15,7 @@ rsaprivtopub,
rsapuballoc,
rsapubfree,
X509toRSApub,
X509reqtoRSApub,
X509rsagen,
X509rsareq,
X509rsaverify,
@ -61,6 +62,9 @@ RSApub* rsaprivtopub(RSApriv*)
RSApub* X509toRSApub(uchar *cert, int ncert, char *name, int nname)
.PP
.B
RSApub* X509reqtoRSApub(uchar *req, int nreq, char *name*, int nname)
.PP
.B
RSApriv* asn1toRSApriv(uchar *priv, int npriv)
.PP
.B
@ -79,7 +83,7 @@ uchar* decodePEM(char *s, char *type, int *len, char **new_s)
uchar* X509rsagen(RSApriv *priv, char *subj, ulong valid[2], int *certlen);
.PP
.B
uchar* X509rsareq(RSApriv *priv, char *subj, int *certlen);
uchar* X509rsareq(RSApriv *priv, char *subj, int *reqlen)
.PP
.B
char* X509rsaverify(uchar *cert, int ncert, RSApub *pk)
@ -159,9 +163,10 @@ returns the public key and, if
.I name
is not
.BR nil ,
the CN part of the Distinguished Name of the
certificate's Subject.
(This is conventionally a userid or a host DNS name.)
a concatenation of the CN part of the Distinguished Name of the
certificate's Subject and further Subject Alternative Names
separated by comma.
(These are conventionally a userid or a host DNS name.)
No verification is done of the certificate signature; the
caller should check the fingerprint,
.IR sha1(cert) ,
@ -180,6 +185,12 @@ It returns
.B nil
if successful, else an error string.
.PP
The routine
.I X509reqtoRSApub
is similar to
.I X509toRSApub
above, but decodes a X509 certificate request.
.PP
.I X509rsaverifydigest
takes a encoded PKCS #1 signature as used in X.509 as
.IR sig [ siglen ]
@ -197,8 +208,8 @@ a issuer/subject string
.IR subj ,
and the starting and ending validity dates,
.IR valid .
Length of the allocated binary certificate is stored in
.IR certlen .
Length of the allocated binary certificate request is stored in
.IR reqlen .
The subject line is conventionally of the form
.IP
.EX

View file

@ -35,6 +35,7 @@ TARG=\
userpasswd\
warning\
wrkey\
x5092pub\
DIRS=\
factotum\

View file

@ -0,0 +1,63 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <auth.h>
#include <mp.h>
#include <libsec.h>
int fd;
int req = 0;
char subject[1024];
void
usage(void)
{
fprint(2, "usage: aux/x5092pub [-r] [file]\n");
exits("usage");
}
void
main(int argc, char **argv)
{
int tot, n;
uchar *buf;
RSApub *pub;
quotefmtinstall();
fmtinstall('B', mpfmt);
fmtinstall('H', encodefmt);
ARGBEGIN{
case 'r':
req = 1;
break;
default:
usage();
}ARGEND
fd = 0;
if(argc == 1)
fd = open(argv[0], OREAD);
else if(argc != 0)
usage();
buf = nil;
tot = 0;
for(;;){
buf = realloc(buf, tot+8192);
if(buf == nil)
sysfatal("realloc: %r");
if((n = read(fd, buf+tot, 8192)) < 0)
sysfatal("read: %r");
if(n == 0)
break;
tot += n;
}
if(req)
pub = X509reqtoRSApub(buf, tot, subject, sizeof(subject));
else
pub = X509toRSApub(buf, tot, subject, sizeof(subject));
if(pub == nil)
sysfatal("X509toRSApub: %r");
print("key proto=rsa size=%d ek=%B n=%B subject=%q \n", mpsignif(pub->n), pub->ek, pub->n, subject);
exits(nil);
}

View file

@ -1589,6 +1589,7 @@ typedef struct CertX509 {
int signature_alg;
Bits* signature;
int curve;
Bytes* ext;
} CertX509;
/* Algorithm object-ids */
@ -1724,6 +1725,8 @@ static void (*namedcurves[])(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, m
nil,
};
static void appendaltnames(char *name, int nname, Bytes *ext, int req);
static void
freecert(CertX509* c)
{
@ -1735,6 +1738,7 @@ freecert(CertX509* c)
free(c->subject);
freebits(c->publickey);
freebits(c->signature);
freebytes(c->ext);
free(c);
}
@ -1863,6 +1867,7 @@ decode_cert(uchar *buf, int len)
c->publickey = nil;
c->signature_alg = -1;
c->signature = nil;
c->ext = nil;
/* Certificate */
if(!is_seq(&ecert, &elcert) || elistlen(elcert) !=3)
@ -1900,6 +1905,10 @@ decode_cert(uchar *buf, int len)
esubj = &el->hd;
el = el->tl;
epubkey = &el->hd;
if(el->tl != nil && el->tl->hd.tag.class == Context && el->tl->hd.tag.num == 3){
c->ext = el->tl->hd.val.u.octetsval;
el->tl->hd.val.u.octetsval = nil; /* transfer ownership */
}
if(!is_int(eserial, &c->serial)) {
if(!is_bigint(eserial, &b))
goto errret;
@ -2260,6 +2269,7 @@ X509toECpub(uchar *cert, int ncert, char *name, int nname, ECdomain *dom)
if(c == nil)
return nil;
copysubject(name, nname, c->subject);
appendaltnames(name, nname, c->ext, 0);
pub = nil;
if(c->publickey_alg == ALG_ecPublicKey){
ecdominit(dom, namedcurves[c->curve]);
@ -2302,6 +2312,7 @@ X509toRSApub(uchar *cert, int ncert, char *name, int nname)
if(c == nil)
return nil;
copysubject(name, nname, c->subject);
appendaltnames(name, nname, c->ext, 0);
pub = nil;
if(c->publickey_alg == ALG_rsaEncryption)
pub = asn1toRSApub(c->publickey->data, c->publickey->len);
@ -2644,7 +2655,7 @@ static Ints15 oid_subjectAltName = {4, 2, 5, 29, 17 };
static Ints15 oid_extensionRequest = { 7, 1, 2, 840, 113549, 1, 9, 14};
static Elist*
mkextensions(char *alts, int req)
mkextensions(char *alts, int isreq)
{
Elist *sl, *xl;
@ -2653,12 +2664,12 @@ mkextensions(char *alts, int req)
xl = mkextel(mkseq(sl), (Ints*)&oid_subjectAltName, xl);
if(xl != nil){
xl = mkel(mkseq(xl), nil);
if(req)
if(isreq)
xl = mkel(mkseq(
mkel(mkoid((Ints*)&oid_extensionRequest),
mkel(mkset(xl), nil))), nil);
}
if(req)
if(isreq)
xl = mkel(mkcont(0, xl), nil);
else if(xl != nil)
xl = mkel(mkcont(3, xl), nil);
@ -2681,6 +2692,85 @@ splitalts(char *s)
return nil;
}
static void
appendaltnames(char *name, int nname, Bytes *ext, int isreq)
{
Elem eext, ealt, edn;
Elist *el, *l;
Ints *oid;
char *alt;
int len;
if(name == nil || ext == nil)
return;
if(decode(ext->data, ext->len, &eext) != ASN_OK)
return;
if(isreq){
if(!is_seq(&eext, &el) || elistlen(el) != 2)
goto errext;
if(!is_oid(&el->hd, &oid) || !ints_eq(oid, (Ints*)&oid_extensionRequest))
goto errext;
el = el->tl;
if(!is_set(&el->hd, &el))
goto errext;
if(!is_seq(&el->hd, &el))
goto errext;
} else {
if(!is_seq(&eext, &el))
goto errext;
}
for(; el != nil; el = el->tl){
if(!is_seq(&el->hd, &l) || elistlen(l) != 2)
goto errext;
if(!is_oid(&l->hd, &oid) || !ints_eq(oid, (Ints*)&oid_subjectAltName))
continue;
el = l->tl;
break;
}
if(el == nil)
goto errext;
if(!is_octetstring(&el->hd, &ext))
goto errext;
if(decode(ext->data, ext->len, &ealt) != ASN_OK)
goto errext;
if(!is_seq(&ealt, &el))
goto erralt;
for(; el != nil; el = el->tl){
ext = el->hd.val.u.octetsval;
switch(el->hd.tag.num){
default:
continue;
case 1: /* email */
case 2: /* DNS */
if(ext == nil)
goto erralt;
alt = smprint("%.*s", ext->len, (char*)ext->data);
break;
case 4: /* DN */
if(ext == nil || decode(ext->data, ext->len, &edn) != ASN_OK)
goto erralt;
alt = parse_name(&edn);
freevalfields(&edn.val);
break;
}
if(alt == nil)
goto erralt;
len = strlen(alt);
if(strncmp(name, alt, len) == 0 && strchr(",", name[len]) == nil){
free(alt); /* same as the subject */
continue;
}
if(name[0] != '\0')
strncat(name, ", ", nname-1);
strncat(name, alt, nname-1);
free(alt);
}
erralt:
freevalfields(&ealt.val);
errext:
freevalfields(&eext.val);
}
static Bytes*
encode_rsapubkey(RSApub *pk)
{
@ -2885,6 +2975,46 @@ errret:
return cert;
}
RSApub*
X509reqtoRSApub(uchar *req, int nreq, char *name, int nname)
{
Elem ereq;
Elist *el;
char *subject;
Bits *bits;
RSApub *pub;
pub = nil;
if(decode(req, nreq, &ereq) != ASN_OK)
goto errret;
if(!is_seq(&ereq, &el) || elistlen(el) != 3)
goto errret;
if(!is_seq(&el->hd, &el) || elistlen(el) < 3)
goto errret;
el = el->tl;
subject = parse_name(&el->hd);
if(subject == nil)
goto errret;
copysubject(name, nname, subject);
free(subject);
el = el->tl;
if(el->tl != nil && el->tl->hd.tag.class == Context && el->tl->hd.tag.num == 0)
appendaltnames(name, nname, el->tl->hd.val.u.octetsval, 1);
if(!is_seq(&el->hd, &el) || elistlen(el) != 2)
goto errret;
if(parse_alg(&el->hd) != ALG_rsaEncryption)
goto errret;
el = el->tl;
if(!is_bitstring(&el->hd, &bits))
goto errret;
pub = asn1toRSApub(bits->data, bits->len);
if(pub == nil)
goto errret;
errret:
freevalfields(&ereq.val);
return pub;
}
static void
digestSPKI(int alg, uchar *pubkey, int npubkey, DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*), uchar *digest)
{