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  }