306 lines · 9.6 KB · Java
RawDownload
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<Object> seq = new ArrayList<Object>();			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));	}}