package plan9.auth; /* * Plan 9 public key authentication, based on Inferno's public key authentication * * Copyright © 2005 Vita Nuova Holdings Limited * Revisions © 2012 Coraid Inc * * to do * secstore interface */ import java.math.BigInteger; import java.security.interfaces.*; import java.security.spec.*; import java.nio.*; import java.nio.channels.*; import java.io.IOException; import javax.crypto.Cipher; import java.io.*; import java.util.Date; import java.util.Arrays; import java.util.ArrayList; import plan9.lib.Attr; import plan9.lib.Attrs; import plan9.lib.Key; import plan9.lib.Keys; import plan9.lib.Packer; import plan9.lib.Strings; import plan9.lib.Base16; import plan9.lib.Encoding; import plan9.lib.Base64; import plan9.lib.Strings; import plan9.lib.Msgio; import plan9.lib.RemoteError; import plan9.LogFactory; import plan9.Log; import static plan9.auth.Pki.*; import static plan9.auth.Aids.*; import static plan9.lib.Strings.bytes; public class P9pk1 { static final Log log = LogFactory.logger(P9pk1.class); static final Encoding base64 = new Base64(); static final Encoding base16 = new Base16(); // alpha and modulus from RFC2409 static final BigInteger dh_alpha = new BigInteger("2"); static final BigInteger dh_p = new BigInteger(1, base16.dec("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF")); public P9pk1(){} public String name(){ return "p9pk1"; } public String init(AuthIO io){ return null; } public final String interaction(AuthIO io, Attrs attrs) throws AuthenticationException { Authinfo info, hisinfo; BigInteger low, r0, alphar0, alphar1, alphar0r1; Certificate hiscert; Signature alphasig; byte[] buf, hispkbuf, alphabuf; PK hispk; byte[] secret; String s, dom; Verifier verifier = new Verifier(); String role = attrs.findattrval("role"); if(role == null) return "role not specified"; if(!role.equals("client") && !role.equals("server")) return "unknown role: "+role; dom = attrs.findattrval("dom"); if(dom == null) return "unknown domain"; info = getauthinfo(io, dom, attrs); // must extract from attrs, key try{ io.sendmsg("pk v.1"); // version s = io.gets(); String[] flds = Strings.getfields(s, "."); if(flds.length < 2 || !flds[0].equals("pk") || !flds[1].equals("v.1")) throw new LocalAuthErr("incompatible authentication protocol: "+s); // check info here, not above, so that the peer also sees the diagnostic checkinfo(info); verifier.addkey(info.user); verifier.addkey(info.signer); low = dh_p.shiftRight(dh_p.bitLength()/4); r0 = rand(low, dh_p); alphar0 = dh_alpha.modPow(r0, dh_p); io.sendmsg(b64(alphar0)); // send a sequence, at least: mypk, (signer, cert, sig)* // public keys must be sent if recipient can't be assumed to know them io.sendmsg(info.user.pk.toString()); io.sendmsg(info.signer.pk.toString()); // needs to be a sequence (Issuer? Subject? Cert Sig)+ in general io.sendmsg(info.cert.toString()); io.sendmsg(info.sig.toString()); io.sendmsg(""); // end of sequence alphar1 = s2big64(io.gets()); if(dh_p.compareTo(alphar1) <= 0) throw new LocalAuthErr("implausible parameter value"); if(alphar0.compareTo(alphar1) == 0) throw new LocalAuthErr("possible replay attack"); // in general, load a sequence, then interpret it // load any keys that arrive into pki? temporary store? // check that keys exist for each cert and sig // discard any cert that fails validity or signature // apply rewrite rules and draw conclusion // need: pk, name cert, signature, check that hash of pk matches hash in cert // need: spk (known?) ArrayList seq = new ArrayList(); for(;;){ s = io.gets(); if(s == null || s.equals("")) break; Attrs a = new Attrs(s); if(a.findattr("sig") != null){ seq.add(new Signature(a)); }else if(a.findattr("cert") != null){ seq.add(new Certificate(a)); }else if(a.findattr("key") != null){ Key k = new Key(a); if(k.secrets.length() > 0) verifier.addkey(Pki.parseSK(k).pk()); else verifier.addkey(Pki.parsePK(k)); }else{ log.debug("unknown sequence item: %s", s); } } hiscert = verifier.verify(seq); // summary certificate if(hiscert.subject.principal == null) hispk = hiscert.issuer.principal.pk; // was relative name (TO DO: check) else hispk = hiscert.subject.principal.pk; if(hispk == null) throw new LocalAuthErr("no public key for subject of remote certificate: "+hiscert.subject); // exchange signatures of alphas to check immediate possession of private keys alphabuf = bytes(b64(alphar0) + b64(alphar1)); alphasig = sign(alphabuf, info.user.sk); io.sendmsg(alphasig.toString()); alphasig = new Signature(io.gets()); alphabuf = bytes(b64(alphar1) + b64(alphar0)); if(!verify(alphabuf, alphasig, hispk)) throw new LocalAuthErr("signature did not match pk"); alphar0r1 = alphar1.modPow(r0, dh_p); secret = trim0(alphar0r1.toByteArray()); io.sendmsg("OK"); // }catch(InvalidCertificate e){ // io.senderrmsg("remote: "+e.getMessage()); // throw e; // }catch(InvalidKey e){ // io.senderrmsg("remote: "+e.getMessage()); // throw e; }catch(LocalAuthErr e){ io.senderrmsg("remote: "+e.getMessage()); throw e; }catch(RemoteAuthErr e){ io.senderrmsg("missing your authentication data"); // strange but true throw new AuthenticationException(e.getMessage(), e); } try{ do{ s = io.gets(); }while(!s.equals("OK")); }catch(LocalAuthErr e){ throw new AuthenticationException(e.getMessage(), e); }catch(RemoteAuthErr e){ throw new AuthenticationException("remote: "+e.getMessage(), e); } // TO DO: add remote pk, spk, names, etc io.done(new Attrs(new Attr("secret", (new Base16()).enc(secret)), new Attr("suid", "admin"), new Attr("cuid", "admin"), new Attr("cap", ""))); // return new AuthResult(new Authinfo(null, hispk, hiscert, info.spk), secret); return null; } static class Authinfo { Principal user; // current user's public and private keys Principal signer; // cert's issuer's public key Name issuer; // issuer name Certificate cert; Signature sig; // cert's signature Authinfo(Principal user, Principal signer, Name issuer, Certificate cert, Signature sig){ this.user = user; this.signer = signer; this.issuer = issuer; this.cert = cert; this.sig = sig; } } static final void checkinfo(Authinfo info) throws LocalAuthErr { if(info == null) throw new LocalAuthErr("no authentication information"); if(info.user == null || info.signer == null || info.issuer == null || !info.issuer.isLocalName() || info.cert == null || info.sig == null) // could check key details throw new LocalAuthErr("invalid authentication information"); } // fetch the output from the prover final Authinfo getauthinfo(AuthIO io, String dom, Attrs attrs) throws AuthenticationException { // signer's public key, certificate, secret key (use sk.getpk to get public one), alpha, p Principal user, signer; Certificate cert; Signature sig; String username; Name issuer; username = attrs.findattrval("user"); if(username == null) throw new AuthenticationException("user not named for authentication in domain "+dom); try{ Key k = io.findkey(null, String.format("dom=%s proto=p9pk1 user=%s", dom, username)); SK sk = Pki.parseSK(k); user = new Principal(sk.pk(), sk, null); }catch(NeedKey e){ throw new AuthenticationException("can't find key: "+e.getMessage()); } try{ Key k = io.findkey(null, String.format("dom=%s proto=p9pk1 role=signer", dom)); PK spk = Pki.parsePK(k); signer = new Principal(spk); }catch(NeedKey e){ throw new AuthenticationException("can't find signer's key for domain "+dom); } issuer = new Name(signer, username); try{ Key k = io.findkey(null, String.format("proto=p9pk1 cert=name issuer=%s subject=%s", issuer, user)); cert = new Certificate(k.visible); }catch(NeedKey e){ throw new AuthenticationException(String.format("can't find certificate: issuer %s subject %s", issuer, user)); } try{ Key k = io.findkey(null, String.format("proto=p9pk1 sig signer=%s", signer)); // could add data=hash(cert) sig = new Signature(k.visible); }catch(NeedKey e){ throw new AuthenticationException("can't find signature by signer: "+signer); } return new Authinfo(user, signer, issuer, cert, sig); } private static int now(){ return (int)((new Date()).getTime()/1000); } public static final BigInteger rand(BigInteger p, BigInteger q) throws CryptoError { if(p.compareTo(q) > 0){ BigInteger t = p; p = q; q = t; } BigInteger diff = q.subtract(p); BigInteger two = BigInteger.ONE.add(BigInteger.ONE); if(diff.compareTo(two) < 0) throw new CryptoError("random number range must be at least two"); int l = diff.bitLength(); BigInteger t = BigInteger.ONE.shiftLeft(l); l = (l + 7) & ~7; // nearest byte BigInteger slop = t.mod(diff); BigInteger r; do{ byte[] buf = new byte[l]; Aids.memrandom(buf, 0, l); r = new BigInteger(1, buf); }while(r.compareTo(slop) < 0); return r.mod(diff).add(p); } public final Signature sign(byte[] a, SK sk) throws CryptoError { return Pki.sign(a, "sha1", new Principal(null, sk, null)); } public final boolean verify(byte[] a, Signature s, PK pk) throws CryptoError { PKsig sig = s.sig; if(!s.alg.equals("rsa-sha1")) return false; byte[] ah = Aids.sha1(a); // TO DO: pkcs1 return pk.verify(sig, new BigInteger(1, ah)); } }