github.com/zmap/zcrypto@v0.0.0-20240512203510-0fef58d9a9db/x509/tor_service_descriptor_test.go (about) 1 package x509 2 3 import ( 4 "fmt" 5 "reflect" 6 "testing" 7 8 "github.com/zmap/zcrypto/encoding/asn1" 9 "github.com/zmap/zcrypto/x509/pkix" 10 ) 11 12 // TestParseTorServiceDescriptorSyntax tests that parsing certificates with the 13 // CAB Forum TorServiceDescriptorSyntax x509 extension works correctly. 14 func TestParseTorServiceDescriptorSyntax(t *testing.T) { 15 // expected TorServiceDescriptorHash hash bytes from test certs. 16 mockHashBytes := []byte{ 17 0xc7, 0x49, 0xf5, 0xb2, 0x49, 0x9c, 0x8f, 0x65, 18 0x5c, 0x19, 0xb3, 0x3f, 0xf9, 0x3e, 0x03, 0x7b, 19 0x7b, 0x7d, 0xbe, 0x47, 0x2a, 0xac, 0x62, 0x78, 20 0x30, 0x71, 0xb0, 0x39, 0xb8, 0x66, 0x38, 0x5c, 21 } 22 mockAlgorithm := pkix.AlgorithmIdentifier{ 23 Algorithm: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}, 24 } 25 // mustASN1 marshals a given object to its ASN1 bytes or panics. 26 mustASN1 := func(value interface{}) []byte { 27 result, err := asn1.Marshal(value) 28 if err != nil { 29 panic(fmt.Sprintf("err marshaling asn1 test data: %v", err)) 30 } 31 return result 32 } 33 // sequence marshals a ASN1 SEQUENCE for the given bytes. 34 sequence := func(bytes []byte) []byte { 35 return mustASN1(asn1.RawValue{ 36 Tag: asn1.TagSequence, 37 Class: asn1.ClassUniversal, 38 IsCompound: true, 39 Bytes: bytes, 40 }) 41 } 42 // torServiceDescriptorHash constructs a marshaled SEQUENCE for 43 // a TorServiceDescriptorHash with the given values. 44 torServiceDescriptorHash := func(onion string, algorithm pkix.AlgorithmIdentifier, hash []byte, bits int) []byte { 45 return sequence( 46 append(mustASN1(asn1.RawValue{ 47 Tag: asn1.TagUTF8String, 48 Class: asn1.ClassUniversal, 49 Bytes: []byte(onion), 50 }), 51 append( 52 mustASN1(algorithm), 53 mustASN1(asn1.BitString{ 54 Bytes: hash, 55 BitLength: bits, 56 })...)..., 57 )) 58 } 59 testCases := []struct { 60 Name string 61 InputExtension pkix.Extension 62 ExpectedErrMsg string 63 ExpectedTorServiceDescriptors []*TorServiceDescriptorHash 64 }{ 65 { 66 Name: "empty Tor service descriptor extension", 67 InputExtension: pkix.Extension{ 68 Value: nil, 69 }, 70 ExpectedErrMsg: "asn1: syntax error: unable to unmarshal outer TorServiceDescriptor SEQUENCE", 71 }, 72 { 73 Name: "invalid outer SEQUENCE in service descriptor extension", 74 InputExtension: pkix.Extension{ 75 Value: mustASN1(asn1.RawValue{}), 76 }, 77 ExpectedErrMsg: "asn1: syntax error: invalid outer TorServiceDescriptor SEQUENCE", 78 }, 79 { 80 Name: "data trailing outer SEQUENCE in service descriptor extension", 81 InputExtension: pkix.Extension{ 82 Value: append( 83 sequence(nil), // Outer SEQUENCE 84 mustASN1(asn1.RawValue{})..., // Trailing data 85 ), 86 }, 87 ExpectedErrMsg: "asn1: syntax error: trailing data after outer TorServiceDescriptor SEQUENCE", 88 }, 89 { 90 Name: "bad service descriptor onion URI field tag", 91 InputExtension: pkix.Extension{ 92 Value: sequence( // Outer SEQUENCE 93 sequence( // TorServiceDescriptorHash SEQUENCE 94 mustASN1(asn1.RawValue{}), // Invalid Onion URI 95 )), 96 }, 97 ExpectedErrMsg: "asn1: syntax error: TorServiceDescriptorHash missing non-compound UTF8String tag", 98 }, 99 { 100 Name: "bad service descriptor algorithm field", 101 InputExtension: pkix.Extension{ 102 Value: sequence( // Outer SEQUENCE 103 sequence( // TorServiceDescriptorHash SEQUENCE 104 mustASN1(asn1.RawValue{ // Onion URI 105 Tag: asn1.TagUTF8String, 106 Class: asn1.ClassUniversal, 107 }))), 108 // No pkix.AlgorithmIdentifier algorithm field 109 }, 110 ExpectedErrMsg: "asn1: syntax error: error unmarshaling TorServiceDescriptorHash algorithm", 111 }, 112 { 113 Name: "bad service descriptor hash field", 114 InputExtension: pkix.Extension{ 115 Value: sequence( // Outer SEQUENCE 116 sequence( // TorServiceDescriptorHash SEQUENCE 117 append(mustASN1(asn1.RawValue{ // Onion URI 118 Tag: asn1.TagUTF8String, 119 Class: asn1.ClassUniversal, 120 }), 121 mustASN1(mockAlgorithm)...), // Algorithm 122 )), 123 // No BitString hash field 124 }, 125 ExpectedErrMsg: "asn1: syntax error: error unmarshaling TorServiceDescriptorHash Hash", 126 }, 127 { 128 Name: "data trailing inner TorServiceDescriptorHash SEQUENCE", 129 InputExtension: pkix.Extension{ 130 Value: sequence( 131 sequence( // Outer SEQUENCE 132 append(mustASN1(asn1.RawValue{ // Onion URI 133 Tag: asn1.TagUTF8String, 134 Class: asn1.ClassUniversal, 135 }), 136 append( 137 append( 138 mustASN1(mockAlgorithm), // Algorithm 139 mustASN1(asn1.BitString{ // Hash 140 Bytes: []byte{0x00}, 141 BitLength: 1, 142 })..., 143 ), 144 mustASN1(asn1.RawValue{})..., // Trailing data 145 )..., 146 ), 147 )), 148 }, 149 ExpectedErrMsg: "asn1: syntax error: trailing data after TorServiceDescriptorHash", 150 }, 151 { 152 Name: "valid service descriptor unknown hash algorithm", 153 InputExtension: pkix.Extension{ 154 Value: sequence( // Outer SEQUENCE 155 torServiceDescriptorHash( 156 "https://zmap.onion", 157 pkix.AlgorithmIdentifier{ 158 Algorithm: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 99}, 159 }, 160 mockHashBytes, 161 256), 162 ), 163 }, 164 ExpectedTorServiceDescriptors: []*TorServiceDescriptorHash{ 165 { 166 Onion: "https://zmap.onion", 167 AlgorithmName: "Unknown", 168 Algorithm: pkix.AlgorithmIdentifier{ 169 Algorithm: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 99}, 170 }, 171 HashBits: 256, 172 Hash: mockHashBytes, 173 }, 174 }, 175 }, 176 { 177 Name: "valid service descriptor extension", 178 InputExtension: pkix.Extension{ 179 Value: sequence( // Outer SEQUENCE 180 torServiceDescriptorHash( 181 "https://zmap.onion", 182 mockAlgorithm, 183 mockHashBytes, 184 256), 185 )}, 186 ExpectedTorServiceDescriptors: []*TorServiceDescriptorHash{ 187 { 188 Onion: "https://zmap.onion", 189 AlgorithmName: "SHA256", 190 Algorithm: mockAlgorithm, 191 HashBits: 256, 192 Hash: mockHashBytes, 193 }, 194 }, 195 }, 196 { 197 Name: "valid service descriptor extension, multiple entries", 198 InputExtension: pkix.Extension{ 199 Value: sequence( // Outer SEQUENCE 200 append(torServiceDescriptorHash( 201 "https://zmap.onion", 202 mockAlgorithm, 203 mockHashBytes, 204 256), 205 torServiceDescriptorHash( 206 "https://other.onion", 207 mockAlgorithm, 208 mockHashBytes, 209 256)...), 210 )}, 211 ExpectedTorServiceDescriptors: []*TorServiceDescriptorHash{ 212 { 213 Onion: "https://zmap.onion", 214 AlgorithmName: "SHA256", 215 Algorithm: mockAlgorithm, 216 HashBits: 256, 217 Hash: mockHashBytes, 218 }, 219 { 220 Onion: "https://other.onion", 221 AlgorithmName: "SHA256", 222 Algorithm: mockAlgorithm, 223 HashBits: 256, 224 Hash: mockHashBytes, 225 }, 226 }, 227 }, 228 } 229 230 for _, tc := range testCases { 231 t.Run(tc.Name, func(t *testing.T) { 232 descs, err := parseTorServiceDescriptorSyntax(tc.InputExtension) 233 if err != nil && tc.ExpectedErrMsg == "" { 234 t.Errorf("expected no error, got %v", err) 235 } else if err == nil && tc.ExpectedErrMsg != "" { 236 t.Errorf("expected error %q, got nil", tc.ExpectedErrMsg) 237 } else if err != nil && err.Error() != tc.ExpectedErrMsg { 238 t.Errorf("expected error %q, got %q", tc.ExpectedErrMsg, err.Error()) 239 } else if err == nil && tc.ExpectedErrMsg == "" { 240 if len(descs) != len(tc.ExpectedTorServiceDescriptors) { 241 t.Errorf("expected %d TorServiceDescriptorHashes, got %d", 242 len(tc.ExpectedTorServiceDescriptors), len(descs)) 243 } 244 for i, servDesc := range descs { 245 if !reflect.DeepEqual(servDesc, tc.ExpectedTorServiceDescriptors[i]) { 246 t.Errorf("expected TorServiceDescriptors %#v in index %d, got %#v", 247 tc.ExpectedTorServiceDescriptors[i], i, servDesc) 248 } 249 } 250 } 251 }) 252 } 253 }