GeneralSubtree.cpp

Go to the documentation of this file.
00001 
00009 #include "GeneralSubtree.h"
00010 #include "GeneralName.h"
00011 #include "Name.h"
00012 #include "ASN1Helper.h"
00013 
00014 #include "PKIX1Implicit88.h"
00015 #include "PKIX1Explicit88.h"
00016 
00017 #include "components.h"
00018 #include "PKIFException.h"
00019 
00020 #include "boost/regex.hpp"
00021 #include "boost/algorithm/string.hpp"
00022 #include "boost/numeric/conversion/cast.hpp"
00023 
00024 using boost::numeric_cast;
00025 using boost::bad_numeric_cast;
00026 
00027 #include <string>
00028 #include <sstream>
00029 using namespace std;
00030 
00031 /*
00032 !"#$%&'*+-/0123456789=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~
00033 */
00034 static bool emailre_init = false;
00035 static const char * emailpat = "([\\w\\d!\"#$%&'*+-/=?@^_`{|}~]+)@([\\w\\d!\"#$%&'*+-/=?@^_`{|}~]+\\.)([\\w\\d!\"#$%&'*+-/=?@^_`{|}~]+)(\\.[\\w\\d!\"#$%&'*+-/=?@^_`{|}~]+)*";
00036 static boost::regex emailre;
00037 
00038 static bool escapere_init = false;
00039 static const char * escapepat = "\\.";
00040 static const char * escapeformat = "\\\\.";
00041 static boost::regex escapere;
00042 
00043 static bool urire_init = false;
00044 // straight from rfc 2396
00045 static const char * uripat = "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?";
00046 static boost::regex urire;
00047 static const char * portpat = "(.*):(\\d+)?$";
00048 static boost::regex portre;
00049 
00050 static bool erdnre_init = false;
00051 static const char * erdnpat = "^e=(.*)$";
00052 static boost::regex erdnre;
00053 
00061 static std::string getHost(const std::string & uri)
00062 {
00063     if(!urire_init) {
00064         urire.assign(uripat,boost::regex_constants::perl);
00065         portre.assign(portpat,boost::regex_constants::perl);
00066         urire_init = true;
00067     }
00068     boost::smatch what;
00069     std::string rv("");
00070     bool found = boost::regex_match(uri,what,urire);
00071     if(found){
00072         rv.assign(what[4].first,what[4].second);
00073         boost::smatch pm;
00074         // this could probably be done with a single expression.
00075         // I was unable to concoct such an expression.
00076         found = boost::regex_match(rv,pm,portre);
00077         if(found) {
00078             rv.assign(pm[1].first,pm[1].second);
00079         }
00080     }
00081     return rv;
00082 }
00083 
00091 static std::string escapeDots(const std::string & inpat)
00092 {
00093     if(!escapere_init) {
00094         escapere.assign(escapepat,boost::regex_constants::perl);
00095         escapere_init = true;
00096     }
00097     ostringstream os;
00098     ostream_iterator<char> oi(os);
00099     boost::regex_replace(oi,inpat.begin(),inpat.end(),escapere,escapeformat,
00100         boost::match_default | boost::format_all);
00101     return os.str();
00102 }
00103 
00113 static bool isEmail(
00115     const std::string & addr)
00116 {
00117     if(!emailre_init) {
00118         emailre.assign(emailpat,boost::regex_constants::perl);
00119         emailre_init = true;
00120     }
00121     boost::smatch what;
00122     bool found = boost::regex_search(addr,what,emailre);
00123     if(!found)
00124     {
00125         if(!urire_init) {
00126             urire.assign(uripat,boost::regex_constants::perl);
00127             portre.assign(portpat,boost::regex_constants::perl);
00128             urire_init = true;
00129         }
00130         boost::smatch what;
00131         found = boost::regex_match(addr,what,urire);
00132     }
00133     return found;
00134 }
00135 
00137 
00138 struct CPKIFGeneralSubtreeImpl
00139 {
00140     CPKIFGeneralNamePtr m_base;
00141     int m_nMin;
00142     int m_nMax;
00143     bool m_empty;
00144     
00145     CPKIFGeneralSubtree::MatchState DNSNameIsInSubtree(const char * name);
00146     CPKIFGeneralSubtree::MatchState IPAddressIsInSubtree(const CPKIFBufferPtr & name);
00147     CPKIFGeneralSubtree::MatchState RFC822NameIsInSubtree(const char * name);
00148     CPKIFGeneralSubtree::MatchState URINameIsInSubtree(const char * name);
00149 
00150 };
00151 
00161 CPKIFGeneralSubtree::MatchState CPKIFGeneralSubtreeImpl::DNSNameIsInSubtree(const char * name)
00162 {
00163     if(CPKIFGeneralName::DNSNAME != m_base->GetType()) return CPKIFGeneralSubtree::NOT_APPLICABLE;
00164     if(m_empty) return CPKIFGeneralSubtree::NO_MATCH;
00165     /* DNS name restrictions are expressed as host.example.com.  Any DNS
00166     name that can be constructed by simply adding subdomains to the left
00167     hand side of the name satisfies the name constraint.  For example,
00168     www.host.example.com would satisfy the constraint but
00169     host1.example.com would not. */
00170 
00171     string base(m_base->dnsName());
00172     string cand(name);
00173     
00174     // DNS name restrictions are case insensitive.
00175     boost::regex_constants::syntax_option_type reflags = 
00176         boost::regex_constants::perl | boost::regex_constants::icase;
00177 
00178     ostringstream patstream;
00179     patstream << base << "$";
00180     string pattern = escapeDots(patstream.str());
00181     boost::regex re;
00182     boost::smatch what;
00183     
00184     re.assign(pattern,reflags);
00185     bool found = boost::regex_search(cand,what,re);
00186     
00187     // if the constraint was not found at the end of the candidate name,
00188     // no match
00189     if(!found) return CPKIFGeneralSubtree::NO_MATCH;
00190 
00191     // if there was an exact match, the restriction is satisfied
00192     if(cand.length() == base.length()) return CPKIFGeneralSubtree::MATCH;
00193 
00194     // if the character just before the match is a period, than the candidate
00195     // is a subdomain of the restriction
00196     int pos;
00197     try {
00198         pos = numeric_cast<int>(what.position());
00199     }catch(bad_numeric_cast &) {
00200         throw CPKIFException(TOOLKIT_ASN, ASN1_DECODE_ERROR, "encountered abusively long dns name");
00201     }
00202     if(pos && cand[pos-1] == '.') return CPKIFGeneralSubtree::MATCH;
00203 
00204     return CPKIFGeneralSubtree::NO_MATCH;
00205 }
00206 
00218 CPKIFGeneralSubtree::MatchState CPKIFGeneralSubtreeImpl::IPAddressIsInSubtree(const CPKIFBufferPtr & name)
00219 {
00220     if(CPKIFGeneralName::IPADDRESS != m_base->GetType()) return CPKIFGeneralSubtree::NOT_APPLICABLE;
00221     if(m_empty) return CPKIFGeneralSubtree::NO_MATCH;
00222     /* The syntax of iPAddress MUST be as described in section 4.2.1.6 with
00223     the following additions specifically for Name Constraints.  For IPv4
00224     addresses, the ipAddress field of GeneralName MUST contain eight (8)
00225     octets, encoded in the style of RFC 1519 (CIDR) to represent an
00226     address range [RFC 1519].  For IPv6 addresses, the ipAddress field
00227     MUST contain 32 octets similarly encoded.  For example, a name
00228     constraint for "class C" subnet 10.9.8.0 is represented as the octets
00229     0A 09 08 00 FF FF FF 00, representing the CIDR notation
00230     10.9.8.0/255.255.255.0. */
00231 
00232     CPKIFBufferPtr base = m_base->ipAddress();
00233     unsigned int baselen = base->GetLength();
00234     unsigned int addrlen = baselen/2;
00235     // make sure the two addresses are comparable
00236     if(name->GetLength() != addrlen) return CPKIFGeneralSubtree::NO_MATCH;
00237     // only support IPv4 and IPv6 address + mask
00238     if(baselen != 32 && baselen != 8) throw CPKIFException(TOOLKIT_ASN, ASN1_DECODE_ERROR, "encountered malformed iPAddress");
00239     
00240     CPKIFBufferPtr test(new CPKIFBuffer(*name));
00241     const unsigned char * baseBuf = base->GetBuffer();
00242     const unsigned char * maskBuf = baseBuf + addrlen;
00243     unsigned char * testBuf = const_cast<unsigned char *>(test->GetBuffer());
00244     // apply the subnet mask to the candidate address
00245     for(unsigned int i = 0; i < addrlen; i++)
00246     {
00247         testBuf[i] &= maskBuf[i];
00248     }
00249     // if the network numbers are now the same, the restriction is met
00250     if(!memcmp(baseBuf,testBuf,addrlen)) {
00251         return CPKIFGeneralSubtree::MATCH;
00252     }   
00253     return CPKIFGeneralSubtree::NO_MATCH;
00254 }
00255 
00265 CPKIFGeneralSubtree::MatchState CPKIFGeneralSubtreeImpl::RFC822NameIsInSubtree(const char * name)
00266 {
00267     if(CPKIFGeneralName::RFC822 != m_base->GetType()) return CPKIFGeneralSubtree::NOT_APPLICABLE;
00268     if(m_empty) return CPKIFGeneralSubtree::NO_MATCH;
00269     /* A name constraint for Internet mail addresses MAY specify a
00270     particular mailbox, all addresses at a particular host, or all
00271     mailboxes in a domain.  To indicate a particular mailbox, the
00272     constraint is the complete mail address.  For example,
00273     "root@example.com" indicates the root mailbox on the host
00274     "example.com".  To indicate all Internet mail addresses on a
00275     particular host, the constraint is specified as the host name.  For
00276     example, the constraint "example.com" is satisfied by any mail
00277     address at the host "example.com".  To specify any address within a
00278     domain, the constraint is specified with a leading period (as with
00279     URIs).  For example, ".example.com" indicates all the Internet mail
00280     addresses in the domain "example.com", but not Internet mail
00281     addresses on the host "example.com".*/
00282 
00283     string base(m_base->rfc822Name());
00284     string cand(name);
00285 
00286     // if the candidate name does not appear to be an email address,
00287     // this test makes no sense
00288     if(!isEmail(cand)) {
00289         throw CPKIFException(TOOLKIT_ASN, ASN1_DECODE_ERROR, "encountered malformed rfc822Name");
00290     }
00291     
00292     // rfc822name restrictions are case insensitive.
00293     boost::regex_constants::syntax_option_type reflags = 
00294         boost::regex_constants::perl | boost::regex_constants::icase;
00295 
00296     ostringstream patstream;
00297     patstream << base << "$";
00298     string pattern = escapeDots(patstream.str());
00299     boost::regex re;
00300     boost::smatch what;
00301 
00302     re.assign(pattern,reflags);
00303     bool found = boost::regex_search(cand,what,re);
00304 
00305     // if the constraint was not found at the end of the candidate name,
00306     // no match
00307     if(!found) return CPKIFGeneralSubtree::NO_MATCH;
00308 
00309     // if the base name is a full address, the only way the
00310     // constraint can be satisfied is with a full match
00311     if(isEmail(base)) { 
00312         if(cand.length() == base.length()) return CPKIFGeneralSubtree::MATCH;
00313     }
00314 
00315     int pos;
00316     try {
00317         pos = numeric_cast<int>(what.position());
00318     }catch(bad_numeric_cast &) {
00319         throw CPKIFException(TOOLKIT_ASN, ASN1_DECODE_ERROR, "encountered abusively long dns name");
00320     }
00321     // if the base was just a host name, the candidate address
00322     // must be on that host
00323     if(base[0] != '.') {
00324         if(pos && (cand[pos-1] == '@')) {
00325             return CPKIFGeneralSubtree::MATCH;
00326         }
00327     } else {
00328         // if the base indicated subdomains, make sure the candidate
00329         // was on a subdomain (so, e.g. foo@.bar.com doesn't pass)
00330         if(pos && (cand[pos-1]!='@')) {
00331             return CPKIFGeneralSubtree::MATCH;
00332         }
00333     }
00334 
00335     return CPKIFGeneralSubtree::NO_MATCH;
00336 }
00337 
00347 CPKIFGeneralSubtree::MatchState CPKIFGeneralSubtreeImpl::URINameIsInSubtree(const char * name)
00348 {
00349     if(CPKIFGeneralName::URI != m_base->GetType()) return CPKIFGeneralSubtree::NOT_APPLICABLE;
00350     if(m_empty) return CPKIFGeneralSubtree::NO_MATCH;
00351     /* For URIs, the constraint applies to the host part of the name.  The
00352     constraint MAY specify a host or a domain.  Examples would be
00353     "host.example.com";  and ".example.com".  When the the constraint
00354     begins with a period, it MAY be expanded with one or more subdomains.
00355     That is, the constraint ".example.com" is satisfied by both
00356     host.example.com and my.host.example.com.  However, the constraint
00357     ".example.com" is not satisfied by "example.com".  When the
00358     constraint does not begin with a period, it specifies a host. */
00359     string base(m_base->uri());
00360     string cand(name);
00361     cand = getHost(cand);
00362     if(cand == ""){
00363         throw CPKIFException(TOOLKIT_ASN, ASN1_DECODE_ERROR, "encountered malformed uriName");
00364     }
00365 
00366     // once the host is extracted from the URI, it's the same logic
00367     // as a DNS name restriction, bar the leading period.
00368     // DNS name restrictions are case insensitive.
00369     boost::regex_constants::syntax_option_type reflags = 
00370         boost::regex_constants::perl | boost::regex_constants::icase;
00371 
00372     ostringstream patstream;
00373     patstream << base << "$";
00374     string pattern = escapeDots(patstream.str());
00375     boost::regex re;
00376     boost::smatch what;
00377     
00378     re.assign(pattern,reflags);
00379     bool found = boost::regex_search(cand,what,re);
00380     
00381     // if the constraint was not found at the end of the candidate name,
00382     // no match
00383     if(!found) return CPKIFGeneralSubtree::NO_MATCH;
00384 
00385     // if there was an exact match, the restriction is satisfied
00386     if(cand.length() == base.length()) return CPKIFGeneralSubtree::MATCH;
00387 
00388     // if the character just before the match is a period, than the candidate
00389     // is a subdomain of the restriction
00390     int pos;
00391     try {
00392         pos = numeric_cast<int>(what.position());
00393     }catch(bad_numeric_cast &) {
00394         throw CPKIFException(TOOLKIT_ASN, ASN1_DECODE_ERROR, "encountered abusively long dns name");
00395     }
00396     if(pos && cand[pos] == '.') return CPKIFGeneralSubtree::MATCH;
00397 
00398     return CPKIFGeneralSubtree::NO_MATCH;
00399 }
00400 
00402 
00410 CPKIFGeneralSubtree::CPKIFGeneralSubtree()
00411   :m_impl (new CPKIFGeneralSubtreeImpl)
00412 {
00413     m_impl->m_nMax = -1;
00414     m_impl->m_nMin = -1;
00415     CPKIFGeneralNamePtr tmpGN;
00416     m_impl->m_base = tmpGN;
00417     m_impl->m_empty = false;
00418 }
00429 CPKIFGeneralSubtree::CPKIFGeneralSubtree(
00431     const CPKIFBufferPtr& genSubtree)
00432   :m_impl (new CPKIFGeneralSubtreeImpl)
00433 {
00434     m_impl->m_empty = false;
00435     CACASNWRAPPER_CREATE(CACX509V3GeneralSubtree, objPDU);
00436     objPDU.Decode(genSubtree->GetBuffer(), genSubtree->GetLength());
00437 
00438     CACASNWRAPPER_CREATE(CACX509V3GeneralName, objPDU2);
00439     ASN1OpenType* data1 = objPDU2.Encode(&(objPDU->base));
00440     CPKIFBufferPtr tmpBuf;
00441     if (data1 != NULL)
00442     {
00443          tmpBuf = CPKIFBufferPtr(new CPKIFBuffer(data1->data, data1->numocts));
00444         delete data1;
00445     }
00446     //CPKIFGeneralNamePtr tmpGN(new CPKIFGeneralName(objPDU->base));
00447     CPKIFGeneralNamePtr tmpGN(new CPKIFGeneralName(tmpBuf));
00448 
00449     m_impl->m_base = tmpGN;
00450 
00451     if(objPDU->m.maximumPresent)
00452         m_impl->m_nMax = objPDU->maximum;
00453     else
00454         m_impl->m_nMax = -1;
00455 
00456     m_impl->m_nMin = objPDU->minimum;
00457 }
00458 
00459 
00467 CPKIFGeneralSubtree::~CPKIFGeneralSubtree()
00468 {
00469     if(m_impl)
00470     {
00471         delete m_impl;
00472         m_impl = 0;
00473     }
00474 }
00475 
00484 CPKIFGeneralNamePtr CPKIFGeneralSubtree::GetBase() const 
00485 {
00486     return m_impl->m_base;
00487 }
00495 int CPKIFGeneralSubtree::GetMin() const 
00496 {
00497     return m_impl->m_nMin;
00498 }
00506 int CPKIFGeneralSubtree::GetMax() const 
00507 {
00508     return m_impl->m_nMax;
00509 }
00510 
00518 void CPKIFGeneralSubtree::SetBase(CPKIFGeneralNamePtr gn) 
00519 {
00520     m_impl->m_base = gn;
00521 }
00529 void CPKIFGeneralSubtree::SetMin(int n) 
00530 {
00531     m_impl->m_nMin = n;
00532 }
00540 void CPKIFGeneralSubtree::SetMax(int n) 
00541 {
00542     m_impl->m_nMax = n;
00543 }
00544 
00556 bool CPKIFGeneralSubtree::operator==(
00558     const CPKIFGeneralSubtree& rhs)
00559 {
00560     if(!(*m_impl->m_base == *rhs.m_impl->m_base))
00561         return false;
00562 
00563     if(m_impl->m_nMin != rhs.m_impl->m_nMin)
00564         return false;
00565 
00566     if(m_impl->m_nMax != rhs.m_impl->m_nMax)
00567         return false;
00568 
00569     // this is only to be used by ShallowCopied pointers...
00570     // don't consider it when testing equivalence
00571     /*if(m_impl->m_empty != rhs.m_impl->m_empty)
00572         return false;*/
00573 
00574     return true;
00575 }
00576 
00590 CPKIFGeneralSubtree::MatchState CPKIFGeneralSubtree::IsInSubtree(
00592     const CPKIFNamePtr& subject)const 
00593 {
00594     // since these can now be constructed and set by apps, make sure
00595     // fields are initialized before use
00596     if(!m_impl->m_base || -1 == m_impl->m_nMin)
00597         throw CPKIFException(TOOLKIT_ASN,COMMON_NOT_INITIALIZED,"An attempt was made to use an uninitialized GeneralSubtree");
00598     if(!subject)
00599         throw CPKIFException(TOOLKIT_X509_ASN, COMMON_INVALID_INPUT, "CPKIFGeneralSubtree::IsInSubtree called with an invalid CPKIFName object");
00600     
00601     switch(m_impl->m_base->GetType()) {
00602         case CPKIFGeneralName::DIRECTORYNAME:
00603             {
00604                 // the empty check needs to go inside the cases because
00605                 // RFC822NAME won't always apply
00606                 if(m_impl->m_empty) return CPKIFGeneralSubtree::NO_MATCH;
00607                 CPKIFNamePtr subtreeName = m_impl->m_base->directoryName();
00608                 if(!subtreeName->DescendedFrom(*subject) && !(*subtreeName == *subject))
00609                     return NO_MATCH;
00610 
00611                 int diff = subject->RDNCount() - subtreeName->RDNCount();
00612                 if(-1 != m_impl->m_nMin && diff < m_impl->m_nMin)
00613                     return NO_MATCH;
00614 
00615                 if(-1 != m_impl->m_nMax && diff > m_impl->m_nMax)
00616                     return NO_MATCH;
00617 
00618                 return MATCH;
00619                 break;
00620             }
00621         /* only rfc822Name and directoryName need to be handled in this function,
00622             other forms are not checked inside DNs. */
00623         case CPKIFGeneralName::RFC822:
00624             {
00625                 if(!erdnre_init) {
00626                     erdnre.assign(erdnpat,boost::regex_constants::perl);
00627                     erdnre_init = true;
00628                 }
00629                 vector<string>rdns;
00630                 subject->GetRDNs(rdns);
00631                 vector<string>::iterator end = rdns.end();
00632                 vector<string>::iterator pos;
00633                 CPKIFGeneralSubtree::MatchState emailState = NOT_APPLICABLE;
00634                 // step through rdn components looking for email addresses.
00635                 // all found email addresses must fall within the subtree
00636                 // for the restriction to be satisfied
00637                 for(pos = rdns.begin(); 
00638                     emailState != NO_MATCH && pos != end;
00639                     ++pos) 
00640                 {
00641                     boost::smatch what;
00642                     bool found = boost::regex_match((*pos),what,erdnre);
00643                     if(found) {
00644                         // the empty check only applies here if an email address
00645                         // is found
00646                         if(m_impl->m_empty) return CPKIFGeneralSubtree::NO_MATCH;
00647                         string email;
00648                         email.assign(what[1].first,what[1].second);
00649                         CPKIFGeneralSubtree::MatchState tmpState = m_impl->RFC822NameIsInSubtree(email.c_str());
00650                         if(emailState == NOT_APPLICABLE)
00651                             emailState = tmpState;
00652                     }
00653 
00654                 }
00655                 return emailState;
00656                 break;
00657             }
00658 
00659         default:
00660             throw CPKIFException(TOOLKIT_ASN, -1, "Unsupported name form");
00661             break;
00662     }
00663     return NO_MATCH;
00664 }
00665 
00680 CPKIFGeneralSubtree::MatchState CPKIFGeneralSubtree::IsInSubtree(
00682     const CPKIFGeneralNamePtr & name
00683     )const 
00684 {
00685     // since these can now be constructed and set by apps, make sure
00686     // fields are initialized before use
00687     if(!m_impl->m_base || -1 == m_impl->m_nMin)
00688         throw CPKIFException(TOOLKIT_ASN,COMMON_NOT_INITIALIZED,"An attempt was made to use an uninitialized GeneralSubtree");
00689     if(!name)
00690         throw CPKIFException(TOOLKIT_X509_ASN, COMMON_INVALID_INPUT, "CPKIFGeneralSubtree::IsInSubtree called with an invalid CPKIFGeneralName object"); 
00691 
00692     if(name->GetType() != m_impl->m_base->GetType())
00693         return NOT_APPLICABLE;
00694 
00695     switch(name->GetType())
00696     {
00697     case CPKIFGeneralName::DIRECTORYNAME:
00698         return IsInSubtree(name->directoryName());
00699         break;
00700     case CPKIFGeneralName::DNSNAME:
00701         return m_impl->DNSNameIsInSubtree(name->dnsName());
00702         break;
00703     case CPKIFGeneralName::IPADDRESS:
00704         return m_impl->IPAddressIsInSubtree(name->ipAddress());
00705         break;
00706     case CPKIFGeneralName::RFC822:
00707         return m_impl->RFC822NameIsInSubtree(name->rfc822Name());
00708         break;
00709     case CPKIFGeneralName::URI:
00710         return m_impl->URINameIsInSubtree(name->uri());
00711         break;
00712     default:
00713         throw CPKIFException(TOOLKIT_ASN,-1,"Unsupported name form");
00714     }
00715     return NO_MATCH;
00716 }
00717 
00727 bool CPKIFGeneralSubtree::IsSupported(
00729     CPKIFGeneralName::GENNAMETYPE type)
00730 {
00731     switch(type)
00732     {
00733     case CPKIFGeneralName::DIRECTORYNAME:
00734     case CPKIFGeneralName::DNSNAME:
00735     case CPKIFGeneralName::IPADDRESS:
00736     case CPKIFGeneralName::RFC822:
00737     case CPKIFGeneralName::URI:
00738         return true;
00739         break;
00740     default:
00741         break;
00742     }
00743     return false;
00744 }
00745 
00756 void CPKIFGeneralSubtree::SetEmpty() 
00757 {
00758     m_impl->m_empty = true;
00759 }
00760 
00769 CPKIFGeneralSubtree * CPKIFGeneralSubtree::ShallowCopy()
00770 {
00771     CPKIFGeneralSubtree * tmp = new CPKIFGeneralSubtree();
00772     tmp->m_impl->m_base = m_impl->m_base;
00773     tmp->m_impl->m_nMax = m_impl->m_nMax;
00774     tmp->m_impl->m_nMin = m_impl->m_nMin;
00775     tmp->m_impl->m_empty = m_impl->m_empty;
00776     return tmp;
00777 }

Generated on Mon Nov 15 11:15:51 2010 for PublicKeyInfrastructureFramework(PKIF) by  doxygen 1.5.6