Unified · Split
| @@ -1,3 +1,6 @@ | ||
| 1 | +20081021 | |
| 2 | + /appl/cmd/auth/ai2key.b new command to convert authinfo files to factotum keys | |
| 3 | + /appl/cmd/auth/factotum/proto/infauth.b allow new key format | |
| 1 | 4 | 20081019 |
| 2 | 5 | /appl/cmd/man2html.b implement "\ " => " " |
| 3 | 6 | /man/3/prog change way a literal " was formatted |
| @@ -0,0 +1,144 @@ | ||
| 1 | +implement Ai2fact; | |
| 2 | + | |
| 3 | +# authinfo to factotum key set | |
| 4 | +# intermediate version, for use until revised Inferno authentication is ready | |
| 5 | + | |
| 6 | + | |
| 7 | +# converts an old authinfo entry in keyring directory to a key for factotum | |
| 8 | +# | |
| 9 | +# keys are in proto=infauth, and include the data for the signed certificate, and the diffie-helman parameters | |
| 10 | + | |
| 11 | +include "sys.m"; | |
| 12 | + sys: Sys; | |
| 13 | + | |
| 14 | +include "draw.m"; | |
| 15 | + | |
| 16 | +include "keyring.m"; | |
| 17 | + keyring: Keyring; | |
| 18 | + Certificate, IPint, PK, SK: import keyring; | |
| 19 | + | |
| 20 | +include "daytime.m"; | |
| 21 | + daytime: Daytime; | |
| 22 | + | |
| 23 | +include "arg.m"; | |
| 24 | + | |
| 25 | +Ai2fact: module | |
| 26 | +{ | |
| 27 | + init: fn(nil: ref Draw->Context, nil: list of string); | |
| 28 | +}; | |
| 29 | + | |
| 30 | +init(nil: ref Draw->Context, args: list of string) | |
| 31 | +{ | |
| 32 | + sys = load Sys Sys->PATH; | |
| 33 | + keyring = load Keyring Keyring->PATH; | |
| 34 | + daytime = load Daytime Daytime->PATH; | |
| 35 | + | |
| 36 | + arg := load Arg Arg->PATH; | |
| 37 | + arg->init(args); | |
| 38 | + arg->setusage("ai2key [-t 'attr=value attr=value ...'] keyfile ..."); | |
| 39 | + tag: string; | |
| 40 | + while((o := arg->opt()) != 0) | |
| 41 | + case o { | |
| 42 | + 't' => | |
| 43 | + tag = arg->earg(); | |
| 44 | + * => | |
| 45 | + arg->usage(); | |
| 46 | + } | |
| 47 | + args = arg->argv(); | |
| 48 | + if(args == nil) | |
| 49 | + arg->usage(); | |
| 50 | + arg = nil; | |
| 51 | + | |
| 52 | + now := daytime->now(); | |
| 53 | + for(; args != nil; args = tl args){ | |
| 54 | + keyfile := hd args; | |
| 55 | + ai := keyring->readauthinfo(keyfile); | |
| 56 | + if(ai == nil) | |
| 57 | + error(sys->sprint("cannot read %s: %r", keyfile)); | |
| 58 | + if(ai.cert.exp != 0 && ai.cert.exp <= now){ | |
| 59 | + sys->fprint(sys->fildes(2), "ai2key: %s: certificate expired -- key ignored\n", keyfile); | |
| 60 | + continue; | |
| 61 | + } | |
| 62 | + | |
| 63 | + if(ai.cert.exp != 0) | |
| 64 | + expires := sys->sprint(" expires=%ud", ai.cert.exp); | |
| 65 | + ha := ai.cert.ha; | |
| 66 | + if(ha == "sha") | |
| 67 | + ha = "sha1"; | |
| 68 | + | |
| 69 | + if(tag != nil) | |
| 70 | + tag = " "+tag; | |
| 71 | + | |
| 72 | + sys->print("key proto=infauth%s %s sigalg=%s-%s user=%q signer=%q pk=%s !sk=%s spk=%s cert=%s dh-alpha=%s dh-p=%s%s\n", | |
| 73 | + tag, locations(filename(keyfile)), ai.cert.sa.name, ha, ai.mypk.owner, ai.spk.owner, pktostr(ai.mypk), sktostr(ai.mysk), | |
| 74 | + pktostr(ai.spk), certtostr(ai.cert), ai.alpha.iptostr(16), ai.p.iptostr(16), expires); | |
| 75 | + } | |
| 76 | +} | |
| 77 | + | |
| 78 | +error(e: string) | |
| 79 | +{ | |
| 80 | + sys->fprint(sys->fildes(2), "ai2key: %s\n", e); | |
| 81 | + raise "fail:error"; | |
| 82 | +} | |
| 83 | + | |
| 84 | +filename(s: string): string | |
| 85 | +{ | |
| 86 | + (nil, fld) := sys->tokenize(s, "/"); | |
| 87 | + for(; fld != nil && tl fld != nil; fld = tl fld){ | |
| 88 | + # skip | |
| 89 | + } | |
| 90 | + return hd fld; | |
| 91 | +} | |
| 92 | + | |
| 93 | +# guess plausible domain, server and service attributes from the file name | |
| 94 | +locations(file: string): string | |
| 95 | +{ | |
| 96 | + if(file == "default") | |
| 97 | + return "dom=* server=*"; | |
| 98 | + (nf, flds) := sys->tokenize(file, "!"); | |
| 99 | + case nf { | |
| 100 | + * => | |
| 101 | + return sys->sprint("%s", server(file)); | |
| 102 | + 2 => | |
| 103 | + return sys->sprint("%s", server(hd tl flds)); | |
| 104 | + 3 => | |
| 105 | + # ignore network component | |
| 106 | + return sys->sprint("%s service=%q", server(hd tl flds), hd tl tl flds); | |
| 107 | + } | |
| 108 | +} | |
| 109 | + | |
| 110 | +server(name: string): string | |
| 111 | +{ | |
| 112 | + # if the name contains dot(s), we'll treat it as a domain name | |
| 113 | + if(sys->tokenize(name, ".").t0 > 1) | |
| 114 | + return sys->sprint("dom=%q server=%q", name, name); | |
| 115 | + return sys->sprint("server=%q", name); | |
| 116 | +} | |
| 117 | + | |
| 118 | +certtostr(c: ref Certificate): string | |
| 119 | +{ | |
| 120 | + return dnl(keyring->certtostr(c)); | |
| 121 | +} | |
| 122 | + | |
| 123 | +pktostr(pk: ref PK): string | |
| 124 | +{ | |
| 125 | + return dnl(keyring->pktostr(pk)); | |
| 126 | +} | |
| 127 | + | |
| 128 | +sktostr(sk: ref SK): string | |
| 129 | +{ | |
| 130 | + return dnl(keyring->sktostr(sk)); | |
| 131 | +} | |
| 132 | + | |
| 133 | +dnl(s: string): string | |
| 134 | +{ | |
| 135 | + for(i := 0; i < len s; i++) | |
| 136 | + if(s[i] == '\n') | |
| 137 | + s[i] = '^'; | |
| 138 | + while(--i > 0 && s[i] == '^'){ | |
| 139 | + # skip | |
| 140 | + } | |
| 141 | + if(i != len s) | |
| 142 | + return s[0: i+1]; | |
| 143 | + return s; | |
| 144 | +} | |
| @@ -139,9 +139,8 @@ interaction(attrs: list of ref Attr, io: ref IO): string · 138 unmodified lines | ||
| 139 | 139 | senderr(io, e); |
| 140 | 140 | break; |
| 141 | 141 | Error1 => |
| 142 | − senderr(io, "missing your authentication data"); | |
| 143 | − x: string = e; | |
| 144 | − return "remote: "+x; | |
| 142 | + senderr(io, "failed"); # acknowledge error | |
| 143 | + return remote(e); | |
| 145 | 144 | } |
| 146 | 145 | |
| 147 | 146 | { |
| @@ -151,8 +150,7 @@ interaction(attrs: list of ref Attr, io: ref IO): string · 3 unmodified lines | ||
| 151 | 150 | Error0 => |
| 152 | 151 | return e; |
| 153 | 152 | Error1 => |
| 154 | − x: string = e; | |
| 155 | − return "remote: "+x; | |
| 153 | + return remote(e); | |
| 156 | 154 | } |
| 157 | 155 | if(err != nil) |
| 158 | 156 | return err; |
| @@ -160,13 +158,26 @@ interaction(attrs: list of ref Attr, io: ref IO): string · 1 unmodified line | ||
| 160 | 158 | return negotiatecrypto(io, key, ai, rattrs); |
| 161 | 159 | } |
| 162 | 160 | |
| 161 | +remote(s: string): string | |
| 162 | +{ | |
| 163 | + # account for strange earlier interface | |
| 164 | + if(len s < 6 || s[0: 6] != "remote") | |
| 165 | + return "remote: "+s; | |
| 166 | + return s; | |
| 167 | +} | |
| 168 | + | |
| 169 | +# TO DO: exchange attr/value pairs, covered by hmac (use part of secret up to hmac block size of 64 bytes) | |
| 170 | +# the old scheme can be distinguished either by a prefix "attrs " or simply because the string contains "=", | |
| 171 | +# and the server side can then reply. the hmac is to prevent tampering. | |
| 163 | 172 | negotiatecrypto(io: ref IO, key: ref Key, ai: ref Authinfo, attrs: list of ref Sexp): string |
| 164 | 173 | { |
| 165 | 174 | role := authio->lookattrval(key.attrs, "role"); |
| 166 | 175 | alg: string; |
| 167 | 176 | { |
| 168 | 177 | if(role == "client"){ |
| 169 | − alg = authio->lookattrval(key.attrs, "alg"); | |
| 178 | + alg = authio->lookattrval(key.attrs, ":alg"); | |
| 179 | + if(alg == nil) | |
| 180 | + alg = authio->lookattrval(key.attrs, "alg"); # old way | |
| 170 | 181 | if(alg == nil) |
| 171 | 182 | alg = "md5/rc4_256"; |
| 172 | 183 | sendmsg(io, array of byte alg); |
| @@ -297,7 +308,8 @@ getmsg(io: ref IO): array of byte raises (Error0, Error1) · 124 unmodified lines | ||
| 297 | 308 | if(len buf != m) |
| 298 | 309 | raise Error0("io error: (impossible?) msg length " + string m); |
| 299 | 310 | if(h[0] == '!'){ |
| 300 | −sys->print("got remote error: %s, len %d\n", string buf, len string buf); | |
| 311 | + if(0) | |
| 312 | + sys->print("got remote error: %q, len %d\n", string buf, len string buf); | |
| 301 | 313 | raise Error1(string buf); |
| 302 | 314 | } |
| 303 | 315 | return buf; |
| @@ -318,16 +330,46 @@ senderr(io: ref IO, e: string) · 14 unmodified lines | ||
| 318 | 330 | io.write(buf, len buf); |
| 319 | 331 | } |
| 320 | 332 | |
| 333 | +# both the s-expression and k=v form are interim, until all | |
| 334 | +# the factotum implementations can manage public keys | |
| 335 | +# the s-expression form was the original one used by Inferno factotum | |
| 336 | +# the form in which Authinfo components are separate attributes is the | |
| 337 | +# one now used by Plan 9 and Plan 9 Ports factotum implementations | |
| 321 | 338 | keytoauthinfo(key:ref Key): (ref Keyring->Authinfo, string) |
| 322 | 339 | { |
| 323 | − if((s := authio->lookattrval(key.secrets, "!authinfo")) == nil){ | |
| 324 | − # XXX could look up authinfo by hash at this point | |
| 325 | − return (nil, "no authinfo attribute"); | |
| 326 | − } | |
| 340 | + if((s := authio->lookattrval(key.secrets, "!authinfo")) != nil) | |
| 341 | + return strtoauthinfo(s); | |
| 342 | + # TO DO: could look up authinfo by hash | |
| 343 | + ai := ref Keyring->Authinfo; | |
| 344 | + if((s = kv(key.secrets, "!sk")) == nil || (ai.mysk = keyring->strtosk(s)) == nil) | |
| 345 | + return (nil, "bad secret key"); | |
| 346 | + if((s = kv(key.attrs, "pk")) == nil || (ai.mypk = keyring->strtopk(s)) == nil) | |
| 347 | + return (nil, "bad public key"); | |
| 348 | + if((s = kv(key.attrs, "cert")) == nil || (ai.cert = keyring->strtocert(s)) == nil) | |
| 349 | + return (nil, "bad certificate"); | |
| 350 | + if((s = kv(key.attrs, "spk")) == nil || (ai.spk = keyring->strtopk(s)) == nil) | |
| 351 | + return (nil, "bad signer public key"); | |
| 352 | + if((s = kv(key.attrs, "dh-alpha")) == nil || (ai.alpha = IPint.strtoip(s, 16)) == nil) | |
| 353 | + return (nil, "bad value for alpha"); | |
| 354 | + if((s = kv(key.attrs, "dh-p")) == nil || (ai.p = IPint.strtoip(s, 16)) == nil) | |
| 355 | + return (nil, "bad value for p"); | |
| 356 | + return (ai, nil); | |
| 357 | +} | |
| 327 | 358 | |
| 328 | − return strtoauthinfo(s); | |
| 359 | +kv(a: list of ref Attr, name: string): string | |
| 360 | +{ | |
| 361 | + return rnl(authio->lookattrval(a, name)); | |
| 362 | +} | |
| 363 | + | |
| 364 | +rnl(s: string): string | |
| 365 | +{ | |
| 366 | + for(i := 0; i < len s; i++) | |
| 367 | + if(s[i] == '^') | |
| 368 | + s[i] = '\n'; | |
| 369 | + return s; | |
| 329 | 370 | } |
| 330 | 371 | |
| 372 | +# s-expression form | |
| 331 | 373 | strtoauthinfo(s: string): (ref Keyring->Authinfo, string) |
| 332 | 374 | { |
| 333 | 375 | (se, err, nil) := Sexp.parse(s); |
| @@ -5,6 +5,7 @@ DIRS=\ · 4 unmodified lines | ||
| 5 | 5 | |
| 6 | 6 | TARG=\ |
| 7 | 7 | aescbc.dis\ |
| 8 | + ai2key.dis\ | |
| 8 | 9 | changelogin.dis\ |
| 9 | 10 | countersigner.dis\ |
| 10 | 11 | convpasswd.dis\ |
Binary file not shown.
Binary file not shown.
| @@ -1696,7 +1696,7 @@ if(0)print("Y"); · 1695 unmodified lines | ||
| 1696 | 1696 | buf[n] = 0; |
| 1697 | 1697 | hiscert = strtocert(buf); |
| 1698 | 1698 | if(hiscert == H){ |
| 1699 | − err = "bad certificate"; | |
| 1699 | + err = "bad certificate syntax"; | |
| 1700 | 1700 | goto out; |
| 1701 | 1701 | } |
| 1702 | 1702 | certimmutable(hiscert); /* hide from the garbage collector */ |
| @@ -1,4 +1,5 @@ | ||
| 1 | 1 | intro 0intro |
| 2 | +ai2key ai2key | |
| 2 | 3 | applylog applylog |
| 3 | 4 | updatelog applylog |
| 4 | 5 | bootpd bootpd |
| @@ -15,6 +16,7 @@ csquery cs · 10 unmodified lines | ||
| 15 | 16 | dhcp dhcp |
| 16 | 17 | dns dns |
| 17 | 18 | dnsquery dns |
| 19 | +dsagen ai2key | |
| 18 | 20 | fpgaload fpgaload |
| 19 | 21 | ftl ftl |
| 20 | 22 | getauthinfo getauthinfo |
| @@ -39,6 +41,7 @@ prep prep · 18 unmodified lines | ||
| 39 | 41 | rdbgsrv rdbgsrv |
| 40 | 42 | register register |
| 41 | 43 | rip rip |
| 44 | +rsagen ai2key | |
| 42 | 45 | rstyxd rstyxd |
| 43 | 46 | styxd rstyxd |
| 44 | 47 | shutdown shutdown |
| @@ -0,0 +1,262 @@ | ||
| 1 | +.TH AI2KEY 8 | |
| 2 | +.SH NAME | |
| 3 | +ai2key, dsagen, rsagen \- generate and reformat public keys | |
| 4 | +.SH SYNOPSIS | |
| 5 | +.B ai2key | |
| 6 | +[ | |
| 7 | +.BI -t " tag" | |
| 8 | +] | |
| 9 | +.I keyfile | |
| 10 | +... | |
| 11 | +.P | |
| 12 | +.B dsagen | |
| 13 | +[ | |
| 14 | +.BI -t " tag" | |
| 15 | +] | |
| 16 | +.PP | |
| 17 | +.B rsagen | |
| 18 | +[ | |
| 19 | +.BI -b " nbits" | |
| 20 | +] [ | |
| 21 | +.BI -t " tag" | |
| 22 | +] | |
| 23 | +.SH DESCRIPTION | |
| 24 | +.IR Factotum (4) | |
| 25 | +represents public keys as lists of attribute-value pairs, each key on a single line prefixed with the string | |
| 26 | +.BR key . | |
| 27 | +.PP | |
| 28 | +.I Ai2key | |
| 29 | +converts the original Inferno representation of authentication data, | |
| 30 | +in the format defined for | |
| 31 | +.B authinfo | |
| 32 | +by | |
| 33 | +.IR keytext (6), | |
| 34 | +to an attribute-value format accepted by | |
| 35 | +.IR factotum (4) | |
| 36 | +for the | |
| 37 | +.B infauth | |
| 38 | +authentication protocol. | |
| 39 | +For each | |
| 40 | +.I keyfile | |
| 41 | +it writes a single line on standard output, containing | |
| 42 | +the following fields: | |
| 43 | +.IP | |
| 44 | +.EX | |
| 45 | +.fi | |
| 46 | +.ti -3n | |
| 47 | +key proto=infauth | |
| 48 | +[ | |
| 49 | +.I tag | |
| 50 | +] | |
| 51 | +.BI "sigalg=" pkalg - hashalg | |
| 52 | +[ | |
| 53 | +.BI dom= host | |
| 54 | +] | |
| 55 | +.BI server= host | |
| 56 | +[ | |
| 57 | +.BI service= svc | |
| 58 | +] | |
| 59 | +.BI "user=" name | |
| 60 | +.BI "signer=" name | |
| 61 | +.BI "pk=" pk | |
| 62 | +.BI "!sk=" sk | |
| 63 | +.BI spk= pk | |
| 64 | +.BI cert= cert | |
| 65 | +.BI dh-alpha= hex | |
| 66 | +.BI dh-p= hex | |
| 67 | +.EE | |
| 68 | +.PP | |
| 69 | +where | |
| 70 | +.RS | |
| 71 | +.TP 15n | |
| 72 | +.I pkalg | |
| 73 | +is | |
| 74 | +.BR dsa , | |
| 75 | +.B elgamal | |
| 76 | +or | |
| 77 | +.BR rsa | |
| 78 | +.PD 0 | |
| 79 | +.ns | |
| 80 | +.TP | |
| 81 | +.I hashalg | |
| 82 | +is | |
| 83 | +.B md5 | |
| 84 | +or | |
| 85 | +.BR sha1 | |
| 86 | +.br | |
| 87 | +.ns | |
| 88 | +.TP | |
| 89 | +.B user | |
| 90 | +is the user name associated with the key, as vouched for by the supporting | |
| 91 | +certificate | |
| 92 | +.BR cert | |
| 93 | +.br | |
| 94 | +.ns | |
| 95 | +.TP | |
| 96 | +.B signer | |
| 97 | +is the user name associated with the key that signed the certificate | |
| 98 | +.br | |
| 99 | +.ns | |
| 100 | +.TP | |
| 101 | +.B pk | |
| 102 | +is the user's public key | |
| 103 | +.br | |
| 104 | +.ns | |
| 105 | +.TP | |
| 106 | +.B !sk | |
| 107 | +is the user's private (secret) key | |
| 108 | +.br | |
| 109 | +.ns | |
| 110 | +.TP | |
| 111 | +.B spk | |
| 112 | +is the signer's public key | |
| 113 | +.br | |
| 114 | +.ns | |
| 115 | +.TP | |
| 116 | +.B cert | |
| 117 | +is the | |
| 118 | +.I certificate | |
| 119 | +.br | |
| 120 | +.ns | |
| 121 | +.TP | |
| 122 | +.BR dh-alpha ,\ dh-p | |
| 123 | +are the Diffie-Hellman parameters shared by the user and file servers. | |
| 124 | +.RE | |
| 125 | +.PD | |
| 126 | +.PP | |
| 127 | +The key is tagged by one or more of | |
| 128 | +.BR dom , | |
| 129 | +.B server | |
| 130 | +and | |
| 131 | +.BR service , | |
| 132 | +derived from the file name | |
| 133 | +.IR keyfile . | |
| 134 | +The server is | |
| 135 | +.RB ` * ' | |
| 136 | +if | |
| 137 | +.I keyfile | |
| 138 | +is | |
| 139 | +.BR default . | |
| 140 | +Otherwise | |
| 141 | +.I keyfile | |
| 142 | +has the form | |
| 143 | +.IP | |
| 144 | +[ | |
| 145 | +.IB net ! | |
| 146 | +] | |
| 147 | +.I host | |
| 148 | +[ | |
| 149 | +.BI ! srv | |
| 150 | +] | |
| 151 | +.PP | |
| 152 | +and | |
| 153 | +.B server | |
| 154 | +and | |
| 155 | +.B service | |
| 156 | +are set accordingly; | |
| 157 | +.B dom | |
| 158 | +is set if | |
| 159 | +.I host | |
| 160 | +looks like a domain name. | |
| 161 | +Key and certificate values have the form defined in | |
| 162 | +.IR keytext (6); | |
| 163 | +.I hex | |
| 164 | +is a large number in hexadecimal. | |
| 165 | +.PP | |
| 166 | +.I Dsagen | |
| 167 | +prints a randomly-generated DSA private key using the NIST-recommended algorithm. | |
| 168 | +If | |
| 169 | +.I tag | |
| 170 | +text is specified, it is printed after the | |
| 171 | +.B proto | |
| 172 | +attribute-value pair. | |
| 173 | +Typically, | |
| 174 | +.I tag | |
| 175 | +is a sequence of attribute-value comments describing the key. | |
| 176 | +A DSA key has the following attributes | |
| 177 | +.RS | |
| 178 | +.TP 8n | |
| 179 | +.B p | |
| 180 | +prime public modulus | |
| 181 | +.PD 0 | |
| 182 | +.TP | |
| 183 | +.B q | |
| 184 | +prime group order; divides | |
| 185 | +.BR p -1 | |
| 186 | +.TP | |
| 187 | +.B alpha | |
| 188 | +group generator | |
| 189 | +.TP | |
| 190 | +.B key | |
| 191 | +.BR alpha ^ !secret | |
| 192 | +mod | |
| 193 | +.B p | |
| 194 | +.TP | |
| 195 | +.B !secret | |
| 196 | +the secret exponent | |
| 197 | +.RE | |
| 198 | +.PD | |
| 199 | +.PP | |
| 200 | +.I Rsagen | |
| 201 | +prints a randomly generated RSA private key | |
| 202 | +whose | |
| 203 | +.B n | |
| 204 | +has exactly | |
| 205 | +.I nbits | |
| 206 | +(default 1024) | |
| 207 | +significant bits. | |
| 208 | +The key has the following attributes: | |
| 209 | +.RS | |
| 210 | +.TP | |
| 211 | +.B size | |
| 212 | +the number of significant bits in | |
| 213 | +.B n | |
| 214 | +.PD 0 | |
| 215 | +.TP | |
| 216 | +.B ek | |
| 217 | +the encryption exponent | |
| 218 | +.TP | |
| 219 | +.B n | |
| 220 | +the product of | |
| 221 | +.B !p | |
| 222 | +and | |
| 223 | +.B !q | |
| 224 | +.TP | |
| 225 | +.B !dk | |
| 226 | +the decryption exponent | |
| 227 | +.TP | |
| 228 | +.B !p | |
| 229 | +a large prime | |
| 230 | +.TP | |
| 231 | +.B !q | |
| 232 | +another large prime | |
| 233 | +.TP | |
| 234 | +.B "!kp\fR, \fL!kq\fR, \fL!c2 | |
| 235 | +parameters derived from the other attributes, cached to speed decryption | |
| 236 | +.RE | |
| 237 | +.PD | |
| 238 | +.PP | |
| 239 | +All the numbers in | |
| 240 | +.I dsagen | |
| 241 | +and | |
| 242 | +.I rsagen | |
| 243 | +output are in hexadecimal except RSA's | |
| 244 | +.BR size , | |
| 245 | +which is decimal. | |
| 246 | +A public key omits the attributes beginning with | |
| 247 | +.L ! . | |
| 248 | +A key may have other attributes as well, for example a | |
| 249 | +.B service | |
| 250 | +attribute identifying how this key is typically used, | |
| 251 | +but to these utilities such attributes are merely comments. | |
| 252 | +They can be provided in a | |
| 253 | +.I tag | |
| 254 | +argument. | |
| 255 | +.SH SOURCE | |
| 256 | +.B /appl/cmd/auth/ai2key.b | |
| 257 | +.br | |
| 258 | +.B /appl/cmd/auth/dsagen.b | |
| 259 | +.br | |
| 260 | +.B /appl/cmd/auth/rsagen.b | |
| 261 | +.SH "SEE ALSO" | |
| 262 | +.IR factotum (4) | |