From 494a0365edee045dc740025aba8ba1a6d6b67c7e Mon Sep 17 00:00:00 2001 From: Adriano Santoni Date: Sat, 1 Aug 2026 10:20:57 +0200 Subject: [PATCH 1/7] Add files via upload --- .../lint_utf8_replac_char_in_subj.go | 113 ++++++++++++++++++ .../lint_utf8_replac_char_in_subj_test.go | 61 ++++++++++ 2 files changed, 174 insertions(+) create mode 100644 v3/lints/community/lint_utf8_replac_char_in_subj.go create mode 100644 v3/lints/community/lint_utf8_replac_char_in_subj_test.go diff --git a/v3/lints/community/lint_utf8_replac_char_in_subj.go b/v3/lints/community/lint_utf8_replac_char_in_subj.go new file mode 100644 index 000000000..258c67884 --- /dev/null +++ b/v3/lints/community/lint_utf8_replac_char_in_subj.go @@ -0,0 +1,113 @@ +/* + * ZLint Copyright 2024 Regents of the University of Michigan + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package community + +import ( + "github.com/zmap/zcrypto/x509" + "github.com/zmap/zlint/v3/lint" + "github.com/zmap/zlint/v3/util" + + "encoding/asn1" + "fmt" + "strings" + "unicode/utf8" +) + +func init() { + lint.RegisterCertificateLint(&lint.CertificateLint{ + LintMetadata: lint.LintMetadata{ + Name: "e_utf8_replacement_char_in_subject", + Description: "Detects the UTF8 Replacement Character anywhere in the Subject field", + Citation: "Do not know what to insert here", + Source: lint.Community, + EffectiveDate: util.ZeroDate, + }, + Lint: NewUTF8ReplacementCharInSubject, + }) +} + +type attributeTypeAndValue struct { + Type asn1.ObjectIdentifier + Value asn1.RawValue +} + +type UTF8ReplacementCharInSubject struct{} + +func NewUTF8ReplacementCharInSubject() lint.LintInterface { + return &UTF8ReplacementCharInSubject{} +} + +func (l *UTF8ReplacementCharInSubject) CheckApplies(c *x509.Certificate) bool { + return true +} + +func getAttribName(oidStr string) string { + attribNames := map[string]string{ + "0.9.2342.19200300.100.1.25": "subject:domainComponent", + "1.2.840.113549.1.9.1": "subject:emailAddress", + "1.3.6.1.4.1.311.60.2.1.1": "subject:jurisdictionLocality", + "1.3.6.1.4.1.311.60.2.1.2": "subject:jurisdictionProvince", + "1.3.6.1.4.1.311.60.2.1.3": "subject:jurisdictionCountry", + "2.5.4.3": "subject:commonName", + "2.5.4.4": "subject:surname", + "2.5.4.5": "subject:serialNumber", + "2.5.4.6": "subject:countryName", + "2.5.4.7": "subject:localityName", + "2.5.4.8": "subject:stateOrProvinceName", + "2.5.4.9": "subject:streetAddress", + "2.5.4.10": "subject:organizationName", + "2.5.4.11": "subject:organizationalUnitName", + "2.5.4.12": "subject:title", + "2.5.4.17": "subject:postalCode", + "2.5.4.42": "subject:givenName", + "2.5.4.65": "subject:pseudonym", + "2.5.4.97": "subject:organizationIdentifier", + } + + name, found := attribNames[oidStr] + if found { + return name + } + return fmt.Sprintf("Subject attribute with OID %s", oidStr) +} + +func (l *UTF8ReplacementCharInSubject) Execute(c *x509.Certificate) *lint.LintResult { + + var rdnSeq []asn1.RawValue // RDNSequence ::= SEQUENCE OF RDN + if _, err := asn1.Unmarshal(c.RawSubject, &rdnSeq); err != nil { + panic(err) + } + + for _, rdn := range rdnSeq { + var atvs []attributeTypeAndValue // RDN ::= SET OF AttributeTypeAndValue + if _, err := asn1.UnmarshalWithParams(rdn.FullBytes, &atvs, "set"); err != nil { + panic(err) + } + for _, atv := range atvs { + if atv.Value.Tag == asn1.TagUTF8String { // tag 12 (0x0C) + str := string(atv.Value.Bytes) + if strings.ContainsRune(str, utf8.RuneError) { + return &lint.LintResult{ + Status: lint.Error, + Details: fmt.Sprintf("UTF8 Replacement Character detected in %s", + getAttribName(atv.Type.String())), + } + } + } + } + } + + return &lint.LintResult{Status: lint.Pass} +} diff --git a/v3/lints/community/lint_utf8_replac_char_in_subj_test.go b/v3/lints/community/lint_utf8_replac_char_in_subj_test.go new file mode 100644 index 000000000..9f3f3747d --- /dev/null +++ b/v3/lints/community/lint_utf8_replac_char_in_subj_test.go @@ -0,0 +1,61 @@ +/* + * ZLint Copyright 2024 Regents of the University of Michigan + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package community + +import ( + "testing" + + "github.com/zmap/zlint/v3/lint" + "github.com/zmap/zlint/v3/test" +) + +func TestUTF8ReplacementCharInSubject(t *testing.T) { + + testCases := []struct { + desc string + path string + want lint.LintStatus + }{ + { + desc: "Clean certificate", + path: "utf8_replac_char_in_subj_clean.pem", + want: lint.Pass, + }, + { + desc: "Certificate with dirty char in subject:stateOrProvinceName", + path: "utf8_replac_char_in_subj_dirty1.pem", + want: lint.Error, + }, + { + desc: "Certificate with dirty char in subject:localityName", + path: "utf8_replac_char_in_subj_dirty2.pem", + want: lint.Error, + }, + { + desc: "Certificate with dirty char in subject:givenName", + path: "utf8_replac_char_in_subj_dirty3.pem", + want: lint.Error, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + out := test.TestLint("e_utf8_replacement_char_in_subject", tc.path) + if out.Status != tc.want { + t.Errorf("expected status %s for %s, got %s", tc.want, tc.path, out.Status) + } + }) + } +} From 4a52997700653ef44b8883fe92e908816274b57c Mon Sep 17 00:00:00 2001 From: Adriano Santoni Date: Sat, 1 Aug 2026 10:21:54 +0200 Subject: [PATCH 2/7] Add files via upload --- .../utf8_replac_char_in_subj_clean.pem | 94 ++++++++++++++ .../utf8_replac_char_in_subj_dirty1.pem | 94 ++++++++++++++ .../utf8_replac_char_in_subj_dirty2.pem | 95 ++++++++++++++ .../utf8_replac_char_in_subj_dirty3.pem | 117 ++++++++++++++++++ 4 files changed, 400 insertions(+) create mode 100644 v3/testdata/utf8_replac_char_in_subj_clean.pem create mode 100644 v3/testdata/utf8_replac_char_in_subj_dirty1.pem create mode 100644 v3/testdata/utf8_replac_char_in_subj_dirty2.pem create mode 100644 v3/testdata/utf8_replac_char_in_subj_dirty3.pem diff --git a/v3/testdata/utf8_replac_char_in_subj_clean.pem b/v3/testdata/utf8_replac_char_in_subj_clean.pem new file mode 100644 index 000000000..b924b6dc0 --- /dev/null +++ b/v3/testdata/utf8_replac_char_in_subj_clean.pem @@ -0,0 +1,94 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 4f:42:cc:64:62:33:fb:d4:4d:cc:9b:6d:44:b1:68:ea + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=XX, O=Some CA, CN=Fake CA for zlint testing + Validity + Not Before: Jul 31 09:47:59 2026 GMT + Not After : Nov 8 08:47:59 2026 GMT + Subject: C=SE, ST=Götaland, L=Göteborg, O=Some Company AB + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:cb:91:76:77:17:44:69:14:3a:f7:c9:f8:b7:07: + a2:ff:ac:96:51:34:25:22:fb:06:52:fd:6c:5f:c7: + ef:9d:48:70:ff:6b:e5:aa:93:da:c2:bc:ac:85:7f: + ac:b4:a8:e8:05:e0:49:a0:8e:8d:e2:0a:a5:0c:2c: + 4f:a8:b6:a5:25:3a:fd:d9:1a:79:61:1d:f5:f4:fe: + cb:f9:57:8c:56:75:1a:67:c7:8f:ea:9b:21:cb:63: + 29:66:c2:84:ab:0b:2e:8b:c5:39:30:66:ad:56:0a: + bb:ea:9e:07:28:02:5f:f7:c7:2c:ef:f7:27:ce:1e: + 19:f2:0d:20:d7:4c:32:40:5f:99:e5:8b:5c:ba:4a: + 59:92:6b:b6:aa:80:a7:1d:e1:a3:d6:1a:1c:b0:e2: + 82:3d:fd:53:ab:f0:80:fb:d0:ff:3b:eb:a3:ae:de: + 4a:28:6e:22:1d:42:cb:df:8f:7f:0e:40:ae:fa:89: + ed:0f:34:e1:83:87:9e:ea:38:40:69:12:16:c0:9d: + 89:9a:18:16:33:35:46:5d:06:8e:3a:6d:3d:d9:c2: + 00:63:8d:20:b5:97:59:9b:1a:8a:19:ce:29:6b:a0: + ed:80:d8:d9:6a:8b:73:55:43:c3:f5:39:f2:36:fd: + 12:83:2d:dc:c3:8b:29:6f:67:2e:c5:f0:94:fa:ad: + 3e:4d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Client Authentication, TLS Web Server Authentication + X509v3 Authority Key Identifier: + E8:B6:F6:76:4B:D0:3B:E5:46:A5:F9:54:D4:7E:07:B3:DE:0D:60:3E + Authority Information Access: + OCSP - URI:http://ca.someca-inc.com/ocsp + CA Issuers - URI:http://ca.someca-inc.local/root + X509v3 Subject Alternative Name: + DNS:example.org + X509v3 Certificate Policies: + Policy: 2.23.140.1.2.2 + X509v3 CRL Distribution Points: + Full Name: + URI:http://ca.someca-inc.com/crl + + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 69:b4:1c:10:2f:dd:4f:e2:e0:8f:66:d6:39:77:bc:ed:cd:0d: + 62:81:62:c2:c2:24:fa:b5:43:71:dc:c5:87:cc:35:61:0a:c4: + 84:9c:f9:51:6e:33:a5:a7:1b:04:46:64:db:89:fb:d3:3d:b2: + eb:95:ee:82:e5:1c:b3:7c:bc:e2:6f:fb:bf:4b:64:d3:de:46: + ca:e5:a5:67:94:31:a2:50:5c:1f:c5:06:d7:b3:62:31:5c:2f: + 2b:98:8d:86:3c:61:b6:0b:a1:bf:8c:9b:ba:f2:b0:4d:51:98: + ae:6f:3b:ea:d7:df:05:ae:cd:bf:c5:e9:67:4c:d6:bd:04:41: + 9a:75:9b:8a:58:d0:cc:ac:25:fd:6c:f7:b0:2d:ca:00:05:e0: + 66:79:b7:35:33:48:9f:76:4e:51:e6:74:e3:ca:34:83:6d:f9: + 88:a7:1f:63:30:40:d6:77:ae:83:5e:df:44:aa:63:cc:36:84: + 08:dd:07:4b:d0:3a:f2:61:22:bd:db:96:20:17:c7:5a:a2:75: + 1e:de:e0:e0:cb:22:92:ab:2f:c1:ce:d4:8e:4b:48:e9:5d:57: + 62:5b:9a:3a:27:4d:dc:d6:ba:e4:e9:b4:2f:6f:02:61:83:84: + 0d:91:2f:d2:5c:02:1d:2e:b7:ec:22:e5:7d:25:bc:0e:cd:7d: + a4:28:fd:0c +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIQT0LMZGIz+9RNzJttRLFo6jANBgkqhkiG9w0BAQsFADBD +MQswCQYDVQQGEwJYWDEQMA4GA1UEChMHU29tZSBDQTEiMCAGA1UEAxMZRmFrZSBD +QSBmb3IgemxpbnQgdGVzdGluZzAeFw0yNjA3MzEwOTQ3NTlaFw0yNjExMDgwODQ3 +NTlaME8xCzAJBgNVBAYTAlNFMRIwEAYDVQQIDAlHw7Z0YWxhbmQxEjAQBgNVBAcM +CUfDtnRlYm9yZzEYMBYGA1UEChMPU29tZSBDb21wYW55IEFCMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy5F2dxdEaRQ698n4twei/6yWUTQlIvsGUv1s +X8fvnUhw/2vlqpPawryshX+stKjoBeBJoI6N4gqlDCxPqLalJTr92Rp5YR319P7L ++VeMVnUaZ8eP6pshy2MpZsKEqwsui8U5MGatVgq76p4HKAJf98cs7/cnzh4Z8g0g +10wyQF+Z5YtcukpZkmu2qoCnHeGj1hocsOKCPf1Tq/CA+9D/O+ujrt5KKG4iHULL +349/DkCu+ontDzThg4ee6jhAaRIWwJ2JmhgWMzVGXQaOOm092cIAY40gtZdZmxqK +Gc4pa6DtgNjZaotzVUPD9TnyNv0Sgy3cw4spb2cuxfCU+q0+TQIDAQABo4IBGDCC +ARQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD +ATAfBgNVHSMEGDAWgBTotvZ2S9A75Ual+VTUfgez3g1gPjBmBggrBgEFBQcBAQRa +MFgwKQYIKwYBBQUHMAGGHWh0dHA6Ly9jYS5zb21lY2EtaW5jLmNvbS9vY3NwMCsG +CCsGAQUFBzAChh9odHRwOi8vY2Euc29tZWNhLWluYy5sb2NhbC9yb290MBYGA1Ud +EQQPMA2CC2V4YW1wbGUub3JnMBMGA1UdIAQMMAowCAYGZ4EMAQICMC0GA1UdHwQm +MCQwIqAgoB6GHGh0dHA6Ly9jYS5zb21lY2EtaW5jLmNvbS9jcmwwDQYJKoZIhvcN +AQELBQADggEBAGm0HBAv3U/i4I9m1jl3vO3NDWKBYsLCJPq1Q3HcxYfMNWEKxISc ++VFuM6WnGwRGZNuJ+9M9suuV7oLlHLN8vOJv+79LZNPeRsrlpWeUMaJQXB/FBtez +YjFcLyuYjYY8YbYLob+Mm7rysE1RmK5vO+rX3wWuzb/F6WdM1r0EQZp1m4pY0Mys +Jf1s97AtygAF4GZ5tzUzSJ92TlHmdOPKNINt+YinH2MwQNZ3roNe30SqY8w2hAjd +B0vQOvJhIr3bliAXx1qidR7e4ODLIpKrL8HO1I5LSOldV2JbmjonTdzWuuTptC9v +AmGDhA2RL9JcAh0ut+wi5X0lvA7NfaQo/Qw= +-----END CERTIFICATE----- diff --git a/v3/testdata/utf8_replac_char_in_subj_dirty1.pem b/v3/testdata/utf8_replac_char_in_subj_dirty1.pem new file mode 100644 index 000000000..c4574389f --- /dev/null +++ b/v3/testdata/utf8_replac_char_in_subj_dirty1.pem @@ -0,0 +1,94 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + f4:d5:92:96:fe:6b:d8:20:69:0f:bc:ab:4d:cb:2a:71 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=XX, O=Some CA, CN=Fake CA for zlint testing + Validity + Not Before: Jul 31 09:44:38 2026 GMT + Not After : Nov 8 08:44:38 2026 GMT + Subject: C=DE, ST=Th�ringen, L=Erfurt, O=Ein Unternehmen GmbH + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:cf:a7:a3:be:44:bc:1e:66:66:cc:70:c7:20:17: + df:5a:f4:f5:e2:51:df:46:85:8f:a2:4a:49:00:1e: + 6a:93:66:bc:68:3d:d9:6f:b3:13:a3:13:0d:c7:8d: + 94:ac:33:66:ef:39:0d:d8:a6:ac:aa:13:f2:b4:87: + 89:2b:3a:6d:88:4c:1b:ab:07:ba:02:54:3a:59:b2: + 1b:29:79:b7:33:99:ec:9a:3b:05:e1:43:32:ff:04: + 27:49:42:5d:06:89:f4:08:e3:c6:69:2e:fa:67:79: + 70:6e:1e:75:02:a1:6c:54:e1:19:2f:8e:85:9f:9f: + 28:31:26:57:c1:4f:06:c5:b8:4b:1a:ad:35:e5:09: + a5:cb:91:89:75:0a:4d:e6:ac:e4:89:82:33:b9:51: + 0e:ce:ca:18:21:48:ae:5a:c9:15:72:7a:7d:52:87: + 26:7f:0a:ba:b2:e2:d6:85:de:00:9d:2f:4e:61:11: + 00:2e:37:42:ee:e9:10:f6:4e:33:76:a6:3a:43:af: + da:0f:72:18:ed:a6:4c:57:6e:23:21:0e:e2:2e:4a: + 9e:28:af:1a:68:24:5c:18:8a:a6:3d:66:8d:cf:39: + 62:9e:50:4e:53:ef:a5:6d:d3:20:b0:06:38:5a:d0: + 1c:d7:f8:1d:e1:30:6b:f1:2e:c8:b0:c5:fa:eb:32: + 12:93 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Client Authentication, TLS Web Server Authentication + X509v3 Authority Key Identifier: + E8:B6:F6:76:4B:D0:3B:E5:46:A5:F9:54:D4:7E:07:B3:DE:0D:60:3E + Authority Information Access: + OCSP - URI:http://ca.someca-inc.com/ocsp + CA Issuers - URI:http://ca.someca-inc.local/root + X509v3 Subject Alternative Name: + DNS:example.org + X509v3 Certificate Policies: + Policy: 2.23.140.1.2.2 + X509v3 CRL Distribution Points: + Full Name: + URI:http://ca.someca-inc.com/crl + + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 60:47:72:cf:d5:f9:49:0b:ce:a2:87:3a:7c:50:14:6e:d2:63: + 96:a1:d7:a2:1c:af:9b:56:c9:a1:b1:97:de:4c:d0:33:c4:fb: + 6d:a0:74:5d:4d:bb:60:41:f4:9b:67:f7:14:90:3e:f2:43:03: + a6:d1:20:1e:37:56:3c:ce:c9:2a:a0:7b:0c:9c:16:c9:ac:d8: + f4:1e:a6:3e:11:70:33:18:3c:63:f5:0f:e8:9d:02:52:c1:76: + 79:e6:79:99:2b:fc:3f:48:63:e1:8c:cb:c5:32:6f:fb:ba:58: + 93:ba:06:87:4b:d0:c1:53:f5:1f:a1:2f:3d:b1:63:33:2c:29: + c6:90:ea:d0:8e:21:2d:7b:75:38:d4:ef:90:83:ae:6f:72:02: + 94:30:00:99:f3:81:5f:8a:12:2f:0e:72:85:f3:dc:20:7a:ac: + df:0e:3e:62:ee:4c:9d:12:85:f9:5e:38:bd:50:e8:b6:54:d3: + 7d:97:38:82:e5:13:e9:51:64:67:39:47:19:e6:b6:49:49:e2: + c5:47:62:03:9d:18:38:8b:57:51:09:64:46:e8:fc:34:34:7e: + 40:05:c5:16:ce:53:98:4d:b4:07:99:bb:11:b1:9e:2a:fd:b2: + 1e:07:3c:49:ee:49:17:48:e5:6a:9a:f5:c5:75:c2:31:d6:c2: + f2:f1:69:7f +-----BEGIN CERTIFICATE----- +MIIEOzCCAyOgAwIBAgIRAPTVkpb+a9ggaQ+8q03LKnEwDQYJKoZIhvcNAQELBQAw +QzELMAkGA1UEBhMCWFgxEDAOBgNVBAoTB1NvbWUgQ0ExIjAgBgNVBAMTGUZha2Ug +Q0EgZm9yIHpsaW50IHRlc3RpbmcwHhcNMjYwNzMxMDk0NDM4WhcNMjYxMTA4MDg0 +NDM4WjBTMQswCQYDVQQGEwJERTEUMBIGA1UECAwLVGjvv71yaW5nZW4xDzANBgNV +BAcTBkVyZnVydDEdMBsGA1UEChMURWluIFVudGVybmVobWVuIEdtYkgwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPp6O+RLweZmbMcMcgF99a9PXiUd9G +hY+iSkkAHmqTZrxoPdlvsxOjEw3HjZSsM2bvOQ3YpqyqE/K0h4krOm2ITBurB7oC +VDpZshspebczmeyaOwXhQzL/BCdJQl0GifQI48ZpLvpneXBuHnUCoWxU4RkvjoWf +nygxJlfBTwbFuEsarTXlCaXLkYl1Ck3mrOSJgjO5UQ7OyhghSK5ayRVyen1ShyZ/ +Crqy4taF3gCdL05hEQAuN0Lu6RD2TjN2pjpDr9oPchjtpkxXbiMhDuIuSp4orxpo +JFwYiqY9Zo3POWKeUE5T76Vt0yCwBjha0BzX+B3hMGvxLsiwxfrrMhKTAgMBAAGj +ggEYMIIBFDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsG +AQUFBwMBMB8GA1UdIwQYMBaAFOi29nZL0DvlRqX5VNR+B7PeDWA+MGYGCCsGAQUF +BwEBBFowWDApBggrBgEFBQcwAYYdaHR0cDovL2NhLnNvbWVjYS1pbmMuY29tL29j +c3AwKwYIKwYBBQUHMAKGH2h0dHA6Ly9jYS5zb21lY2EtaW5jLmxvY2FsL3Jvb3Qw +FgYDVR0RBA8wDYILZXhhbXBsZS5vcmcwEwYDVR0gBAwwCjAIBgZngQwBAgIwLQYD +VR0fBCYwJDAioCCgHoYcaHR0cDovL2NhLnNvbWVjYS1pbmMuY29tL2NybDANBgkq +hkiG9w0BAQsFAAOCAQEAYEdyz9X5SQvOooc6fFAUbtJjlqHXohyvm1bJobGX3kzQ +M8T7baB0XU27YEH0m2f3FJA+8kMDptEgHjdWPM7JKqB7DJwWyazY9B6mPhFwMxg8 +Y/UP6J0CUsF2eeZ5mSv8P0hj4YzLxTJv+7pYk7oGh0vQwVP1H6EvPbFjMywpxpDq +0I4hLXt1ONTvkIOub3IClDAAmfOBX4oSLw5yhfPcIHqs3w4+Yu5MnRKF+V44vVDo +tlTTfZc4guUT6VFkZzlHGea2SUnixUdiA50YOItXUQlkRuj8NDR+QAXFFs5TmE20 +B5m7EbGeKv2yHgc8Se5JF0jlapr1xXXCMdbC8vFpfw== +-----END CERTIFICATE----- diff --git a/v3/testdata/utf8_replac_char_in_subj_dirty2.pem b/v3/testdata/utf8_replac_char_in_subj_dirty2.pem new file mode 100644 index 000000000..85973e3d4 --- /dev/null +++ b/v3/testdata/utf8_replac_char_in_subj_dirty2.pem @@ -0,0 +1,95 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 62:af:ed:21:5e:18:09:5c:31:77:3a:8a:b4:e4:50:95 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=XX, O=Some CA, CN=Fake CA for zlint testing + Validity + Not Before: Jul 31 09:46:26 2026 GMT + Not After : Nov 8 08:46:26 2026 GMT + Subject: C=SE, ST=Götaland, L=G�teborg, O=Något företag AB, businessCategory=Non-Commercial Entity, jurisdictionC=SE + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b4:44:a9:8c:55:79:13:d4:71:28:82:ce:5b:79: + 2f:cb:27:05:85:8a:6f:b8:1e:56:e8:2a:fe:87:48: + 92:11:33:d3:6d:e9:17:3d:55:81:4d:f7:11:d5:9a: + 32:aa:30:a2:16:56:b3:94:70:44:2b:d0:e7:10:cc: + f4:cc:8e:ed:ec:5e:b9:8a:23:57:82:a9:47:db:24: + 0b:59:a2:c0:b6:d3:1a:95:72:1b:df:40:84:94:55: + 84:d6:f1:2d:38:a1:d0:10:be:8d:05:82:b2:f7:c8: + 03:ed:e7:97:71:c6:ac:03:82:2c:68:79:ec:5f:9e: + 0d:7f:be:bb:24:64:06:0c:a4:56:a3:5b:f9:41:d4: + 8e:5b:1f:69:bb:67:47:13:88:27:1b:da:a9:2a:8b: + 30:2f:ff:dc:96:2d:ac:42:70:65:a7:a2:01:4d:5d: + 54:fd:be:cb:17:39:e5:5d:c6:d3:ad:3e:cb:79:fd: + 3c:d5:3c:25:4d:fa:40:47:11:bf:06:ed:e7:3b:82: + 4d:d7:c0:94:e3:6d:5d:3a:55:a9:2f:7f:45:de:63: + 3b:c8:d9:c9:fa:e5:28:ee:32:be:21:fa:74:e6:2f: + 78:8e:96:3f:b1:bf:43:72:d6:20:a0:00:11:dd:c9: + 0b:da:51:3a:08:29:f4:76:f1:15:dd:6a:a4:9d:21: + 39:65 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Client Authentication, TLS Web Server Authentication + X509v3 Authority Key Identifier: + E8:B6:F6:76:4B:D0:3B:E5:46:A5:F9:54:D4:7E:07:B3:DE:0D:60:3E + Authority Information Access: + OCSP - URI:http://ca.someca-inc.com/ocsp + CA Issuers - URI:http://ca.someca-inc.local/root + X509v3 Subject Alternative Name: + DNS:example.org + X509v3 Certificate Policies: + Policy: 2.23.140.1.1 + X509v3 CRL Distribution Points: + Full Name: + URI:http://ca.someca-inc.com/crl + + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 66:72:c4:a2:6f:06:16:f2:28:4c:ff:2c:1b:85:27:1c:95:7f: + da:7c:f9:fb:ad:20:10:7d:b1:51:99:d7:01:85:b6:bd:99:63: + 23:52:f8:8c:f0:a2:a1:2b:42:18:b8:4e:91:3c:ad:e9:2a:d7: + 1f:b9:24:ac:68:41:4f:4c:7e:c2:24:fa:65:ad:0d:dd:cd:38: + 1b:3f:07:68:a6:45:b9:1c:6f:fb:af:32:44:c7:e4:2c:31:0e: + 34:58:e1:05:bc:67:c4:5f:e1:6a:d2:7f:c1:20:62:fa:49:97: + 1b:51:34:4a:5e:e2:d6:4b:50:17:c9:6a:35:86:15:8d:1b:72: + f7:5a:64:ec:90:ac:e6:ae:1d:12:eb:cb:73:eb:ff:9c:44:49: + 8c:17:ee:11:d8:8c:08:30:8d:05:f0:a2:37:26:67:ce:f0:02: + 31:cc:e6:c1:62:fe:f4:4c:d1:d0:a1:3c:20:71:03:65:1f:5e: + 78:33:ed:ed:24:59:ee:43:dc:f9:9f:7a:9f:88:e4:3b:2d:14: + f6:86:54:ff:08:a1:32:4b:51:7c:5e:c3:51:12:17:3f:5e:ea: + 51:c0:23:40:b7:68:8a:60:89:3f:4d:c2:f4:3c:1e:52:59:d8: + 26:51:2e:1a:22:79:9e:b4:9f:31:3a:d1:c5:a2:79:93:06:8a: + bc:79:37:7c +-----BEGIN CERTIFICATE----- +MIIEbzCCA1egAwIBAgIQYq/tIV4YCVwxdzqKtORQlTANBgkqhkiG9w0BAQsFADBD +MQswCQYDVQQGEwJYWDEQMA4GA1UEChMHU29tZSBDQTEiMCAGA1UEAxMZRmFrZSBD +QSBmb3IgemxpbnQgdGVzdGluZzAeFw0yNjA3MzEwOTQ2MjZaFw0yNjExMDgwODQ2 +MjZaMIGIMQswCQYDVQQGEwJTRTESMBAGA1UECAwJR8O2dGFsYW5kMRMwEQYDVQQH +DApH77+9dGVib3JnMRswGQYDVQQKDBJOw6Vnb3QgZsO2cmV0YWcgQUIxHjAcBgNV +BA8TFU5vbi1Db21tZXJjaWFsIEVudGl0eTETMBEGCysGAQQBgjc8AgEDEwJTRTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALREqYxVeRPUcSiCzlt5L8sn +BYWKb7geVugq/odIkhEz023pFz1VgU33EdWaMqowohZWs5RwRCvQ5xDM9MyO7exe +uYojV4KpR9skC1miwLbTGpVyG99AhJRVhNbxLTih0BC+jQWCsvfIA+3nl3HGrAOC +LGh57F+eDX++uyRkBgykVqNb+UHUjlsfabtnRxOIJxvaqSqLMC//3JYtrEJwZaei +AU1dVP2+yxc55V3G060+y3n9PNU8JU36QEcRvwbt5zuCTdfAlONtXTpVqS9/Rd5j +O8jZyfrlKO4yviH6dOYveI6WP7G/Q3LWIKAAEd3JC9pROggp9HbxFd1qpJ0hOWUC +AwEAAaOCARcwggETMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD +AgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAU6Lb2dkvQO+VGpflU1H4Hs94NYD4wZgYI +KwYBBQUHAQEEWjBYMCkGCCsGAQUFBzABhh1odHRwOi8vY2Euc29tZWNhLWluYy5j +b20vb2NzcDArBggrBgEFBQcwAoYfaHR0cDovL2NhLnNvbWVjYS1pbmMubG9jYWwv +cm9vdDAWBgNVHREEDzANggtleGFtcGxlLm9yZzASBgNVHSAECzAJMAcGBWeBDAEB +MC0GA1UdHwQmMCQwIqAgoB6GHGh0dHA6Ly9jYS5zb21lY2EtaW5jLmNvbS9jcmww +DQYJKoZIhvcNAQELBQADggEBAGZyxKJvBhbyKEz/LBuFJxyVf9p8+futIBB9sVGZ +1wGFtr2ZYyNS+IzwoqErQhi4TpE8rekq1x+5JKxoQU9MfsIk+mWtDd3NOBs/B2im +Rbkcb/uvMkTH5CwxDjRY4QW8Z8Rf4WrSf8EgYvpJlxtRNEpe4tZLUBfJajWGFY0b +cvdaZOyQrOauHRLry3Pr/5xESYwX7hHYjAgwjQXwojcmZ87wAjHM5sFi/vRM0dCh +PCBxA2UfXngz7e0kWe5D3Pmfep+I5DstFPaGVP8IoTJLUXxew1ESFz9e6lHAI0C3 +aIpgiT9NwvQ8HlJZ2CZRLhoieZ60nzE60cWieZMGirx5N3w= +-----END CERTIFICATE----- diff --git a/v3/testdata/utf8_replac_char_in_subj_dirty3.pem b/v3/testdata/utf8_replac_char_in_subj_dirty3.pem new file mode 100644 index 000000000..17652d735 --- /dev/null +++ b/v3/testdata/utf8_replac_char_in_subj_dirty3.pem @@ -0,0 +1,117 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 56:35:2e:3e:58:07:d6:00:9c:6e:0d:76:dc:05:db:f5 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=XX, O=Some CA, CN=Fake CA for Zlint testing + Validity + Not Before: Jul 31 09:54:30 2026 GMT + Not After : Jul 31 09:54:30 2027 GMT + Subject: C=BR, ST=Bahia, L=Juazeiro, CN=João Gilberto, GN=Jo�o, SN=Gilberto + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b2:0e:f6:4f:de:51:1c:59:23:51:35:17:84:4c: + 33:6d:4f:3a:66:99:96:79:34:fc:69:0e:d8:48:00: + 4c:06:56:8c:f4:8b:71:53:d0:fd:1f:0e:12:2e:d7: + da:97:6e:16:66:fb:ca:6b:f9:dc:d5:f1:a5:c8:f4: + e4:8a:af:cc:c5:3f:93:f3:6e:1c:f7:49:88:c3:ac: + f0:80:b1:01:40:1f:2d:a0:74:10:b3:a7:d5:45:4c: + 20:a2:4c:a5:09:01:16:78:60:f3:9a:52:2d:72:0a: + b2:a1:83:bc:5b:ed:30:15:c5:62:86:2b:eb:ec:9b: + df:d5:b2:ba:ae:d8:f4:9f:f5:3a:1f:38:3c:01:86: + fb:72:58:c1:9b:ab:72:c2:68:2d:58:69:9e:9b:cf: + 67:af:6c:65:28:ab:76:e5:ea:44:80:84:19:3b:41: + 36:6d:40:d9:65:b3:0d:eb:06:71:a8:00:e4:64:24: + 91:12:b8:1c:8d:e5:7b:67:f1:1e:03:95:82:52:9f: + 03:e5:36:51:d2:ee:ab:44:e7:50:bf:f3:e7:9a:41: + 80:3c:29:f6:9a:aa:b2:02:aa:be:cd:dd:6d:6f:7f: + c7:a1:39:ff:fa:d0:9b:2e:91:7f:0a:a3:d3:1a:39: + 8b:bb:77:40:3b:73:62:d6:5d:4c:a7:e9:c4:0b:dc: + 7e:83 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Client Authentication, E-mail Protection + X509v3 Subject Key Identifier: + 96:5C:82:05:35:24:A9:BC:97:34:C0:23:92:C8:89:8A:3D:87:2B:43 + X509v3 Authority Key Identifier: + E8:B6:F6:76:4B:D0:3B:E5:46:A5:F9:54:D4:7E:07:B3:DE:0D:60:3E + Authority Information Access: + OCSP - URI:http://ca.someca-inc.com/ocsp + CA Issuers - URI:http://ca.someca-inc.com/root + X509v3 Subject Alternative Name: + email:joao.gilberto@example.org + X509v3 Certificate Policies: + Policy: 2.23.140.1.5.4.2 + X509v3 CRL Distribution Points: + Full Name: + URI:http://ca.someca-inc.com/crl + + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 14:17:44:3a:9e:06:f6:c7:41:fe:7e:22:76:19:d6:c7:10:70: + 17:18:73:2d:37:90:43:91:9a:41:1b:31:a1:fd:80:ed:d1:2f: + 06:9d:a8:43:61:b8:47:b2:1a:f1:78:d9:f4:4f:d9:70:16:41: + a2:3c:9a:61:48:25:89:35:03:c5:30:7a:9e:22:32:ba:09:8f: + 04:e3:ac:4c:1a:88:81:30:d5:09:7c:f3:be:f2:03:21:7d:ec: + ed:f5:30:a9:d1:22:d7:26:90:80:d1:e9:00:b4:b9:79:f0:40: + 47:9d:9e:b6:de:3d:70:17:05:e1:21:e2:f0:ab:a2:ec:1c:2a: + b6:62:6c:f6:97:be:51:bb:74:cf:62:43:3f:6b:ee:a1:9d:c1: + 05:85:21:b9:f3:53:82:95:99:38:63:72:45:96:14:d9:a9:4d: + c1:28:f8:56:5d:dc:61:47:fd:73:c2:e1:8a:90:0a:c6:11:0d: + 66:4f:8c:3b:a2:28:b3:4f:1c:9e:3d:33:a3:08:4f:a8:22:87: + 6a:62:b9:97:5b:2e:ec:b8:67:c6:2c:38:11:e0:d5:64:ed:91: + c2:e2:40:a7:ef:0b:44:d2:d8:df:2b:ac:0c:8a:9e:34:ad:7e: + 26:08:eb:66:d1:ac:a5:10:bf:28:59:6a:7b:30:35:ab:15:c3: + b5:8f:5b:38:77:d9:dc:a0:fa:ac:00:ed:a3:5e:2c:9a:7e:13: + 09:b7:6c:72:26:51:84:7d:25:cf:d8:6d:bf:f6:13:fc:24:ad: + 40:0f:1f:6e:57:34:4b:70:9a:0d:df:70:d3:38:0b:c2:04:1d: + 40:83:75:7a:ad:95:93:58:d2:19:65:47:92:74:8a:90:f4:e1: + 52:10:f8:cc:64:63:89:54:d1:11:de:b1:c7:b8:c8:a2:65:01: + 3c:96:57:aa:0a:2d:a1:94:eb:41:3b:c4:82:f8:f1:96:8e:de: + 85:b0:2e:a1:c8:c3:d4:a9:e8:03:c3:26:3b:c5:66:72:ef:b8: + d1:7a:25:06:91:e9:f4:e0:23:d4:66:72:7f:f7:af:02:98:4d: + ab:47:bb:94:b8:59:25:6a:e9:3c:8d:8c:cb:46:27:a6:da:12: + fd:6f:15:90:4a:1d:19:95:d6:8d:88:10:0d:85:79:ca:43:5b: + 25:9f:e2:f6:5e:0c:97:fc:2f:8e:15:b0:b7:2c:f5:7c:f6:95: + 82:94:7f:69:46:33:e2:6a:65:72:01:24:0d:29:9d:e3:57:54: + 22:41:6a:19:91:76:7f:86:4e:6e:d8:84:21:ce:d8:2f:f4:9c: + 44:6d:91:fb:4b:b1:9f:ab:24:46:36:07:d4:d0:4f:dc:2d:09: + 7c:b4:16:69:bd:9c:1c:c3 +-----BEGIN CERTIFICATE----- +MIIFgDCCA2igAwIBAgIQVjUuPlgH1gCcbg123AXb9TANBgkqhkiG9w0BAQsFADBD +MQswCQYDVQQGEwJYWDEQMA4GA1UEChMHU29tZSBDQTEiMCAGA1UEAxMZRmFrZSBD +QSBmb3IgWmxpbnQgdGVzdGluZzAeFw0yNjA3MzEwOTU0MzBaFw0yNzA3MzEwOTU0 +MzBaMG0xCzAJBgNVBAYTAkJSMQ4wDAYDVQQIEwVCYWhpYTERMA8GA1UEBxMISnVh +emVpcm8xFzAVBgNVBAMMDkpvw6NvIEdpbGJlcnRvMQ8wDQYDVQQqDAZKb++/vW8x +ETAPBgNVBAQTCEdpbGJlcnRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAsg72T95RHFkjUTUXhEwzbU86ZpmWeTT8aQ7YSABMBlaM9ItxU9D9Hw4SLtfa +l24WZvvKa/nc1fGlyPTkiq/MxT+T824c90mIw6zwgLEBQB8toHQQs6fVRUwgokyl +CQEWeGDzmlItcgqyoYO8W+0wFcVihivr7Jvf1bK6rtj0n/U6Hzg8AYb7cljBm6ty +wmgtWGmem89nr2xlKKt25epEgIQZO0E2bUDZZbMN6wZxqADkZCSRErgcjeV7Z/Ee +A5WCUp8D5TZR0u6rROdQv/PnmkGAPCn2mqqyAqq+zd1tb3/HoTn/+tCbLpF/CqPT +GjmLu3dAO3Ni1l1Mp+nEC9x+gwIDAQABo4IBRDCCAUAwDgYDVR0PAQH/BAQDAgWg +MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAdBgNVHQ4EFgQUllyCBTUk +qbyXNMAjksiJij2HK0MwHwYDVR0jBBgwFoAU6Lb2dkvQO+VGpflU1H4Hs94NYD4w +ZAYIKwYBBQUHAQEEWDBWMCkGCCsGAQUFBzABhh1odHRwOi8vY2Euc29tZWNhLWlu +Yy5jb20vb2NzcDApBggrBgEFBQcwAoYdaHR0cDovL2NhLnNvbWVjYS1pbmMuY29t +L3Jvb3QwJAYDVR0RBB0wG4EZam9hby5naWxiZXJ0b0BleGFtcGxlLm9yZzAUBgNV +HSAEDTALMAkGB2eBDAEFBAIwLQYDVR0fBCYwJDAioCCgHoYcaHR0cDovL2NhLnNv +bWVjYS1pbmMuY29tL2NybDANBgkqhkiG9w0BAQsFAAOCAgEAFBdEOp4G9sdB/n4i +dhnWxxBwFxhzLTeQQ5GaQRsxof2A7dEvBp2oQ2G4R7Ia8XjZ9E/ZcBZBojyaYUgl +iTUDxTB6niIyugmPBOOsTBqIgTDVCXzzvvIDIX3s7fUwqdEi1yaQgNHpALS5efBA +R52ett49cBcF4SHi8Kui7BwqtmJs9pe+Ubt0z2JDP2vuoZ3BBYUhufNTgpWZOGNy +RZYU2alNwSj4Vl3cYUf9c8LhipAKxhENZk+MO6Ios08cnj0zowhPqCKHamK5l1su +7Lhnxiw4EeDVZO2RwuJAp+8LRNLY3yusDIqeNK1+JgjrZtGspRC/KFlqezA1qxXD +tY9bOHfZ3KD6rADto14smn4TCbdsciZRhH0lz9htv/YT/CStQA8fblc0S3CaDd9w +0zgLwgQdQIN1eq2Vk1jSGWVHknSKkPThUhD4zGRjiVTREd6xx7jIomUBPJZXqgot +oZTrQTvEgvjxlo7ehbAuocjD1KnoA8MmO8Vmcu+40XolBpHp9OAj1GZyf/evAphN +q0e7lLhZJWrpPI2My0YnptoS/W8VkEodGZXWjYgQDYV5ykNbJZ/i9l4Ml/wvjhWw +tyz1fPaVgpR/aUYz4mplcgEkDSmd41dUIkFqGZF2f4ZObtiEIc7YL/ScRG2R+0ux +n6skRjYH1NBP3C0JfLQWab2cHMM= +-----END CERTIFICATE----- From 204d5e047986695f09932ca944fafc9a5dacb9b2 Mon Sep 17 00:00:00 2001 From: Adriano Santoni Date: Sat, 1 Aug 2026 10:32:17 +0200 Subject: [PATCH 3/7] Add files via upload --- v3/lints/community/lint_utf8_replac_char_in_subj.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/v3/lints/community/lint_utf8_replac_char_in_subj.go b/v3/lints/community/lint_utf8_replac_char_in_subj.go index 258c67884..a1ec74cb7 100644 --- a/v3/lints/community/lint_utf8_replac_char_in_subj.go +++ b/v3/lints/community/lint_utf8_replac_char_in_subj.go @@ -20,7 +20,6 @@ import ( "github.com/zmap/zlint/v3/util" "encoding/asn1" - "fmt" "strings" "unicode/utf8" ) @@ -80,7 +79,7 @@ func getAttribName(oidStr string) string { if found { return name } - return fmt.Sprintf("Subject attribute with OID %s", oidStr) + return "Subject attribute with OID " + oidStr } func (l *UTF8ReplacementCharInSubject) Execute(c *x509.Certificate) *lint.LintResult { @@ -101,8 +100,8 @@ func (l *UTF8ReplacementCharInSubject) Execute(c *x509.Certificate) *lint.LintRe if strings.ContainsRune(str, utf8.RuneError) { return &lint.LintResult{ Status: lint.Error, - Details: fmt.Sprintf("UTF8 Replacement Character detected in %s", - getAttribName(atv.Type.String())), + Details: "UTF8 Replacement Character detected in " + + getAttribName(atv.Type.String()), } } } From 328d3c1a9fcb02bb7764de80636ca204aeb5da22 Mon Sep 17 00:00:00 2001 From: Adriano Santoni Date: Sat, 1 Aug 2026 10:35:50 +0200 Subject: [PATCH 4/7] Add error count for UTF-8 replacement character --- v3/integration/config.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/v3/integration/config.json b/v3/integration/config.json index 90de4e756..958b8fbc7 100644 --- a/v3/integration/config.json +++ b/v3/integration/config.json @@ -1010,6 +1010,9 @@ }, "e_subj_email_not_in_san": { "ErrCount": 16576 + }, + "e_utf8_replacement_char_in_subject": { + "ErrCount": 2 } } } From bd0c83a5efebf7ceb091032bc7c2bf3c2a11e53f Mon Sep 17 00:00:00 2001 From: Adriano Santoni Date: Mon, 3 Aug 2026 07:11:32 +0200 Subject: [PATCH 5/7] Add files via upload --- v3/lint_utf8_replac_char_in_subj.go | 112 ++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 v3/lint_utf8_replac_char_in_subj.go diff --git a/v3/lint_utf8_replac_char_in_subj.go b/v3/lint_utf8_replac_char_in_subj.go new file mode 100644 index 000000000..c00e9a3c5 --- /dev/null +++ b/v3/lint_utf8_replac_char_in_subj.go @@ -0,0 +1,112 @@ +/* + * ZLint Copyright 2024 Regents of the University of Michigan + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package community + +import ( + "github.com/zmap/zcrypto/x509" + "github.com/zmap/zlint/v3/lint" + "github.com/zmap/zlint/v3/util" + + "encoding/asn1" + "strings" + "unicode/utf8" +) + +func init() { + lint.RegisterCertificateLint(&lint.CertificateLint{ + LintMetadata: lint.LintMetadata{ + Name: "e_utf8_replacement_char_in_subject", + Description: "Detects the UTF8 Replacement Character anywhere in the Subject field", + Citation: "Do not know what to insert here", + Source: lint.Community, + EffectiveDate: util.ZeroDate, + }, + Lint: NewUTF8ReplacementCharInSubject, + }) +} + +type attributeTypeAndValue struct { + Type asn1.ObjectIdentifier + Value asn1.RawValue +} + +type UTF8ReplacementCharInSubject struct{} + +func NewUTF8ReplacementCharInSubject() lint.LintInterface { + return &UTF8ReplacementCharInSubject{} +} + +func (l *UTF8ReplacementCharInSubject) CheckApplies(c *x509.Certificate) bool { + return true +} + +func getAttribName(oidStr string) string { + attribNames := map[string]string{ + "0.9.2342.19200300.100.1.25": "subject:domainComponent", + "1.2.840.113549.1.9.1": "subject:emailAddress", + "1.3.6.1.4.1.311.60.2.1.1": "subject:jurisdictionLocality", + "1.3.6.1.4.1.311.60.2.1.2": "subject:jurisdictionProvince", + "1.3.6.1.4.1.311.60.2.1.3": "subject:jurisdictionCountry", + "2.5.4.3": "subject:commonName", + "2.5.4.4": "subject:surname", + "2.5.4.5": "subject:serialNumber", + "2.5.4.6": "subject:countryName", + "2.5.4.7": "subject:localityName", + "2.5.4.8": "subject:stateOrProvinceName", + "2.5.4.9": "subject:streetAddress", + "2.5.4.10": "subject:organizationName", + "2.5.4.11": "subject:organizationalUnitName", + "2.5.4.12": "subject:title", + "2.5.4.17": "subject:postalCode", + "2.5.4.42": "subject:givenName", + "2.5.4.65": "subject:pseudonym", + "2.5.4.97": "subject:organizationIdentifier", + } + + name, found := attribNames[oidStr] + if found { + return name + } + return "Subject attribute with OID " + oidStr +} + +func (l *UTF8ReplacementCharInSubject) Execute(c *x509.Certificate) *lint.LintResult { + + var rdnSeq []asn1.RawValue // RDNSequence ::= SEQUENCE OF RDN + if _, err := asn1.Unmarshal(c.RawSubject, &rdnSeq); err != nil { + return &lint.LintResult{Status: lint.Fatal, Details: err.Error()} + } + + for _, rdn := range rdnSeq { + var atvs []attributeTypeAndValue // RDN ::= SET OF AttributeTypeAndValue + if _, err := asn1.UnmarshalWithParams(rdn.FullBytes, &atvs, "set"); err != nil { + return &lint.LintResult{Status: lint.Fatal, Details: err.Error()} + } + for _, atv := range atvs { + if atv.Value.Tag == asn1.TagUTF8String { // tag 12 (0x0C) + str := string(atv.Value.Bytes) + if strings.ContainsRune(str, utf8.RuneError) { + return &lint.LintResult{ + Status: lint.Error, + Details: "UTF8 Replacement Character detected in " + + getAttribName(atv.Type.String()), + } + } + } + } + } + + return &lint.LintResult{Status: lint.Pass} +} From e87f85cebfcdbe8fdb833d5d9d1e40d8471f9117 Mon Sep 17 00:00:00 2001 From: Adriano Santoni Date: Mon, 3 Aug 2026 07:16:23 +0200 Subject: [PATCH 6/7] Delete v3/lint_utf8_replac_char_in_subj.go --- v3/lint_utf8_replac_char_in_subj.go | 112 ---------------------------- 1 file changed, 112 deletions(-) delete mode 100644 v3/lint_utf8_replac_char_in_subj.go diff --git a/v3/lint_utf8_replac_char_in_subj.go b/v3/lint_utf8_replac_char_in_subj.go deleted file mode 100644 index c00e9a3c5..000000000 --- a/v3/lint_utf8_replac_char_in_subj.go +++ /dev/null @@ -1,112 +0,0 @@ -/* - * ZLint Copyright 2024 Regents of the University of Michigan - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - * implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package community - -import ( - "github.com/zmap/zcrypto/x509" - "github.com/zmap/zlint/v3/lint" - "github.com/zmap/zlint/v3/util" - - "encoding/asn1" - "strings" - "unicode/utf8" -) - -func init() { - lint.RegisterCertificateLint(&lint.CertificateLint{ - LintMetadata: lint.LintMetadata{ - Name: "e_utf8_replacement_char_in_subject", - Description: "Detects the UTF8 Replacement Character anywhere in the Subject field", - Citation: "Do not know what to insert here", - Source: lint.Community, - EffectiveDate: util.ZeroDate, - }, - Lint: NewUTF8ReplacementCharInSubject, - }) -} - -type attributeTypeAndValue struct { - Type asn1.ObjectIdentifier - Value asn1.RawValue -} - -type UTF8ReplacementCharInSubject struct{} - -func NewUTF8ReplacementCharInSubject() lint.LintInterface { - return &UTF8ReplacementCharInSubject{} -} - -func (l *UTF8ReplacementCharInSubject) CheckApplies(c *x509.Certificate) bool { - return true -} - -func getAttribName(oidStr string) string { - attribNames := map[string]string{ - "0.9.2342.19200300.100.1.25": "subject:domainComponent", - "1.2.840.113549.1.9.1": "subject:emailAddress", - "1.3.6.1.4.1.311.60.2.1.1": "subject:jurisdictionLocality", - "1.3.6.1.4.1.311.60.2.1.2": "subject:jurisdictionProvince", - "1.3.6.1.4.1.311.60.2.1.3": "subject:jurisdictionCountry", - "2.5.4.3": "subject:commonName", - "2.5.4.4": "subject:surname", - "2.5.4.5": "subject:serialNumber", - "2.5.4.6": "subject:countryName", - "2.5.4.7": "subject:localityName", - "2.5.4.8": "subject:stateOrProvinceName", - "2.5.4.9": "subject:streetAddress", - "2.5.4.10": "subject:organizationName", - "2.5.4.11": "subject:organizationalUnitName", - "2.5.4.12": "subject:title", - "2.5.4.17": "subject:postalCode", - "2.5.4.42": "subject:givenName", - "2.5.4.65": "subject:pseudonym", - "2.5.4.97": "subject:organizationIdentifier", - } - - name, found := attribNames[oidStr] - if found { - return name - } - return "Subject attribute with OID " + oidStr -} - -func (l *UTF8ReplacementCharInSubject) Execute(c *x509.Certificate) *lint.LintResult { - - var rdnSeq []asn1.RawValue // RDNSequence ::= SEQUENCE OF RDN - if _, err := asn1.Unmarshal(c.RawSubject, &rdnSeq); err != nil { - return &lint.LintResult{Status: lint.Fatal, Details: err.Error()} - } - - for _, rdn := range rdnSeq { - var atvs []attributeTypeAndValue // RDN ::= SET OF AttributeTypeAndValue - if _, err := asn1.UnmarshalWithParams(rdn.FullBytes, &atvs, "set"); err != nil { - return &lint.LintResult{Status: lint.Fatal, Details: err.Error()} - } - for _, atv := range atvs { - if atv.Value.Tag == asn1.TagUTF8String { // tag 12 (0x0C) - str := string(atv.Value.Bytes) - if strings.ContainsRune(str, utf8.RuneError) { - return &lint.LintResult{ - Status: lint.Error, - Details: "UTF8 Replacement Character detected in " + - getAttribName(atv.Type.String()), - } - } - } - } - } - - return &lint.LintResult{Status: lint.Pass} -} From 7aa773d2421472d82d4ce951fb0a8d607f8c7cd1 Mon Sep 17 00:00:00 2001 From: Adriano Santoni Date: Mon, 3 Aug 2026 07:16:55 +0200 Subject: [PATCH 7/7] Add files via upload --- v3/lints/community/lint_utf8_replac_char_in_subj.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v3/lints/community/lint_utf8_replac_char_in_subj.go b/v3/lints/community/lint_utf8_replac_char_in_subj.go index a1ec74cb7..c00e9a3c5 100644 --- a/v3/lints/community/lint_utf8_replac_char_in_subj.go +++ b/v3/lints/community/lint_utf8_replac_char_in_subj.go @@ -86,13 +86,13 @@ func (l *UTF8ReplacementCharInSubject) Execute(c *x509.Certificate) *lint.LintRe var rdnSeq []asn1.RawValue // RDNSequence ::= SEQUENCE OF RDN if _, err := asn1.Unmarshal(c.RawSubject, &rdnSeq); err != nil { - panic(err) + return &lint.LintResult{Status: lint.Fatal, Details: err.Error()} } for _, rdn := range rdnSeq { var atvs []attributeTypeAndValue // RDN ::= SET OF AttributeTypeAndValue if _, err := asn1.UnmarshalWithParams(rdn.FullBytes, &atvs, "set"); err != nil { - panic(err) + return &lint.LintResult{Status: lint.Fatal, Details: err.Error()} } for _, atv := range atvs { if atv.Value.Tag == asn1.TagUTF8String { // tag 12 (0x0C)