]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/x509: add certificate support.
authorAdam Langley <agl@golang.org>
Thu, 19 Nov 2009 00:08:24 +0000 (16:08 -0800)
committerAdam Langley <agl@golang.org>
Thu, 19 Nov 2009 00:08:24 +0000 (16:08 -0800)
R=rsc
CC=golang-dev
https://golang.org/cl/156054

src/pkg/crypto/x509/x509.go
src/pkg/crypto/x509/x509_test.go

index c1488e41e09c649c944c700c97ec907a20627767..ee0f3858d1646c083bb547d7b4e049e41e3b832d 100644 (file)
@@ -2,16 +2,19 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//     NOTE: PACKAGE UNDER CONSTRUCTION.
-//
 // This package parses X.509-encoded keys and certificates.
 package x509
 
 import (
        "asn1";
        "big";
+       "container/vector";
        "crypto/rsa";
+       "crypto/sha1";
+       "hash";
        "os";
+       "strings";
+       "time";
 )
 
 // pkcs1PrivateKey is a structure which mirrors the PKCS#1 ASN.1 for an RSA private key.
@@ -32,7 +35,11 @@ func rawValueIsInteger(raw *asn1.RawValue) bool {
 // ParsePKCS1PrivateKey returns an RSA private key from its ASN.1 PKCS#1 DER encoded form.
 func ParsePKCS1PrivateKey(der []byte) (key *rsa.PrivateKey, err os.Error) {
        var priv pkcs1PrivateKey;
-       _, err = asn1.Unmarshal(&priv, der);
+       rest, err := asn1.Unmarshal(&priv, der);
+       if len(rest) > 0 {
+               err = asn1.SyntaxError{"trailing data"};
+               return;
+       }
        if err != nil {
                return
        }
@@ -61,3 +68,524 @@ func ParsePKCS1PrivateKey(der []byte) (key *rsa.PrivateKey, err os.Error) {
        }
        return;
 }
+
+// These structures reflect the ASN.1 structure of X.509 certificates.:
+
+type certificate struct {
+       TBSCertificate          tbsCertificate;
+       SignatureAlgorithm      algorithmIdentifier;
+       SignatureValue          asn1.BitString;
+}
+
+type tbsCertificate struct {
+       Raw                     asn1.RawContents;
+       Version                 int     "optional,explicit,default:1,tag:0";
+       SerialNumber            asn1.RawValue;
+       SignatureAlgorithm      algorithmIdentifier;
+       Issuer                  rdnSequence;
+       Validity                validity;
+       Subject                 rdnSequence;
+       PublicKey               publicKeyInfo;
+       UniqueId                asn1.BitString  "optional,explicit,tag:1";
+       SubjectUniqueId         asn1.BitString  "optional,explicit,tag:2";
+       Extensions              []extension     "optional,explicit,tag:3";
+}
+
+type algorithmIdentifier struct {
+       Algorithm asn1.ObjectIdentifier;
+}
+
+type rdnSequence []relativeDistinguishedName
+
+type relativeDistinguishedName []attributeTypeAndValue
+
+type attributeTypeAndValue struct {
+       Type    asn1.ObjectIdentifier;
+       Value   interface{};
+}
+
+type validity struct {
+       NotBefore, NotAfter *time.Time;
+}
+
+type publicKeyInfo struct {
+       Algorithm       algorithmIdentifier;
+       PublicKey       asn1.BitString;
+}
+
+type extension struct {
+       Id              asn1.ObjectIdentifier;
+       Critical        bool    "optional";
+       Value           []byte;
+}
+
+// RFC 5280,  4.2.1.1
+type authKeyId struct {
+       Id []byte "optional,tag:0";
+}
+
+type SignatureAlgorithm int
+
+const (
+       UnknownSignatureAlgorithm       SignatureAlgorithm      = iota;
+       MD2WithRSA;
+       MD5WithRSA;
+       SHA1WithRSA;
+       SHA256WithRSA;
+       SHA384WithRSA;
+       SHA512WithRSA;
+)
+
+type PublicKeyAlgorithm int
+
+const (
+       UnknownPublicKeyAlgorithm       PublicKeyAlgorithm      = iota;
+       RSA;
+)
+
+// Name represents an X.509 distinguished name. This only includes the common
+// elements of a DN.  Additional elements in the name are ignored.
+type Name struct {
+       Country, Organization, OrganizationalUnit       string;
+       CommonName, SerialNumber, Locality              string;
+       Province, StreetAddress, PostalCode             string;
+}
+
+func (n *Name) fillFromRDNSequence(rdns *rdnSequence) {
+       for _, rdn := range *rdns {
+               if len(rdn) == 0 {
+                       continue
+               }
+               atv := rdn[0];
+               value, ok := atv.Value.(string);
+               if !ok {
+                       continue
+               }
+
+               t := atv.Type;
+               if len(t) == 4 && t[0] == 2 && t[1] == 5 && t[2] == 4 {
+                       switch t[3] {
+                       case 3:
+                               n.CommonName = value
+                       case 5:
+                               n.SerialNumber = value
+                       case 6:
+                               n.Country = value
+                       case 7:
+                               n.Locality = value
+                       case 8:
+                               n.Province = value
+                       case 9:
+                               n.StreetAddress = value
+                       case 10:
+                               n.Organization = value
+                       case 11:
+                               n.OrganizationalUnit = value
+                       case 17:
+                               n.PostalCode = value
+                       }
+               }
+       }
+}
+
+func getSignatureAlgorithmFromOID(oid []int) SignatureAlgorithm {
+       if len(oid) == 7 && oid[0] == 1 && oid[1] == 2 && oid[2] == 840 &&
+               oid[3] == 113549 && oid[4] == 1 && oid[5] == 1 {
+               switch oid[6] {
+               case 2:
+                       return MD2WithRSA
+               case 4:
+                       return MD5WithRSA
+               case 5:
+                       return SHA1WithRSA
+               case 11:
+                       return SHA256WithRSA
+               case 12:
+                       return SHA384WithRSA
+               case 13:
+                       return SHA512WithRSA
+               }
+       }
+
+       return UnknownSignatureAlgorithm;
+}
+
+func getPublicKeyAlgorithmFromOID(oid []int) PublicKeyAlgorithm {
+       if len(oid) == 7 && oid[0] == 1 && oid[1] == 2 && oid[2] == 840 &&
+               oid[3] == 113549 && oid[4] == 1 && oid[5] == 1 {
+               switch oid[6] {
+               case 1:
+                       return RSA
+               }
+       }
+
+       return UnknownPublicKeyAlgorithm;
+}
+
+// KeyUsage represents the set of actions that are valid for a given key. It's
+// a bitmap of the KeyUsage* constants.
+type KeyUsage int
+
+const (
+       KeyUsageDigitalSignature        KeyUsage        = 1 << iota;
+       KeyUsageContentCommitment;
+       KeyUsageKeyEncipherment;
+       KeyUsageDataEncipherment;
+       KeyUsageKeyAgreement;
+       KeyUsageCertSign;
+       KeyUsageCRLSign;
+       KeyUsageEncipherOnly;
+       KeyUsageDecipherOnly;
+)
+
+// A Certificate represents an X.509 certificate.
+type Certificate struct {
+       Raw                     []byte; // Raw ASN.1 DER contents.
+       Signature               []byte;
+       SignatureAlgorithm      SignatureAlgorithm;
+
+       PublicKeyAlgorithm      PublicKeyAlgorithm;
+       PublicKey               interface{};
+
+       Version                 int;
+       SerialNumber            []byte;
+       Issuer                  Name;
+       Subject                 Name;
+       NotBefore, NotAfter     *time.Time;     // Validity bounds.
+       KeyUsage                KeyUsage;
+
+       BasicConstraintsValid   bool;   // if true then the next two fields are valid.
+       IsCA                    bool;
+       MaxPathLen              int;
+
+       SubjectKeyId    []byte;
+       AuthorityKeyId  []byte;
+
+       // Subject Alternate Name values
+       DNSNames        []string;
+       EmailAddresses  []string;
+}
+
+// UnsupportedAlgorithmError results from attempting to perform an operation
+// that involves algorithms that are not currently implemented.
+type UnsupportedAlgorithmError struct{}
+
+func (UnsupportedAlgorithmError) String() string {
+       return "cannot verify signature: algorithm unimplemented"
+}
+
+// ConstraintViolationError results when a requested usage is not permitted by
+// a certificate. For example: checking a signature when the public key isn't a
+// certificate signing key.
+type ConstraintViolationError struct{}
+
+func (ConstraintViolationError) String() string {
+       return "invalid signature: parent certificate cannot sign this kind of certificate"
+}
+
+// CheckSignatureFrom verifies that the signature on c is a valid signature
+// from parent.
+func (c *Certificate) CheckSignatureFrom(parent *Certificate) (err os.Error) {
+       // RFC 5280, 4.2.1.9:
+       // "If the basic constraints extension is not present in a version 3
+       // certificate, or the extension is present but the cA boolean is not
+       // asserted, then the certified public key MUST NOT be used to verify
+       // certificate signatures."
+       if parent.Version == 3 && !parent.BasicConstraintsValid ||
+               parent.BasicConstraintsValid && !parent.IsCA {
+               return ConstraintViolationError{}
+       }
+
+       if parent.KeyUsage != 0 && parent.KeyUsage&KeyUsageCertSign == 0 {
+               return ConstraintViolationError{}
+       }
+
+       if parent.PublicKeyAlgorithm == UnknownPublicKeyAlgorithm {
+               return UnsupportedAlgorithmError{}
+       }
+
+       // TODO(agl): don't ignore the path length constraint.
+
+       var h hash.Hash;
+       var hashType rsa.PKCS1v15Hash;
+
+       switch c.SignatureAlgorithm {
+       case SHA1WithRSA:
+               h = sha1.New();
+               hashType = rsa.HashSHA1;
+       default:
+               return UnsupportedAlgorithmError{}
+       }
+
+       pub, ok := parent.PublicKey.(*rsa.PublicKey);
+       if !ok {
+               return UnsupportedAlgorithmError{}
+       }
+
+       h.Write(c.Raw);
+       digest := h.Sum();
+
+       return rsa.VerifyPKCS1v15(pub, hashType, digest, c.Signature);
+}
+
+func matchHostnames(pattern, host string) bool {
+       if len(pattern) == 0 || len(host) == 0 {
+               return false
+       }
+
+       patternParts := strings.Split(pattern, ".", 0);
+       hostParts := strings.Split(host, ".", 0);
+
+       if len(patternParts) != len(hostParts) {
+               return false
+       }
+
+       for i, patternPart := range patternParts {
+               if patternPart == "*" {
+                       continue
+               }
+               if patternPart != hostParts[i] {
+                       return false
+               }
+       }
+
+       return true;
+}
+
+// IsValidForHost returns true iff c is a valid certificate for the given host.
+func (c *Certificate) IsValidForHost(h string) bool {
+       if len(c.DNSNames) > 0 {
+               for _, match := range c.DNSNames {
+                       if matchHostnames(match, h) {
+                               return true
+                       }
+               }
+               // If Subject Alt Name is given, we ignore the common name.
+               return false;
+       }
+
+       return matchHostnames(c.Subject.CommonName, h);
+}
+
+type UnhandledCriticalExtension struct{}
+
+func (h UnhandledCriticalExtension) String() string {
+       return "unhandled critical extension"
+}
+
+type basicConstraints struct {
+       IsCA            bool    "optional";
+       MaxPathLen      int     "optional";
+}
+
+type rsaPublicKey struct {
+       N       asn1.RawValue;
+       E       int;
+}
+
+func parsePublicKey(algo PublicKeyAlgorithm, asn1Data []byte) (interface{}, os.Error) {
+       switch algo {
+       case RSA:
+               p := new(rsaPublicKey);
+               _, err := asn1.Unmarshal(p, asn1Data);
+               if err != nil {
+                       return nil, err
+               }
+
+               if !rawValueIsInteger(&p.N) {
+                       return nil, asn1.StructuralError{"tags don't match"}
+               }
+
+               pub := &rsa.PublicKey{
+                       E: p.E,
+                       N: new(big.Int).SetBytes(p.N.Bytes),
+               };
+               return pub, nil;
+       default:
+               return nil, nil
+       }
+
+       panic("unreachable");
+}
+
+func appendString(in []string, v string) (out []string) {
+       if cap(in)-len(in) < 1 {
+               out = make([]string, len(in)+1, len(in)*2+1);
+               for i, v := range in {
+                       out[i] = v
+               }
+       } else {
+               out = in[0 : len(in)+1]
+       }
+       out[len(in)] = v;
+       return out;
+}
+
+func parseCertificate(in *certificate) (*Certificate, os.Error) {
+       out := new(Certificate);
+       out.Raw = in.TBSCertificate.Raw;
+
+       out.Signature = in.SignatureValue.RightAlign();
+       out.SignatureAlgorithm =
+               getSignatureAlgorithmFromOID(in.TBSCertificate.SignatureAlgorithm.Algorithm);
+
+       out.PublicKeyAlgorithm =
+               getPublicKeyAlgorithmFromOID(in.TBSCertificate.PublicKey.Algorithm.Algorithm);
+       var err os.Error;
+       out.PublicKey, err = parsePublicKey(out.PublicKeyAlgorithm, in.TBSCertificate.PublicKey.PublicKey.RightAlign());
+       if err != nil {
+               return nil, err
+       }
+
+       out.Version = in.TBSCertificate.Version;
+       out.SerialNumber = in.TBSCertificate.SerialNumber.Bytes;
+       out.Issuer.fillFromRDNSequence(&in.TBSCertificate.Issuer);
+       out.Subject.fillFromRDNSequence(&in.TBSCertificate.Subject);
+       out.NotBefore = in.TBSCertificate.Validity.NotBefore;
+       out.NotAfter = in.TBSCertificate.Validity.NotAfter;
+
+       for _, e := range in.TBSCertificate.Extensions {
+               if len(e.Id) == 4 && e.Id[0] == 2 && e.Id[1] == 5 && e.Id[2] == 29 {
+                       switch e.Id[3] {
+                       case 15:
+                               // RFC 5280, 4.2.1.3
+                               var usageBits asn1.BitString;
+                               _, err := asn1.Unmarshal(&usageBits, e.Value);
+
+                               if err == nil {
+                                       var usage int;
+                                       for i := 0; i < 9; i++ {
+                                               if usageBits.At(i) != 0 {
+                                                       usage |= 1 << uint(i)
+                                               }
+                                       }
+                                       out.KeyUsage = KeyUsage(usage);
+                                       continue;
+                               }
+                       case 19:
+                               // RFC 5280, 4.2.1.9
+                               var constriants basicConstraints;
+                               _, err := asn1.Unmarshal(&constriants, e.Value);
+
+                               if err == nil {
+                                       out.BasicConstraintsValid = true;
+                                       out.IsCA = constriants.IsCA;
+                                       out.MaxPathLen = constriants.MaxPathLen;
+                                       continue;
+                               }
+                       case 17:
+                               // RFC 5280, 4.2.1.6
+
+                               // SubjectAltName ::= GeneralNames
+                               //
+                               // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+                               //
+                               // GeneralName ::= CHOICE {
+                               //      otherName                       [0]     OtherName,
+                               //      rfc822Name                      [1]     IA5String,
+                               //      dNSName                         [2]     IA5String,
+                               //      x400Address                     [3]     ORAddress,
+                               //      directoryName                   [4]     Name,
+                               //      ediPartyName                    [5]     EDIPartyName,
+                               //      uniformResourceIdentifier       [6]     IA5String,
+                               //      iPAddress                       [7]     OCTET STRING,
+                               //      registeredID                    [8]     OBJECT IDENTIFIER }
+                               var seq asn1.RawValue;
+                               _, err := asn1.Unmarshal(&seq, e.Value);
+                               if err != nil {
+                                       return nil, err
+                               }
+                               if !seq.IsCompound || seq.Tag != 16 || seq.Class != 0 {
+                                       return nil, asn1.StructuralError{"bad SAN sequence"}
+                               }
+
+                               parsedName := false;
+
+                               rest := seq.Bytes;
+                               for len(rest) > 0 {
+                                       var v asn1.RawValue;
+                                       rest, err = asn1.Unmarshal(&v, rest);
+                                       if err != nil {
+                                               return nil, err
+                                       }
+                                       switch v.Tag {
+                                       case 1:
+                                               out.EmailAddresses = appendString(out.EmailAddresses, string(v.Bytes));
+                                               parsedName = true;
+                                       case 2:
+                                               out.DNSNames = appendString(out.DNSNames, string(v.Bytes));
+                                               parsedName = true;
+                                       }
+                               }
+
+                               if parsedName {
+                                       continue
+                               }
+                               // If we didn't parse any of the names then we
+                               // fall through to the critical check below.
+
+                       case 35:
+                               // RFC 5280, 4.2.1.1
+                               var a authKeyId;
+                               _, err = asn1.Unmarshal(&a, e.Value);
+                               if err != nil {
+                                       return nil, err
+                               }
+                               out.AuthorityKeyId = a.Id;
+                               continue;
+
+                       case 14:
+                               // RFC 5280, 4.2.1.2
+                               out.SubjectKeyId = e.Value;
+                               continue;
+                       }
+               }
+
+               if e.Critical {
+                       return out, UnhandledCriticalExtension{}
+               }
+       }
+
+       return out, nil;
+}
+
+// ParseCertificate parses a single certificate from the given ASN.1 DER data.
+func ParseCertificate(asn1Data []byte) (*Certificate, os.Error) {
+       var cert certificate;
+       rest, err := asn1.Unmarshal(&cert, asn1Data);
+       if err != nil {
+               return nil, err
+       }
+       if len(rest) > 0 {
+               return nil, asn1.SyntaxError{"trailing data"}
+       }
+
+       return parseCertificate(&cert);
+}
+
+// ParseCertificates parses one or more certificates from the given ASN.1 DER
+// data. The certificates must be concatenated with no intermediate padding.
+func ParseCertificates(asn1Data []byte) ([]*Certificate, os.Error) {
+       v := vector.New(0);
+
+       for len(asn1Data) > 0 {
+               cert := new(certificate);
+               var err os.Error;
+               asn1Data, err = asn1.Unmarshal(cert, asn1Data);
+               if err != nil {
+                       return nil, err
+               }
+               v.Push(cert);
+       }
+
+       ret := make([]*Certificate, v.Len());
+       for i := 0; i < v.Len(); i++ {
+               cert, err := parseCertificate(v.At(i).(*certificate));
+               if err != nil {
+                       return nil, err
+               }
+               ret[i] = cert;
+       }
+
+       return ret, nil;
+}
index 2c94d35ee7bea289eb4bdc0d0b9672391d673ca0..17244d6ec15ba636132f2d2615c4e2ea8d08068e 100644 (file)
@@ -7,6 +7,7 @@ package x509
 import (
        "big";
        "crypto/rsa";
+       "encoding/hex";
        "encoding/pem";
        "reflect";
        "strings";
@@ -50,3 +51,94 @@ var rsaPrivateKey = &rsa.PrivateKey{
        P: bigFromString("98920366548084643601728869055592650835572950932266967461790948584315647051443"),
        Q: bigFromString("94560208308847015747498523884063394671606671904944666360068158221458669711639"),
 }
+
+type matchHostnamesTest struct {
+       pattern, host   string;
+       ok              bool;
+}
+
+var matchHostnamesTests = []matchHostnamesTest{
+       matchHostnamesTest{"a.b.c", "a.b.c", true},
+       matchHostnamesTest{"a.b.c", "b.b.c", false},
+       matchHostnamesTest{"", "b.b.c", false},
+       matchHostnamesTest{"a.b.c", "", false},
+       matchHostnamesTest{"example.com", "example.com", true},
+       matchHostnamesTest{"example.com", "www.example.com", false},
+       matchHostnamesTest{"*.example.com", "www.example.com", true},
+       matchHostnamesTest{"*.example.com", "xyz.www.example.com", false},
+       matchHostnamesTest{"*.*.example.com", "xyz.www.example.com", true},
+       matchHostnamesTest{"*.www.*.com", "xyz.www.example.com", true},
+}
+
+func TestMatchHostnames(t *testing.T) {
+       for i, test := range matchHostnamesTests {
+               r := matchHostnames(test.pattern, test.host);
+               if r != test.ok {
+                       t.Errorf("#%d mismatch got: %t want: %t", i, r, test.ok)
+               }
+       }
+}
+
+func TestCertificateParse(t *testing.T) {
+       s, _ := hex.DecodeString(certBytes);
+       certs, err := ParseCertificates(s);
+       if err != nil {
+               t.Error(err)
+       }
+       if len(certs) != 2 {
+               t.Errorf("Wrong number of certs: got %d want 2", len(certs));
+               return;
+       }
+
+       err = certs[0].CheckSignatureFrom(certs[1]);
+       if err != nil {
+               t.Error(err)
+       }
+
+       if !certs[0].IsValidForHost("mail.google.com") {
+               t.Errorf("cert not valid for host")
+       }
+}
+
+var certBytes = "308203223082028ba00302010202106edf0d9499fd4533dd1297fc42a93be1300d06092a864886"
+       "f70d0101050500304c310b3009060355040613025a4131253023060355040a131c546861777465"
+       "20436f6e73756c74696e67202850747929204c74642e311630140603550403130d546861777465"
+       "20534743204341301e170d3039303332353136343932395a170d3130303332353136343932395a"
+       "3069310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630"
+       "140603550407130d4d6f756e7461696e205669657731133011060355040a130a476f6f676c6520"
+       "496e63311830160603550403130f6d61696c2e676f6f676c652e636f6d30819f300d06092a8648"
+       "86f70d010101050003818d0030818902818100c5d6f892fccaf5614b064149e80a2c9581a218ef"
+       "41ec35bd7a58125ae76f9ea54ddc893abbeb029f6b73616bf0ffd868791fba7af9c4aebf3706ba"
+       "3eeaeed27435b4ddcfb157c05f351d66aa87fee0de072d66d773affbd36ab78bef090e0cc861a9"
+       "03ac90dd98b51c9c41566c017f0beec3bff391051ffba0f5cc6850ad2a590203010001a381e730"
+       "81e430280603551d250421301f06082b0601050507030106082b06010505070302060960864801"
+       "86f842040130360603551d1f042f302d302ba029a0278625687474703a2f2f63726c2e74686177"
+       "74652e636f6d2f54686177746553474343412e63726c307206082b060105050701010466306430"
+       "2206082b060105050730018616687474703a2f2f6f6373702e7468617774652e636f6d303e0608"
+       "2b060105050730028632687474703a2f2f7777772e7468617774652e636f6d2f7265706f736974"
+       "6f72792f5468617774655f5347435f43412e637274300c0603551d130101ff04023000300d0609"
+       "2a864886f70d01010505000381810062f1f3050ebc105e497c7aedf87e24d2f4a986bb3b837bd1"
+       "9b91ebcad98b065992f6bd2b49b7d6d3cb2e427a99d606c7b1d46352527fac39e6a8b6726de5bf"
+       "70212a52cba07634a5e332011bd1868e78eb5e3c93cf03072276786f207494feaa0ed9d53b2110"
+       "a76571f90209cdae884385c882587030ee15f33d761e2e45a6bc308203233082028ca003020102"
+       "020430000002300d06092a864886f70d0101050500305f310b3009060355040613025553311730"
+       "15060355040a130e566572695369676e2c20496e632e31373035060355040b132e436c61737320"
+       "33205075626c6963205072696d6172792043657274696669636174696f6e20417574686f726974"
+       "79301e170d3034303531333030303030305a170d3134303531323233353935395a304c310b3009"
+       "060355040613025a4131253023060355040a131c54686177746520436f6e73756c74696e672028"
+       "50747929204c74642e311630140603550403130d5468617774652053474320434130819f300d06"
+       "092a864886f70d010101050003818d0030818902818100d4d367d08d157faecd31fe7d1d91a13f"
+       "0b713cacccc864fb63fc324b0794bd6f80ba2fe10493c033fc093323e90b742b71c403c6d2cde2"
+       "2ff50963cdff48a500bfe0e7f388b72d32de9836e60aad007bc4644a3b847503f270927d0e62f5"
+       "21ab693684317590f8bfc76c881b06957cc9e5a8de75a12c7a68dfd5ca1c875860190203010001"
+       "a381fe3081fb30120603551d130101ff040830060101ff020100300b0603551d0f040403020106"
+       "301106096086480186f842010104040302010630280603551d110421301fa41d301b3119301706"
+       "035504031310507269766174654c6162656c332d313530310603551d1f042a30283026a024a022"
+       "8620687474703a2f2f63726c2e766572697369676e2e636f6d2f706361332e63726c303206082b"
+       "0601050507010104263024302206082b060105050730018616687474703a2f2f6f6373702e7468"
+       "617774652e636f6d30340603551d25042d302b06082b0601050507030106082b06010505070302"
+       "06096086480186f8420401060a6086480186f845010801300d06092a864886f70d010105050003"
+       "81810055ac63eadea1ddd2905f9f0bce76be13518f93d9052bc81b774bad6950a1eededcfddb07"
+       "e9e83994dcab72792f06bfab8170c4a8edea5334edef1e53d906c7562bd15cf4d18a8eb42bb137"
+       "9048084225c53e8acb7feb6f04d16dc574a2f7a27c7b603c77cd0ece48027f012fb69b37e02a2a"
+       "36dcd585d6ace53f546f961e05af"