File: tor_service_descriptor.go

package info (click to toggle)
golang-github-zmap-zcrypto 0.0~git20240512.0fef58d-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 6,856 kB
  • sloc: python: 567; sh: 124; makefile: 9
file content (158 lines) | stat: -rw-r--r-- 5,371 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package x509

import (
	"github.com/zmap/zcrypto/encoding/asn1"
	"github.com/zmap/zcrypto/x509/pkix"
)

var (
	// oidBRTorServiceDescriptor is the assigned OID for the CAB Forum Tor Service
	// Descriptor Hash extension (see EV Guidelines Appendix F)
	oidBRTorServiceDescriptor = asn1.ObjectIdentifier{2, 23, 140, 1, 31}
)

// TorServiceDescriptorHash is a structure corrsponding to the
// TorServiceDescriptorHash SEQUENCE described in Appendix F ("Issuance of
// Certificates for .onion Domain Names").
//
// Each TorServiceDescriptorHash holds an onion URI (a utf8 string with the
// .onion address that was validated), a hash algorithm name (computed based on
// the pkix.AlgorithmIdentifier in the TorServiceDescriptorHash), the hash bytes
// (computed over the DER encoding of the ASN.1 SubjectPublicKey of the .onion
// service), and the number of bits in the hash bytes.
type TorServiceDescriptorHash struct {
	Onion         string                   `json:"onion"`
	Algorithm     pkix.AlgorithmIdentifier `json:"-"`
	AlgorithmName string                   `json:"algorithm_name"`
	Hash          CertificateFingerprint   `json:"hash"`
	HashBits      int                      `json:"hash_bits"`
}

// parseTorServiceDescriptorSyntax parses the given pkix.Extension (assumed to
// have OID == oidBRTorServiceDescriptor) and returns a slice of parsed
// TorServiceDescriptorHash objects, or an error. An error will be returned if
// there are any structural errors related to the ASN.1 content (wrong tags,
// trailing data, missing fields, etc).
func parseTorServiceDescriptorSyntax(ext pkix.Extension) ([]*TorServiceDescriptorHash, error) {
	// TorServiceDescriptorSyntax ::=
	//    SEQUENCE ( 1..MAX ) of TorServiceDescriptorHash
	var seq asn1.RawValue
	rest, err := asn1.Unmarshal(ext.Value, &seq)
	if err != nil {
		return nil, asn1.SyntaxError{
			Msg: "unable to unmarshal outer TorServiceDescriptor SEQUENCE",
		}
	}
	if len(rest) != 0 {
		return nil, asn1.SyntaxError{
			Msg: "trailing data after outer TorServiceDescriptor SEQUENCE",
		}
	}
	if seq.Tag != asn1.TagSequence || seq.Class != asn1.ClassUniversal || !seq.IsCompound {
		return nil, asn1.SyntaxError{
			Msg: "invalid outer TorServiceDescriptor SEQUENCE",
		}
	}

	var descriptors []*TorServiceDescriptorHash
	rest = seq.Bytes
	for len(rest) > 0 {
		var descriptor *TorServiceDescriptorHash
		descriptor, rest, err = parseTorServiceDescriptorHash(rest)
		if err != nil {
			return nil, err
		}
		descriptors = append(descriptors, descriptor)
	}
	return descriptors, nil
}

// parseTorServiceDescriptorHash unmarshals a SEQUENCE from the provided data
// and parses a TorServiceDescriptorHash using the data contained in the
// sequence. The TorServiceDescriptorHash object and the remaining data are
// returned if no error occurs.
func parseTorServiceDescriptorHash(data []byte) (*TorServiceDescriptorHash, []byte, error) {
	// TorServiceDescriptorHash:: = SEQUENCE {
	//   onionURI UTF8String
	//   algorithm AlgorithmIdentifier
	//   subjectPublicKeyHash BIT STRING
	// }
	var outerSeq asn1.RawValue
	var err error
	data, err = asn1.Unmarshal(data, &outerSeq)
	if err != nil {
		return nil, data, asn1.SyntaxError{
			Msg: "error unmarshaling TorServiceDescriptorHash SEQUENCE",
		}
	}
	if outerSeq.Tag != asn1.TagSequence ||
		outerSeq.Class != asn1.ClassUniversal ||
		!outerSeq.IsCompound {
		return nil, data, asn1.SyntaxError{
			Msg: "TorServiceDescriptorHash missing compound SEQUENCE tag",
		}
	}
	fieldData := outerSeq.Bytes

	// Unmarshal and verify the structure of the onionURI UTF8String field.
	var rawOnionURI asn1.RawValue
	fieldData, err = asn1.Unmarshal(fieldData, &rawOnionURI)
	if err != nil {
		return nil, data, asn1.SyntaxError{
			Msg: "error unmarshaling TorServiceDescriptorHash onionURI",
		}
	}
	if rawOnionURI.Tag != asn1.TagUTF8String ||
		rawOnionURI.Class != asn1.ClassUniversal ||
		rawOnionURI.IsCompound {
		return nil, data, asn1.SyntaxError{
			Msg: "TorServiceDescriptorHash missing non-compound UTF8String tag",
		}
	}

	// Unmarshal and verify the structure of the algorithm UTF8String field.
	var algorithm pkix.AlgorithmIdentifier
	fieldData, err = asn1.Unmarshal(fieldData, &algorithm)
	if err != nil {
		return nil, nil, asn1.SyntaxError{
			Msg: "error unmarshaling TorServiceDescriptorHash algorithm",
		}
	}

	var algorithmName string
	if algorithm.Algorithm.Equal(oidSHA256) {
		algorithmName = "SHA256"
	} else if algorithm.Algorithm.Equal(oidSHA384) {
		algorithmName = "SHA384"
	} else if algorithm.Algorithm.Equal(oidSHA512) {
		algorithmName = "SHA512"
	} else {
		algorithmName = "Unknown"
	}

	// Unmarshal and verify the structure of the Subject Public Key Hash BitString
	// field.
	var spkh asn1.BitString
	fieldData, err = asn1.Unmarshal(fieldData, &spkh)
	if err != nil {
		return nil, data, asn1.SyntaxError{
			Msg: "error unmarshaling TorServiceDescriptorHash Hash",
		}
	}

	// There should be no trailing data after the TorServiceDescriptorHash
	// SEQUENCE.
	if len(fieldData) > 0 {
		return nil, data, asn1.SyntaxError{
			Msg: "trailing data after TorServiceDescriptorHash",
		}
	}

	return &TorServiceDescriptorHash{
		Onion:         string(rawOnionURI.Bytes),
		Algorithm:     algorithm,
		AlgorithmName: algorithmName,
		HashBits:      spkh.BitLength,
		Hash:          CertificateFingerprint(spkh.Bytes),
	}, data, nil
}