- Page 3 and 4: Secure Programming Cookbook ΤΜ fo
- Page 5 and 6: Secure Programming CookbookΤΜ for
- Page 7 and 8: Table of Contents Foreword . . . .
- Page 9 and 10: 5.8 Using a Generic OFB Mode Implem
- Page 11 and 12: 8.10 Performing Password-Based Auth
- Page 13: 12.10 Restructuring Arrays 672 12.1
- Page 17 and 18: Most of the security patches issued
- Page 19 and 20: This is the Title of the Book, eMat
- Page 21 and 22: This book doesn’t always force yo
- Page 23 and 24: Chapter 3, Input Validation, teache
- Page 25 and 26: Italic Is used for filenames, direc
- Page 27: John Viega: Thanks to Crispin Cowan
- Page 30 and 31: Typically, Unix systems are conside
- Page 32 and 33: variants we’re explicitly support
- Page 34 and 35: not be allocated, the function will
- Page 36 and 37: which the service is running. The t
- Page 38 and 39: RestrictedSidCount Number of elemen
- Page 40 and 41: * Create a restricted token with al
- Page 42 and 43: * state information. */ hProcess =
- Page 44 and 45: strings suitable for use here. Each
- Page 46 and 47: is unchanged so that the effective
- Page 48 and 49: } else { if (newgid != oldgid && ge
- Page 50 and 51: Normally, the two processes are clo
- Page 52 and 53: and avoiding having an attacker pla
- Page 54 and 55: 1.6 Creating a Child Process Secure
- Page 56 and 57: process is forked, the original pro
- Page 58 and 59: The two easiest and safest function
- Page 60 and 61: SPC_PIPE *spc_popen(const char *pat
- Page 62 and 63: Discussion The Win32 API provides s
- Page 64 and 65:
Unfortunately, there is no way to p
- Page 66 and 67:
Chapter CHAPTER 2 2 Access Control
- Page 68 and 69:
tory. It is common to see the stick
- Page 70 and 71:
modify its DACLcan result in denial
- Page 72 and 73:
opening a file that it shouldn’t,
- Page 74 and 75:
directory /home/myhome/stuff/secure
- Page 76 and 77:
actual data is stored is then marke
- Page 78 and 79:
} fsync(fd); return 0; } static int
- Page 80 and 81:
cbWrite = (dwFileSize > cbBuffer ?
- Page 82 and 83:
ate on the name of the file, especi
- Page 84 and 85:
efore creating the file or to use f
- Page 86 and 87:
Solution Two basic types of locks e
- Page 88 and 89:
BOOL UnlockFile(HANDLE hFile, DWORD
- Page 90 and 91:
int spc_lock_file(const char *lfpat
- Page 92 and 93:
on Unix, you must use a lock file a
- Page 94 and 95:
Temporary files on Unix The best so
- Page 96 and 97:
if (!MakeTempFilename(lpszBuffer, d
- Page 98 and 99:
Creating a jail is as simple as fil
- Page 100 and 101:
Solution Perform data validation at
- Page 102 and 103:
Beware of special commands, charact
- Page 104 and 105:
Solution Functions such as the prin
- Page 106 and 107:
Avoid using vsprintf( ) and sprintf
- Page 108 and 109:
the buffer is heap-allocated (that
- Page 110 and 111:
All of the string-handling improvem
- Page 112 and 113:
All of the compiler-based solutions
- Page 114 and 115:
86 | Chapter 3: Input Validation Ca
- Page 116 and 117:
In this example, if you omitted the
- Page 118 and 119:
There are a few ways to alleviate t
- Page 120 and 121:
that uses an unsigned 32-bit value
- Page 122 and 123:
value of an environment variable ac
- Page 124 and 125:
Especially if you use the code from
- Page 126 and 127:
The signature for realpath( ) is: c
- Page 128 and 129:
Discussion RFC 1738 defines the syn
- Page 130 and 131:
#include 102 | Chapter 3: Input Va
- Page 132 and 133:
cases, and it is the only solution
- Page 134 and 135:
if (!isalnum(*input)) return 0; bre
- Page 136 and 137:
Obviously, the best way to avoid SQ
- Page 138 and 139:
case '\b': *ptr++ = '\\'; *ptr++ =
- Page 140 and 141:
3.13 Preventing File Descriptor Ove
- Page 142 and 143:
int spc_fd_isset(int fd, SPC_FD_SET
- Page 144 and 145:
Chapter CHAPTER 4 4 Symmetric Crypt
- Page 146 and 147:
When you’re done using a key, you
- Page 148 and 149:
4.3 Representing Binary Keys (or Ot
- Page 150 and 151:
leading “0x” if it exists. The
- Page 152 and 153:
If the length of the input string i
- Page 154 and 155:
Note that we check to ensure string
- Page 156 and 157:
*len = 0; outbuf = 0; } return outb
- Page 158 and 159:
The previous code is subtly incompa
- Page 160 and 161:
if (cmp > 0) min = ix + 1; else if
- Page 162 and 163:
The use of salt means that the atta
- Page 164 and 165:
Another downside is that there is g
- Page 166 and 167:
for (j = 0; j < PRF_OUT_LEN; j++) o
- Page 168 and 169:
eturn TRUE; } static BOOL PKCS5Fina
- Page 170 and 171:
from a password. If you make key de
- Page 172 and 173:
Distinguisher Selection The basic i
- Page 174 and 175:
while (cbOut >= HMAC_OUT_LEN) { if
- Page 176 and 177:
long i; unsigned char c; for (i = 0
- Page 178 and 179:
4.14 Timing Cryptographic Primitive
- Page 180 and 181:
On Windows machines, you can read t
- Page 182 and 183:
time by subtracting the start from
- Page 184 and 185:
if an algorithm turns out to be ser
- Page 186 and 187:
tively new breed of block cipher th
- Page 188 and 189:
Clearly, Triple-DES isn’t fast in
- Page 190 and 191:
Triple-DES does. When it was the de
- Page 192 and 193:
Electronic Code Book (ECB) mode Thi
- Page 194 and 195:
The primary disadvantages of CTR mo
- Page 196 and 197:
The primary advantages of CFB mode
- Page 198 and 199:
Because of its patent status and th
- Page 200 and 201:
a key, which may be of a different
- Page 202 and 203:
Table 5-3. Implementations for the
- Page 204 and 205:
implementation. If you need code th
- Page 206 and 207:
These two functions erase the key f
- Page 208 and 209:
You’re responsible for making sur
- Page 210 and 211:
*/ if (ctx->ix) { while (ctx->ix <
- Page 212 and 213:
unsigned char *next_iv, *start = ou
- Page 214 and 215:
*out++ = ctx->ctbuf[i]; if (ol) *ol
- Page 216 and 217:
This implementation is only for the
- Page 218 and 219:
The function spc_cfb_encrypt_update
- Page 220 and 221:
*out++ = *in++ ^ ctx->nonce[ctx->ix
- Page 222 and 223:
Both of these functions output the
- Page 224 and 225:
we ignore that because it will alwa
- Page 226 and 227:
encrypting a plaintext block that i
- Page 228 and 229:
void spc_ctr_init(SPC_CTR_CTX *ctx,
- Page 230 and 231:
int spc_ctr_final(SPC_CTR_CTX *ctx)
- Page 232 and 233:
a Buffer containing optional data t
- Page 234 and 235:
most important requirement is that
- Page 236 and 237:
out += SPC_BLOCK_SZ; } SPC_DO_ENCRY
- Page 238 and 239:
Because we assume the message is av
- Page 240 and 241:
5.14 Parallelizing Encryption and D
- Page 242 and 243:
gle time you want to protect a modi
- Page 244 and 245:
To switch hash functions, replace t
- Page 246 and 247:
SPC_CIPHERQ objects are initialized
- Page 248 and 249:
The functions spc_cipherq_encrypt(
- Page 250 and 251:
senting that mode. Note that OpenSS
- Page 252 and 253:
Table 5-6. Cipher instantiation ref
- Page 254 and 255:
This example selects a key and init
- Page 256 and 257:
Discussion Particularly when you ar
- Page 258 and 259:
To get the length of the initializa
- Page 260 and 261:
In CBC and ECB modes, the cipher ca
- Page 262 and 263:
Solution You can’t be very confid
- Page 264 and 265:
Without going into the technical de
- Page 266 and 267:
Solution Microsoft’s CryptoAPI is
- Page 268 and 269:
HCRYPTKEY SpcGetDerivedKey(HCRYPTPR
- Page 270 and 271:
encryption and decryption can be do
- Page 272 and 273:
Finally, when you’re finished usi
- Page 274 and 275:
cbData = cbKeyData; cbHeaderLen = s
- Page 276 and 277:
HCRYPTKEY hExpKey = 0; if (!CryptGe
- Page 278 and 279:
Solution See the “Discussion” s
- Page 280 and 281:
The basic idea is to take two model
- Page 282 and 283:
One disadvantage of turning a block
- Page 284 and 285:
assume that you always need it, the
- Page 286 and 287:
SHA-256, SHA-384, SHA-512 After the
- Page 288 and 289:
Table 6-2. MACs and their propertie
- Page 290 and 291:
easily last years. The 128-bit vers
- Page 292 and 293:
eturn 0; } Every hash function that
- Page 294 and 295:
EVP_DigestFinal(&ctx, result, &ol);
- Page 296 and 297:
#include #include #include /* Re
- Page 298 and 299:
For example, here’s a wrapper for
- Page 300 and 301:
Here’s a pair of functions that d
- Page 302 and 303:
6.9 Checking Message Integrity Prob
- Page 304 and 305:
unsigned char *HMAC(const EVP_MD *e
- Page 306 and 307:
to key data. In general, CALG_RC4 s
- Page 308 and 309:
void DGST_Update(DGST_CTX *ctx, uns
- Page 310 and 311:
int spc_omac1_init(SPC_OMAC_CTX *ct
- Page 312 and 313:
if (condition) ctx->c1[SPC_BLOCK_SZ
- Page 314 and 315:
If you’re using an off-the-shelf
- Page 316 and 317:
Discussion Be sure to look at our g
- Page 318 and 319:
typedef struct { struct hash127 hct
- Page 320 and 321:
Figure 6-1. The Davies-Meyer constr
- Page 322 and 323:
See Also Recipes 5.5, 6.3, 6.7, 6.1
- Page 324 and 325:
Our API allows for incremental proc
- Page 326 and 327:
spc_mdc2_oneblock(c, c->bf); memcpy
- Page 328 and 329:
Figure 6-5. The authenticate-then-e
- Page 330 and 331:
Using a MAC in CTR mode is easy. As
- Page 332 and 333:
spc_omac1_init(&c, k, 16); if (*las
- Page 334 and 335:
spc_omac_update(ctx1, text, INTERLE
- Page 336 and 337:
Establishing identity A third use o
- Page 338 and 339:
obtained the public key of the serv
- Page 340 and 341:
Elliptic curve cryptography can pro
- Page 342 and 343:
In the public key world, the future
- Page 344 and 345:
BIGNUM bn; void BN_init(&bn); If yo
- Page 346 and 347:
This function has the following arg
- Page 348 and 349:
*outsz = BN_num_bytes(b); if (BN_is
- Page 350 and 351:
Table 7-2. Math operations supporte
- Page 352 and 353:
andomness in depth in Recipe 11.1).
- Page 354 and 355:
static int passes_rabin_miller_once
- Page 356 and 357:
RSA. For example, public key certif
- Page 358 and 359:
Solution Remove all elements of the
- Page 360 and 361:
little-endian machine and word-size
- Page 362 and 363:
With EME-OAEP padding, the message
- Page 364 and 365:
If you are using a predefined paddi
- Page 366 and 367:
7.12 Signing Data Using an RSA Priv
- Page 368 and 369:
dgst Buffer containing the digest t
- Page 370 and 371:
This function has the following arg
- Page 372 and 373:
solution to this problem is to conc
- Page 374 and 375:
free(out); out = 0; goto err; } byt
- Page 376 and 377:
tion during key agreement in Recipe
- Page 378 and 379:
kinvp Pointer to a BIGNUM object, w
- Page 380 and 381:
See Also • NIST web site: http://
- Page 382 and 383:
if (!(buf = next = (unsigned char *
- Page 384 and 385:
Solution The PEM format represents
- Page 386 and 387:
Table 7-5. PEM encryption algorithm
- Page 388 and 389:
cb_arg If a callback function is sp
- Page 390 and 391:
Chapter CHAPTER 8 8 Authentication
- Page 392 and 393:
Clearly, choosing the right technol
- Page 394 and 395:
so why bother building a secure cha
- Page 396 and 397:
In addition, the user will need to
- Page 398 and 399:
mount an offline dictionary attack
- Page 400 and 401:
cant window of time between when a
- Page 402 and 403:
In our example code, we call endpwe
- Page 404 and 405:
Discussion The Win32 API function L
- Page 406 and 407:
Sid Buffer containing the SID to lo
- Page 408 and 409:
The first step in restricting acces
- Page 410 and 411:
Access restriction information read
- Page 412 and 413:
if (!(f = fopen(filename, "r"))) re
- Page 414 and 415:
This function has the following arg
- Page 416 and 417:
char *spc_generate_password(char *b
- Page 418 and 419:
spc_wordlist_file = f; spc_wordlist
- Page 420 and 421:
from choosing insecure passwords. I
- Page 422 and 423:
ufsiz Size of the buffer (in bytes)
- Page 424 and 425:
et[ctr] = ch; if (ctr && !(ctr & BU
- Page 426 and 427:
8.8 Throttling Failed Authenticatio
- Page 428 and 429:
for (exp = *attempts - allowed_fail
- Page 430 and 431:
salt[0] = choices[spc_rand_range(0,
- Page 432 and 433:
#include #include #include #incl
- Page 434 and 435:
#include #include static LPSTR Cr
- Page 436 and 437:
} lpszBase64Salt[dwSaltLength] = 0;
- Page 438 and 439:
char *spc_pbkdf2_encrypt(const char
- Page 440 and 441:
Most modern Unix systems provide su
- Page 442 and 443:
After the authentication is success
- Page 444 and 445:
version Version string that is sent
- Page 446 and 447:
free_context = 1; } if ((rc = krb5_
- Page 448 and 449:
Initialization requires a key to us
- Page 450 and 451:
8.15 Performing Password-Based Auth
- Page 452 and 453:
salt_len Length of the salt that wi
- Page 454 and 455:
This function returns a pointer to
- Page 456 and 457:
This call will throw an XXLexceptio
- Page 458 and 459:
In such a scenario, the sender has
- Page 460 and 461:
8.17 Using Basic Diffie-Hellman Key
- Page 462 and 463:
err Pointer to an integer to which
- Page 464 and 465:
BN_CTX *tmp_ctx; if (!(secret = BN_
- Page 466 and 467:
See Also In many circumstances, the
- Page 468 and 469:
#include #include #include #incl
- Page 470 and 471:
if (!(bio = BIO_new(BIO_s_mem( ))))
- Page 472 and 473:
eturn result; } int spc_accept_key(
- Page 474 and 475:
dom numbers each time. Throw away a
- Page 476 and 477:
Discussion The most common use for
- Page 478 and 479:
* Confirmation receipts must be rec
- Page 480 and 481:
typedef struct { LPTSTR lpszAddress
- Page 482 and 483:
Chapter CHAPTER 9 9 Networking Toda
- Page 484 and 485:
SSL_CTX_set_app_data(*ctx, spc_stor
- Page 486 and 487:
servers. In particular, you need to
- Page 488 and 489:
if (our_ctx) *ctx = 0; return 0; }
- Page 490 and 491:
that demonstrates the use of this f
- Page 492 and 493:
Solution The Microsoft WinInet API
- Page 494 and 495:
server name, starting with the forw
- Page 496 and 497:
new request object can be created a
- Page 498 and 499:
Client mode To enable client mode,
- Page 500 and 501:
while the two implementations share
- Page 502 and 503:
} krb5_data_free(&edata); } free(tm
- Page 504 and 505:
Solution Modern operating systems s
- Page 506 and 507:
#include #include #include #ifnd
- Page 508 and 509:
int opt = 1; spc_socket_t *sock = 0
- Page 510 and 511:
int spc_socket_recv(spc_socket_t *s
- Page 512 and 513:
#ifdef LOCAL_CREDS nb = 1; if (sets
- Page 514 and 515:
9.9 Performing Session ID Managemen
- Page 516 and 517:
MySQL By default, SSLsupport is dis
- Page 518 and 519:
In a default configuration, Postgre
- Page 520 and 521:
negotiate which version of the prot
- Page 522 and 523:
we might have the client and server
- Page 524 and 525:
static unsigned char spc_msg_ok = 0
- Page 526 and 527:
* If it's the client's turn to spea
- Page 528 and 529:
Instead, you should use this code f
- Page 530 and 531:
Chapter CHAPTER 10 10 Public Key In
- Page 532 and 533:
Certification Authority Figure 10-1
- Page 534 and 535:
Private CAs Usually, private CAs ar
- Page 536 and 537:
they learn about key compromises. I
- Page 538 and 539:
An indirect CRLis one that is not n
- Page 540 and 541:
There is no way to digitally verify
- Page 542 and 543:
CAs, both public and private. For t
- Page 544 and 545:
people are going to trust it, so th
- Page 546 and 547:
cate. We recommend that you stick w
- Page 548 and 549:
had three different valid root cert
- Page 550 and 551:
Table 10-1. CA certificates, their
- Page 552 and 553:
are updated periodically and should
- Page 554 and 555:
We begin our solution by defining t
- Page 556 and 557:
void spc_x509store_addcrl(spc_x509s
- Page 558 and 559:
validity of the certificate. If the
- Page 560 and 561:
for the types of verification check
- Page 562 and 563:
The SpcNewStoreForCert( ) function
- Page 564 and 565:
ent mode, you should always be veri
- Page 566 and 567:
take precedence over a preexisting
- Page 568 and 569:
sents a certificate, the commonly a
- Page 570 and 571:
LocalFree(wstr); return 0; } return
- Page 572 and 573:
IN DWORD dwType, IN DWORD dwFlags,
- Page 574 and 575:
You can use a whitelist in place of
- Page 576 and 577:
the CRLthat corresponds to the cert
- Page 578 and 579:
"https://www.trustcenter.de:443/cgi
- Page 580 and 581:
Once we have the URLof the CRLwe wa
- Page 582 and 583:
end_error: if (data) { free(data);
- Page 584 and 585:
10.11 Obtaining CRLs with CryptoAPI
- Page 586 and 587:
The worker function GetDistribution
- Page 588 and 589:
if (pIssuer) { if (!(pCACert = SpcL
- Page 590 and 591:
See Also • RFC 3280: Internet X.5
- Page 592 and 593:
sign_key If the sign_cert member is
- Page 594 and 595:
goto end; } if (!(req = OCSP_REQUES
- Page 596 and 597:
Chapter CHAPTER 11 11 Random Number
- Page 598 and 599:
Note that cryptographic pseudo-rand
- Page 600 and 601:
Finally, you need to realize that e
- Page 602 and 603:
• Get entropy if it is available,
- Page 604 and 605:
don’t think you should worry abou
- Page 606 and 607:
spc_rand( ) by reading from /dev/ur
- Page 608 and 609:
11.4 Using the Standard Windows Ran
- Page 610 and 611:
Discussion Stream ciphers are actua
- Page 612 and 613:
The seed should be at least as larg
- Page 614 and 615:
while (xl-- && i < SPC_BLOCK_SZ) pr
- Page 616 and 617:
#define SPC_RC4RNG_UNLOCK( ) Releas
- Page 618 and 619:
SPC_MPRNG_UNLOCK( ); } unsigned cha
- Page 620 and 621:
Discussion There are two common rea
- Page 622 and 623:
11.7 Using an Entropy Gathering Dae
- Page 624 and 625:
process. While EGADS implements the
- Page 626 and 627:
} return buf; } unsigned char *spc_
- Page 628 and 629:
on the strength of the AES cryptogr
- Page 630 and 631:
The egads_randlong( ) function gets
- Page 632 and 633:
anyway. You can use any of the sour
- Page 634 and 635:
Discussion Do not use this solution
- Page 636 and 637:
You might worry about a situation w
- Page 638 and 639:
In all cases, we start with a numbe
- Page 640 and 641:
char *p = buf; while (--len) *p++ =
- Page 642 and 643:
Most simpler compression methods ar
- Page 644 and 645:
Discussion FIPS 140 tests are usefu
- Page 646 and 647:
#define FIPS_POKER_HIBOUND 57.4 int
- Page 648 and 649:
The second output is also saved and
- Page 650 and 651:
about? For example, it is possible
- Page 652 and 653:
Particularly when an attacker may h
- Page 654 and 655:
However, in practice, even that num
- Page 656 and 657:
However, if you have a more liberal
- Page 658 and 659:
11.20 Gathering Entropy from the Ke
- Page 660 and 661:
#include #include #include #incl
- Page 662 and 663:
Collecting entropy from the keyboar
- Page 664 and 665:
*pHwnd = 0; return TRUE; } BOOL Spc
- Page 666 and 667:
11.21 Gathering Entropy from Mouse
- Page 668 and 669:
creates a modal dialog. A modal dia
- Page 670 and 671:
if (cbOutput > cbHashData) goto don
- Page 672 and 673:
On Windows, the idea is the same, a
- Page 674 and 675:
Often, these commands will need to
- Page 676 and 677:
12.1 Understanding the Problem of S
- Page 678 and 679:
The threat of protection crackers S
- Page 680 and 681:
When planning to implement a protec
- Page 682 and 683:
Solution Detecting whether portions
- Page 684 and 685:
*/ printf("CRC is %08X\n", crc); #e
- Page 686 and 687:
When this program is compiled with
- Page 688 and 689:
Table 12-1. Intel conditional branc
- Page 690 and 691:
Comparing the assembly code generat
- Page 692 and 693:
if (!s.write) s.write = SPC_RESOLVE
- Page 694 and 695:
* typedefs for clarity */ typedef u
- Page 696 and 697:
Solution Merging multiple scalar va
- Page 698 and 699:
init_rand(a, b); return 0; } 12.8 D
- Page 700 and 701:
int main(int argc, char *argv[ ]) {
- Page 702 and 703:
} /* Create an array folded 'layers
- Page 704 and 705:
Figure 12-1. Memory representation
- Page 706 and 707:
} putchar('\n'); for (i = 0; i < a_
- Page 708 and 709:
strncat( buf, "mp", buf_len - strle
- Page 710 and 711:
int main(int argc, char *argv[ ]) {
- Page 712 and 713:
#include #include #include #incl
- Page 714 and 715:
ecause of its power. Four well-know
- Page 716 and 717:
• “Anti-Debugging Tricks” by
- Page 718 and 719:
Most disassemblers can be fooled by
- Page 720 and 721:
function at the top of the stack. T
- Page 722 and 723:
Discussion For the code presented i
- Page 724 and 725:
phsz = *(unsigned short *) &buf[ELF
- Page 726 and 727:
int bogus_routine(void) { int x, y;
- Page 728 and 729:
Chapter CHAPTER 13 13 Other Topics
- Page 730 and 731:
Exception handling essentially acts
- Page 732 and 733:
If you prefer to give programmers t
- Page 734 and 735:
ANSI/ISO 9899-1990 standard, which
- Page 736 and 737:
All modern operating systems have v
- Page 738 and 739:
} while (0) #define VARARG_CALL_1(f
- Page 740 and 741:
solution can likely be ported to so
- Page 742 and 743:
We strongly recommend against perfo
- Page 744 and 745:
The function signal_was_caught( ) i
- Page 746 and 747:
See Also • Microsoft Security Bul
- Page 748 and 749:
typedef struct _spc_threadpool_task
- Page 750 and 751:
#ifndef WIN32 static void *worker_t
- Page 752 and 753:
13.8 Guarding Against Creating Too
- Page 754 and 755:
#endif return 1; } int spc_socketpo
- Page 756 and 757:
limits are usually set much higher
- Page 758 and 759:
#endif -1 }; void spc_rsrclimit(int
- Page 760 and 761:
cause any process exceeding any of
- Page 762 and 763:
If the assignment is successful, th
- Page 764 and 765:
tion with the logging service. When
- Page 767 and 768:
Symbols = (equals), base64 padding
- Page 769 and 770:
BIO_write( ), 457 birthday attacks,
- Page 771 and 772:
CryptGetUserKey( ), 245, 247 CryptH
- Page 773 and 774:
EGD (Entropy Gathering Daemon), 594
- Page 775 and 776:
GetFileInformationByHandle( ), 54 G
- Page 777 and 778:
L length extension attacks, 252 pre
- Page 779 and 780:
OCSPSigning bit, 562 OFB (Output Fe
- Page 781 and 782:
preventing cross-site scripting, 10
- Page 783 and 784:
oot certificates, 519-522 CAs, list
- Page 785 and 786:
obfuscating code, 658-664 assembly-
- Page 787 and 788:
spc_send_credentials( ), 483 SpcSet
- Page 789 and 790:
environment variables, dependencies
- Page 791 and 792:
About the Authors John Viega is a w