MODULE SSHKeys;  (* g.f.	2002.08.29 *)

(*	2002.10.15	g.f.	RSA hostkeys added*)

IMPORT
	RSA := CryptoRSA, DSA := CryptoDSA, B:= CryptoBigNumbers, U := CryptoUtils, SHA1 := CryptoSHA1,
	MD5 := CryptoMD5, P := CryptoPrimes, G := SSHGlobals,
	Files, Streams, Out := KernelLog, WMDialogs, Strings;

CONST
	Ok = WMDialogs.ResOk;
	Abort = WMDialogs.ResAbort;

TYPE
	BigNumber = B.BigNumber;



	PROCEDURE SSHDSSVerify( dsa: DSA.Key;  sig: DSA.Signature;  CONST hash: ARRAY OF CHAR ): BOOLEAN;
	VAR h: SHA1.Hash;  digest: ARRAY 20 OF CHAR;
	BEGIN
		NEW( h );
		h.Initialize;  h.Update( hash, 0, 20 );  h.GetHash( digest, 0 );
		RETURN dsa.Verify( digest, 20, sig )
	END SSHDSSVerify;

	PROCEDURE SSHRSAVerify( rsa: RSA.Key;  sig: BigNumber;  CONST hash: ARRAY OF CHAR ): BOOLEAN;
	VAR h1: SHA1.Hash;  h2: MD5.Hash;  digest: ARRAY 20 OF CHAR;
	BEGIN
		NEW( h1 );
		h1.Initialize;  h1.Update( hash, 0, 20 );  h1.GetHash( digest, 0 );
		IF rsa.Verify( digest, 20, sig ) THEN   RETURN TRUE
		ELSE
			(* SSH_BUG_RSASIGMD5 in server implementation ? *)
			NEW( h2 );
			h2.Initialize;  h2.Update( hash, 0, 20 );  h2.GetHash( digest, 0 );
			RETURN rsa.Verify( digest, 16, sig )
		END
	END SSHRSAVerify;



	PROCEDURE GetKeyStart( CONST keykind, host: ARRAY OF CHAR ): Files.Reader;
	VAR
		f: Files.File; r: Files.Reader;
		buf1, buf2: ARRAY 512 OF CHAR;
		i: LONGINT;
		names: Strings.StringArray;
	BEGIN
		f := Files.Old( G.HostkeysFile );
		IF f = NIL THEN  RETURN NIL
		ELSE
			Files.OpenReader( r, f, 0 );
			REPEAT
				r.SkipWhitespace; r.String( buf1 );
				r.SkipWhitespace; r.String( buf2 );
				IF buf2 = keykind THEN
					names := Strings.Split( buf1, ',' );
					i := 0;
					REPEAT
						IF names[i]^ = host THEN  RETURN r  END;
						INC( i )
					UNTIL i >= LEN( names^ )
				END;
				r.SkipLn;
			UNTIL r.Available() = 0;
			RETURN NIL
		END;
	END GetKeyStart;


	PROCEDURE WriterAtEnd(): Streams.Writer;
	VAR
		f: Files.File;  w: Files.Writer;  r: Files.Reader; c: CHAR;
	BEGIN
		f := Files.Old( G.HostkeysFile );
		IF f = NIL THEN
			f := Files.New( G.HostkeysFile );  Files.Register( f );
			Files.OpenWriter( w, f, 0 );
			RETURN w
		ELSE
			Files.OpenReader( r, f, f.Length() -1 );
			r.Char( c );
			Files.OpenWriter( w, f, f.Length() );
			IF c >= ' ' THEN  w.Ln  END;
			RETURN w
		END;
	END WriterAtEnd;


	PROCEDURE CompareDSAKeys( CONST host: ARRAY OF CHAR; key: DSA.Key ): BOOLEAN;
	VAR
		r: Streams.Reader;  w: Streams.Writer;
		msg: ARRAY 512 OF CHAR;
		res: LONGINT;
		knownKey: DSA.Key;
	BEGIN
		r := GetKeyStart( "ssh-dss", host );
		IF r = NIL THEN
			msg := "no suitable dsa hostkey found in ";
			Strings.Append( msg, G.HostkeysFile );
			Strings.AppendChar( msg, 0DX );
			Strings.Append( msg, "will you trust the connection anyway?" );
			res := WMDialogs.Message( 1, "Load Public Server Hostkey", msg, {Ok, Abort} );
			IF res # Ok THEN  RETURN FALSE
			ELSE
				w := WriterAtEnd();
				w.String( host );  w.Char( ' ' );
				DSA.StorePublicKey( w, key );
				w.Update;
				RETURN TRUE
			END
		END;
		knownKey := DSA.LoadPublicKey( r );
		IF B.Cmp( key.y, knownKey.y ) # 0 THEN
			Out.String( "### error: hostkey of remote host has changed" ); Out.Ln;
			RETURN FALSE
		END;
		RETURN TRUE
	END CompareDSAKeys;


	PROCEDURE CompareRSAKeys( CONST host: ARRAY OF CHAR; key: RSA.Key ): BOOLEAN;
	VAR
		r: Streams.Reader;  w: Streams.Writer;
		msg: ARRAY 512 OF CHAR;
		res: LONGINT;
		knownKey: RSA.Key;
	BEGIN
		r := GetKeyStart( "ssh-rsa", host );
		IF r = NIL THEN
			msg := "no suitable rsa hostkey found in ";
			Strings.Append( msg, G.HostkeysFile );
			Strings.AppendChar( msg, 0DX );
			Strings.Append( msg, "will you trust the connection anyway?" );
			res := WMDialogs.Message( 1, "Load Public Server Hostkey", msg, {Ok, Abort} );
			IF res # Ok THEN  RETURN FALSE
			ELSE
				w := WriterAtEnd();
				w.String( host );  w.Char( ' ' );
				RSA.StorePublicKey( w, key );
				w.Update;
				RETURN TRUE
			END
		END;
		knownKey := RSA.LoadPublicKey( r );
		IF B.Cmp( key.modulus, knownKey.modulus ) # 0 THEN
			Out.String( "### error: hostkey of remote host has changed" ); Out.Ln;
			RETURN FALSE
		END;
		RETURN TRUE
	END CompareRSAKeys;


	PROCEDURE VerifyIdentity*( CONST keyblob, signature, host, hash: ARRAY OF CHAR ): BOOLEAN;
	VAR
		name1, name2: ARRAY 128 OF CHAR; i, j, len: LONGINT;
		dsa: DSA.Key; dsasig: DSA.Signature;
		rsa: RSA.Key; e, n, rsasig: BigNumber;
		p, q, g, pub, r, s: BigNumber;
	BEGIN
		i := 0;  U.GetString( keyblob, i, name1 );
		j := 0;  U.GetString( signature, j, name2 );
		IF name1 # name2 THEN  RETURN FALSE  END;
		IF name1 = "ssh-dss" THEN
			U.GetBigNumber( keyblob, i, p );
			U.GetBigNumber( keyblob, i, q );
			U.GetBigNumber( keyblob, i, g );
			U.GetBigNumber( keyblob, i, pub );
			dsa := DSA.PubKey( p, q, g, pub );
			U.GetLength( signature, j, len );
			IF len # 40 THEN  RETURN FALSE  END;
			B.AssignBin( r, signature, j, 20 ); INC( j, 20 );
			B.AssignBin( s, signature, j, 20 );
			NEW( dsasig, r, s );
			IF SSHDSSVerify( dsa, dsasig, hash ) THEN  RETURN CompareDSAKeys( host, dsa )
			ELSE  RETURN FALSE
			END
		ELSIF name1 = "ssh-rsa" THEN
			U.GetBigNumber( keyblob, i, e );
			U.GetBigNumber( keyblob, i, n );
			rsa := RSA.PubKey( e, n );
			U.GetBigNumber( signature, j, rsasig );
			IF (rsa.name = "unkown") (* from openssh! *) OR SSHRSAVerify( rsa, rsasig, hash ) THEN
				RETURN CompareRSAKeys( host, rsa )
			ELSE  RETURN FALSE
			END
		ELSE
			Out.String( "### error: unsupported public hostkey type: " );  Out.String( name1 ); Out.Ln;
			RETURN FALSE
		END;
	END VerifyIdentity;



	PROCEDURE RSAKeyGen*;
	CONST
		headline1 = "enter passphrase for new rsa key";
		headline2 = "repeat passphrase for new rsa key";
		Size = 2048;

	VAR
		pw1, pw2: ARRAY 32 OF CHAR; ignore, res: LONGINT; ok: BOOLEAN;
		p, q, e: BigNumber;
		priv, pub: RSA.Key;
		f: Files.File; w: Files.Writer;
	BEGIN
		REPEAT
			ignore := WMDialogs.QueryPassword( headline1, pw1 );
			ok := Strings.Length( pw1 ) > 5;
			IF ~ok THEN
				res := WMDialogs.Message( 1, "RSA Keygen", "pease insert a longer key", {Ok, Abort} );
				IF res = Abort THEN  RETURN  END
			END
		UNTIL ok;
		ignore := WMDialogs.QueryPassword( headline2, pw2 );
		IF pw1 # pw2 THEN
			res := WMDialogs.Message( 1, "RSA Keygen", "passphrases don't match", {Ok} );
			RETURN
		END;
		p := P.NewPrime( Size DIV 2, FALSE );
		q := P.NewPrime( Size DIV 2, FALSE );
		B.AssignInt( e, 3 );
		RSA.MakeKeys( p, q, e, "Aos rsa-key", pub, priv );
		f := Files.New( G.PrivateKeyFile );  Files.OpenWriter( w, f, 0 );
		RSA.StorePrivateKey( w, priv, pw1 );
		w.Update;
		Files.Register( f );
		f := Files.New( G.PublicKeyFile );  Files.OpenWriter( w, f, 0 );
		RSA.StorePublicKey( w, pub );
		w.Update;
		Files.Register( f );
	END RSAKeyGen;

BEGIN
END SSHKeys.
.


SSHKeys.RSAKeyGen ~

SystemTools.Free SSHKeys ~