/* This is an experimental implementation of the signing and checking algorithms for a digital signature based on one-way hash functions. The new signature method is, for this reason, called a "hash signature." Many methods of generating a digital signature from a one-way hash function are feasible, so to distinguish this particular method it is called the "Xerox Hash Signature." The Xerox Hash Signature presently includes three separate components: 1.) The Abstract Xerox Hash Signature. This includes the software which defines how to use a one-way hash function to generate a digital signature, but excludes the software of any particular one way hash function. In this implementation, the Abstract Xerox Hash Signature specifically excludes both MD4 and Snefru. The Abstract Xerox Hash Signature specifies how to generate a digital signature given a one-way hash function, but does NOT specify any particular signature system, nor any particular one-way hash function. 2.) The one-way hash function Snefru (4 passes). 3.) The one-way hash function MD4. * Addendum dated 91.11.13: since the original implementation of this * system, MD5 has been made available and MD4 is no longer recommended. * Further, Snefru with 8 passes has been made available, and Snefru * with 4 passes is no longer recommended. For these and various other * reasons, the present system should be viewed as a experimental, and * is not intended for production use. It is convenient to have a name for the combination of these components. In general, the term "Xerox Hash Signature" refers to one or more specific digital signatures generated by combining the Abstract Xerox Hash Signature with one or more specific one-way hash functions. In this particular case, the Xerox Hash Signature refers to the coupling of the Abstract Xerox Hash Signature with the particular one-way hash functions Snefru or MD4. In other contexts, the term "Xerox Hash Signature" could be used to describe the coupling of the Abstract Xerox Hash Signature with other specific one-way hash functions. When it is necessary to discuss a specific digital signature method created by coupling the Abstract Xerox Hash Signature with some specific one-way hash function, the combination can use the name "The Xerox Hash Signature based on ." For example, the present software can be specifically described as the Xerox Hash Signature based on Snefru or MD4. In general, the signature system described by the term "The Xerox Hash Signature" will be determined by context. The term "The Abstract Xerox Hash Signature" refers specifically to the general method of combining an arbitrary one-way hash function into a digital signature system, but does not include any particular one-way hash function nor does it define or specify any particular implementation of a digital signature. Only when the Abstract Xerox Hash Signature is combined with some specific one way hash function have we defined a particular digital signature. Both Snefru and MD4 are available separately. Both Snefru and MD4 are accompanied by separate notices. The implementations of Snefru and MD4 included here have been modified for convenient use with the present software, but they are distinct entities. The notices which accompany Snefru and MD4 have been reproduced in the appropriate files. Those notices do not pertain to the Abstract Xerox Hash Signature, which is a separate and distinct software entity and which is covered by separate and distinct notices, which follow: ************************************************************************** COPYRIGHT (C) 1990 Xerox corporation. All rights reserved. XEROX CORPORATION MAKES NO REPRESENTATIONS CONCERNING EITHER THE MERCHANTABILITY OF THIS SOFTWARE OR THE SUITABILITY OF THIS SOFTWARE FOR ANY PARTICULAR PURPOSE. IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. This software is being made available for experimental use only. No right is granted to use this software for any other purpose. The rights (if any) granted for using the individual modules of this software are specified by notices appearing in those modules. Further, in the author's opinion, it is very possible that one or more security related bugs exist in this experimental implementation. Also note that the routine OpenAndLockTwoFiles has been modified to improve portability, but this has created a timing window. This should not significantly affect experimental use. This software may be covered by one or more claims of either or both of the following patents: #4,881,264: Digital Signature System and Method Based on a Conventional Encryption Function Issued: November 14, 1989. Inventor: Ralph C. Merkle. Patent owned by: Ralph C. Merkle. Contact: Ralph C. Merkle Xerox PARC 3333 Coyote Hill Road Palo Alto, CA 94304 merkle@xerox.com #4,309,569: Method of Providing Digital Signatures Issued: January 5, 1982. Inventor: Ralph C. Merkle. Patent owned by: Stanford University. Contact: Joe Koepnick Stanford University Office of Technology Licensing 857 Serra Street, Second Floor Stanford, CA 94305-6225. koepnick@angelo.Stanford.EDU XEROX MAKES NO REPRESENTATION WHATSOEVER WITH RESPECT TO THE RELEVANCE OF SUCH PATENTS TO THIS SOFTWARE OR WITH RESPECT TO THE VALIDITY OF THESE PATENTS. XEROX AUTHORIZES NO USES OF THE ABSTRACT XEROX HASH SIGNATURE EXCEPT FOR THOSE THAT ARE STRICTLY LIMITED TO EXPERIMENTAL PURPOSES. These notices must be retained in any copies of any part of this software. ************************************************************************** The following papers describe some of the concepts and ideas used in this program: "A Certified Digital Signature" by Ralph C. Merkle, Crypto '89. "One Way Hash Functions and DES" by Ralph C. Merkle, Crypto '89. "A Fast Software One Way Hash Function" by Ralph C. Merkle, accepted by The Journal of Cryptology. "A Digital Signature Based On A Conventional Encryption Function" by Ralph C. Merkle, Crypto '87. "Secrecy, Authentication, and Public Key Systems" by Ralph C. Merkle, Ph.D. Thesis, Stanford University Electrical Engineering Dept. 1979. "Design Principles for Hash Functions" by Ivan Bjerre Damgard, Crypto '89. "Constructing Digital Signatures from a One Way Function" by Leslie Lamport, Computer Science Laboratory Technical Report October 18, 1979, CSL-98 This is version 1.0d, June 19, 1990. THE SIGNING ALGORITHM The signing algorithm accepts, as input, 1.) An input file to be signed, and an optional short ascii "message" that accompanies the signed file. 2.) The "current auxilliary information" (a file) which keeps track of various bookkeeping information. The file is not secret, and the information in it can be divulged without compromising the security of the signature. Loss or destruction of the aux info file is not a catastrophe -- the information in it can be re-computed from the machine key file and the secret user key using the "recoverauxinfo" command. 3.) A "user key" entered by the user. The user key is not retained by the program after the message has been signed. 4.) A "machine key" file that holds a "machine key" and the authentication path. If the "machine key" is compromised, then the security of the system rests solely on the user key. If the user key is compromised, then the security of the system rests solely on the machine key. Both keys together are required to generate the "secret key" that is actually used to sign messages. The use of two separate keys has two distinct purposes: (a) It means that guessing the user key (which is often ill-chosen by an unsophisticated or disinterested user) will not compromise the machine key or the secret key, thus improving security, and (b) It insures that the user will not use the same secret key to generate a new secret-key/public-key pair. Generating the same secret-key/public-key pair and signing two sets of messages with it will seriously compromise security. The "machine key" file must also be resistant to unauthorized modification -- unauthorized changes to the "authentication path" specified in this file can compromise security. In most applications, it would also be advisable to make the "machine key" unreadable by anyone other than those properly authorized. The signing algorithm produces as output: 1.) A "signature file." This file is normally .JZ, (e.g., the input file name prefixed with ".JZ"). THE CHECKING ALGORITHM The checking algorithm re-computes the public key from the signature and the signed file. The signed file and message are first hashed using a one-way hash function (Snefru 2.0 or MD4, other algorithms can be added easily) to 128 bits. This 128-bit hash value is then further hashed to 64-bits using a parameterized one-way hash function. This 64-bit value is then hashed to compute the public key. The public key is then looked up in a public directory to determine the signer. If no matching public key is found in the public directory, the signer is unknown. Note that re-computing the public key is idiosyncratic to hash signatures. Other signature methods do not re-compute the public key, but instead require that the public key be entered as a parameter to the signature checking algorithm. This difference might be confusing for those familiar with the normal methods of checking a signature. Input for the checking algorithm is: 1.) An input file whose signature is to be checked. 2.) The signature file, .JZ. Output from the checking algorithm is: 1.) The re-computed public key. The re-computed public key is used to find any matching (previously authenticated) public keys present in the public directory. If such a match is found, then the program prints out the name of the signer and other information taken from the signed message and the public entry. If no known public key matches the re-computed public key, then a diagnostic is issued. Clearly, the correctness of the result depends on the validity of the entries in the public directory. Various methods of safeguarding the public entries have been proposed in the literature. A consideration of these methods is beyond the scope of these comments. In general, the security of hash signatures is dependent on the security of the underlieing one-way hash function. If that one-way hash function is secure, then the signature method is secure. Two one-way hash functions suitable for use in this hash signature are: Snefru 2.0 (with 4 passes) and MD4. Other one-way hash functions can be easily added to the code. The selection of which one way-hash function to use can be based on any of several criteria, including performance, perceived security, availability, etc. Snefru 2.0 is available via anonymous FTP from arisia.xerox.com in directory /pub/hash. (MD4 is also available in the same directory as a courtesy). MD4 has not yet received adequate review to establish its security. Its security should therefore be viewed with caution at the present time (90.04.25). Further information about MD4 is available from: RSA Data Security. 10 Twin Dolphin Drive Redwood City, CA 94065 415-595-8782 rsa@well.uucp Either Snefru or MD4 can be used in the current hash signature method by setting the "hashMethod" variable appropriately. Snefru is set to 4 passes. Setting Snefru to 2 passes is known to be insecure (courtesy of the work of Eli Biham). This is available by setting the "hashMethod" variable to SNEFRU4_METHOD. MD4 can be specified by setting "hashMethod" to MD4_METHOD. Snefru with 2 passes can be broken. The security of Snefru with 4 passes has not yet received adequate analysis and its use must be viewed with caution at the present time (90.04.25). * Addendum added 91.11.13: Snefru with 8 passes and MD5 are both * available. For these and other reasons, the present software * should be viewed as experimental. Use of the present system * for production use is not recommended. Please note that the constant values that specify different hash methods must be unique. For this reason, anyone who wishes to add a new method should contact Ralph C. Merkle to insure that the constant value selected does not conflict with other values. In any event, constants below 100 (decimal) are reserved for use by Xerox. Other one - way hash functions are known. DES-based one-way hash functions could be used, but the inclusion of DES source code here might create export issues. DES-based one way hash functions are significantly slower than the two functions specifically designed for software implementation, Snefru and MD4; on the other hand it is reasonable to place greater confidence in the security of the recent IBM proposal based on DES. (Note that the security of other DES-based one-way hash functions should be viewed with great caution). Other one-way hash functions known to the author at this time are either significantly less efficient or are still subject to serious reservations about security. * Addendum added 91.11.13: it is expected that NIST will soon * adopt a one way hash function. This hash function could be * adopted as the basis for the hash signature, in which case the * security of the system would depend on the security of the NIST * provided one-way hash function (modulo the fact that it is likely * that some security-related bugs exist in this implementation). The two values "securitySize" and "parameterSize" directly control the security that is provided by the hash signature. "securitySize" is the number of 32 - bit words that are used to hold security-critical values. "parameterSize" is the number of 32-bit words used to hold the security-related "parameter" values. It is expected that securitySize will either be 2 (providing 64 bits of effective security) or 3 (providing 96 bits of effective security). The value of parameterSize will usually be 2 (providing 2 ** 64 possible parameters). Note that "parameters" to the one-way hash function need not be randomly selected from a large space. However, different applications of the one-way hash function should normally use different values for the parameter. Thus, parameter values might start at 1 and proceed systematically through 2, 3, 4, etc. Accidental re-use of the same parameter value has only a minor impact on security, but widespread re-use of the same parameter value would substantially reduce security. Setting both securitySize and parameterSize to 2 (64 bits) should provide a level of security sufficient for most commercial applications. For applications requiring a higher level of security, securitySize can be increased. In most applications, even at a very high security level, it will not be necessary to increase parameterSize. The security of the one-way hash functions is also extremely important. At this point in time, both Snefru 2.0 and MD4 are receiving widespread scrutiny. It is too early to make reliable statements about the level of security that they provide. Note: Snefru 2.0 with 2 passes was broken by Eli Biham, a PhD student of Adi Shamir's. As a consequence, the use of 2 passes is not recommended. It would seem prudent to use 4 passes at the time of this writing (90.04.25). Further note: Although it is in general possible to prove that hash signatures are secure if the underlieing one-way hash function is secure, it should be remembered that the present program has some 6,000 lines of code in it. The present code has not been reviewed by anyone other than the author, nor does the author make any claims of infallibility. It is therefore probable that some security related bugs exist in the present implementation. This implementation should be verified or re-implemented independently prior to any non-experimental use. If anyone using this program finds a bug or error, please contact Ralph C.Merkle via E-mail (merkle@xerox.com) or via normal mail at: Xerox PARC 3333 Coyote Hill Road Palo Alto, CA 94304 (415) 494-4000 A signature (the contents of the ".sig" file) has the following data structure: bits description word 0 16 hashMethod. Uniquely identifies the particular method used. 4 Reserved for future use -- set to 0 for now 4 securitySize (in 32-bit words) 4 parameterSize (in 32-bit words) 4 bitsPerVerifier word 1 16 total number of words (in the signature itself) to be hashed 8 number of words of binary data (excluding the ascii message) 8 number of OTTs (One Time Trees) in this signature word 2 32 The date and time the message was signed, in seconds since 00:00:00 GMT, Jan. 1, 1970. (Standard UNIX time format). word 3 128 hash value of file being signed. Can occupy more than 4 words depending on the value of "securitySize". word 7 64 initial parameter. Can occupy more than 2 words depending on the value of "parameterSize". word 9 0 Additional binary data. Can occupy more than 0 words depending on the value in the "number of words of binary data" field (see above). word 9 0 An ascii text message to be associated with the signature. Can occupy more than 0 words depending on the "total number of words" field (see above). Further 32-bit words: Repeat the following block according to the 8-bit "number of OTTs" (One Time Trees) parameter. 64 count verifier (size varies according to "securitySize") 64 count verifier . . repeated 2**bitsPerVerifier times . 64 count verifier 32 path description (Fixed at 32 bits for all time) 64 branch verifier (size varies according to "securitySize") 64 branch verifier 64 branch verifier . . repeated according to "path description" (up to 31 times) . 64 branch verifier 64 branch verifier End of repeated block Both the count verifiers and the branch verifiers given above can be increased in size in increments of 32 bits. When securitySize is 2 they will be 64 bits (as shown); when securitySize is 3 they would be 96 bits, when securitySize is 4 they would be 128 bits, etc. The 64-bit initial parameter can be increased in size in increments of 32 bits by increasing the parameterSize from 2. Thus, when parameterSize is 2 the initial parameter will be 64 bits (as shown). When the parameterSize is 3, the initial parameter will be 96 bits. When the parameterSize is 4, the initial parameter will be 128 bits. The path description will always be 32 bits. Note that "word32" MUST be 32 bits Inventor, designer, and implementor: Ralph C. Merkle */ #include #include #include #include #include #include #include #include "md4.h" #include "wellKnownDirectories" /* security related parameters */ #define MAX_PARAMETER_SIZE 4 #define MAX_SECURITY_SIZE 4 #define MAX_PUBLIC_KEY_SIZE (1+MAX_PARAMETER_SIZE+MAX_SECURITY_SIZE) #define MACHINE_KEY_SIZE 4 #define USER_KEY_SIZE 8 #define USER_KEY_SIZE_IN_BYTES (4*USER_KEY_SIZE) #define SECRET_KEY_SIZE 4 #define HASHED_USER_KEY_SIZE 2 #define WORD_SIZE_IN_BITS 32 #define MAX_HASH_VALUE_SIZE (2*MAX_SECURITY_SIZE) /* * Hash method constants. * Note: DO NOT add an additional hash method constant without * contacting Xerox. These constants must be globally * unique. Values less than 100 are reserved by Xerox and * should not be used by others. */ #define MD4_METHOD 100 #define SNEFRU4_METHOD 4 /* other parameters and constants */ #define MAX_OTT_SIZE 128 #define MAX_BITS_PER_VERIFIER 10 #define TRUE 1 #define FALSE 0 #define SIGN 1 #define CHECK 0 #define COMPUTE_OTT_HASH 2 #define PATHOVERFLOW 1 #define NOPATHOVERFLOW 0 #define DO_NOT_CREATE_FILE 1 #define CREATE_FILE 0 #define MAX_INPUT_BLOCK_SIZE 16 #define SNEFRU_INPUT_BLOCK_SIZE 16 #define INPUT_BLOCK_SIZE 16 #define MAX_SIGNATURE_SIZE 4096 #define MAX_FILE_NAME_SIZE_IN_BYTES 100 /* warning! If you change MAX_USER_NAME_SIZE_IN_BYTES, * scan through the source for all occurences -- * some occurences are in quotes and so cannot * be expanded by the pre-processor (and * just have the integer value 300...) */ #define MAX_USER_NAME_SIZE_IN_BYTES 300 #define MAX_LINE_LENGTH 1000 #define MAX_NO_OF_COUNTS 50 #define MAX_STACK_DEPTH 10 #define MAX_MESSAGE_SIZE 512 #define MAX_MESSAGE_SIZE_IN_BYTES (MAX_MESSAGE_SIZE*4) #define DEBUG 0 #define SELF_TEST 0 #define SHOW_TIMES 0 #define SHORT_UNUSUAL_STRING "JZ" #define MIN_DEPTH 1 #define WIPE_IT 4096 #define VERSION "@(#) Hash Signatures Version 1.0d June 19, 1990" typedef unsigned long int word32; struct SIG_BUF { word32 signature[MAX_SIGNATURE_SIZE]; int locInSignature; }; void HashExpand(); void HashAny(); void DoSelfTest(); void Snefru512(); long clock(); long time(); char *sprintf(); void SnefruHashFile(); #if DEBUG void PrintIt (string, val, length) char *string; word32 val[]; int length; { int di; printf ("%s", string); for (di = 0; di < length; di++) printf (" %lx", val[di]); printf ("\n"); } #endif /* * Compute the number of bits required to represent the given value */ int SizeInBits (value) word32 value; { int i; for (i = 0; i < WORD_SIZE_IN_BITS; i++) if ((value >> i) == 0) return (i); return (WORD_SIZE_IN_BITS); } /* * Copy an input array to an output array of "size" words. The following * routine can be replaced with "memcpy" if this is defined in your * implementation. */ void Copy (out, in, size) word32 in[]; word32 out[]; int size; { int i; for (i = 0; i < size; i++) out[i] = in[i]; } /* * The following routine is a simple error exit routine -- it prints a * message and aborts */ void ErrAbort (s) char *s; { /* Don't print empty strings */ if (s[0] != (char) 0) fprintf (stderr, "%s\n", s); exit (2); }; /* * PORTABILITY WARNING: The following routine should port * to most Unix systems. It returns the GMT time in seconds since * January 1, 1970. */ long GetTime32() { time_t temp[1]; (void) time(temp); return(temp[0]); } #if SHOW_TIMES /* * PORTABILITY WARNING: The following routine should port to * most Unix systems. It returns the cpu time in microseconds. */ word32 GetCpuTime() { return(clock()); } #endif /* PORTABILITY WARNING: The following routine should * be moderately portable among Unix systems. * It is intended to create a new * file and set the access modes for that file to * READ and WRITE by the creator of the file, but * to disallow any access (read, write, or anything else) * by others. It returns a stream that will * be used to write information to the created file. * * If the file already exists, this routine will check * to see if it's "empty". By definition, an "empty" * file has 4 bytes of "0" at its beginning (or a length * of less than 4 bytes). If the file is empty, the stream * is returned. If the file is not empty, a diagnostic * is issued. * */ FILE * OpenIfEmpty(fileName) char *fileName; { int fd; FILE *stream; char temp[4]; int length; int i; fd = open(fileName, O_WRONLY | O_CREAT | O_EXCL, 0600); stream = fdopen(fd, "w"); if (stream == NULL){ stream = fopen(fileName, "r+"); if (stream == NULL) { fprintf(stderr, "Can't read from \"%s\"\n", fileName); ErrAbort(""); }; length = fread(temp, 1, 4, stream); if (ferror(stream) != 0) { fprintf(stderr, "Can't access \"%s\"\n", fileName); ErrAbort(""); }; for (i=0; ist_size == 0) return(-1); return(0); } /* * PORTABILITY WARNING: The following routine is not very * portable, although it should work on most Unix systems. * * Returns the name of the owner of the file */ char * FileOwnersName(file) FILE *file; { struct passwd temp[1]; struct stat buf[1]; static char ownersName[MAX_USER_NAME_SIZE_IN_BYTES]; if (fstat(fileno(file), buf) != 0) ErrAbort("Bad fstat"); *temp = *getpwuid(buf->st_uid); strcpy(ownersName, temp->pw_name); return(ownersName); } /* * PORTABILITY WARNING: The following routine is not very * portable, although it should work on most Unix systems. * * The following routine is intended to determine the user's * name. This name will then be used as the file name which * holds the user's "stable storage" (machine key and stuff). * The most important thing to remember about this routine is * that THE TWO USER NAMES FOR TWO DIFFERENT USERS MUST BE * DIFFERENT. If not, there will be a collision in the file * name space, with resulting chaos and confusion. */ void GetUserName(userName) char userName[MAX_USER_NAME_SIZE_IN_BYTES]; { struct passwd temp[1]; *temp = *getpwuid(getuid()); if (strlen(temp->pw_name) >= MAX_USER_NAME_SIZE_IN_BYTES) { fprintf(stderr, "User name \"%s\" is too long\n", temp->pw_name); ErrAbort(""); }; strcpy(userName, temp->pw_name); } /* * The following routine creates the public file name which * holds the public file entry. */ char * MakePublicFileName(publicDirectoryName, publicKey) char *publicDirectoryName; word32 publicKey[MAX_PUBLIC_KEY_SIZE]; { static char publicFileName[MAX_FILE_NAME_SIZE_IN_BYTES]; char digits[50]; /* public file entries are file names like: * publicDirectory/p361ad90c */ strcpy(publicFileName, publicDirectoryName); (void) sprintf(digits, "/p%08lx_%08lx", publicKey[1], publicKey[2]); strcat(publicFileName, digits); return(publicFileName); }; /* * The following routine creates the names of the two * "stable storage" files. */ void NameStableStorage(file1Name, file2Name) char file1Name[MAX_FILE_NAME_SIZE_IN_BYTES]; char file2Name[MAX_FILE_NAME_SIZE_IN_BYTES]; { char userName[MAX_USER_NAME_SIZE_IN_BYTES]; strcpy(file1Name, STABLE1_DIRECTORY); strcpy(file2Name, STABLE2_DIRECTORY); strcat(file1Name, "/"); strcat(file2Name, "/"); GetUserName(userName); if (5+strlen(file1Name)+strlen(userName)>=MAX_FILE_NAME_SIZE_IN_BYTES) { fprintf(stderr, " File name \"%s%s\" too long\n", file1Name, userName); ErrAbort(""); }; if (5+strlen(file2Name)+strlen(userName)>=MAX_FILE_NAME_SIZE_IN_BYTES) { fprintf(stderr, " File name \"%s%s\" too long\n", file2Name, userName); ErrAbort(""); }; strcat(file1Name, userName); strcat(file2Name, userName); strcat(file1Name, "1"); strcat(file2Name, "2"); } /* * The following routine returns the file name after removing * any preceding directory names. Example input: * "/usr/merkle/vorpal". Example output: "vorpal" */ GetFileSuffix(fileName, suffixName) char *fileName; char suffixName[MAX_FILE_NAME_SIZE_IN_BYTES]; { int i; i = strlen(fileName); while( (fileName[i-1]!= '/') && (i > 0)) i--; strcpy(suffixName, &fileName[i]); } /* * The following routine creates and returns the name of the * auxilliary information files. Note that there are two * auxilliary file names, only one of which is in use at any * given time. The one in use is selected by examining the * binary variable "toggle". The use of two auxilliary file * names (used in a "ping-pong" fashion) eliminates timing problems * that can arise when the signing process fails to go to * a normal completion. */ char * AuxInfoFile(toggle) int toggle; { char userName[MAX_USER_NAME_SIZE_IN_BYTES]; static char file1Name[MAX_FILE_NAME_SIZE_IN_BYTES]; static char file2Name[MAX_FILE_NAME_SIZE_IN_BYTES]; static int flag = 0; if (flag == 0) { flag = 1; strcpy(file1Name, AUX_INFO_DIRECTORY); strcpy(file2Name, AUX_INFO_DIRECTORY); strcat(file1Name, "/"); strcat(file2Name, "/"); GetUserName(userName); if (5+strlen(file1Name)+strlen(userName) >= MAX_FILE_NAME_SIZE_IN_BYTES) { fprintf(stderr, " File name \"%s%s\" too long\n", file1Name, userName); ErrAbort(""); }; if (5+strlen(file2Name)+strlen(userName) >= MAX_FILE_NAME_SIZE_IN_BYTES) { fprintf(stderr, " File name \"%s%s\" too long\n", file2Name, userName); ErrAbort(""); }; strcat(file1Name, userName); strcat(file2Name, userName); strcat(file1Name, "Aux0"); strcat(file2Name, "Aux1"); }; if (toggle==0) return(file1Name); else return(file2Name); } /* * A simple error testing routine. If the variable is not between * "low" and "high" inclusive, then print out the error message and * abort. */ void RangeCheck (value, low, high, message) int value; int low; int high; char *message; { if ((value < low) || (value > high)) { fprintf(stderr, "Error: %s is %d which is outside [%d .. %d]\n", message, value, low, high); ErrAbort (""); }; } /* PORTABILITY WARNING: This routine assumes that file names * use "/" to separate the directory names in a full path name. * The following routine creates the signature file name * given the file name. Basically, if fileName is * /this/that/other/name * then the signature file is * /this/that/other/.JZname */ void makeSigFileName(fileName, sigFileName) char *fileName; char sigFileName[MAX_FILE_NAME_SIZE_IN_BYTES]; { int length; int slashLoc; char tempName[MAX_FILE_NAME_SIZE_IN_BYTES]; length = strlen(fileName); if (length > MAX_FILE_NAME_SIZE_IN_BYTES-6) { fprintf(stderr, "Signature file name \"%s\" too long.\n", fileName); ErrAbort(""); }; for(slashLoc = length-1; slashLoc >= 0; slashLoc--) if(fileName[slashLoc] == '/') break; strcpy(tempName, &fileName[slashLoc+1]); strcpy(sigFileName, fileName); sigFileName[slashLoc+1] = 0; /* terminate after the '/' */ strcat(sigFileName, "."); strcat(sigFileName, SHORT_UNUSUAL_STRING); strcat(sigFileName, tempName); } /** The following rather awful computation determines how many "counts" there should be. Basically, its securitySizeInBits / noOfBitsPerVerifier + fudgeFactor where your really have to add and subtract 1's to make things work out exactly right. The fudgeFactor provides enough additional counts to take care of the "check" field. Examples: for a 64-bit word and 4 bits per verifier, we need 18 counts for a 96-bit word and 4 bits per verifier, we need 27 counts for a 128-bit word and 4 bits per verifier, we need 35 counts for a 64-bit word and 8 bits per verifier, we need 10 counts */ int ComputeNumberOfCounts (securitySize, wordSizeInBits, noOfBitsPerVerifier) int securitySize; int wordSizeInBits; int noOfBitsPerVerifier; { int noOfCounts; word32 maxSumOfCounts; noOfCounts = (wordSizeInBits * securitySize - 1) / noOfBitsPerVerifier + 1; /* * 1L << noOfBitsPerVerifier - 1 is really 2**noOfBitsPerVerifier-1 */ maxSumOfCounts = noOfCounts * ((1L << noOfBitsPerVerifier) - 1); noOfCounts += (SizeInBits (maxSumOfCounts) - 1) / noOfBitsPerVerifier + 1; return (noOfCounts); } /* * The following routine unpacks the 32-bits in "header" and * puts them into the specified output variables. * Yes, it would be nice to use bit fields, but there's * no guarantee that the bit fields would be laid out in * the same fashion from compiler to compiler. */ void UnPackHeader ( header, hashMethodPtr, securitySizePtr, parameterSizePtr, noOfBitsPerVerifierPtr, hashValueSizePtr, noOfCountsPtr) word32 header; int *hashMethodPtr; int *securitySizePtr; int *parameterSizePtr; int *noOfBitsPerVerifierPtr; int *hashValueSizePtr; int *noOfCountsPtr; { *hashMethodPtr = header >> 16; *securitySizePtr = header>>8 & 0xf; *parameterSizePtr = header>>4 & 0xf; *noOfBitsPerVerifierPtr = header & 0xf; *hashValueSizePtr = 2 * (*securitySizePtr); *noOfCountsPtr = ComputeNumberOfCounts (*securitySizePtr, WORD_SIZE_IN_BITS, *noOfBitsPerVerifierPtr); /* range check everything */ switch (*hashMethodPtr) { case SNEFRU4_METHOD: break; case MD4_METHOD: break; default: ErrAbort("bad hash method"); }; RangeCheck (*securitySizePtr, 2, MAX_SECURITY_SIZE, "security size"); RangeCheck (*parameterSizePtr, 2, MAX_PARAMETER_SIZE, "parameter size"); RangeCheck (*noOfBitsPerVerifierPtr, 0, MAX_BITS_PER_VERIFIER, "bits per verifier"); RangeCheck (*noOfCountsPtr, 0, MAX_NO_OF_COUNTS, "number of counts"); } /* * The following routine packs the given input variables * into *headerPtr. */ void PackHeader ( headerPtr, hashMethod, securitySize, parameterSize, noOfBitsPerVerifier) word32 *headerPtr; int hashMethod; int securitySize; int parameterSize; int noOfBitsPerVerifier; { switch (hashMethod) { case SNEFRU4_METHOD: break; case MD4_METHOD: break; default: ErrAbort("bad hash method"); }; RangeCheck (securitySize, 2, MAX_SECURITY_SIZE, "security size"); RangeCheck (parameterSize, 2, MAX_PARAMETER_SIZE, "parameter size"); RangeCheck (noOfBitsPerVerifier, 0, MAX_BITS_PER_VERIFIER, "number of bits per verifier"); *headerPtr = hashMethod << 16 | securitySize << 8 | parameterSize << 4 | noOfBitsPerVerifier; } /* * The following routine computes the size of the public key * by examining the first word (32 bits) of the public key. * The first word encodes the hash method, the security size, * the parameter size, and other good stuff. The size of * the public key in 32-bit words is just the security size * plus the parameter size plus 1 (the size of the 32-bit header). */ int ComputePublicKeySize(publicKey) word32 publicKey[MAX_PUBLIC_KEY_SIZE]; { int hashMethod; int securitySize; int parameterSize; int noOfBitsPerVerifier; int hashValueSize; int noOfCounts; if (publicKey[0] == 0) return(1); UnPackHeader ( publicKey[0], &hashMethod, &securitySize, ¶meterSize, &noOfBitsPerVerifier, &hashValueSize, &noOfCounts); return(securitySize + parameterSize + 1); } /* * The following routine converts an array of word32 to a byte * array. It is primarily intended to eliminate the byte-ordering problem. * VAXes order the bytes in a character array differently than SUN's do. This * routine is not needed on SUNs (or other big-endian machines). */ void ConvertLongToChar (wordBuffer, charBuffer, wordLength) word32 wordBuffer[ /* wordLength */ ];/* input buffer */ char charBuffer[ /* wordLength * 4 */ ]; /* output buffer */ int wordLength; { int i; word32 temp; for (i = 0; i < wordLength; i++) { temp = wordBuffer[i]; charBuffer[4 * i + 0] = (temp >> 24) & 0xff; charBuffer[4 * i + 1] = (temp >> 16) & 0xff; charBuffer[4 * i + 2] = (temp >> 8) & 0xff; charBuffer[4 * i + 3] = (temp >> 0) & 0xff; }; }; /* * The following routine converts a byte array to an array of word32. * It is intended to eliminate the byte-ordering problem. VAXes order * the bytes in a character array differently than SUN's do. Some * machines do REALLY horrible things -- this routine should be * fairly portable (if not blindingly fast). */ void ConvertBytes (buffer, wordBuffer, charCount) char buffer[]; /* input buffer */ word32 wordBuffer[]; /* output buffer */ int charCount; { int i; int wordCount; word32 t0, t1, t2, t3; if (charCount <= 0) return; wordCount = (charCount+3)>>2; for (i = 0; i < wordCount; i++) { t0 = buffer[4 * i + 0]; t1 = buffer[4 * i + 1]; t2 = buffer[4 * i + 2]; t3 = buffer[4 * i + 3]; t0 &= 0xff; t1 &= 0xff; t2 &= 0xff; t3 &= 0xff; wordBuffer[i] = (t0 << 24) | (t1 << 16) | (t2 << 8) | t3; }; /* Get rid of the bottom bytes that aren't wanted */ switch (charCount&3) { case 0: break; case 1: wordBuffer[wordCount-1] &= 0xff000000L; break; case 2: wordBuffer[wordCount-1] &= 0xffff0000L; break; case 3: wordBuffer[wordCount-1] &= 0xffffff00L; }; } /* * The following routine increments a "path" location. This is a 32-bit word * that encodes a "path" up a binary tree. Succeeding bits in the word * following the first "1" bit show whether to take the left or right branch * in the "path" from the root of the tree to any particular leaf. * * When a "path" is incremented, it might overflow. In this case, the path value * is left unchanged and the routine returns the value "PATHOVERFLOW". * Otherwise the routine returns the value "NOPATHOVERFLOW" */ int BumpPath (pathPtr) word32 *pathPtr; { /* * If the bit pattern passed in looks like: 000...00111....11 * * then, by definition, incrementing this by one causes an "overflow" in * the path description because it alters the location of the leading * "1" bit. */ if (SizeInBits (*pathPtr) != SizeInBits ((*pathPtr) + 1)) return (PATHOVERFLOW); else { (*pathPtr)++; return (NOPATHOVERFLOW); }; } /* * The following routine sets a single bit in "array" at the designated * offset */ void SetBit (array, bitOffset) word32 array[]; int bitOffset; { int wordOffset; word32 mask; /* * compute word and bit offsets for a 32-bit word size. Yes, * "WORD_SIZE_IN_BITS" is 32. If not, watch out! */ wordOffset = bitOffset / WORD_SIZE_IN_BITS; bitOffset %= WORD_SIZE_IN_BITS; mask = 1; mask <<= 31; /* Set the topmost bit */ /* set the bit... */ array[wordOffset] |= (mask >> bitOffset); } /* * The following routine grabs noOfBits bits from the given array, at the * given bitOffset. Bits beyond the array limit are defined to be 0 */ word32 FetchBits (array, arrayLimit, bitOffset, noOfBits) word32 array[ /* arrayLimit */ ]; int arrayLimit; int bitOffset; int noOfBits; { word32 temp1, temp2; int wordOffset; /* * compute word and bit offsets for a 32-bit word size. Yes, * "WORD_SIZE_IN_BITS" is 32. If not, watch out! */ wordOffset = bitOffset / WORD_SIZE_IN_BITS; bitOffset %= WORD_SIZE_IN_BITS; /* fetch the (possibly two) words involved from the array */ temp1 = array[wordOffset]; temp2 = array[wordOffset + 1]; /* check for references beyond array limit */ if (wordOffset >= arrayLimit) temp1 = 0; if (wordOffset + 1 >= arrayLimit) temp2 = 0; /* grab the bits from the right spot */ temp1 <<= bitOffset; temp1 >>= WORD_SIZE_IN_BITS - noOfBits; /* "or" in the bits from the second word, if needed */ if (bitOffset + noOfBits > WORD_SIZE_IN_BITS) temp1 |= (temp2 >> (2 * WORD_SIZE_IN_BITS - bitOffset - noOfBits)); /* and return the bits! */ return (temp1); } void DownLeft (p, n) word32 p[ /* n */ ]; int n; { word32 overFlow; int temp; int i; /* grab the bottom bit from the last word of the parameter */ overFlow = p[n - 1] & 1; for (i = 0; i < n; i++) { temp = p[i] & 1; p[i] >>= 1; p[i] |= overFlow << 31; overFlow = temp; }; }; void DownRight (p, n) word32 p[ /* n */ ]; { word32 overFlow; int temp; int i; /* grab the bottom bit from the last word of the parameter */ overFlow = p[n - 1] & 1; overFlow ^= 1; /* flip the bit */ for (i = 0; i < n; i++) { temp = p[i] & 1; p[i] >>= 1; p[i] |= overFlow << 31; overFlow = temp; }; }; void UpRight (p, n) word32 p[ /* n */ ]; int n; { word32 overFlow; int temp; int i; /* Quick test for overflow */ if ((p[0] >> (WORD_SIZE_IN_BITS - 4)) == 0) ErrAbort (" Parameter overflow!"); /* grab the top bit from the first word of the parameter */ overFlow = (p[0] >> 31) & 1; for (i = n - 1; i >= 0; i--) { temp = (p[i] >> 31) & 1; p[i] <<= 1; p[i] |= overFlow; overFlow = temp; }; }; void UpLeft (p, n) word32 p[ /* n */ ]; int n; /* length (in 32-bit words) of the parameter */ { word32 overFlow; int temp; int i; /* Quick test for overflow */ if ((p[0] >> (WORD_SIZE_IN_BITS - 4)) == 0) ErrAbort (" Parameter overflow!"); /* grab the top bit from the first word of the parameter */ overFlow = (p[0] >> 31) & 1; for (i = n - 1; i >= 0; i--) { temp = (p[i] >> 31) & 1; p[i] <<= 1; p[i] |= overFlow; overFlow = temp; }; p[n - 1] ^= 1; /* flip the bottom bit */ }; void UpPath (p, parameterSize, path) word32 p[]; word32 path; { word32 reversePath; int pathLength; if (path <= 0) ErrAbort ("logic error -- path is not positive"); reversePath = 0; /* First, reverse the bits */ for (pathLength = -1; path != 0; pathLength++) { reversePath <<= 1; reversePath |= (path & 1); path >>= 1; }; /* * and throw away the bottom bit (which used to be the top bit) * because it's always 1 */ reversePath >>= 1; /* and go up the path! */ while (pathLength != 0) { if ((reversePath & 1) == 1) UpLeft (p, parameterSize); else UpRight (p, parameterSize); reversePath >>= 1; pathLength--; }; } void HashThreeItems (out, outSize, in1, in1Size, in2, in2Size, in3, in3Size, hashMethod) word32 out[]; int outSize; word32 in1[]; int in1Size; word32 in2[]; int in2Size; word32 in3[]; int in3Size; int hashMethod; { word32 tempIn[MAX_INPUT_BLOCK_SIZE]; if ((in1Size + in2Size + in3Size) > MAX_INPUT_BLOCK_SIZE) ErrAbort ("Input to HashThreeItems is too large"); Copy (&tempIn[0], in1, in1Size); Copy (&tempIn[in1Size], in2, in2Size); Copy (&tempIn[in1Size + in2Size], in3, in3Size); HashAny (out, outSize, tempIn, in1Size + in2Size + in3Size, hashMethod); }; void HashTwoItems (out, outSize, in1, in1Size, in2, in2Size, hashMethod) word32 out[]; int outSize; word32 in1[]; int in1Size; word32 in2[]; int in2Size; int hashMethod; { word32 empty[1]; HashThreeItems (out, outSize, in1, in1Size, in2, in2Size, empty, 0, hashMethod); }; /* * This routine increments a 64-bit counter by the given increment. */ void Increment64BitCounter (counter, increment) word32 counter[2]; long int increment; { word32 maxInt = 0xffffffffL; if ( (maxInt-counter[1]) < increment) { /* Overflow from the lower 32 bits */ if (counter[0] == maxInt) ErrAbort("64-bit counter overflowed"); /* bump the upper 32 bits */ counter[0]++; /* and then increment the lower 32 bits */ /* without ever overflowing! */ counter[1] = maxInt-counter[1]; counter[1] = increment - counter[1]; } else /* increment the total number of bits read */ counter[1] += increment; } /* Read a "chunk" of "chunkSize" 32-bit words from the given * file. Take care of any byte ordering problems involved * in reading the 32-bit input values */ int ReadChunk (file, chunk, chunkSize) FILE *file; word32 *chunk; int chunkSize; { char temp[4*SNEFRU_INPUT_BLOCK_SIZE]; int byteCount; int i; byteCount = fread(temp, 1, 4*chunkSize, file); if (ferror(file) != 0) { ErrAbort("Can't read from input file"); }; for (i=byteCount; i<4*chunkSize; i++) temp[i] = 0; ConvertBytes(temp, chunk, 4*chunkSize); return(byteCount); } void HashFile (inputFile, hashValue, hashValueSize, hashMethod) FILE *inputFile; word32 hashValue[MAX_HASH_VALUE_SIZE]; int hashValueSize; { switch (hashMethod) { case MD4_METHOD: Md4HashFile(inputFile, hashValue, hashValueSize); break; case SNEFRU4_METHOD: SnefruHashFile(inputFile, hashValue, hashValueSize, hashMethod); break; }; } /* Write out information to one of the "stable storage files". * The stable storage file is small, so it's kept in readable * ASCII format. This simplifies debugging, but makes the * following routine more complex than a simple series of binary * writes. */ void WriteOneStableStorageFile (stableStorageFile, machineKey, publicKey, hashedUserKey, path, top, toggle, OTTsize) FILE *stableStorageFile; word32 machineKey[MACHINE_KEY_SIZE]; word32 publicKey[MAX_PUBLIC_KEY_SIZE]; word32 hashedUserKey[HASHED_USER_KEY_SIZE]; word32 path[MAX_STACK_DEPTH]; int top; int toggle; { int i; int printCode; int publicKeySize; RangeCheck(top, 0, MAX_STACK_DEPTH-1, "top"); rewind(stableStorageFile); printCode = fprintf (stableStorageFile, "machineKey ="); if (printCode <= 0) ErrAbort ("can't write string to stable storage"); for (i = 0; i < MACHINE_KEY_SIZE; i++) { printCode = fprintf (stableStorageFile, " %08lx", machineKey[i]); if (printCode <= 0) ErrAbort ("bad write to stable storage"); if ((i % 4 == 3) && (MACHINE_KEY_SIZE - 1 > i)) printCode = fprintf (stableStorageFile, "\n "); if (printCode <= 0) ErrAbort ("bad string write to stable storage"); }; printCode = fprintf (stableStorageFile, "\nhashedUserKey ="); if (printCode <= 0) ErrAbort ("can't write string to stable storage"); for (i = 0; i < HASHED_USER_KEY_SIZE; i++) { printCode = fprintf (stableStorageFile, " %08lx", hashedUserKey[i]); if (printCode <= 0) ErrAbort ("bad write to stable storage"); if ((i % 4 == 3) && (HASHED_USER_KEY_SIZE - 1 > i)) printCode = fprintf (stableStorageFile, "\n "); if (printCode <= 0) ErrAbort ("bad string write to stable storage"); }; printCode = fprintf (stableStorageFile, "\npublicKey ="); if (printCode <= 0) ErrAbort ("can't write string to stable storage"); publicKeySize = ComputePublicKeySize(publicKey); for (i = 0; i < publicKeySize; i++) { printCode = fprintf (stableStorageFile, " %08lx", publicKey[i]); if (printCode <= 0) ErrAbort ("bad public key write to stable storage"); }; printCode = fprintf (stableStorageFile, "\ntoggle = %d\n", toggle); if (printCode <= 0) ErrAbort ("can't write toggle to stable storage"); printCode = fprintf (stableStorageFile, "OTTsize = %d\n", OTTsize); if (printCode <= 0) ErrAbort ("can't write OTTsize to stable storage"); printCode = fprintf (stableStorageFile, "top = %d\npath =", top); if (printCode <= 0) ErrAbort ("can't write top to stable storage"); for (i = 0; i <= top; i++) { printCode = fprintf (stableStorageFile, " %lx", path[i]); if (printCode <= 0) ErrAbort ("bad path write to stable storage"); }; printCode = fprintf (stableStorageFile, "\n"); if (printCode <= 0) ErrAbort ("can't write string to stable storage"); } /* * The following routines write the machine key, the current signature * path, and the public key redundantly to two "stable storage" files. * Stable storage can be relied upon not to be changed, altered, or lost in * some unpleasant fashion. As long as one of the two "stable storage" files * is intact, the aux info can be recovered. If the two stable storage * files are kept on separate disk drives, then it is unlikely that both will * be lost simultaneously. * * Stable storage should not be backed up. If one stable storage file is * damaged, it should be re-initialized from the other stable storage file. * In the unlikely event that both stable storage files are damaged, then no * further messages should be signed with this signing key. A new * signing-key/checking-key pair should be issued. * * Note that loss of the secret signing key is a nuisance, but does not cause * any significant damage. Messages already signed continue to be valid even * if the secret signing key is destroyed. The newly issued secret signing * key can be used to sign new messages. If interruption of service is an * issue, then a second signing-key/checking-key pair can be prepared for the * user in advance of need. This will also provide uninterrupted service to * the user in case of other problems, such as theft, destruction, loss, * compromise, etc. * * (Note that these precautions will prevent compromise of the machine * key and will also prevent re-signing of two different messages with * the same one-time signature. The latter would seriously * jeopordize security). * * Exactly how this "stable storage" is realized in any particular instance is * left up to the implementor. */ void WriteStableStorage (file1, file2, machineKey, publicKey, hashedUserKey, path, top, toggle, OTTsize) FILE *file1; FILE *file2; word32 machineKey[]; word32 publicKey[MAX_PUBLIC_KEY_SIZE]; word32 hashedUserKey[HASHED_USER_KEY_SIZE]; word32 path[]; int top; int toggle; int OTTsize; { WriteOneStableStorageFile (file1, machineKey, publicKey, hashedUserKey, path, top, toggle, OTTsize); WriteOneStableStorageFile (file2, machineKey, publicKey, hashedUserKey, path, top, toggle, OTTsize); }; /* * The following two routines read the machine key and the path from stable * storage. If the two files have conflicting values, abort at once. */ void ReadOneStableStorageFile (stableStorageFile, machineKey, publicKey, hashedUserKey, path, topPtr, togglePtr, OTTsizePtr) FILE *stableStorageFile; word32 machineKey[MACHINE_KEY_SIZE]; word32 publicKey[MAX_PUBLIC_KEY_SIZE]; word32 hashedUserKey[HASHED_USER_KEY_SIZE]; word32 path[MAX_STACK_DEPTH]; int *topPtr; int *togglePtr; int *OTTsizePtr; { int i; int scanCode; int publicKeySize; scanCode = fscanf (stableStorageFile, " machineKey = "); if (scanCode != 0) ErrAbort ("can't read string from stable storage"); for (i = 0; i < MACHINE_KEY_SIZE; i++) { scanCode = fscanf (stableStorageFile, " %lx", &machineKey[i]); if (scanCode != 1) ErrAbort ("bad read from stable storage"); }; scanCode = fscanf (stableStorageFile, " hashedUserKey = "); if (scanCode != 0) ErrAbort ("can't read string from stable storage"); for (i = 0; i < HASHED_USER_KEY_SIZE; i++) { scanCode = fscanf (stableStorageFile, " %lx", &hashedUserKey[i]); if (scanCode != 1) ErrAbort ("bad read from stable storage"); }; /* Read in the public key */ scanCode = fscanf (stableStorageFile, " publicKey = "); if (scanCode != 0) ErrAbort ("can't read string from stable storage"); scanCode = fscanf (stableStorageFile, " %lx", publicKey); if (scanCode != 1) ErrAbort ("bad path read from stable storage"); publicKeySize = ComputePublicKeySize(publicKey); for (i = 1; i < publicKeySize; i++) { scanCode = fscanf (stableStorageFile, " %lx", &publicKey[i]); if (scanCode != 1) ErrAbort ("bad path read from stable storage"); }; scanCode = fscanf (stableStorageFile, " toggle = %d", togglePtr); if (scanCode != 1) ErrAbort ( "can't read toggle from stable storage"); RangeCheck(*togglePtr, 0, 1, "toggle"); scanCode = fscanf (stableStorageFile, " OTTsize = %d", OTTsizePtr); if (scanCode != 1) ErrAbort ( "can't read OTTsize from stable storage"); RangeCheck(*OTTsizePtr, 4, MAX_OTT_SIZE, "OTT size"); scanCode = fscanf (stableStorageFile, " top = %d path =", topPtr); if (scanCode != 1) ErrAbort ("can't read top from stable storage"); if ((*topPtr) < 0) ErrAbort ("top negative"); if ((*topPtr) >= MAX_STACK_DEPTH) ErrAbort ("top too big"); for (i = 0; i <= *topPtr; i++) { scanCode = fscanf (stableStorageFile, " %lx", &path[i]); if (scanCode != 1) ErrAbort ("bad path read from stable storage"); }; } /* * Read the inputs from both files, and then compare them. If they disagree, * abort. */ void ReadStableStorage (file1, file2, machineKey, publicKey, hashedUserKey, path, topPtr, togglePtr, OTTsizePtr) FILE *file1; FILE *file2; word32 machineKey[MACHINE_KEY_SIZE]; word32 publicKey[MAX_PUBLIC_KEY_SIZE]; word32 hashedUserKey[HASHED_USER_KEY_SIZE]; word32 path[MAX_STACK_DEPTH]; int *topPtr; int *togglePtr; int *OTTsizePtr; { word32 tempMachineKey[MACHINE_KEY_SIZE]; word32 tempPublicKey[MAX_PUBLIC_KEY_SIZE]; word32 tempPath[MAX_STACK_DEPTH]; int publicKeySize; int tempTop; int tempToggle; int tempOTTsize; int i; ReadOneStableStorageFile (file1, machineKey, publicKey, hashedUserKey, path, topPtr, togglePtr, OTTsizePtr); ReadOneStableStorageFile (file2, tempMachineKey, tempPublicKey, hashedUserKey, tempPath, &tempTop, &tempToggle, &tempOTTsize); if (tempTop != (*topPtr)) ErrAbort ("conflicting stable storage for \"top\""); if (tempToggle != (*togglePtr)) ErrAbort ("conflicting stable storage for \"toggle\""); if (tempOTTsize!= (*OTTsizePtr)) ErrAbort ("conflicting stable storage for \"OTTsize\""); for (i = 0; i < tempTop; i++) if (tempPath[i] != path[i]) ErrAbort ("conflicting stable storage for path"); for (i = 0; i < MACHINE_KEY_SIZE; i++) if (machineKey[i] != tempMachineKey[i]) ErrAbort ("conflicting stable storage for machine key"); publicKeySize = ComputePublicKeySize(publicKey); for (i = 0; i < publicKeySize; i++) if (publicKey[i] != tempPublicKey[i]) ErrAbort ("conflicting stable storage for public key"); } /* * This routine reads in the data from the "signature file", which * is kept in binary, and then unpacks the data, performs range * checking, and generally converts the information in the file * into a useful internal format. */ void ReadSignature (sigFile, hashMethodPtr, noOfSignaturesPtr, parameter, parameterSizePtr, securitySizePtr, hashValueSizePtr, noOfCountsPtr, noOfBitsPerVerifierPtr, signature, locationPtr, wordsToHashPtr, hashFromSig, dateTime32Ptr, binaryDataFieldSizePtr, message) FILE *sigFile; int *hashMethodPtr; int *noOfSignaturesPtr; word32 parameter[MAX_PARAMETER_SIZE]; int *parameterSizePtr; int *securitySizePtr; int *hashValueSizePtr; int *noOfCountsPtr; int *noOfBitsPerVerifierPtr; word32 signature[MAX_SIGNATURE_SIZE]; int *locationPtr; word32 *wordsToHashPtr; word32 hashFromSig[MAX_HASH_VALUE_SIZE]; word32 *dateTime32Ptr; int *binaryDataFieldSizePtr; char message[MAX_MESSAGE_SIZE_IN_BYTES]; { int lengthOfSignatureInBytes; word32 *locInSig; int messageSize; char charSignature[MAX_SIGNATURE_SIZE * 4]; lengthOfSignatureInBytes = fread (charSignature, 1, MAX_SIGNATURE_SIZE * 4, sigFile); if (ferror(sigFile) != 0) { ErrAbort("Can't read from signature file"); }; ConvertBytes (charSignature, signature, MAX_SIGNATURE_SIZE * 4); /* dull error tests */ RangeCheck (lengthOfSignatureInBytes, 0, MAX_SIGNATURE_SIZE * 4, "Signature length"); if (lengthOfSignatureInBytes % 4 != 0) ErrAbort ("Signature length not a multiple of 4 bytes"); /* We've read in the signature file */ locInSig = signature; /* now unpack the header */ UnPackHeader( *locInSig, hashMethodPtr, securitySizePtr, parameterSizePtr, noOfBitsPerVerifierPtr, hashValueSizePtr, noOfCountsPtr); /* * return the number of one-time signatures used in this * signature */ locInSig++; *noOfSignaturesPtr = (*locInSig) & 0xff; *wordsToHashPtr = (*locInSig)>>16; *binaryDataFieldSizePtr = ((*locInSig)>>8) & 0xff; locInSig++; *dateTime32Ptr = *locInSig; locInSig++; Copy(hashFromSig, locInSig, *hashValueSizePtr); locInSig += *hashValueSizePtr; Copy(parameter, locInSig, *parameterSizePtr); locInSig += *parameterSizePtr; messageSize = *wordsToHashPtr - *binaryDataFieldSizePtr-2; if (messageSize >= MAX_MESSAGE_SIZE-4) ErrAbort("Message size in signature is too big"); ConvertLongToChar(locInSig, message, messageSize); locInSig += messageSize; /* * compute the start of the one-time signature count verifier block */ *locationPtr = locInSig - signature; }; /* * This routine checks an authentication path */ void CheckAuthenticationPath (checkValue, securitySize, parameter, parameterSize, hashMethod, path, branchVerifiers) word32 checkValue[]; int securitySize; word32 parameter[]; int parameterSize; int hashMethod; word32 path; word32 branchVerifiers[]; { while (path != 1) { if ((path & 1) == 1) { #if DEBUG printf ("auth path bit is 1\n"); PrintIt ("branchVerifiers=", branchVerifiers, securitySize); PrintIt ("checkValue=", checkValue, securitySize); PrintIt ("parameter=", parameter, parameterSize); #endif DownRight (parameter, parameterSize); HashThreeItems (checkValue, securitySize, branchVerifiers, securitySize, checkValue, securitySize, parameter, parameterSize, hashMethod); } else { #if DEBUG printf ("auth path bit is 0\n"); PrintIt ("checkValue=", checkValue, securitySize); PrintIt ("branchVerifiers=", branchVerifiers, securitySize); PrintIt ("parameter=", parameter, parameterSize); #endif DownLeft (parameter, parameterSize); HashThreeItems (checkValue, securitySize, checkValue, securitySize, branchVerifiers, securitySize, parameter, parameterSize, hashMethod); }; path >>= 1; branchVerifiers += securitySize; }; } /* * The following routine accepts as input a public key * (normally computed from a signature) and the name * of the public directory. It looks up the public key * in the public directory. It returns 0 if the search was * successful, and sets "userName" to the name found in * the public directory. It returns non-zero if the search * was unsuccessful, and userName is left empty (""); * */ int LookUpPublicKey (publicKey, publicDirectoryName, userName) word32 publicKey[MAX_PUBLIC_KEY_SIZE]; /* Input */ char *publicDirectoryName; char userName[MAX_USER_NAME_SIZE_IN_BYTES]; /* Output */ { char *publicFileName; word32 tempPublicKey[MAX_PUBLIC_KEY_SIZE]; int i; int publicKeySize; int scanReturnCode; FILE *publicFile; char *ownersName; char tempName[MAX_USER_NAME_SIZE_IN_BYTES]; /* open the "public file" entry, read only */ publicFileName = MakePublicFileName(publicDirectoryName, publicKey); publicFile = fopen (publicFileName, "r"); userName[0] = (char) 0; if (publicFile == NULL) return(-1); ownersName = FileOwnersName(publicFile); scanReturnCode = fscanf (publicFile, /* WARNING: * update constant 300 when MAX_USER_NAME_SIZE_IN_BYTES * is changed. Updating this constant is required * to correctly prevent overflow if the public file * has a name which is too long. */ "%*[^#]#%300[^:]: %lx", tempName, tempPublicKey); if (scanReturnCode == 0) /* didn't scan the "#" */ scanReturnCode = fscanf (publicFile, /* WARNING: update constant 300 when * MAX_USER_NAME_SIZE_IN_BYTES is changed. */ "#%300[^:]: %lx", tempName, tempPublicKey); if(scanReturnCode != 2) return(-1); publicKeySize = ComputePublicKeySize(tempPublicKey); for (i = 1; i < publicKeySize; i++) { scanReturnCode = fscanf(publicFile, " %lx", &tempPublicKey[i]); if (scanReturnCode != 1) return(-1); }; for (i = 0; i < publicKeySize; i++) if (publicKey[i] != tempPublicKey[i]) return(-1); FileClose (publicFile, publicFileName); if ( (strlen(tempName)+strlen(ownersName)) >= MAX_USER_NAME_SIZE_IN_BYTES-4) { fprintf(stderr, "String: \"%s:%s\" is longer than %d bytes\n", ownersName, tempName, MAX_USER_NAME_SIZE_IN_BYTES); ErrAbort(""); }; strcpy(userName, ownersName); strcat(userName, ":"); strcat(userName, tempName); return(0); } /* * The following routine creates/checks a one-time signature. Note * that the routine can be used both to sign and to check a value to * sign. */ void OneTimeSignCheck (valueToSign, securitySize, parameter, parameterSize, signCheckFlag, noOfCounts, bitsPerVerifier, xyVector, hashForSignature, hashMethod) word32 valueToSign[ /* securitySize */ ]; int securitySize; word32 parameter[MAX_PARAMETER_SIZE]; int parameterSize; int signCheckFlag; word32 xyVector[MAX_NO_OF_COUNTS * MAX_SECURITY_SIZE]; word32 hashForSignature[MAX_SECURITY_SIZE]; int hashMethod; { int i, j; word32 count[MAX_NO_OF_COUNTS]; word32 twoToTheBitsPerVerifier; int bitsNeeded; word32 parameterLastWord; twoToTheBitsPerVerifier = 1; twoToTheBitsPerVerifier <<= bitsPerVerifier; /* compute the counts */ for (i = 0; i < noOfCounts; i++) count[i] = FetchBits (valueToSign, securitySize, i * bitsPerVerifier, bitsPerVerifier); /* * The valueToSign has some bits in it. We need to pad out this * array with a "check" field to prevent tampering with the count * values by evil doers */ FillInTheCheckField (count, noOfCounts, securitySize, bitsPerVerifier, twoToTheBitsPerVerifier); /* * Okay, we've spread the bits out into the array "count" and also * added the check field to the array "count" */ /* * Compute the number of bits needed for the combined count and the * bitsPerVerifier in the parameter */ bitsNeeded = SizeInBits ((word32) noOfCounts * twoToTheBitsPerVerifier); /* and make room for that many bits in the parameter */ for (i = 0; i < bitsNeeded; i++) UpRight (parameter, parameterSize); /* * Remember the last word of the parameter -- we diddle it to * parameterize each application of hashN for the one time signature */ parameterLastWord = parameter[parameterSize - 1]; /* zero out the hash value result for the signature */ for (i = 0; i < securitySize; i++) hashForSignature[i] = 0; for (i = 0; i < noOfCounts; i++) { /* hash down the sequence */ if (signCheckFlag != CHECK) { for (j = twoToTheBitsPerVerifier - 1; j > count[i]; j--) { /* tweak the parameter for */ parameter[parameterSize - 1] = parameterLastWord ^ (i * twoToTheBitsPerVerifier + j); HashTwoItems (&xyVector[i * securitySize], securitySize, &xyVector[i * securitySize], securitySize, parameter, parameterSize, hashMethod); }; }; /* keep hashing */ if (signCheckFlag != SIGN) { for (j = count[i]; j > 0; j--) { parameter[parameterSize - 1] = parameterLastWord ^ (i * twoToTheBitsPerVerifier + j); HashTwoItems (&xyVector[i * securitySize], securitySize, &xyVector[i * securitySize], securitySize, parameter, parameterSize, hashMethod); }; parameter[parameterSize - 1] = parameterLastWord ^ (i * twoToTheBitsPerVerifier); HashThreeItems (hashForSignature, securitySize, hashForSignature, securitySize, &xyVector[i * securitySize], securitySize, parameter, parameterSize, hashMethod); }; }; /* Restore the parameter */ parameter[parameterSize - 1] = parameterLastWord; for (i = 0; i < bitsNeeded; i++) DownLeft (parameter, parameterSize); } /* * The following routine hashes the input file, compares this * with the hash stored in the signature (to verify they are * equal), then re-computes the public key using the provided * authentication information, and finally returns the computed * public key. */ Check(inputFile, sigFile, publicKey, dateTime32Ptr, msgBuffer) FILE *inputFile; FILE *sigFile; word32 publicKey[MAX_PUBLIC_KEY_SIZE]; word32 *dateTime32Ptr; char msgBuffer[MAX_MESSAGE_SIZE_IN_BYTES]; { int location; word32 pathDescription; int signatures; word32 outputCheckValue[MAX_SECURITY_SIZE]; int hashMethod; int securitySize; int hashValueSize; word32 hashValue[MAX_HASH_VALUE_SIZE]; int noOfCounts; int noOfSignatures; int noOfBitsPerVerifier; word32 parameter[MAX_PARAMETER_SIZE]; word32 checkValue[MAX_SECURITY_SIZE]; int parameterSize; word32 signature[MAX_SIGNATURE_SIZE]; int i; word32 hashFromSig[MAX_HASH_VALUE_SIZE]; word32 *hashPtr; int chunkSize; word32 wordsToHash; int binaryDataFieldSize; #if SHOW_TIMES word32 startTime, stopTime; int signRepeatIndex; double signTime; #endif /* self-test, to make sure everything is okay. */ DoSelfTest (SELF_TEST); /* Check the digitally signed message */ #if SHOW_TIMES startTime = GetCpuTime(); for(signRepeatIndex=0; signRepeatIndex<1000; signRepeatIndex++) { #endif ReadSignature (sigFile, &hashMethod, &noOfSignatures, parameter, ¶meterSize, &securitySize, &hashValueSize, &noOfCounts, &noOfBitsPerVerifier, signature, &location, &wordsToHash, hashFromSig, dateTime32Ptr, &binaryDataFieldSize, msgBuffer); HashFile (inputFile, hashValue, hashValueSize, hashMethod); for (i=0; i= chunkSize) { HashTwoItems(hashValue, hashValueSize, hashValue, hashValueSize, hashPtr, chunkSize, hashMethod); hashPtr += chunkSize; i -= chunkSize; }; if (i>0) HashTwoItems(hashValue, hashValueSize, hashValue, hashValueSize, hashPtr, i, hashMethod); HashTwoItems(hashValue, hashValueSize, hashValue, hashValueSize, &wordsToHash, 1, hashMethod); #if DEBUG PrintIt ("hashValue=", hashValue, hashValueSize); PrintIt ("parameter=", parameter, parameterSize); #endif /* initialize checkValue */ HashTwoItems (checkValue, securitySize, hashValue, hashValueSize, parameter, parameterSize, hashMethod); for (signatures = 0; signatures < noOfSignatures; signatures++) { /* Set parameter for one-time signature */ DownLeft (parameter, parameterSize); UpLeft (parameter, parameterSize); #if DEBUG printf ("\nAbout to check one time signature\n"); PrintIt ("checkValue=", checkValue, securitySize); PrintIt ("parameter=", parameter, parameterSize); #endif OneTimeSignCheck (checkValue, securitySize, parameter, parameterSize, CHECK, noOfCounts, noOfBitsPerVerifier, &signature[location], outputCheckValue, hashMethod); Copy (checkValue, outputCheckValue, securitySize); #if DEBUG printf ("\nfinished checking one time signature\n"); PrintIt ("checkValue=", checkValue, securitySize); PrintIt ("parameter=", parameter, parameterSize); #endif location += securitySize * noOfCounts; pathDescription = signature[location]; location++; /* skip the path description */ DownRight (parameter, parameterSize); CheckAuthenticationPath (checkValue, securitySize, parameter, parameterSize, hashMethod, pathDescription, &signature[location]); /* now skip past the authentication path */ location -= securitySize; /* fix off-by-one error */ while (pathDescription != 0) { pathDescription >>= 1; location += securitySize; }; }; #if SHOW_TIMES }; stopTime = GetCpuTime(); signTime = stopTime-startTime; signTime = signTime/1.0e6; fprintf(stderr, "%9.3f milliseconds to read and check signature\n", signTime); #endif #if DEBUG printf ("about to print results\n"); PrintIt ("checkValue=", checkValue, securitySize); PrintIt ("parameter=", parameter, parameterSize); #endif #if SHOW_TIMES startTime = GetCpuTime(); for(signRepeatIndex=0; signRepeatIndex<1000; signRepeatIndex++) { #endif publicKey[0] = signature[0]; Copy(&publicKey[1], checkValue, securitySize); Copy(&publicKey[1+securitySize], parameter, parameterSize); #if SHOW_TIMES }; stopTime = GetCpuTime(); signTime = stopTime-startTime; signTime = signTime/1.0e6; fprintf(stderr, "%9.3f milliseconds for look up in public file\n", signTime); #endif } /* * The following routine dumps a signature in a readable format. */ void DumpSig(sigFile) FILE *sigFile; { int location; word32 pathDescription; int signatures; int hashMethod; int securitySize; int hashValueSize; int noOfCounts; int noOfSignatures; int noOfBitsPerVerifier; word32 parameter[MAX_PARAMETER_SIZE]; int parameterSize; word32 signature[MAX_SIGNATURE_SIZE]; int i; int j; word32 dateTime32; char message[MAX_MESSAGE_SIZE_IN_BYTES]; word32 hashFromSig[MAX_HASH_VALUE_SIZE]; word32 wordsToHash; int binaryDataFieldSize; long longTemp; char dateString[80]; ReadSignature (sigFile, &hashMethod, &noOfSignatures, parameter, ¶meterSize, &securitySize, &hashValueSize, &noOfCounts, &noOfBitsPerVerifier, signature, &location, &wordsToHash, hashFromSig, &dateTime32, &binaryDataFieldSize, message); printf(" hash method = %d\n", hashMethod); printf(" number of counts = %d\n", noOfCounts); printf(" no of bits per verifier = %d\n", noOfBitsPerVerifier); printf(" number of signatures = %d\n", noOfSignatures); printf(" location of first sig = %d\n", location); longTemp = dateTime32; strcpy(dateString, ctime(&longTemp)); if (dateString[strlen(dateString)-1] == '\n') dateString[strlen(dateString)-1] = (char) 0; printf(" date of signature = %s\n", dateString); printf(" message = \"%s\"\n", message); printf(" words to hash = %d\n", wordsToHash); printf(" binary data field size = %d\n", binaryDataFieldSize); printf(" security size = %d\n", securitySize); printf(" parameter size = %d\n", parameterSize); printf(" parameter ="); for (i=0; i>= 1; location += securitySize; }; }; } /* The signing routines follow this point */ /* The following routine emptys the signature buffer */ /* All it does is set the "next available word" pointer */ /* to 0 */ void EmptySignatureBuffer (sigBuffer) struct SIG_BUF *sigBuffer; { sigBuffer->locInSignature = 0; } /* Places the passed in array into the signature buffer. */ void PutInSignatureBuffer (array, size, sigBuffer) word32 array[]; int size; struct SIG_BUF *sigBuffer; { int i; if (sigBuffer->locInSignature + size >= MAX_SIGNATURE_SIZE) ErrAbort (" overflow of authentication path"); for (i = 0; i < size; i++) sigBuffer->signature[sigBuffer->locInSignature + i] = array[i]; sigBuffer->locInSignature += size; } void GenPathForOTT (depth, top, securitySize, noOfCounts, path, countVerifiers, authPathValues, sigBuffer) int depth; int top; int securitySize; int noOfCounts; word32 path[MAX_STACK_DEPTH]; word32 countVerifiers [MAX_STACK_DEPTH] [MAX_NO_OF_COUNTS] [MAX_SECURITY_SIZE]; word32 authPathValues [MAX_STACK_DEPTH] [2 * MAX_OTT_SIZE] [MAX_SECURITY_SIZE]; struct SIG_BUF *sigBuffer; { int i; word32 temp; /* spit out the count verifiers */ if (depth < top) for (i = 0; i < noOfCounts; i++) { #if DEBUG PrintIt ("countVerifier=", countVerifiers[depth + 1][i], securitySize); #endif PutInSignatureBuffer (countVerifiers[depth + 1][i], securitySize, sigBuffer); }; /* and the path description */ #if DEBUG PrintIt ("path=", &path[depth], 1); #endif PutInSignatureBuffer (&path[depth], 1, sigBuffer); /* and then the authentication path */ temp = path[depth]; for (temp = path[depth]; temp != 1; temp >>= 1) { #if DEBUG PrintIt ("authPathValue=", authPathValues[depth][temp ^ 1], securitySize); #endif PutInSignatureBuffer (authPathValues[depth][temp ^ 1], securitySize, sigBuffer); }; } void GenerateAuthenticationPath (top, securitySize, noOfCounts, path, countVerifiers, authPathValues, sigBuffer) int top; int securitySize; int noOfCounts; word32 path[MAX_STACK_DEPTH]; word32 countVerifiers [MAX_STACK_DEPTH] [MAX_NO_OF_COUNTS] [MAX_SECURITY_SIZE]; word32 authPathValues [MAX_STACK_DEPTH] [2 * MAX_OTT_SIZE] [MAX_SECURITY_SIZE]; struct SIG_BUF *sigBuffer; { int depth; for (depth = top; depth >= 0; depth--) GenPathForOTT (depth, top, securitySize, noOfCounts, path, countVerifiers, authPathValues, sigBuffer); } /* * Read in the information from the auxilliary file (which is stored * in binary) and convert it into some useful internal format. */ void ReadBinaryAuxInfo (top, securitySize, noOfCounts, OTTsize, authPathValues, countVerifiers, toggle) int top; int securitySize; int noOfCounts; int OTTsize; word32 authPathValues [MAX_STACK_DEPTH] [2 * MAX_OTT_SIZE] [MAX_SECURITY_SIZE]; word32 countVerifiers [MAX_STACK_DEPTH] [MAX_NO_OF_COUNTS] [MAX_SECURITY_SIZE]; int toggle; { int readReturnCode; FILE *auxInfoFile; int i,j,k,loc; word32 temp[MAX_STACK_DEPTH*MAX_SECURITY_SIZE*2*MAX_OTT_SIZE]; if (2*MAX_OTT_SIZE < MAX_NO_OF_COUNTS) ErrAbort("2*MAX_OTT_SIZE < MAX_NO_OF_COUNTS"); RangeCheck (top, 0, MAX_STACK_DEPTH - 1, "top"); auxInfoFile = fopen (AuxInfoFile(toggle), "rb"); if (auxInfoFile == NULL) ErrAbort ("Can't open aux info file for read"); readReturnCode = fread((char*)temp, 1, top*noOfCounts*securitySize*sizeof(word32), auxInfoFile); if (readReturnCode != top*noOfCounts*securitySize*sizeof(word32)) { fprintf(stderr, "Can't read verifiers from %s\n", AuxInfoFile(toggle)); exit(1); }; loc = 0; for(i=1; i<=top; i++) for(j=0; j MAX_INPUT_BLOCK_SIZE) ErrAbort("Input size too large to generate xyVector"); /* zero out the tempBlock bits */ for (i = 0; i < MAX_INPUT_BLOCK_SIZE; i++) tempBlock[i] = 0; /* and copy the bits to tempBlock */ Copy( tempBlock, bits1, sizeOfBits1); Copy(&tempBlock[sizeOfBits1], bits2, sizeOfBits2); /* * generate some secret random bits by hashing together the input * bits, and putting them in xyVector. */ HashExpand (xyVector, elementsInXVector * sizeOfXYElementInWords, tempBlock, sizeOfBits1+sizeOfBits2, hashMethod); } /* * The following routine takes a set of counts in the array "count," * determines how big the "check" field should be, and how many elements near * the end of the "count" array it should occupy, computes the "check" field * and appends the needed bits to the end of "count" */ FillInTheCheckField (count, noOfCounts, securitySize, bitsPerVerifier, twoToTheBitsPerVerifier) word32 count[ /* noOfCounts */ ]; int noOfCounts; int securitySize; int bitsPerVerifier; word32 twoToTheBitsPerVerifier; { word32 sumOfCounts; word32 maxPossibleSumOfCounts; int startOfCheckFieldInCountArray; int sizeOfCheckFieldInBits; int checkFieldCounts; int startOfCheckFieldInBits; int i; /* * figure out where the check field starts (the offset in the "count" * array) */ startOfCheckFieldInCountArray = (WORD_SIZE_IN_BITS * securitySize - 1) / bitsPerVerifier + 1; /* compute the maximum possible sum of counts */ maxPossibleSumOfCounts = startOfCheckFieldInCountArray * (twoToTheBitsPerVerifier - 1); /* * figure out how many bits are in the check field by seeing how many * bits it takes to hold the maximum possible sum of counts */ sizeOfCheckFieldInBits = SizeInBits (maxPossibleSumOfCounts); /* and figure out how many counts that takes */ checkFieldCounts = (sizeOfCheckFieldInBits - 1) / bitsPerVerifier + 1; /* make sure the check field will fit! */ if (startOfCheckFieldInCountArray + checkFieldCounts != noOfCounts) ErrAbort ("adjust noOfCounts"); /* There shouldn't be anything in the check field right now */ for (i = startOfCheckFieldInCountArray; i < noOfCounts; i++) if (count[i] != 0) ErrAbort ("Undefined check field"); /* add up the total value of the counts */ sumOfCounts = 0; for (i = 0; i < noOfCounts; i++) sumOfCounts += count[i]; /* and reverse it */ sumOfCounts = maxPossibleSumOfCounts - sumOfCounts; /* figure out where the bits start */ startOfCheckFieldInBits = WORD_SIZE_IN_BITS - checkFieldCounts * bitsPerVerifier; /* * The following is not intrinsically wrong -- I'm just not going to * bother handling a check field bigger than 32 bits. I figure it * should never happen.... */ if (startOfCheckFieldInBits <= 0) ErrAbort ("Check field overflowed 32 bits"); /* * Okay, now we know the start and size of the "check" field, grab * the bits and put them into the "count" array at the proper spot */ for (i = startOfCheckFieldInCountArray; i < noOfCounts; i++) { count[i] = FetchBits (&sumOfCounts, 1, startOfCheckFieldInBits, bitsPerVerifier); startOfCheckFieldInBits += bitsPerVerifier; }; if (startOfCheckFieldInBits < WORD_SIZE_IN_BITS) ErrAbort ("check field wouldn't fit"); } /* The following routine finishes an OTT (One Time Tree). An * OTT consists of several one-time signatures combined in a * fixed-sized tree pattern by the use of one-way hash functions. * (See, for example, "A Digital Signature Based on a Conventional * Encryption Function" by Ralph C. Merkle, Crypto '87, page 369). * This routine generates all of the needed one-time signatures, and * computes the required hash value for the tree. In addition, once * the hash value for this OTT has been computed, the signature for * the hash value, which must be generated using the appropriate * one-time signature from the previous OTT, is also computed. */ void FinishNewOTT ( top, securitySize, parameterSize, hashMethod, noOfCounts, noOfBitsPerVerifier, OTTsize, authPathValues, OTTparameterTop, countVerifiers, secretKey, secretKeySize ) int top; int securitySize; int parameterSize; int hashMethod; int noOfCounts; int noOfBitsPerVerifier; int OTTsize; word32 authPathValues [MAX_STACK_DEPTH] [2 * MAX_OTT_SIZE] [MAX_SECURITY_SIZE]; word32 OTTparameterTop[MAX_PARAMETER_SIZE]; word32 countVerifiers [MAX_STACK_DEPTH] [MAX_NO_OF_COUNTS] [MAX_SECURITY_SIZE]; word32 secretKey[SECRET_KEY_SIZE]; int secretKeySize; { word32 i, j; word32 parameter[MAX_PARAMETER_SIZE]; word32 dummyValueToSign[MAX_SECURITY_SIZE]; word32 throwAwayHashResult[MAX_SECURITY_SIZE]; word32 xyVector[MAX_NO_OF_COUNTS * MAX_SECURITY_SIZE]; for (i = 0; i < MAX_SECURITY_SIZE; i++) dummyValueToSign[i] = 0; /* * loop through for each one-time signature in the OTT and compute * its authentication value */ for (i = OTTsize; i < 2 * OTTsize; i++) { Copy (parameter, OTTparameterTop, parameterSize); UpPath (parameter, parameterSize, i); UpLeft (parameter, parameterSize); /* generate the secret X values with which we sign things */ GenerateSecretXValues (xyVector, noOfCounts, securitySize, parameter, parameterSize, secretKey, secretKeySize, hashMethod); #if DEBUG printf ("\ngenerating authPath value\n"); PrintIt ("path=", &i, 1); PrintIt ("secretKey=", secretKey, secretKeySize); PrintIt ("xyVector[last]=", &xyVector[noOfCounts * securitySize - 1], 1); PrintIt ("parameter=", parameter, parameterSize); #endif OneTimeSignCheck (dummyValueToSign, securitySize, parameter, parameterSize, COMPUTE_OTT_HASH, noOfCounts, noOfBitsPerVerifier, xyVector, authPathValues[top][i], hashMethod); #if DEBUG printf ("\done generating authPath value\n"); PrintIt ("xyVector[last]=", &xyVector[noOfCounts * securitySize - 1], 1); PrintIt ("parameter=", parameter, parameterSize); PrintIt ("authPathValues[..]=", authPathValues[top][i], securitySize); #endif }; for (i = OTTsize - 1; i > 0; i--) { Copy (parameter, OTTparameterTop, parameterSize); UpPath (parameter, parameterSize, i); #if DEBUG printf ("\ngenerating non-leaves in authPathValues\n"); PrintIt ("path=", &i, 1); PrintIt ("parameter=", parameter, parameterSize); PrintIt ("authPathValues[top][2*path]=", authPathValues[top][2 * i], securitySize); PrintIt ("authPathValues[top][2*path+1]=", authPathValues[top][2 * i + 1], securitySize); #endif HashThreeItems ( authPathValues[top][i], securitySize, authPathValues[top][2 * i], securitySize, authPathValues[top][2 * i + 1], securitySize, parameter, parameterSize, hashMethod); #if DEBUG printf ("\just-computed parent is:\n"); PrintIt ("authPathValues[top][path]=", authPathValues[top][i], securitySize); #endif }; if (top > 0) { /* * gotta add the signature from the previous OTT to this * OTT */ Copy (parameter, OTTparameterTop, parameterSize); DownLeft (parameter, parameterSize); UpLeft (parameter, parameterSize); /* generate the secret X values with which we sign things */ GenerateSecretXValues (xyVector, noOfCounts, securitySize, parameter, parameterSize, secretKey, secretKeySize, hashMethod); OneTimeSignCheck (authPathValues[top][1], securitySize, parameter, parameterSize, SIGN, noOfCounts, noOfBitsPerVerifier, xyVector, throwAwayHashResult, hashMethod); for (i = 0; i < noOfCounts; i++) for (j = 0; j < securitySize; j++) countVerifiers[top][i][j] = xyVector[i * securitySize + j]; }; } /* * Write out the "aux Info" file in binary. Convert the internal * information describing the signing "state" into a suitable binary * output format. Note that the information in the auxilliary file * is not security sensitive, need not be kept secret, and can even * be modified. If the information is modified, generated signatures * will be invalid (which will be rapidly detected) but security of * valid signatures will not be compromised. Note that in principle * the incorrectly generated signatures could be repaired by determining * the correct authentication paths to use. */ void WriteBinaryAuxInfo(top, securitySize, noOfCounts, OTTsize, authPathValues, countVerifiers, toggle) int top; int securitySize; int noOfCounts; int OTTsize; word32 authPathValues [MAX_STACK_DEPTH] [2 * MAX_OTT_SIZE] [MAX_SECURITY_SIZE]; word32 countVerifiers [MAX_STACK_DEPTH] [MAX_NO_OF_COUNTS] [MAX_SECURITY_SIZE]; int toggle; { FILE *auxInfoFile; int writeReturnCode; word32 temp[MAX_STACK_DEPTH*MAX_SECURITY_SIZE*2*MAX_OTT_SIZE]; int i,j,k,loc; if (2*MAX_OTT_SIZE < MAX_NO_OF_COUNTS) ErrAbort("2*MAX_OTT_SIZE < MAX_NO_OF_COUNTS"); RangeCheck (top, 0, MAX_STACK_DEPTH - 1, "top"); auxInfoFile = fopen (AuxInfoFile(toggle), "wb"); if (auxInfoFile == NULL) ErrAbort ("Can't open aux info file for write"); loc = 0; for(i=1; i<=top; i++) for(j=0; jsignature, charSignature, sigBuffer->locInSignature); printCode = fwrite (charSignature, 4, sigBuffer->locInSignature, sigFile); if (printCode != sigBuffer->locInSignature) ErrAbort ("can't write auth path to *.sig"); } /* * The following routine simply generates some more-or-less random bits and * puts them into "randomOutput". * * Notice that this routine is adequate ONLY IF "machineKey" IS NOT GOING TO * BE KEPT SECRET. If "machineKey" is going to be kept secret, then a more * random method of generating random numbers is required here. * * Whether or not to keep "machineKey" secret is a system design issue not * addressed here. It will often be desirable to keep it secret. * * Many implementors will well wish to use a better "random number" generator * than hashing the time.... */ void AssignRandom (randomOutput, sizeOfRandomOutputInWords, moreBits, sizeOfMoreBitsInWords) word32 randomOutput[]; int sizeOfRandomOutputInWords; word32 moreBits[]; int sizeOfMoreBitsInWords; { word32 unSignedTime; if (sizeOfRandomOutputInWords > 8) ErrAbort ("can't generate enough random bits"); if (sizeOfRandomOutputInWords > (sizeOfMoreBitsInWords + SECRET_KEY_SIZE)) ErrAbort ("too few input bits to generate random bits"); unSignedTime = GetTime32(); /* * hash together the time and whatever extra bits were passed in to * us */ HashTwoItems (randomOutput, sizeOfRandomOutputInWords, moreBits, sizeOfMoreBitsInWords, &unSignedTime, 1, 4); /* randomOutput should now be reasonably random. */ } /* * Push an OTT (One Time Tree) onto the stack of One Time Trees. * Mostly, this is a housekeeping routine that pushes the stack, * initializes one or two critical variables in the OTT, and then * calles "FinishNewOTT". */ void PushOTT ( topPtr, securitySize, parameterSize, hashMethod, noOfCounts, noOfBitsPerVerifier, path, authPathValues, OTTparameterTop, countVerifiers, secretKey, secretKeySize, OTTsize ) int *topPtr; int securitySize; int parameterSize; int hashMethod; int noOfCounts; int noOfBitsPerVerifier; word32 path[MAX_STACK_DEPTH]; word32 authPathValues [MAX_STACK_DEPTH] [2 * MAX_OTT_SIZE] [MAX_SECURITY_SIZE]; word32 OTTparameterTop[MAX_PARAMETER_SIZE]; word32 countVerifiers [MAX_STACK_DEPTH] [MAX_NO_OF_COUNTS] [MAX_SECURITY_SIZE]; word32 secretKey[SECRET_KEY_SIZE]; int secretKeySize; int OTTsize; { UpPath (OTTparameterTop, parameterSize, path[*topPtr]); UpRight (OTTparameterTop, parameterSize); (*topPtr)++; if ((*topPtr) >= MAX_STACK_DEPTH) ErrAbort ("stack overflow"); FinishNewOTT (*topPtr, securitySize, parameterSize, hashMethod, noOfCounts, noOfBitsPerVerifier, OTTsize, authPathValues, OTTparameterTop, countVerifiers, secretKey, secretKeySize); } /* * The following routine determines how much (if any) the stack of OTTs * should be allowed to grow. * * The following method was chosen so that an infinite number of messages can be * signed while the depth (size) of the signature grows logarithmically. It * would also be possible to have this routine return a simple fixed constant * (like 4), in which case all signatures would have the same size (a stack * depth of 4) but the number of signatures would be fixed and finite. * * This method of generating signatures also has the useful property that an OTT * can be generated once, used for a while, and then forgotten. Other * methods of generating signatures (other patterns of signatures within the * tree) result in the need to periodically re-create OTTs. While no great * burden, it seemed easier to avoid this situation. * * A small modification was made -- MIN_DEPTH is the minimum value that * this function will return. This is useful when it is known that some * fixed number of messages will be signed, so there is no point in generating * very small signatures initially (with top = 0, say) when this * will simply force longer signatures later. * In other words, the tree of OTTs will * always have at least a certain depth (from the root OTT to the leaf OTT). */ int ComputeNewStackTop (top, path) int top; word32 path[MAX_STACK_DEPTH]; { int i; int newTop; int bitsInPath; word32 signatureNumber; word32 signaturesAllowed; int depthToAdd; /* * fancy computation basically determines how much stack depth to add * for the level we're looking at in the stack. * * Example: if the number of one-time signatures allowed per OTT is * always 32, then we want the following pattern for the first level: */ /** 32+0 <= path[0] < 32+16: desired top: 0 32+16 <= path[0] < 32+24: desired top: 1 32+24 <= path[0] < 32+28: desired top: 2 32+28 <= path[0] < 32+30: desired top: 3 32+30 <= path[0] < 32+31: desired top: 4 32+31 = path[0] desired top: look at next level */ /* * Note: "path[0]" enumerates the leaves of this OTT (the one time * signatures) by encoding the first path as 32, the second path as * 33, etc. etc. The last path is numbered 63. There are a total of * 32 paths: numbered 32 to 63. * */ newTop = 0; for (i = 0; i <= top; i++) { /* Figure out where the top bit is */ bitsInPath = SizeInBits (path[i]) - 1; /* Make a mask of just the top bit */ signaturesAllowed = (word32) 1 << bitsInPath; /* Now knock off the top bit */ signatureNumber = path[i] ^ signaturesAllowed; if (signatureNumber >= signaturesAllowed) ErrAbort ("logic error: sigNum >= sigsAllowed"); /* And compute how much more depth to add to the stack */ depthToAdd = bitsInPath - SizeInBits (signaturesAllowed - signatureNumber - 1); if (depthToAdd < 0) ErrAbort ("logic error: depthToAdd < 0"); newTop += depthToAdd; /* * If we're using the very last signature at this depth, we * need to look at the next depth to compute new top. * Otherwise, we know the value of newTop right now and * should return. */ if (signaturesAllowed > signatureNumber + 1) { /* force depth to MIN_DEPTH no matter what */ if (newTop < MIN_DEPTH) newTop = MIN_DEPTH; return (newTop); } }; /* force depth to MIN_DEPTH no matter what */ if (newTop < MIN_DEPTH) newTop = MIN_DEPTH; return (newTop); } /* * The following routine reads and hashes the input (taken from * "inputFile") and generates a valid signature for that hash. * The valid signature is written into the file: "sigFile". * The provided userKeyString is a secret key provided by the * user. This user-provided secret key is combined with the * "machineKey" (which might or might not be secret, depending * on the desires of the particular user/site) to generate the * secretKey which is actually used to sign messages. * Much of the code is taken up * with the trivia of error checking, command-line parameter checking, * self-tests, and the like. * */ void Sign (userKeyString, message, inputFile, sigFile) char *userKeyString; char message[MAX_MESSAGE_SIZE_IN_BYTES]; FILE *inputFile; FILE *sigFile; { int top; int newStackTop; int i; word32 parameter[MAX_PARAMETER_SIZE]; word32 throwAwayHashResult[MAX_SECURITY_SIZE]; word32 temp; int updateAuxInfo; int hashMethod; int securitySize; int hashValueSize; word32 hashValue[MAX_HASH_VALUE_SIZE]; char charUserKey[USER_KEY_SIZE_IN_BYTES]; word32 userKey[USER_KEY_SIZE]; word32 secretKey[SECRET_KEY_SIZE]; word32 publicKey[MAX_PUBLIC_KEY_SIZE]; word32 hashedUserKey[HASHED_USER_KEY_SIZE]; word32 machineKey[MACHINE_KEY_SIZE]; word32 authPathValues [MAX_STACK_DEPTH] [2 * MAX_OTT_SIZE] [MAX_SECURITY_SIZE]; word32 countVerifiers [MAX_STACK_DEPTH] [MAX_NO_OF_COUNTS] [MAX_SECURITY_SIZE]; word32 path[MAX_STACK_DEPTH]; int noOfCounts; int noOfBitsPerVerifier; word32 rootParameter[MAX_PARAMETER_SIZE]; word32 checkValue[MAX_SECURITY_SIZE]; word32 root[MAX_SECURITY_SIZE]; word32 xyVector[MAX_NO_OF_COUNTS * MAX_SECURITY_SIZE]; int parameterSize; struct SIG_BUF sigBuffer[1]; word32 tempHSC[HASHED_USER_KEY_SIZE]; word32 dateTime32; word32 wordMessage[MAX_MESSAGE_SIZE]; int messageSize; int messageSizeInBytes; word32 wordsToHash; word32 *hashPtr; word32 binaryDataFieldSize; int startOfBinaryData; int endOfBinaryData; int chunkSize; char file1Name[MAX_FILE_NAME_SIZE_IN_BYTES]; char file2Name[MAX_FILE_NAME_SIZE_IN_BYTES]; FILE *file1; FILE *file2; int toggle; int OTTsize; #if SHOW_TIMES word32 startTime, stopTime; int signRepeatIndex; double signTime; int rememberedTop; #endif /* self-test, to make sure everything is okay. */ DoSelfTest (SELF_TEST); strncpy(charUserKey, userKeyString, USER_KEY_SIZE_IN_BYTES); ConvertBytes (charUserKey, userKey, USER_KEY_SIZE_IN_BYTES); NameStableStorage(file1Name, file2Name); OpenAndLockTwoFiles(&file1, file1Name, &file2, file2Name); ReadStableStorage (file1, file2, machineKey, publicKey, hashedUserKey, path, &top, &toggle, &OTTsize); UnPackHeader (publicKey[0], &hashMethod, &securitySize, ¶meterSize, &noOfBitsPerVerifier, &hashValueSize, &noOfCounts); HashTwoItems(tempHSC, HASHED_USER_KEY_SIZE, userKey, USER_KEY_SIZE, userKey, USER_KEY_SIZE, hashMethod); for (i=0; i<20; i++) HashTwoItems( tempHSC, HASHED_USER_KEY_SIZE, tempHSC, HASHED_USER_KEY_SIZE, tempHSC, HASHED_USER_KEY_SIZE, hashMethod); for (i=0; i= 255) ErrAbort ("Stack exceeds depth of 255"); temp = top + 1; messageSizeInBytes = strlen(message)+1; messageSize = (messageSizeInBytes+3)>>2; binaryDataFieldSize = 1+hashValueSize+parameterSize; wordsToHash = 2+binaryDataFieldSize+messageSize; if ( (wordsToHash & 0xffff0000L) != 0) ErrAbort("message + stuff over 64 kilobytes."); temp |= (wordsToHash<<16); temp |= (binaryDataFieldSize << 8); PutInSignatureBuffer (&temp, 1, sigBuffer); startOfBinaryData = sigBuffer->locInSignature; #if DEBUG PrintIt ("\n initial parameter=", parameter, parameterSize); #endif dateTime32 = GetTime32(); PutInSignatureBuffer (&dateTime32, 1, sigBuffer); PutInSignatureBuffer (hashValue, hashValueSize, sigBuffer); PutInSignatureBuffer (parameter, parameterSize, sigBuffer); /* * Additional binary fields of data can be added at this point by * inserting additional calls to "PutInSignatureBuffer". The additional * fields will be ignored by the signature checking program. To inform * the signature checking program of the size of the additional fields, * it is necessary to increase the size of "binaryDataFieldSize" by the * appropriate amount. It is computed above, and its value is checked * in the following two statements. */ endOfBinaryData = sigBuffer->locInSignature; if ( (endOfBinaryData-startOfBinaryData) != binaryDataFieldSize) ErrAbort("error while generating size of binary data field"); ConvertBytes(message, wordMessage, messageSizeInBytes); PutInSignatureBuffer (wordMessage, messageSize, sigBuffer); if (wordMessage[messageSize-1]&0xffL != 0) ErrAbort("wordMessage is not null terminated"); hashPtr = sigBuffer->signature; for (i=0; i= chunkSize) { HashTwoItems(hashValue, hashValueSize, hashValue, hashValueSize, hashPtr, chunkSize, hashMethod); hashPtr += chunkSize; i -= chunkSize; }; if (i>0) HashTwoItems(hashValue, hashValueSize, hashValue, hashValueSize, hashPtr, i,hashMethod); HashTwoItems(hashValue, hashValueSize, hashValue, hashValueSize, &wordsToHash, 1, hashMethod); /* * hash the double-sized value produced by the strong hash function * into the smaller value that is actually signed. */ #if DEBUG printf ("SIGNING\n"); PrintIt ("hashValue=", hashValue, hashValueSize); PrintIt ("parameter=", parameter, parameterSize); #endif HashTwoItems (checkValue, securitySize, hashValue, hashValueSize, parameter, parameterSize, hashMethod); DownLeft (parameter, parameterSize); UpLeft (parameter, parameterSize); /* generate the secret X values with which we sign things */ GenerateSecretXValues (xyVector, noOfCounts, securitySize, parameter, parameterSize, secretKey, SECRET_KEY_SIZE, hashMethod); #if DEBUG printf ("\nAbout to generate one time signature\n"); PrintIt ("path=", &path[top], 1); PrintIt ("secretKey=", secretKey, SECRET_KEY_SIZE); PrintIt ("xyVector[last]=", &xyVector[noOfCounts * securitySize - 1], 1); PrintIt ("checkValue=", checkValue, securitySize); PrintIt ("parameter=", parameter, parameterSize); #endif OneTimeSignCheck (checkValue, securitySize, parameter, parameterSize, SIGN, noOfCounts, noOfBitsPerVerifier, xyVector, throwAwayHashResult, hashMethod); #if DEBUG printf ("\nfinished generating one time signature\n"); PrintIt ("xyVector[last]=", &xyVector[noOfCounts * securitySize - 1], 1); PrintIt ("checkValue (discarded)=", throwAwayHashResult, securitySize); #endif for (i = 0; i < noOfCounts; i++) PutInSignatureBuffer (&xyVector[i * securitySize], securitySize, sigBuffer); GenerateAuthenticationPath (top, securitySize, noOfCounts, path, countVerifiers, authPathValues, sigBuffer); #if SHOW_TIMES }; stopTime = GetCpuTime(); signTime = stopTime-startTime; signTime = signTime/1.0e6; fprintf(stderr, "%9.3f milliseconds generating signature\n", signTime); #endif /* * make sure the aux info is updated in preparation for the next * signature */ updateAuxInfo = FALSE; if (BumpPath (&path[top]) == PATHOVERFLOW) { updateAuxInfo = TRUE; /* * We've run out of signatures in the top OTT. Do Something! */ while (BumpPath (&path[top]) == PATHOVERFLOW) { top--; if (top < 0) ErrAbort ("Logic Error: no signatures"); }; }; #if SHOW_TIMES startTime = GetCpuTime(); rememberedTop = top; for(signRepeatIndex=0; signRepeatIndex<1000; signRepeatIndex++) { top = rememberedTop; #endif newStackTop = ComputeNewStackTop (top, path); if (newStackTop != top) updateAuxInfo = TRUE; #if DEBUG printf ("\n new top= %d\n", newStackTop); #endif /* get the root parameter */ Copy (parameter, rootParameter, parameterSize); /* push the root parameter up the authentication path */ for (i=0; i < top; i++) { UpPath (parameter, parameterSize, path[i]); UpRight (parameter, parameterSize); }; while (top < newStackTop) { path[top+1] = OTTsize; PushOTT (&top, securitySize, parameterSize, hashMethod, noOfCounts, noOfBitsPerVerifier, path, authPathValues, parameter, countVerifiers, secretKey, SECRET_KEY_SIZE, OTTsize); }; #if SHOW_TIMES }; stopTime = GetCpuTime(); signTime = stopTime-startTime; signTime = signTime/1.0e6; fprintf(stderr, "%9.3f milliseconds setting up for next signature\n", signTime); startTime = GetCpuTime(); for(signRepeatIndex=0; signRepeatIndex<1000; signRepeatIndex++) { #endif if (updateAuxInfo == TRUE) { toggle ^= 1; WriteBinaryAuxInfo(top, securitySize, noOfCounts, OTTsize, authPathValues, countVerifiers, toggle); }; /* * DON'T WRITE OUT THE SIGNATURE UNTIL AFTER UPDATING STABLE * STORAGE!! */ WriteStableStorage (file1, file2, machineKey, publicKey, hashedUserKey, path, top, toggle, OTTsize); #if SHOW_TIMES }; stopTime = GetCpuTime(); signTime = stopTime-startTime; signTime = signTime/1.0e6; fprintf(stderr, "%9.3f milliseconds writing stable storage\n", signTime); #endif /* Hard to repeatedly close the file.... */ FileSyncAndClose(file1, file1Name); FileSyncAndClose(file2, file2Name); #if SHOW_TIMES startTime = GetCpuTime(); for(signRepeatIndex=0; signRepeatIndex<1000; signRepeatIndex++) { #endif WriteSignatureBuffer (sigFile, sigBuffer); #if SHOW_TIMES }; stopTime = GetCpuTime(); signTime = stopTime-startTime; signTime = signTime/1.0e6; fprintf(stderr, "%9.3f milliseconds writing state and signature\n", signTime); #endif }; /* The following routine writes an entry to the public file. It * is used during generation of the public key. Note that the * "public file" is in fact a directory, and that the file names * in the directory are in fact derived from the public key * values, not from the user's name. This is convenient for various * odd reasons that are idiosyncratic to this signature method. * Each public key gets its own file in the directory. Note that * it is theoretically possible (but can be made arbitrarily * improbable) for two different public keys to have the same * file name in the public directory. If this happens, it's just * tough. You get an error message and are told to try again. * For those who find this offensive, the file name for the public * key could in fact encode the entire public key. In this case, * a collsion in the directory would be as improbable as two * public keys being generated that were in fact identical. This * probability can also be made as low as desired. */ void WriteEntryToPublicFile (publicDirectoryName, name, publicKey, secretKey, secretKeySize) char *publicDirectoryName; char name[]; word32 publicKey[MAX_PUBLIC_KEY_SIZE]; word32 secretKey[]; int secretKeySize; { int i; FILE *descriptor; int printReturnCode; int publicKeySize; char *publicFileName; /* open the "public file" entry for writing */ publicFileName = MakePublicFileName(publicDirectoryName, publicKey); if (FileExists(publicFileName) == 0) ErrAbort("Highly improbable collision occured -- try again."); descriptor = fopen (publicFileName, "w"); if (descriptor == NULL) ErrAbort ("Could not write entry to public file"); printReturnCode = fprintf (descriptor, "#%s: %08lx", name, publicKey[0]); if (printReturnCode <= 0) ErrAbort ("bad write to public file"); publicKeySize = ComputePublicKeySize(publicKey); for (i = 1; i < publicKeySize; i++) { printReturnCode = fprintf (descriptor, " %08lx", publicKey[i]); if (printReturnCode <= 0) ErrAbort ("bad public key write to public file"); }; printReturnCode = fprintf (descriptor, "\n"); if (printReturnCode <= 0) ErrAbort ("bad string write to public file"); FileClose (descriptor, publicFileName); }; /* The following routine is used to recover the auxilliary information * if it is damaged or lost. The auxilliary information can be derived * from the information kept in stable storage. */ void RecoverAuxInfo(userKeyString) char *userKeyString; { int top; int newStackTop; int i; int hashMethod; int securitySize; int hashValueSize; char charUserKey[USER_KEY_SIZE_IN_BYTES]; word32 userKey[USER_KEY_SIZE]; word32 secretKey[SECRET_KEY_SIZE]; word32 publicKey[MAX_PUBLIC_KEY_SIZE]; word32 hashedUserKey[HASHED_USER_KEY_SIZE]; word32 machineKey[MACHINE_KEY_SIZE]; word32 authPathValues [MAX_STACK_DEPTH] [2 * MAX_OTT_SIZE] [MAX_SECURITY_SIZE]; word32 countVerifiers [MAX_STACK_DEPTH] [MAX_NO_OF_COUNTS] [MAX_SECURITY_SIZE]; word32 path[MAX_STACK_DEPTH]; int noOfCounts; int noOfBitsPerVerifier; word32 rootParameter[MAX_PARAMETER_SIZE]; word32 root[MAX_SECURITY_SIZE]; int parameterSize; word32 tempHSC[HASHED_USER_KEY_SIZE]; char file1Name[MAX_FILE_NAME_SIZE_IN_BYTES]; char file2Name[MAX_FILE_NAME_SIZE_IN_BYTES]; FILE *file1; FILE *file2; int toggle; int OTTsize; strncpy(charUserKey, userKeyString, USER_KEY_SIZE_IN_BYTES); ConvertBytes (charUserKey, userKey, USER_KEY_SIZE_IN_BYTES); NameStableStorage(file1Name, file2Name); OpenAndLockTwoFiles(&file1, file1Name, &file2, file2Name); ReadStableStorage (file1, file2, machineKey, publicKey, hashedUserKey, path, &newStackTop, &toggle, &OTTsize); UnPackHeader (publicKey[0], &hashMethod, &securitySize, ¶meterSize, &noOfBitsPerVerifier, &hashValueSize, &noOfCounts); HashTwoItems(tempHSC, HASHED_USER_KEY_SIZE, userKey, USER_KEY_SIZE, userKey, USER_KEY_SIZE, hashMethod); for (i=0; i<20; i++) HashTwoItems( tempHSC, HASHED_USER_KEY_SIZE, tempHSC, HASHED_USER_KEY_SIZE, tempHSC, HASHED_USER_KEY_SIZE, hashMethod); for (i=0; i= 0; i--) if (FetchBits (rootParameter, parameterSize, i, 4) == 0) SetBit (rootParameter, i); for (i = WORD_SIZE_IN_BITS * parameterSize - 5; i >= 0; i--) if (FetchBits (rootParameter, parameterSize, i, 4) == 0) ErrAbort ("Internal logic error"); /* End of diddling the root parameter */ /* initialize the top (i.e. 0th) OTT */ Copy (OTTparameter[top], rootParameter, parameterSize); path[top] = OTTsize; FinishNewOTT (top, securitySize, parameterSize, hashMethod, noOfCounts, noOfBitsPerVerifier, OTTsize, authPathValues, OTTparameter[top], countVerifiers, secretKey, SECRET_KEY_SIZE); Copy (root, authPathValues[top][1], securitySize); newStackTop = ComputeNewStackTop (top, path); Copy (parameter, rootParameter, parameterSize); while (top < newStackTop) { path[top+1] = OTTsize; PushOTT (&top, securitySize, parameterSize, hashMethod, noOfCounts, noOfBitsPerVerifier, path, authPathValues, parameter, countVerifiers, secretKey, SECRET_KEY_SIZE, OTTsize); }; /* generate public key */ PackHeader(publicKey, hashMethod, securitySize, parameterSize, noOfBitsPerVerifier); Copy(&publicKey[1], root, securitySize); Copy(&publicKey[1+securitySize], rootParameter, parameterSize); HashTwoItems(hashedUserKey, HASHED_USER_KEY_SIZE, userKey, USER_KEY_SIZE, userKey, USER_KEY_SIZE, hashMethod); for (i=0; i<20; i++) HashTwoItems( hashedUserKey, HASHED_USER_KEY_SIZE, hashedUserKey, HASHED_USER_KEY_SIZE, hashedUserKey, HASHED_USER_KEY_SIZE, hashMethod); NameStableStorage(file1Name, file2Name); file1 = OpenIfEmpty(file1Name); file2 = OpenIfEmpty(file2Name); WriteStableStorage (file1, file2, machineKey, publicKey, hashedUserKey, path, top, 0, OTTsize); FileSyncAndClose(file1, file1Name); FileSyncAndClose(file2, file2Name); WriteBinaryAuxInfo(top, securitySize, noOfCounts, OTTsize, authPathValues, countVerifiers, 0); WriteEntryToPublicFile (publicDirName, userName, publicKey, secretKey, SECRET_KEY_SIZE); } /* * The following routine writes 0's over the old signing key, if * the file exists. */ void DestroyOldKey() { char file1Name[MAX_FILE_NAME_SIZE_IN_BYTES]; char file2Name[MAX_FILE_NAME_SIZE_IN_BYTES]; FILE *file1; FILE *file2; char nothing[WIPE_IT]; int i; int fileSize; for(i=0; i WIPE_IT) fileSize = WIPE_IT; fseek(file1, 0L, 0); if (fwrite(nothing, 1, fileSize, file1) != fileSize) fprintf(stderr, "Can't write to \"%s\"\n", file1Name); FileSyncAndClose(file1, file1Name); }; }; if (FileExists(file2Name) != 0) fprintf(stderr, "File \"%s\" does not exist\n", file2Name); else { file2 = fopen(file2Name, "r+"); if (file2 == (FILE *) NULL) fprintf(stderr, "Can't open \"%s\" for modify\n", file2Name); else { fseek(file2, 0L, 2); fileSize = ftell(file2); if (fileSize < 100) fileSize = 100; if (fileSize > WIPE_IT) fileSize = WIPE_IT; fseek(file2, 0L, 0); if (fwrite(nothing, 1, fileSize, file2) != fileSize) fprintf(stderr, "Can't write to \"%s\"\n", file2Name); FileSyncAndClose(file2, file2Name); }; }; } /* * given integer hashMethod, determine the proper string name * (printable name) for that hash method. */ char * NameForHashMethod(hashMethod) int hashMethod; { switch (hashMethod) { case SNEFRU4_METHOD: return("Snefru4"); case MD4_METHOD: return("MD4"); default: return("Unknown"); }; } /* * Given a string, s, select the proper integer value for the * hash method specified by the string. * Allowed hash methods are: SNEFRU4_METHOD and MD4_METHOD. * Other hashing methods can be added in the future as warranted. * Please contact Xerox before selecting a constant for * a new method -- the constant value specifying a given * method should be unique. Values less than 100 are reserved, * and should not be used except by Xerox. */ int SelectHashMethod(s) char *s; { if (strcmp(s,"md4" )==0) return(MD4_METHOD); else if (strcmp(s,"MD4" )==0) return(MD4_METHOD); else if (strcmp(s,"snefru4" )==0) return(SNEFRU4_METHOD); else if (strcmp(s,"snefru" )==0) return(SNEFRU4_METHOD); else if (strcmp(s,"SNEFRU4" )==0) return(SNEFRU4_METHOD); else if (strcmp(s,"SNEFRU" )==0) return(SNEFRU4_METHOD); else ErrAbort("Method must be md4 or snefru[4]"); return(-1); }; /* * The main program reads in a command line, interprets it, * opens the input and signature files (if necessary), and * then invokes the proper routine to sign/check/whatever. */ void main (argc, argv) int argc; char *argv[]; { int j; int arg; int start; int hashMethod; int securitySize; int parameterSize; int noOfBitsPerVerifier; int OTTsize; char sigFileName[MAX_FILE_NAME_SIZE_IN_BYTES]; int msgSize; char *message; char msgBuffer[MAX_MESSAGE_SIZE_IN_BYTES]; FILE *inputFile; FILE *sigFile; word32 publicKey[MAX_PUBLIC_KEY_SIZE]; char dateString[80]; char userName[MAX_USER_NAME_SIZE_IN_BYTES]; word32 dateTime32; int exitCode; long longTemp; int publicKeySize; int silentFlag = FALSE; int verboseFlag = FALSE; char *publicDirName; int errorCount; char commandString[MAX_FILE_NAME_SIZE_IN_BYTES]; GetFileSuffix(argv[0], commandString); if (strncmp(commandString,"destroyoldkey", 13)==0) { DestroyOldKey(); exit(0); } if (argc<2) { fprintf(stdout, "%s\n", VERSION); fprintf(stdout, "Copyright (C) 1990 Xerox Corporation.\n"); fprintf(stdout, "EXPERIMENTAL SOFTWARE\n"); fprintf(stdout, "See source code header for scope of\n"); fprintf(stdout, "license granted and for warranty disclaimers.\n"); fprintf(stderr, "Usage: sign [-m message | -] userKey [files]\n"); fprintf(stderr, " or: check [-dpublicDir] [-s] [-v] [files]\n"); fprintf(stderr, " or: makepublickey [options] userKey user-name\n"); fprintf(stderr, " or: dumpsig [files]\n"); fprintf(stderr, " or: destroyoldkey\n"); fprintf(stderr, " or: recoverauxinfo userKey\n"); ErrAbort(""); }; if (strncmp(commandString,"sign", 4)==0) { fprintf(stdout, "Copyright (C) 1990 Xerox Corporation.\n"); fprintf(stdout, "EXPERIMENTAL SOFTWARE\n"); fprintf(stdout, "See source code header for scope of\n"); fprintf(stdout, "license granted and for warranty disclaimers.\n"); if (argc <= 2) ErrAbort( "Usage: sign [-m message | -] userKey [files]"); start = 2; message = ""; if (strcmp(argv[1], "-m") == 0) { if (argc <= 3) ErrAbort("No -m message to sign"); message = argv[2]; start = 4; } else if (strcmp(argv[1], "-") == 0) { msgSize = fread(msgBuffer, 1, MAX_MESSAGE_SIZE_IN_BYTES, stdin); if (msgSize == MAX_MESSAGE_SIZE_IN_BYTES) ErrAbort("message from stdin too big"); if (ferror(stdin) != 0) { ErrAbort("Can't read from stdin"); }; message = msgBuffer; start = 3; } if (argc <= start) ErrAbort("No files to sign"); for(arg=start; arg