lints/lint_ext_tor_service_descriptor_hash_invalid.go
func (l *torServiceDescHashInvalid) Execute(c *x509.Certificate) *LintResult {
	// If the BRTorServiceDescriptor extension is missing return a lint error. We
	// know the cert contains one or more `.onion` subjects because of
	// `CheckApplies` and all such certs are expected to have this extension after
	// util.CABV201Date.
	if ext := util.GetExtFromCert(c, util.BRTorServiceDescriptor); ext == nil {
		return failResult(
			"certificate contained a %s domain but is missing a TorServiceDescriptor "+
				"extension (oid %s)",
			onionTLD, util.BRTorServiceDescriptor.String())
	}

	// The certificate should have at least one TorServiceDescriptorHash in the
	// TorServiceDescriptor extension.
	descriptors := c.TorServiceDescriptors
	if len(descriptors) == 0 {
		return failResult(
			"certificate contained a %s domain but TorServiceDescriptor "+
				"extension (oid %s) had no TorServiceDescriptorHash objects",
			onionTLD, util.BRTorServiceDescriptor.String())
	}

	// Build a map of all the eTLD+1 onion subjects in the cert to compare against
	// the service descriptors.
	onionETLDPlusOneMap := make(map[string]string)
	for _, subj := range append(c.DNSNames, c.Subject.CommonName) {
		if !strings.HasSuffix(subj, onionTLD) {
			continue
		}
		labels := strings.Split(subj, ".")
		if len(labels) < 2 {
			return failResult("certificate contained a %s domain with too few "+
				"labels: %q",
				onionTLD, subj)
		}
		eTLDPlusOne := strings.Join(labels[len(labels)-2:], ".")
		onionETLDPlusOneMap[eTLDPlusOne] = subj
	}

	expectedHashBits := map[string]int{
		"SHA256": 256,
		"SHA384": 384,
		"SHA512": 512,
	}

	// Build a map of onion hostname -> TorServiceDescriptorHash using the parsed
	// TorServiceDescriptors from zcrypto.
	descriptorMap := make(map[string]*x509.TorServiceDescriptorHash)
	for _, descriptor := range descriptors {
		// each descriptor's Onion URL must be valid
		if errResult := lintOnionURL(descriptor.Onion); errResult != nil {
			return errResult
		}
		// each descriptor should have a known hash algorithm and the correct
		// corresponding size of hash.
		if expectedBits, found := expectedHashBits[descriptor.AlgorithmName]; !found {
			return failResult(
				"%s contained a TorServiceDescriptorHash for Onion URI %q with an "+
					"unknown hash algorithm",
				torServiceDescExtName, descriptor.Onion)
		} else if expectedBits != descriptor.HashBits {
			return failResult(
				"%s contained a TorServiceDescriptorHash with hash algorithm %q but "+
					"only %d bits of hash not %d",
				torServiceDescExtName, descriptor.AlgorithmName,
				descriptor.HashBits, expectedBits)
		}
		// NOTE(@cpu): Throwing out the err result here because lintOnionURL already
		//             ensured the URL is valid.
		url, _ := url.Parse(descriptor.Onion)
		hostname := url.Hostname()
		// there should only be one TorServiceDescriptorHash for each Onion hostname.
		if _, exists := descriptorMap[hostname]; exists {
			return failResult(
				"%s contained more than one TorServiceDescriptorHash for base "+
					"Onion URI %q",
				torServiceDescExtName, descriptor.Onion)
		}
		// there shouldn't be a TorServiceDescriptorHash for a Onion hostname that
		// isn't an eTLD+1 in the certificate's subjects.
		if _, found := onionETLDPlusOneMap[hostname]; !found {
			return failResult(
				"%s contained a TorServiceDescriptorHash with a hostname (%q) not "+
					"present as a subject in the certificate",
				torServiceDescExtName, hostname)
		}
		descriptorMap[hostname] = descriptor
	}

	// Check if any of the onion subjects in the certificate don't have
	// a TorServiceDescriptorHash for the eTLD+1 in the descriptorMap.
	for eTLDPlusOne, subjDomain := range onionETLDPlusOneMap {
		if _, found := descriptorMap[eTLDPlusOne]; !found {
			return failResult(
				"%s subject domain name %q does not have a corresponding "+
					"TorServiceDescriptorHash for its eTLD+1",
				onionTLD, subjDomain)
		}
	}

	// Everything checks out!
	return &LintResult{
		Status: Pass,
	}
}
func (l *torServiceDescHashInvalid) Execute(c *x509.Certificate) *LintResult {
	// If the BRTorServiceDescriptor extension is missing return a lint error. We
	// know the cert contains one or more `.onion` subjects because of
	// `CheckApplies` and all such certs are expected to have this extension after
	// util.CABV201Date.
	if ext := util.GetExtFromCert(c, util.BRTorServiceDescriptor); ext == nil {
		return failResult(
			"certificate contained a %s domain but is missing a TorServiceDescriptor "+
				"extension (oid %s)",
			onionTLD, util.BRTorServiceDescriptor.String())
	}

	// The certificate should have at least one TorServiceDescriptorHash in the
	// TorServiceDescriptor extension.
	descriptors := c.TorServiceDescriptors
	if len(descriptors) == 0 {
		return failResult(
			"certificate contained a %s domain but TorServiceDescriptor "+
				"extension (oid %s) had no TorServiceDescriptorHash objects",
			onionTLD, util.BRTorServiceDescriptor.String())
	}

	// Build a map of all the eTLD+1 onion subjects in the cert to compare against
	// the service descriptors.
	onionETLDPlusOneMap := make(map[string]string)
	for _, subj := range append(c.DNSNames, c.Subject.CommonName) {
		if !strings.HasSuffix(subj, onionTLD) {
			continue
		}
		labels := strings.Split(subj, ".")
		if len(labels) < 2 {
			return failResult("certificate contained a %s domain with too few "+
				"labels: %q",
				onionTLD, subj)
		}
		eTLDPlusOne := strings.Join(labels[len(labels)-2:], ".")
		onionETLDPlusOneMap[eTLDPlusOne] = subj
	}

	expectedHashBits := map[string]int{
		"SHA256": 256,
		"SHA384": 384,
		"SHA512": 512,
	}

	// Build a map of onion hostname -> TorServiceDescriptorHash using the parsed
	// TorServiceDescriptors from zcrypto.
	descriptorMap := make(map[string]*x509.TorServiceDescriptorHash)
	for _, descriptor := range descriptors {
		// each descriptor's Onion URL must be valid
		if errResult := lintOnionURL(descriptor.Onion); errResult != nil {
			return errResult
		}
		// each descriptor should have a known hash algorithm and the correct
		// corresponding size of hash.
		if expectedBits, found := expectedHashBits[descriptor.AlgorithmName]; !found {
			return failResult(
				"%s contained a TorServiceDescriptorHash for Onion URI %q with an "+
					"unknown hash algorithm",
				torServiceDescExtName, descriptor.Onion)
		} else if expectedBits != descriptor.HashBits {
			return failResult(
				"%s contained a TorServiceDescriptorHash with hash algorithm %q but "+
					"only %d bits of hash not %d",
				torServiceDescExtName, descriptor.AlgorithmName,
				descriptor.HashBits, expectedBits)
		}
		// NOTE(@cpu): Throwing out the err result here because lintOnionURL already
		//             ensured the URL is valid.
		url, _ := url.Parse(descriptor.Onion)
		hostname := url.Hostname()
		// there should only be one TorServiceDescriptorHash for each Onion hostname.
		if _, exists := descriptorMap[hostname]; exists {
			return failResult(
				"%s contained more than one TorServiceDescriptorHash for base "+
					"Onion URI %q",
				torServiceDescExtName, descriptor.Onion)
		}
		// there shouldn't be a TorServiceDescriptorHash for a Onion hostname that
		// isn't an eTLD+1 in the certificate's subjects.
		if _, found := onionETLDPlusOneMap[hostname]; !found {
			return failResult(
				"%s contained a TorServiceDescriptorHash with a hostname (%q) not "+
					"present as a subject in the certificate",
				torServiceDescExtName, hostname)
		}
		descriptorMap[hostname] = descriptor
	}

	// Check if any of the onion subjects in the certificate don't have
	// a TorServiceDescriptorHash for the eTLD+1 in the descriptorMap.
	for eTLDPlusOne, subjDomain := range onionETLDPlusOneMap {
		if _, found := descriptorMap[eTLDPlusOne]; !found {
			return failResult(
				"%s subject domain name %q does not have a corresponding "+
					"TorServiceDescriptorHash for its eTLD+1",
				onionTLD, subjDomain)
		}
	}

	// Everything checks out!
	return &LintResult{
		Status: Pass,
	}
}
// Execute will lint the provided certificate. An Error LintResult will be
// returned if:
//
//   1) There is no TorServiceDescriptor extension present.
//   2) There were no TorServiceDescriptors parsed by zcrypto
//   3) There are TorServiceDescriptorHash entries with an invalid Onion URL.
//   4) There are TorServiceDescriptorHash entries with an unknown hash
//      algorithm or incorrect hash bit length.
//   5) There is a TorServiceDescriptorHash entry that doesn't correspond to
//      an onion subject in the cert.
//   6) There is an onion subject in the cert that doesn't correspond to
//      a TorServiceDescriptorHash.