135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker/* rsa_e_f4.c 235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** 335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** Copyright 2012, The Android Open Source Project 435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** 535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** Redistribution and use in source and binary forms, with or without 635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** modification, are permitted provided that the following conditions are met: 735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** * Redistributions of source code must retain the above copyright 835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** notice, this list of conditions and the following disclaimer. 935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** * Redistributions in binary form must reproduce the above copyright 1035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** notice, this list of conditions and the following disclaimer in the 1135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** documentation and/or other materials provided with the distribution. 1235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** * Neither the name of Google Inc. nor the names of its contributors may 1335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** be used to endorse or promote products derived from this software 1435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** without specific prior written permission. 1535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** 1635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR 1735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 1835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 1935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 2035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 2135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 2235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 2335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 2435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 2535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker*/ 2735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 2835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker#include "mincrypt/rsa.h" 2935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker#include "mincrypt/sha.h" 3035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 3135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// a[] -= mod 3235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongkerstatic void subM(const RSAPublicKey* key, 3335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint32_t* a) { 3435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker int64_t A = 0; 3535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker int i; 3635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker for (i = 0; i < key->len; ++i) { 3735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker A += (uint64_t)a[i] - key->n[i]; 3835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker a[i] = (uint32_t)A; 3935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker A >>= 32; 4035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 4135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker} 4235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 4335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// return a[] >= mod 4435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongkerstatic int geM(const RSAPublicKey* key, 4535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker const uint32_t* a) { 4635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker int i; 4735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker for (i = key->len; i;) { 4835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker --i; 4935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker if (a[i] < key->n[i]) return 0; 5035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker if (a[i] > key->n[i]) return 1; 5135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 5235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker return 1; // equal 5335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker} 5435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 5535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// montgomery c[] += a * b[] / R % mod 5635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongkerstatic void montMulAdd(const RSAPublicKey* key, 5735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint32_t* c, 5835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker const uint32_t a, 5935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker const uint32_t* b) { 6035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint64_t A = (uint64_t)a * b[0] + c[0]; 6135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint32_t d0 = (uint32_t)A * key->n0inv; 6235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint64_t B = (uint64_t)d0 * key->n[0] + (uint32_t)A; 6335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker int i; 6435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 6535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker for (i = 1; i < key->len; ++i) { 6635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker A = (A >> 32) + (uint64_t)a * b[i] + c[i]; 6735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker B = (B >> 32) + (uint64_t)d0 * key->n[i] + (uint32_t)A; 6835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker c[i - 1] = (uint32_t)B; 6935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 7035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 7135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker A = (A >> 32) + (B >> 32); 7235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 7335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker c[i - 1] = (uint32_t)A; 7435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 7535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker if (A >> 32) { 7635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker subM(key, c); 7735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 7835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker} 7935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 8035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// montgomery c[] = a[] * b[] / R % mod 8135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongkerstatic void montMul(const RSAPublicKey* key, 8235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint32_t* c, 8335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker const uint32_t* a, 8435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker const uint32_t* b) { 8535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker int i; 8635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker for (i = 0; i < key->len; ++i) { 8735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker c[i] = 0; 8835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 8935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker for (i = 0; i < key->len; ++i) { 9035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker montMulAdd(key, c, a[i], b); 9135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 9235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker} 9335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 9435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// In-place public exponentiation. 9535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// Input and output big-endian byte array in inout. 9635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongkerstatic void modpowF4(const RSAPublicKey* key, 9735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint8_t* inout) { 9835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint32_t a[RSANUMWORDS]; 9935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint32_t aR[RSANUMWORDS]; 10035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint32_t aaR[RSANUMWORDS]; 10135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint32_t* aaa = aaR; // Re-use location. 10235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker int i; 10335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 10435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker // Convert from big endian byte array to little endian word array. 10535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker for (i = 0; i < key->len; ++i) { 10635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint32_t tmp = 10735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker (inout[((key->len - 1 - i) * 4) + 0] << 24) | 10835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker (inout[((key->len - 1 - i) * 4) + 1] << 16) | 10935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker (inout[((key->len - 1 - i) * 4) + 2] << 8) | 11035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker (inout[((key->len - 1 - i) * 4) + 3] << 0); 11135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker a[i] = tmp; 11235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 11335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 11435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker montMul(key, aR, a, key->rr); // aR = a * RR / R mod M 11535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker for (i = 0; i < 16; i += 2) { 11635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker montMul(key, aaR, aR, aR); // aaR = aR * aR / R mod M 11735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker montMul(key, aR, aaR, aaR); // aR = aaR * aaR / R mod M 11835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 11935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker montMul(key, aaa, aR, a); // aaa = aR * a / R mod M 12035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 12135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker // Make sure aaa < mod; aaa is at most 1x mod too large. 12235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker if (geM(key, aaa)) { 12335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker subM(key, aaa); 12435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 12535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 12635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker // Convert to bigendian byte array 12735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker for (i = key->len - 1; i >= 0; --i) { 12835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint32_t tmp = aaa[i]; 12935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker *inout++ = tmp >> 24; 13035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker *inout++ = tmp >> 16; 13135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker *inout++ = tmp >> 8; 13235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker *inout++ = tmp >> 0; 13335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 13435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker} 13535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 13635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// Expected PKCS1.5 signature padding bytes, for a keytool RSA signature. 13735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// Has the 0-length optional parameter encoded in the ASN1 (as opposed to the 13835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// other flavor which omits the optional parameter entirely). This code does not 13935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// accept signatures without the optional parameter. 14035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker/* 14135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongkerstatic const uint8_t padding[RSANUMBYTES] = { 14235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker0x00,0x01,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x30,0x21,0x30,0x09,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x05,0x00,0x04,0x14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 14335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker}; 14435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker*/ 14535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 14635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// SHA-1 of PKCS1.5 signature padding for 2048 bit, as above. 14735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// At the location of the bytes of the hash all 00 are hashed. 14835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongkerstatic const uint8_t kExpectedPadShaRsa2048[SHA_DIGEST_SIZE] = { 14935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 0xdc, 0xbd, 0xbe, 0x42, 0xd5, 0xf5, 0xa7, 0x2e, 0x6e, 0xfc, 15035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 0xf5, 0x5d, 0xaf, 0x9d, 0xea, 0x68, 0x7c, 0xfb, 0xf1, 0x67 15135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker}; 15235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 15335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// Verify a 2048 bit RSA e=65537 PKCS1.5 signature against an expected 15435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker// SHA-1 hash. Returns 0 on failure, 1 on success. 15535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongkerint RSA_e_f4_verify(const RSAPublicKey* key, 15635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker const uint8_t* signature, 15735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker const int len, 15835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker const uint8_t* sha) { 15935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker uint8_t buf[RSANUMBYTES]; 16035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker int i; 16135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 16235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker if (key->len != RSANUMWORDS) { 16335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker return 0; // Wrong key passed in. 16435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 16535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 16635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker if (len != sizeof(buf)) { 16735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker return 0; // Wrong input length. 16835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 16935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 17035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker if (key->exponent != 65537) { 17135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker return 0; // Wrong exponent. 17235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 17335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 17435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker for (i = 0; i < len; ++i) { // Copy input to local workspace. 17535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker buf[i] = signature[i]; 17635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 17735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 17835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker modpowF4(key, buf); // In-place exponentiation. 17935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 18035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker // Xor sha portion, so it all becomes 00 iff equal. 18135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker for (i = len - SHA_DIGEST_SIZE; i < len; ++i) { 18235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker buf[i] ^= *sha++; 18335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 18435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 18535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker // Hash resulting buf, in-place. 18635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker SHA(buf, len, buf); 18735d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 18835d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker // Compare against expected hash value. 18935d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker for (i = 0; i < SHA_DIGEST_SIZE; ++i) { 19035d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker if (buf[i] != kExpectedPadShaRsa2048[i]) { 19135d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker return 0; 19235d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 19335d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker } 19435d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker 19535d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker return 1; // All checked out OK. 19635d9ad5ae72de846967b91aed97060f0e8558661Doug Zongker} 197