397 lines · 13.1 KB · Java
RawDownload
package plan9.auth; /* * Inferno public key authentication * *	Copyright © 2005 Vita Nuova Holdings Limited * * to do *	attr=val keys *	secstore interface *	Auth.auth */ import java.math.BigInteger;import java.security.*;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 static plan9.lib.Strings.bytes;import plan9.lib.Strings;import plan9.lib.Msgio;import plan9.lib.RemoteError; //java.security.interfaces.RSAKey, RSAPrivateCrtKey, RSAPrivateKey, RSAPublicKey// use instanceof applied to java.security.PublicKey //java.security.spec//	use with java.security.KeyFactory and java.security.AlgorithmParameters//	RSAKeyGenParameterSpec(modulus, pubexp, privexp, primeP, primeQ, primeExpP, primeExpQ, crtCoeff)//		getCrtCoefficient, getPrimeExponentP, getPrimeExponentQ, getPrimeP, getPrimeQ, getPublicExponent//	RSAPrivateKeySpec(modulus, privateexp)//		getModulus, getPrivateExponent//	RSAPublicKeySpec(modulus, publicexp)//		getModulus, getPublicExponent import static plan9.auth.Aids.*; public class Infauth { 	final static int Maxmsg = 4096; 	static KeyFactory keyfactory; 	public Infauth() throws NoSuchAlgorithmException {		if(keyfactory == null)			keyfactory = KeyFactory.getInstance("RSA");	} 	public static class InfPublicKey {		public PublicKey	pk;		public String	owner; 		public InfPublicKey(PublicKey pk, String owner){			this.pk = pk; this.owner = owner;		}		public InfPublicKey(String s) throws InvalidKey {			if(s == null)				throw new InvalidKey("missing public key");			String[] a = Strings.getfields(s, "\n");			if(a.length < 4)				throw new InvalidKey("bad public key syntax");			if(!a[0].equals("rsa"))				throw new InvalidKey("unknown key algorithm: "+a[0]);			BigInteger modulus = s2big64(a[2]);			BigInteger publicexp = s2big64(a[3]);			try{				this.pk = keyfactory.generatePublic(new RSAPublicKeySpec(modulus, publicexp));			}catch(InvalidKeySpecException e){				throw new InvalidKey("bad key spec: "+e.getMessage(), e);			}			this.owner = a[1];		}		public String text(){			RSAPublicKey pk = (RSAPublicKey)this.pk;			return "rsa\n"+this.owner+"\n"+b64(pk.getModulus())+"\n"+b64(pk.getPublicExponent())+"\n";		}		public String toString(){			return pk.toString()+"\nowner="+owner;		}	} 	public static class InfPrivateKey {		PrivateKey	sk;		public String	owner; 		public InfPrivateKey(PrivateKey sk, String owner){			this.sk = sk; this.owner = owner;		}		public InfPrivateKey(String s) throws InvalidKey {			if(s == null)				throw new InvalidKey("missing private key");			String[] a = Strings.getfields(s, "\n");			if(a.length < 10)				throw new InvalidKey("bad private key syntax");			if(!a[0].equals("rsa"))				throw new InvalidKey("unknown key algorithm: "+a[0]);			BigInteger n = s2big64(a[2]);			BigInteger e = s2big64(a[3]);			BigInteger dk = s2big64(a[4]);			BigInteger p = s2big64(a[5]);			BigInteger q = s2big64(a[6]);			BigInteger kp = s2big64(a[7]);			BigInteger kq = s2big64(a[8]);			BigInteger c12 = s2big64(a[9]);			// mind your p's and q's: libsec's p is java's q!  (Java follows PKCS#1 in reversing their roles)			// if using Java's RSA implementation directly, reverse p and q, and kp and kq			// we can't use it here because it imposes PKCS#1, so we do the calculation ourselves			try{				this.sk = keyfactory.generatePrivate(new RSAPrivateCrtKeySpec(n, e, dk, p, q, kp, kq, c12));			}catch(InvalidKeySpecException ex){				throw new InvalidKey("bad key spec: "+ex.getMessage(), e);			}			this.owner = a[1];		}		public InfPublicKey getpk() throws InvalidKey {			RSAPrivateCrtKey rsk = (RSAPrivateCrtKey)this.sk;			try{				PublicKey pk = keyfactory.generatePublic(new RSAPublicKeySpec(rsk.getModulus(), rsk.getPublicExponent()));				return new InfPublicKey(pk, this.owner);			}catch(InvalidKeySpecException e){				throw new InvalidKey("bad key spec: "+e.getMessage(), e);			}		}		public String text(){			RSAPrivateCrtKey sk = (RSAPrivateCrtKey)this.sk;			return "rsa\n"+				b64(sk.getModulus())+"\n"+				b64(sk.getPublicExponent())+"\n"+				b64(sk.getPrivateExponent())+"\n"+				b64(sk.getPrimeP())+"\n"+				b64(sk.getPrimeQ())+"\n"+				b64(sk.getPrimeExponentP())+"\n"+				b64(sk.getPrimeExponentQ())+"\n"+				b64(sk.getCrtCoefficient())+"\n";		}		public String toString(){			return sk.toString()+"\nowner="+owner;		}	} 	public static class Authinfo {		public InfPrivateKey	mysk;		public InfPublicKey	mypk;		public Certificate	cert;	// signature of my public key		public InfPublicKey	spk;	// signer's public key		public BigInteger	alpha;	// diffie-hellman parameters		public BigInteger	p; 		public Authinfo(InfPrivateKey sk, InfPublicKey pk, Certificate cert, InfPublicKey spk, BigInteger alpha, BigInteger p){			this.mysk = sk; this.mypk = pk; this.cert = cert; this.spk = spk; this.alpha = alpha; this.p = p;		}	}	public final Authinfo readauthinfo(ByteChannel fd) throws Exception {		// signer's public key, certificate, secret key (use sk.getpk to get public one), alpha, p		InfPublicKey spk;		Certificate cert;		InfPrivateKey mysk;		BigInteger alpha, p;		Msgio io; 		io = new Msgio(fd);		spk = new InfPublicKey(io.gets());		cert = new Certificate(io.gets());		mysk = new InfPrivateKey(io.gets());		alpha = s2big64(io.gets());		p = s2big64(io.gets());		return new Authinfo(mysk, mysk.getpk(), cert, spk, alpha, p);	} 	public static class Certificate {		public String	sa;	// signature algorithm		public String	ha;	// hash algorithm		public String	signer;	// name of signer		public int		exp;	// expiration date (seconds from Epoch, 0=never)		BigInteger	rsa;	// only RSA signatures supported 		public Certificate(String sa, String ha, String signer, int exp, BigInteger rsa){			this.sa = sa; this.ha = ha; this.signer = signer; this.exp = exp; this.rsa = rsa;		}		public Certificate(String s) throws InvalidCertificate {			if(s == null)				throw new InvalidCertificate("missing certificate");			String[] a = Strings.getfields(s, "\n");			if(a.length < 5)				throw new InvalidCertificate("bad certificate syntax"+":"+a.length);			this.sa = a[0];			this.ha = a[1];			this.signer = a[2];			this.exp = Integer.parseInt(a[3]);			this.rsa = s2big64(a[4]);		}		public final String text(){			return this.sa+"\n"+this.ha+"\n"+this.signer+"\n"+this.exp+"\n"+b64(this.rsa)+"\n";		}	} 	public static class AuthResult {		public Authinfo	info;		public byte[]	secret; 		AuthResult(Authinfo info, byte[] secret){			this.info = info; this.secret = secret;		}	} 	public final AuthResult basicauth(ByteChannel fd, Authinfo info) throws AuthenticationException {		BigInteger low, r0, alphar0, alphar1, alphar0r1;		Certificate hiscert, alphacert;		Msgio io = new Msgio(fd);		byte[] buf, hispkbuf, alphabuf;		InfPublicKey hispk;		byte[] secret;		int vers; 		try{			io.sendmsg("1");	// version			buf = io.getmsg();			vers = Integer.parseInt(Strings.S(buf));			if(vers != 1 || buf.length > 4)				throw new LocalAuthErr("incompatible authentication protocol");			if(info == null)				throw new LocalAuthErr("no authentication information");			if(info.p == null)				throw new LocalAuthErr("missing diffie hellman mod");			if(info.alpha == null)				throw new LocalAuthErr("missing diffie hellman base");			if(info.mysk == null || info.mypk == null || info.cert == null || info.spk == null)	// could check key details				throw new LocalAuthErr("invalid authentication information");			if(info.p.compareTo(BigInteger.ZERO) <= 0)				throw new LocalAuthErr("-ve modulus"); 			low = info.p.shiftRight(info.p.bitLength()/4);			r0 = rand(low, info.p);			alphar0 = info.alpha.modPow(r0, info.p);			io.sendmsg(b64(alphar0));			io.sendmsg(info.cert.text());			io.sendmsg(info.mypk.text()); 			alphar1 = s2big64(io.gets());			if(info.p.compareTo(alphar1) <= 0)				throw new LocalAuthErr("implausible parameter value");			if(alphar0.compareTo(alphar1) == 0)				throw new LocalAuthErr("possible replay attack");			hiscert = new Certificate(io.gets());			hispkbuf = io.getmsg();			hispk = new InfPublicKey(Strings.S(hispkbuf));			if(!verify(info.spk, hiscert, hispkbuf))				throw new LocalAuthErr("pk doesn't match certificate");			if(hiscert.exp != 0 && hiscert.exp <= now())				throw new LocalAuthErr("certificate expired"); 			alphabuf = bytes(b64(alphar0) + b64(alphar1));			alphacert = sign(info.mysk, 0, alphabuf);			io.sendmsg(alphacert.text());			alphacert = new Certificate(io.gets());			alphabuf = bytes(b64(alphar1) + b64(alphar0));			if(!verify(hispk, alphacert, alphabuf))				throw new LocalAuthErr("signature did not match pk"); 			alphar0r1 = alphar1.modPow(r0, info.p);			secret = trim0(alphar0r1.toByteArray()); 			io.sendmsg("OK");		}catch(ClosedByInterruptException e){			throw new LocalAuthErr("interrupted by time-out", e);		}catch(IOException e){			throw new LocalAuthErr("i/o error: "+e.getMessage());	// could distinguish a few cases		}catch(InvalidCertificate e){			io.senderrmsg("remote: "+e.getMessage());			throw e;		}catch(InvalidKey e){			io.senderrmsg("remote: "+e.getMessage());			throw e;		}catch(NoSuchAlgorithmException e){			String msg =  "unsupported algorithm: "+e.getMessage();			io.senderrmsg("remote: "+msg);			throw new AuthenticationException(msg);		}catch(LocalAuthErr e){			io.senderrmsg("remote: "+e.getMessage());			throw e;		}catch(RemoteError e){			io.senderrmsg("missing your authentication data");	// strange but true			throw new AuthenticationException(e.getMessage());		}		try{			String s;			do{				s = io.gets();			}while(!s.equals("OK"));		}catch(ClosedByInterruptException e){			throw new AuthenticationException("interrupted by time-out", e);		}catch(IOException e){			throw new AuthenticationException("i/o error: "+e.getMessage());		}catch(RemoteError e){			throw new AuthenticationException("remote: "+e.getMessage());		}		return new AuthResult(new Authinfo(null, hispk, hiscert, info.spk, info.alpha, info.p), secret);	} 	private static int now(){		return (int)((new Date()).getTime()/1000);	} 	public static final BigInteger rand(BigInteger p, BigInteger q) throws IllegalArgumentException  {		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 IllegalArgumentException("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 void setlinecrypt(ByteChannel fd, String role, String[] algs) throws IOException {		String alg;		Msgio io; 		io = new Msgio(fd);		if(role.equals("client")){			if(algs != null && algs.length > 0)				alg = algs[0];			else				alg = "none";	// alg = "md5/rc4_256";	// no idea how to make use of SSL without its handshake			io.sendmsg(alg);		}else if(role.equals("server")){			try{				alg = io.gets();			}catch(RemoteError e){				throw new IOException("remote: "+e.getMessage(), e);	// can't happen			}			if(!alg.equals("none"))				throw new IOException("unsupported algorithm: "+alg);		}else			throw new IOException("invalid role: "+role);	} 	public final AuthResult auth(ByteChannel fd, String role, Authinfo info, String[] algs) throws AuthenticationException, IOException {		AuthResult a;		a = basicauth(fd, info);		setlinecrypt(fd, role, algs);		return a;	} 	private static final BigInteger  rsaencrypt(RSAPublicKey pk, BigInteger data){		return data.modPow(pk.getPublicExponent(), pk.getModulus());	}	public final Certificate sign(InfPrivateKey sk, int exp, byte a[]) throws NoSuchAlgorithmException {		MessageDigest sha1 = MessageDigest.getInstance("SHA1");		BigInteger sig, digest;		sha1.update(a);		// add signer's name and expiration to hash		sha1.update(bytes(sk.owner+" "+exp));	// "%s %d"		digest = new BigInteger(1, sha1.digest());		sig = rsadecrypt(digest, sk.sk);		return new Certificate("rsa", "sha1", sk.owner, exp, sig);	}	public final boolean verify(InfPublicKey pk, Certificate c, byte a[]) throws NoSuchAlgorithmException {		if(!c.sa.equals("rsa") || !c.ha.equals("sha1") && !c.ha.equals("sha"))			return false;		MessageDigest sha1 = MessageDigest.getInstance("SHA1");		sha1.update(a);		sha1.update(bytes(c.signer+" "+c.exp));		return rsaverify(new BigInteger(1, sha1.digest()), c.rsa, (RSAPublicKey)pk.pk);	}	public static final BigInteger rsadecrypt(BigInteger n, PrivateKey rsa1){		RSAPrivateCrtKey rsa = (RSAPrivateCrtKey)rsa1;		BigInteger p, q, v1, v2;		p = rsa.getPrimeP();		v1 = n.mod(p);		q = rsa.getPrimeQ();		v2 = n.mod(q);		v1 = v1.modPow(rsa.getPrimeExponentP(), p);		v2 = v2.modPow(rsa.getPrimeExponentQ(), q);		// out = v1 + p*((v2-v1)*c2 mod q)		return v2.subtract(v1).multiply(rsa.getCrtCoefficient()).mod(q).multiply(p).add(v1);	}	public static final boolean rsaverify(BigInteger m, BigInteger sig, RSAPublicKey key){		return rsaencrypt(key, sig).equals(m);	}}