diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /appl/lib/crypt | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'appl/lib/crypt')
| -rw-r--r-- | appl/lib/crypt/mkfile | 24 | ||||
| -rw-r--r-- | appl/lib/crypt/pkcs.b | 572 | ||||
| -rw-r--r-- | appl/lib/crypt/ssl3.b | 5557 | ||||
| -rw-r--r-- | appl/lib/crypt/sslsession.b | 176 | ||||
| -rw-r--r-- | appl/lib/crypt/x509.b | 4125 |
5 files changed, 10454 insertions, 0 deletions
diff --git a/appl/lib/crypt/mkfile b/appl/lib/crypt/mkfile new file mode 100644 index 00000000..c97dab08 --- /dev/null +++ b/appl/lib/crypt/mkfile @@ -0,0 +1,24 @@ +<../../../mkconfig + +TARG=\ + pkcs.dis\ + ssl3.dis\ + sslsession.dis\ + x509.dis\ + +MODULES= + +SYSMODULES= \ + asn1.m\ + daytime.m\ + keyring.m\ + pkcs.m\ + security.m\ + ssl3.m\ + sslsession.m\ + sys.m\ + x509.m\ + +DISBIN=$ROOT/dis/lib/crypt + +<$ROOT/mkfiles/mkdis diff --git a/appl/lib/crypt/pkcs.b b/appl/lib/crypt/pkcs.b new file mode 100644 index 00000000..e74391ca --- /dev/null +++ b/appl/lib/crypt/pkcs.b @@ -0,0 +1,572 @@ +implement PKCS; + +include "sys.m"; + sys : Sys; + +include "keyring.m"; + keyring : Keyring; + IPint : import keyring; + DESstate : import keyring; + +include "security.m"; + random : Random; + +include "asn1.m"; + asn1 : ASN1; + Elem, Oid : import asn1; + +include "pkcs.m"; + +# pkcs object identifiers + +objIdTab = array [] of { + id_pkcs => Oid(array [] of {1,2,840,113549,1}), + id_pkcs_1 => Oid(array [] of {1,2,840,113549,1,1}), + id_pkcs_rsaEncryption => Oid(array [] of {1,2,840,113549,1,1,1}), + id_pkcs_md2WithRSAEncryption => Oid(array [] of {1,2,840,113549,1,1,2}), + id_pkcs_md4WithRSAEncryption => Oid(array [] of {1,2,840,113549,1,1,3}), + id_pkcs_md5WithRSAEncryption => Oid(array [] of {1,2,840,113549,1,1,4}), + + id_pkcs_3 => Oid(array [] of {1,2,840,113549,1,3}), + id_pkcs_dhKeyAgreement => Oid(array [] of {1,2,840,113549,1,3,1}), + + id_pkcs_5 => Oid(array [] of {1,2,840,113549,1,5}), + id_pkcs_pbeWithMD2AndDESCBC => Oid(array [] of {1,2,840,113549,1,5,1}), + id_pkcs_pbeWithMD5AndDESCBC => Oid(array [] of {1,2,840,113549,1,5,3}), + + id_pkcs_7 => Oid(array [] of {1,2,840,113549,1,7}), + id_pkcs_data => Oid(array [] of {1,2,840,113549,1,7,1}), + id_pkcs_singnedData => Oid(array [] of {1,2,840,113549,1,7,2}), + id_pkcs_envelopedData => Oid(array [] of {1,2,840,113549,1,7,3}), + id_pkcs_signedAndEnvelopedData => + Oid(array [] of {1,2,840,113549,1,7,4}), + id_pkcs_digestData => Oid(array [] of {1,2,840,113549,1,7,5}), + id_pkcs_encryptedData => Oid(array [] of {1,2,840,113549,1,7,6}), + + id_pkcs_9 => Oid(array [] of {1,2,840,113549,1,9}), + id_pkcs_emailAddress => Oid(array [] of {1,2,840,113549,1,9,1}), + id_pkcs_unstructuredName => Oid(array [] of {1,2,840,113549,1,9,2}), + id_pkcs_contentType => Oid(array [] of {1,2,840,113549,1,9,3}), + id_pkcs_messageDigest => Oid(array [] of {1,2,840,113549,1,9,4}), + id_pkcs_signingTime => Oid(array [] of {1,2,840,113549,1,9,5}), + id_pkcs_countersignature => Oid(array [] of {1,2,840,113549,1,9,6}), + id_pkcs_challengePassword => Oid(array [] of {1,2,840,113549,1,9,7}), + id_pkcs_unstructuredAddress => Oid(array [] of {1,2,840,113549,1,9,8}), + id_pkcs_extCertAttrs => Oid(array [] of {1,2,840,113549,1,9,9}), + id_algorithm_shaWithDSS => Oid(array [] of {1,3,14,3,2,13}) +}; + +# [public] +# initialize PKCS module + +init(): string +{ + sys = load Sys Sys->PATH; + if(sys == nil) + return "load sys module failed"; + + keyring = load Keyring Keyring->PATH; + if(keyring == nil) + return "load keyring module failed"; + + random = load Random Random->PATH; + if(random == nil) + return "load random module failed"; + + asn1 = load ASN1 ASN1->PATH; + if(asn1 == nil) + return "load asn1 module failed"; + asn1->init(); + + return ""; +} + +# [public] +# Encrypt data according to PKCS#1, with given blocktype, using given key. + +rsa_encrypt(data: array of byte, key: ref RSAKey, blocktype: int) + : (string, array of byte) +{ + if(key == nil) + return ("null pkcs#1 key", nil); + k := key.modlen; + dlen := len data; + if(k < 12 || dlen > k-11) + return ("bad parameters for pkcs#1", nil); + padlen := k-3-dlen; + pad := random->randombuf(Random->NotQuiteRandom, padlen); + for(i:=0; i < padlen; i++) { + if(blocktype == 0) + pad[i] = byte 0; + else if(blocktype == 1) + pad[i] = byte 16rff; + else if(pad[i] == byte 0) + pad[i] = byte 1; + } + eb := array[k] of byte; + eb[0] = byte 0; + eb[1] = byte blocktype; + eb[2:] = pad[0:]; + eb[padlen+2] = byte 0; + eb[padlen+3:] = data[0:]; + return ("", rsacomp(eb, key)); +} + +# [public] +# Decrypt data according to PKCS#1, with given key. +# If public is true, expect a block type of 0 or 1, else 2. + +rsa_decrypt(data: array of byte, key: ref RSAKey, public: int) + : (string, array of byte) +{ + eb := rsacomp(data, key); + k := key.modlen; + if(len eb == k) { + bt := int eb[1]; + if(int eb[0] == 0 && ((public && (bt == 0 || bt == 1)) || (!public && bt == 2))) { + for(i := 2; i < k; i++) + if(eb[i] == byte 0) + break; + if(i < k-1) { + ans := array[k-(i+1)] of byte; + ans[0:] = eb[i+1:]; + return ("", ans); + } + } + } + return ("pkcs1 decryption error", nil); +} + +# [private] +# Do RSA computation on block according to key, and pad +# result on left with zeros to make it key.modlen long. + +rsacomp(block: array of byte, key: ref RSAKey): array of byte +{ + x := keyring->IPint.bebytestoip(block); + y := x.expmod(key.exponent, key.modulus); + ybytes := y.iptobebytes(); + k := key.modlen; + ylen := len ybytes; + if(ylen < k) { + a := array[k] of { * => byte 0}; + a[k-ylen:] = ybytes[0:]; + ybytes = a; + } + else if(ylen > k) { + # assume it has leading zeros (mod should make it so) + a := array[k] of byte; + a[0:] = ybytes[ylen-k:]; + ybytes = a; + } + return ybytes; +} + +# [public] + +rsa_sign(data: array of byte, sk: ref RSAKey, algid: int): (string, array of byte) +{ + # digesting and add proper padding to it + ph := padhash(data, algid); + + return rsa_encrypt(ph, sk, 0); # blocktype <- padding with zero +} + +# [public] + +rsa_verify(data, signature: array of byte, pk: ref RSAKey, algid: int): int +{ + # digesting and add proper padding to it + ph := padhash(data, algid); + + (err, orig) := rsa_decrypt(signature, pk, 0); # blocktype ? + if(err != "" || !byte_cmp(orig, ph)) + return 0; + + return 1; +} + +# [private] +# padding block A +PA := array [] of { + byte 16r30, byte 16r20, byte 16r30, byte 16r0c, + byte 16r06, byte 16r08, byte 16r2a, byte 16r86, + byte 16r48, byte 16r86, byte 16rf7, byte 16r0d, + byte 16r02 +}; + +# [private] +# padding block B +PB := array [] of {byte 16r05, byte 16r00, byte 16r04, byte 16r10}; + +# [private] +# require either md5 or md2 of 16 bytes digest +# length of padded digest = 13 + 1 + 4 + 16 + +padhash(data: array of byte, algid: int): array of byte +{ + padded := array [34] of byte; + case algid { + MD2_WithRSAEncryption => + padded[13] = byte 2; + # TODO: implement md2 in keyring module + # keyring->md2(data, len data, padded[18:], nil); + + MD5_WithRSAEncryption => + padded[13] = byte 5; + keyring->md5(data, len data, padded[18:], nil); + * => + return nil; + } + padded[0:] = PA; + padded[14:] = PB; + + return padded; +} + +# [private] +# compare byte to byte of two array of byte + +byte_cmp(a, b: array of byte): int +{ + if(len a != len b) + return 0; + + for(i := 0; i < len a; i++) { + if(a[i] != b[i]) + return 0; + } + + return 1; +} + +# [public] + +RSAKey.bits(key: self ref RSAKey): int +{ + return key.modulus.bits(); +} + +# [public] +# Decode an RSAPublicKey ASN1 type, defined as: +# +# RSAPublickKey :: SEQUENCE { +# modulus INTEGER, +# publicExponent INTEGER +# } + +decode_rsapubkey(a: array of byte): (string, ref RSAKey) +{ +parse: + for(;;) { + (err, e) := asn1->decode(a); + if(err != "") + break parse; + (ok, el) := e.is_seq(); + if(!ok || len el != 2) + break parse; + modbytes, expbytes: array of byte; + (ok, modbytes) = (hd el).is_bigint(); + if(!ok) + break parse; + modulus := IPint.bebytestoip(modbytes); + # get modlen this way, because sometimes it + # comes with leading zeros that are to be ignored! + mbytes := modulus.iptobebytes(); + modlen := len mbytes; + (ok, expbytes) = (hd tl el).is_bigint(); + if(!ok) + break parse; + exponent := keyring->IPint.bebytestoip(expbytes); + return ("", ref RSAKey(modulus, modlen, exponent)); + } + return ("rsa public key: syntax error", nil); +} + + +# [public] +# generate a pair of DSS public and private keys + +generateDSSKeyPair(strength: int): (ref DSSPublicKey, ref DSSPrivateKey) +{ + # TODO: need add getRandBetween in IPint + return (nil, nil); +} + +# [public] + +dss_sign(a: array of byte, sk: ref DSSPrivateKey): (string, array of byte) +{ + #signature, digest: array of byte; + + #case hash { + #Keyring->MD4 => + # digest = array [Keyring->MD4dlen] of byte; + # keyring->md4(a, len a, digest, nil); + #Keyring->MD5 => + # digest = array [Keyring->MD5dlen] of byte; + # keyring->md5(a, len a, digest, nil); + #Keyring->SHA => + # digest = array [Keyring->SHA1dlen] of byte; + # keyring->sha1(a, len a, digest, nil); + #* => + # return ("unknown hash algorithm", nil); + #} + + # TODO: add gcd or getRandBetween in Keyring->IPint + return ("unsupported error", nil); +} + +# [public] + +dss_verify(a, signa: array of byte, pk: ref DSSPublicKey): int +{ + unsigned: array of byte; + + #case hash { + #Keyring->MD4 => + # digest = array [Keyring->MD4dlen] of byte; + # keyring->md4(a, len a, digest, nil); + #Keyring->MD5 => + # digest = array [Keyring->MD5dlen] of byte; + # keyring->md5(a, len a, digest, nil); + #Keyring->SHA => + # digest = array [Keyring->SHA1dlen] of byte; + # keyring->sha1(a, len a, digest, nil); + #* => + # return 0; + #} + + # get unsigned from signa and compare it with digest + + if(byte_cmp(unsigned, a)) + return 1; + + return 0; +} + +# [public] +decode_dsspubkey(a: array of byte): (string, ref DSSPublicKey) +{ + return ("unsupported error", nil); +} + + +# [public] +# generate DH parameters with prime length at least (default) 512 bits + +generateDHParams(primelen: int): ref DHParams +{ + # prime - at least 512 bits + if(primelen < 512) # DHmodlen + primelen = 512; + + # generate prime and base (generator) integers + (p, g) := keyring->dhparams(primelen); + if(p == nil || g == nil) + return nil; + + return ref DHParams(p, g, 0); +} + +# [public] +# generate public and private key pair +# Note: use iptobytes as integer to octet string conversion +# and bytestoip as octect string to integer reversion + +setupDHAgreement(dh: ref DHParams): (ref DHPrivateKey, ref DHPublicKey) +{ + if(dh == nil || dh.prime == nil || dh.base == nil) + return (nil, nil); + + # prime length in bits + bits := dh.prime.bits(); + + # generate random private key of length between bits/4 and bits + x := IPint.random(bits/4, bits); + if(x == nil) + return (nil, nil); + dh.privateValueLength = x.bits(); + + # calculate public key + y := dh.base.expmod(x, dh.prime); + if(y == nil) + return (nil, nil); + + return (ref DHPrivateKey(dh, y, x), ref DHPublicKey(dh, x)); +} + +# [public] +# The second phase of Diffie-Hellman key agreement + +computeDHAgreedKey(dh: ref DHParams, mysk, upk: ref IPint) + : array of byte +{ + if(mysk == nil || upk == nil) + return nil; + + # exponential - calculate agreed key (shared secret) + z := upk.expmod(mysk, dh.prime); + + # integer to octet conversion + return z.iptobebytes(); +} + +# [public] +# ASN1 encoding + +decode_dhpubkey(a: array of byte): (string, ref DHPublicKey) +{ + return ("unsupported error", nil); +} + + +# [public] +# Digest the concatenation of password and salt with count iterations of +# selected message-digest algorithm (either md2 or md5). +# The first 8 bytes of the message digest become the DES key. +# The last 8 bytes of the message digest become the initializing vector IV. + +generateDESKey(pw: array of byte, param: ref PBEParams, alg: int) + : (ref DESstate, array of byte, array of byte) +{ + if(param.iterationCount < 1) + return (nil, nil, nil); + + # concanate password and salt + pwlen := len pw; + pslen := pwlen + len param.salt; + ps := array [pslen] of byte; + ps[0:] = pw; + ps[pwlen:] = param.salt; + key, iv: array of byte; + + # digest iterations + case alg { + PBE_MD2_DESCBC => + ds : ref Keyring->DigestState = nil; + # TODO: implement md2 in keyring module + #result := array [Keyring->MD2dlen] of byte; + #for(i := 0; i < param.iterationCount; i++) + # ds = keyring->md2(ps, pslen, nil, ds); + #keyring->md2(ps, pslen, result, ds); + #key = result[0:8]; + #iv = result[8:]; + + PBE_MD5_DESCBC => + ds: ref Keyring->DigestState = nil; + result := array [Keyring->MD5dlen] of byte; + for(i := 0; i < param.iterationCount; i++) + ds = keyring->md5(ps, pslen, nil, ds); + keyring->md5(ps, pslen, result, ds); + key = result[0:8]; + iv = result[8:]; + + * => + return (nil, nil, nil); + } + + state := keyring->dessetup(key, iv); + + return (state, key, iv); +} + +# [public] +# The message M and a padding string PS shall be formatted into +# an octet string EB +# EB = M + PS +# where +# PS = 1 if M mod 8 = 7; +# PS = 2 + 2 if M mod 8 = 6; +# ... +# PS = 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 if M mod 8 = 0; + +pbe_encrypt(state: ref DESstate, m: array of byte): array of byte +{ + mlen := len m; + padvalue := mlen % 8; + pdlen := 8 - padvalue; + + eb := array [mlen + pdlen] of byte; + eb[0:] = m; + for(i := mlen; i < pdlen; i++) + eb[i] = byte padvalue; + + keyring->descbc(state, eb, len eb, Keyring->Encrypt); + + return eb; +} + +# [public] + +pbe_decrypt(state: ref DESstate, eb: array of byte): array of byte +{ + eblen := len eb; + if(eblen%8 != 0) # must a multiple of 8 bytes + return nil; + + keyring->descbc(state, eb, eblen, Keyring->Decrypt); + + # remove padding + for(i := eblen -8; i < 8; i++) { + if(int eb[i] == i) { + for(j := i; j < 8; j++) + if(int eb[j] != i) + break; + if(j == 8) + break; + } + } + + return eb[0:i]; +} + +# [public] + +PrivateKeyInfo.encode(p: self ref PrivateKeyInfo): (string, array of byte) +{ + + return ("unsupported error", nil); +} + +# [public] + +PrivateKeyInfo.decode(a: array of byte): (string, ref PrivateKeyInfo) +{ + return ("unsupported error", nil); +} + +# [public] + +EncryptedPrivateKeyInfo.encode(p: self ref EncryptedPrivateKeyInfo) + : (string, array of byte) +{ + + return ("unsupported error", nil); +} + +# [public] + +EncryptedPrivateKeyInfo.decode(a: array of byte) + : (string, ref EncryptedPrivateKeyInfo) +{ + return ("unsupported error", nil); +} + +# [public] + +decode_extcertorcert(a: array of byte): (int, int, array of byte) +{ + (err, all) := asn1->decode(a); + if(err == "") { + + } +} + +# [public] + +encode_extcertorcert(a: array of byte, which: int): (int, array of byte) +{ + +} + diff --git a/appl/lib/crypt/ssl3.b b/appl/lib/crypt/ssl3.b new file mode 100644 index 00000000..91819851 --- /dev/null +++ b/appl/lib/crypt/ssl3.b @@ -0,0 +1,5557 @@ +# +# SSL 3.0 protocol +# +implement SSL3; + +include "sys.m"; + sys : Sys; + +include "keyring.m"; + keyring : Keyring; + IPint, DigestState : import keyring; + +include "security.m"; + random : Random; + ssl : SSL; + +include "daytime.m"; + daytime : Daytime; + +include "asn1.m"; + asn1 : ASN1; + +include "pkcs.m"; + pkcs : PKCS; + DHParams, DHPublicKey, DHPrivateKey, + RSAParams, RSAKey, + DSSPrivateKey, DSSPublicKey : import PKCS; + +include "x509.m"; + x509 : X509; + Signed, Certificate, SubjectPKInfo : import x509; + +include "sslsession.m"; + sslsession : SSLsession; + Session : import sslsession; + +include "ssl3.m"; + +# +# debug mode +# +SSL_DEBUG : con 0; +logfd : ref Sys->FD; + +# +# version {major, minor} +# +SSL_VERSION_2_0 := array [] of {byte 0, byte 16r02}; +SSL_VERSION_3_0 := array [] of {byte 16r03, byte 0}; + + +# SSL Record Protocol + +SSL_CHANGE_CIPHER_SPEC : con 20; + SSL_ALERT : con 21; + SSL_HANDSHAKE : con 22; + SSL_APPLICATION_DATA : con 23; + SSL_V2HANDSHAKE : con 0; # escape to sslv2 + +# SSL Application Protocol consists of alert protocol, change cipher spec protocol +# v2 and v3 handshake protocol and application data protocol. The v2 handshake +# protocol is included only for backward compatibility + +Protocol: adt { + pick { + pAlert => + alert : ref Alert; + pChangeCipherSpec => + change_cipher_spec : ref ChangeCipherSpec; + pHandshake => + handshake : ref Handshake; + pV2Handshake => + handshake : ref V2Handshake; + pApplicationData => + data : array of byte; + } + + decode: fn(r: ref Record, ctx: ref Context): (ref Protocol, string); + encode: fn(p: self ref Protocol, vers: array of byte): (ref Record, string); + tostring: fn(p: self ref Protocol): string; +}; + +# +# ssl alert protocol +# +SSL_WARNING : con 1; + SSL_FATAL : con 2; + +SSL_CLOSE_NOTIFY : con 0; + SSL_UNEXPECTED_MESSAGE : con 10; + SSL_BAD_RECORD_MAC : con 20; + SSL_DECOMPRESSION_FAILURE : con 30; + SSL_HANDSHAKE_FAILURE : con 40; + SSL_NO_CERTIFICATE : con 41; + SSL_BAD_CERTIFICATE : con 42; + SSL_UNSUPPORTED_CERTIFICATE : con 43; + SSL_CERTIFICATE_REVOKED : con 44; + SSL_CERTIFICATE_EXPIRED : con 45; + SSL_CERTIFICATE_UNKNOWN : con 46; + SSL_ILLEGAL_PARAMETER : con 47; + +Alert: adt { + level : int; + description : int; + + tostring: fn(a: self ref Alert): string; +}; + +# +# ssl change cipher spec protocol +# +ChangeCipherSpec: adt { + value : int; +}; + +# +# ssl handshake protocol +# +SSL_HANDSHAKE_HELLO_REQUEST : con 0; + SSL_HANDSHAKE_CLIENT_HELLO : con 1; + SSL_HANDSHAKE_SERVER_HELLO : con 2; + SSL_HANDSHAKE_CERTIFICATE : con 11; + SSL_HANDSHAKE_SERVER_KEY_EXCHANGE : con 12; + SSL_HANDSHAKE_CERTIFICATE_REQUEST : con 13; + SSL_HANDSHAKE_SERVER_HELLO_DONE : con 14; + SSL_HANDSHAKE_CERTIFICATE_VERIFY : con 15; + SSL_HANDSHAKE_CLIENT_KEY_EXCHANGE : con 16; + SSL_HANDSHAKE_FINISHED : con 20; + +Handshake: adt { + pick { + HelloRequest => + ClientHello => + version : array of byte; # [2] + random : array of byte; # [32] + session_id : array of byte; # <0..32> + suites : array of byte; # [2] x + compressions : array of byte; # [1] x + ServerHello => + version : array of byte; # [2] + random : array of byte; # [32] + session_id : array of byte; # <0..32> + suite : array of byte; # [2] + compression : byte; # [1] + Certificate => + cert_list : list of array of byte; # X509 cert chain + ServerKeyExchange => + xkey : array of byte; # exchange_keys + CertificateRequest => + cert_types : array of byte; + dn_list : list of array of byte; # DN list + ServerHelloDone => + CertificateVerify => + signature : array of byte; + ClientKeyExchange => + xkey : array of byte; + Finished => + md5_hash : array of byte; # [16] Keyring->MD5dlen + sha_hash : array of byte; # [20] Keyring->SHA1dlen + } + + decode: fn(buf: array of byte): (ref Handshake, string); + encode: fn(hm: self ref Handshake): (array of byte, string); + tostring: fn(hm: self ref Handshake): string; +}; + +# cipher suites and cipher specs (default, not all supported) +# - key exchange, signature, encrypt and digest algorithms + +SSL3_Suites := array [] of { + NULL_WITH_NULL_NULL => array [] of {byte 0, byte 16r00}, + + RSA_WITH_NULL_MD5 => array [] of {byte 0, byte 16r01}, + RSA_WITH_NULL_SHA => array [] of {byte 0, byte 16r02}, + RSA_EXPORT_WITH_RC4_40_MD5 => array [] of {byte 0, byte 16r03}, + RSA_WITH_RC4_128_MD5 => array [] of {byte 0, byte 16r04}, + RSA_WITH_RC4_128_SHA => array [] of {byte 0, byte 16r05}, + RSA_EXPORT_WITH_RC2_CBC_40_MD5 => array [] of {byte 0, byte 16r06}, + RSA_WITH_IDEA_CBC_SHA => array [] of {byte 0, byte 16r07}, + RSA_EXPORT_WITH_DES40_CBC_SHA => array [] of {byte 0, byte 16r08}, + RSA_WITH_DES_CBC_SHA => array [] of {byte 0, byte 16r09}, + RSA_WITH_3DES_EDE_CBC_SHA => array [] of {byte 0, byte 16r0A}, + + DH_DSS_EXPORT_WITH_DES40_CBC_SHA => array [] of {byte 0, byte 16r0B}, + DH_DSS_WITH_DES_CBC_SHA => array [] of {byte 0, byte 16r0C}, + DH_DSS_WITH_3DES_EDE_CBC_SHA => array [] of {byte 0, byte 16r0D}, + DH_RSA_EXPORT_WITH_DES40_CBC_SHA => array [] of {byte 0, byte 16r0E}, + DH_RSA_WITH_DES_CBC_SHA => array [] of {byte 0, byte 16r0F}, + DH_RSA_WITH_3DES_EDE_CBC_SHA => array [] of {byte 0, byte 16r10}, + DHE_DSS_EXPORT_WITH_DES40_CBC_SHA => array [] of {byte 0, byte 16r11}, + DHE_DSS_WITH_DES_CBC_SHA => array [] of {byte 0, byte 16r12}, + DHE_DSS_WITH_3DES_EDE_CBC_SHA => array [] of {byte 0, byte 16r13}, + DHE_RSA_EXPORT_WITH_DES40_CBC_SHA => array [] of {byte 0, byte 16r14}, + DHE_RSA_WITH_DES_CBC_SHA => array [] of {byte 0, byte 16r15}, + DHE_RSA_WITH_3DES_EDE_CBC_SHA => array [] of {byte 0, byte 16r16}, + + DH_anon_EXPORT_WITH_RC4_40_MD5 => array [] of {byte 0, byte 16r17}, + DH_anon_WITH_RC4_128_MD5 => array [] of {byte 0, byte 16r18}, + DH_anon_EXPORT_WITH_DES40_CBC_SHA => array [] of {byte 0, byte 16r19}, + DH_anon_WITH_DES_CBC_SHA => array [] of {byte 0, byte 16r1A}, + DH_anon_WITH_3DES_EDE_CBC_SHA => array [] of {byte 0, byte 16r1B}, + + FORTEZZA_KEA_WITH_NULL_SHA => array [] of {byte 0, byte 16r1C}, + FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA => array [] of {byte 0, byte 16r1D}, + FORTEZZA_KEA_WITH_RC4_128_SHA => array [] of {byte 0, byte 16r1E}, +}; + +# +# key exchange algorithms +# +DHmodlen : con 512; # default length + + +# +# certificate types +# +SSL_RSA_sign : con 1; + SSL_DSS_sign : con 2; + SSL_RSA_fixed_DH : con 3; + SSL_DSS_fixed_DH : con 4; + SSL_RSA_emhemeral_DH : con 5; + SSL_DSS_empemeral_DH : con 6; + SSL_FORTEZZA_MISSI : con 20; + +# +# cipher definitions +# +SSL_EXPORT_TRUE : con 0; + SSL_EXPORT_FALSE : con 1; + +SSL_NULL_CIPHER, + SSL_RC4, + SSL_RC2_CBC, + SSL_IDEA_CBC, + SSL_DES_CBC, + SSL_3DES_EDE_CBC, + SSL_FORTEZZA_CBC : con iota; + +SSL_STREAM_CIPHER, + SSL_BLOCK_CIPHER : con iota; + +SSL_NULL_MAC, + SSL_MD5, + SSL_SHA : con iota; + +# +# MAC paddings +# +SSL_MAX_MAC_PADDING : con 48; +SSL_MAC_PAD1 := array [] of { + byte 16r36, byte 16r36, byte 16r36, byte 16r36, + byte 16r36, byte 16r36, byte 16r36, byte 16r36, + byte 16r36, byte 16r36, byte 16r36, byte 16r36, + byte 16r36, byte 16r36, byte 16r36, byte 16r36, + byte 16r36, byte 16r36, byte 16r36, byte 16r36, + byte 16r36, byte 16r36, byte 16r36, byte 16r36, + byte 16r36, byte 16r36, byte 16r36, byte 16r36, + byte 16r36, byte 16r36, byte 16r36, byte 16r36, + byte 16r36, byte 16r36, byte 16r36, byte 16r36, + byte 16r36, byte 16r36, byte 16r36, byte 16r36, + byte 16r36, byte 16r36, byte 16r36, byte 16r36, + byte 16r36, byte 16r36, byte 16r36, byte 16r36, +}; +SSL_MAC_PAD2 := array [] of { + byte 16r5c, byte 16r5c, byte 16r5c, byte 16r5c, + byte 16r5c, byte 16r5c, byte 16r5c, byte 16r5c, + byte 16r5c, byte 16r5c, byte 16r5c, byte 16r5c, + byte 16r5c, byte 16r5c, byte 16r5c, byte 16r5c, + byte 16r5c, byte 16r5c, byte 16r5c, byte 16r5c, + byte 16r5c, byte 16r5c, byte 16r5c, byte 16r5c, + byte 16r5c, byte 16r5c, byte 16r5c, byte 16r5c, + byte 16r5c, byte 16r5c, byte 16r5c, byte 16r5c, + byte 16r5c, byte 16r5c, byte 16r5c, byte 16r5c, + byte 16r5c, byte 16r5c, byte 16r5c, byte 16r5c, + byte 16r5c, byte 16r5c, byte 16r5c, byte 16r5c, + byte 16r5c, byte 16r5c, byte 16r5c, byte 16r5c, +}; + +# +# finished messages +# +SSL_CLIENT_SENDER := array [] of { + byte 16r43, byte 16r4C, byte 16r4E, byte 16r54}; +SSL_SERVER_SENDER := array [] of { + byte 16r53, byte 16r52, byte 16r56, byte 16r52}; + +# +# a default distiguished names +# +RSA_COMMERCIAL_CA_ROOT_SUBJECT_NAME := array [] of { + byte 16r30, byte 16r5F, byte 16r31, byte 16r0B, + byte 16r30, byte 16r09, byte 16r06, byte 16r03, + byte 16r55, byte 16r04, byte 16r06, byte 16r13, + byte 16r02, byte 16r55, byte 16r53, byte 16r31, + byte 16r20, byte 16r30, byte 16r1E, byte 16r06, + byte 16r03, byte 16r55, byte 16r04, byte 16r0A, + byte 16r13, byte 16r17, byte 16r52, byte 16r53, + byte 16r41, byte 16r20, byte 16r44, byte 16r61, + byte 16r74, byte 16r61, byte 16r20, byte 16r53, + byte 16r65, byte 16r63, byte 16r75, byte 16r72, + byte 16r69, byte 16r74, byte 16r79, byte 16r2C, + byte 16r20, byte 16r49, byte 16r6E, byte 16r63, + byte 16r2E, byte 16r31, byte 16r2E, byte 16r30, + byte 16r2C, byte 16r06, byte 16r03, byte 16r55, + byte 16r04, byte 16r0B, byte 16r13, byte 16r25, + byte 16r53, byte 16r65, byte 16r63, byte 16r75, + byte 16r72, byte 16r65, byte 16r20, byte 16r53, + byte 16r65, byte 16r72, byte 16r76, byte 16r65, + byte 16r72, byte 16r20, byte 16r43, byte 16r65, + byte 16r72, byte 16r74, byte 16r69, byte 16r66, + byte 16r69, byte 16r63, byte 16r61, byte 16r74, + byte 16r69, byte 16r6F, byte 16r6E, byte 16r20, + byte 16r41, byte 16r75, byte 16r74, byte 16r68, + byte 16r6F, byte 16r72, byte 16r69, byte 16r74, + byte 16r79, +}; + +# SSL internal status +USE_DEVSSL, + SSL3_RECORD, + SSL3_HANDSHAKE, + SSL2_HANDSHAKE, + CLIENT_SIDE, + SESSION_RESUMABLE, + CLIENT_AUTH, + CERT_REQUEST, + CERT_SENT, + CERT_RECEIVED, + OUT_READY, + IN_READY : con 1 << iota; + +# SSL internal state +STATE_EXIT, + STATE_CHANGE_CIPHER_SPEC, + STATE_HELLO_REQUEST, + STATE_CLIENT_HELLO, + STATE_SERVER_HELLO, + STATE_CLIENT_KEY_EXCHANGE, + STATE_SERVER_KEY_EXCHANGE, + STATE_SERVER_HELLO_DONE, + STATE_CLIENT_CERTIFICATE, + STATE_SERVER_CERTIFICATE, + STATE_CERTIFICATE_VERIFY, + STATE_FINISHED : con iota; + +# +# load necessary modules +# +init(): string +{ + sys = load Sys Sys->PATH; + if(sys == nil) + return "ssl3: load sys module failed"; + logfd = sys->fildes(1); + + keyring = load Keyring Keyring->PATH; + if(keyring == nil) + return "ssl3: load keyring module failed"; + + random = load Random Random->PATH; + if(random == nil) + return "ssl3: load random module failed"; + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + return "ssl3: load Daytime module failed"; + + pkcs = load PKCS PKCS->PATH; + if(pkcs == nil) + return "ssl3: load pkcs module failed"; + pkcs->init(); + + x509 = load X509 X509->PATH; + if(x509 == nil) + return "ssl3: load x509 module failed"; + x509->init(); + + ssl = load SSL SSL->PATH; + if(ssl == nil) + return "ssl3: load SSL module failed"; + sslsession = load SSLsession SSLsession->PATH; + if(sslsession == nil) + return "ssl3: load sslsession module failed"; + e := sslsession->init(); + if(e != nil) + return "ssl3: sslsession init failed: "+e; + + return ""; +} + +log(s: string) +{ + a := array of byte (s + "\n"); + sys->write(logfd, a, len a); +} + +# +# protocol context +# + +Context.new(): ref Context +{ + ctx := ref Context; + + ctx.c = nil; + ctx.session = nil; + ctx.local_info = nil; + + ctx.sha_state = nil; + ctx.md5_state = nil; + + ctx.status = 0; + ctx.state = 0; + + ctx.client_random = array [32] of byte; + ctx.server_random = array [32] of byte; + + ctx.cw_mac = nil; + ctx.sw_mac = nil; + ctx.cw_key = nil; + ctx.sw_key = nil; + ctx.cw_IV = nil; + ctx.sw_IV = nil; + + ctx.in_queue = RecordQueue.new(); + ctx.in_queue.data = ref Record(0, nil, array [1<<15] of byte) :: nil; + ctx.out_queue = RecordQueue.new(); + + # set session resumable as default + ctx.status |= SESSION_RESUMABLE; + + return ctx; +} + +Context.client(ctx: self ref Context, fd: ref Sys->FD, peername: string, ver: int, info: ref Authinfo) + : (string, int) +{ + if(SSL_DEBUG) + log(sys->sprint("ssl3: Context.Client peername=%s ver=%d\n", peername, ver)); + if ((ckstr := cksuites(info.suites)) != nil) + return (ckstr, ver); + # the order is important + ctx.local_info = info; + ctx.state = STATE_HELLO_REQUEST; + e := ctx.connect(fd); + if(e != "") + return (e, ver); + ctx.session = sslsession->get_session_byname(peername); + + # Request to resume an SSL 3.0 session should use an SSL 3.0 client hello + if(ctx.session.session_id != nil) { + if((ctx.session.version[0] == SSL_VERSION_3_0[0]) && + (ctx.session.version[1] == SSL_VERSION_3_0[1])) { + ver = 3; + ctx.status |= SSL3_HANDSHAKE; + ctx.status &= ~SSL2_HANDSHAKE; + } + } + e = ctx.set_version(ver); + if(e != "") + return (e, ver); + reset_client_random(ctx); + ctx.status |= CLIENT_SIDE; + e = do_protocol(ctx); + if(e != nil) + return (e, ver); + + if(ctx.status & SSL3_RECORD) + ver = 3; + else + ver = 2; + return (nil, ver); +} + +Context.server(ctx: self ref Context, fd: ref Sys->FD, info: ref Authinfo, client_auth: int) + : string +{ + if ((ckstr := cksuites(info.suites)) != nil) + return ckstr; + ctx.local_info = info; + if(client_auth) + ctx.status |= CLIENT_AUTH; + ctx.state = STATE_CLIENT_HELLO; + e := ctx.connect(fd); + if(e != "") + return e; + reset_server_random(ctx); + e = ctx.set_version(3); # set ssl device to version 3 + if(e != "") + return e; + ctx.status &= ~CLIENT_SIDE; + e = do_protocol(ctx); + if(e != nil) + return e; + + return ""; +} + + +Context.use_devssl(ctx: self ref Context) +{ + if(!(ctx.status & IN_READY) && !(ctx.status & OUT_READY)) + ctx.status |= USE_DEVSSL; +} + +Context.set_version(ctx: self ref Context, vers: int): string +{ + err := ""; + + if(ctx.c == nil) { + err = "no connection provided"; + } + else { + if(SSL_DEBUG) + log("ssl3: record version = " + string vers); + + if(vers == 2) { + ctx.status &= ~SSL3_RECORD; + ctx.status &= ~SSL3_HANDSHAKE; + ctx.status |= SSL2_HANDSHAKE; + if (ctx.session != nil) + ctx.session.version = SSL_VERSION_2_0; + } + else if(vers == 3) { # may be sslv2 handshake using ssl3 record + ctx.status |= SSL3_RECORD; + ctx.status |= SSL3_HANDSHAKE; + ctx.status &= ~SSL2_HANDSHAKE; # tmp test only + if (ctx.session != nil) + ctx.session.version = SSL_VERSION_3_0; + } + else if(vers == 23) { # may be sslv2 handshake using ssl3 record + ctx.status &= ~SSL3_RECORD; + ctx.status |= SSL3_HANDSHAKE; + ctx.status |= SSL2_HANDSHAKE; + vers = 2; + } + else + err = "unsupported ssl device version"; + + if((err == "") && (ctx.status & USE_DEVSSL)) { + if(sys->fprint(ctx.c.cfd, "ver %d", vers) < 0) + err = sys->sprint("ssl3: set ssl device version failed: %r"); + } + } + + return err; +} + +Context.connect(ctx: self ref Context, fd: ref Sys->FD): string +{ + err := ""; + + if(ctx.status & USE_DEVSSL) + (err, ctx.c) = ssl->connect(fd); + else { + ctx.c = ref Sys->Connection(fd, nil, ""); + ctx.in_queue.sequence_numbers[0] = 0; + ctx.out_queue.sequence_numbers[1] = 0; + } + + return err; +} + +Context.read(ctx: self ref Context, a: array of byte, n: int): int +{ + if(ctx.state != STATE_EXIT || !(ctx.status & IN_READY)) { + if(SSL_DEBUG) + log("ssl3: read not ready\n"); + return -1; + } + + if(ctx.out_queue.data != nil) + record_write_queue(ctx); + + if(ctx.status & USE_DEVSSL) { + fd := ctx.c.dfd; + if(ctx.status & SSL3_RECORD) { + buf := array [n+3] of byte; + m := sys->read(fd, buf, n+3); # header + n bytes + if(m < 3) { + if(SSL_DEBUG) + log(sys->sprint("ssl3: read failure: %r")); + return -1; + } + + if(buf[1] != SSL_VERSION_3_0[0] || buf[2] != SSL_VERSION_3_0[1]) { + if(SSL_DEBUG) + log("ssl3: not ssl version 3 data: header = [" + bastr(buf[0:3]) + "]"); + return -1; + } + + a[0:] = buf[3:m]; + + content_type := int buf[0]; + case content_type { + SSL_APPLICATION_DATA => + break; + SSL_ALERT => + if(SSL_DEBUG) + log("ssl3: expect application data, got alert: [" + bastr(buf[3:m]) +"]"); + return 0; + SSL_HANDSHAKE => + if(SSL_DEBUG) + log("ssl3: expect application data, got handshake message"); + return 0; + SSL_CHANGE_CIPHER_SPEC => + if(SSL_DEBUG) + log("ssl3: dynamic change cipher spec not supported yet"); + return 0; + } + return m-3; + } + else + return sys->read(fd, a, n); + } + else { + q := ctx.in_queue; + got := 0; + if(q.fragment) { + d := (hd q.data).data; + m := q.e - q.b; + i := q.e - q.fragment; + if(q.fragment > n) { + a[0:] = d[i:i+n]; + q.fragment -= n; + got = n; + } + else { + a[0:] = d[i:q.e]; + got = q.fragment; + q.fragment = 0; + } + } +out: + while(got < n) { + err := q.read(ctx, ctx.c.dfd); + if(err != "") { + if(SSL_DEBUG) + log("ssl3: read: " + err); + break; + } + r := hd q.data; + if(ctx.status & SSL3_RECORD) { + case r.content_type { + SSL_APPLICATION_DATA => + break; + SSL_ALERT => + if(SSL_DEBUG) + log("ssl3: read: got alert\n\t\t" + bastr(r.data[q.b:q.e])); + # delete session id + ctx.session.session_id = nil; + ctx.status &= ~IN_READY; + break out; + SSL_CHANGE_CIPHER_SPEC => + if(SSL_DEBUG) + log("ssl3: read: got change cipher spec\n"); + SSL_HANDSHAKE => + if(SSL_DEBUG) + log("ssl3: read: got handshake data\n"); + #do_handshake(ctx, r); # ? + * => + if(SSL_DEBUG) + log("ssl3: read: unknown data\n"); + } + } + + if((n - got) <= (q.e - q.b)) { + a[got:] = r.data[q.b:q.b+n-got]; + q.fragment = q.e - q.b - n + got; + got = n; + } + else { + a[got:] = r.data[q.b:q.e]; + q.fragment = 0; + got += q.e - q.b; + } + } + if(SSL_DEBUG) + log(sys->sprint("ssl3: read: returning %d bytes\n", got)); + return got; + } +} + +Context.write(ctx: self ref Context, a: array of byte, n: int): int +{ + if(ctx.state != STATE_EXIT || !(ctx.status & OUT_READY)) + return-1; + + if(ctx.out_queue.data != nil) + record_write_queue(ctx); + + if(ctx.status & USE_DEVSSL) { + if(ctx.status & SSL3_RECORD) { + buf := array [n+3] of byte; + buf[0] = byte SSL_APPLICATION_DATA; + buf[1:] = SSL_VERSION_3_0; + buf[3:] = a[0:n]; + n = sys->write(ctx.c.dfd, buf, n+3); + if(n > 0) + n -= 3; + } + else + n = sys->write(ctx.c.dfd, a, n); + } + else { + q := ctx.out_queue; + v := SSL_VERSION_2_0; + if(ctx.status&SSL3_RECORD) + v = SSL_VERSION_3_0; + for(i := 0; i < n;){ + m := n-i; + if(m > q.length) + m = q.length; + r := ref Record(SSL_APPLICATION_DATA, v, a[i:i+m]); + record_write(r, ctx); # return error? + i += m; + } + } + return n; +} + +devssl_read(ctx: ref Context): (ref Record, string) +{ + buf := array [Sys->ATOMICIO] of byte; + r: ref Record; + c := ctx.c; + + n := sys->read(c.dfd, buf, 3); + if(n < 0) + return (nil, sys->sprint("record read: read failure: %r")); + + # in case of undetermined, do auto record version detection + if((ctx.state == SSL2_STATE_SERVER_HELLO) && + (ctx.status & SSL2_HANDSHAKE) && (ctx.status & SSL3_HANDSHAKE)) { + + fstatus := sys->open(ctx.c.dir + "/status", Sys->OREAD); + if(fstatus == nil) + return (nil, "open status: " + sys->sprint("%r")); + status := array [64] of byte; + nbyte := sys->read(fstatus, status, len status); + if(nbyte != 1) + return (nil, "read status: " + sys->sprint("%r")); + + ver := int status[0]; + + if(SSL_DEBUG) + log("ssl3: auto record version detect as: " + string ver); + + # assert ctx.status & SSL2_RECORD true ? before reset + if(ver == 2) { + ctx.status &= ~SSL3_RECORD; + ctx.status |= SSL2_HANDSHAKE; + ctx.status &= ~SSL3_HANDSHAKE; + } + else { + ctx.status |= SSL3_RECORD; + } + } + + if(ctx.status & SSL3_RECORD) { + if(n < 3) + return (nil, sys->sprint("record read: read failure: %r")); + + # assert only major version number + if(buf[1] != SSL_VERSION_3_0[0]) + return (nil, "record read: version mismatch"); + + case int buf[0] { + SSL_ALERT => + n = sys->read(c.dfd, buf, 5); # read in header plus rest + if(n != 5) + return (nil, sys->sprint("read alert failed: %r")); + r = ref Record(SSL_ALERT, SSL_VERSION_3_0, buf[3:5]); + + SSL_CHANGE_CIPHER_SPEC => + n = sys->read(c.dfd, buf, 4); # read in header plus rest + if(n != 4) + return (nil, sys->sprint("read change_cipher_spec failed: %r")); + r = ref Record(SSL_CHANGE_CIPHER_SPEC, SSL_VERSION_3_0, buf[3:4]); + + SSL_HANDSHAKE => + n = sys->read(c.dfd, buf, 7); # header + msg length + if(n != 7) + return (nil, sys->sprint("read handshake header + msg length failed: %r")); + m := int_decode(buf[4:7]); + if(m < 0) + return (nil, "read handshake failed: unexpected length"); + data := array [m+4] of byte; + data[0:] = buf[3:7]; # msg type + length + if(m != 0) { + # need exact m bytes (exclude header), otherwise failure + remain := m; + now := 4; + while(remain > 0) { + n = sys->read(c.dfd, buf, remain+3); # header + msg + if(n < 3 || int buf[0] != SSL_HANDSHAKE) + return (nil, sys->sprint("read handshake msg body failed: %r")); + sys->print("expect %d, got %d bytes\n", m, n-3); + remain -= n - 3; + data[now:] = buf[3:n]; + now += n - 3; + } + } + + r = ref Record(SSL_HANDSHAKE, SSL_VERSION_3_0, data); + * => + return (nil, "trying to read unknown protocol message"); + } + + if(SSL_DEBUG) + log("ssl3: record_read: \n\theader = \n\t\t" + bastr(buf[0:3]) + + "\n\tdata = \n\t\t" + bastr(r.data) + "\n"); + } + # v2 record layer + else { + # assume the handshake record size less than Sys->ATOMICIO + # in most case, this is ok + if(n == 3) { + n = sys->read(c.dfd, buf[3:], Sys->ATOMICIO - 3); + if(n < 0) + return (nil, sys->sprint("v2 record read: read failure: %r")); + } + + r = ref Record(SSL_V2HANDSHAKE, SSL_VERSION_2_0, buf[0:n+3]); + + if(SSL_DEBUG) + log("ssl3: v2 record_read: \n\tdata = \n\t\t" + bastr(r.data) + "\n"); + } + + return (r, nil); +} + +record_read(ctx: ref Context): (ref Record, string) +{ + q := ctx.in_queue; + if(q.fragment == 0) { + err := q.read(ctx, ctx.c.dfd); + if(err != "") + return (nil, err); + q.fragment = q.e - q.b; + } + + r := hd q.data; + if(ctx.status & SSL3_RECORD) { + # confirm only major version number + if(r.version[0] != SSL_VERSION_3_0[0]) + return (nil, "record read: not v3 record"); + + case r.content_type { + SSL_ALERT => + a := array [2] of byte; + n := fetch_data(ctx, a, 2); + if(n != 2) + return (nil, "read alert failed"); + r = ref Record(SSL_ALERT, SSL_VERSION_3_0, a); + + SSL_CHANGE_CIPHER_SPEC => + a := array [1] of byte; + n := fetch_data(ctx, a, 1); + if(n != 1) + return (nil, "read change_cipher_spec failed"); + r = ref Record(SSL_CHANGE_CIPHER_SPEC, SSL_VERSION_3_0, a); + + SSL_HANDSHAKE => + a := array [4] of byte; + n := fetch_data(ctx, a, 4); + if(n != 4) + return (nil, "read message length failed"); + m := int_decode(a[1:]); + if(m < 0) + return (nil, "unexpected handshake message length"); + b := array [m+4] of byte; + b[0:] = a; + n = fetch_data(ctx, b[4:], m); + if(n != m) + return (nil, "read message body failed"); + r = ref Record(SSL_HANDSHAKE, SSL_VERSION_3_0, b); + * => + return (nil, "trying to read unknown protocol message"); + } + } + # v2 record layer + else { + r = ref Record(SSL_V2HANDSHAKE, SSL_VERSION_2_0, r.data[q.b:q.e]); + q.fragment = 0; + } + + return (r, nil); +} + +fetch_data(ctx: ref Context, a: array of byte, n: int): int +{ + q := ctx.in_queue; + r := hd q.data; + + got := 0; + cnt := -1; +out: + while(got < n) { + if(q.fragment) { + cnt = r.content_type; + i := q.e - q.fragment; + if(n-got <= q.fragment) { + a[got:] = r.data[i:i+n-got]; + q.fragment -= n - got; + got = n; + } + else { + a[got:] = r.data[i:q.e]; + got += q.fragment; + q.fragment = 0; + } + } + else { + err := q.read(ctx, ctx.c.dfd); + if(err != "") + break out; + if(cnt == -1) + cnt = r.content_type; + if(ctx.status & SSL3_RECORD) { + case r.content_type { + SSL_APPLICATION_DATA => + break; + * => + if(cnt != r.content_type) + break out; + } + } + else { + r.content_type = SSL_V2HANDSHAKE; + } + } + } + return got; +} + +record_write(r: ref Record, ctx: ref Context) +{ + if(ctx.status & USE_DEVSSL) { + buf: array of byte; + n: int; + c := ctx.c; + + if(ctx.status & SSL3_RECORD) { + buf = array [3 + len r.data] of byte; + buf[0] = byte r.content_type; + buf[1:] = r.version; + buf[3:] = r.data; + n = sys->write(c.dfd, buf, len buf); + if(n < 0 || n != len buf) { + if(SSL_DEBUG) + log(sys->sprint("ssl3: v3 record write error: %d %r", n)); + return; # don't terminated until alerts being read + } + } + else { + buf = r.data; + n = sys->write(c.dfd, buf, len buf); + if(n < 0 || n != len buf) { + if(SSL_DEBUG) + log(sys->sprint("ssl3: v2 record write error: %d %r", n)); + return; # don't terminated until alerts being read + } + } + } + else + ctx.out_queue.write(ctx, ctx.c.dfd, r); + + # if(SSL_DEBUG) + # log("ssl3: record_write: \n\t\t" + bastr(buf) + "\n"); +} + +RecordQueue.new(): ref RecordQueue +{ + q := ref RecordQueue( + ref MacState.null(0), + ref CipherState.null(1), + 1 << 15, + array [2] of { * => 0}, + nil, + 0, + 0, # b + 0 # e + ); + return q; +} + +RecordQueue.read(q: self ref RecordQueue, ctx: ref Context, fd: ref Sys->FD): string +{ + r := hd q.data; + a := r.data; + if(ensure(fd, a, 2) < 0) + return "no more data"; + # auto record version detection + m, h, pad: int = 0; + if(int a[0] < 20 || int a[0] > 23) { + ctx.status &= ~SSL3_RECORD; + if(int a[0] & 16r80) { + h = 2; + m = ((int a[0] & 16r7f) << 8) | int a[1]; + pad = 0; + } else { + h = 3; + m = ((int a[0] & 16r3f) << 8) | int a[1]; + if(ensure(fd, a[2:], 1) < 0) + return "bad v2 record"; + pad = int a[2]; + if(pad > m) + return "bad v2 pad"; + } + r.content_type = SSL_V2HANDSHAKE; + r.version = SSL_VERSION_2_0; + } + else { + ctx.status |= SSL3_RECORD; + h = 5; + if(ensure(fd, a[2:], 3) < 0) + return "bad v3 record"; + m = ((int a[3]) << 8) | int a[4]; + r.content_type = int a[0]; + r.version = a[1:3]; + } + if(ensure(fd, a[h:], m) < 0) +# return "data too short"; + return sys->sprint("data too short wanted %d", m); + if(SSL_DEBUG) { + log("ssl3: record read\n\tbefore decrypt\n\t\t" + bastr(a[0:m+h])); + log(sys->sprint("SSL3=%d\n", ctx.status & SSL3_RECORD)); + } + + # decrypt (data, pad, mac) + pick dec := q.cipherState { + null => + rc4 => + keyring->rc4(dec.es, a[h:], m); + if (SSL_DEBUG) log("rc4 1"); + descbc => + keyring->descbc(dec.es, a[h:], m, 1); + if (SSL_DEBUG) log("descbc 1"); + ideacbc => + keyring->ideacbc(dec.es, a[h:], m, 1); + if (SSL_DEBUG) log("ideacbc 1"); + * => + } + + if(SSL_DEBUG) + log("ssl3: record read\n\tafter decrypt\n\t\t" + bastr(a[0:m])); + + idata, imac, ipad: int = 0; + if(ctx.status & SSL3_RECORD) { + if(q.cipherState.block_size > 1){ + pad = int a[h + m - 1]; + if(pad >= q.cipherState.block_size) + return "bad v3 pad"; + # pad++; + ipad = h+m-pad-1; + } + else + ipad = h+m-pad; + imac = ipad - q.macState.hash_size; + idata = h; + } + else { + imac = h; + idata = imac + q.macState.hash_size; + ipad = h + m - pad; + } + if(tagof q.macState != tagof MacState.null) { + if (ctx.status & SSL3_RECORD) + mac := q.calcmac(ctx, r.content_type, a, idata, imac-idata); + else + mac = q.calcmac(ctx, r.content_type, a, idata, ipad+pad-idata); + if(bytes_cmp(mac, a[imac:imac+len mac]) < 0) + return "bad mac"; + } + q.b = idata; + if (ctx.status & SSL3_RECORD) + q.e = imac; + else + q.e = ipad; + q.fragment = q.e - q.b; + + if((++q.sequence_numbers[0] == 0) && (ctx.status&SSL3_RECORD)) + ++q.sequence_numbers[1]; + + return ""; +} + +ensure(fd: ref Sys->FD, a: array of byte, n: int): int +{ + i, m: int = 0; + while(i < n) { + m = sys->read(fd, a[i:], n - i); + if(m <= 0) { + return -1; + } + i += m; + } + return n; +} + +RecordQueue.write(q: self ref RecordQueue, ctx: ref Context, fd: ref Sys->FD, + r: ref Record): string +{ + m := len r.data; + h, pad: int = 0; + if(ctx.status & SSL3_RECORD) { + h = 5; + if(q.cipherState.block_size > 1) { + pad = (m+q.macState.hash_size+1)%q.cipherState.block_size; + if (pad) + pad = q.cipherState.block_size - pad; + } + } + else { + h = 2; + if(q.cipherState.block_size > 1) { + pad = m%q.cipherState.block_size; + if(pad) { + pad = q.cipherState.block_size - pad; + h++; + } + } + } + + m += pad + q.macState.hash_size; + if ((ctx.status & SSL3_RECORD) && q.cipherState.block_size > 1) + m++; + a := array [h+m] of byte; + + idata, imac, ipad: int = 0; + if(ctx.status & SSL3_RECORD) { + a[0] = byte r.content_type; + a[1:] = r.version; + a[3] = byte (m >> 8); #CJL - netscape ssl3 traces do not show top bit set +# a[3] = byte ((m >> 8) | 16r80); #CJL +# a[3] = byte (m | 16r8000) >> 8; + a[4] = byte m; + idata = h; + imac = idata + len r.data; + ipad = imac + q.macState.hash_size; + if (q.cipherState.block_size > 1) + a[h+m-1] = byte pad; + } + else { + if(pad) { + a[0] = byte m >> 8; + a[2] = byte pad; + } + else + a[0] = byte ((m >> 8) | 16r80); + a[1] = byte m; + imac = h; + idata = imac + q.macState.hash_size; + ipad = idata + len r.data; + } + a[idata:] = r.data; + if(pad) + a[ipad:] = array [pad] of { * => byte (pad-1)}; + + if(tagof q.macState != tagof MacState.null) { + if (ctx.status & SSL3_RECORD) + a[imac:] = q.calcmac(ctx, r.content_type, a, idata, len r.data); + else + a[imac:] = q.calcmac(ctx, r.content_type, a, idata, ipad+pad-idata); + } + + if(SSL_DEBUG) { + log("ssl3: record write\n\tbefore encrypt\n\t\t" + bastr(a)); + log(sys->sprint("SSL3=%d\n", ctx.status & SSL3_RECORD)); + } + + # encrypt (data, pad, mac) + pick enc := q.cipherState { + null => + rc4 => + keyring->rc4(enc.es, a[h:], m); + if (SSL_DEBUG) log("rc4 0"); + descbc => + keyring->descbc(enc.es, a[h:], m, 0); + if (SSL_DEBUG) log(sys->sprint("descbc 0 %d", m)); + ideacbc => + keyring->ideacbc(enc.es, a[h:], m, 0); + if (SSL_DEBUG) log(sys->sprint("ideacbc 0 %d", m)); + * => + } + + if(SSL_DEBUG) + log("ssl3: record write\n\tafter encrypt\n\t\t" + bastr(a)); + + if(sys->write(fd, a, h+m) < 0) + return sys->sprint("ssl3: record write: %r"); + + if((++q.sequence_numbers[0] == 0) && (ctx.status&SSL3_RECORD)) + ++q.sequence_numbers[1]; + + return ""; +} + +RecordQueue.calcmac(q: self ref RecordQueue, ctx: ref Context, cntype: int, a: array of byte, + ofs, n: int) : array of byte +{ + digest, b: array of byte; + + if(ctx.status & SSL3_RECORD) { + b = array [11] of byte; + i := putn(b, 0, q.sequence_numbers[1], 4); + i = putn(b, i, q.sequence_numbers[0], 4); + b[i++] = byte cntype; + putn(b, i, n, 2); + } + else { + b = array [4] of byte; + putn(b, 0, q.sequence_numbers[0], 4); + } + + # if(SSL_DEBUG) + # log("ssl3: record mac\n\tother =\n\t\t" + bastr(b)); + + pick ms := q.macState { + md5 => + digest = array [Keyring->MD5dlen] of byte; + ds0 := ms.ds[0].copy(); + if(ctx.status & SSL3_RECORD) { + keyring->md5(b, len b, nil, ds0); + keyring->md5(a[ofs:], n, digest, ds0); + ds1 := ms.ds[1].copy(); + keyring->md5(digest, len digest, digest, ds1); + } + else { + keyring->md5(a[ofs:], n, nil, ds0); + keyring->md5(b, len b, digest, ds0); + } + sha => + digest = array [Keyring->SHA1dlen] of byte; + ds0 := ms.ds[0].copy(); + if(ctx.status & SSL3_RECORD) { + keyring->sha1(b, len b, nil, ds0); + keyring->sha1(a[ofs:], n, digest, ds0); + ds1 := ms.ds[1].copy(); + keyring->sha1(digest, len digest, digest, ds1); + } + else { + keyring->sha1(a[ofs:], n, nil, ds0); + keyring->sha1(b, len b, digest, ds0); + } + } + return digest; +} + +set_queues(ctx: ref Context): string +{ + sw: array of byte; + if(ctx.sw_key != nil) { + sw = array [len ctx.sw_key + len ctx.sw_IV] of byte; + sw[0:] = ctx.sw_key; + sw[len ctx.sw_key:] = ctx.sw_IV; + } + cw: array of byte; + if(ctx.cw_key != nil) { + cw = array [len ctx.cw_key + len ctx.cw_IV] of byte; + cw[0:] = ctx.cw_key; + cw[len ctx.cw_key:] = ctx.cw_IV; + } + + err := ""; + if(ctx.status & USE_DEVSSL) { + err = set_secrets(ctx.c, ctx.sw_mac, ctx.cw_mac, sw, cw); + if(err == "") + err = set_cipher_algs(ctx); + } + else { + err = set_out_queue(ctx); + if(err == "") + err = set_in_queue(ctx); + } + + return err; +} + +set_in_queue(ctx: ref Context): string +{ + sw: array of byte; + if(ctx.sw_key != nil) { + sw = array [len ctx.sw_key + len ctx.sw_IV] of byte; + sw[0:] = ctx.sw_key; + sw[len ctx.sw_key:] = ctx.sw_IV; + } + + err := ""; + if(ctx.status & USE_DEVSSL) { + err = set_secrets(ctx.c, ctx.sw_mac, nil, sw, nil); + if(err == "") + err = set_cipher_algs(ctx); + } + else + err = set_queue(ctx, ctx.in_queue, ctx.sw_mac, sw); + + return err; +} + +set_out_queue(ctx: ref Context): string +{ + cw: array of byte; + if(ctx.cw_key != nil) { + cw = array [len ctx.cw_key + len ctx.cw_IV] of byte; + cw[0:] = ctx.cw_key; + cw[len ctx.cw_key:] = ctx.cw_IV; + } + + err := ""; + if(ctx.status & USE_DEVSSL) { + err = set_secrets(ctx.c, nil, ctx.cw_mac, nil, cw); + if(err == "") + err = set_cipher_algs(ctx); + } + else + err = set_queue(ctx, ctx.out_queue, ctx.cw_mac, cw); + + return err; +} + +set_queue(ctx: ref Context, q: ref RecordQueue, mac, key: array of byte): string +{ + e := ""; + + case ctx.sel_ciph.mac_algorithm { + SSL_NULL_MAC => + q.macState = ref MacState.null(0); + SSL_MD5 => + ds: array of ref DigestState; + if(ctx.status & SSL3_RECORD) { + ds = array [2] of ref DigestState; + ds[0] = keyring->md5(mac, len mac, nil, nil); + ds[1] = keyring->md5(mac, len mac, nil, nil); + ds[0] = keyring->md5(SSL_MAC_PAD1, 48, nil, ds[0]); + ds[1] = keyring->md5(SSL_MAC_PAD2, 48, nil, ds[1]); + } + else { + ds = array [1] of ref DigestState; + ds[0] = keyring->md5(mac, len mac, nil, nil); + } + q.macState = ref MacState.md5(Keyring->MD5dlen, ds); + SSL_SHA => + ds: array of ref DigestState; + if(ctx.status & SSL3_RECORD) { + ds = array [2] of ref DigestState; + ds[0] = keyring->sha1(mac, len mac, nil, nil); + ds[1] = keyring->sha1(mac, len mac, nil, nil); + ds[0] = keyring->sha1(SSL_MAC_PAD1, 40, nil, ds[0]); + ds[1] = keyring->sha1(SSL_MAC_PAD2, 40, nil, ds[1]); + } + else { + ds = array [1] of ref DigestState; + ds[0] = keyring->sha1(mac, len mac, nil, nil); + } + q.macState = ref MacState.sha(Keyring->SHA1dlen, ds); + * => + e = "ssl3: digest method: unknown"; + } + + case ctx.sel_ciph.bulk_cipher_algorithm { + SSL_NULL_CIPHER => + q.cipherState = ref CipherState.null(1); + SSL_RC4 => + if (SSL_DEBUG) log("rc4setup"); + rcs := keyring->rc4setup(key); + q.cipherState = ref CipherState.rc4(1, rcs); + SSL_DES_CBC => + dcs : ref keyring->DESstate; + + if (SSL_DEBUG) log(sys->sprint("dessetup %d", len key)); + if (len key >= 16) + dcs = keyring->dessetup(key[0:8], key[8:16]); + else if (len key >= 8) + dcs = keyring->dessetup(key[0:8], nil); + else + e = "ssl3: bad DES key length"; + q.cipherState = ref CipherState.descbc(8, dcs); + SSL_IDEA_CBC => + ics : ref keyring->IDEAstate; + + if (SSL_DEBUG) log(sys->sprint("ideasetup %d", len key)); + if (len key >= 24) + ics = keyring->ideasetup(key[0:16], key[16:24]); + else if (len key >= 16) + ics = keyring->ideasetup(key[0:16], nil); + else + e = "ssl3: bad IDEA key length"; + q.cipherState = ref CipherState.ideacbc(8, ics); + SSL_RC2_CBC or + SSL_3DES_EDE_CBC or + SSL_FORTEZZA_CBC => + e = "ssl3: unsupported cipher"; + * => + e = "ssl3: unknown cipher"; + } + + if(ctx.status & SSL3_RECORD) { + q.length = 1 << 14; + if(tagof q.macState != tagof MacState.null) + q.length += 2048; + } + else { + if(q.cipherState.block_size > 1) { + q.length = (1<<14) - q.macState.hash_size - 1; + q.length -= q.length % q.cipherState.block_size; + } + else + q.length = (1<<15) - q.macState.hash_size - 1; + } + if(ctx.status & SSL3_RECORD) + q.sequence_numbers[0] = q.sequence_numbers[1] = 0; + + return e; +} + +set_cipher_algs(ctx: ref Context) : string +{ + e: string; + + algspec := "alg"; + + case enc := ctx.sel_ciph.bulk_cipher_algorithm { + SSL_NULL_CIPHER => + algspec += " clear"; + SSL_RC4 => # stream cipher + algspec += " rc4_128"; + SSL_DES_CBC => # block cipher + algspec += " descbc"; + SSL_IDEA_CBC => # block cipher + algspec += " ideacbc"; + SSL_RC2_CBC or + SSL_3DES_EDE_CBC or + SSL_FORTEZZA_CBC => + e = "ssl3: encrypt method: unsupported"; + * => + e = "ssl3: encrypt method: unknown"; + } + + case mac := ctx.sel_ciph.mac_algorithm { + SSL_NULL_MAC => + algspec += " clear"; + SSL_MD5 => + algspec += " md5"; + SSL_SHA => + algspec += " sha1"; + * => + e = "ssl3: digest method: unknown"; + } + + e = set_ctl(ctx.c, algspec); + if(e != "") { + if(SSL_DEBUG) + log("failed to set cipher algs: " + e); + } + + return e; +} + +set_ctl(c: ref Sys->Connection, s: string): string +{ + a := array of byte s; + if(sys->write(c.cfd, a, len a) < 0) + return sys->sprint("error writing sslctl: %r"); + + if(SSL_DEBUG) + log("ssl3: set cipher algorithm:\n\t\t" + s + "\n"); + + return ""; +} + +set_secrets(c: ref Sys->Connection, min, mout, sin, sout: array of byte) : string +{ + fmin := sys->open(c.dir + "/macin", Sys->OWRITE); + fmout := sys->open(c.dir + "/macout", Sys->OWRITE); + fsin := sys->open(c.dir + "/secretin", Sys->OWRITE); + fsout := sys->open(c.dir + "/secretout", Sys->OWRITE); + if(fmin == nil || fmout == nil || fsin == nil || fsout == nil) + return sys->sprint("can't open ssl secret files: %r\n"); + + if(sin != nil) { + if(SSL_DEBUG) + log("ssl3: set encryption secret and IV\n\tsecretin:\n\t\t" + bastr(sin) + "\n"); + if(sys->write(fsin, sin, len sin) < 0) + return sys->sprint("error writing secretin: %r"); + } + if(sout != nil) { + if(SSL_DEBUG) + log("ssl3: set encryption secret and IV\n\tsecretout:\n\t\t" + bastr(sout) + "\n"); + if(sys->write(fsout, sout, len sout) < 0) + return sys->sprint("error writing secretout: %r"); + } + if(min != nil) { + if(SSL_DEBUG) + log("ssl3: set digest secret\n\tmacin:\n\t\t" + bastr(min) + "\n"); + if(sys->write(fmin, min, len min) < 0) + return sys->sprint("error writing macin: %r"); + } + if(mout != nil) { + if(SSL_DEBUG) + log("ssl3: set digest secret\n\tmacout:\n\t\t" + bastr(mout) + "\n"); + if(sys->write(fmout, mout, len mout) < 0) + return sys->sprint("error writing macout: %r"); + } + + return ""; +} + +# +# description must be alert description +# +fatal(description: int, debug_msg: string, ctx: ref Context) +{ + if(SSL_DEBUG) + log("ssl3: " + debug_msg); + + # TODO: use V2Handshake.Error for v2 + alert_enque(ref Alert(SSL_FATAL, description), ctx); + + # delete session id + ctx.session.session_id = nil; + + ctx.state = STATE_EXIT; +} + +alert_enque(a: ref Alert, ctx: ref Context) +{ + p := ref Protocol.pAlert(a); + + protocol_write(p, ctx); +} + +# clean up out queue before switch cipher. this is why +# change cipher spec differs from handshake message by ssl spec + +ccs_enque(cs: ref ChangeCipherSpec, ctx: ref Context) +{ + p := ref Protocol.pChangeCipherSpec(cs); + + protocol_write(p, ctx); + + record_write_queue(ctx); + ctx.out_queue.data = nil; +} + +handshake_enque(h: ref Handshake, ctx: ref Context) +{ + p := ref Protocol.pHandshake(h); + + protocol_write(p, ctx); +} + +protocol_write(p: ref Protocol, ctx: ref Context) +{ + record_version := SSL_VERSION_2_0; + if(ctx.status & SSL3_RECORD) + record_version = SSL_VERSION_3_0; + (r, e) := p.encode(record_version); + if(e != "") { + if(SSL_DEBUG) + log("ssl3: protocol_write: " + e); + exit; + } + + # Note: only for sslv3 + if((ctx.status&SSL2_HANDSHAKE) && (ctx.status&SSL3_HANDSHAKE)) { + if(ctx.state == STATE_HELLO_REQUEST) { + e = update_handshake_hash(ctx, r); + if(e != "") { + if(SSL_DEBUG) + log("ssl3: protocol_write: " + e); + exit; + } + } + } + if((ctx.status&SSL3_HANDSHAKE) && (r.content_type == SSL_HANDSHAKE)) { + e = update_handshake_hash(ctx, r); + if(e != "") { + if(SSL_DEBUG) + log("ssl3: protocol_write: " + e); + exit; + } + } + + ctx.out_queue.data = r :: ctx.out_queue.data; +} + +#feed_data(ctx: ref Context, a: array of byte, n: int): int +#{ +# +#} + +# FIFO +record_write_queue(ctx: ref Context) +{ + write_queue : list of ref Record; + + wq := ctx.out_queue.data; + while(wq != nil) { + write_queue = hd wq :: write_queue; + wq = tl wq; + } + + wq = write_queue; + while(wq != nil) { + record_write(hd wq, ctx); + wq = tl wq; + } +} + +# Possible combinations are v2 only, v3 only and both (undetermined). The v2 only must be +# v2 handshake and v2 record layer. The v3 only must be v3 handshake and v3 record layer. +# If both v2 and v3 are supported, it may be v2 handshake and v2 record layer, or v3 +# handshake and v3 record layer, or v2 handshake and v3 record layer. In the case of +# both, the client should send a v2 client hello message with handshake protocol version v3. + +do_protocol(ctx: ref Context): string +{ + r: ref Record; + in: ref Protocol; + e: string = nil; + + while(ctx.state != STATE_EXIT) { + + if(SSL_DEBUG) + log("ssl3: state = " + state_info(ctx)); + + # init a new handshake + if(ctx.state == STATE_HELLO_REQUEST) { + # v2 and v3 + if((ctx.status&SSL2_HANDSHAKE) && (ctx.status&SSL3_HANDSHAKE)) { + ch := ref V2Handshake.ClientHello( + SSL_VERSION_3_0, + v3tov2specs(ctx.local_info.suites), + ctx.session.session_id, + ctx.client_random + ); + v2handshake_enque(ch, ctx); + in = ref Protocol.pV2Handshake(ch); + } + # v3 only + else if(ctx.status&SSL3_HANDSHAKE) { + in = ref Protocol.pHandshake(ref Handshake.HelloRequest()); + } + # v2 only + else if(ctx.status&SSL2_HANDSHAKE) { + ch := ref V2Handshake.ClientHello( + SSL_VERSION_2_0, + v3tov2specs(ctx.local_info.suites), + ctx.session.session_id, + ctx.client_random[32-SSL2_CHALLENGE_LENGTH:32] + ); + v2handshake_enque(ch, ctx); + in = ref Protocol.pV2Handshake(ch); + } + # unknown version + else { + e = "unknown ssl device version"; + fatal(SSL_CLOSE_NOTIFY, "ssl3: " + e, ctx); + continue; + } + } + + if(in == nil) { + (r, in, e) = protocol_read(ctx); + if(e != "") { + fatal(SSL_CLOSE_NOTIFY, "ssl3: " + e, ctx); + continue; + } + if(SSL_DEBUG) + log("ssl3: protocol_read: ------\n" + in.tostring()); + } + + pick p := in { + pAlert => + do_alert(p.alert, ctx); + + pChangeCipherSpec => + if(ctx.state != STATE_CHANGE_CIPHER_SPEC) { + e += "ChangeCipherSpec"; + break; + } + do_change_cipher_spec(ctx); + + pHandshake => + if(!(ctx.status & SSL3_HANDSHAKE)) { + e = "Wrong Handshake"; + break; + } + if((ctx.status & SSL3_RECORD) && + (ctx.state == SSL2_STATE_SERVER_HELLO)) { + ctx.state = STATE_SERVER_HELLO; + ctx.status &= ~SSL2_HANDSHAKE; + } + e = do_handshake(p.handshake, ctx); + + pV2Handshake => + if(ctx.state != STATE_HELLO_REQUEST) { + if(!(ctx.status & SSL2_HANDSHAKE)) { + e = "Wrong Handshake"; + break; + } + e = do_v2handshake(p.handshake, ctx); + } + else + ctx.state = SSL2_STATE_SERVER_HELLO; + + + * => + e = "unknown protocol message"; + } + + if(e != nil) { + e = "do_protocol: wrong protocol side or protocol message: " + e; + fatal(SSL_UNEXPECTED_MESSAGE, e, ctx); + } + + in = nil; + + record_write_queue(ctx); + ctx.out_queue.data = nil; + } + + return e; +} + +state_info(ctx: ref Context): string +{ + info: string; + + if(ctx.status & SSL3_RECORD) + info = "\n\tRecord Version 3: "; + else + info = "\n\tRecord Version 2: "; + + if(ctx.status & SSL2_HANDSHAKE) { + + if(ctx.status & SSL3_HANDSHAKE) { + info += "\n\tHandshake Version Undetermined: Client Hello"; + } + else { + info += "\n\tHandshake Version 2: "; + + case ctx.state { + SSL2_STATE_CLIENT_HELLO => + info += "Client Hello"; + SSL2_STATE_SERVER_HELLO => + info += "Server Hello"; + SSL2_STATE_CLIENT_MASTER_KEY => + info += "Client Master Key"; + SSL2_STATE_SERVER_VERIFY => + info += "Server Verify"; + SSL2_STATE_REQUEST_CERTIFICATE => + info += "Request Certificate"; + SSL2_STATE_CLIENT_CERTIFICATE => + info += "Client Certificate"; + SSL2_STATE_CLIENT_FINISHED => + info += "Client Finished"; + SSL2_STATE_SERVER_FINISHED => + info += "Server Finished"; + SSL2_STATE_ERROR => + info += "Error"; + } + } + } + else { + info = "\n\tHandshake Version 3: "; + + case ctx.state { + STATE_EXIT => + info += "Exit"; + + STATE_CHANGE_CIPHER_SPEC => + info += "Change Cipher Spec"; + + STATE_HELLO_REQUEST => + info += "Hello Request"; + + STATE_CLIENT_HELLO => + info += "Client Hello"; + + STATE_SERVER_HELLO => + info += "Server Hello"; + + STATE_CLIENT_KEY_EXCHANGE => + info += "Client Key Exchange"; + + STATE_SERVER_KEY_EXCHANGE => + info += "Server Key Exchange"; + + STATE_SERVER_HELLO_DONE => + info += "Server Hello Done"; + + STATE_CLIENT_CERTIFICATE => + info += "Client Certificate"; + + STATE_SERVER_CERTIFICATE => + info += "Server Certificate"; + + STATE_CERTIFICATE_VERIFY => + info += "Certificate Verify"; + + STATE_FINISHED => + info += "Finished"; + } + } + + if(ctx.status & CLIENT_AUTH) + info += ": Client Auth"; + if(ctx.status & CERT_REQUEST) + info += ": Cert Request"; + if(ctx.status & CERT_SENT) + info += ": Cert Sent"; + if(ctx.status & CERT_RECEIVED) + info += ": Cert Received"; + + return info; +} + +reset_client_random(ctx: ref Context) +{ + ctx.client_random[0:] = int_encode(ctx.session.connection_time, 4); + ctx.client_random[4:] = random->randombuf(Random->NotQuiteRandom, 28); +} + +reset_server_random(ctx: ref Context) +{ + ctx.server_random[0:] = int_encode(ctx.session.connection_time, 4); + ctx.server_random[4:] = random->randombuf(Random->NotQuiteRandom, 28); +} + +update_handshake_hash(ctx: ref Context, r: ref Record): string +{ + err := ""; + + ctx.sha_state = keyring->sha1(r.data, len r.data, nil, ctx.sha_state); + ctx.md5_state = keyring->md5(r.data, len r.data, nil, ctx.md5_state); + if(ctx.sha_state == nil || ctx.md5_state == nil) + err = "update handshake hash failed"; + + # if(SSL_DEBUG) + # log("ssl3: update_handshake_hash\n\tmessage_data =\n\t\t" + bastr(r.data) + "\n"); + + return err; +} + +# Note: +# this depends on the record protocol +protocol_read(ctx: ref Context): (ref Record, ref Protocol, string) +{ + p: ref Protocol; + r: ref Record; + e: string; + + vers := SSL_VERSION_2_0; + if(ctx.status & SSL3_RECORD) + vers = SSL_VERSION_3_0; + if(ctx.status & USE_DEVSSL) + (r, e) = devssl_read(ctx); + else + (r, e) = record_read(ctx); + if(e != "") + return (nil, nil, e); + + (p, e) = Protocol.decode(r, ctx); + if(e != "") + return (r, nil, e); + + return (r, p, nil); +} + +# Alert messages with a level of fatal result in the immediate +# termination of the connection and zero out session. + +do_alert(a: ref Alert, ctx: ref Context) +{ + case a.level { + SSL_FATAL => + + case a.description { + SSL_UNEXPECTED_MESSAGE => + + # should never be observed in communication + # between proper implementations. + break; + + SSL_HANDSHAKE_FAILURE => + + # unable to negotiate an acceptable set of security + # parameters given the options available. + break; + + * => + break; + } + + ctx.session.session_id = nil; + ctx.state = STATE_EXIT; + + SSL_WARNING => + + case a.description { + SSL_CLOSE_NOTIFY => + + if(SSL_DEBUG) + log("ssl3: do_alert SSL_WARNING:SSL_CLOSE_NOTIFY\n"); + # notifies the recipient that the sender will not + # send any more messages on this connection. + + ctx.state = STATE_EXIT; + fatal(SSL_CLOSE_NOTIFY, "ssl3: response close notify", ctx); + + SSL_NO_CERTIFICATE => + + # A no_certificate alert message may be sent in + # response to a certification request if no + # appropriate certificate is available. + + if(ctx.state == STATE_CLIENT_CERTIFICATE) { + hm := ref Handshake.Certificate(ctx.local_info.certs); + handshake_enque(hm, ctx); + } + + SSL_BAD_CERTIFICATE or + + # A certificate was corrupt, contained signatures + # that did not verify correctly, etc. + + SSL_UNSUPPORTED_CERTIFICATE or + + # A certificate was of an unsupported type. + + SSL_CERTIFICATE_REVOKED or + + # A certificate was revoked by its signer. + + SSL_CERTIFICATE_EXPIRED or + + # A certificate has expired or is not currently + # valid. + + SSL_CERTIFICATE_UNKNOWN => + + # Some other (unspecified) issue arose in + # processing the certificate, rendering it + # unacceptable. + break; + + * => + ctx.session.session_id = nil; + fatal(SSL_ILLEGAL_PARAMETER, "ssl3: unknown alert description", ctx); + } + + * => + ctx.session.session_id = nil; + fatal(SSL_ILLEGAL_PARAMETER, "ssl3: unknown alert level received", ctx); + } +} + +# notify the receiving party that subsequent records will +# be protected under the just-negotiated CipherSpec and keys. + +do_change_cipher_spec(ctx: ref Context) +{ + # calculate and set new keys + if(!(ctx.status & IN_READY)) { + e := set_in_queue(ctx); + if(e != "") { + fatal(SSL_CLOSE_NOTIFY, "do_change_cipher_spec: setup new cipher failed", ctx); + return; + } + ctx.status |= IN_READY; + + if(SSL_DEBUG) + log("ssl3: set in cipher done\n"); + } + + ctx.state = STATE_FINISHED; +} + + +# process and advance handshake messages, update internal stack and switch to next +# expected state(s). + +do_handshake(handshake: ref Handshake, ctx: ref Context) : string +{ + e := ""; + + pick h := handshake { + HelloRequest => + if(!(ctx.status & CLIENT_SIDE) || ctx.state != STATE_HELLO_REQUEST) { + e = "HelloRequest"; + break; + } + do_hello_request(ctx); + + ClientHello => + if((ctx.status & CLIENT_SIDE) || ctx.state != STATE_CLIENT_HELLO) { + e = "ClientHello"; + break; + } + do_client_hello(h, ctx); + + ServerHello => + if(!(ctx.status & CLIENT_SIDE) || ctx.state != STATE_SERVER_HELLO) { + e = "ServerHello"; + break; + } + do_server_hello(h, ctx); + + ClientKeyExchange => + if((ctx.status & CLIENT_SIDE) || ctx.state != STATE_CLIENT_KEY_EXCHANGE) { + e = "ClientKeyExchange"; + break; + } + do_client_keyex(h, ctx); + + ServerKeyExchange => + if(!(ctx.status & CLIENT_SIDE) || + (ctx.state != STATE_SERVER_KEY_EXCHANGE && ctx.state != STATE_SERVER_HELLO_DONE)) { + e = "ServerKeyExchange"; + break; + } + do_server_keyex(h, ctx); + + ServerHelloDone => + # diff from SSLRef, to support variant impl + if(!(ctx.status & CLIENT_SIDE) || + (ctx.state != STATE_SERVER_HELLO_DONE && ctx.state != STATE_SERVER_KEY_EXCHANGE)) { + e = "ServerHelloDone"; + break; + } + do_server_done(ctx); + + Certificate => + if(ctx.status & CLIENT_SIDE) { + if(ctx.state != STATE_SERVER_CERTIFICATE) { + e = "ServerCertificate"; + break; + } + do_server_cert(h, ctx); + } + else { + if(ctx.state != STATE_CLIENT_CERTIFICATE) { + e = "ClientCertificate"; + break; + } + do_client_cert(h, ctx); # server_side + } + + CertificateRequest => + if(!(ctx.status & CLIENT_SIDE) || ctx.state != STATE_SERVER_HELLO_DONE + || ctx.state != STATE_SERVER_KEY_EXCHANGE) { + e = "CertificateRequest"; + break; + } + do_cert_request(h, ctx); + + CertificateVerify => + if((ctx.status & CLIENT_SIDE) || ctx.state != STATE_CERTIFICATE_VERIFY) { + e = "CertificateVerify"; + break; + } + do_cert_verify(h, ctx); + + Finished => + if(ctx.status & CLIENT_SIDE) { + if(ctx.state != STATE_FINISHED) { + e = "ClientFinished"; + break; + } + do_finished(SSL_CLIENT_SENDER, ctx); + } + else { + if(ctx.state != STATE_FINISHED) { + e = "ServerFinished"; + break; + } + do_finished(SSL_SERVER_SENDER, ctx); + } + + * => + e = "unknown handshake message"; + } + + if(e != nil) + e = "do_handshake: " + e; + + return e; +} + +# [client side] +# The hello request message may be sent by server at any time, but will be ignored by +# the client if the handshake protocol is already underway. It is simple notification +# that the client should begin the negotiation process anew by sending a client hello +# message. + +do_hello_request(ctx: ref Context) +{ + # start from new handshake digest state + ctx.sha_state = ctx.md5_state = nil; + + # Note: + # sending ctx.local_info.suites instead of ctx.session.suite, + # if session is resumable by server, ctx.session.suite will be used. + handshake_enque( + ref Handshake.ClientHello( + ctx.session.version, + ctx.client_random, + ctx.session.session_id, + ctx.local_info.suites, + ctx.local_info.comprs + ), + ctx + ); + + ctx.state = STATE_SERVER_HELLO; +} + +# [client side] +# Processes the received server hello handshake message and determines if the session +# is resumable. (The client sends a client hello using the session id of the session +# to be resumed. The server then checks its session cache for a match. If a match is +# FOUND, and the server is WILLING to re-establish the connection under the specified +# session state, it will send a server hello with the SAME session id value.) If the +# session is resumed, at this point both client and server must send change cipher +# spec messages. If the session is not resumable, the client and server perform +# a full handshake. (On the server side, if a session id match is not found, the +# server generates a new session id or if the server is not willing to resume, the +# server uses a null session id). + +do_server_hello(hm: ref Handshake.ServerHello, ctx: ref Context) +{ + # trying to resume + if(bytes_cmp(ctx.session.session_id, hm.session_id) == 0) { + + if(SSL_DEBUG) + log("ssl3: session resumed\n"); + + ctx.status |= SESSION_RESUMABLE; + # avoid version attack + if(ctx.session.version[0] != hm.version[0] || + ctx.session.version[1] != hm.version[1]) { + fatal(SSL_CLOSE_NOTIFY, "do_server_hello: version mismatch", ctx); + return; + } + + ctx.server_random = hm.random; + + # uses the retrieved session suite by server (should be same by client) + (ciph, keyx, sign, e) + := suite_to_spec(hm.suite, SSL3_Suites); + if(e != nil) { + fatal(SSL_UNEXPECTED_MESSAGE, "server hello: suite not found", ctx); + return; + } + ctx.sel_ciph = ciph; + ctx.sel_keyx = keyx; + ctx.sel_sign = sign; + ctx.sel_cmpr = int ctx.session.compression; # not supported by ssl3 yet + + # calculate keys + (ctx.cw_mac, ctx.sw_mac, ctx.cw_key, ctx.sw_key, ctx.cw_IV, ctx.sw_IV) + = calc_keys(ctx.sel_ciph, ctx.session.master_secret, + ctx.client_random, ctx.server_random); + + + ctx.state = STATE_CHANGE_CIPHER_SPEC; + } + else { + ctx.status &= ~SESSION_RESUMABLE; + + # On the server side, if a session id match is not found, the + # server generates a new session id or if the server is not willing + # to resume, the server uses an empty session id and cannot be + # cached by both client and server. + + ctx.session.session_id = hm.session_id; + ctx.session.version = hm.version; + ctx.server_random = hm.random; + + if(SSL_DEBUG) + log("ssl3: do_server_hello:\n\tselected cipher suite =\n\t\t" + + cipher_suite_info(hm.suite, SSL3_Suites) + "\n"); + + (ciph, keyx, sign, e) := suite_to_spec(hm.suite, SSL3_Suites); + if(e != nil) { + fatal(SSL_UNEXPECTED_MESSAGE, "server hello: suite not found", ctx); + return; + } + + ctx.sel_ciph = ciph; + ctx.sel_keyx = keyx; + ctx.sel_sign = sign; + ctx.sel_cmpr = int hm.compression; # not supported by ssl3 yet + + # next state is determined by selected key exchange and signature methods + # the ctx.sel_keyx and ctx.sel_sign are completed by the following handshake + # Certificate and/or ServerKeyExchange + + if(tagof ctx.sel_keyx == tagof KeyExAlg.DH && + tagof ctx.sel_sign == tagof SigAlg.anon) + ctx.state = STATE_SERVER_KEY_EXCHANGE; + else + ctx.state = STATE_SERVER_CERTIFICATE; + } +} + +# [client side] +# Processes the received server key exchange message. The server key exchange message +# is sent by the server if it has no certificate, has a certificate only used for +# signing, or FORTEZZA KEA key exchange is used. + +do_server_keyex(hm: ref Handshake.ServerKeyExchange, ctx: ref Context) +{ + # install exchange keys sent by server, this may require public key + # retrieved from certificate sent by Handshake.Certificate message + + (err, i) := install_server_xkey(hm.xkey, ctx.sel_keyx); + if(err == "") + err = verify_server_xkey(ctx.client_random, ctx.server_random, hm.xkey, i, ctx.sel_sign); + + if(err == "") + ctx.state = STATE_SERVER_HELLO_DONE; + else + fatal(SSL_HANDSHAKE_FAILURE, "do_server_keyex: " + err, ctx); +} + +# [client side] +# Processes the received server hello done message by verifying that the server +# provided a valid certificate if required and checking that the server hello +# parameters are acceptable. + +do_server_done(ctx: ref Context) +{ + # On client side, optionally send client cert chain if client_auth + # is required by the server. The server may drop the connection, + # if it does not receive client certificate in the following + # Handshake.ClientCertificate message + if(ctx.status & CLIENT_AUTH) { + if(ctx.local_info.certs != nil) { + handshake_enque( + ref Handshake.Certificate(ctx.local_info.certs), + ctx + ); + ctx.status |= CERT_SENT; + } + else { + alert_enque( + ref Alert(SSL_WARNING, SSL_NO_CERTIFICATE), + ctx + ); + } + } + + # calculate premaster secrect, client exchange keys and update ref KeyExAlg + # of the client side + (x, pm, e) := calc_client_xkey(ctx.sel_keyx); + if(e != "") { + fatal(SSL_HANDSHAKE_FAILURE, e, ctx); + return; + } + handshake_enque(ref Handshake.ClientKeyExchange(x), ctx); + + ms := calc_master_secret(pm, ctx.client_random, ctx.server_random); + if(ms == nil) { + fatal(SSL_HANDSHAKE_FAILURE, "server hello done: calc master secret failed", ctx); + return; + } + # ctx.premaster_secret = pm; + ctx.session.master_secret = ms; + + # sending certificate verifiy message if the client auth is required + # and client certificate has been sent, + if(ctx.status & CERT_SENT) { + sig : array of byte; + (md5_hash, sha_hash) + := calc_finished(nil, ctx.session.master_secret, ctx.sha_state, ctx.md5_state); + # check type of client cert being sent + pick sk := ctx.local_info.sk { + RSA => + hashes := array [36] of byte; + hashes[0:] = md5_hash; + hashes[16:] = sha_hash; + #(e, sig) = pkcs->rsa_sign(hashes, sk, PKCS->MD5_WithRSAEncryption); + DSS => + #(e, sig) = pkcs->dss_sign(sha_hash, sk); + * => + e = "unknown sign"; + } + if(e != "") { + fatal(SSL_HANDSHAKE_FAILURE, "server hello done: sign cert verify failed", ctx); + return; + } + handshake_enque(ref Handshake.CertificateVerify(sig), ctx); + } + + ccs_enque(ref ChangeCipherSpec(1), ctx); + (ctx.cw_mac, ctx.sw_mac, ctx.cw_key, ctx.sw_key, ctx.cw_IV, ctx.sw_IV) + = calc_keys(ctx.sel_ciph, ctx.session.master_secret, + ctx.client_random, ctx.server_random); + + # set cipher on write channel + e = set_out_queue(ctx); + if(e != nil) { + fatal(SSL_HANDSHAKE_FAILURE, "do_server_done: " + e, ctx); + return; + } + ctx.status |= OUT_READY; + + if(SSL_DEBUG) + log("ssl3: set out cipher done\n"); + (mh, sh) := calc_finished(SSL_CLIENT_SENDER, ctx.session.master_secret, + ctx.sha_state, ctx.md5_state); +# sending out the Finished msg causes MS https servers to hangup +#sys->print("RETURNING FROM DO_SERVER_DONE\n"); +#return; + handshake_enque(ref Handshake.Finished(mh, sh), ctx); + + ctx.state = STATE_CHANGE_CIPHER_SPEC; +} + +# [client side] +# Process the received certificate message. +# Note: +# according to current US export law, RSA moduli larger than 512 bits +# may not be used for key exchange in software exported from US. With +# this message, larger RSA keys may be used as signature only +# certificates to sign temporary shorter RSA keys for key exchange. + +do_server_cert(hm: ref Handshake.Certificate, ctx: ref Context) +{ + if(hm.cert_list == nil) { + fatal(SSL_UNEXPECTED_MESSAGE, "nil peer certificate", ctx); + return; + } + + # server's certificate is the last one in the chain (reverse required) + cl := hm.cert_list; + ctx.session.peer_certs = nil; + while(cl != nil) { + ctx.session.peer_certs = hd cl::ctx.session.peer_certs; + cl = tl cl; + } + + # TODO: verify certificate chain + # check if in the acceptable dnlist + # ctx.sel_keyx.peer_pk = x509->verify_chain(ctx.session.peer_certs); + if(SSL_DEBUG) + log("ssl3: number certificates got: " + string len ctx.session.peer_certs); + peer_cert := hd ctx.session.peer_certs; + (e, signed) := x509->Signed.decode(peer_cert); + if(e != "") { + if(SSL_DEBUG) + log("ss3: server certificate: " + e); + fatal(SSL_HANDSHAKE_FAILURE, "server certificate: " + e, ctx); + return; + } + + srv_cert: ref Certificate; + (e, srv_cert) = x509->Certificate.decode(signed.tobe_signed); + if(e != "") { + if(SSL_DEBUG) + log("ss3: server certificate: " + e); + fatal(SSL_HANDSHAKE_FAILURE, "server certificate: " + e, ctx); + return; + } + if(SSL_DEBUG) + log("ssl3: " + srv_cert.tostring()); + + # extract and determine byte of user certificate + id: int; + peer_pk: ref X509->PublicKey; + (e, id, peer_pk) = srv_cert.subject_pkinfo.getPublicKey(); + if(e != "") { + if(SSL_DEBUG) + log("ss3: server certificate: " + e); + fatal(SSL_HANDSHAKE_FAILURE, "server certificate:" + e, ctx); + return; + } + + pick key := peer_pk { + RSA => + # TODO: to allow checking X509v3 KeyUsage extension + if((0 && key.pk.modulus.bits() > 512 && ctx.sel_ciph.is_exportable) + || id == PKCS->id_pkcs_md2WithRSAEncryption + || id == PKCS->id_pkcs_md4WithRSAEncryption + || id == PKCS->id_pkcs_md5WithRSAEncryption) { + pick sign := ctx.sel_sign { + anon => + break; + RSA => + break; + * => + # error + } + if(ctx.local_info.sk == nil) + ctx.sel_sign = ref SigAlg.RSA(nil, key.pk); + else { + pick mysk := ctx.local_info.sk { + RSA => + ctx.sel_sign = ref SigAlg.RSA(mysk.sk, key.pk); + * => + ctx.sel_sign = ref SigAlg.RSA(nil, key.pk); + } + } + # key exchange may be tmp RSA, emhemeral DH depending on cipher suite + ctx.state = STATE_SERVER_KEY_EXCHANGE; + } + # TODO: allow id == PKCS->id_rsa + else if(id == PKCS->id_pkcs_rsaEncryption) { + pick sign := ctx.sel_sign { + anon => + break; + * => + # error + } + ctx.sel_sign = ref SigAlg.anon(); + pick keyx := ctx.sel_keyx { + RSA => + keyx.peer_pk = key.pk; + * => + # error + } + ctx.state = STATE_SERVER_HELLO_DONE; + } + else { + # error + } + DSS => + pick sign := ctx.sel_sign { + DSS => + sign.peer_pk = key.pk; + break; + * => + # error + } + # should be key exchagne such as emhemeral DH + ctx.state = STATE_SERVER_KEY_EXCHANGE; + DH => + # fixed DH signed in certificate either by RSA or DSS??? + pick keyx := ctx.sel_keyx { + DH => + keyx.peer_pk = key.pk; + * => + # error + } + ctx.state = STATE_SERVER_KEY_EXCHANGE; + } + + if(e != nil) { + fatal(SSL_HANDSHAKE_FAILURE, "do_server_cert: " + e, ctx); + return; + } +} + +# [client side] +# Processes certificate request message. A non-anonymous server can optionally +# request a certificate from the client, if appropriate for the selected cipher +# suite It is a fatal handshake failure alert for an anonymous server to +# request client identification. + +# TODO: use another module to do x509 certs, lookup and matching rules + +do_cert_request(hm: ref Handshake.CertificateRequest, ctx: ref Context) +{ + found := 0; + for(i := 0; i < len hm.cert_types; i++) { + if(ctx.local_info.root_type == int hm.cert_types[i]) { + found = 1; + break; + } + } + if(!found) { + fatal(SSL_HANDSHAKE_FAILURE, "do_cert_request: no required type of cert", ctx); + return; + } + if(dn_cmp(ctx.local_info.dns, hm.dn_list) < 0) { + fatal(SSL_HANDSHAKE_FAILURE, "do_cert_request: no required dn", ctx); + return; + } + if(ctx.session.peer_certs == nil) { + fatal(SSL_NO_CERTIFICATE, "certificate request: no peer certificates", ctx); + return; + } + + ctx.status |= CLIENT_AUTH; +} + +dn_cmp(a, b: list of array of byte): int +{ + return -1; +} + +# [server side] +# Process client hello message. + +do_client_hello(hm: ref Handshake.ClientHello, ctx: ref Context) +{ + sndm : ref Handshake; + e : string; + + if(hm.version[0] != SSL_VERSION_3_0[0] || hm.version[1] != SSL_VERSION_3_0[1]) { + fatal(SSL_UNEXPECTED_MESSAGE, "client hello: version mismatch", ctx); + return; + } + # else SSL_VERSION_2_0 + + if(hm.session_id != nil) { # trying to resume + if(ctx.status & SESSION_RESUMABLE) { + s := sslsession->get_session_byid(hm.session_id); + if(s == nil) { + fatal(SSL_UNEXPECTED_MESSAGE, "client hello: retrieve nil session", ctx); + return; + } + + if(s.version[0] != hm.version[0] || s.version[1] != hm.version[1]) { + # avoid version attack + fatal(SSL_UNEXPECTED_MESSAGE, "client hello: protocol mismatch", ctx); + return; + } + + reset_server_random(ctx); + ctx.client_random = hm.random; + + sndm = ref Handshake.ServerHello(s.version, ctx.server_random, + s.session_id, s.suite, s.compression); + handshake_enque(sndm, ctx); + + ccs_enque(ref ChangeCipherSpec(1), ctx); + # use existing master_secret, calc keys + (ctx.cw_mac, ctx.sw_mac, ctx.cw_key, ctx.sw_key, ctx.cw_IV, ctx.sw_IV) + = calc_keys(ctx.sel_ciph, ctx.session.master_secret, ctx.client_random, + ctx.server_random); + e = set_out_queue(ctx); + if(e != nil) { + fatal(SSL_CLOSE_NOTIFY, "client hello: setup new cipher failure", ctx); + return; + } + if(SSL_DEBUG) + log("do_client_hello: set out cipher done\n"); + + (md5_hash, sha_hash) := calc_finished(SSL_SERVER_SENDER, + s.master_secret, ctx.sha_state, ctx.md5_state); + + handshake_enque(ref Handshake.Finished(md5_hash, sha_hash), ctx); + + ctx.session = s; + ctx.state = STATE_CHANGE_CIPHER_SPEC; + return; + } + + fatal(SSL_CLOSE_NOTIFY, "client hello: resume session failed", ctx); + return; + } + + ctx.session.version = hm.version; + if(ctx.session.peer != nil) { + ctx.session.session_id = random->randombuf(Random->NotQuiteRandom, 32); + if(ctx.session.session_id == nil) { + fatal(SSL_CLOSE_NOTIFY, "client hello: generate session id failed", ctx); + return; + } + } + + suite := find_cipher_suite(hm.suites, ctx.local_info.suites); + if(suite != nil) { + fatal(SSL_HANDSHAKE_FAILURE, "client hello: find cipher suite failed", ctx); + return; + } + + (ctx.sel_ciph, ctx.sel_keyx, ctx.sel_sign, e) = suite_to_spec(suite, SSL3_Suites); + if(e != nil) { + fatal(SSL_HANDSHAKE_FAILURE, "client hello: find cipher suite failed" + e, ctx); + return; + } + + # not supported by ssl3 yet + ctx.sel_cmpr = int hm.compressions[0]; + ctx.client_random = hm.random; + ctx.sha_state = nil; + ctx.md5_state = nil; + + sndm = ref Handshake.ServerHello(ctx.session.version, ctx.server_random, + ctx.session.session_id, ctx.session.suite, ctx.session.compression); + handshake_enque(sndm, ctx); + + # set up keys based on algorithms + + if(tagof ctx.sel_keyx != tagof KeyExAlg.DH) { + if(ctx.local_info.certs == nil || ctx.local_info.sk == nil) { + fatal(SSL_HANDSHAKE_FAILURE, "client hello: no local cert or key", ctx); + return; + } + + sndm = ref Handshake.Certificate(ctx.local_info.certs); + handshake_enque(sndm, ctx); + } + + if(tagof ctx.sel_keyx != tagof KeyExAlg.RSA || + tagof ctx.sel_sign != tagof SigAlg.anon) { + params, signed_params, xkey: array of byte; + (params, e) = calc_server_xkey(ctx.sel_keyx); + if(e == "") + (signed_params, e) = sign_server_xkey(ctx.sel_sign, params, + ctx.client_random, ctx.server_random); + if(e != "") + + n := len params + 2 + len signed_params; + xkey = array [n] of byte; + xkey[0:] = params; + xkey[len params:] = int_encode(len signed_params, 2); + xkey[len params+2:] = signed_params; + handshake_enque(ref Handshake.ServerKeyExchange(xkey), ctx); + } + + if(ctx.status & CLIENT_AUTH) { + sndm = ref Handshake.CertificateRequest(ctx.local_info.types, ctx.local_info.dns); + handshake_enque(sndm, ctx); + + ctx.status |= CERT_REQUEST; + ctx.state = STATE_CLIENT_CERTIFICATE; + } + else + ctx.state = STATE_CLIENT_KEY_EXCHANGE; + + handshake_enque(ref Handshake.ServerHelloDone(), ctx); +} + +# [server side] +# Process the received client key exchange message. + +do_client_keyex(hm: ref Handshake.ClientKeyExchange, ctx: ref Context) +{ + (premaster_secret, err) := install_client_xkey(hm.xkey, ctx.sel_keyx); + if(err != "") { + fatal(SSL_HANDSHAKE_FAILURE, err, ctx); + return; + } + + ctx.session.master_secret = calc_master_secret(premaster_secret, + ctx.client_random, ctx.server_random); + + if(ctx.status & CERT_RECEIVED) + ctx.state = STATE_CERTIFICATE_VERIFY; + else + ctx.state = STATE_CHANGE_CIPHER_SPEC; +} + +# [server side] +# Process the received certificate message from client. + +do_client_cert(hm: ref Handshake.Certificate, ctx: ref Context) +{ + ctx.session.peer_certs = hm.cert_list; + + # verify cert chain and determine the type of cert + # ctx.peer_info.sk = x509->verify_chain(ctx.session.peer_certs); + # if(ctx.peer_info.key == nil) { + # fatal(SSL_HANDSHAKE_FAILURE, "client certificate: cert verify failed", ctx); + # return; + # } + + ctx.status |= CERT_RECEIVED; + + ctx.state = STATE_CLIENT_KEY_EXCHANGE; +} + +# [server side] +# Process the received certificate verify message from client. + +do_cert_verify(hm: ref Handshake.CertificateVerify, ctx: ref Context) +{ + if(ctx.status & CERT_RECEIVED) { + # exp : array of byte; + (md5_hash, sha_hash) + := calc_finished(nil, ctx.session.master_secret, ctx.sha_state, ctx.md5_state); + ok := 0; + pick upk := ctx.sel_sign { + RSA => + hashes := array [36] of byte; + hashes[0:] = md5_hash; + hashes[16:] = sha_hash; + ok = pkcs->rsa_verify(hashes, hm.signature, upk.peer_pk, PKCS->MD5_WithRSAEncryption); + DSS => + ok = pkcs->dss_verify(sha_hash, hm.signature, upk.peer_pk); + } + + if(!ok) { + fatal(SSL_HANDSHAKE_FAILURE, "do_cert_verify: client auth failed", ctx); + return; + } + } + else { + alert_enque(ref Alert(SSL_WARNING, SSL_NO_CERTIFICATE), ctx); + return; + } + + ctx.state = STATE_CHANGE_CIPHER_SPEC; +} + +# [client or server side] +# Process the received finished message either from client or server. + +do_finished(sender: array of byte, ctx: ref Context) +{ + # setup write_cipher if not yet + if(!(ctx.status & OUT_READY)) { + ccs_enque(ref ChangeCipherSpec(1), ctx); + e := set_out_queue(ctx); + if(e != nil) { + fatal(SSL_CLOSE_NOTIFY, "do_finished: setup new cipher failed", ctx); + return; + } + ctx.status |= OUT_READY; + + if(SSL_DEBUG) + log("ssl3: set out cipher done\n"); + + (md5_hash, sha_hash) := calc_finished(sender, ctx.session.master_secret, + ctx.sha_state, ctx.md5_state); + handshake_enque(ref Handshake.Finished(md5_hash, sha_hash), ctx); + } + + ctx.state = STATE_EXIT; # normal + + # clean read queue + ctx.in_queue.fragment = 0; + + sslsession->add_session(ctx.session); + + if(SSL_DEBUG) + log("ssl3: add session to session database done\n"); +} + +install_client_xkey(a: array of byte, keyx: ref KeyExAlg): (array of byte, string) +{ + pmaster, x : array of byte; + err := ""; + pick kx := keyx { + DH => + i := 0; + (kx.peer_pk, i) = dh_params_decode(a); + if(kx.peer_pk != nil) + pmaster = pkcs->computeDHAgreedKey(kx.sk.param, kx.sk.sk, kx.peer_pk.pk); + else + err = "decode dh params failed"; + RSA => + (err, x) = pkcs->rsa_decrypt(a, kx.sk, 2); + if(err != "" || len x != 48) { + err = "impl error"; + } + else { + if(x[0] != SSL_VERSION_3_0[0] && x[1] != SSL_VERSION_3_0[1]) + err = "version wrong: possible version attack"; + else + pmaster = x[2:]; + } + FORTEZZA_KEA => + err = "Fortezza unsupported"; + } + return (pmaster, err); +} + +install_server_xkey(a: array of byte, keyx: ref KeyExAlg): (string, int) +{ + err := ""; + i := 0; + + pick kx := keyx { + DH => + (kx.peer_pk, i) = dh_params_decode(a); + if(kx.peer_pk != nil) + kx.peer_params = kx.peer_pk.param; + RSA => + peer_tmp: ref RSAParams; + (peer_tmp, i, err) = rsa_params_decode(a); + if(err == "") { + modlen := len peer_tmp.modulus.iptobebytes(); + kx.peer_pk = ref RSAKey(peer_tmp.modulus, modlen, peer_tmp.exponent); + } + FORTEZZA_KEA => + return ("Fortezza unsupported", i); + } + + return (err, i); +} + +verify_server_xkey(crand, srand: array of byte, a: array of byte, i : int, sign: ref SigAlg) + : string +{ + pick sg := sign { + anon => + RSA => + lb := a[0:i]::crand::srand::nil; + (exp, nil, nil) := md5_sha_hash(lb, nil, nil); + ok := pkcs->rsa_verify(exp, a[i+2:], sg.peer_pk, PKCS->MD5_WithRSAEncryption); + if(!ok) + return "RSA sigature verification failed"; + DSS => + lb := a[0:i]::crand::srand::nil; + (exp, nil) := sha_hash(lb, nil); + ok := pkcs->dss_verify(exp, a[i+2:], sg.peer_pk); + if(!ok) + return "DSS sigature verification failed"; + } + + return ""; +} + +calc_client_xkey(keyx: ref KeyExAlg): (array of byte, array of byte, string) +{ + pm, x : array of byte; + err := ""; + pick kx := keyx { + DH => + # generate our own DH keys based on DH params of peer side + (kx.sk, kx.exch_pk) = pkcs->setupDHAgreement(kx.peer_params); + # TODO: need check type of client cert if(!ctx.status & CLIENT_AUTH) + # for implicit case + (x, err) = dh_exchpub_encode(kx.exch_pk); + pm = pkcs->computeDHAgreedKey(kx.sk.param, kx.sk.sk, kx.peer_pk.pk); + RSA => + pm = array [48] of byte; + pm[0:] = SSL_VERSION_3_0; # against version attack + pm[2:] = random->randombuf(Random->NotQuiteRandom, 46); + (err, x) = pkcs->rsa_encrypt(pm, kx.peer_pk, 2); + FORTEZZA_KEA => + err = "Fortezza unsupported"; + } + if(SSL_DEBUG) + log("ssl3: calc_client_xkey: " + bastr(x)); + return (x, pm, err); +} + +calc_server_xkey(keyx: ref KeyExAlg): (array of byte, string) +{ + params: array of byte; + err: string; + pick kx := keyx { + DH => + (kx.sk, kx.exch_pk) = pkcs->setupDHAgreement(kx.params); + (params, err) = dh_params_encode(kx.exch_pk); + RSA => + tmp := ref RSAParams(kx.export_key.modulus, kx.export_key.exponent); + (params, err) = rsa_params_encode(tmp); + + FORTEZZA_KEA => + err = "Fortezza unsupported"; + } + return (params, err); +} + +sign_server_xkey(sign: ref SigAlg, params, cr, sr: array of byte): (array of byte, string) +{ + signed_params: array of byte; + err: string; + pick sg := sign { + anon => + RSA => + lb := cr::sr::params::nil; + (hashes, nil, nil) := md5_sha_hash(lb, nil, nil); + (err, signed_params) = pkcs->rsa_sign(hashes, sg.sk, PKCS->MD5_WithRSAEncryption); + DSS => + lb := cr::sr::params::nil; + (hashes, nil) := sha_hash(lb, nil); + (err, signed_params) = pkcs->dss_sign(hashes, sg.sk); + } + return (signed_params, err); +} + +# ssl encoding of DH exchange public key + +dh_exchpub_encode(dh: ref DHPublicKey): (array of byte, string) +{ + if(dh != nil) { + yb := dh.pk.iptobebytes(); + if(yb != nil) { + n := 2 + len yb; + a := array [n] of byte; + i := 0; + a[i:] = int_encode(len yb, 2); + i += 2; + a[i:] = yb; + return (a, nil); + } + } + return (nil, "nil dh params"); +} + +dh_params_encode(dh: ref DHPublicKey): (array of byte, string) +{ + if(dh != nil && dh.param != nil) { + pb := dh.param.prime.iptobebytes(); + gb := dh.param.base.iptobebytes(); + yb := dh.pk.iptobebytes(); + if(pb != nil && gb != nil && yb != nil) { + n := 6 + len pb + len gb + len yb; + a := array [n] of byte; + i := 0; + a[i:] = int_encode(len pb, 2); + i += 2; + a[i:] = pb; + i += len pb; + a[i:] = int_encode(len gb, 2); + i += 2; + a[i:] = gb; + i += len gb; + a[i:] = int_encode(len yb, 2); + i += 2; + a[i:] = yb; + i += len yb; + return (a, nil); + } + } + return (nil, "nil dh public key"); +} + +dh_params_decode(a: array of byte): (ref DHPublicKey, int) +{ + i := 0; + for(;;) { + n := int_decode(a[i:i+2]); + i += 2; + if(i+n > len a) + break; + p := a[i:i+n]; + i += n; + n = int_decode(a[i:i+2]); + i += 2; + if(i+n > len a) + break; + g := a[i:i+n]; + i += n; + n = int_decode(a[i:i+2]); + i += 2; + if(i+n > len a) + break; + Ys := a[i:i+n]; + i += n; + + if(SSL_DEBUG) + log("ssl3: dh_params_decode:" + "\n\tp =\n\t\t" + bastr(p) + + "\n\tg =\n\t\t" + bastr(g) + "\n\tYs =\n\t\t" + bastr(Ys) + "\n"); + + # don't care privateValueLength + param := ref DHParams(IPint.bebytestoip(p), IPint.bebytestoip(g), 0); + return (ref DHPublicKey(param, IPint.bebytestoip(Ys)), i); + } + return (nil, i); +} + +rsa_params_encode(rsa_params: ref RSAParams): (array of byte, string) +{ + if(rsa_params != nil) { + mod := rsa_params.modulus.iptobebytes(); + exp := rsa_params.exponent.iptobebytes(); + if(mod != nil || exp != nil) { + n := 4 + len mod + len exp; + a := array [n] of byte; + i := 0; + a[i:] = int_encode(len mod, 2); + i += 2; + a[i:] = mod; + i += len mod; + a[i:] = int_encode(len exp, 2); + i += 2; + a[i:] = exp; + i += len exp; + return (a, nil); + } + } + return (nil, "nil rsa params"); +} + +rsa_params_decode(a: array of byte): (ref RSAParams, int, string) +{ + i := 0; + for(;;) { + if(len a < 2) + break; + n := int_decode(a[i:i+2]); + i += 2; + if(n < 0 || n + i > len a) + break; + mod := a[i:i+n]; + i += n; + n = int_decode(a[i:i+2]); + i += 2; + if(n < 0 || n + i > len a) + break; + exp := a[i:i+n]; + i += n; + m := i; + modulus := IPint.bebytestoip(mod); + exponent := IPint.bebytestoip(exp); + + if(SSL_DEBUG) + log("ssl3: decode RSA params\n\tmodulus = \n\t\t" + bastr(mod) + + "\n\texponent = \n\t\t" + bastr(exp) + "\n"); + + if(len a < i+2) + break; + n = int_decode(a[i:i+2]); + i += 2; + if(len a != i + n) + break; + return (ref RSAParams(modulus, exponent), m, nil); + } + return (nil, i, "encoding error"); +} + +# md5_hash MD5(master_secret + pad2 + +# MD5(handshake_messages + Sender + +# master_secret + pad1)); +# sha_hash SHA(master_secret + pad2 + +# SHA(handshake_messages + Sender + +# master_secret + pad1)); +# +# handshake_messages All of the data from all handshake messages +# up to but not including this message. This +# is only data visible at the handshake layer +# and does not include record layer headers. +# +# sender [4], master_secret [48] +# pad1 and pad2, 48 bytes for md5, 40 bytes for sha + +calc_finished(sender, master_secret: array of byte, sha_state, md5_state: ref DigestState) + : (array of byte, array of byte) +{ + sha_value := array [Keyring->SHA1dlen] of byte; + md5_value := array [Keyring->MD5dlen] of byte; + sha_inner := array [Keyring->SHA1dlen] of byte; + md5_inner := array [Keyring->MD5dlen] of byte; + + lb := master_secret::SSL_MAC_PAD1[0:48]::nil; + if(sender != nil) + lb = sender::lb; + (md5_inner, nil) = md5_hash(lb, md5_state); + + lb = master_secret::SSL_MAC_PAD1[0:40]::nil; + if(sender != nil) + lb = sender::lb; + (sha_inner, nil) = sha_hash(lb, sha_state); + + (md5_value, nil) = md5_hash(master_secret::SSL_MAC_PAD2[0:48]::md5_inner::nil, nil); + (sha_value, nil) = sha_hash(master_secret::SSL_MAC_PAD2[0:40]::sha_inner::nil, nil); + + # if(SSL_DEBUG) + # log("ssl3: calc_finished:" + # + "\n\tmd5_inner = \n\t\t" + bastr(md5_inner) + # + "\n\tsha_inner = \n\t\t" + bastr(sha_inner) + # + "\n\tmd5_value = \n\t\t" + bastr(md5_value) + # + "\n\tsha_value = \n\t\t" + bastr(sha_value) + # + "\n"); + + return (md5_value, sha_value); +} + + +# master_secret = +# MD5(premaster_secret + SHA('A' + premaster_secret + +# ClientHello.random + ServerHello.random)) + +# MD5(premaster_secret + SHA('BB' + premaster_secret + +# ClientHello.random + ServerHello.random)) + +# MD5(premaster_secret + SHA('CCC' + premaster_secret + +# ClientHello.random + ServerHello.random)); + +calc_master_secret(pm, cr, sr: array of byte): array of byte +{ + ms := array [48] of byte; + sha_value := array [Keyring->SHA1dlen] of byte; + leader := array [3] of byte; + + j := 0; + lb := pm::cr::sr::nil; + for(i := 1; i <= 3; i++) { + leader[0] = leader[1] = leader[2] = byte (16r40 + i); + (sha_value, nil) = sha_hash(leader[0:i]::lb, nil); + (ms[j:], nil) = md5_hash(pm::sha_value::nil, nil); + j += 16; # Keyring->MD5dlen + } + + if(SSL_DEBUG) + log("ssl3: calc_master_secret:\n\tmaster_secret = \n\t\t" + bastr(ms) + "\n"); + + return ms; +} + + +# key_block = +# MD5(master_secret + SHA(`A' + master_secret + +# ServerHello.random + ClientHello.random)) + +# MD5(master_secret + SHA(`BB' + master_secret + +# ServerHello.random + ClientHello.random)) + +# MD5(master_secret + SHA(`CCC' + master_secret + +# ServerHello.random + ClientHello.random)) + +# [...]; + +calc_key_material(n: int, ms, cr, sr: array of byte): array of byte +{ + key_block := array [n] of byte; + sha_value := array [Keyring->SHA1dlen] of byte; # [20] + md5_value := array [Keyring->MD5dlen] of byte; # [16] + leader := array [10] of byte; + + if(n > 16*(len leader)) { + if(SSL_DEBUG) + log(sys->sprint("ssl3: calc key block: key size too long [%d]", n)); + return nil; + } + + m := n; + i, j, consumed, next : int = 0; + lb := ms::sr::cr::nil; + for(i = 0; m > 0; i++) { + for(j = 0; j <= i; j++) + leader[j] = byte (16r41 + i); # 'A', 'BB', 'CCC', etc. + + (sha_value, nil) = sha_hash(leader[0:i+1]::lb, nil); + (md5_value, nil) = md5_hash(ms::sha_value::nil, nil); + + consumed = Keyring->MD5dlen; + if(m < Keyring->MD5dlen) + consumed = m; + m -= consumed; + + key_block[next:] = md5_value[0:consumed]; + next += consumed; + } + + if(SSL_DEBUG) + log("ssl3: calc_key_material:" + "\n\tkey_block = \n\t\t" + bastr(key_block) + "\n"); + + return key_block; +} + +# Then the key_block is partitioned as follows. +# +# client_write_MAC_secret[CipherSpec.hash_size] +# server_write_MAC_secret[CipherSpec.hash_size] +# client_write_key[CipherSpec.key_material] +# server_write_key[CipherSpec.key_material] +# client_write_IV[CipherSpec.IV_size] /* non-export ciphers */ +# server_write_IV[CipherSpec.IV_size] /* non-export ciphers */ +# +# Any extra key_block material is discarded. +# +# Exportable encryption algorithms (for which +# CipherSpec.is_exportable is true) require additional processing as +# follows to derive their final write keys: +# +# final_client_write_key = MD5(client_write_key + +# ClientHello.random + +# ServerHello.random); +# final_server_write_key = MD5(server_write_key + +# ServerHello.random + +# ClientHello.random); +# +# Exportable encryption algorithms derive their IVs from the random +# messages: +# +# client_write_IV = MD5(ClientHello.random + ServerHello.random); +# server_write_IV = MD5(ServerHello.random + ClientHello.random); + +calc_keys(ciph: ref CipherSpec, ms, cr, sr: array of byte) + : (array of byte, array of byte, array of byte, array of byte, array of byte, array of byte) +{ + cw_mac, sw_mac, cw_key, sw_key, cw_IV, sw_IV: array of byte; + + n := ciph.key_material + ciph.hash_size; + if(ciph.is_exportable == SSL_EXPORT_FALSE) + n += ciph.IV_size; + n *= 2; + + key_block := calc_key_material(n, ms, cr, sr); + + i := 0; + if(ciph.hash_size != 0) { + cw_mac = key_block[i:i+ciph.hash_size]; + i += ciph.hash_size; + sw_mac = key_block[i:i+ciph.hash_size]; + i += ciph.hash_size; + } + + if(ciph.is_exportable == SSL_EXPORT_FALSE) { + if(ciph.key_material != 0) { + cw_key = key_block[i:i+ciph.key_material]; + i += ciph.key_material; + sw_key = key_block[i:i+ciph.key_material]; + i += ciph.key_material; + } + if(ciph.IV_size != 0) { + cw_IV = key_block[i:i+ciph.IV_size]; + i += ciph.IV_size; + sw_IV = key_block[i:i+ciph.IV_size]; + i += ciph.IV_size; + } + } + else { + if(ciph.key_material != 0) { + cw_key = key_block[i:i+ciph.key_material]; + i += ciph.key_material; + sw_key = key_block[i:i+ciph.key_material]; + i += ciph.key_material; + (cw_key, nil) = md5_hash(cw_key::cr::sr::nil, nil); + (sw_key, nil) = md5_hash(sw_key::sr::cr::nil, nil); + } + if(ciph.IV_size != 0) { + (cw_IV, nil) = md5_hash(cr::sr::nil, nil); + (sw_IV, nil) = md5_hash(sr::cr::nil, nil); + } + } + + if(SSL_DEBUG) + log("ssl3: calc_keys:" + + "\n\tclient_write_mac = \n\t\t" + bastr(cw_mac) + + "\n\tserver_write_mac = \n\t\t" + bastr(sw_mac) + + "\n\tclient_write_key = \n\t\t" + bastr(cw_key) + + "\n\tserver_write_key = \n\t\t" + bastr(sw_key) + + "\n\tclient_write_IV = \n\t\t" + bastr(cw_IV) + + "\n\tserver_write_IV = \n\t\t" + bastr(sw_IV) + "\n"); + + return (cw_mac, sw_mac, cw_key, sw_key, cw_IV, sw_IV); +} + +# +# decode protocol message +# +Protocol.decode(r: ref Record, ctx: ref Context): (ref Protocol, string) +{ + p : ref Protocol; + + case r.content_type { + SSL_ALERT => + if(len r.data != 2) + return (nil, "alert decode failed"); + + p = ref Protocol.pAlert(ref Alert(int r.data[0], int r.data[1])); + + SSL_CHANGE_CIPHER_SPEC => + if(len r.data != 1 || r.data[0] != byte 1) + return (nil, "ChangeCipherSpec decode failed"); + + p = ref Protocol.pChangeCipherSpec(ref ChangeCipherSpec(1)); + + SSL_HANDSHAKE => + (hm, e) := Handshake.decode(r.data); + if(e != nil) + return (nil, e); + + pick h := hm { + Finished => + exp_sender := SSL_CLIENT_SENDER; + if(ctx.status & CLIENT_SIDE) + exp_sender = SSL_SERVER_SENDER; + + (md5_hash, sha_hash) := calc_finished(exp_sender, + ctx.session.master_secret, ctx.sha_state, ctx.md5_state); + + if(SSL_DEBUG) + log("ssl3: handshake_decode: finished" + + "\n\texpected_md5_hash = \n\t\t" + bastr(md5_hash) + + "\n\tgot_md5_hash = \n\t\t" + bastr(h.md5_hash) + + "\n\texpected_sha_hash = \n\t\t" + bastr(sha_hash) + + "\n\tgot_sha_hash = \n\t\t" + bastr(h.sha_hash) + "\n"); + + #if(string md5_hash != string h.md5_hash || string sha_hash != string h.sha_hash) + if(bytes_cmp(md5_hash, h.md5_hash) < 0 || bytes_cmp(sha_hash, h.sha_hash) < 0) + return (nil, "finished: sender mismatch"); + + e = update_handshake_hash(ctx, r); + if(e != nil) + return (nil, e); + + CertificateVerify => + + e = update_handshake_hash(ctx, r); + if(e != nil) + return (nil, e); + + * => + e = update_handshake_hash(ctx, r); + if(e != nil) + return (nil, e); + } + + p = ref Protocol.pHandshake(hm); + + SSL_V2HANDSHAKE => + + (hm, e) := V2Handshake.decode(r.data); + if(e != "") + return (nil, e); + + p = ref Protocol.pV2Handshake(hm); + + * => + return (nil, "protocol read: unknown protocol"); + } + + return (p, nil); + +} + + +# encode protocol message and return tuple of data record and error message, +# may be v2 or v3 record depending on vers. + +Protocol.encode(protocol: self ref Protocol, vers: array of byte): (ref Record, string) +{ + r: ref Record; + e: string; + + pick p := protocol { + pAlert => + r = ref Record( + SSL_ALERT, + vers, + array [] of {byte p.alert.level, byte p.alert.description} + ); + + pChangeCipherSpec => + r = ref Record( + SSL_CHANGE_CIPHER_SPEC, + vers, + array [] of {byte p.change_cipher_spec.value} + ); + + pHandshake => + data: array of byte; + (data, e) = p.handshake.encode(); + if(e != "") + break; + r = ref Record( + SSL_HANDSHAKE, + vers, + data + ); + + pV2Handshake => + data: array of byte; + (data, e) = p.handshake.encode(); + if(e != "") + break; + r = ref Record( + SSL_V2HANDSHAKE, + vers, + data + ); + + * => + e = "unknown protocol"; + } + + if(SSL_DEBUG) + log("ssl3: protocol encode\n" + protocol.tostring()); + + return (r, e); +} + +# +# protocol message description +# +Protocol.tostring(protocol: self ref Protocol): string +{ + info : string; + + pick p := protocol { + pAlert => + info = "\tAlert\n" + p.alert.tostring(); + + pChangeCipherSpec => + info = "\tChangeCipherSpec\n"; + + pHandshake => + info = "\tHandshake\n" + p.handshake.tostring(); + + pV2Handshake => + info = "\tV2Handshake\n" + p.handshake.tostring(); + + pApplicationData => + info = "\tApplicationData\n"; + + * => + info = "\tUnknownProtocolType\n"; + } + + return "ssl3: Protocol:\n" + info; +} + +Handshake.decode(buf: array of byte): (ref Handshake, string) +{ + m : ref Handshake; + e : string; + + a := buf[4:]; # ignore msg length + + i := 0; + case int buf[0] { + SSL_HANDSHAKE_HELLO_REQUEST => + m = ref Handshake.HelloRequest(); + + SSL_HANDSHAKE_CLIENT_HELLO => + if(len a < 38) { + e = "client hello: unexpected message"; + break; + } + cv := a[i:i+2]; + i += 2; + rd := a[i:i+32]; + i += 32; + lsi := int a[i++]; + if(len a < 38 + lsi) { + e = "client hello: unexpected message"; + break; + } + sid: array of byte; + if(lsi != 0) { + sid = a[i:i+lsi]; + i += lsi; + } + else + sid = nil; + lcs := int_decode(a[i:i+2]); + i += 2; + if((lcs & 1) || lcs < 2 || len a < 40 + lsi + lcs) { + e = "client hello: unexpected message"; + break; + } + cs := array [lcs/2] of byte; + cs = a[i:i+lcs]; + i += lcs; + lcm := int a[i++]; + cr := a[i:i+lcm]; + i += lcm; + # In the interest of forward compatibility, it is + # permitted for a client hello message to include + # extra data after the compression methods. This + # data must be included in the handshake hashes, + # but otherwise be ignored. + # if(i != len a) { + # e = "client hello: unexpected message"; + # break; + # } + m = ref Handshake.ClientHello(cv, rd, sid, cs, cr); + + SSL_HANDSHAKE_SERVER_HELLO => + if(len a < 38) { + e = "server hello: unexpected message"; + break; + } + sv := a[i:i+2]; + i += 2; + rd := a[i:i+32]; + i += 32; + lsi := int a[i++]; + if(len a < 38 + lsi) { + e = "server hello: unexpected message"; + break; + } + sid : array of byte; + if(lsi != 0) { + sid = a[i:i+lsi]; + i += lsi; + } + else + sid = nil; + cs := a[i:i+2]; + i += 2; + cr := a[i++]; + if(i != len a) { + e = "server hello: unexpected message"; + break; + } + m = ref Handshake.ServerHello(sv, rd, sid, cs, cr); + + SSL_HANDSHAKE_CERTIFICATE => + n := int_decode(a[i:i+3]); + i += 3; + if(len a != n + 3) { + e = "certificate: unexpected message"; + break; + } + cl : list of array of byte; + k : int; + while(i < n) { + k = int_decode(a[i:i+3]); + i += 3; + if(k < 0 || i + k > len a) { + e = "certificate: unexpected message"; + break; + } + cl = a[i:i+k] :: cl; + i += k; + } + if(e != nil) + break; + m = ref Handshake.Certificate(cl); + + SSL_HANDSHAKE_SERVER_KEY_EXCHANGE => + + m = ref Handshake.ServerKeyExchange(a[i:]); + + SSL_HANDSHAKE_CERTIFICATE_REQUEST => + ln := int_decode(a[i:i+2]); + i += 2; + types := a[i:i+ln]; + i += ln; + ln = int_decode(a[i:i+2]); + i += 2; + auths : list of array of byte; + for(j := 0; j < ln; j++) { + ln = int_decode(a[i:i+2]); + i += 2; + auths = a[i:i+ln]::auths; + i += ln; + } + m = ref Handshake.CertificateRequest(types, auths); + + SSL_HANDSHAKE_SERVER_HELLO_DONE => + if(len a != 0) { + e = "server hello done: unexpected message"; + break; + } + m = ref Handshake.ServerHelloDone(); + + SSL_HANDSHAKE_CERTIFICATE_VERIFY => + ln := int_decode(a[i:i+2]); + i +=2; + sig := a[i:]; + i += ln; + if(i != len a) { + e = "certificate verify: unexpected message"; + break; + } + m = ref Handshake.CertificateVerify(sig); + + SSL_HANDSHAKE_CLIENT_KEY_EXCHANGE => + m = ref Handshake.ClientKeyExchange(a); + + SSL_HANDSHAKE_FINISHED => + if(len a != Keyring->MD5dlen + Keyring->SHA1dlen) { # 16+20 + e = "finished: unexpected message"; + break; + } + md5_hash := a[i:i+Keyring->MD5dlen]; + i += Keyring->MD5dlen; + sha_hash := a[i:i+Keyring->SHA1dlen]; + i += Keyring->SHA1dlen; + if(i != len a) { + e = "finished: unexpected message"; + break; + } + m = ref Handshake.Finished(md5_hash, sha_hash); + + * => + e = "unknown message"; + } + + if(e != nil) + return (nil, "Handshake decode: " + e); + + return (m, nil); +} + +Handshake.encode(hm: self ref Handshake): (array of byte, string) +{ + a : array of byte; + n : int; + e : string; + + i := 0; + pick m := hm { + HelloRequest => + a = array [4] of byte; + a[i++] = byte SSL_HANDSHAKE_HELLO_REQUEST; + a[i:] = int_encode(n, 3); + i += 3; + if(i != 4) + e = "hello request: wrong message length"; + + ClientHello => + lsi := len m.session_id; + lcs := len m.suites; + if((lcs &1) || lcs < 2) { + e = "client hello: cipher suites is not multiple of 2 bytes"; + break; + } + lcm := len m.compressions; + n = 38 + lsi + lcs + lcm; # 2+32+1+2+1 + a = array[n+4] of byte; + a[i++] = byte SSL_HANDSHAKE_CLIENT_HELLO; + a[i:] = int_encode(n, 3); + i += 3; + a[i:] = m.version; + i += 2; + a[i:] = m.random; + i += 32; + a[i++] = byte lsi; + if(lsi != 0) { + a[i:] = m.session_id; + i += lsi; + } + a[i:] = int_encode(lcs, 2); + i += 2; + a[i:] = m.suites; # not nil + i += lcs; + a[i++] = byte lcm; + a[i:] = m.compressions; # not nil + i += lcm; + if(i != n+4) + e = "client hello: wrong message length"; + + ServerHello => + lsi := len m.session_id; + n = 38 + lsi; # 2+32+1+2+1 + a = array [n+4] of byte; + a[i++] = byte SSL_HANDSHAKE_SERVER_HELLO; + a[i:] = int_encode(n, 3); + i += 3; + a[i:] = m.version; + i += 2; + a[i:] = m.random; + i += 32; + a[i++] = byte lsi; + if(lsi != 0) { + a[i:] = m.session_id; + i += lsi; + } + a[i:] = m.suite; # should be verified, not nil + i += 2; + a[i++] = m.compression; # should be verified, not nil + if(i != n+4) + e = "server hello: wrong message length"; + + Certificate => + cl := m.cert_list; + while(cl != nil) { + n += 3 + len hd cl; + cl = tl cl; + } + a = array [n+7] of byte; + a[i++] = byte SSL_HANDSHAKE_CERTIFICATE; + a[i:] = int_encode(n+3, 3); # length of record + i += 3; + a[i:] = int_encode(n, 3); # total length of cert chain + i += 3; + cl = m.cert_list; + while(cl != nil) { + a[i:] = int_encode(len hd cl, 3); + i += 3; + a[i:] = hd cl; + i += len hd cl; + cl = tl cl; + } + if(i != n+7) + e = "certificate: wrong message length"; + + ServerKeyExchange => + n = len m.xkey; + a = array [n+4] of byte; + a[i++] = byte SSL_HANDSHAKE_SERVER_KEY_EXCHANGE; + a[i:] = int_encode(n, 3); + i += 3; + a[i:] = m.xkey; + i += len m.xkey; + if(i != n+4) + e = "server key exchange: wrong message length"; + + CertificateRequest => + ntypes := len m.cert_types; + nauths := len m.dn_list; + n = 1 + ntypes; + dl := m.dn_list; + while(dl != nil) { + n += 2 + len hd dl; + dl = tl dl; + } + n += 2; + a = array [n+4] of byte; + a[i++] = byte SSL_HANDSHAKE_CERTIFICATE_REQUEST; + a[i:] = int_encode(n, 3); + i += 3; + a[i++] = byte ntypes; + a[i:] = m.cert_types; + i += ntypes; + a[i:] = int_encode(nauths, 2); + i += 2; + dl = m.dn_list; + while(dl != nil) { + a[i:] = int_encode(len hd dl, 2); + i += 2; + a[i:] = hd dl; + i += len hd dl; + dl = tl dl; + } + if(i != n+4) + e = "certificate request: wrong message length"; + + ServerHelloDone => + n = 0; + a = array[n+4] of byte; + a[i++] = byte SSL_HANDSHAKE_SERVER_HELLO_DONE; + a[i:] = int_encode(0, 3); # message has 0 length + i += 3; + if(i != n+4) + e = "server hello done: wrong message length"; + + CertificateVerify => + n = 2 + len m.signature; + a = array [n+4] of byte; + a[i++] = byte SSL_HANDSHAKE_CERTIFICATE_VERIFY; + a[i:] = int_encode(n, 3); + i += 3; + a[i:] = int_encode(n-2, 2); + i += 2; + a[i:] = m.signature; + i += n-2; + if(i != n+4) + e = "certificate verify: wrong message length"; + + ClientKeyExchange => + n = len m.xkey; + a = array [n+4] of byte; + a[i++] = byte SSL_HANDSHAKE_CLIENT_KEY_EXCHANGE; + a[i:] = int_encode(n, 3); + i += 3; + a[i:] = m.xkey; + i += n; + if(i != n+4) + e = "client key exchange: wrong message length"; + + Finished => + n = len m.md5_hash + len m.sha_hash; + a = array [n+4] of byte; + a[i++] = byte SSL_HANDSHAKE_FINISHED; + a[i:] = int_encode(n, 3); + i += 3; + a[i:] = m.md5_hash; + i += len m.md5_hash; + a[i:] = m.sha_hash; + i += len m.sha_hash; + if(i != n+4) + e = "finished: wrong message length"; + + * => + e = "unknown message"; + } + + if(e != nil) + return (nil, "Handshake encode: " + e); + + return (a, e); +} + +Handshake.tostring(handshake: self ref Handshake): string +{ + info: string; + + pick m := handshake { + HelloRequest => + info = "\tHelloRequest\n"; + + ClientHello => + info = "\tClientHello\n" + + "\tversion = \n\t\t" + bastr(m.version) + "\n" + + "\trandom = \n\t\t" + bastr(m.random) + "\n" + + "\tsession_id = \n\t\t" + bastr(m.session_id) + "\n" + + "\tsuites = \n\t\t" + bastr(m.suites) + "\n" + + "\tcomperssion_methods = \n\t\t" + bastr(m.compressions) +"\n"; + + ServerHello => + info = "\tServerHello\n" + + "\tversion = \n\t\t" + bastr(m.version) + "\n" + + "\trandom = \n\t\t" + bastr(m.random) + "\n" + + "\tsession_id = \n\t\t" + bastr(m.session_id) + "\n" + + "\tsuite = \n\t\t" + bastr(m.suite) + "\n" + + "\tcomperssion_method = \n\t\t" + string m.compression +"\n"; + + Certificate => + info = "\tCertificate\n" + + "\tcert_list = \n\t\t" + lbastr(m.cert_list) + "\n"; + + ServerKeyExchange => + info = "\tServerKeyExchange\n" + + "\txkey = \n\t\t" + bastr(m.xkey) +"\n"; + + CertificateRequest => + info = "\tCertificateRequest\n" + + "\tcert_types = \n\t\t" + bastr(m.cert_types) + "\n" + + "\tdn_list = \n\t\t" + lbastr(m.dn_list) + "\n"; + + ServerHelloDone => + info = "\tServerDone\n"; + + CertificateVerify => + info = "\tCertificateVerify\n" + + "\tsignature = \n\t\t" + bastr(m.signature) + "\n"; + + ClientKeyExchange => + info = "\tClientKeyExchange\n" + + "\txkey = \n\t\t" + bastr(m.xkey) +"\n"; + + Finished => + info = "\tFinished\n" + + "\tmd5_hash = \n\t\t" + bastr(m.md5_hash) + "\n" + + "\tsha_hash = \n\t\t" + bastr(m.sha_hash) + "\n"; + } + + return info; +} + +Alert.tostring(alert: self ref Alert): string +{ + info: string; + + case alert.level { + SSL_WARNING => + info += "\t\twarning: "; + + SSL_FATAL => + info += "\t\tfatal: "; + + * => + info += sys->sprint("unknown alert level[%d]: ", alert.level); + } + + case alert.description { + SSL_CLOSE_NOTIFY => + info += "close notify"; + + SSL_NO_CERTIFICATE => + info += "no certificate"; + + SSL_BAD_CERTIFICATE => + info += "bad certificate"; + + SSL_UNSUPPORTED_CERTIFICATE => + info += "unsupported certificate"; + + SSL_CERTIFICATE_REVOKED => + info += "certificate revoked"; + + SSL_CERTIFICATE_EXPIRED => + info += "certificate expired"; + + SSL_CERTIFICATE_UNKNOWN => + info += "certificate unknown"; + + SSL_UNEXPECTED_MESSAGE => + info += "unexpected message"; + + SSL_BAD_RECORD_MAC => + info += "bad record mac"; + + SSL_DECOMPRESSION_FAILURE => + info += "decompression failure"; + + SSL_HANDSHAKE_FAILURE => + info += "handshake failure"; + + SSL_ILLEGAL_PARAMETER => + info += "illegal parameter"; + + * => + info += sys->sprint("unknown alert description[%d]", alert.description); + } + + return info; +} + +find_cipher_suite(s, suites: array of byte) : array of byte +{ + i, j : int; + a, b : array of byte; + + n := len s; + if((n & 1) || n < 2) + return nil; + + m := len suites; + if((m & 1) || m < 2) + return nil; + + for(i = 0; i < n; ) { + a = s[i:i+2]; + i += 2; + for(j = 0; j < m; ) { + b = suites[j:j+2]; + j += 2; + if(a[0] == b[0] && a[1] == b[1]) + return b; + } + } + + return nil; +} + +# +# cipher suites and specs +# +suite_to_spec(cs: array of byte, cipher_suites: array of array of byte) + : (ref CipherSpec, ref KeyExAlg, ref SigAlg, string) +{ + cip : ref CipherSpec; + kex : ref KeyExAlg; + sig : ref SigAlg; + + n := len cipher_suites; + i : int; + found := array [2] of byte; + for(i = 0; i < n; i++) { + found = cipher_suites[i]; + if(found[0]==cs[0] && found[1]==cs[1]) break; + } + + if(i == n) + return (nil, nil, nil, "fail to find a matched spec"); + + case i { + NULL_WITH_NULL_NULL => + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_NULL_CIPHER, + SSL_STREAM_CIPHER, 0, 0, SSL_NULL_MAC, 0); + kex = ref KeyExAlg.NULL(); + sig = ref SigAlg.anon(); + + RSA_WITH_NULL_MD5 => # sign only certificate + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_NULL_CIPHER, + SSL_STREAM_CIPHER, 0, 0, SSL_MD5, Keyring->MD5dlen); + kex = ref KeyExAlg.RSA(nil, nil, nil); + sig = ref SigAlg.anon(); + + RSA_WITH_NULL_SHA => + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_NULL_CIPHER, + SSL_STREAM_CIPHER, 0, 0, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.RSA(nil, nil, nil); + sig = ref SigAlg.anon(); + + RSA_EXPORT_WITH_RC4_40_MD5 => + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_RC4, + SSL_STREAM_CIPHER, 5, 0, SSL_MD5, Keyring->MD5dlen); + kex = ref KeyExAlg.RSA(nil, nil, nil); + sig = ref SigAlg.anon(); + + RSA_WITH_RC4_128_MD5 => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_RC4, + SSL_STREAM_CIPHER, 16, 0, SSL_MD5, Keyring->MD5dlen); + kex = ref KeyExAlg.RSA(nil, nil, nil); + sig = ref SigAlg.anon(); + + RSA_WITH_RC4_128_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_RC4, + SSL_STREAM_CIPHER, 16, 0, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.RSA(nil, nil, nil); + sig = ref SigAlg.anon(); + + RSA_EXPORT_WITH_RC2_CBC_40_MD5 => + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_RC2_CBC, + SSL_BLOCK_CIPHER, 5, 8, SSL_MD5, Keyring->MD5dlen); + kex = ref KeyExAlg.RSA(nil, nil, nil); + sig = ref SigAlg.RSA(nil, nil); + + RSA_WITH_IDEA_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_IDEA_CBC, + SSL_BLOCK_CIPHER, 16, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.RSA(nil, nil, nil); + sig = ref SigAlg.anon(); + + RSA_EXPORT_WITH_DES40_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_DES_CBC, + SSL_BLOCK_CIPHER, 5, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.RSA(nil, nil, nil); + sig = ref SigAlg.RSA(nil, nil); + + RSA_WITH_DES_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_DES_CBC, + SSL_BLOCK_CIPHER, 8, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.RSA(nil, nil, nil); + sig = ref SigAlg.anon(); + + RSA_WITH_3DES_EDE_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_3DES_EDE_CBC, + SSL_BLOCK_CIPHER, 24, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.RSA(nil, nil, nil); + sig = ref SigAlg.anon(); + + DH_DSS_EXPORT_WITH_DES40_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_DES_CBC, + SSL_BLOCK_CIPHER, 5, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.DSS(nil, nil); + + DH_DSS_WITH_DES_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_DES_CBC, + SSL_BLOCK_CIPHER, 8, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.DSS(nil, nil); + + DH_DSS_WITH_3DES_EDE_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_3DES_EDE_CBC, + SSL_BLOCK_CIPHER, 24, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.DSS(nil, nil); + + DH_RSA_EXPORT_WITH_DES40_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_DES_CBC, + SSL_BLOCK_CIPHER, 5, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.RSA(nil, nil); + + DH_RSA_WITH_DES_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_DES_CBC, + SSL_STREAM_CIPHER, 8, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.RSA(nil, nil); + + DH_RSA_WITH_3DES_EDE_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_3DES_EDE_CBC, + SSL_BLOCK_CIPHER, 24, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.RSA(nil, nil); + + DHE_DSS_EXPORT_WITH_DES40_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_DES_CBC, + SSL_BLOCK_CIPHER, 5, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.DSS(nil, nil); + + DHE_DSS_WITH_DES_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_DES_CBC, + SSL_BLOCK_CIPHER, 8, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.DSS(nil, nil); + + DHE_DSS_WITH_3DES_EDE_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_3DES_EDE_CBC, + SSL_BLOCK_CIPHER, 24, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.DSS(nil, nil); + + DHE_RSA_EXPORT_WITH_DES40_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_DES_CBC, + SSL_BLOCK_CIPHER, 5, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.RSA(nil, nil); + + DHE_RSA_WITH_DES_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_DES_CBC, + SSL_BLOCK_CIPHER, 8, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.RSA(nil, nil); + + DHE_RSA_WITH_3DES_EDE_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_3DES_EDE_CBC, + SSL_BLOCK_CIPHER, 24, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.RSA(nil, nil); + + DH_anon_EXPORT_WITH_RC4_40_MD5 => + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_RC4, + SSL_STREAM_CIPHER, 5, 0, SSL_MD5, Keyring->MD5dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.anon(); + + DH_anon_WITH_RC4_128_MD5 => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_RC4, + SSL_STREAM_CIPHER, 16, 0, SSL_MD5, Keyring->MD5dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.anon(); + + DH_anon_EXPORT_WITH_DES40_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_DES_CBC, + SSL_BLOCK_CIPHER, 5, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.anon(); + + DH_anon_WITH_DES_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_DES_CBC, + SSL_BLOCK_CIPHER, 8, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.anon(); + + DH_anon_WITH_3DES_EDE_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_3DES_EDE_CBC, + SSL_BLOCK_CIPHER, 24, 8, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.DH(nil, nil, nil, nil, nil); + sig = ref SigAlg.anon(); + + FORTEZZA_KEA_WITH_NULL_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_NULL_CIPHER, + SSL_STREAM_CIPHER, 0, 0, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.FORTEZZA_KEA(); + sig = ref SigAlg.FORTEZZA_KEA(); + + FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_FORTEZZA_CBC, + SSL_BLOCK_CIPHER, 0, 0, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.FORTEZZA_KEA(); + sig = ref SigAlg.FORTEZZA_KEA(); + + FORTEZZA_KEA_WITH_RC4_128_SHA => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_RC4, + SSL_STREAM_CIPHER, 16, 0, SSL_SHA, Keyring->SHA1dlen); + kex = ref KeyExAlg.FORTEZZA_KEA(); + sig = ref SigAlg.FORTEZZA_KEA(); + + } + + return (cip, kex, sig, nil); +} + +# +# use suites as default SSL3_Suites +# +cipher_suite_info(cs: array of byte, suites: array of array of byte) : string +{ + tag : string; + + a := array [2] of byte; + n := len suites; + for(i := 0; i < n; i++) { + a = suites[i]; + if(a[0]==cs[0] && a[1]==cs[1]) break; + } + + if(i == n) + return "unknown cipher suite [" + string cs + "]"; + + case i { + NULL_WITH_NULL_NULL => + tag = "NULL_WITH_NULL_NULL"; + + RSA_WITH_NULL_MD5 => + tag = "RSA_WITH_NULL_MD5"; + + RSA_WITH_NULL_SHA => + tag = "RSA_WITH_NULL_SHA"; + + RSA_EXPORT_WITH_RC4_40_MD5 => + tag = "RSA_EXPORT_WITH_RC4_40_MD5"; + + RSA_WITH_RC4_128_MD5 => + tag = "RSA_WITH_RC4_128_MD5"; + + RSA_WITH_RC4_128_SHA => + tag = "RSA_WITH_RC4_128_SHA"; + + RSA_EXPORT_WITH_RC2_CBC_40_MD5 => + tag = "RSA_EXPORT_WITH_RC2_CBC_40_MD5"; + + RSA_WITH_IDEA_CBC_SHA => + tag = "RSA_WITH_IDEA_CBC_SHA"; + + RSA_EXPORT_WITH_DES40_CBC_SHA => + tag ="RSA_EXPORT_WITH_DES40_CBC_SHA"; + + RSA_WITH_DES_CBC_SHA => + tag = "RSA_WITH_DES_CBC_SHA"; + + RSA_WITH_3DES_EDE_CBC_SHA => + tag = "RSA_WITH_3DES_EDE_CBC_SHA"; + + DH_DSS_EXPORT_WITH_DES40_CBC_SHA => + tag = "DH_DSS_EXPORT_WITH_DES40_CBC_SHA"; + + DH_DSS_WITH_DES_CBC_SHA => + tag = "DH_DSS_WITH_DES_CBC_SHA"; + + DH_DSS_WITH_3DES_EDE_CBC_SHA => + tag = "DH_DSS_WITH_3DES_EDE_CBC_SHA"; + + DH_RSA_EXPORT_WITH_DES40_CBC_SHA => + tag = "DH_RSA_EXPORT_WITH_DES40_CBC_SHA"; + + DH_RSA_WITH_DES_CBC_SHA => + tag = "DH_RSA_WITH_DES_CBC_SHA"; + + DH_RSA_WITH_3DES_EDE_CBC_SHA => + tag = "DH_RSA_WITH_3DES_EDE_CBC_SHA"; + + DHE_DSS_EXPORT_WITH_DES40_CBC_SHA => + tag = "DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"; + + DHE_DSS_WITH_DES_CBC_SHA => + tag = "DHE_DSS_WITH_DES_CBC_SHA"; + + DHE_DSS_WITH_3DES_EDE_CBC_SHA => + tag = "DHE_DSS_WITH_3DES_EDE_CBC_SHA"; + + DHE_RSA_EXPORT_WITH_DES40_CBC_SHA => + tag = "DHE_RSA_EXPORT_WITH_DES40_CBC_SHA"; + + DHE_RSA_WITH_DES_CBC_SHA => + tag = "DHE_RSA_WITH_DES_CBC_SHA"; + + DHE_RSA_WITH_3DES_EDE_CBC_SHA => + tag = "DHE_RSA_WITH_3DES_EDE_CBC_SHA"; + + DH_anon_EXPORT_WITH_RC4_40_MD5 => + tag = "DH_anon_EXPORT_WITH_RC4_40_MD5"; + + DH_anon_WITH_RC4_128_MD5 => + tag = "DH_anon_WITH_RC4_128_MD5"; + + DH_anon_EXPORT_WITH_DES40_CBC_SHA => + tag = "DH_anon_EXPORT_WITH_DES40_CBC_SHA"; + + DH_anon_WITH_DES_CBC_SHA => + tag = "DH_anon_WITH_DES_CBC_SHA"; + + DH_anon_WITH_3DES_EDE_CBC_SHA => + tag = "DH_anon_WITH_3DES_EDE_CBC_SHA"; + + FORTEZZA_KEA_WITH_NULL_SHA => + tag = "FORTEZZA_KEA_WITH_NULL_SHA"; + + FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA => + tag = "FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA"; + + FORTEZZA_KEA_WITH_RC4_128_SHA => + tag = "FORTEZZA_KEA_WITH_RC4_128_SHA"; + } + + return "cipher suite = [" + tag + "]"; +} + +################################# +## FOR SSLv2 BACKWARD COMPATIBLE +################################# + +# Protocol Version Codes +SSL2_CLIENT_VERSION := array [] of {byte 0, byte 16r02}; +SSL2_SERVER_VERSION := array [] of {byte 0, byte 16r02}; + +# Protocol Message Codes + +SSL2_MT_ERROR, + SSL2_MT_CLIENT_HELLO, + SSL2_MT_CLIENT_MASTER_KEY, + SSL2_MT_CLIENT_FINISHED, + SSL2_MT_SERVER_HELLO, + SSL2_MT_SERVER_VERIFY, + SSL2_MT_SERVER_FINISHED, + SSL2_MT_REQUEST_CERTIFICATE, + SSL2_MT_CLIENT_CERTIFICATE : con iota; + +# Error Message Codes + +SSL2_PE_NO_CIPHER := array [] of {byte 0, byte 16r01}; +SSL2_PE_NO_CERTIFICATE := array [] of {byte 0, byte 16r02}; +SSL2_PE_BAD_CERTIFICATE := array [] of {byte 0, byte 16r04}; +SSL2_PE_UNSUPPORTED_CERTIFICATE_TYPE := array [] of {byte 0, byte 16r06}; + +# Cipher Kind Values + +SSL2_CK_RC4_128_WITH_MD5, + SSL2_CK_RC4_128_EXPORT40_WITH_MD5, + SSL2_CK_RC2_CBC_128_CBC_WITH_MD5, + SSL2_CK_RC2_CBC_128_CBC_EXPORT40_WITH_MD5, + SSL2_CK_IDEA_128_CBC_WITH_MD5, + SSL2_CK_DES_64_CBC_WITH_MD5, + SSL2_CK_DES_192_EDE3_CBC_WITH_MD5 : con iota; + +SSL2_Cipher_Kinds := array [] of { + SSL2_CK_RC4_128_WITH_MD5 => array [] of {byte 16r01, byte 0, byte 16r80}, + SSL2_CK_RC4_128_EXPORT40_WITH_MD5 => array [] of {byte 16r02, byte 0, byte 16r80}, + SSL2_CK_RC2_CBC_128_CBC_WITH_MD5 => array [] of {byte 16r03, byte 0, byte 16r80}, + SSL2_CK_RC2_CBC_128_CBC_EXPORT40_WITH_MD5 => + array [] of {byte 16r04, byte 0, byte 16r80}, + SSL2_CK_IDEA_128_CBC_WITH_MD5 => array [] of {byte 16r05, byte 0, byte 16r80}, + SSL2_CK_DES_64_CBC_WITH_MD5 => array [] of {byte 16r06, byte 0, byte 16r40}, + SSL2_CK_DES_192_EDE3_CBC_WITH_MD5 => array [] of {byte 16r07, byte 0, byte 16rC0}, +}; + +# Certificate Type Codes + +SSL2_CT_X509_CERTIFICATE : con 16r01; # encode as one byte + +# Authentication Type Codes + +SSL2_AT_MD5_WITH_RSA_ENCRYPTION : con byte 16r01; + +# Upper/Lower Bounds + +SSL2_MAX_MASTER_KEY_LENGTH_IN_BITS : con 256; +SSL2_MAX_SESSION_ID_LENGTH_IN_BYTES : con 16; +SSL2_MIN_RSA_MODULUS_LENGTH_IN_BYTES : con 64; +SSL2_MAX_RECORD_LENGTH_2_BYTE_HEADER : con 32767; +SSL2_MAX_RECORD_LENGTH_3_BYTE_HEADER : con 16383; + +# Handshake Internal State + +SSL2_STATE_CLIENT_HELLO, + SSL2_STATE_SERVER_HELLO, + SSL2_STATE_CLIENT_MASTER_KEY, + SSL2_STATE_SERVER_VERIFY, + SSL2_STATE_REQUEST_CERTIFICATE, + SSL2_STATE_CLIENT_CERTIFICATE, + SSL2_STATE_CLIENT_FINISHED, + SSL2_STATE_SERVER_FINISHED, + SSL2_STATE_ERROR : con iota; + +# The client's challenge to the server for the server to identify itself is a +# (near) arbitary length random. The v3 server will right justify the challenge +# data to become the ClientHello.random data (padding with leading zeros, if +# necessary). If the length of the challenge is greater than 32 bytes, then only +# the last 32 bytes are used. It is legitimate (but not necessary) for a v3 +# server to reject a v2 ClientHello that has fewer than 16 bytes of challenge +# data. + +SSL2_CHALLENGE_LENGTH : con 16; + +V2Handshake: adt { + pick { + Error => + code : array of byte; # [2]; + ClientHello => + version : array of byte; # [2] + cipher_specs : array of byte; # [3] x + session_id : array of byte; + challenge : array of byte; + ServerHello => + session_id_hit : int; + certificate_type : int; + version : array of byte; # [2] + certificate : array of byte; # only user certificate + cipher_specs : array of byte; # [3] x + connection_id : array of byte; + ClientMasterKey => + cipher_kind : array of byte; # [3] + clear_key : array of byte; + encrypt_key : array of byte; + key_arg : array of byte; + ServerVerify => + challenge : array of byte; + RequestCertificate => + authentication_type : int; + certificate_challenge : array of byte; + ClientCertificate => + certificate_type : int; + certificate : array of byte; # only user certificate + response : array of byte; + ClientFinished => + connection_id : array of byte; + ServerFinished => + session_id : array of byte; + } + + encode: fn(hm: self ref V2Handshake): (array of byte, string); + decode: fn(a: array of byte): (ref V2Handshake, string); + tostring: fn(h: self ref V2Handshake): string; +}; + + +V2Handshake.tostring(handshake: self ref V2Handshake): string +{ + info := ""; + + pick m := handshake { + ClientHello => + info += "\tClientHello\n" + + "\tversion = \n\t\t" + bastr(m.version) + "\n" + + "\tcipher_specs = \n\t\t" + bastr(m.cipher_specs) + "\n" + + "\tsession_id = \n\t\t" + bastr(m.session_id) + "\n" + + "\tchallenge = \n\t\t" + bastr(m.challenge) + "\n"; + + ServerHello => + info += "\tServerHello\n" + + "\tsession_id_hit = \n\t\t" + string m.session_id_hit + "\n" + + "\tcertificate_type = \n\t\t" + string m.certificate_type + "\n" + + "\tversion = \n\t\t" + bastr(m.version) + "\n" + + "\tcertificate = \n\t\t" + bastr(m.certificate) + "\n" + + "\tcipher_specs = \n\t\t" + bastr(m.cipher_specs) + "\n" + + "\tconnection_id = \n\t\t" + bastr(m.connection_id) + "\n"; + + ClientMasterKey => + info += "\tClientMasterKey\n" + + "\tcipher_kind = \n\t\t" + bastr(m.cipher_kind) + "\n" + + "\tclear_key = \n\t\t" + bastr(m.clear_key) + "\n" + + "\tencrypt_key = \n\t\t" + bastr(m.encrypt_key) + "\n" + + "\tkey_arg = \n\t\t" + bastr(m.key_arg) + "\n"; + + ServerVerify => + info += "\tServerVerify\n" + + "\tchallenge = \n\t\t" + bastr(m.challenge) + "\n"; + + RequestCertificate => + info += "\tRequestCertificate\n" + + "\tauthentication_type = \n\t\t" + string m.authentication_type + "\n" + + "\tcertificate_challenge = \n\t\t" + bastr(m.certificate_challenge) + "\n"; + + ClientCertificate => + info += "ClientCertificate\n" + + "\tcertificate_type = \n\t\t" + string m.certificate_type + "\n" + + "\tcertificate = \n\t\t" + bastr(m.certificate) + "\n" + + "\tresponse = \n\t\t" + bastr(m.response) + "\n"; + + ClientFinished => + info += "\tClientFinished\n" + + "\tconnection_id = \n\t\t" + bastr(m.connection_id) + "\n"; + + ServerFinished => + info += "\tServerFinished\n" + + "\tsession_id = \n\t\t" + bastr(m.session_id) + "\n"; + } + + return info; +} + + +# v2 handshake protocol - message driven, v2 and v3 sharing the same context stack + +do_v2handshake(v2hs: ref V2Handshake, ctx: ref Context): string +{ + e: string = nil; + + pick h := v2hs { + Error => + do_v2error(h, ctx); + + ClientHello => + if((ctx.status & CLIENT_SIDE) || ctx.state != SSL2_STATE_CLIENT_HELLO) { + e = "V2ClientHello"; + break; + } + do_v2client_hello(h, ctx); + + ServerHello => + if(!(ctx.status & CLIENT_SIDE) || ctx.state != SSL2_STATE_SERVER_HELLO) { + e = "V2ServerHello"; + break; + } + do_v2server_hello(h, ctx); + + ClientMasterKey => + if((ctx.status & CLIENT_SIDE) || ctx.state != SSL2_STATE_CLIENT_MASTER_KEY) { + e = "V2ClientMasterKey"; + break; + } + do_v2client_master_key(h, ctx); + + ServerVerify => + if(!(ctx.status & CLIENT_SIDE) || ctx.state != SSL2_STATE_SERVER_VERIFY) { + e = "V2ServerVerify"; + break; + } + do_v2server_verify(h, ctx); + + RequestCertificate => + if(!(ctx.status & CLIENT_SIDE) || ctx.state != SSL2_STATE_SERVER_VERIFY) { + e = "V2RequestCertificate"; + break; + } + do_v2req_cert(h, ctx); + + ClientCertificate => + if((ctx.status & CLIENT_SIDE) || ctx.state != SSL2_STATE_CLIENT_CERTIFICATE) { + e = "V2ClientCertificate"; + break; + } + do_v2client_certificate(h, ctx); + + ClientFinished => + if((ctx.status & CLIENT_SIDE) || ctx.state != SSL2_STATE_CLIENT_FINISHED) { + e = "V2ClientFinished"; + break; + } + do_v2client_finished(h, ctx); + + ServerFinished => + if(!(ctx.status & CLIENT_SIDE) || ctx.state != SSL2_STATE_SERVER_FINISHED) { + e = "V2ServerFinished"; + break; + } + do_v2server_finished(h, ctx); + } + + return e; +} + +do_v2error(v2hs: ref V2Handshake.Error, ctx: ref Context) +{ + if(SSL_DEBUG) + log("do_v2error: " + string v2hs.code); + ctx.state = STATE_EXIT; +} + +# [server side] +do_v2client_hello(v2hs: ref V2Handshake.ClientHello, ctx: ref Context) +{ + if(v2hs.version[0] != SSL2_CLIENT_VERSION[0] || v2hs.version[1] != SSL2_CLIENT_VERSION[1]) { + # promote this message to v3 handshake protocol + ctx.state = STATE_CLIENT_HELLO; + return; + } + + # trying to resume + s: ref Session; + if((v2hs.session_id != nil) && (ctx.status & SESSION_RESUMABLE)) + s = sslsession->get_session_byid(v2hs.session_id); + if(s != nil) { # found a hit + # prepare and send v2 handshake hello message + v2handshake_enque( + ref V2Handshake.ServerHello( + 1, # hit found + 0, # no certificate required + SSL2_SERVER_VERSION, + nil, # no authetication required + s.suite, # use hit session cipher kind + ctx.server_random # connection_id + ), + ctx + ); + # TODO: should in supported cipher_kinds + err: string; + (ctx.sel_ciph, ctx.sel_keyx, ctx.sel_sign, err) + = v2suite_to_spec(ctx.session.suite, SSL2_Cipher_Kinds); + if(err != "") { + if(SSL_DEBUG) + log("do_v2client_hello: " + err); + ctx.state = SSL2_STATE_ERROR; + return; + } + ctx.state = SSL2_STATE_SERVER_FINISHED; + } + else { + # find matching cipher kinds + n := len v2hs.cipher_specs; + matchs := array [n] of byte; + j, k: int = 0; + while(j < n) { + # ignore those not in SSL2_Cipher_Kinds + matchs[k:] = v2hs.cipher_specs[j:j+3]; + for(i := 0; i < len SSL2_Cipher_Kinds; i++) { + ck := SSL2_Cipher_Kinds[i]; + if(matchs[k] == ck[0] && matchs[k+1] == ck[1] && matchs[k+2] == ck[2]) + k +=3; + } + j += 3; + } + if(k == 0) { + if(SSL_DEBUG) + log("do_v2client_hello: No matching cipher kind"); + ctx.state = SSL2_STATE_ERROR; + } + else { + matchs = matchs[0:k]; + + # Note: + # v2 challenge -> v3 client_random + # v2 connection_id -> v3 server_random + + chlen := len v2hs.challenge; + if(chlen > 32) + chlen = 32; + ctx.client_random = array [chlen] of byte; + if(chlen > 32) + ctx.client_random[0:] = v2hs.challenge[chlen-32:]; + else + ctx.client_random[0:] = v2hs.challenge; + ctx.server_random = random->randombuf(Random->NotQuiteRandom, 16); + s.session_id = random->randombuf ( + Random->NotQuiteRandom, + SSL2_MAX_SESSION_ID_LENGTH_IN_BYTES + ); + s.suite = matchs; + ctx.session = s; + v2handshake_enque( + ref V2Handshake.ServerHello( + 0, # no hit - not resumable + SSL2_CT_X509_CERTIFICATE, + SSL2_SERVER_VERSION, + hd ctx.local_info.certs, # the first is user certificate + ctx.session.suite, # matched cipher kinds + ctx.server_random # connection_id + ), + ctx + ); + ctx.state = SSL2_STATE_CLIENT_MASTER_KEY; + } + } +} + +# [client side] + +do_v2server_hello(v2hs: ref V2Handshake.ServerHello, ctx: ref Context) +{ + # must be v2 server hello otherwise it will be v3 server hello + # determined by auto record layer version detection + if(v2hs.version[0] != SSL2_SERVER_VERSION[0] + || v2hs.version[1] != SSL2_SERVER_VERSION[1]) { + if(SSL_DEBUG) + log("do_v2server_hello: not a v2 version"); + ctx.state = SSL2_STATE_ERROR; + return; + } + + ctx.session.version = SSL2_SERVER_VERSION; + ctx.server_random = v2hs.connection_id; + + # check if a resumable session is found + if(v2hs.session_id_hit != 0) { # resume ok + err: string; + # TODO: should in supported cipher_kinds + (ctx.sel_ciph, nil, nil, err) = v2suite_to_spec(ctx.session.suite, SSL2_Cipher_Kinds); + if(err != "") { + if(SSL_DEBUG) + log("do_v2server_hello: " + err); + ctx.state = SSL2_STATE_ERROR; + return; + } + } + else { # not resumable session + + # use the first matched cipher kind; install cipher spec + if(len v2hs.cipher_specs < 3) { + if(SSL_DEBUG) + log("do_v2server_hello: too few bytes"); + ctx.state = SSL2_STATE_ERROR; + return; + } + ctx.session.suite = array [3] of byte; + ctx.session.suite[0:] = v2hs.cipher_specs[0:3]; + err: string; + (ctx.sel_ciph, nil, nil, err) = v2suite_to_spec(ctx.session.suite, SSL2_Cipher_Kinds); + if(err != "") { + if(SSL_DEBUG) + log("do_v2server_hello: " + err); + return; + } + + # decode x509 certificates, authenticate server and extract + # public key from server certificate + if(v2hs.certificate_type != int SSL2_CT_X509_CERTIFICATE) { + if(SSL_DEBUG) + log("do_v2server_hello: not x509 certificate"); + ctx.state = SSL2_STATE_ERROR; + return; + } + ctx.session.peer_certs = v2hs.certificate :: nil; + # TODO: decode v2hs.certificate as list of certificate + # verify the list of certificate + (e, signed) := x509->Signed.decode(v2hs.certificate); + if(e != "") { + if(SSL_DEBUG) + log("do_v2server_hello: " + e); + ctx.state = SSL2_STATE_ERROR; + return; + } + certificate: ref Certificate; + (e, certificate) = x509->Certificate.decode(signed.tobe_signed); + if(e != "") { + if(SSL_DEBUG) + log("do_v2server_hello: " + e); + ctx.state = SSL2_STATE_ERROR; + return; + } + id: int; + peer_pk: ref X509->PublicKey; + (e, id, peer_pk) = certificate.subject_pkinfo.getPublicKey(); + if(e != nil) { + ctx.state = SSL2_STATE_ERROR; # protocol error + return; + } + pk: ref RSAKey; + pick key := peer_pk { + RSA => + pk = key.pk; + * => + } + # prepare and send client master key message + # TODO: change CipherSpec adt for more key info + # Temporary solution + # mkey (master key), ckey (clear key), skey(secret key) + mkey, ckey, skey, keyarg: array of byte; + (mkeylen, ckeylen, keyarglen) := v2suite_more(ctx.sel_ciph); + mkey = random->randombuf(Random->NotQuiteRandom, mkeylen); + if(ckeylen != 0) + ckey = mkey[0:ckeylen]; + if(mkeylen > ckeylen) + skey = mkey[ckeylen:]; + if(keyarglen > 0) + keyarg = random->randombuf(Random->NotQuiteRandom, keyarglen); + ekey: array of byte; + (e, ekey) = pkcs->rsa_encrypt(skey, pk, 2); + if(e != nil) { + if(SSL_DEBUG) + log("do_v2server_hello: " + e); + ctx.state = SSL2_STATE_ERROR; + return; + } + ctx.session.master_secret = mkey; + v2handshake_enque( + ref V2Handshake.ClientMasterKey(ctx.session.suite, ckey, ekey, keyarg), + ctx + ); + } + + # clean up out_queue before switch cipher + record_write_queue(ctx); + ctx.out_queue.data = nil; + + # install keys onto ctx that will be pushed on ssl record when ready + (ctx.cw_mac, ctx.sw_mac, ctx.cw_key, ctx.sw_key, ctx.cw_IV, ctx.sw_IV) + = v2calc_keys(ctx.sel_ciph, ctx.session.master_secret, + ctx.client_random, ctx.server_random); + e := set_queues(ctx); + if(e != "") { + if(SSL_DEBUG) + log("do_v2server_finished: " + e); + ctx.state = SSL2_STATE_ERROR; + return; + } + ctx.status |= IN_READY; + ctx.status |= OUT_READY; + + # prepare and send client finished message + v2handshake_enque( + ref V2Handshake.ClientFinished(ctx.server_random), # as connection_id + ctx + ); + + ctx.state = SSL2_STATE_SERVER_VERIFY; +} + +# [server side] + +do_v2client_master_key(v2hs: ref V2Handshake.ClientMasterKey, ctx: ref Context) +{ + #if(cmk.cipher == -1 || cipher_info[cmk.cipher].cryptalg == -1) { + # # return ("protocol error: bad cipher in masterkey", nullc); + # ctx.state = SSL2_STATE_ERROR; # protocol error + # return; + #} + + ctx.session.suite = v2hs.cipher_kind; + + # TODO: + # someplace shall be able to install the key + # need further encapsulate encrypt and decrypt functions from KeyExAlg adt + master_key_length: int; + secret_key: array of byte; + pick alg := ctx.sel_keyx { + RSA => + e: string; + (e, secret_key) = pkcs->rsa_decrypt(v2hs.encrypt_key, alg.sk, 0); + if(e != "") { + if(SSL_DEBUG) + log("do_v2client_master_key: " + e); + ctx.state = SSL2_STATE_ERROR; + return; + } + master_key_length = len v2hs.clear_key + len secret_key; + * => + if(SSL_DEBUG) + log("do_v2client_master_key: unknown public key algorithm"); + ctx.state = SSL2_STATE_ERROR; + return; + } + #TODO: do the following lines after modifying the CipherSpec adt + #if(master_key_length != ci.keylen) { + # ctx.state = SSL2_STATE_ERROR; # protocol error + # return; + #} + + ctx.session.master_secret = array [master_key_length] of byte; + ctx.session.master_secret[0:] = v2hs.clear_key; + ctx.session.master_secret[len v2hs.clear_key:] = secret_key; + + # install keys onto ctx that will be pushed on ssl record when ready + (ctx.cw_mac, ctx.sw_mac, ctx.cw_key, ctx.sw_key, ctx.cw_IV, ctx.sw_IV) + = v2calc_keys(ctx.sel_ciph, ctx.session.master_secret, + ctx.client_random, ctx.server_random); + v2handshake_enque( + ref V2Handshake.ServerVerify(ctx.client_random[16:]), + ctx + ); + v2handshake_enque( + ref V2Handshake.ServerFinished(ctx.session.session_id), + ctx + ); + ctx.state = SSL2_STATE_CLIENT_FINISHED; +} + +# used by client side +do_v2server_verify(v2hs: ref V2Handshake.ServerVerify, ctx: ref Context) +{ + # TODO: + # the challenge length may not be 16 bytes + if(bytes_cmp(v2hs.challenge, ctx.client_random[32-SSL2_CHALLENGE_LENGTH:]) < 0) { + if(SSL_DEBUG) + log("do_v2server_verify: challenge mismatch"); + ctx.state = SSL2_STATE_ERROR; + return; + } + + ctx.state = SSL2_STATE_SERVER_FINISHED; +} + +# [client side] + +do_v2req_cert(v2hs: ref V2Handshake.RequestCertificate, ctx: ref Context) +{ + # not supported until v3 + if(SSL_DEBUG) + log("do_v2req_cert: authenticate client not supported"); + v2hs = nil; + ctx.state = SSL2_STATE_ERROR; +} + +# [server side] + +do_v2client_certificate(v2hs: ref V2Handshake.ClientCertificate, ctx: ref Context) +{ + # not supported until v3 + if(SSL_DEBUG) + log("do_v2client_certificate: authenticate client not supported"); + v2handshake_enque ( + ref V2Handshake.Error(SSL2_PE_NO_CERTIFICATE), + ctx + ); + v2hs = nil; + ctx.state = SSL2_STATE_ERROR; +} + +# [server side] + +do_v2client_finished(v2hs: ref V2Handshake.ClientFinished, ctx: ref Context) +{ + if(bytes_cmp(ctx.server_random, v2hs.connection_id) < 0) { + ctx.session.session_id = nil; + if(SSL_DEBUG) + log("do_v2client_finished: connection id mismatch"); + ctx.state = SSL2_STATE_ERROR; + } + # TODO: + # the challenge length may not be 16 bytes + v2handshake_enque( + ref V2Handshake.ServerVerify(ctx.client_random[32-SSL2_CHALLENGE_LENGTH:]), + ctx + ); + if(ctx.session.session_id == nil) + ctx.session.session_id = random->randombuf(Random->NotQuiteRandom, 16); + v2handshake_enque( + ref V2Handshake.ServerFinished(ctx.session.session_id), + ctx + ); + e := set_queues(ctx); + if(e != "") { + if(SSL_DEBUG) + log("do_v2client_finished: " + e); + ctx.state = SSL2_STATE_ERROR; + return; + } + ctx.status |= IN_READY; + ctx.status |= OUT_READY; + sslsession->add_session(ctx.session); + + ctx.state = STATE_EXIT; +} + +# [client side] + +do_v2server_finished(v2hs: ref V2Handshake.ServerFinished, ctx: ref Context) +{ + if(ctx.session.session_id == nil) + ctx.session.session_id = array [16] of byte; + ctx.session.session_id[0:] = v2hs.session_id[0:]; + + sslsession->add_session(ctx.session); + + ctx.state = STATE_EXIT; +} + + +# Note: +# the key partitioning for v2 is different from v3 + +v2calc_keys(ciph: ref CipherSpec, ms, cr, sr: array of byte) + : (array of byte, array of byte, array of byte, array of byte, array of byte, array of byte) +{ + cw_mac, sw_mac, cw_key, sw_key, cw_IV, sw_IV: array of byte; + + # TODO: check the size of key block if IV exists + (mkeylen, ckeylen, keyarglen) := v2suite_more(ciph); + kblen := 2*mkeylen; + if(kblen%Keyring->MD5dlen != 0) { + if(SSL_DEBUG) + log("v2calc_keys: key block length is not multiple of MD5 hash length"); + } + else { + key_block := array [kblen] of byte; + + challenge := cr[32-SSL2_CHALLENGE_LENGTH:32]; # TODO: if challenge length != 16 ? + connection_id := sr[0:16]; # TODO: if connection_id length != 16 ? + var := array [1] of byte; + var[0] = byte 16r30; + i := 0; + while(i < kblen) { + (hash, nil) := md5_hash(ms::var::challenge::connection_id::nil, nil); + key_block[i:] = hash; + i += Keyring->MD5dlen; + ++var[0]; + } + + if(SSL_DEBUG) + log("ssl3: calc_keys:" + + "\n\tmaster key = \n\t\t" + bastr(ms) + + "\n\tchallenge = \n\t\t" + bastr(challenge) + + "\n\tconnection id = \n\t\t" + bastr(connection_id) + + "\n\tkey block = \n\t\t" + bastr(key_block) + "\n"); + + i = 0; + # server write key == client read key + # server write mac == server write key + sw_key = array [mkeylen] of byte; + sw_key[0:] = key_block[i:mkeylen]; + sw_mac = array [mkeylen] of byte; + sw_mac[0:] = key_block[i:mkeylen]; + # client write key == server read key + # client write mac == client write key + i += mkeylen; + cw_key = array [mkeylen] of byte; + cw_key[0:] = key_block[i:i+mkeylen]; + cw_mac = array [mkeylen] of byte; + cw_mac[0:] = key_block[i:i+mkeylen]; + # client IV == server IV + # Note: + # IV is a part of writing or reading key for ssl device + # this is composed again in setctl + cw_IV = array [keyarglen] of byte; + cw_IV[0:] = ms[mkeylen:mkeylen+keyarglen]; + sw_IV = array [keyarglen] of byte; + sw_IV[0:] = ms[mkeylen:mkeylen+keyarglen]; + } + + if(SSL_DEBUG) + log("ssl3: calc_keys:" + + "\n\tclient_write_mac = \n\t\t" + bastr(cw_mac) + + "\n\tserver_write_mac = \n\t\t" + bastr(sw_mac) + + "\n\tclient_write_key = \n\t\t" + bastr(cw_key) + + "\n\tserver_write_key = \n\t\t" + bastr(sw_key) + + "\n\tclient_write_IV = \n\t\t" + bastr(cw_IV) + + "\n\tserver_write_IV = \n\t\t" + bastr(sw_IV) + "\n"); + + return (cw_mac, sw_mac, cw_key, sw_key, cw_IV, sw_IV); +} + +v3tov2specs(suites: array of byte): array of byte +{ + # v3 suite codes are 2 bytes each, v2 codes are 3 bytes + n := len suites / 2; + kinds := array [n*3*2] of byte; + k := 0; + for(i := 0; i < n;) { + a := suites[i:i+2]; + i += 2; + m := len SSL3_Suites; + for(j := 0; j < m; j++) { + b := SSL3_Suites[j]; + if(a[0]==b[0] && a[1]==b[1]) + break; + } + if (j == m) { + if(SSL_DEBUG) + log("ssl3: unknown v3 suite"); + continue; + } + case j { + RSA_EXPORT_WITH_RC4_40_MD5 => + kinds[k:] = SSL2_Cipher_Kinds[SSL2_CK_RC4_128_EXPORT40_WITH_MD5]; + k += 3; + RSA_WITH_RC4_128_MD5 => + kinds[k:] = SSL2_Cipher_Kinds[SSL2_CK_RC4_128_WITH_MD5]; + k += 3; + RSA_WITH_IDEA_CBC_SHA => + kinds[k:] = SSL2_Cipher_Kinds[SSL2_CK_IDEA_128_CBC_WITH_MD5]; + k += 3; + RSA_WITH_DES_CBC_SHA => + ; + * => + if(SSL_DEBUG) + log("ssl3: unable to convert v3 suite to v2 kind"); + } + # append v3 code in v2-safe manner + # (suite[0] == 0) => will be ignored by v2 server, picked up by v3 server + kinds[k] = byte 16r00; + kinds[k+1:] = SSL3_Suites[j]; + k += 3; + } + return kinds[0:k]; +} + +v2suite_to_spec(cs: array of byte, cipher_kinds: array of array of byte) + : (ref CipherSpec, ref KeyExAlg, ref SigAlg, string) +{ + cip : ref CipherSpec; + kex : ref KeyExAlg; + sig : ref SigAlg; + + n := len cipher_kinds; + for(i := 0; i < n; i++) { + found := cipher_kinds[i]; + if(found[0]==cs[0] && found[1]==cs[1] && found[2]==cs[2]) break; + } + + if(i == n) + return (nil, nil, nil, "fail to find a matched spec"); + + case i { + SSL2_CK_RC4_128_WITH_MD5 => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_RC4, SSL_STREAM_CIPHER, + 16, 0, SSL_MD5, Keyring->MD4dlen); + + SSL2_CK_RC4_128_EXPORT40_WITH_MD5 => + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_RC4, SSL_STREAM_CIPHER, + 5, 0, SSL_MD5, Keyring->MD4dlen); + + SSL2_CK_RC2_CBC_128_CBC_WITH_MD5 => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_RC2_CBC, SSL_BLOCK_CIPHER, + 16, 8, SSL_MD5, Keyring->MD4dlen); + + SSL2_CK_RC2_CBC_128_CBC_EXPORT40_WITH_MD5 => + cip = ref CipherSpec(SSL_EXPORT_TRUE, SSL_RC2_CBC, SSL_BLOCK_CIPHER, + 5, 8, SSL_MD5, Keyring->MD4dlen); + + SSL2_CK_IDEA_128_CBC_WITH_MD5 => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_IDEA_CBC, SSL_BLOCK_CIPHER, + 16, 8, SSL_MD5, Keyring->MD4dlen); + + SSL2_CK_DES_64_CBC_WITH_MD5 => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_DES_CBC, SSL_BLOCK_CIPHER, + 8, 8, SSL_MD5, Keyring->MD4dlen); + + SSL2_CK_DES_192_EDE3_CBC_WITH_MD5 => + cip = ref CipherSpec(SSL_EXPORT_FALSE, SSL_3DES_EDE_CBC, SSL_BLOCK_CIPHER, + 24, 8, SSL_MD5, Keyring->MD4dlen); + } + + kex = ref KeyExAlg.RSA(nil, nil, nil); + sig = ref SigAlg.RSA(nil, nil); + + return (cip, kex, sig, nil); +} + +v2suite_more(ciph: ref CipherSpec): (int, int, int) +{ + mkeylen, ckeylen, keyarglen: int; + + case ciph.bulk_cipher_algorithm { + SSL_RC4 => + mkeylen = 16; + if(ciph.key_material == 5) + ckeylen = 16 - 5; + else + ckeylen = 0; + keyarglen = 0; + + SSL_RC2_CBC => + mkeylen = 16; + if(ciph.key_material == 5) + ckeylen = 16 - 5; + else + ckeylen = 0; + keyarglen = 8; + + SSL_IDEA_CBC => + mkeylen = 16; + ckeylen = 0; + keyarglen = 8; + + SSL_DES_CBC => + mkeylen = 8; + if(ciph.key_material == 5) + ckeylen = 8 - 5; + else + ckeylen = 0; + keyarglen = 8; + + SSL_3DES_EDE_CBC => + mkeylen = 24; + ckeylen = 0; + keyarglen = 8; + } + + return (mkeylen, ckeylen, keyarglen); +} + +v2handshake_enque(h: ref V2Handshake, ctx: ref Context) +{ + p := ref Protocol.pV2Handshake(h); + + protocol_write(p, ctx); +} + +V2Handshake.encode(hm: self ref V2Handshake): (array of byte, string) +{ + a : array of byte; + n : int; + e : string; + + i := 0; + pick m := hm { + Error => + n = 3; + a = array[n] of byte; + a[i++] = byte SSL2_MT_ERROR; + a[i:] = m.code; + + ClientHello => + specslen := len m.cipher_specs; + sidlen := len m.session_id; + challen := len m.challenge; + n = 9+specslen + sidlen + challen; + a = array[n] of byte; + a[i++] = byte SSL2_MT_CLIENT_HELLO; + a[i:] = m.version; + i += 2; + a[i:] = int_encode(specslen, 2); + i += 2; + a[i:] = int_encode(sidlen, 2); + i += 2; + a[i:] = int_encode(challen, 2); + i += 2; + a[i:] = m.cipher_specs; + i += specslen; + if(sidlen != 0) { + a[i:] = m.session_id; + i += sidlen; + } + if(challen != 0) { + a[i:] = m.challenge; + i += challen; + } + + ServerHello => + # use only the user certificate + certlen := len m.certificate; +# specslen := 3*len m.cipher_specs; + specslen := len m.cipher_specs; + cidlen := len m.connection_id; + n = 11 + certlen + specslen + cidlen; + a = array[n] of byte; + a[i++] = byte SSL2_MT_SERVER_HELLO; + a[i++] = byte m.session_id_hit; + a[i++] = byte m.certificate_type; + a[i:] = m.version; + i += 2; + a[i:] = int_encode(certlen, 2); + i += 2; + a[i:] = int_encode(specslen, 2); + i += 2; + a[i:] = int_encode(cidlen, 2); + i += 2; + a[i:] = m.certificate; + i += certlen; + a[i:] = m.cipher_specs; + i += specslen; + a[i:] = m.connection_id; + i += cidlen; + + ClientMasterKey => + ckeylen := len m.clear_key; + ekeylen := len m.encrypt_key; + karglen := len m.key_arg; + n = 10 + ckeylen + ekeylen + karglen; + a = array[n] of byte; + a[i++] = byte SSL2_MT_CLIENT_MASTER_KEY; + a[i:] = m.cipher_kind; + i += 3; + a[i:] = int_encode(ckeylen, 2); + i += 2; + a[i:] = int_encode(ekeylen, 2); + i += 2; + a[i:] = int_encode(karglen, 2); + i += 2; + a[i:] = m.clear_key; + i += ckeylen; + a[i:] = m.encrypt_key; + i += ekeylen; + a[i:] = m.key_arg; + i += karglen; + + ServerVerify => + challen := len m.challenge; + n = 1 + challen; + a = array[n] of byte; + a[i++] = byte SSL2_MT_SERVER_VERIFY; + a[i:] = m.challenge; + + RequestCertificate => + cclen := len m.certificate_challenge; + n = 2 + cclen; + a = array[n] of byte; + a[i++] = byte SSL2_MT_REQUEST_CERTIFICATE; + a[i++] = byte m.authentication_type; + a[i:] = m.certificate_challenge; + i += cclen; + + ClientCertificate => + # use only the user certificate + certlen := len m.certificate; + resplen := len m.response; + n = 6 + certlen + resplen; + a = array[n] of byte; + a[i++] = byte SSL2_MT_CLIENT_CERTIFICATE; + a[i++] = byte m.certificate_type; + a[i:] = int_encode(certlen, 2); + i += 2; + a[i:] = int_encode(resplen, 2); + i += 2; + a[i:] = m.certificate; + i += certlen; + a[i:] = m.response; + i += resplen; + + ClientFinished => + cidlen := len m.connection_id; + n = 1 + cidlen; + a = array[n] of byte; + a[i++] = byte SSL2_MT_CLIENT_FINISHED; + a[i:] = m.connection_id; + i += cidlen; + + ServerFinished => + sidlen := len m.session_id; + n = 1 + sidlen; + a = array[n] of byte; + a[i++] = byte SSL2_MT_SERVER_FINISHED; + a[i:] = m.session_id; + i += sidlen; + } + + return (a, e); +} + +V2Handshake.decode(a: array of byte): (ref V2Handshake, string) +{ + m : ref V2Handshake; + e : string; + + n := len a; + i := 1; + case int a[0] { + SSL2_MT_ERROR => + if(n != 3) + break; + code := a[i:i+2]; + i += 2; + m = ref V2Handshake.Error(code); + + SSL2_MT_CLIENT_HELLO => + if(n < 9) { + e = "client hello: message too short"; + break; + } + ver := a[i:i+2]; + i += 2; + specslen := int_decode(a[i:i+2]); + i += 2; + sidlen := int_decode(a[i:i+2]); + i += 2; + challen := int_decode(a[i:i+2]); + i += 2; + if(n != 9+specslen+sidlen+challen) { + e = "client hello: length mismatch"; + break; + } + if(specslen%3 != 0) { + e = "client hello: must multiple of 3 bytes"; + break; + } + specs: array of byte; + if(specslen != 0) { + specs = a[i:i+specslen]; + i += specslen; + } + sid: array of byte; + if(sidlen != 0) { + sid = a[i:i+sidlen]; + i += sidlen; + } + chal: array of byte; + if(challen != 0) { + chal = a[i:i+challen]; + i += challen; + } + m = ref V2Handshake.ClientHello(ver, specs, sid, chal); + + SSL2_MT_CLIENT_MASTER_KEY => + if(n < 10) { + e = "client master key: message too short"; + break; + } + kind := a[i:i+3]; + i += 3; + ckeylen := int_decode(a[i:i+2]); + i += 2; + ekeylen := int_decode(a[i:i+2]); + i += 2; + karglen := int_decode(a[i:i+2]); + i += 2; + if(n != 10 + ckeylen + ekeylen + karglen) { + e = "client master key: length mismatch"; + break; + } + ckey := a[i:i+ckeylen]; + i += ckeylen; + ekey := a[i:i+ekeylen]; + i += ekeylen; + karg := a[i:i+karglen]; + i += karglen; + m = ref V2Handshake.ClientMasterKey(kind, ckey, ekey, karg); + + SSL2_MT_CLIENT_FINISHED => + cid := a[i:n]; + i = n; + m = ref V2Handshake.ClientFinished(cid); + + SSL2_MT_SERVER_HELLO => + if(n < 11) { + e = "server hello: messsage too short"; + break; + } + sidhit := int a[i++]; + certtype := int a[i++]; + ver := a[i:i+2]; + i += 2; + certlen := int_decode(a[i:i+2]); + i += 2; + specslen := int_decode(a[i:i+2]); + i += 2; + cidlen := int_decode(a[i:i+2]); + i += 2; + if(n != 11+certlen+specslen+cidlen) { + e = "server hello: length mismatch"; + break; + } + cert := a[i:i+certlen]; + i += certlen; + if(specslen%3 != 0) { + e = "server hello: must be multiple of 3 bytes"; + break; + } + specs := a[i:i+specslen]; + i += specslen; + if(cidlen < 16 || cidlen > 32) { + e = "server hello: connection id length out of range"; + break; + } + cid := a[i:i+cidlen]; + i += cidlen; + m = ref V2Handshake.ServerHello(sidhit, certtype, ver, cert, specs, cid); + + SSL2_MT_SERVER_VERIFY => + chal := a[i:n]; + i = n; + m = ref V2Handshake.ServerVerify(chal); + + SSL2_MT_SERVER_FINISHED => + sid := a[i:n]; + m = ref V2Handshake.ServerFinished(sid); + + SSL2_MT_REQUEST_CERTIFICATE => + if(n < 2) { + e = "request certificate: message too short"; + break; + } + authtype := int a[i++]; + certchal := a[i:n]; + i = n; + m = ref V2Handshake.RequestCertificate(authtype, certchal); + + SSL2_MT_CLIENT_CERTIFICATE => + if(n < 6) { + e = "client certificate: message too short"; + break; + } + certtype := int a[i++]; + certlen := int_decode(a[i:i+2]); + i += 2; + resplen := int_decode(a[i:i+2]); + i += 2; + if(n != 6+certlen+resplen) { + e = "client certificate: length mismatch"; + break; + } + cert := a[i:i+certlen]; + i += certlen; + resp := a[i:i+resplen]; + m = ref V2Handshake.ClientCertificate(certtype, cert, resp); + + * => + e = "unknown message [" + string a[0] + "]"; + } + + return (m, e); +} + +# utilities + +md5_hash(input: list of array of byte, md5_ds: ref DigestState): (array of byte, ref DigestState) +{ + hash_value := array [Keyring->MD5dlen] of byte; + ds : ref DigestState; + + if(md5_ds != nil) + ds = md5_ds.copy(); + + lab := input; + for(i := 0; i < len input - 1; i++) { + ds = keyring->md5(hd lab, len hd lab, nil, ds); + lab = tl lab; + } + ds = keyring->md5(hd lab, len hd lab, hash_value, ds); + + return (hash_value, ds); +} + +sha_hash(input: list of array of byte, sha_ds: ref DigestState): (array of byte, ref DigestState) +{ + hash_value := array [Keyring->SHA1dlen] of byte; + ds : ref DigestState; + + if(sha_ds != nil) + ds = sha_ds.copy(); + + lab := input; + for(i := 0; i < len input - 1; i++) { + ds = keyring->sha1(hd lab, len hd lab, nil, ds); + lab = tl lab; + } + ds = keyring->sha1(hd lab, len hd lab, hash_value, ds); + + return (hash_value, ds); +} + +md5_sha_hash(input: list of array of byte, md5_ds, sha_ds: ref DigestState) + : (array of byte, ref DigestState, ref DigestState) +{ + buf := array [Keyring->MD5dlen+Keyring->SHA1dlen] of byte; + + (buf[0:], md5_ds) = md5_hash(input, md5_ds); + (buf[Keyring->MD5dlen:], sha_ds) = sha_hash(input, sha_ds); + + return (buf, md5_ds, sha_ds); +} + +int_decode(buf: array of byte): int +{ + val := 0; + for(i := 0; i < len buf; i++) + val = (val << 8) | (int buf[i]); + + return val; +} + +int_encode(value, length: int): array of byte +{ + buf := array [length] of byte; + + while(length--) { + buf[length] = byte value; + value >>= 8; + } + + return buf; +} + + +bastr(a: array of byte) : string +{ + ans : string = ""; + + for(i := 0; i < len a; i++) { + if(i < len a - 1 && i != 0 && i%10 == 0) + ans += "\n\t\t"; + if(i == len a -1) + ans += sys->sprint("%2x", int a[i]); + else + ans += sys->sprint("%2x ", int a[i]); + } + + return ans; +} + +bbastr(a: array of array of byte) : string +{ + info := ""; + + for(i := 0; i < len a; i++) + info += bastr(a[i]); + + return info; +} + +lbastr(a: list of array of byte) : string +{ + info := ""; + + l := a; + while(l != nil) { + info += bastr(hd l) + "\n\t\t"; + l = tl l; + } + + return info; +} + +# need to fix (string a == string b) +bytes_cmp(a, b: array of byte): int +{ + if(len a != len b) + return -1; + + n := len a; + for(i := 0; i < n; i++) { + if(a[i] != b[i]) + return -1; + } + + return 0; +} + +putn(a: array of byte, i, value, n: int): int +{ + j := n; + while(j--) { + a[i+j] = byte value; + value >>= 8; + } + return i+n; +} + +INVALID_SUITE : con "invalid suite list"; +ILLEGAL_SUITE : con "illegal suite list"; + +cksuites(suites : array of byte) : string +{ + m := len suites; + if (m == 0 || (m&1)) + return INVALID_SUITE; + n := len SSL3_Suites; + ssl3s := array [2] of byte; + for (j := 0; j < m; j += 2) { + for( i := 0; i < n; i++) { + ssl3s = SSL3_Suites[i]; + if(suites[j] == ssl3s[0] && suites[j+1] == ssl3s[1]) + break; + } + if (i == n) + return ILLEGAL_SUITE; + } + return nil; +} diff --git a/appl/lib/crypt/sslsession.b b/appl/lib/crypt/sslsession.b new file mode 100644 index 00000000..cce8399e --- /dev/null +++ b/appl/lib/crypt/sslsession.b @@ -0,0 +1,176 @@ +# +# SSL Session Cache +# +implement SSLsession; + +include "sys.m"; + sys : Sys; + +include "daytime.m"; + daytime : Daytime; + +include "sslsession.m"; + + +# default session id timeout +TIMEOUT_SECS : con 5*60; # sec + +SessionCache: adt { + db : list of ref Session; + time_out : int; +}; + +# The shared session cache by all ssl contexts is available for efficiently resumming +# sessions for different run time contexts. + +Session_Cache : ref SessionCache; + + +init(): string +{ + sys = load Sys Sys->PATH; + if(sys == nil) + return "sslsession: load sys module failed"; + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + return "sslsession: load Daytime module failed"; + + Session_Cache = ref SessionCache(nil, TIMEOUT_SECS); + + return ""; +} + +Session.new(peer: string, time: int, ver: array of byte): ref Session +{ + s := ref Session; + + s.peer = peer; + s.connection_time = time; + s.version = array [2] of byte; + s.version[0:] = ver; + s.session_id = nil; + s.suite = nil; + s.master_secret = nil; + s.peer_certs = nil; + + return s; +} + +Session.duplicate(s: self ref Session): ref Session +{ + new := ref Session; + + new.peer = s.peer; + new.connection_time = s.connection_time; + new.version = array [len s.version] of byte; + new.version[0:] = s.version; + new.session_id = array [len s.session_id] of byte; + new.session_id[0:] = s.session_id; + new.suite = array [len s.suite] of byte; + new.suite[0:] = s.suite; + new.master_secret = array [len s.master_secret] of byte; + new.master_secret[0:] = s.master_secret; + l: list of array of byte; + pcs := s.peer_certs; + while(pcs != nil) { + a := hd pcs; + b := array [len a] of byte; + b[0:] = a; + l = b :: l; + pcs = tl pcs; + } + while(l != nil) { + new.peer_certs = (hd l) :: new.peer_certs; + l = tl l; + } + return new; +} + +# Each request process should get a copy of a session. A session will be +# removed from database if it is expired. The garbage +# collector will finally remove it from memory if there are no more +# references to it. + +get_session_byname(peer: string): ref Session +{ + s: ref Session; + now := daytime->now(); # not accurate but more efficient + + l := Session_Cache.db; + while(l != nil) { + if((hd l).peer == peer) { + s = hd l; + # TODO: remove expired session + if(now > s.connection_time+Session_Cache.time_out) + s = nil; + break; + } + l = tl l; + } + if(s == nil) + s = Session.new(peer, now, nil); + else + s = s.duplicate(); + + return s; +} + +# replace the old by the new one +add_session(s: ref Session) +{ + #old : ref Session; + + #ls := Session_Cache.db; + #while(ls != nil) { + # old = hd ls; + # if(s.session_id == old.session_id) { + # # old = s; + # return; + # } + #} + + # always resume the most recent + if(s != nil) + Session_Cache.db = s :: Session_Cache.db; +} + +get_session_byid(session_id: array of byte): ref Session +{ + s: ref Session; + now := daytime->now(); # not accurate but more efficient + l := Session_Cache.db; + while(l != nil) { + if(bytes_cmp((hd l).session_id, session_id) == 0) { + s = hd l; + # replace expired session + if(now > s.connection_time+Session_Cache.time_out) + s = Session.new(s.peer, now, nil); + else + s = s.duplicate(); + break; + } + l = tl l; + } + return s; +} + +set_timeout(t: int) +{ + Session_Cache.time_out = t; +} + +bytes_cmp(a, b: array of byte): int +{ + if(len a != len b) + return -1; + + n := len a; + for(i := 0; i < n; i++) { + if(a[i] != b[i]) + return -1; + } + + return 0; +} + diff --git a/appl/lib/crypt/x509.b b/appl/lib/crypt/x509.b new file mode 100644 index 00000000..059a58f6 --- /dev/null +++ b/appl/lib/crypt/x509.b @@ -0,0 +1,4125 @@ +implement X509; + +include "sys.m"; + sys : Sys; + +include "asn1.m"; + asn1 : ASN1; + Elem, Tag, Value, Oid, + Universal, Context, + BOOLEAN, + INTEGER, + BIT_STRING, + OCTET_STRING, + OBJECT_ID, + SEQUENCE, + UTCTime, + IA5String, + GeneralString, + GeneralizedTime : import asn1; + +include "keyring.m"; + keyring : Keyring; + MD4, MD5, SHA1, IPint, DESstate : import keyring; + +include "security.m"; + random : Random; + +include "daytime.m"; + daytime : Daytime; + +include "pkcs.m"; + pkcs : PKCS; + +include "x509.m"; + +X509_DEBUG : con 0; +logfd : ref Sys->FD; + +TAG_MASK : con 16r1F; +CONSTR_MASK : con 16r20; +CLASS_MASK : con 16rC0; + +# object identifiers + +objIdTab = array [] of { + id_at => Oid(array [] of {2,5,4}), + id_at_commonName => Oid(array [] of {2,5,4,3}), + id_at_countryName => Oid(array [] of {2,5,4,6}), + id_at_localityName => Oid(array [] of {2,5,4,7}), + id_at_stateOrProvinceName => Oid(array [] of {2,5,4,8}), + id_at_organizationName => Oid(array [] of {2,5,4,10}), + id_at_organizationalUnitName => Oid(array [] of {2,5,4,11}), + id_at_userPassword => Oid(array [] of {2,5,4,35}), + id_at_userCertificate => Oid(array [] of {2,5,4,36}), + id_at_cAcertificate => Oid(array [] of {2,5,4,37}), + id_at_authorityRevocationList => + Oid(array [] of {2,5,4,38}), + id_at_certificateRevocationList => + Oid(array [] of {2,5,4,39}), + id_at_crossCertificatePair => Oid(array [] of {2,5,4,40}), +# id_at_crossCertificatePair => Oid(array [] of {2,5,4,58}), + id_at_supportedAlgorithms => Oid(array [] of {2,5,4,52}), + id_at_deltaRevocationList => Oid(array [] of {2,5,4,53}), + + id_ce => Oid(array [] of {2,5,29}), + id_ce_subjectDirectoryAttributes => + Oid(array [] of {2,5,29,9}), + id_ce_subjectKeyIdentifier => Oid(array [] of {2,5,29,14}), + id_ce_keyUsage => Oid(array [] of {2,5,29,15}), + id_ce_privateKeyUsage => Oid(array [] of {2,5,29,16}), + id_ce_subjectAltName => Oid(array [] of {2,5,29,17}), + id_ce_issuerAltName => Oid(array [] of {2,5,29,18}), + id_ce_basicConstraints => Oid(array [] of {2,5,29,19}), + id_ce_cRLNumber => Oid(array [] of {2,5,29,20}), + id_ce_reasonCode => Oid(array [] of {2,5,29,21}), + id_ce_instructionCode => Oid(array [] of {2,5,29,23}), + id_ce_invalidityDate => Oid(array [] of {2,5,29,24}), + id_ce_deltaCRLIndicator => Oid(array [] of {2,5,29,27}), + id_ce_issuingDistributionPoint => + Oid(array [] of {2,5,29,28}), + id_ce_certificateIssuer => Oid(array [] of {2,5,29,29}), + id_ce_nameConstraints => Oid(array [] of {2,5,29,30}), + id_ce_cRLDistributionPoint => Oid(array [] of {2,5,29,31}), + id_ce_certificatePolicies => Oid(array [] of {2,5,29,32}), + id_ce_policyMapping => Oid(array [] of {2,5,29,33}), + id_ce_authorityKeyIdentifier => + Oid(array [] of {2,5,29,35}), + id_ce_policyConstraints => Oid(array [] of {2,5,29,36}), + +# id_mr => Oid(array [] of {2,5,?}), +# id_mr_certificateMatch => Oid(array [] of {2,5,?,35}), +# id_mr_certificatePairExactMatch => +# Oid(array [] of {2,5,?,36}), +# id_mr_certificatePairMatch => Oid(array [] of {2,5,?,37}), +# id_mr_certificateListExactMatch => +# Oid(array [] of {2,5,?,38}), +# id_mr_certificateListMatch => Oid(array [] of {2,5,?,39}), +# id_mr_algorithmidentifierMatch => +# Oid(array [] of {2,5,?,40}) +}; + +# [public] + +init(): string +{ + sys = load Sys Sys->PATH; + + if(X509_DEBUG) + logfd = sys->fildes(1); + + keyring = load Keyring Keyring->PATH; + if(keyring == nil) + return sys->sprint("load %s: %r", Keyring->PATH); + + random = load Random Random->PATH; + if(random == nil) + return sys->sprint("load %s: %r", Random->PATH); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + return sys->sprint("load %s: %r", Daytime->PATH); + + asn1 = load ASN1 ASN1->PATH; + if(asn1 == nil) + return sys->sprint("load %s: %r", ASN1->PATH); + asn1->init(); + + pkcs = load PKCS PKCS->PATH; + if(pkcs == nil) + return sys->sprint("load %s: %r", PKCS->PATH); + if((e := pkcs->init()) != nil) + return sys->sprint("pkcs: %s", e); + + return nil; +} + +# [private] + +log(s: string) +{ + if(X509_DEBUG) + sys->fprint(logfd, "x509: %s\n", s); +} + +## SIGNED { ToBeSigned } ::= SEQUENCE { +## toBeSigned ToBeSigned, +## COMPONENTS OF SIGNATURE { ToBeSigned }} +## +## SIGNATURE {OfSignature} ::= SEQUENCE { +## algorithmIdentifier AlgorithmIdentifier, +## encrypted ENCRYPTED { HASHED { OfSignature }}} +## +## ENCRYPTED { ToBeEnciphered } ::= BIT STRING ( CONSTRAINED BY { +## -- must be the result of applying an encipherment procedure -- +## -- to the BER-encoded octets of a value of -- ToBeEnciphered } ) + +# [public] + +Signed.decode(a: array of byte): (string, ref Signed) +{ +parse: + for(;;) { + # interpret the enclosing structure + (ok, tag, i, n) := der_dec1(a, 0, len a); + if(!ok || n != len a || !tag.constr || + tag.class != Universal || tag.num != SEQUENCE) + break parse; + s := ref Signed; + # SIGNED sequence + istart := i; + (ok, tag, i, n) = der_dec1(a, i, len a); + if(!ok || n == len a) + break parse; + s.tobe_signed = a[istart:n]; + # AlgIdentifier + istart = n; + (ok, tag, i, n) = der_dec1(a, n, len a); + if(!ok || n == len a + || !tag.constr || tag.class != Universal || tag.num != SEQUENCE) { + if(X509_DEBUG) + log("signed: der data: " + + sys->sprint("ok=%d, n=%d, constr=%d, class=%d, num=%d", + ok, n, tag.constr, tag.class, tag.num)); + break parse; + } + (ok, s.alg) = decode_algid(a[istart:n]); + if(!ok) { + if(X509_DEBUG) + log("signed: alg identifier: syntax error"); + break; + } + # signature + (ok, tag, i, n) = der_dec1(a, n, len a); + if(!ok || n != len a + || tag.constr || tag.class != Universal || tag.num != BIT_STRING) { + if(X509_DEBUG) + log("signed: signature: " + + sys->sprint("ok=%d, n=%d, constr=%d, class=%d, num=%d", + ok, n, tag.constr, tag.class, tag.num)); + break parse; + } + s.signature = a[i:n]; + # to the end of no error been found + return ("", s); + } + return ("signed: syntax error", nil); +} + +# [public] +# der encoding of signed data + +Signed.encode(s: self ref Signed): (string, array of byte) +{ + (err, e_dat) := asn1->decode(s.tobe_signed); # why? + if(err != "") + return (err, nil); + e_alg := pack_alg(s.alg); + e_sig := ref Elem( + Tag(Universal, BIT_STRING, 0), + ref Value.BitString(0,s.signature) # DER encode of BIT STRING + ); + all := ref Elem( + Tag(Universal, SEQUENCE, 1), + ref Value.Seq(e_dat::e_alg::e_sig::nil) + ); + return asn1->encode(all); +} + +# [public] + +Signed.sign(s: self ref Signed, sk: ref PrivateKey, hash: int): (string, array of byte) +{ + # we require tobe_signed has 0 bits of padding + if(int s.tobe_signed[0] != 0) + return ("syntax error", nil); + pick key := sk { + RSA => + (err, signature) := pkcs->rsa_sign(s.tobe_signed, key.sk, hash); + s.signature = signature; + # TODO: add AlgIdentifier based on public key and hash + return (err, signature); + DSS => + # TODO: hash s.tobe_signed for signing + (err, signature) := pkcs->dss_sign(s.tobe_signed, key.sk); + s.signature = signature; + return (err, signature); + DH => + return ("cannot sign using DH algorithm", nil); + } + return ("sign: failed", nil); +} + +# [public] +# hash algorithm should be MD2, MD4, MD5 or SHA + +Signed.verify(s: self ref Signed, pk: ref PublicKey, hash: int): int +{ + ok := 0; + + pick key := pk { + RSA => + ok = pkcs->rsa_verify(s.tobe_signed, s.signature, key.pk, hash); + DSS => + # TODO: hash s.tobe_signed for verifying + ok = pkcs->dss_verify(s.tobe_signed, s.signature, key.pk); + DH => + # simply failure + } + + return ok; +} + +# [public] + +Signed.tostring(s: self ref Signed): string +{ + str := "Signed"; + + str += "\nToBeSigned: " + bastr(s.tobe_signed); + str += "\nAlgorithm: " + s.alg.tostring(); + str += "\nSignature: " + bastr(s.signature); + + return str + "\n"; +} + +# DER +# a) the definite form of length encoding shall be used, encoded in the minimum number of +# octets; +# b) for string types, the constructed form of encoding shall not be used; +# c) if the value of a type is its default value, it shall be absent; +# d) the components of a Set type shall be encoded in ascending order of their tag value; +# e) the components of a Set-of type shall be encoded in ascending order of their octet value; +# f) if the value of a Boolean type is true, the encoding shall have its contents octet +# set to "FF"16; +# g) each unused bits in the final octet of the encoding of a Bit String value, if there are +# any, shall be set to zero; +# h) the encoding of a Real type shall be such that bases 8, 10, and 16 shall not be used, +# and the binary scaling factor shall be zero. + +# [private] +# decode ASN1 one record at a time and return (err, tag, start of data, +# end of data) for indefinite length, the end of data is same as 'n' + +der_dec1(a: array of byte, i, n: int): (int, Tag, int, int) +{ + length: int; + tag: Tag; + ok := 1; + (ok, i, tag) = der_dectag(a, i, n); + if(ok) { + (ok, i, length) = der_declen(a, i, n); + if(ok) { + if(length == -1) { + if(!tag.constr) + ok = 0; + length = n - i; + } + else { + if(i+length > n) + ok = 0; + } + } + } + if(!ok && X509_DEBUG) + log("der_dec1: syntax error"); + return (ok, tag, i, i+length); +} + +# [private] +# der tag decode + +der_dectag(a: array of byte, i, n: int): (int, int, Tag) +{ + ok := 1; + class, num, constr: int; + if(n-i >= 2) { + v := int a[i++]; + class = v & CLASS_MASK; + if(v & CONSTR_MASK) + constr = 1; + else + constr = 0; + num = v & TAG_MASK; + if(num == TAG_MASK) + # long tag number + (ok, i, num) = uint7_decode(a, i, n); + } + else + ok = 0; + if(!ok && X509_DEBUG) + log("der_declen: syntax error"); + return (ok, i, Tag(class, num, constr)); +} + +# [private] + +int_decode(a: array of byte, i, n, count, unsigned: int): (int, int, int) +{ + ok := 1; + num := 0; + if(n-i >= count) { + if((count > 4) || (unsigned && count == 4 && (int a[i] & 16r80))) + ok = 1; + else { + if(!unsigned && count > 0 && count < 4 && (int a[i] & 16r80)) + num = -1; # all bits set + for(j := 0; j < count; j++) { + v := int a[i++]; + num = (num << 8) | v; + } + } + } + else + ok = 0; + if(!ok && X509_DEBUG) + log("int_decode: syntax error"); + return (ok, i, num); +} + + +# [private] + +uint7_decode(a: array of byte, i, n: int) : (int, int, int) +{ + ok := 1; + num := 0; + more := 1; + while(more && i < n) { + v := int a[i++]; + if(num & 16r7F000000) { + ok = 0; + break; + } + num <<= 7; + more = v & 16r80; + num |= (v & 16r7F); + } + if(n == i) + ok = 0; + if(!ok && X509_DEBUG) + log("uint7_decode: syntax error"); + return (ok, i, num); +} + + +# [private] +# der length decode - the definite form of length encoding shall be used, encoded +# in the minimum number of octets + +der_declen(a: array of byte, i, n: int): (int, int, int) +{ + ok := 1; + num := 0; + if(i < n) { + v := int a[i++]; + if(v & 16r80) + return int_decode(a, i, n, v&16r7F, 1); + else if(v == 16r80) # indefinite length + ok = 0; + else + num = v; + } + else + ok = 0; + if(!ok && X509_DEBUG) + log("der_declen: syntax error"); + return (ok, i, num); +} + +# [private] +# parse der encoded algorithm identifier + +decode_algid(a: array of byte): (int, ref AlgIdentifier) +{ + (err, el) := asn1->decode(a); + if(err != "") { + if(X509_DEBUG) + log("decode_algid: " + err); + return (0, nil); + } + return parse_alg(el); +} + + +## TBS (Public Key) Certificate is signed by Certificate Authority and contains +## information of public key usage (as a comparison of Certificate Revocation List +## and Attribute Certificate). + +# [public] +# constructs a certificate by parsing a der encoded certificate +# returns error if parsing is failed or nil if parsing is ok + +certsyn(s: string): (string, ref Certificate) +{ + if(0) + sys->fprint(sys->fildes(2), "cert: %s\n", s); + return ("certificate syntax: "+s, nil); +} + +# Certificate ::= SEQUENCE { +# certificateInfo CertificateInfo, +# signatureAlgorithm AlgorithmIdentifier, +# signature BIT STRING } +# +# CertificateInfo ::= SEQUENCE { +# version [0] INTEGER DEFAULT v1 (0), +# serialNumber INTEGER, +# signature AlgorithmIdentifier, +# issuer Name, +# validity Validity, +# subject Name, +# subjectPublicKeyInfo SubjectPublicKeyInfo } +# (version v2 has two more fields, optional unique identifiers for +# issuer and subject; since we ignore these anyway, we won't parse them) +# +# Validity ::= SEQUENCE { +# notBefore UTCTime, +# notAfter UTCTime } +# +# SubjectPublicKeyInfo ::= SEQUENCE { +# algorithm AlgorithmIdentifier, +# subjectPublicKey BIT STRING } +# +# AlgorithmIdentifier ::= SEQUENCE { +# algorithm OBJECT IDENTIFER, +# parameters ANY DEFINED BY ALGORITHM OPTIONAL } +# +# Name ::= SEQUENCE OF RelativeDistinguishedName +# +# RelativeDistinguishedName ::= SETOF SIZE(1..MAX) OF AttributeTypeAndValue +# +# AttributeTypeAndValue ::= SEQUENCE { +# type OBJECT IDENTIFER, +# value DirectoryString } +# (selected attributes have these Object Ids: +# commonName {2 5 4 3} +# countryName {2 5 4 6} +# localityName {2 5 4 7} +# stateOrProvinceName {2 5 4 8} +# organizationName {2 5 4 10} +# organizationalUnitName {2 5 4 11} +# ) +# +# DirectoryString ::= CHOICE { +# teletexString TeletexString, +# printableString PrintableString, +# universalString UniversalString } +# +# See rfc1423, rfc2437 for AlgorithmIdentifier, subjectPublicKeyInfo, signature. + +Certificate.decode(a: array of byte): (string, ref Certificate) +{ +parse: + # break on error + for(;;) { + (err, all) := asn1->decode(a); + if(err != "") + return certsyn(err); + c := ref Certificate; + + # certificate must be a ASN1 sequence + (ok, el) := all.is_seq(); + if(!ok) + return certsyn("invalid certificate sequence"); + + if(len el == 3){ # ssl3.b uses CertificateInfo; others use Certificate (TO DO: fix this botch) + certinfo := hd el; + sigalgid := hd tl el; + sigbits := hd tl tl el; + + # certificate info is another ASN1 sequence + (ok, el) = certinfo.is_seq(); + if(!ok || len el < 6) + return certsyn("invalid certificate info sequence"); + } + + c.version = 0; # set to default (v1) + (ok, c.version) = parse_version(hd el); + if(!ok) + return certsyn("can't parse version"); + if(c.version > 0) { + el = tl el; + if(len el < 6) + break parse; + } + # serial number + (ok, c.serial_number) = parse_sernum(hd el); + if(!ok) + return certsyn("can't parse serial number"); + el = tl el; + # signature algorithm + (ok, c.sig) = parse_alg(hd el); + if(!ok) + return certsyn("can't parse sigalg"); + el = tl el; + # issuer + (ok, c.issuer) = parse_name(hd el); + if(!ok) + return certsyn("can't parse issuer"); + el = tl el; + # validity + evalidity := hd el; + (ok, c.validity) = parse_validity(evalidity); + if(!ok) + return certsyn("can't parse validity"); + el = tl el; + # Subject + (ok, c.subject) = parse_name(hd el); + if(!ok) + return certsyn("can't parse subject"); + el = tl el; + # SubjectPublicKeyInfo + (ok, c.subject_pkinfo) = parse_pkinfo(hd el); + if(!ok) + return certsyn("can't parse subject pk info"); + el = tl el; + # OPTIONAL for v2 and v3, must be in order + # issuer unique identifier + if(c.version == 0 && el != nil) + return certsyn("bad unique ID"); + if(el != nil) { + if(c.version < 1) # at least v2 + return certsyn("invalid v1 cert"); + (ok, c.issuer_uid) = parse_uid(hd el, 1); + if(ok) + el = tl el; + } + # subject unique identifier + if(el != nil) { + if(c.version < 1) # v2 or v3 + return certsyn("invalid v1 cert"); + (ok, c.issuer_uid) = parse_uid(hd el, 2); + if(ok) + el = tl el; + } + # extensions + if(el != nil) { + if(c.version < 2) # must be v3 + return certsyn("invalid v1/v2 cert"); + e : ref Elem; + (ok, e) = is_context(hd el, 3); + if (!ok) + break parse; + (ok, c.exts) = parse_extlist(e); + if(!ok) + return certsyn("can't parse extension list"); + el = tl el; + } + # must be no more left + if(el != nil) + return certsyn("unexpected data at end"); + return ("", c); + } + + return ("certificate: syntax error", nil); +} + +# [public] +# a der encoding of certificate data; returns (error, nil) tuple in failure + +Certificate.encode(c: self ref Certificate): (string, array of byte) +{ +pack: + for(;;) { + el: list of ref Elem; + # always has a version packed + e_version := pack_version(c.version); + if(e_version == nil) + break pack; + el = e_version :: el; + # serial number + e_sernum := pack_sernum(c.serial_number); + if(e_sernum == nil) + break pack; + el = e_sernum :: el; + # algorithm + e_sig := pack_alg(c.sig); + if(e_sig == nil) + break pack; + el = e_sig :: el; + # issuer + e_issuer := pack_name(c.issuer); + if(e_issuer == nil) + break pack; + el = e_issuer :: el; + # validity + e_validity := pack_validity(c.validity); + if(e_validity == nil) + break pack; + el = e_validity :: el; + # subject + e_subject := pack_name(c.subject); + if(e_subject == nil) + break pack; + el = e_subject :: el; + # public key info + e_pkinfo := pack_pkinfo(c.subject_pkinfo); + if(e_pkinfo == nil) + break pack; + el = e_pkinfo :: el; + # issuer unique identifier + if(c.issuer_uid != nil) { + e_issuer_uid := pack_uid(c.issuer_uid); + if(e_issuer_uid == nil) + break pack; + el = e_issuer_uid :: el; + } + # subject unique identifier + if(c.subject_uid != nil) { + e_subject_uid := pack_uid(c.subject_uid); + if(e_subject_uid == nil) + break pack; + el = e_subject_uid :: el; + } + # extensions + if(c.exts != nil) { + e_exts := pack_extlist(c.exts); + if(e_exts == nil) + break pack; + el = e_exts :: el; + } + # SEQUENCE order is important + lseq: list of ref Elem; + while(el != nil) { + lseq = (hd el) :: lseq; + el = tl el; + } + all := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(lseq)); + return asn1->encode(all); + } + return ("incompleted certificate; unable to pack", nil); +} + +# [public] +# converts content of a certificate as visible string + +Certificate.tostring(c: self ref Certificate): string +{ + s := "\tTBS Certificate"; + s += "\n\tVersion:\n\t\t" + string c.version; + s += "\n\tSerialNumber:\n\t\t" + c.serial_number.iptostr(10); + s += "\n\tSignature: " + c.sig.tostring(); + s += "\n\tIssuer: " + c.issuer.tostring(); + s += "\n\tValidity: " + c.validity.tostring("local"); + s += "\n\tSubject: " + c.subject.tostring(); + s += "\n\tSubjectPKInfo: " + c.subject_pkinfo.tostring(); + s += "\n\tIssuerUID: " + bastr(c.issuer_uid); + s += "\n\tSubjectUID: " + bastr(c.subject_uid); + s += "\n\tExtensions: "; + exts := c.exts; + while(exts != nil) { + s += "\t\t" + (hd exts).tostring(); + exts = tl exts; + } + return s; +} + +# [public] + +Certificate.is_expired(c: self ref Certificate, date: int): int +{ + if(date > c.validity.not_after || date < c.validity.not_before) + return 1; + + return 0; +} + + +# [private] +# version is optional marked by explicit context tag 0; no version is +# required if default version (v1) is used + +parse_version(e: ref Elem): (int, int) +{ + ver := 0; + (ans, ec) := is_context(e, 0); + if(ans) { + ok := 0; + (ok, ver) = ec.is_int(); + if(!ok || ver < 0 || ver > 2) + return (0, -1); + } + return (1, ver); +} + +# [private] + +pack_version(v: int): ref Elem +{ + return ref Elem(Tag(Universal, INTEGER, 0), ref Value.Int(v)); +} + +# [private] + +parse_sernum(e: ref Elem): (int, ref IPint) +{ + (ok, a) := e.is_bigint(); + if(ok) + return (1, IPint.bebytestoip(a)); + if(X509_DEBUG) + log("parse_sernum: syntax error"); + return (0, nil); +} + +# [private] + +pack_sernum(sn: ref IPint): ref Elem +{ + return ref Elem(Tag(Universal, INTEGER, 0), ref Value.BigInt(sn.iptobebytes())); +} + +# [private] + +parse_alg(e: ref Elem): (int, ref AlgIdentifier) +{ +parse: + for(;;) { + (ok, el) := e.is_seq(); + if(!ok || el == nil) + break parse; + oid: ref Oid; + (ok, oid) = (hd el).is_oid(); + if(!ok) + break parse; + el = tl el; + params: array of byte; + if(el != nil) { + # TODO: determine the object type based on oid + # then parse params + #unused: int; + #(ok, unused, params) = (hd el).is_bitstring(); + #if(!ok || unused || tl el != nil) + # break parse; + } + return (1, ref AlgIdentifier(oid, params)); + } + if(X509_DEBUG) + log("parse_alg: syntax error"); + return (0, nil); +} + +# [private] + +pack_alg(a: ref AlgIdentifier): ref Elem +{ + if(a.oid != nil) { + el: list of ref Elem; + el = ref Elem(Tag(Universal, ASN1->OBJECT_ID, 0), ref Value.ObjId(a.oid)) :: nil; + if(a.parameter != nil) { + el = ref Elem( + Tag(Universal, BIT_STRING, 0), + ref Value.BitString(0, a.parameter) + ) :: el; + } + return ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + } + return nil; +} + +# [private] + +parse_name(e: ref Elem): (int, ref Name) +{ +parse: + for(;;) { + (ok, el) := e.is_seq(); + if(!ok) + break parse; + lrd: list of ref RDName; + while(el != nil) { + rd: ref RDName; + (ok, rd) = parse_rdname(hd el); + if(!ok) + break parse; + lrd = rd :: lrd; + el = tl el; + } + # SEQUENCE + l: list of ref RDName; + while(lrd != nil) { + l = (hd lrd) :: l; + lrd = tl lrd; + } + return (1, ref Name(l)); + } + if(X509_DEBUG) + log("parse_name: syntax error"); + return (0, nil); +} + +# [private] + +pack_name(n: ref Name): ref Elem +{ + el: list of ref Elem; + + lrd := n.rd_names; + while(lrd != nil) { + rd := pack_rdname(hd lrd); + if(rd == nil) + return nil; + el = rd :: el; + lrd = tl lrd; + } + # reverse order + l: list of ref Elem; + while(el != nil) { + l = (hd el) :: l; + el = tl el; + } + + return ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(l)); +} + +# [private] + +parse_rdname(e: ref Elem): (int, ref RDName) +{ +parse: + for(;;) { + (ok, el) := e.is_set(); # unordered + if(!ok) + break parse; + lava: list of ref AVA; + while(el != nil) { + ava: ref AVA; + (ok, ava) = parse_ava(hd el); + if(!ok) + break parse; + lava = ava :: lava; + el = tl el; + } + return (1, ref RDName(lava)); + } + if(X509_DEBUG) + log("parse_rdname: syntax error"); + return (0, nil); +} + +# [private] + +pack_rdname(r: ref RDName): ref Elem +{ + el: list of ref Elem; + lava := r.avas; + while(lava != nil) { + ava := pack_ava(hd lava); + if(ava == nil) + return nil; + el = ava :: el; + lava = tl lava; + } + return ref Elem(Tag(Universal, ASN1->SET, 1), ref Value.Set(el)); +} + +# [private] + +parse_ava(e: ref Elem): (int, ref AVA) +{ +parse: + for(;;) { + (ok, el) := e.is_seq(); + if(!ok || len el != 2) + break parse; + a := ref AVA; + (ok, a.oid) = (hd el).is_oid(); + if(!ok) + break parse; + el = tl el; + (ok, a.value) = (hd el).is_string(); + if(!ok) + break parse; + return (1, a); + } + if(X509_DEBUG) + log("parse_ava: syntax error"); + return (0, nil); +} + +# [private] + +pack_ava(a: ref AVA): ref Elem +{ + el: list of ref Elem; + if(a.oid == nil || a.value == "") + return nil; + # Note: order is important + el = ref Elem(Tag(Universal, ASN1->GeneralString, 0), ref Value.String(a.value)) :: el; + el = ref Elem(Tag(Universal, ASN1->OBJECT_ID, 0), ref Value.ObjId(a.oid)) :: el; + return ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); +} + +# [private] + +parse_validity(e: ref Elem): (int, ref Validity) +{ +parse: + for(;;) { + (ok, el) := e.is_seq(); + if(!ok || len el != 2) + break parse; + v := ref Validity; + (ok, v.not_before) = parse_time(hd el, UTCTime); + if(!ok) + break parse; + el = tl el; + (ok, v.not_after) = parse_time(hd el, UTCTime); + if(!ok) + break parse; + return (1, v); + } + if(X509_DEBUG) + log("parse_validity: syntax error"); + return (0, nil); +} + +# [private] +# standard says only UTC Time allowed for TBS Certificate, but there is exception of +# GeneralizedTime for CRL and Attribute Certificate. Parsing is based on format of +# UTCTime, GeneralizedTime or undetermined (any int not UTCTime or GeneralizedTime). + +parse_time(e: ref Elem, format: int): (int, int) +{ +parse: + for(;;) { + (ok, date) := e.is_time(); + if(!ok) + break parse; + if(e.tag.num != UTCTime && e.tag.num != GeneralizedTime) + break parse; + if(format == UTCTime && e.tag.num != UTCTime) + break parse; + if(format == GeneralizedTime && e.tag.num != GeneralizedTime) + break parse; + t := decode_time(date, e.tag.num); + if(t < 0) + break parse; + return (1, t); + } + if(X509_DEBUG) + log("parse_time: syntax error"); + return (0, -1); +} + +# [private] +# decode a BER encoded UTC or Generalized time into epoch (seconds since 1/1/1970 GMT) +# UTC time format: YYMMDDhhmm[ss](Z|(+|-)hhmm) +# Generalized time format: YYYYMMDDhhmm[ss.s(...)](Z|(+|-)hhmm[ss.s(...)) + +decode_time(date: string, format: int): int +{ + time := ref Daytime->Tm; +parse: + for(;;) { + i := 0; + if(format == UTCTime) { + if(len date < 11) + break parse; + time.year = get2(date, i); + if(time.year < 0) + break parse; + if(time.year < 70) + time.year += 100; + i += 2; + } + else { + if(len date < 13) + break parse; + time.year = get2(date, i); + if(time.year-19 < 0) + break parse; + time.year = (time.year - 19)*100; + i += 2; + time.year += get2(date, i); + i += 2; + } + time.mon = get2(date, i) - 1; + if(time.mon < 0 || time.mon > 11) + break parse; + i += 2; + time.mday = get2(date, i); + if(time.mday < 1 || time.mday > 31) + break parse; + i += 2; + time.hour = get2(date, i); + if(time.hour < 0 || time.hour > 23) + break parse; + i += 2; + time.min = get2(date, i); + if(time.min < 0 || time.min > 59) + break parse; + i += 2; + if(int date[i] >= '0' && int date[i] <= '9') { + if(len date < i+3) + break parse; + time.sec = get2(date, i); + if(time.sec < 0 || time.sec > 59) + break parse; + i += 2; + if(format == GeneralizedTime) { + if((len date < i+3) || int date[i++] != '.') + break parse; + # ignore rest + ig := int date[i]; + while(ig >= '0' && ig <= '9' && i++ < len date) { + ig = int date[i]; + } + } + } + else { + time.sec = 0; + } + zf := int date[i]; + if(zf != 'Z' && zf != '+' && zf != '-') + break parse; + if(zf == 'Z') { + if(len date != i+1) + break parse; + time.tzoff = 0; + } + else { + if(len date < i + 3) + break parse; + time.tzoff = get2(date, i+1); + if(time.tzoff < 0 || time.tzoff > 23) + break parse; + i += 2; + min := get2(date, i); + if(min < 0 || min > 59) + break parse; + i += 2; + sec := 0; + if(i != len date) { + if(format == UTCTime || len date < i+4) + break parse; + sec = get2(date, i); + i += 2; + # ignore the rest + } + time.tzoff = (time.tzoff*60 + min)*60 + sec; + if(zf == '-') + time.tzoff = -time.tzoff; + } + return daytime->tm2epoch(time); + } + if(X509_DEBUG) + log("decode_time: syntax error: " + + sys->sprint("year=%d mon=%d mday=%d hour=%d min=%d, sec=%d", + time.year, time.mon, time.mday, time.hour, time.min, time.sec)); + return -1; +} + +# [private] +# pack as UTC time + +pack_validity(v: ref Validity): ref Elem +{ + el: list of ref Elem; + el = ref Elem( + Tag(Universal, UTCTime, 0), + ref Value.String(pack_time(v.not_before, UTCTime)) + ) :: nil; + el = ref Elem( + Tag(Universal, UTCTime, 0), + ref Value.String(pack_time(v.not_after, UTCTime)) + ) :: el; + return ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); +} + +# [private] +# Format must be either UTCTime or GeneralizedTime +# TODO: convert to coordinate time + +pack_time(t: int, format: int): string +{ + date := array [32] of byte; + tm := daytime->gmt(t); + + i := 0; + if(format == UTCTime) { + i = put2(date, tm.year, i); + } + else { # GeneralizedTime + i = put2(date, 19 + tm.year/100, i); + i = put2(date, tm.year%100, i); + } + i = put2(date, tm.mon, i); + i = put2(date, tm.mday, i); + i = put2(date, tm.hour, i); + i = put2(date, tm.min, i); + if(tm.sec != 0) { + if(format == UTCTime) + i = put2(date, tm.sec, i); + else { + i = put2(date, tm.sec, i); + date[i++] = byte '.'; + date[i++] = byte 0; + } + } + if(tm.tzoff == 0) { + date[i++] = byte 'Z'; + } + else { + off := tm.tzoff; + if(tm.tzoff < 0) { + off = -off; + date[i++] = byte '-'; + } + else { + date[i++] = byte '+'; + } + hoff := int (off/3600); + moff := int ((off%3600)/60); + soff := int ((off%3600)%60); + i = put2(date, hoff, i); + i = put2(date, moff, i); + if(soff) { + if(format == UTCTime) + i = put2(date, soff, i); + else { + i = put2(date, soff, i); + date[i++] = byte '.'; + date[i++] = byte 0; + } + } + } + return string date[0:i]; +} + +# [private] + +parse_pkinfo(e: ref Elem): (int, ref SubjectPKInfo) +{ +parse: + for(;;) { + p := ref SubjectPKInfo; + (ok, el) := e.is_seq(); + if(!ok || len el != 2) + break parse; + (ok, p.alg_id) = parse_alg(hd el); + if(!ok) + break parse; + unused: int; + (ok, unused, p.subject_pk) = (hd tl el).is_bitstring(); + if(!ok || unused != 0) + break parse; + return (1, p); + } + if(X509_DEBUG) + log("parse_pkinfo: syntax error"); + return (0, nil); +} + +# [private] + +pack_pkinfo(p: ref SubjectPKInfo): ref Elem +{ + el: list of ref Elem; + # SEQUENCE order is important + el = ref Elem( + Tag(Universal, BIT_STRING, 0), + ref Value.BitString(0, p.subject_pk) # 0 bits unused ? + ) :: nil; + el = pack_alg(p.alg_id) :: el; + return ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); +} + +# [private] + +parse_uid(e: ref Elem, num: int): (int, array of byte) +{ + ok, unused : int; + uid : array of byte; + e2 : ref Elem; +parse: + for(;;) { + (ok, e2) = is_context(e, num); + if (!ok) + break parse; + e = e2; + + (ok, unused, uid) = e.is_bitstring(); +# if(!ok || unused != 0) + if(!ok) + break parse; + return (1, uid); + } + if(X509_DEBUG) + log("parse_uid: syntax error"); + return (0, nil); +} + +# [private] + +pack_uid(u: array of byte): ref Elem +{ + return ref Elem(Tag(Universal, ASN1->BIT_STRING, 0), ref Value.BitString(0,u)); +} + +# [private] + +parse_extlist(e: ref Elem): (int, list of ref Extension) +{ +parse: + # dummy loop for breaking out of + for(;;) { + l: list of ref Extension; + (ok, el) := e.is_seq(); + if(!ok) + break parse; + while(el != nil) { + ext := ref Extension; + (ok, ext) = parse_extension(hd el); + if(!ok) + break parse; + l = ext :: l; + el = tl el; + } + # sort to order + nl: list of ref Extension; + while(l != nil) { + nl = (hd l) :: nl; + l = tl l; + } + return (1, nl); + } + if(X509_DEBUG) + log("parse_extlist: syntax error"); + return (0, nil); +} + +# [private] + +pack_extlist(e: list of ref Extension): ref Elem +{ + el: list of ref Elem; + exts := e; + while(exts != nil) { + ext := pack_extension(hd exts); + if(ext == nil) + return nil; + el = ext :: el; + exts = tl exts; + } + # reverse order + l: list of ref Elem; + while(el != nil) { + l = (hd el) :: l; + el = tl el; + } + return ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(l)); +} + +# [private] +# Require further parse to check oid if critical is set to TRUE (see parse_exts) + +parse_extension(e: ref Elem): (int, ref Extension) +{ +parse: + for(;;) { + ext := ref Extension; + (ok, el) := e.is_seq(); + if(!ok) + break parse; + oid: ref Oid; + (ok, oid) = (hd el).is_oid(); + if(!ok) + break parse; + ext.oid = oid; + el = tl el; + # BOOLEAN DEFAULT FALSE + (ok, ext.critical) = (hd el).is_int(); + if(ok) + el = tl el; + else + ext.critical = 0; + if (len el != 1) { + break parse; + } + (ok, ext.value) = (hd el).is_octetstring(); + if(!ok) + break parse; + return (1, ext); + } + if(X509_DEBUG) + log("parse_extension: syntax error"); + return (0, nil); +} + +# [private] + +pack_extension(e: ref Extension): ref Elem +{ + el: list of ref Elem; + + if(e.oid == nil || (e.critical !=0 && e.critical != 1) || e.value == nil) + return nil; + # SEQUENCE order + el = ref Elem(Tag(Universal, OCTET_STRING, 0), ref Value.Octets(e.value)) :: el; + el = ref Elem(Tag(Universal, BOOLEAN, 0), ref Value.Bool(e.critical)) :: el; + el = ref Elem(Tag(Universal, OBJECT_ID, 0), ref Value.ObjId(e.oid)) :: el; + return ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); +} + +# [public] + +AlgIdentifier.tostring(a: self ref AlgIdentifier): string +{ + return "\n\t\toid: " + a.oid.tostring() + "\n\t\twith parameter: "+ bastr(a.parameter); +} + +# [public] + +Name.equal(a: self ref Name, b: ref Name): int +{ + rda := a.rd_names; + rdb := b.rd_names; + if(len rda != len rdb) + return 0; + while(rda != nil && rdb != nil) { + ok := (hd rda).equal(hd rdb); + if(!ok) + return 0; + rda = tl rda; + rdb = tl rdb; + } + + return 1; +} + +# [public] +# The sequence of RelativeDistinguishedName's gives a sort of pathname, from most general to +# most specific. Each element of the path can be one or more (but usually just one) +# attribute-value pair, such as countryName="US". We'll just form a "postal-style" address +# string by concatenating the elements from most specific to least specific, separated by commas. + +Name.tostring(a: self ref Name): string +{ + path: string; + rdn := a.rd_names; + while(rdn != nil) { + path += (hd rdn).tostring(); + rdn = tl rdn; + if(rdn != nil) + path += ","; + } + return path; +} + +# [public] +# The allocation of distinguished names is the responsibility of the Naming Authorities. +# Each user shall therefore trust the Naming Authorities not to issue duplicate distinguished +# names. The comparison shall be unique one to one match but may not in the same order. + +RDName.equal(a: self ref RDName, b: ref RDName): int +{ + if(len a.avas != len b.avas) + return 0; + aa := a.avas; + ba := b.avas; + while(aa != nil) { + found:= 0; + rest: list of ref AVA; + while(ba != nil) { + ok := (hd ba).equal(hd ba); + if(!ok) + rest = (hd aa) :: rest; + else { + if(found) + return 0; + found = 1; + } + ba = tl ba; + } + if(found == 0) + return 0; + ba = rest; + aa = tl aa; + } + return 1; +} + +# [public] + +RDName.tostring(a: self ref RDName): string +{ + s: string; + avas := a.avas; + while(avas != nil) { + s += (hd avas).tostring(); + avas = tl avas; + if(avas != nil) + s += "-"; + } + return s; +} + +# [public] +# AVA are equal if they have the same type oid and value + +AVA.equal(a: self ref AVA, b: ref AVA): int +{ + # TODO: need to match different encoding (T61String vs. IA5String) + if(a.value != b.value) + return 0; + + return oid_cmp(a.oid, b.oid); +} + +# [public] + +AVA.tostring(a: self ref AVA): string +{ + return a.value; +} + +# [public] + +Validity.tostring(v: self ref Validity, format: string): string +{ + s: string; + if(format == "local") { + s = "\n\t\tnot_before[local]: "; + s += daytime->text(daytime->local(v.not_before)); + s += "\n\t\tnot_after[local]: "; + s += daytime->text(daytime->local(v.not_after)); + } + else if(format == "gmt") { + s = "\n\t\tnot_before[gmt]: "; + s += daytime->text(daytime->gmt(v.not_before)); + s += "\n\t\tnot_after[gmt]: "; + s += daytime->text(daytime->gmt(v.not_after)); + } + else + s += "unknown format: " + format; + return s; +} + +# [public] + +SubjectPKInfo.getPublicKey(pkinfo: self ref SubjectPKInfo): (string, int, ref PublicKey) +{ +parse: + for(;;) { + pk: ref PublicKey; + id := asn1->oid_lookup(pkinfo.alg_id.oid, pkcs->objIdTab); + case id { + PKCS->id_pkcs_rsaEncryption or + PKCS->id_pkcs_md2WithRSAEncryption or + PKCS->id_pkcs_md4WithRSAEncryption or + PKCS->id_pkcs_md5WithRSAEncryption => + (err, k) := pkcs->decode_rsapubkey(pkinfo.subject_pk); + if(err != nil) + break parse; + pk = ref PublicKey.RSA(k); + PKCS->id_algorithm_shaWithDSS => + (err, k) := pkcs->decode_dsspubkey(pkinfo.subject_pk); + if(err != nil) + break parse; + pk = ref PublicKey.DSS(k); + PKCS->id_pkcs_dhKeyAgreement => + (err, k) := pkcs->decode_dhpubkey(pkinfo.subject_pk); + if(err != nil) + break parse; + pk = ref PublicKey.DH(k); + * => + break parse; + } + return ("", id, pk); + } + return ("subject public key: syntax error", -1, nil); +} + +# [public] + +SubjectPKInfo.tostring(pkinfo: self ref SubjectPKInfo): string +{ + s := pkinfo.alg_id.tostring(); + s += "\n\t\tencoded key: " + bastr(pkinfo.subject_pk); + return s; +} + +# [public] + +Extension.tostring(e: self ref Extension): string +{ + s := "oid: " + e.oid.tostring(); + s += "critical: "; + if(e.critical) + s += "true "; + else + s += "false "; + s += bastr(e.value); + return s; +} + +## Certificate PATH +## A list of certificates needed to allow a particular user to obtain +## the public key of another, is known as a certificate path. A +## certificate path logically forms an unbroken chain of trusted +## points in the DIT between two users wishing to authenticate. +## To establish a certification path between user A and user B using +## the Directory without any prior information, each CA may store +## one certificate and one reverse certificate designated as +## corresponding to its superior CA. + +# The ASN.1 data byte definitions for certificates and a certificate +# path is +# +# Certificates ::= SEQUENCE { +# userCertificate Certificate, +# certificationPath ForwardCertificationPath OPTIONAL } +# +# ForwardCertificationPath ::= SEQUENCE OF CrossCertificates +# CrossCertificates ::= SET OF Certificate +# + +# [public] +# Verify a decoded certificate chain in order of root to user. This is useful for +# non_ASN.1 encoding of certificates, e.g. in SSL. Return (0, error string) if +# verification failure or (1, "") if verification ok + +verify_certchain(cs: list of array of byte): (int, string) +{ + lsc: list of (ref Signed, ref Certificate); + + l := cs; + while(l != nil) { + (err, s) := Signed.decode(hd l); + if(err != "") + return (0, err); + c: ref Certificate; + (err, c) = Certificate.decode(s.tobe_signed); + if(err != "") + return (0, err); + lsc = (s, c) :: lsc; + l = tl l; + } + # reverse order + a: list of (ref Signed, ref Certificate); + while(lsc != nil) { + a = (hd lsc) :: a; + lsc = tl lsc; + } + return verify_certpath(a); +} + +# [private] +# along certificate path; first certificate is root + +verify_certpath(sc: list of (ref Signed, ref Certificate)): (int, string) +{ + # verify self-signed root certificate + (s, c) := hd sc; + # TODO: check root RDName with known CAs and using + # external verification of root - Directory service + (err, id, pk) := c.subject_pkinfo.getPublicKey(); + if(err != "") + return (0, err); + if(!is_validtime(c.validity) + || !c.issuer.equal(c.subject) + || !s.verify(pk, 0)) # TODO: prototype verify(key, ref AlgIdentifier)? + return (0, "verification failure"); + + sc = tl sc; + while(sc != nil) { + (ns, nc) := hd sc; + # TODO: check critical flags of extension list + # check alt names field + (err, id, pk) = c.subject_pkinfo.getPublicKey(); + if(err != "") + return (0, err); + if(!is_validtime(nc.validity) + || !nc.issuer.equal(c.subject) + || !ns.verify(pk, 0)) # TODO: move prototype as ? + return (0, "verification failure"); + (s, c) = (ns, nc); + sc = tl sc; + } + + return (1, ""); +} + +# [public] +is_validtime(validity: ref Validity): int +{ + # a little more expensive but more accurate + now := daytime->now(); + + # need some conversion here + if(now < validity.not_before || now > validity.not_after) + return 0; + + return 1; +} + +is_validpair(): int +{ + return 0; +} + +## Certificate Revocation List (CRL) +## +## A CRL is a time-stampted list identifying revoked certificates. It is signed by a +## Certificate Authority (CA) and made freely available in a public repository. +## +## Each revoked certificate is identified in a CRL by its certificate serial number. +## When a certificate-using system uses a certificate (e.g., for verifying a remote +## user's digital signature), that system not only checks the certificate signature +## and validity but also acquires a suitably-recent CRL and checks that the certificate +## serial number is not on that CRL. The meaning of "suitably-recent" may vary with +## local policy, but it usually means the most recently-issued CRL. A CA issues a new +## CRL on a regular periodic basis (e.g., hourly, daily, or weekly). Entries are added +## on CRLs as revocations occur, and an entry may be removed when the certificate +## expiration date is reached. + +# [public] + +CRL.decode(a: array of byte): (string, ref CRL) +{ +parse: + # break on error + for(;;) { + (err, all) := asn1->decode(a); + if(err != "") + break parse; + c := ref CRL; + # CRL must be a ASN1 sequence + (ok, el) := all.is_seq(); + if(!ok || len el < 3) + break parse; + c.version = 1; # set to default (v2) + (ok, c.version) = parse_version(hd el); + if(!ok) + break parse; + if(c.version < 0) { + el = tl el; + if(len el < 4) + break parse; + } + # signature algorithm + (ok, c.sig) = parse_alg(hd el); + if(!ok) + break parse; + el = tl el; + # issuer: who issues the CRL + (ok, c.issuer) = parse_name(hd el); + if(!ok) + break parse; + el = tl el; + # this update + (ok, c.this_update) = parse_time(hd el, UTCTime); + if(!ok) + break parse; + el = tl el; + # OPTIONAL, must be in order + # next_update + if(el != nil) { + (ok, c.next_update) = parse_time(hd el, UTCTime); + if(!ok) + break parse; + el = tl el; + } + # revoked certificates + if(el != nil) { + (ok, c.revoked_certs) = parse_revoked_certs(hd el); + if(!ok) + break parse; + el = tl el; + } + # extensions + if(el != nil) { + (ok, c.exts) = parse_extlist(hd el); + if(!ok) + break parse; + el = tl el; + } + # must be no more left + if(el != nil) + break parse; + return ("", c); + } + return ("CRL: syntax error", nil); +} + +# [public] + +CRL.encode(c: self ref CRL): (string, array of byte) +{ +pack: + for(;;) { + el: list of ref Elem; + # always has a version packed + e_version := pack_version(c.version); + if(e_version == nil) + break pack; + el = e_version :: el; + # algorithm + e_sig := pack_alg(c.sig); + if(e_sig == nil) + break pack; + el = e_sig :: el; + # crl issuer + e_issuer := pack_name(c.issuer); + if(e_issuer == nil) + break pack; + el = e_issuer :: el; + # validity + e_this_update := pack_time(c.this_update, UTCTime); + if(e_this_update == nil) + break pack; + el = ref Elem( + Tag(Universal, ASN1->UTCTime, 0), + ref Value.String(e_this_update) + ) :: el; + # next crl update + if(c.next_update != 0) { + e_next_update := pack_time(c.next_update, UTCTime); + if(e_next_update == nil) + break pack; + el = ref Elem( + Tag(Universal, ASN1->UTCTime, 0), + ref Value.String(e_next_update) + ) :: el; + } + # revoked certificates + if(c.revoked_certs != nil) { + e_revoked_certs := pack_revoked_certs(c.revoked_certs); + if(e_revoked_certs == nil) + break pack; + el = e_revoked_certs :: el; + } + # crl extensions + if(c.exts != nil) { + e_exts := pack_extlist(c.exts); + if(e_exts == nil) + break pack; + el = e_exts :: el; + } + # compose all elements + lseq: list of ref Elem; + while(el != nil) { + lseq = (hd el) :: lseq; + el = tl el; + } + all := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(lseq)); + (err, ret) := asn1->encode(all); + if(err != "") + break; + return ("", ret); + } + return ("incompleted CRL; unable to pack", nil); +} + +# [public] + +CRL.tostring(c: self ref CRL): string +{ + s := "Certificate Revocation List (CRL)"; + s += "\nVersion: " + string c.version; + s += "\nSignature: " + c.sig.tostring(); + s += "\nIssuer: " + c.issuer.tostring(); + s += "\nThis Update: " + daytime->text(daytime->local(c.this_update)); + s += "\nNext Update: " + daytime->text(daytime->local(c.next_update)); + s += "\nRevoked Certificates: "; + rcs := c.revoked_certs; + while(rcs != nil) { + s += "\t" + (hd rcs).tostring(); + rcs = tl rcs; + } + s += "\nExtensions: "; + exts := c.exts; + while(exts != nil) { + s += "\t" + (hd exts).tostring(); + exts = tl exts; + } + return s; +} + +# [public] + +CRL.is_revoked(c: self ref CRL, sn: ref IPint): int +{ + es := c.revoked_certs; + while(es != nil) { + if(sn.eq((hd es).user_cert)) + return 1; + es = tl es; + } + return 0; +} + +# [public] + +RevokedCert.tostring(rc: self ref RevokedCert): string +{ + s := "Revoked Certificate"; + if(rc.user_cert == nil) + return s + " [Bad Format]\n"; + s += "\nSerial Number: " + rc.user_cert.iptostr(10); + if(rc.revoc_date != 0) + s += "\nRevocation Date: " + daytime->text(daytime->local(rc.revoc_date)); + if(rc.exts != nil) { + exts := rc.exts; + while(exts != nil) { + s += "\t" + (hd exts).tostring(); + exts = tl exts; + } + } + return s; +} + + +# [private] + +parse_revoked_certs(e: ref Elem): (int, list of ref RevokedCert) +{ + lc: list of ref RevokedCert; +parse: + for(;;) { + (ok, el) := e.is_seq(); + if(!ok) + break parse; + while(el != nil) { + c: ref RevokedCert; + (ok, c) = parse_revoked(hd el); + if(!ok) + break parse; + lc = c :: lc; + el = tl el; + } + + return (1, lc); + } + + return (0, nil); +} + +# [private] + +pack_revoked_certs(r: list of ref RevokedCert): ref Elem +{ + el: list of ref Elem; + + rs := r; + while(rs != nil) { + rc := pack_revoked(hd rs); + if(rc == nil) + return nil; + el = rc :: el; + rs = tl rs; + } + # reverse order + l: list of ref Elem; + while(el != nil) { + l = (hd el) :: l; + el = tl el; + } + return ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(l)); + +} + +# [private] + +parse_revoked(e: ref Elem): (int, ref RevokedCert) +{ +parse: + for(;;) { + c: ref RevokedCert; + (ok, el) := e.is_seq(); + if(!ok || len el < 2) + break parse; + uc: array of byte; + (ok, uc) = (hd el).is_bigint(); + if(!ok) + break parse; + c.user_cert = IPint.bebytestoip(uc); + el = tl el; + (ok, c.revoc_date) = parse_time(hd el, UTCTime); + if(!ok) + break parse; + el = tl el; + if(el != nil) { + (ok, c.exts) = parse_extlist(hd el); + if(!ok) + break parse; + } + return (1, c); + } + return (0, nil); +} + +# [private] + +pack_revoked(r: ref RevokedCert): ref Elem +{ + el: list of ref Elem; + if(r.exts != nil) { + e_exts := pack_extlist(r.exts); + if(e_exts == nil) + return nil; + el = e_exts :: el; + } + if(r.revoc_date != 0) { + e_date := pack_time(r.revoc_date, UTCTime); + if(e_date == nil) + return nil; + el = ref Elem( + Tag(Universal, ASN1->UTCTime, 0), + ref Value.String(e_date) + ) :: el; + } + if(r.user_cert == nil) + return nil; + el = ref Elem(Tag(Universal, INTEGER, 0), + ref Value.BigInt(r.user_cert.iptobebytes()) + ) :: el; + return ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); +} + +## The extensions field allows addition of new fields to the structure +## without modification to the ASN.1 definition. An extension field +## consists of an extension identifier, a criticality flag, and a +## canonical encoding of a data value of an ASN.1 type associated with +## the identified extension. For those extensions where ordering of +## individual extensions within the SEQUENCE is significant, the +## specification of those individual extensions shall include the rules +## for the significance of the ordering. When an implementation +## processing a certificate does not recognize an extension, if the +## criticality flag is FALSE, it may ignore that extension. If the +## criticality flag is TRUE, unrecognized extensions shall cause the +## structure to be considered invalid, i.e. in a certificate, an +## unrecognized critical extension would cause validation of a signature +## using that certificate to fail. + +# [public] + +cr_exts(es: list of ref Extension): list of ref Extension +{ + cr: list of ref Extension; + l := es; + while(l != nil) { + e := hd l; + if(e.critical == 1) + cr = e :: cr; + l = tl l; + } + return cr; +} + +# [public] + +noncr_exts(es: list of ref Extension): list of ref Extension +{ + ncr: list of ref Extension; + l := es; + while(l != nil) { + e := hd l; + if(e.critical == 0) + ncr = e :: ncr; + l = tl l; + } + return ncr; +} + +# [public] + +parse_exts(exts: list of ref Extension): (string, list of ref ExtClass) +{ + ets: list of ref ExtClass; + l := exts; + while(l != nil) { + ext := hd l; + (err, et) := ExtClass.decode(ext); + if(err != "") + return (err, nil); + ets = et :: ets; + l = tl l; + } + lseq: list of ref ExtClass; + while(ets != nil) { + lseq = (hd ets) :: lseq; + ets = tl ets; + } + return ("", lseq); +} + +# [public] + +ExtClass.decode(ext: ref Extension): (string, ref ExtClass) +{ + err: string; + eclass: ref ExtClass; + + oid := asn1->oid_lookup(ext.oid, objIdTab); + case oid { + id_ce_authorityKeyIdentifier => + (err, eclass) = decode_authorityKeyIdentifier(ext); + if(err == "" && ext.critical == 1) { + err = "authority key identifier: should be non-critical"; + break; + } + id_ce_subjectKeyIdentifier => + (err, eclass) = decode_subjectKeyIdentifier(ext); + if(err != "" && ext.critical != 0) { + err = "subject key identifier: should be non-critical"; + break; + } + id_ce_basicConstraints => + (err, eclass) = decode_basicConstraints(ext); + if(err == "" && ext.critical != 1) { + err = "basic constraints: should be critical"; + break; + } + id_ce_keyUsage => + (err, eclass) = decode_keyUsage(ext); + if(err == "" && ext.critical != 1) { + err = "key usage: should be critical"; + break; + } + id_ce_privateKeyUsage => + (err, eclass) = decode_privateKeyUsage(ext); + if(err == "" && ext.critical != 0) { + err = "private key usage: should be non-critical"; + break; + } + id_ce_policyMapping => + (err, eclass) = decode_policyMapping(ext); + if(err == "" && ext.critical != 0) { + err = "policy mapping: should be non-critical"; + break; + } + id_ce_certificatePolicies => + (err, eclass) = decode_certificatePolicies(ext); + # either critical or non-critical + id_ce_issuerAltName => + n: list of ref GeneralName; + (err, n) = decode_alias(ext); + if(err == "") + eclass = ref ExtClass.IssuerAltName(n); + # either critical or non-critical + id_ce_subjectAltName => + n: list of ref GeneralName; + (err, n) = decode_alias(ext); + if(err == "") + eclass = ref ExtClass.SubjectAltName(n); + # either critical or non-critical + id_ce_nameConstraints => + (err, eclass) = decode_nameConstraints(ext); + # either critical or non-critical + id_ce_policyConstraints => + (err, eclass) = decode_policyConstraints(ext); + # either critical or non-critical + id_ce_cRLNumber => + (err, eclass) = decode_cRLNumber(ext); + if(err == "" && ext.critical != 0) { + err = "crl number: should be non-critical"; + break; + } + id_ce_reasonCode => + (err, eclass) = decode_reasonCode(ext); + if(err == "" && ext.critical != 0) { + err = "crl reason: should be non-critical"; + break; + } + id_ce_instructionCode => + (err, eclass) = decode_instructionCode(ext); + if(err == "" && ext.critical != 0) { + err = "instruction code: should be non-critical"; + break; + } + id_ce_invalidityDate => + (err, eclass) = decode_invalidityDate(ext); + if(err == "" && ext.critical != 0) { + err = "invalidity date: should be non-critical"; + break; + } + id_ce_issuingDistributionPoint => + (err, eclass) = decode_issuingDistributionPoint(ext); + if(err == "" && ext.critical != 1) { + err = "issuing distribution point: should be critical"; + break; + } + id_ce_cRLDistributionPoint => + (err, eclass) = decode_cRLDistributionPoint(ext); + # either critical or non-critical + id_ce_certificateIssuer => + (err, eclass) = decode_certificateIssuer(ext); + if(err == "" && ext.critical != 1) { + err = "certificate issuer: should be critical"; + break; + } + id_ce_deltaCRLIndicator => + (err, eclass) = decode_deltaCRLIndicator(ext); + if(err == "" && ext.critical != 1) { + err = "delta crl indicator: should be critical"; + break; + } + id_ce_subjectDirectoryAttributes => + (err, eclass) = decode_subjectDirectoryAttributes(ext); + if(ext.critical != 0) { + err = "subject directory attributes should be non-critical"; + break; + } + * => + err = "unknown extension class"; + } + + return (err, eclass); +} + +# [public] + +ExtClass.encode(ec: self ref ExtClass, critical: int): ref Extension +{ + ext: ref Extension; + + if(critical) + ; # unused + pick c := ec { + AuthorityKeyIdentifier => + (err, a) := encode_authorityKeyIdentifier(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_authorityKeyIdentifier], 0, a); + SubjectKeyIdentifier => + (err, a) := encode_subjectKeyIdentifier(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_subjectKeyIdentifier], 0, a); + BasicConstraints => + (err, a) := encode_basicConstraints(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_basicConstraints], 0, a); + KeyUsage => + (err, a) := encode_keyUsage(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_keyUsage], 0, a); + PrivateKeyUsage => + (err, a) := encode_privateKeyUsage(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_privateKeyUsage], 0, a); + PolicyMapping => + (err, a) := encode_policyMapping(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_policyMapping], 0, a); + CertificatePolicies => + (err, a) := encode_certificatePolicies(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_certificatePolicies], 0, a); + IssuerAltName => + (err, a) := encode_alias(c.alias); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_issuerAltName], 0, a); + SubjectAltName => + (err, a) := encode_alias(c.alias); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_subjectAltName], 0, a); + NameConstraints => + (err, a) := encode_nameConstraints(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_nameConstraints], 0, a); + PolicyConstraints => + (err, a) := encode_policyConstraints(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_policyConstraints], 0, a); + CRLNumber => + (err, a) := encode_cRLNumber(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_cRLNumber], 0, a); + ReasonCode => + (err, a) := encode_reasonCode(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_reasonCode], 0, a); + InstructionCode => + (err, a) := encode_instructionCode(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_instructionCode], 0, a); + InvalidityDate => + (err, a) := encode_invalidityDate(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_invalidityDate], 0, a); + CRLDistributionPoint => + (err, a) := encode_cRLDistributionPoint(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_cRLDistributionPoint], 0, a); + IssuingDistributionPoint => + (err, a) := encode_issuingDistributionPoint(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_issuingDistributionPoint], 0, a); + CertificateIssuer => + (err, a) := encode_certificateIssuer(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_certificateIssuer], 0, a); + DeltaCRLIndicator => + (err, a) := encode_deltaCRLIndicator(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_deltaCRLIndicator], 0, a); + SubjectDirectoryAttributes => + (err, a) := encode_subjectDirectoryAttributes(c); + if(err == "") + ext = ref Extension(ref objIdTab[id_ce_subjectDirectoryAttributes], 0, a); + } + return ext; +} + +# [public] + +ExtClass.tostring(et: self ref ExtClass): string +{ + s: string; + + pick t := et { + AuthorityKeyIdentifier => + s = "Authority Key Identifier: "; + s += "\n\tid = " + bastr(t.id); + s += "\n\tissuer = " + t.issuer.tostring(); + s += "\n\tserial_number = " + bastr(t.serial_number.iptobebytes()); + SubjectKeyIdentifier => + s = "Subject Key Identifier "; + s += "\n\tid = " + bastr(t.id); + BasicConstraints => + s = "Basic Constraints: "; + s += "\n\tdepth = " + string t.depth; + KeyUsage => + s = "Key Usage: "; + s += "\n\tusage = "; + PrivateKeyUsage => + s = "Private Key Usage: "; + s += "\n\tusage = "; + PolicyMapping => + s = "Policy Mapping: "; + pl := t.pairs; + while(pl != nil) { + (issuer_oid, subject_oid) := hd pl; + s += "\n\t(" + issuer_oid.tostring() + ", " + subject_oid.tostring() + ")"; + pl = tl pl; + } + CertificatePolicies => + s = "Certificate Policies: "; + pl := t.policies; + while(pl != nil) { + s += (hd pl).tostring(); + pl = tl pl; + } + IssuerAltName => + s = "Issuer Alt Name: "; + al := t.alias; + while(al != nil) { + s += (hd al).tostring() + ","; + al = tl al; + } + SubjectAltName => + s = "Subject Alt Name: "; + al := t.alias; + while(al != nil) { + s += (hd al).tostring() + ","; + al = tl al; + } + NameConstraints => + s = "Name Constraints: "; + s += "\n\tpermitted = "; + p := t.permitted; + while(p != nil) { + s += (hd p).tostring(); + p = tl p; + } + s += "\n\texcluded = "; + e := t.excluded; + while(e != nil) { + s += (hd e).tostring(); + e = tl e; + } + PolicyConstraints => + s = "Policy Constraints: "; + s += "\n\trequire = " + string t.require; + s += "\n\tinhibit = " + string t.inhibit; + CRLNumber => + s = "CRL Number: "; + s += "\n\tcurrent crl number = " + string t.curr; + ReasonCode => + s = "Reason Code: "; + s += "\n\tcode = "; + InstructionCode => + s = "Instruction Code: "; + s += "\n\thold with oid = " + t.oid.tostring(); + InvalidityDate => + s = "Invalidity Date: "; + s += "\n\tdate = " + daytime->text(daytime->local(t.date)); + CRLDistributionPoint => + s = "CRL Distribution Point: "; + ps := t.ps; + while(ps != nil) { + s += (hd ps).tostring() + ","; + ps = tl ps; + } + IssuingDistributionPoint => + s = "Issuing Distribution Point: "; + CertificateIssuer => + s = "Certificate Issuer: "; + DeltaCRLIndicator => + s = "Delta CRL Indicator: "; + SubjectDirectoryAttributes => + s = "Subject Directory Attributes: "; + * => + s = "Unknown Extension: "; + } + + return s; +} + +# [private] + +decode_authorityKeyIdentifier(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, el) := all.is_seq(); + if(!ok) + break parse; + ak := ref ExtClass.AuthorityKeyIdentifier; + e := hd el; + (ok, e) = is_context(e, 0); + if(ok) { + (ok, ak.id) = e.is_octetstring(); + if(!ok) + break parse; + el = tl el; + } + if(el != nil && len el != 2) + break parse; + e = hd el; + (ok, e) = is_context(e, 1); + if(!ok) + break parse; + (ok, ak.issuer) = parse_gname(e); + if(!ok) + break parse; + e = hd tl el; + (ok, e) = is_context(e, 2); + if(!ok) + break parse; + (ok, ak.serial_number) = parse_sernum(e); + if(!ok) + break; + return ("", ak); + } + return ("syntax error", nil); +} + +# [private] + +encode_authorityKeyIdentifier(c: ref ExtClass.AuthorityKeyIdentifier): (string, array of byte) +{ + el: list of ref Elem; + if(c.serial_number != nil) { + (ok, e) := pack_context( + ref Elem( + Tag(Universal, INTEGER, 0), + ref Value.BigInt(c.serial_number.iptobebytes()) + ), + 2 + ); + if(!ok) + return ("syntax error", nil); + el = e :: nil; + } + if(c.issuer != nil) { + (ok, e) := pack_gname(c.issuer); + if(!ok) + return ("authority key identifier: encoding error", nil); + (ok, e) = pack_context(e, 1); + if(!ok) + return ("authority key identifier: encoding error", nil); + el = e :: el; + } + if(c.id != nil) { + (ok, e) := pack_context( + ref Elem( + Tag(Universal, OCTET_STRING, 0), + ref Value.Octets(c.id) + ), + 0 + ); + if(!ok) + return ("authority key identifier: encoding error", nil); + el = e :: el; + } + return asn1->encode(ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el))); +} + +# [private] + +decode_subjectKeyIdentifier(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, id) := all.is_octetstring(); + if(!ok) + break parse; + return ("", ref ExtClass.SubjectKeyIdentifier(id)); + + } + return ("subject key identifier: syntax error", nil); +} + +# [private] + +encode_subjectKeyIdentifier(c: ref ExtClass.SubjectKeyIdentifier): (string, array of byte) +{ + if(c.id == nil) + return ("syntax error", nil); + e := ref Elem(Tag(Universal, OCTET_STRING, 0), ref Value.Octets(c.id)); + return asn1->encode(e); +} + +# [private] + +decode_basicConstraints(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, el) := all.is_seq(); + if(!ok || len el != 2) + break parse; + ca: int; + (ok, ca) = (hd el).is_int(); # boolean + if(!ok || ca != 1) + break parse; + path: int; + (ok, path) = (hd tl el).is_int(); # integer + if(!ok || path < 0) + break parse; + return ("", ref ExtClass.BasicConstraints(path)); + } + return ("basic constraints: syntax error", nil); +} + +# [private] + +encode_basicConstraints(c: ref ExtClass.BasicConstraints): (string, array of byte) +{ + el: list of ref Elem; + el = ref Elem(Tag(Universal, INTEGER, 0), ref Value.Int(c.depth)) :: nil; + el = ref Elem(Tag(Universal, BOOLEAN, 0), ref Value.Bool(1)) :: el; + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return asn1->encode(e); +} + +# [private] + +decode_keyUsage(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + # assert bits can fit into a limbo int + if(len ext.value > 4) + break parse; + return ("", ref ExtClass.KeyUsage(b4int(ext.value))); + } + return ("key usage: syntax error", nil); +} + +# [private] + +encode_keyUsage(c: ref ExtClass.KeyUsage): (string, array of byte) +{ + return ("", int4b(c.usage)); +} + +# [private] + +decode_privateKeyUsage(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, el) := all.is_seq(); + if(!ok || len el < 1) # at least one exists + break parse; + v := ref Validity; + e := hd el; + (ok, e) = is_context(e, 0); + if(ok) { + (ok, v.not_before) = parse_time(e, GeneralizedTime); + if(!ok) + break parse; + el = tl el; + } + if(el != nil) { + e = hd el; + (ok, e) = is_context(e, 1); + if(!ok) + break parse; + (ok, v.not_after) = parse_time(e, GeneralizedTime); + if(!ok) + break parse; + } + return ("", ref ExtClass.PrivateKeyUsage(v)); + } + return ("private key usage: syntax error", nil); +} + +# [private] + +encode_privateKeyUsage(c: ref ExtClass.PrivateKeyUsage): (string, array of byte) +{ + el: list of ref Elem; + e: ref Elem; + ok := 1; + p := c.period; + if(p == nil) + return ("encode private key usage: imcomplete data", nil); + if(p.not_after > 0) { + t := pack_time(p.not_after, GeneralizedTime); + e = ref Elem(Tag(Universal, GeneralizedTime, 0), ref Value.String(t)); + (ok, e) = pack_context(e, 1); + if(!ok) + return ("encode private key usage: illegal context", nil); + el = e :: nil; + } + if(p.not_before > 0) { + t := pack_time(p.not_before, GeneralizedTime); + e = ref Elem(Tag(Universal, GeneralizedTime, 0), ref Value.String(t)); + (ok, e) = pack_context(e, 0); + if(!ok) + return ("encode private key usage: illegal context", nil); + el = e :: el; + } + e = ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return asn1->encode(e); +} + +# [private] + +decode_policyMapping(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, el) := all.is_seq(); + if(!ok) + break parse; + l_pm: list of (ref Oid, ref Oid); + while(el != nil) { + e_pm: list of ref Elem; + (ok, e_pm) = (hd el).is_seq(); + if(!ok || len e_pm != 2) + break parse; + idp, sdp: ref Oid; + (ok, idp) = (hd e_pm).is_oid(); + if(!ok) + break parse; + (ok, sdp) = (hd tl e_pm).is_oid(); + if(!ok) + break parse; + l_pm = (idp, sdp) :: l_pm; + } + # reverse the order + l: list of (ref Oid, ref Oid); + while(l_pm != nil) { + l = (hd l_pm) :: l; + l_pm = tl l_pm; + } + return ("", ref ExtClass.PolicyMapping(l)); + } + return ("policy mapping: syntax error", nil); +} + +# [private] + +encode_policyMapping(c: ref ExtClass.PolicyMapping): (string, array of byte) +{ + el, pel: list of ref Elem; + if(c.pairs == nil) + return ("policy mapping: incomplete data", nil); + pl := c.pairs; + while(pl != nil) { + (a, b) := hd pl; + if(a == nil || b == nil) + return ("policy mapping: incomplete data", nil); + be := ref Elem(Tag(Universal, OBJECT_ID, 0), ref Value.ObjId(b)); + ae := ref Elem(Tag(Universal, OBJECT_ID, 0), ref Value.ObjId(a)); + pel = ref Elem( + Tag(Universal, SEQUENCE, 1), + ref Value.Seq(ae::be::nil) + ) :: pel; + pl = tl pl; + } + while(pel != nil) { + el = (hd pel) :: el; + pel = tl pel; + } + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return asn1->encode(e); +} + +# [private] + +decode_certificatePolicies(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, el) := all.is_seq(); + if(!ok) + break parse; + l_pi: list of ref PolicyInfo; + while(el != nil) { + e_pi: list of ref Elem; + (ok, e_pi) = (hd el).is_seq(); + if(!ok || len e_pi > 2 || len e_pi < 1) + break parse; + pi: ref PolicyInfo; + (ok, pi.oid) = (hd e_pi).is_oid(); + if(!ok) + break parse; + # get optional policy qualifier info + e_pi = tl e_pi; + if(e_pi != nil) { + e_pq: list of ref Elem; + (ok, e_pq) = (hd e_pi).is_seq(); + if(!ok || len e_pq > 2 || len e_pq < 1) + break parse; + l_pq: list of ref PolicyQualifier; + while(e_pq != nil) { + pq: ref PolicyQualifier; + (ok, pq.oid) = (hd e_pq).is_oid(); + if(!ok || pq.oid == nil) + break parse; + # get optional value + if(tl e_pq != nil) { + (ok, pq.value) = (hd tl e_pq).is_octetstring(); + if(!ok) + break parse; + } + l_pq = pq :: l_pq; + e_pq = tl e_pq; + } + # reverse the order + while(l_pq != nil) { + pi.qualifiers = (hd l_pq) :: pi.qualifiers; + l_pq = tl l_pq; + } + } + l_pi = pi :: l_pi; + } + # reverse the order + l: list of ref PolicyInfo; + while(l_pi != nil) { + l = (hd l_pi) :: l; + l_pi = tl l_pi; + } + return ("", ref ExtClass.CertificatePolicies(l)); + } + return ("certificate policies: syntax error", nil); +} + +# [private] + +encode_certificatePolicies(c: ref ExtClass.CertificatePolicies): (string, array of byte) +{ + el, pel: list of ref Elem; + pl := c.policies; + while(pl != nil) { + p := hd pl; + if(p.oid == nil) + return ("certificate policies: incomplete data", nil); + plseq: list of ref Elem; + if(p.qualifiers != nil) { + ql := p.qualifiers; + qel, qlseq: list of ref Elem; + while(ql != nil) { + pq := hd ql; + pqseq: list of ref Elem; + if(pq.oid == nil) + return ("certificate policies: incomplete data", nil); + if(pq.value != nil) { + pqseq = ref Elem( + Tag(Universal, OCTET_STRING, 0), + ref Value.Octets(pq.value) + ) :: nil; + } + pqseq = ref Elem( + Tag(Universal, OBJECT_ID, 0), + ref Value.ObjId(pq.oid) + ) :: pqseq; + qlseq = ref Elem( + Tag(Universal, SEQUENCE, 1), + ref Value.Seq(pqseq) + ) :: qlseq; + ql = tl ql; + } + while(qlseq != nil) { + qel = (hd qlseq) :: qel; + qlseq = tl qlseq; + } + plseq = ref Elem( + Tag(Universal, SEQUENCE, 1), + ref Value.Seq(qel) + ) :: nil; + } + plseq = ref Elem( + Tag(Universal, OBJECT_ID, 0), + ref Value.ObjId(p.oid) + ) :: plseq; + pel = ref Elem( + Tag(Universal, SEQUENCE, 1), + ref Value.Seq(plseq) + ) :: pel; + pl = tl pl; + } + while(pel != nil) { + el = (hd pel) :: el; + pel = tl pel; + } + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return asn1->encode(e); +} + +# [private] + +decode_alias(ext: ref Extension): (string, list of ref GeneralName) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, el) := all.is_seq(); + if(!ok) + break parse; + l_sa: list of ref GeneralName; + while(el != nil) { + gn: ref GeneralName; + (ok, gn) = parse_gname(hd el); + if(!ok) + break parse; + l_sa = gn :: l_sa; + el = tl el; + } + # reverse order + sa: list of ref GeneralName; + while(l_sa != nil) { + sa = (hd l_sa) :: sa; + l_sa = tl l_sa; + } + return ("", sa); + } + return ("alias: syntax error", nil); +} + +# [private] + +encode_alias(gl: list of ref GeneralName): (string, array of byte) +{ + el, gel: list of ref Elem; + while(gl != nil) { + g := hd gl; + (ok, e) := pack_gname(g); + if(!ok) + return ("alias: encoding error", nil); + gel = e :: gel; + gl = tl gl; + } + while(gel != nil) { + el = (hd gel) :: el; + gel = tl gel; + } + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return asn1->encode(e); +} + +# [private] + +decode_subjectDirectoryAttributes(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, el) := all.is_seq(); + if(!ok) + break parse; + l_a: list of ref Attribute; + while(el != nil) { + a: ref Attribute; + #(ok, a) = parse_attr(hd el); + #if(!ok) + # break parse; + l_a = a :: l_a; + el = tl el; + } + # reverse order + as: list of ref Attribute; + while(l_a != nil) { + as = (hd l_a) :: as; + l_a = tl l_a; + } + return ("", ref ExtClass.SubjectDirectoryAttributes(as)); + } + return ("subject directory attributes: syntax error", nil); +} + +# [private] + +encode_subjectDirectoryAttributes(c: ref ExtClass.SubjectDirectoryAttributes) + : (string, array of byte) +{ + el, ael: list of ref Elem; + al := c.attrs; + while(al != nil) { + (ok, e) := pack_attr(hd al); + if(!ok) + return ("subject directory attributes: encoding error", nil); + ael = e :: ael; + al = tl al; + } + while(ael != nil) { + el = (hd ael) :: el; + ael = tl ael; + } + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return asn1->encode(e); +} + +# [private] + +decode_nameConstraints(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, el) := all.is_seq(); + if(!ok || len el < 1 || len el > 2) + break parse; + nc := ref ExtClass.NameConstraints; + if(el != nil) { + (ok, nc.permitted) = parse_gsubtrees(hd el); + if(!ok || nc.permitted == nil) + break parse; + el = tl el; + } + if(el!= nil) { + (ok, nc.excluded) = parse_gsubtrees(hd el); + if(!ok || nc.excluded == nil) + break parse; + } + return ("", nc); + } + return ("name constraints: syntax error", nil); +} + +# [private] + +encode_nameConstraints(c: ref ExtClass.NameConstraints): (string, array of byte) +{ + el: list of ref Elem; + if(c.permitted == nil && c.excluded == nil) + return ("name constraints: incomplete data", nil); + if(c.excluded != nil) { + (ok, e) := pack_gsubtrees(c.excluded); + if(!ok) + return ("name constraints: encoding error", nil); + el = e :: el; + } + if(c.permitted != nil) { + (ok, e) := pack_gsubtrees(c.permitted); + if(!ok) + return ("name constraints: encoding error", nil); + el = e :: el; + } + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return asn1->encode(e); +} + +# [private] + +parse_gsubtrees(e: ref Elem): (int, list of ref GSubtree) +{ +parse: + for(;;) { + (ok, el) := e.is_seq(); + if(!ok) + break parse; + l, lgs: list of ref GSubtree; + while(el != nil) { + gs: ref GSubtree; + (ok, gs) = parse_gsubtree(hd el); + if(!ok) + break parse; + lgs = gs :: lgs; + el = tl el; + } + while(lgs != nil) { + l = (hd lgs) :: l; + lgs = tl lgs; + } + return (1, l); + } + return (0, nil); +} + +# [private] + +pack_gsubtrees(gs: list of ref GSubtree): (int, ref Elem) +{ + el, l: list of ref Elem; + while(gs != nil) { + (ok, e) := pack_gsubtree(hd gs); + if(!ok) + return (0, nil); + l = e :: l; + } + while(l != nil) { + el = (hd l) :: el; + l = tl l; + } + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return (1, e); +} + +# [private] + +parse_gsubtree(e: ref Elem): (int, ref GSubtree) +{ +parse: + for(;;) { + (ok, el) := e.is_seq(); + if(!ok || len el > 3 || len el < 2) + break parse; + gs := ref GSubtree; + e = hd el; + (ok, gs.base) = parse_gname(e); + if(!ok) + break parse; + el = tl el; + e = hd el; + (ok, e) = is_context(e, 0); + if(ok) { + (ok, gs.min) = e.is_int(); + if(!ok) + break parse; + el = tl el; + } + # get optional maximum base distance + if(el != nil) { + e = hd el; + (ok, e) = is_context(e, 1); + if(!ok) + break parse; + (ok, gs.max) = e.is_int(); + if(!ok) + break parse; + } + return (1, gs); + } + return (0, nil); +} + +# [private] + +pack_gsubtree(g: ref GSubtree): (int, ref Elem) +{ + el: list of ref Elem; + ok := 1; + e: ref Elem; + if(g.base == nil) + return (0, nil); + if(g.max != 0) { + e = ref Elem(Tag(Universal, INTEGER, 0), ref Value.Int(g.max)); + (ok, e) = pack_context(e, 1); + if(!ok) + return (0, nil); + el = e :: nil; + } + if(g.min != 0) { + e = ref Elem(Tag(Universal, INTEGER, 0), ref Value.Int(g.min)); + (ok, e) = pack_context(e, 0); + if(!ok) + return (0, nil); + el = e :: el; + } + (ok, e) = pack_gname(g.base); + if(!ok) + return (0, nil); + el = e :: el; + e = ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return (1, e); +} + +# [private] + +decode_policyConstraints(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, el) := all.is_seq(); + if(!ok || len el < 1 || len el > 2) + break parse; + pc := ref ExtClass.PolicyConstraints; + e := hd el; + (ok, e) = is_context(e, 0); + if(ok) { + (ok, pc.require) = e.is_int(); + if(!ok) + break parse; + el = tl el; + } + if(el != nil) { + e = hd el; + (ok, e) = is_context(e, 1); + if(!ok) + break parse; + (ok, pc.inhibit) = e.is_int(); + if(!ok) + break parse; + } + return ("", pc); + } + return ("policy constraints: syntax error", nil); +} + +# [private] + +encode_policyConstraints(c: ref ExtClass.PolicyConstraints): (string, array of byte) +{ + el: list of ref Elem; + ok := 1; + if(c.inhibit > 0) { + e := ref Elem(Tag(Universal, INTEGER, 0), ref Value.Int(c.inhibit)); + (ok, e) = pack_context(e, 1); + if(!ok) + return ("policy constraints: encoding error", nil); + el = e :: nil; + } + if(c.require > 0) { + e := ref Elem(Tag(Universal, INTEGER, 0), ref Value.Int(c.require)); + (ok, e) = pack_context(e, 0); + if(!ok) + return ("policy constraints: encoding error", nil); + el = e :: el; + } + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return asn1->encode(e); +} + +# [private] + +decode_cRLNumber(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, n) := all.is_int(); # TODO: should be IPint + if(!ok) + break parse; + return ("", ref ExtClass.CRLNumber(n)); + } + return ("crl number: syntax error", nil); +} + +# [private] + +encode_cRLNumber(c: ref ExtClass.CRLNumber): (string, array of byte) +{ + e := ref Elem(Tag(Universal, INTEGER, 0), ref Value.Int(c.curr)); + return asn1->encode(e); +} + +# [private] + +decode_reasonCode(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, un_used_bits, code) := all.is_bitstring(); + if(!ok) + break parse; + # no harm to ignore unused bits + if(len code > 4) + break parse; + return ("", ref ExtClass.ReasonCode(b4int(code))); + } + return ("crl reason: syntax error", nil); +} + +# [private] + +encode_reasonCode(c: ref ExtClass.ReasonCode): (string, array of byte) +{ + e := ref Elem( + Tag(Universal, BIT_STRING, 0), + ref Value.BitString(0, int4b(c.code)) + ); + return asn1->encode(e); +} + +# [private] + +decode_instructionCode(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, code) := all.is_oid(); + if(!ok) + break parse; + return ("", ref ExtClass.InstructionCode(code)); + } + return ("instruction code: syntax error", nil); +} + +# [private] + +encode_instructionCode(c: ref ExtClass.InstructionCode): (string, array of byte) +{ + e := ref Elem(Tag(Universal, OBJECT_ID, 0), ref Value.ObjId(c.oid)); + return asn1->encode(e); +} + +# [private] + +decode_invalidityDate(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, date) := all.is_time(); + if(!ok) + break parse; + t := decode_time(date, GeneralizedTime); + if(t < 0) + break parse; + return ("", ref ExtClass.InvalidityDate(t)); + } + return ("", nil); +} + +# [private] + +encode_invalidityDate(c: ref ExtClass.InvalidityDate): (string, array of byte) +{ + e := ref Elem( + Tag(Universal, GeneralizedTime, 0), + ref Value.String(pack_time(c.date, GeneralizedTime)) + ); + return asn1->encode(e); +} + +# [private] + +decode_cRLDistributionPoint(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, el) := all.is_seq(); + if(!ok || len el < 1) # Note: at least one + break parse; + l, dpl: list of ref DistrPoint; + while(el != nil) { + dp: ref DistrPoint; + (ok, dp) = parse_distrpoint(hd el); + if(!ok) + break parse; + dpl = dp :: dpl; + } + # reverse order + while(dpl != nil) { + l = (hd dpl) :: l; + dpl = tl dpl; + } + return ("", ref ExtClass.CRLDistributionPoint(l)); + } + return ("crl distribution point: syntax error", nil); +} + +# [private] + +encode_cRLDistributionPoint(c: ref ExtClass.CRLDistributionPoint): (string, array of byte) +{ + el, l: list of ref Elem; + dpl := c.ps; + if(dpl == nil) # at lease one + return ("crl distribution point: incomplete data error", nil); + while(dpl != nil) { + (ok, e) := pack_distrpoint(hd dpl); + if(!ok) + return ("crl distribution point: encoding error", nil); + l = e :: l; + } + while(l != nil) { + el = (hd l) :: el; + l = tl l; + } + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return asn1->encode(e); +} + +# [private] + +parse_distrpoint(e: ref Elem): (int, ref DistrPoint) +{ +parse: + for(;;) { + (ok, el) := e.is_seq(); + if(!ok) + break parse; + if(!ok || len el > 3 || len el < 1) + break parse; + dp: ref DistrPoint; + e = hd el; + # get optional distribution point name + (ok, e) = is_context(e, 0); + if(ok) { + (ok, dp.name) = parse_dpname(e); + if(!ok) + break parse; + el = tl el; + } + # get optional reason flags + if(el != nil) { + e = hd el; + (ok, e) = is_context(e, 1); + if(ok) { + unused_bits: int; + reasons: array of byte; + (ok, unused_bits, reasons) = e.is_bitstring(); + if(!ok) + break parse; + # no harm to ignore unused bits + if(len reasons > 4) + break parse; + dp.reasons = b4int(reasons); + } + el = tl el; + } + # get optional crl issuer + if(el != nil) { + e = hd el; + (ok, e) = is_context(e, 2); + if(!ok) + break parse; + (ok, dp.issuer) = parse_lgname(e); + if(!ok) + break parse; + el = tl el; + } + # must be no more left + if(el != nil) + break parse; + return (1, dp); + } + return (0, nil); +} + +# [private] + +pack_distrpoint(dp: ref DistrPoint): (int, ref Elem) +{ + el: list of ref Elem; + if(dp.issuer != nil) { + (ok, e) := pack_lgname(dp.issuer); + if(!ok) + return (0, nil); + (ok, e) = pack_context(e, 2); + if(!ok) + return (0, nil); + el = e :: nil; + } + if(dp.reasons != 0) { + e := ref Elem( + Tag(Universal, BIT_STRING, 0), + ref Value.BitString(0, int4b(dp.reasons)) + ); + ok := 1; + (ok, e) = pack_context(e, 1); + if(!ok) + return (0, nil); + el = e :: el; + } + if(dp.name != nil) { + (ok, e) := pack_dpname(dp.name); + if(!ok) + return (0, nil); + (ok, e) = pack_context(e, 0); + if(!ok) + return (0, nil); + el = e :: el; + } + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return (1, e); +} + +# [private] + +parse_dpname(e: ref Elem): (int, ref DistrPointName) +{ +parse: + for(;;) { + # parse CHOICE + ok := 0; + (ok, e) = is_context(e, 0); + if(ok) { + lg: list of ref GeneralName; + (ok, lg) = parse_lgname(e); + if(!ok) + break parse; + return (1, ref DistrPointName(lg, nil)); + } + (ok, e) = is_context(e, 1); + if(!ok) + break parse; + n: ref Name; + (ok, n) = parse_name(e); + if(!ok) + break parse; + return (1, ref DistrPointName(nil, n.rd_names)); + } + return (0, nil); +} + +# [private] + +pack_dpname(dpn: ref DistrPointName): (int, ref Elem) +{ + if(dpn.full_name != nil) { + (ok, e) := pack_lgname(dpn.full_name); + if(!ok) + return (0, nil); + return pack_context(e, 0); + } + if(dpn.rdname != nil) { + rdn := dpn.rdname; + el, l: list of ref Elem; + while(rdn != nil) { + l = pack_rdname(hd rdn) :: l; + rdn = tl rdn; + } + while(l != nil) { + el = (hd l) :: el; + l = tl l; + } + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return pack_context(e, 1); + } + return (0, nil); +} + +# [private] + +decode_issuingDistributionPoint(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, el) := all.is_seq(); + if(!ok || len el < 3 || len el > 5) + break parse; + ip := ref ExtClass.IssuingDistributionPoint; + ae := hd el; + # get optional distribution point name + (ok, ae) = is_context(ae, 0); + if(ok) { + #(ok, ip.name) = parse_dpname(ae); + if(!ok) + break parse; + el = tl el; + } + # get only contains user certs field + if(el != nil) { + ae = hd el; + (ok, ae) = is_context(ae, 1); + if(ok) { + (ok, ip.only_usercerts) = ae.is_int(); # boolean + if(!ok) + break parse; + } + el = tl el; + } + # get only contains ca certs field + if(el != nil) { + ae = hd el; + (ok, ae) = is_context(ae, 2); + if(ok) { + (ok, ip.only_cacerts) = ae.is_int(); # boolean + if(!ok) + break parse; + } + el = tl el; + } + # get optioinal only some reasons + if(el != nil) { + ae = hd el; + (ok, ae) = is_context(ae, 3); + if(ok) { + reasons: array of byte; + unused_bits: int; + (ok, unused_bits, reasons) = ae.is_bitstring(); + if(!ok || len reasons > 4) + break parse; + ip.only_reasons = b4int(reasons); + } + el = tl el; + } + # get indirect crl field + if(el != nil) { + ae = hd el; + (ok, ae) = is_context(ae, 4); + if(!ok) + break parse; + (ok, ip.indirect_crl) = ae.is_int(); # boolean + if(!ok) + break parse; + el = tl el; + } + # must be no more left + if(el != nil) + break parse; + return ("", ip); + } + return ("issuing distribution point: syntax error", nil); +} + +# [private] + +encode_issuingDistributionPoint(c: ref ExtClass.IssuingDistributionPoint) + : (string, array of byte) +{ + el: list of ref Elem; + ok := 1; + if(c.indirect_crl != 0) { # no encode for DEFAULT + e := ref Elem( + Tag(Universal, BOOLEAN, 0), + ref Value.Bool(c.indirect_crl) + ); + (ok, e) = pack_context(e, 4); + if(!ok) + return ("issuing distribution point: encoding error", nil); + el = e :: el; + } + if(c.only_reasons != 0) { + e := ref Elem( + Tag(Universal, BIT_STRING, 0), + ref Value.BitString(0, int4b(c.only_reasons)) + ); + (ok, e) = pack_context(e, 3); + if(!ok) + return ("issuing distribution point: encoding error", nil); + el = e :: el; + } + if(c.only_cacerts != 0) { + e := ref Elem( + Tag(Universal, BOOLEAN, 0), + ref Value.Bool(c.only_cacerts) + ); + (ok, e) = pack_context(e, 2); + if(!ok) + return ("issuing distribution point: encoding error", nil); + el = e :: el; + } + if(c.only_usercerts != 0) { + e := ref Elem( + Tag(Universal, BOOLEAN, 0), + ref Value.Bool(c.only_usercerts) + ); + (ok, e) = pack_context(e, 1); + if(!ok) + return ("issuing distribution point: encoding error", nil); + el = e :: el; + } + if(c.name != nil) { + e: ref Elem; + (ok, e) = pack_dpname(c.name); + if(!ok) + return ("issuing distribution point: encoding error", nil); + (ok, e) = pack_context(e, 0); + if(!ok) + return ("issuing distribution point: encoding error", nil); + el = e :: el; + } + + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return asn1->encode(e); +} + +# [private] + +decode_certificateIssuer(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, el) := all.is_seq(); + if(!ok) + break parse; + gl, gnl: list of ref GeneralName; + while(el != nil) { + g: ref GeneralName; + (ok, g) = parse_gname(hd el); + if(!ok) + break parse; + gnl = g :: gnl; + el = tl el; + } + while(gnl != nil) { + gl = (hd gnl) :: gl; + gnl = tl gnl; + } + return ("", ref ExtClass.CertificateIssuer(gl)); + } + + return ("certificate issuer: syntax error", nil); +} + +# [private] + +encode_certificateIssuer(c: ref ExtClass.CertificateIssuer): (string, array of byte) +{ + el, nel: list of ref Elem; + ns := c.names; + while(ns != nil) { + (ok, e) := pack_gname(hd ns); + if(!ok) + return ("certificate issuer: encoding error", nil); + nel = e :: nel; + ns = tl ns; + } + while(nel != nil) { + el = (hd nel) :: el; + nel = tl nel; + } + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return asn1->encode(e); +} + +# [private] + +decode_deltaCRLIndicator(ext: ref Extension): (string, ref ExtClass) +{ +parse: + for(;;) { + (err, all) := asn1->decode(ext.value); + if(err != "") + break parse; + (ok, b) := all.is_bigint(); + if(!ok) + break parse; + return ("", ref ExtClass.DeltaCRLIndicator(IPint.bebytestoip(b))); + } + return ("delta crl number: syntax error", nil); +} + +# [private] + +encode_deltaCRLIndicator(c: ref ExtClass.DeltaCRLIndicator): (string, array of byte) +{ + e := ref Elem( + Tag(Universal, INTEGER, 0), + ref Value.BigInt(c.number.iptobebytes()) + ); + return asn1->encode(e); +} + +# [public] + +GeneralName.tostring(gn: self ref GeneralName): string +{ + s: string; + + pick g := gn { + otherName => + s = "other name: " + g.str; + rfc822Name => + s = "rfc822 name: " + g.str; + dNSName => + s = "dns name: " + g.str; + x400Address => + s = "x400 address: " + g.str; + uniformResourceIdentifier => + s = "url: " + g.str; + iPAddress => + s = "ip address: " + bastr(g.ip); + registeredID => + s = "oid: " + g.oid.tostring(); + ediPartyName => + s = "edi party name: "; + s += "\n\tname assigner is " + g.nameAssigner.tostring(); + s += "\n\tparty name is " + g.partyName.tostring(); + directoryName => + s = "directory name: " + g.dir.tostring(); + } + return s; +} + +# [public] + +PolicyInfo.tostring(pi: self ref PolicyInfo): string +{ + s := "oid: " + pi.oid.tostring(); + s += "qualifiers: "; + ql := pi.qualifiers; + while(ql != nil) { + s += (hd ql).tostring(); + ql = tl ql; + } + return s; +} + +# [public] + +PolicyQualifier.tostring(pq: self ref PolicyQualifier): string +{ + s := "oid: " + pq.oid.tostring(); + s += "value: " + bastr(pq.value); + return s; +} + +# [public] + +GSubtree.tostring(gs: self ref GSubtree): string +{ + s := "base: " + gs.base.tostring(); + s += "range: " + string gs.min + "-" + string gs.max; + return s; +} + +# [public] + +DistrPoint.tostring(dp: self ref DistrPoint): string +{ + s := "Distribution Point: "; + s += "\n\tname = "; + d := dp.name; + if(d.full_name != nil) { + f := d.full_name; + while(f != nil) { + s += (hd f).tostring() + ","; + f = tl f; + } + } + else { + r := d.rdname; + while(r != nil) { + s += (hd r).tostring() + ","; + r = tl r; + } + } + s += "\n\treasons = " + string dp.reasons; + s += "\n\tissuer = "; + gl := dp.issuer; + while(gl != nil) { + s += (hd gl).tostring() + ","; + gl = tl gl; + } + return s; +} + +# [private] + +is_context(e: ref Elem, num: int): (int, ref Elem) +{ + if(e.tag.class == ASN1->Context && e.tag.num == num) { + pick v := e.val { + Octets => + (err, all) := asn1->decode(v.bytes); + if(err == "") + return (1, all); + } + } + return (0, nil); +} + +# [private] + +pack_context(e: ref Elem, num: int): (int, ref Elem) +{ + (err, b) := asn1->encode(e); + if(err == "") + return (1, ref Elem(Tag(Context, num, 0), ref Value.Octets(b))); + return (0, nil); +} + +# [private] + +parse_lgname(e: ref Elem): (int, list of ref GeneralName) +{ +parse: + for(;;) { + (ok, el) := e.is_seq(); + if(!ok) + break parse; + l, lg: list of ref GeneralName; + while(el != nil) { + g: ref GeneralName; + (ok, g) = parse_gname(hd el); + if(!ok) + break parse; + lg = g :: lg; + el = tl el; + } + while(lg != nil) { + l = (hd lg) :: l; + lg = tl lg; + } + return (1, l); + } + return (0, nil); +} + +# [private] + +pack_lgname(lg: list of ref GeneralName): (int, ref Elem) +{ + el, gel: list of ref Elem; + while(lg != nil) { + (ok, e) := pack_gname(hd lg); + if(!ok) + return (0, nil); + gel = e :: gel; + lg = tl lg; + } + while(gel != nil) { + el = (hd gel) :: el; + gel = tl gel; + } + e := ref Elem(Tag(Universal, SEQUENCE, 1), ref Value.Seq(el)); + return (1, e); +} + +# [private] + +parse_gname(e: ref Elem): (int, ref GeneralName) +{ +parse: + for(;;) { + g: ref GeneralName; + ok := 1; + case e.tag.num { + 0 => + (ok, e) = is_context(e, 0); + if(!ok) + break parse; + str: string; + (ok, str) = e.is_string(); + if(!ok) + break parse; + g = ref GeneralName.otherName(str); + 1 => + (ok, e) = is_context(e, 1); + if(!ok) + break parse; + str: string; + (ok, str) = e.is_string(); + if(!ok) + break parse; + g = ref GeneralName.rfc822Name(str); + 2 => + (ok, e) = is_context(e, 2); + if(!ok) + break parse; + str: string; + (ok, str) = e.is_string(); + if(!ok) + break parse; + g = ref GeneralName.dNSName(str); + 3 => + (ok, e) = is_context(e, 3); + if(!ok) + break parse; + str: string; + (ok, str) = e.is_string(); + if(!ok) + break parse; + g = ref GeneralName.x400Address(str); + 4 => + (ok, e) = is_context(e, 4); + if(!ok) + break parse; + dir: ref Name; + (ok, dir) = parse_name(e); + if(!ok) + break parse; + g = ref GeneralName.directoryName(dir); + 5 => + (ok, e) = is_context(e, 5); + if(!ok) + break parse; + el: list of ref Elem; + (ok, el) = e.is_seq(); + if(!ok || len el < 1 || len el > 3) + break parse; + na, pn: ref Name; + (ok, e) = is_context(hd el, 0); + if(ok) { + (ok, na) = parse_name(e); + if(!ok) + break parse; + el = tl el; + } + if(el != nil) { + (ok, e) = is_context(hd el, 1); + if(!ok) + break parse; + (ok, pn) = parse_name(e); + if(!ok) + break parse; + } + g = ref GeneralName.ediPartyName(na, pn); + 6 => + (ok, e) = is_context(e, 6); + if(!ok) + break parse; + str: string; + (ok, str) = e.is_string(); + if(!ok) + break parse; + g = ref GeneralName.uniformResourceIdentifier(str); + 7 => + (ok, e) = is_context(e, 7); + if(!ok) + break parse; + ip: array of byte; + (ok, ip) = e.is_octetstring(); + if(!ok) + break parse; + g = ref GeneralName.iPAddress(ip); + 8 => + (ok, e) = is_context(e, 8); + if(!ok) + break parse; + oid: ref Oid; + (ok, oid) = e.is_oid(); + if(!ok) + break parse; + g = ref GeneralName.registeredID(oid); + * => + break parse; + } + return (1, g); + } + return (0, nil); +} + +# [private] + +pack_gname(gn: ref GeneralName): (int, ref Elem) +{ + e: ref Elem; + ok := 1; + + pick g := gn { + otherName => + e = ref Elem( + Tag(Universal, GeneralString, 0), + ref Value.String(g.str) + ); + (ok, e) = pack_context(e, 0); + if(!ok) + return (0, nil); + rfc822Name => + e = ref Elem( + Tag(Universal, IA5String, 0), + ref Value.String(g.str) + ); + (ok, e) = pack_context(e, 1); + if(!ok) + return (0, nil); + dNSName => + e = ref Elem( + Tag(Universal, IA5String, 0), + ref Value.String(g.str) + ); + (ok, e) = pack_context(e, 2); + if(!ok) + return (0, nil); + x400Address => + e = ref Elem( + Tag(Universal, GeneralString, 0), + ref Value.String(g.str) + ); + (ok, e) = pack_context(e, 3); + if(!ok) + return (0, nil); + uniformResourceIdentifier => + e = ref Elem( + Tag(Universal, GeneralString, 0), + ref Value.String(g.str) + ); + (ok, e) = pack_context(e, 6); + if(!ok) + return (0, nil); + iPAddress => + e = ref Elem( + Tag(Universal, OCTET_STRING, 0), + ref Value.Octets(g.ip) + ); + (ok, e) = pack_context(e, 7); + if(!ok) + return (0, nil); + + registeredID => + e = ref Elem( + Tag(Universal, OBJECT_ID, 0), + ref Value.ObjId(g.oid) + ); + (ok, e) = pack_context(e, 8); + if(!ok) + return (0, nil); + + ediPartyName => + el: list of ref Elem; + if(g.partyName != nil) { + e = pack_name(g.partyName); + (ok, e) = pack_context(e, 1); + if(!ok) + return (0, nil); + el = e :: nil; + } + if(g.nameAssigner != nil) { + e = pack_name(g.nameAssigner); + (ok, e) = pack_context(e, 0); + if(!ok) + return (0, nil); + el = e :: el; + } + e = ref Elem( + Tag(Universal, SEQUENCE, 1), + ref Value.Seq(el) + ); + (ok, e) = pack_context(e, 5); + if(!ok) + return (0, nil); + directoryName => + e = pack_name(g.dir); + (ok, e) = pack_context(e, 4); + if(!ok) + return (0, nil); + } + return (1, e); +} + +# [private] +# convert at most 4 bytes to int, len buf must be less than 4 + +b4int(buf: array of byte): int +{ + val := 0; + for(i := 0; i < len buf; i++) + val = (val << 8) | (int buf[i]); + return val; +} + +# [private] + +int4b(value: int): array of byte +{ + n := 4; + buf := array [n] of byte; + while(n--) { + buf[n] = byte value; + value >>= 8; + } + return buf; +} + +# [private] + +oid_cmp(a, b: ref Oid): int +{ + na := len a.nums; + nb := len b.nums; + if(na != nb) + return 0; + for(i := 0; i < na; i++) { + if(a.nums[i] != b.nums[i]) + return 0; + } + return 1; +} + +# [private] +# decode two bytes into an integer [0-99] +# return -1 for an invalid encoding + +get2(a: string, i: int): int +{ + a0 := int a[i]; + a1 := int a[i+1]; + if(a0 < '0' || a0 > '9' || a1 < '0' || a1 > '9') + return -1; + return (a0 - '0')*10 + a1 - '0'; +} + +# [private] +# encode an integer [0-99] into two bytes + +put2(a: array of byte, n, i: int): int +{ + a[i] = byte (n/10 + '0'); + a[i+1] = byte (n%10 + '0'); + return i+2; +} + +# [private] + +bastr(a: array of byte) : string +{ + ans := ""; + for(i := 0; i < len a; i++) { + if(i < len a - 1 && i%10 == 0) + ans += "\n\t\t"; + ans += sys->sprint("%2x ", int a[i]); + } + return ans; +} + +# [private] + +parse_attr(nil: ref Elem): (int, ref Attribute) +{ + return (0, nil); +} + +# [private] + +pack_attr(nil: ref Attribute): (int, ref Elem) +{ + return (0, nil); +} |
