github.com/zmap/zcrypto@v0.0.0-20240512203510-0fef58d9a9db/x509/tor_service_descriptor.go (about) 1 package x509 2 3 import ( 4 "github.com/zmap/zcrypto/encoding/asn1" 5 "github.com/zmap/zcrypto/x509/pkix" 6 ) 7 8 var ( 9 // oidBRTorServiceDescriptor is the assigned OID for the CAB Forum Tor Service 10 // Descriptor Hash extension (see EV Guidelines Appendix F) 11 oidBRTorServiceDescriptor = asn1.ObjectIdentifier{2, 23, 140, 1, 31} 12 ) 13 14 // TorServiceDescriptorHash is a structure corrsponding to the 15 // TorServiceDescriptorHash SEQUENCE described in Appendix F ("Issuance of 16 // Certificates for .onion Domain Names"). 17 // 18 // Each TorServiceDescriptorHash holds an onion URI (a utf8 string with the 19 // .onion address that was validated), a hash algorithm name (computed based on 20 // the pkix.AlgorithmIdentifier in the TorServiceDescriptorHash), the hash bytes 21 // (computed over the DER encoding of the ASN.1 SubjectPublicKey of the .onion 22 // service), and the number of bits in the hash bytes. 23 type TorServiceDescriptorHash struct { 24 Onion string `json:"onion"` 25 Algorithm pkix.AlgorithmIdentifier `json:"-"` 26 AlgorithmName string `json:"algorithm_name"` 27 Hash CertificateFingerprint `json:"hash"` 28 HashBits int `json:"hash_bits"` 29 } 30 31 // parseTorServiceDescriptorSyntax parses the given pkix.Extension (assumed to 32 // have OID == oidBRTorServiceDescriptor) and returns a slice of parsed 33 // TorServiceDescriptorHash objects, or an error. An error will be returned if 34 // there are any structural errors related to the ASN.1 content (wrong tags, 35 // trailing data, missing fields, etc). 36 func parseTorServiceDescriptorSyntax(ext pkix.Extension) ([]*TorServiceDescriptorHash, error) { 37 // TorServiceDescriptorSyntax ::= 38 // SEQUENCE ( 1..MAX ) of TorServiceDescriptorHash 39 var seq asn1.RawValue 40 rest, err := asn1.Unmarshal(ext.Value, &seq) 41 if err != nil { 42 return nil, asn1.SyntaxError{ 43 Msg: "unable to unmarshal outer TorServiceDescriptor SEQUENCE", 44 } 45 } 46 if len(rest) != 0 { 47 return nil, asn1.SyntaxError{ 48 Msg: "trailing data after outer TorServiceDescriptor SEQUENCE", 49 } 50 } 51 if seq.Tag != asn1.TagSequence || seq.Class != asn1.ClassUniversal || !seq.IsCompound { 52 return nil, asn1.SyntaxError{ 53 Msg: "invalid outer TorServiceDescriptor SEQUENCE", 54 } 55 } 56 57 var descriptors []*TorServiceDescriptorHash 58 rest = seq.Bytes 59 for len(rest) > 0 { 60 var descriptor *TorServiceDescriptorHash 61 descriptor, rest, err = parseTorServiceDescriptorHash(rest) 62 if err != nil { 63 return nil, err 64 } 65 descriptors = append(descriptors, descriptor) 66 } 67 return descriptors, nil 68 } 69 70 // parseTorServiceDescriptorHash unmarshals a SEQUENCE from the provided data 71 // and parses a TorServiceDescriptorHash using the data contained in the 72 // sequence. The TorServiceDescriptorHash object and the remaining data are 73 // returned if no error occurs. 74 func parseTorServiceDescriptorHash(data []byte) (*TorServiceDescriptorHash, []byte, error) { 75 // TorServiceDescriptorHash:: = SEQUENCE { 76 // onionURI UTF8String 77 // algorithm AlgorithmIdentifier 78 // subjectPublicKeyHash BIT STRING 79 // } 80 var outerSeq asn1.RawValue 81 var err error 82 data, err = asn1.Unmarshal(data, &outerSeq) 83 if err != nil { 84 return nil, data, asn1.SyntaxError{ 85 Msg: "error unmarshaling TorServiceDescriptorHash SEQUENCE", 86 } 87 } 88 if outerSeq.Tag != asn1.TagSequence || 89 outerSeq.Class != asn1.ClassUniversal || 90 !outerSeq.IsCompound { 91 return nil, data, asn1.SyntaxError{ 92 Msg: "TorServiceDescriptorHash missing compound SEQUENCE tag", 93 } 94 } 95 fieldData := outerSeq.Bytes 96 97 // Unmarshal and verify the structure of the onionURI UTF8String field. 98 var rawOnionURI asn1.RawValue 99 fieldData, err = asn1.Unmarshal(fieldData, &rawOnionURI) 100 if err != nil { 101 return nil, data, asn1.SyntaxError{ 102 Msg: "error unmarshaling TorServiceDescriptorHash onionURI", 103 } 104 } 105 if rawOnionURI.Tag != asn1.TagUTF8String || 106 rawOnionURI.Class != asn1.ClassUniversal || 107 rawOnionURI.IsCompound { 108 return nil, data, asn1.SyntaxError{ 109 Msg: "TorServiceDescriptorHash missing non-compound UTF8String tag", 110 } 111 } 112 113 // Unmarshal and verify the structure of the algorithm UTF8String field. 114 var algorithm pkix.AlgorithmIdentifier 115 fieldData, err = asn1.Unmarshal(fieldData, &algorithm) 116 if err != nil { 117 return nil, nil, asn1.SyntaxError{ 118 Msg: "error unmarshaling TorServiceDescriptorHash algorithm", 119 } 120 } 121 122 var algorithmName string 123 if algorithm.Algorithm.Equal(oidSHA256) { 124 algorithmName = "SHA256" 125 } else if algorithm.Algorithm.Equal(oidSHA384) { 126 algorithmName = "SHA384" 127 } else if algorithm.Algorithm.Equal(oidSHA512) { 128 algorithmName = "SHA512" 129 } else { 130 algorithmName = "Unknown" 131 } 132 133 // Unmarshal and verify the structure of the Subject Public Key Hash BitString 134 // field. 135 var spkh asn1.BitString 136 fieldData, err = asn1.Unmarshal(fieldData, &spkh) 137 if err != nil { 138 return nil, data, asn1.SyntaxError{ 139 Msg: "error unmarshaling TorServiceDescriptorHash Hash", 140 } 141 } 142 143 // There should be no trailing data after the TorServiceDescriptorHash 144 // SEQUENCE. 145 if len(fieldData) > 0 { 146 return nil, data, asn1.SyntaxError{ 147 Msg: "trailing data after TorServiceDescriptorHash", 148 } 149 } 150 151 return &TorServiceDescriptorHash{ 152 Onion: string(rawOnionURI.Bytes), 153 Algorithm: algorithm, 154 AlgorithmName: algorithmName, 155 HashBits: spkh.BitLength, 156 Hash: CertificateFingerprint(spkh.Bytes), 157 }, data, nil 158 }