[Webfunds-commits] java/webfunds/ricardian Contract.java ContractException.java

Ian Grigg iang@cypherpunks.ai
Wed, 16 Aug 2000 14:40:49 -0400 (AST)


iang        00/08/16 14:40:48

  Modified:    webfunds/ricardian Contract.java ContractException.java
  Log:
  Ex now has number indicating what went wrong (which key, what format
  problem).  toString prints out applicable string based on number.
  
  Contract now throws ContractException with numbers set so that
  caller knows what has gone wrong.  Used by SignContractWizard.
  (only lightly tested within WebFunds itself, the verify fails on
  new OpenPGP contracts so far.)

Revision  Changes    Path
1.30      +401 -169  java/webfunds/ricardian/Contract.java

Index: Contract.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/ricardian/Contract.java,v
retrieving revision 1.29
retrieving revision 1.30
diff -u -r1.29 -r1.30
--- Contract.java	2000/08/08 02:15:50	1.29
+++ Contract.java	2000/08/16 18:40:48	1.30
@@ -1,4 +1,4 @@
-/* $Id: Contract.java,v 1.29 2000/08/08 02:15:50 edwin Exp $
+/* $Id: Contract.java,v 1.30 2000/08/16 18:40:48 iang Exp $
  *
  * Copyright (c) Systemics Ltd 1995-1999 on behalf of
  * the WebFunds Development Team.  All Rights Reserved.
@@ -38,6 +38,7 @@
 
 import webfunds.utils.Debug;
 import webfunds.utils.Hex;
+import webfunds.utils.Panic;
 
 import webfunds.sox.Armoury;
 import webfunds.sox.Crypto;
@@ -45,6 +46,7 @@
 import webfunds.sox.SOXKeyException;
 import webfunds.sox.utils.Base64;
 
+import cryptix.openpgp.PGPUserID;
 import cryptix.openpgp.PGPException;
 import cryptix.openpgp.PGPMessage;
 import cryptix.openpgp.PGPPublicKey;
@@ -101,11 +103,16 @@
         this.userData       = yData;
 
         if (contractData == null || contractData.length == 0)
-            throw new ContractException("contract has no data");
+            throw new ContractException(ContractException.NO_DATA,
+                                        "contract has no data");
 
         /*
-         * It is not defined what the order of reading the two in is.
-         * Here, mirror the Perl code, and have the contract override local.
+         *  It is not defined what the order of reading the two in is.
+         *  Here, mirror the Perl code, and have the contract override local.
+         *
+         *  But, all local fields can start with local_ and local_ should
+         *  be illegal in the contract itself, so there should be no
+         *  conflict?
          */
         fields = new IniFileReader(localData);
         fields.addByteArray(contractData);
@@ -118,7 +125,7 @@
         //
 
         // drop this crud when DigiGold Series 1 disappears.
-        int v = 2;
+        int v = CANHASH_WHITE_BUG;
         String version = getField("local", "digest_version");
         try {
             if (version != null && version.length() != 0)
@@ -142,7 +149,8 @@
 
         this.type           = getField("issue", "type");
         if (this.type == null || this.type.equals(""))
-            throw new ContractException("not a contract: no issue type");
+            throw new ContractException(ContractException.NOT_A_CONTRACT,
+                                        "no [issue] type");
     }
 
     public String fileNameFromDigest(byte[] hash)
@@ -167,22 +175,29 @@
 
 /////////// Canonical Digest /////////////////////////////////////////
 
+    public static final int
+                  CANHASH_WHITE_BUG = 2,  // trailing whitespace not stripped
+                  CANHASH_CURRENT = 3;    // repairs whitespace bug
+
     /**
-     * Do a canonical hash on the data.
-     * A canonical hash is:
+     *  Do a canonical hash on the data.
+     *  A canonical hash (v = CANHASH_CURRENT) is:
+     *
      *     + requires a PGP signature (lines outside removed)
      *     + trailing whitespace removed
      *     + line endings are CR/LN
-     * It is slightly different to canonical signature preparation.
-     * This (code) was in sox.
-     * @param txt is the file data
+     *
+     *  It is slightly different to canonical signature preparation.
+     *  This (code) was in sox.
+     *  @param txt is the file data
      */
     public static byte[] doCanonicalDigest(byte[] txt)
         throws ContractException
     {
-        return doCanonicalDigest(txt, 3);
+        return doCanonicalDigest(txt, CANHASH_CURRENT);
     }
 
+
     /**
      * Do a canonical hash on the data.
      * @param version 3 is current
@@ -233,9 +248,11 @@
 
     /**       
      * This method prepares data to be canonically hashed:
+     *
      *    + all lines end with \r\n
      *    + all trailing whitespace is removed (codes <= '\u0020')
      *    + all lines before and after signed section are removed
+     *
      * This used to be Armoury.prepareDataToSign().
      * @param data the original data
      * @return canonical data
@@ -292,7 +309,8 @@
             String dashes = "===========" + sep;
             System.err.println(lines[0] + sep + lines[1] + sep + lines[2] + sep + dashes);
             System.err.println(lines[lineNum-1] + sep + lines[lineNum-2] + sep);
-            throw new ContractException("not a contract: no BEGIN");
+            throw new ContractException(ContractException.NOT_A_CONTRACT,
+                                        lineNum, "no BEGIN");
         }
 //System.err.println("starting at " + lineNum);
 
@@ -328,7 +346,8 @@
             lineNum++;
         }
         if (!found)
-            throw new ContractException("not a contract: no END");
+            throw new ContractException(ContractException.NOT_A_CONTRACT,
+                                        lineNum, "no END");
 //System.err.println("ending at " + lineNum + " (of " + numLines + ")");
 
         //
@@ -383,9 +402,8 @@
     }
 
 
-
 
-/////////// Read In /////////////////////////////////////////
+///////////  Contract Factory  ////////////////////////////
 
     /**
      * Take the data, work out which type of contract it is, and
@@ -397,6 +415,10 @@
     public static Contract getContract(byte[] con, byte[] loc, byte[] my)
         throws ContractException
     {
+        if (con == null || con.length == 0)
+            throw new ContractException(ContractException.NO_DATA,
+                                        "no data in contract");
+
         switch (getType(con))
         {
             case CURRENCY_TYPE:
@@ -463,7 +485,8 @@
         catch (IOException ex)
         {
             ex.printStackTrace(System.err);
-            throw new ContractException(contractfile + " is bad: " + ex);
+            throw new ContractException(ContractException.FILE_ERROR,
+                                        "cannot read "+contractfile+"\n" + ex);
         }
 
         return getContract(contractData, localData, userData) ;
@@ -503,14 +526,18 @@
         //
         URL ascurl;
         URL localurl;
+        String s = null;
         try
         {
-            ascurl = new URL(root + ".asc");
-            localurl = new URL(root + ".loc");
+            s = root + ".asc";
+            ascurl = new URL(s);
+            s = root + ".loc";
+            localurl = new URL(s);
         }
         catch (MalformedURLException ex)
         {
-            throw new ContractException("Not a valid URL:\n" + ex);
+            throw new ContractException(ContractException.FILE_ERROR,
+                                        "URL malformed: "+s+"\n" + ex);
         }
 
         try
@@ -522,12 +549,14 @@
         }
         catch (FileNotFoundException ex)
         {
-            throw new ContractException(ascurl + ": File not found\n" + ex);
+            throw new ContractException(ContractException.FILE_ERROR,
+                                        ascurl + ": File not found\n" + ex);
         }
         catch (IOException ex)
         {
             ex.printStackTrace(System.err);
-            throw new ContractException(ascurl + " is bad: " + ex);
+            throw new ContractException(ContractException.FILE_ERROR,
+                                        ascurl + " is bad: " + ex);
         }
 
 
@@ -548,7 +577,8 @@
             // how to know if the file is not there or there is
             // a more serious failure.
             ex.printStackTrace(System.err);
-            throw new ContractException(localurl + " local is bad: " + ex);
+            throw new ContractException(ContractException.FILE_ERROR,
+                                        localurl + " local is bad: " + ex);
         }
 
         return getContract(contractData, localData, new byte[0]);
@@ -556,6 +586,11 @@
 
 
 
+    /**
+     *  Work out what type of contract this is.
+     *  An unknown one is not an exception, just something
+     *  that we can't interpret further than being a contract.
+     */
     public static int getType(byte[] contractData)
         throws ContractException
     {
@@ -664,10 +699,26 @@
     }
 
 
+
+/////////// Signatures /////////////////////////////////////////
+
+  //  ..  ..  Get Certificates  ..  ..  ..  ..  ..  ..  ..  ..  ..  .. 
 
-/////////// Signature /////////////////////////////////////////
 
     /**
+     *  OpenPGP User Id tags - special Ricardian strings that indicate what
+     *  this key is used for.
+     *
+     *  @see http://www.systemics.com/docs/ricardo/issuer/server-manage.html
+     */
+    public final static String USERID_TOP_LEVEL    = "[cert]",
+                               USERID_CONTRACT     = "[contract]",
+                               USERID_SERVER       = "[server]";
+
+    public final static String FIELD_TOP_LEVEL    = "certification",
+                               FIELD_CONTRACT     = "contract",
+                               FIELD_SERVER       = "server_certification";
+    /**
      * Each contract provides hints as to which server keys are
      * to be used.
      * This key is the one that signs the contract, and is only a hint,
@@ -676,7 +727,9 @@
     private Certificate getContractCert()
         throws ContractException
     {
-        return getCert("contract");
+        return getCert(FIELD_CONTRACT,
+                       ContractException.KEY_CONTRACT,
+                       USERID_CONTRACT);
     }
 
     /**
@@ -687,7 +740,9 @@
     private Certificate getCertificationCert()
         throws ContractException
     {
-        return getCert("certification");
+        return getCert(FIELD_TOP_LEVEL,
+                       ContractException.KEY_TOP_LEVEL,
+                       USERID_TOP_LEVEL);
     }
 
     /**
@@ -697,91 +752,188 @@
     protected Certificate getServerCert()
         throws ContractException
     {
-        String CERT = "server_certification";
         // this is what we should do:
-        // return getCert(CERT);
-        // but because of an error in one of the contracts.....
+        //       return getCert(FIELD_SERVER, ...);
+        // but because of an error in one of the contracts
+        // we have to work out what the real field name is.
 
-        // deprecate this with the DigiGold.asc V1.3 contract
-        String field = getField("keys", CERT);
-logmsg("ok, got " + field + " from " + CERT);
-        String CERT2 = "servercertifiation";    // missing _, speeling mistake
-        if (field == null || field.length() == 0)
-            field = getField("keys", CERT2);
-logmsg("now got " + field + " from " + CERT2);
-        if (field == null || field.length() == 0)
-            throw new ContractException("cert <" + CERT + "> is not present");
+        String fieldName = FIELD_SERVER;
 
-        return getCertFromString(field);
+        // deprecate this with the DigiGold.asc V1.3 contract
+        String text = getField("keys", fieldName);
+//logmsg("ok, got " + text + " from " + fieldName);
+        fieldName = "servercertifiation";    // missing _, speeling mistake
+        if (text == null || text.length() == 0)
+            text = getField("keys", fieldName);
+//logmsg("now got " + text + " from " + FS2);
+        if (text == null || text.length() == 0)
+            throw new ContractException(ContractException.KEY_SERVER,
+                      "cert <" + FIELD_SERVER + "> is not present");
+
+        return getCertFromString(text,
+                                 fieldName,  // may be the deprecated one
+                                 ContractException.KEY_SERVER,
+                                 USERID_SERVER);
     }
 
-    private Certificate getCert(String name)
+    /**
+     *  @param name of the field (left of equals)
+     *  @param errno number to set in any exception
+     *  @param userIdTag tag inside an OpenPGP key that describes purpose
+     *
+     *  @throws ContractException with errno set as key indicator
+     *
+     *  @returns a valid, self-signed certificate with field name of field
+     */
+    private Certificate getCert(String name, int errno, String userIdTag)
         throws ContractException
     {
-        String field = getField("keys", name);
-        if (field == null || field.length() == 0)
-            throw new ContractException("cert <" + name + "> is not present");
+        String text = getField("keys", name);
+        if (text == null || text.length() == 0)
+            throw new ContractException(errno,
+                                        "cert <" + name + "> is not present");
 
-        return getCertFromString(field);
+        return getCertFromString(text, name, errno, userIdTag);
     }
 
-    private Certificate getCertFromString(String field)
+    /**
+     *  @param text contents of the field (right of equals)
+     *  @param name of the field (left of equals)
+     *  @param errno number to set in any exception
+     *  @param userIdTag tag inside an OpenPGP key that describes purpose
+     *
+     *  @throws ContractException on empty data, bad armouring,
+     *                                           parsing, bad self-signature
+     *
+     *  @returns a valid, self-signed certificate with field name of field
+     */
+    private Certificate getCertFromString(String text,
+                                          String name,
+                                          int errno,
+                                          String userIdTag
+                                          )
         throws ContractException
     {
-        if (field == null || field.length() == 0)
-            throw new ContractException("cert <" + name + "> is not present");
+        if (text == null || text.length() == 0)
+            throw new ContractException(errno,
+                      "cert <" + name + "> is not present");
 
-        int end = field.indexOf("- ");
+        int end = text.indexOf("- ");
         if(end >= 0)
-            field = field.substring(0, end) + field.substring(end + 2);
-        end = field.indexOf("- ");
+            text = text.substring(0, end) + text.substring(end + 2);
+        end = text.indexOf("- ");
         if(end >= 0)
-            field = field.substring(0, end) + field.substring(end + 2);
+            text = text.substring(0, end) + text.substring(end + 2);
 
-        Certificate retval = null;
+        Certificate cert = null;
 
-        if (field.indexOf("-----BEGIN PGP") >= 0) {
+        if (text.indexOf("-----BEGIN PGP") >= 0) {
         
             // OpenPGP key
-            PGPArmoury armoured;
-
-            try {
-                armoured = new PGPArmoury(field);
-            } catch (IllegalArgumentException iae) {
-                throw new ContractException("Error parsing cert <"+name+"> -"+
-                                            iae);
-            }
+            cert = getOpenPGPCertFromString(text, name, errno, userIdTag);
 
-            ByteArrayInputStream bais =
-                                new ByteArrayInputStream(armoured.getPayload());
 
-            try {
-                CertificateFactory factory =
-                                      CertificateFactory.getInstance("OpenPGP");
-                retval = factory.generateCertificate(bais);
-            } catch (CertificateException ce) {
-                throw new ContractException("Error parsing cert <"+name+"> -"+
-                                            ce);
-            }
-            
         } else {
         
             // X.509 key
-            try {
-                retval = Armoury.decodeCert( field );
-            } catch (SOXKeyException ex) {
-                throw new ContractException("contract signing key: " + ex);
-            }
+            cert = getX509CertFromString(text, name, errno);
+
+        }
+
+        return cert;
+    }
+
+    private Certificate getOpenPGPCertFromString(String text, String name,
+                                          int errno, String userIdTag)
+        throws ContractException
+    {
+        Certificate cert = null;
+
+        // OpenPGP key
+        PGPArmoury armoured;
+        String e = "OpenPGP cert <" + name + "> ";
+
+        try {
+            armoured = new PGPArmoury(text);
+        } catch (IllegalArgumentException iae) {
+            throw new ContractException(errno,
+                      "Error parsing " + e + "- "+ iae);
         }
+
+        ByteArrayInputStream bais =
+                            new ByteArrayInputStream(armoured.getPayload());
 
-        return retval;
+        try {
+            CertificateFactory factory =
+                                  CertificateFactory.getInstance("OpenPGP");
+            cert = factory.generateCertificate(bais);
+        } catch (CertificateException ce) {
+            throw new ContractException(errno,
+                      "Error parsing " + e + " - "+ ce);
+        }
+
+        // convert it to a PGPPublicKey and check the self-signature
+        PGPPublicKey key = (PGPPublicKey)cert.getPublicKey();
+
+        /*
+         *  Only accept valid self-signed keys.
+         *  AFAIK, this applies to all ricardian OpenPGP keys.
+         */
+        try {
+            cert.verify(key);
+        } catch (SignatureException se) {
+            throw new ContractException(errno, e + "not self-signed - "+ se);
+        } catch (NoSuchProviderException nspe) {
+            throw new InternalError("Should not happen - "+nspe);
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new InternalError("Should not happen - "+nsae);
+        } catch (InvalidKeyException ike) {
+            throw new ContractException(errno, e + "Invalid key - "+ ike);
+        } catch (CertificateException ce) {
+            throw new ContractException(errno, e + "Invalid cert - "+ ce);
+        }
+
+        /*
+         *  Check that it has the appropriate purpose inside it.
+         */
+        PGPUserID uid;
+        try {
+            uid = KeyUtil.findUserId(key, userIdTag);
+        } catch (StripKeyException ex) {
+            throw new ContractException(errno, e +
+                      "too many Ids " + userIdTag + " - "+ ex);
+        }
+
+        if (uid == null)
+            throw new ContractException(errno, e +
+                      "not fit for purpose (no " + userIdTag + " tag)");
+
+        return cert;
+    }
+
+    private Certificate getX509CertFromString(String text, String name,
+                                              int errno)
+        throws ContractException
+    {
+
+        Certificate cert;
+        try {
+            cert = Armoury.decodeCert( text );
+        } catch (SOXKeyException ex) {
+            throw new ContractException(errno,
+                          "Error parsing x509 cert <"+name+"> - " + ex);
+        }
+
+        return cert;
     }
 
+  //  ..  ..  Verify Contract  ..  ..  ..  ..  ..  ..  ..  ..  ..  .. 
 
     /**
-     * Verify that the contract is signed correctly by the cert.
-     * Also see getContentErrors().
-     * @return true if sig verifies, else false if sig fails
+     *  Verify that the contract is signed correctly by the cert.
+     *  Also see getContentErrors().
+     *  @return true if sig verifies, else false if sig fails
+     *  @throws ContractException, set to the approximate causes
      */
     public boolean verifyContract()
         throws ContractException
@@ -791,97 +943,176 @@
         // ### FIXME (edwin): Figure out a better way to check this
         if (s.startsWith("-----BEGIN PGP SIGNED MESSAGE-----")) {
 
-            // get the contract cert and convert it to a PGPPublicKey
-            Certificate contractCert = getContractCert();
-            PGPPublicKey contractKey =
-                                      (PGPPublicKey)contractCert.getPublicKey();
-            
-            // verify the contract
-            boolean result;
-            try {
-                result = PGPMessage.verifyClearSign(s, contractKey);
-            } catch (IOException ioe) {
-                return false;
-            } catch (PGPException pe) {
-                throw new ContractException("Format error in contract - "+pe);
-            }
-             
-            // get the other certs
-            Certificate certificationCert = getCertificationCert();
-            Certificate serverCert = getServerCert();
-            
-            // verify certification path
-            try {
-                contractCert.verify(
-                                (PGPPublicKey)certificationCert.getPublicKey());
-                serverCert.verify(
-                                (PGPPublicKey)certificationCert.getPublicKey());
-            } catch (SignatureException se) {
-                result = false;
-            } catch (NoSuchProviderException nspe) {
-                throw new InternalError("Should not happen - "+nspe);
-            } catch (NoSuchAlgorithmException nsae) {
-                throw new InternalError("Should not happen - "+nsae);
-            } catch (InvalidKeyException ike) {
-                throw new ContractException("Invalid key in contract - "+ike);
-            } catch (CertificateException ce) {
-                throw new ContractException("Invalid cert in contract - "+ce);
-            }
+            return verifyOpenPGPSignatures();
             
-            // pff... finally
-            return result;
-            
         } else {  // X.509 signed contract
+
+            return verifyX509Signatures();
+        }
+    }
+
+
+    /**
+     *  Verify that the OpenPGP contract is signed correctly and
+     *  that all certs match our current path goodness criteria:
+     *
+     *       1. all keys {top, contract, server} are self-signed.
+     *       2. top-level cert key signs contract key.
+     *       3. contract key signs contract.
+     *       4. no other signatures are included.
+     *
+     *  why is this:
+     *  // @return true if sig verifies, else false if sig fails
+     *  @return true
+     *  @throws ContractException, set to the approximate cause
+     */
+    public boolean verifyOpenPGPSignatures()
+        throws ContractException
+    {
+        String s = new String(contractData);
+
+        /*
+         *  Get the certs and convert them to PGPPublicKeys
+         *  as needed.
+         *  Getting any cert will also check the self-sig and userIdTag.
+         */
+        Certificate topLevelCert = getCertificationCert();
+
+        Certificate contractCert = getContractCert();
+
+        /*
+         *  No cert path for signing key yet, may be signed by intermediate
+         *  key some time.
+         *  Server cert is *not* signed by top-level key here, as that
+         *  would imply unspecified endorsement.  We need to define an
+         *  "server-key-signing-key" for the legal contract issuer.
+         */
+        Certificate serverCert = getServerCert(); // getting it will check it
         
-            // Get the Certificate from the contract
-            Certificate issuerCert = getContractCert();
-    
-            // Extract the signature
-            byte[] sig;
-            try {
-                sig = Armoury.decodeByteArray("SIGNATURE", s);
-            } catch (IOException ex) {
-                throw new ContractException("signature is bad: " + ex);
-            }
-    
-            // Get the prepared (i.e. CR/NL processed) contract
-            byte[] decoded;
-            byte[] data;
+        /*
+         *  Verify the signature on the contract.
+         */
+        PGPPublicKey contractKey = (PGPPublicKey)contractCert.getPublicKey();
+        boolean contractSigned;
+        try {
+            contractSigned = PGPMessage.verifyClearSign(s, contractKey);
+        } catch (IOException ioe) {
+            throw new ContractException(ContractException.SIG_BAD,
+                      "dud code, throws IOEx: " + ioe);
+            // return false;
+        } catch (PGPException pe) {
+            throw new ContractException(ContractException.SIG_BAD,
+                      "Format error in signature - "+pe);
+        }
+
+        // what was the cause of the failure?
+        if (!contractSigned)
+            throw new ContractException(ContractException.SIG_VERIFY,
+                      "Contract signature failed!");
+         
+        PGPPublicKey topLevelKey = (PGPPublicKey)topLevelCert.getPublicKey();
+
+        /*
+         *  Verify certification path - top-level signs contract signing key
+         */
+        try {
+            contractCert.verify(topLevelKey);
+        } catch (SignatureException se) {
+            throw new ContractException(ContractException.KEY_CON_SIG,
+                                    "unsigned by cert key - "+se);
+            // return false; // result = false;
+        } catch (NoSuchProviderException nspe) {
+            throw new Panic("Should not happen - "+nspe);
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new Panic("Should not happen - "+nsae);
+        } catch (InvalidKeyException ike) {
+            throw new ContractException(ContractException.KEY_CONTRACT,
+                                    "Invalid key - "+ike);
+        } catch (CertificateException ce) {
+            throw new ContractException(ContractException.KEY_CONTRACT,
+                                    "Invalid cert - "+ce);
+        }
+
+        /*
+         *  Verify that keys do not have any superfluous signatures.
+         *  We have already checked all the positive errors like the
+         *  certificate authority chain, so the only thing left should
+         *  be superfluous packets that we won't permit because they
+         *  might be badly interpreted when push comes to shove.
+         */
+        String e = "Illegal keyIds or signatures";
+        if (!KeyUtil.verifyKey(topLevelKey, USERID_TOP_LEVEL, null))
+            throw new ContractException(ContractException.KEY_TOP_LEVEL, e);
+
+        if (!KeyUtil.verifyKey(contractKey, USERID_CONTRACT, topLevelKey))
+            throw new ContractException(ContractException.KEY_CONTRACT, e);
+
+        PGPPublicKey serverKey = (PGPPublicKey)serverCert.getPublicKey();
+        if (!KeyUtil.verifyKey(serverKey, USERID_SERVER, null))
+            throw new ContractException(ContractException.KEY_SERVER, e);
+
+        return true;
+    }
+
+    /**
+     *  Verify that the x509 contract is signed correctly and
+     *  that all certs match our current path goodness criteria.
+     *
+     *  @return true if sig verifies, else false if sig fails
+     *  @throws ContractException, set to the approximate cause
+     */
+    public boolean verifyX509Signatures()
+        throws ContractException
+    {
+        // Get the Certificate from the contract
+        Certificate issuerCert = getContractCert();
+
+        // Extract the signature
+        byte[] sig;
+        try {
+            sig = Armoury.decodeByteArray("SIGNATURE",new String(contractData));
+        } catch (IOException ex) {
+            throw new ContractException(ContractException.SIG_BAD,
+                                        "X.509 signature is bad: " + ex);
+        }
+
+        // Get the prepared (i.e. CR/NL processed) contract
+        byte[] decoded;
+        byte[] data;
+        try {
+            decoded = Armoury.decodeData(contractData);
+            data = Armoury.prepareDataToSign(decoded);
+        } catch (IOException ex) {
+            throw new ContractException(ContractException.NOT_A_CONTRACT,
+                                        "contract format is bad: " + ex);
+        }
+
+        PublicKey pK = Crypto.getPublicKeyFromCert(issuerCert);
+
+        //
+        // Drop last 2 bytes - which are a CR/LN - as these might have
+        // snuck in?  Pox on those who don't comment this!  Further
+        // pestilence & plague on them who didn't fix it at source!!
+        // Rot & Ruin on they who let the "standard" reflect this
+        // abomination !*!%!
+        //
+        // It turns out that the last CR/LN is not counted in a signature.
+        //
+        boolean ok;
+        int i = 0;
+        do
+        {
             try {
-                decoded = Armoury.decodeData(contractData);
-                data = Armoury.prepareDataToSign(decoded);
-            } catch (IOException ex) {
-                throw new ContractException("contract part is bad: " + ex);
+                ok = Crypto.verify(pK, sig, data);
+            } catch (java.security.KeyException ex) {
+                throw new ContractException(ContractException.KEY_CONTRACT,
+                                        "bad key on verify: " + ex);
             }
-    
-            PublicKey pK = Crypto.getPublicKeyFromCert(issuerCert);
-    
-            //
-            // Drop last 2 bytes - which are a CR/LN - as these might have
-            // snuck in?  Pox on those who don't comment this!  Further
-            // pestilence & plague on them who didn't fix it at source!!
-            // Rot & Ruin on they who let the "standard" reflect this
-            // abomination !*!%!
-            //
-            // It turns out that the last CR/LN is not counted in a signature.
-            //
-            boolean ok;
-            int i = 0;
-            do
-            {
-                try {
-                    ok = Crypto.verify(pK, sig, data);
-                } catch (java.security.KeyException ex) {
-                    throw new ContractException("bad key on verify: " + ex);
-                }
-                logmsg("Attempt " + (++i) + ": " + ok);
-    
-            } while (!ok && (data = mungeLastLine(data)) != null) ;
-    
-            return ok ;
-        
-        }
-      
+            logmsg("Attempt " + (++i) + ": " + ok);
+
+        } while (!ok && (data = mungeLastLine(data)) != null) ;
+
+        return ok ;
     }
 
     /*
@@ -943,7 +1174,8 @@
             else
                 myFile.changeSectionItemValue(section, item, old);
 
-            throw new ContractException("cannot set fields: " + ex);
+            throw new ContractException(ContractException.FUZ_FILE_IO,
+                                        "cannot set fields: " + ex);
         }
     }
 
@@ -1283,7 +1515,7 @@
 
         try {
             con.verifyContract();
-        } catch (Exception ex) {
+        } catch (ContractException ex) {
             ex.printStackTrace(System.err);
             throw new RuntimeException(ex.getMessage());
         }



1.2       +110 -1    java/webfunds/ricardian/ContractException.java

Index: ContractException.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/ricardian/ContractException.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- ContractException.java	1999/09/11 23:21:44	1.1
+++ ContractException.java	2000/08/16 18:40:48	1.2
@@ -1,13 +1,96 @@
 /*
- * $Id: ContractException.java,v 1.1 1999/09/11 23:21:44 iang Exp $
+ * $Id: ContractException.java,v 1.2 2000/08/16 18:40:48 iang Exp $
  *
  * Copyright (c) Systemics Ltd 1995-1999 on behalf of
  * the WebFunds Development Team.  All Rights Reserved.
  */
 package webfunds.ricardian;
 
-public class ContractException extends Exception
+public class ContractException
+    extends Exception
 {
+    /**
+     *  Known Contract failure modes detected.
+     */
+    public static final int   UNKNOWN        = 0,
+                              CATCH_ALL      = 1, // catch all for other errors
+                              FILE_ERROR     = 2, // file could not be read
+                              NO_DATA        = 3, // not enough contract data
+                              MULTILINE      = 4, // multiline not terminated
+                              NOT_A_CONTRACT = 5, // important fields missing
+
+                              KEY_TOP_LEVEL  = 10, // top level key bad
+                              KEY_CONTRACT   = 11, // contract signing key bad
+                              KEY_SERVER     = 12, // server operator's key bad
+                              KEY_CON_SIG    = 13, // contract key unsigned
+
+                              SIG_BAD        = 20, // signature is bad
+                              SIG_VERIFY     = 21, // signature does not verify
+
+                              FUZ_FILE_IO    = 30; // cannot write my FUZ file
+
+    // public static final String unknown = "'unknown'";
+
+    public static final String[] errors = {
+                              "<not set, old Ex>",
+                              "'miscellaneous error'",
+                              "File Error",
+                              "No Data",
+                              "Multi-line Field Is Not Terminated",
+                              "Not A Contract",
+                                    null, null, null, null,
+
+                              "Bad Top-level Certification Key",
+                              "Bad Contract Signing Key",
+                              "Bad Server Key",
+                              "Contract Signing Key Not Authorised",
+                                                      null,
+                              null, null, null, null, null,
+
+                              "Bad Or Unreadable Signature",
+                              "Signature Does Not Verify",
+                                          null, null, null,
+                              null, null, null, null, null,
+
+                              "Write Error To FUZ File",
+                              };
+
+
+    /**
+     *  What error number has been set by the thrower.
+     */
+    protected int errno = UNKNOWN;
+    public int    getErrno() { return errno; }
+
+    /**
+     *  The line number where the error occurred, counting from 0 to n-1,
+     *  if available (-1 is not set).
+     */
+    protected int line = -1;
+    public int    getLine()   { return line; }
+
+
+    public String getErrnoString()
+    {
+        if (! (0 <= errno && errno < errors.length) )
+            return "<invalid: " + errno + ">";
+
+        String e = errors[errno];
+
+        return "(" + errno + ") " + ((e == null) ? "'unknown'" : e);
+    }
+
+    public String toString()
+    {
+        String s = "";
+        if (errno > 0)
+            s += getErrnoString() + ": ";
+        s += super.toString();
+        if (line >= 0)
+            s += " [line " + line + "]";
+        return s;
+    }
+
     
     public ContractException()
     {
@@ -17,5 +100,31 @@
     public ContractException(String msg)
     {
         super(msg);
+    }
+
+    /**
+     *  line number is set to default of -1
+     *
+     *  @param errno an identified error from public constants above
+     *  @param msg string interpretation
+     */
+    public ContractException(int errno, String msg)
+    {
+        super(msg);
+
+        this.errno = errno;
+    }
+
+    /**
+     *  @param errno an identified error from public constants above
+     *  @param line where (approximately) it happened in the file
+     *  @param msg string interpretation
+     */
+    public ContractException(int errno, int line, String msg)
+    {
+        super(msg);
+
+        this.errno = errno;
+        this.line  = line;
     }
 }