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*); void rsaprivfree(RSApriv*);
RSApub* rsaprivtopub(RSApriv*); RSApub* rsaprivtopub(RSApriv*);
RSApub* X509toRSApub(uchar*, int, char*, int); RSApub* X509toRSApub(uchar*, int, char*, int);
RSApub* X509reqtoRSApub(uchar*, int, char*, int);
RSApriv* asn1toRSApriv(uchar*, int); RSApriv* asn1toRSApriv(uchar*, int);
RSApub* asn1toRSApub(uchar*, int); RSApub* asn1toRSApub(uchar*, int);
void asn1dump(uchar *der, int len); void asn1dump(uchar *der, int len);

View file

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

View file

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

View file

@ -35,6 +35,7 @@ TARG=\
userpasswd\ userpasswd\
warning\ warning\
wrkey\ wrkey\
x5092pub\
DIRS=\ DIRS=\
factotum\ 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; int signature_alg;
Bits* signature; Bits* signature;
int curve; int curve;
Bytes* ext;
} CertX509; } CertX509;
/* Algorithm object-ids */ /* Algorithm object-ids */
@ -1724,6 +1725,8 @@ static void (*namedcurves[])(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, m
nil, nil,
}; };
static void appendaltnames(char *name, int nname, Bytes *ext, int req);
static void static void
freecert(CertX509* c) freecert(CertX509* c)
{ {
@ -1735,6 +1738,7 @@ freecert(CertX509* c)
free(c->subject); free(c->subject);
freebits(c->publickey); freebits(c->publickey);
freebits(c->signature); freebits(c->signature);
freebytes(c->ext);
free(c); free(c);
} }
@ -1863,6 +1867,7 @@ decode_cert(uchar *buf, int len)
c->publickey = nil; c->publickey = nil;
c->signature_alg = -1; c->signature_alg = -1;
c->signature = nil; c->signature = nil;
c->ext = nil;
/* Certificate */ /* Certificate */
if(!is_seq(&ecert, &elcert) || elistlen(elcert) !=3) if(!is_seq(&ecert, &elcert) || elistlen(elcert) !=3)
@ -1900,6 +1905,10 @@ decode_cert(uchar *buf, int len)
esubj = &el->hd; esubj = &el->hd;
el = el->tl; el = el->tl;
epubkey = &el->hd; 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_int(eserial, &c->serial)) {
if(!is_bigint(eserial, &b)) if(!is_bigint(eserial, &b))
goto errret; goto errret;
@ -2260,6 +2269,7 @@ X509toECpub(uchar *cert, int ncert, char *name, int nname, ECdomain *dom)
if(c == nil) if(c == nil)
return nil; return nil;
copysubject(name, nname, c->subject); copysubject(name, nname, c->subject);
appendaltnames(name, nname, c->ext, 0);
pub = nil; pub = nil;
if(c->publickey_alg == ALG_ecPublicKey){ if(c->publickey_alg == ALG_ecPublicKey){
ecdominit(dom, namedcurves[c->curve]); ecdominit(dom, namedcurves[c->curve]);
@ -2302,6 +2312,7 @@ X509toRSApub(uchar *cert, int ncert, char *name, int nname)
if(c == nil) if(c == nil)
return nil; return nil;
copysubject(name, nname, c->subject); copysubject(name, nname, c->subject);
appendaltnames(name, nname, c->ext, 0);
pub = nil; pub = nil;
if(c->publickey_alg == ALG_rsaEncryption) if(c->publickey_alg == ALG_rsaEncryption)
pub = asn1toRSApub(c->publickey->data, c->publickey->len); 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 Ints15 oid_extensionRequest = { 7, 1, 2, 840, 113549, 1, 9, 14};
static Elist* static Elist*
mkextensions(char *alts, int req) mkextensions(char *alts, int isreq)
{ {
Elist *sl, *xl; Elist *sl, *xl;
@ -2653,12 +2664,12 @@ mkextensions(char *alts, int req)
xl = mkextel(mkseq(sl), (Ints*)&oid_subjectAltName, xl); xl = mkextel(mkseq(sl), (Ints*)&oid_subjectAltName, xl);
if(xl != nil){ if(xl != nil){
xl = mkel(mkseq(xl), nil); xl = mkel(mkseq(xl), nil);
if(req) if(isreq)
xl = mkel(mkseq( xl = mkel(mkseq(
mkel(mkoid((Ints*)&oid_extensionRequest), mkel(mkoid((Ints*)&oid_extensionRequest),
mkel(mkset(xl), nil))), nil); mkel(mkset(xl), nil))), nil);
} }
if(req) if(isreq)
xl = mkel(mkcont(0, xl), nil); xl = mkel(mkcont(0, xl), nil);
else if(xl != nil) else if(xl != nil)
xl = mkel(mkcont(3, xl), nil); xl = mkel(mkcont(3, xl), nil);
@ -2681,6 +2692,85 @@ splitalts(char *s)
return nil; 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* static Bytes*
encode_rsapubkey(RSApub *pk) encode_rsapubkey(RSApub *pk)
{ {
@ -2885,6 +2975,46 @@ errret:
return cert; 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 static void
digestSPKI(int alg, uchar *pubkey, int npubkey, DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*), uchar *digest) digestSPKI(int alg, uchar *pubkey, int npubkey, DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*), uchar *digest)
{ {