Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 {
panic(err)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Panics from lints are caught-and-recovered. However, if you don't mind, I believe that should prefer propagating a lint.Fatal with the Detail being the error.

}

for _, rdn := range rdnSeq {
var atvs []attributeTypeAndValue // RDN ::= SET OF AttributeTypeAndValue
if _, err := asn1.UnmarshalWithParams(rdn.FullBytes, &atvs, "set"); err != nil {
panic(err)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done.

}
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