Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions v3/integration/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,9 @@
},
"e_subj_email_not_in_san": {
"ErrCount": 16576
},
"e_utf8_replacement_char_in_subject": {
"ErrCount": 2
}
}
}
112 changes: 112 additions & 0 deletions v3/lints/community/lint_utf8_replac_char_in_subj.go
Original file line number Diff line number Diff line change
@@ -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}
}
61 changes: 61 additions & 0 deletions v3/lints/community/lint_utf8_replac_char_in_subj_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
94 changes: 94 additions & 0 deletions v3/testdata/utf8_replac_char_in_subj_clean.pem
Original file line number Diff line number Diff line change
@@ -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-----
94 changes: 94 additions & 0 deletions v3/testdata/utf8_replac_char_in_subj_dirty1.pem
Original file line number Diff line number Diff line change
@@ -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-----
Loading
Loading