diff --git a/v3/lints/etsi/lint_qcstatem_psd2_ncaname_valid.go b/v3/lints/etsi/lint_qcstatem_psd2_ncaname_valid.go new file mode 100644 index 000000000..d72b90999 --- /dev/null +++ b/v3/lints/etsi/lint_qcstatem_psd2_ncaname_valid.go @@ -0,0 +1,93 @@ +/* + * ZLint Copyright 2026 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 etsi + +import ( + "unicode/utf8" + + "github.com/zmap/zcrypto/x509" + "github.com/zmap/zlint/v3/lint" + "github.com/zmap/zlint/v3/util" +) + +type qcStatemPsd2NcaNameValid struct{} + +// ETSI TS 119 495 V1.8.1 (2026-04), Section 5.2.3: +// +// GEN-5.2.3-1: The NCAName shall be plain text using Latin alphabet +// provided by the Competent Authority itself for purpose of +// identification in certificates. +// +// NCAName ::= UTF8String (SIZE(1..256)) +// +// GEN-5.2.3-1A: If the subject role is international central bank +// (PSP_CB) or international public authority (PSP_PA), NCAName shall +// have the value "NA". +func init() { + lint.RegisterCertificateLint(&lint.CertificateLint{ + LintMetadata: lint.LintMetadata{ + Name: "e_qcstatem_psd2_ncaname_valid", + Description: "Checks that the NCAName field of a PSD2 QcStatement is non-empty and at most 256 characters, or 'NA' for a certificate declaring a PSP_CB or PSP_PA role", + Citation: "ETSI TS 119 495 V1.8.1 (2026-04), Section 5.2.3, GEN-5.2.3-1, GEN-5.2.3-1A", + Source: lint.EtsiEsi, + EffectiveDate: util.EtsiTs119495_V1_1_2_Date, + }, + Lint: NewQcStatemPsd2NcaNameValid, + }) +} + +func NewQcStatemPsd2NcaNameValid() lint.LintInterface { + return &qcStatemPsd2NcaNameValid{} +} + +func (l *qcStatemPsd2NcaNameValid) CheckApplies(c *x509.Certificate) bool { + if !util.IsExtInCert(c, util.QcStateOid) { + return false + } + return util.ParseQcStatem(util.GetExtFromCert(c, util.QcStateOid).Value, util.IdEtsiPsd2Statem).IsPresent() +} + +func (l *qcStatemPsd2NcaNameValid) Execute(c *x509.Certificate) *lint.LintResult { + ext := util.GetExtFromCert(c, util.QcStateOid) + s := util.ParseQcStatem(ext.Value, util.IdEtsiPsd2Statem) + psd2, ok := s.(util.EtsiPsd2) + if !ok { + return &lint.LintResult{Status: lint.Fatal, Details: "parsed QC statement is not of type EtsiPsd2"} + } + + // Check NCAName itself against psd2.Decoded (populated whenever the + // PSD2QcType SEQUENCE itself unmarshaled, even if e_qcstatem_psd2_valid's + // structural SIZE(1..256) check already flagged it) before falling back + // to s.GetErrorInfo()'s generic message, so this lint's own + // NCAName-specific Details take precedence over that structural check. + if util.IsPsd2CentralBankOrPublicAuthority(psd2.Decoded.RolesOfPSP) { + if psd2.Decoded.NCAName != "NA" { + return &lint.LintResult{Status: lint.Error, Details: "NCAName must be 'NA' for a PSD2 QcStatement declaring a PSP_CB or PSP_PA role"} + } + } else { + nameLen := utf8.RuneCountInString(psd2.Decoded.NCAName) + if nameLen == 0 { + return &lint.LintResult{Status: lint.Error, Details: "NCAName must not be empty"} + } + if nameLen > 256 { + return &lint.LintResult{Status: lint.Error, Details: "NCAName must be at most 256 characters"} + } + } + + if s.GetErrorInfo() != "" { + return &lint.LintResult{Status: lint.Error, Details: s.GetErrorInfo()} + } + return &lint.LintResult{Status: lint.Pass} +} diff --git a/v3/lints/etsi/lint_qcstatem_psd2_ncaname_valid_test.go b/v3/lints/etsi/lint_qcstatem_psd2_ncaname_valid_test.go new file mode 100644 index 000000000..459315d64 --- /dev/null +++ b/v3/lints/etsi/lint_qcstatem_psd2_ncaname_valid_test.go @@ -0,0 +1,48 @@ +package etsi + +/* + * ZLint Copyright 2026 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. + */ + +import ( + "testing" + + "github.com/zmap/zlint/v3/lint" + "github.com/zmap/zlint/v3/test" +) + +func TestEtsiQcStatemPsd2NcaNameValid(t *testing.T) { + cases := map[string]struct { + status lint.LintStatus + details string // empty means "don't check Details" + }{ + "QcStmtEtsiPsd2ValidCert01.pem": {status: lint.Pass}, + "QcStmtEtsiPsd2NcaNameEmptyCert01.pem": {status: lint.Error, details: "NCAName must not be empty"}, + "QcStmtEtsiPsd2NcaNameTooLongCert01.pem": {status: lint.Error, details: "NCAName must be at most 256 characters"}, + "QcStmtEtsiPsd2NcaNamePspCbValidCert01.pem": {status: lint.Pass}, + "QcStmtEtsiPsd2NcaNamePspPaValidCert01.pem": {status: lint.Pass}, + "QcStmtEtsiPsd2NcaNamePspCbInvalidCert01.pem": {status: lint.Error, + details: "NCAName must be 'NA' for a PSD2 QcStatement declaring a PSP_CB or PSP_PA role"}, + "QcStmtEtsiPsd2WrongEncodingCert01.pem": {status: lint.Error, details: "error with ASN.1 encoding, possibly a wrong ASN.1 string type was used"}, + "QcStmtEtsiValidCert11.pem": {status: lint.NA}, + } + for inputPath, tc := range cases { + out := test.TestLint("e_qcstatem_psd2_ncaname_valid", inputPath) + if out.Status != tc.status { + t.Errorf("%s: expected %s, got %s", inputPath, tc.status, out.Status) + } + if tc.details != "" && out.Details != tc.details { + t.Errorf("%s: expected details %q, got %q", inputPath, tc.details, out.Details) + } + } +} diff --git a/v3/testdata/QcStmtEtsiPsd2NcaNameEmptyCert01.pem b/v3/testdata/QcStmtEtsiPsd2NcaNameEmptyCert01.pem new file mode 100644 index 000000000..60faa5d84 --- /dev/null +++ b/v3/testdata/QcStmtEtsiPsd2NcaNameEmptyCert01.pem @@ -0,0 +1,42 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: ecdsa-with-SHA256 + Issuer: + Validity + Not Before: Jan 1 00:00:00 2020 GMT + Not After : Jan 1 00:00:00 2030 GMT + Subject: CN = PSD2 NCAName Test Leaf + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (256 bit) + pub: + 04:5e:79:45:e9:69:ae:23:1a:53:74:23:1f:5f:ac: + e7:1c:70:f4:d0:02:b4:61:74:dc:01:67:c8:5d:07: + d2:b2:87:9c:b7:be:6b:62:88:1c:86:23:c1:85:c3: + 98:39:d0:40:23:3a:1f:6c:56:1c:97:2c:c4:0f:3b: + e6:12:f8:0e:03 + ASN1 OID: prime256v1 + NIST CURVE: P-256 + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:FALSE + qcStatements: + 0+0)......'.0.0.0.......'....PSP_AS....ES-BDE + Signature Algorithm: ecdsa-with-SHA256 + Signature Value: + 30:46:02:21:00:94:1d:4a:b1:4a:50:a6:a1:6a:f2:d5:39:c4: + e0:6f:10:6b:ae:37:18:3e:41:90:5b:c0:96:87:35:49:53:f9: + c7:02:21:00:d4:ff:ee:29:6c:43:a8:71:21:40:af:90:90:9c: + df:a4:46:ac:b9:8e:14:74:33:f4:f9:df:82:fb:82:4d:79:e6 +-----BEGIN CERTIFICATE----- +MIIBXDCCAQGgAwIBAgIBAjAKBggqhkjOPQQDAjAAMB4XDTIwMDEwMTAwMDAwMFoX +DTMwMDEwMTAwMDAwMFowITEfMB0GA1UEAxMWUFNEMiBOQ0FOYW1lIFRlc3QgTGVh +ZjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABF55RelpriMaU3QjH1+s5xxw9NAC +tGF03AFnyF0H0rKHnLe+a2KIHIYjwYXDmDnQQCM6H2xWHJcsxA875hL4DgOjSzBJ +MAwGA1UdEwEB/wQCMAAwOQYIKwYBBQUHAQMELTArMCkGBgQAgZgnAjAfMBMwEQYH +BACBmCcBAQwGUFNQX0FTDAAMBkVTLUJERTAKBggqhkjOPQQDAgNJADBGAiEAlB1K +sUpQpqFq8tU5xOBvEGuuNxg+QZBbwJaHNUlT+ccCIQDU/+4pbEOocSFAr5CQnN+k +Rqy5jhR0M/T534L7gk155g== +-----END CERTIFICATE----- diff --git a/v3/testdata/QcStmtEtsiPsd2NcaNamePspCbInvalidCert01.pem b/v3/testdata/QcStmtEtsiPsd2NcaNamePspCbInvalidCert01.pem new file mode 100644 index 000000000..e65003f50 --- /dev/null +++ b/v3/testdata/QcStmtEtsiPsd2NcaNamePspCbInvalidCert01.pem @@ -0,0 +1,42 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 6 (0x6) + Signature Algorithm: ecdsa-with-SHA256 + Issuer: + Validity + Not Before: Jan 1 00:00:00 2020 GMT + Not After : Jan 1 00:00:00 2030 GMT + Subject: CN = PSD2 NCAName Test Leaf + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (256 bit) + pub: + 04:29:51:cc:1d:d0:6d:0e:7b:ea:03:23:a6:c8:f4: + 13:92:5d:36:a3:bd:ce:b0:05:13:fa:3c:64:1b:be: + 84:a9:23:9f:d3:b8:51:ba:2d:bb:15:8d:b2:32:0a: + 48:fb:8d:e0:fe:46:4f:e7:30:a2:17:66:8e:42:b5: + 16:0e:87:b2:bf + ASN1 OID: prime256v1 + NIST CURVE: P-256 + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:FALSE + qcStatements: + 0705......'.0+0.0.......'....PSP_CB..Banco de Espa..a..NA + Signature Algorithm: ecdsa-with-SHA256 + Signature Value: + 30:44:02:20:08:76:c4:60:b3:bd:65:56:30:d8:20:0f:c1:5c: + 96:1c:30:50:6a:e3:b8:2a:ac:31:7c:a6:69:68:c0:eb:3b:fa: + 02:20:5d:a5:13:e2:38:39:d6:d7:24:d2:47:b8:57:ed:1c:69: + 02:70:f5:4d:d1:7e:80:9e:28:05:17:b7:60:e8:40:4b +-----BEGIN CERTIFICATE----- +MIIBZjCCAQ2gAwIBAgIBBjAKBggqhkjOPQQDAjAAMB4XDTIwMDEwMTAwMDAwMFoX +DTMwMDEwMTAwMDAwMFowITEfMB0GA1UEAxMWUFNEMiBOQ0FOYW1lIFRlc3QgTGVh +ZjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABClRzB3QbQ576gMjpsj0E5JdNqO9 +zrAFE/o8ZBu+hKkjn9O4UbotuxWNsjIKSPuN4P5GT+cwohdmjkK1Fg6Hsr+jVzBV +MAwGA1UdEwEB/wQCMAAwRQYIKwYBBQUHAQMEOTA3MDUGBgQAgZgnAjArMBMwEQYH +BACBmCcBBQwGUFNQX0NCDBBCYW5jbyBkZSBFc3Bhw7FhDAJOQTAKBggqhkjOPQQD +AgNHADBEAiAIdsRgs71lVjDYIA/BXJYcMFBq47gqrDF8pmlowOs7+gIgXaUT4jg5 +1tck0ke4V+0caQJw9U3RfoCeKAUXt2DoQEs= +-----END CERTIFICATE----- diff --git a/v3/testdata/QcStmtEtsiPsd2NcaNamePspCbValidCert01.pem b/v3/testdata/QcStmtEtsiPsd2NcaNamePspCbValidCert01.pem new file mode 100644 index 000000000..916ce3260 --- /dev/null +++ b/v3/testdata/QcStmtEtsiPsd2NcaNamePspCbValidCert01.pem @@ -0,0 +1,42 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4 (0x4) + Signature Algorithm: ecdsa-with-SHA256 + Issuer: + Validity + Not Before: Jan 1 00:00:00 2020 GMT + Not After : Jan 1 00:00:00 2030 GMT + Subject: CN = PSD2 NCAName Test Leaf + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (256 bit) + pub: + 04:64:ec:a7:f8:e8:eb:39:55:bc:96:5d:6c:97:e4: + 64:5e:1d:a8:11:1c:3b:5b:fb:b9:1a:21:00:a1:51: + f9:c6:55:17:c1:d2:07:b2:41:39:9a:dc:43:7f:bf: + e6:a6:cb:14:ed:a6:00:71:97:c0:6f:b1:84:15:3f: + 25:44:e9:9c:71 + ASN1 OID: prime256v1 + NIST CURVE: P-256 + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:FALSE + qcStatements: + 0)0'......'.0.0.0.......'....PSP_CB..NA..NA + Signature Algorithm: ecdsa-with-SHA256 + Signature Value: + 30:45:02:20:03:dd:d7:fb:ec:5f:c9:2f:bb:c9:bd:76:aa:7b: + fa:85:2a:fd:f9:ba:cb:37:9c:93:fd:32:31:80:2d:72:b2:1a: + 02:21:00:f1:35:18:1b:88:5f:e0:46:82:cd:45:49:a2:a4:ff: + 92:7e:f0:f7:2a:61:0f:fb:18:49:e7:6b:a1:10:de:d3:34 +-----BEGIN CERTIFICATE----- +MIIBWDCB/6ADAgECAgEEMAoGCCqGSM49BAMCMAAwHhcNMjAwMTAxMDAwMDAwWhcN +MzAwMTAxMDAwMDAwWjAhMR8wHQYDVQQDExZQU0QyIE5DQU5hbWUgVGVzdCBMZWFm +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZOyn+OjrOVW8ll1sl+RkXh2oERw7 +W/u5GiEAoVH5xlUXwdIHskE5mtxDf7/mpssU7aYAcZfAb7GEFT8lROmccaNJMEcw +DAYDVR0TAQH/BAIwADA3BggrBgEFBQcBAwQrMCkwJwYGBACBmCcCMB0wEzARBgcE +AIGYJwEFDAZQU1BfQ0IMAk5BDAJOQTAKBggqhkjOPQQDAgNIADBFAiAD3df77F/J +L7vJvXaqe/qFKv35uss3nJP9MjGALXKyGgIhAPE1GBuIX+BGgs1FSaKk/5J+8Pcq +YQ/7GEnna6EQ3tM0 +-----END CERTIFICATE----- diff --git a/v3/testdata/QcStmtEtsiPsd2NcaNamePspPaValidCert01.pem b/v3/testdata/QcStmtEtsiPsd2NcaNamePspPaValidCert01.pem new file mode 100644 index 000000000..27ea3d22c --- /dev/null +++ b/v3/testdata/QcStmtEtsiPsd2NcaNamePspPaValidCert01.pem @@ -0,0 +1,42 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 5 (0x5) + Signature Algorithm: ecdsa-with-SHA256 + Issuer: + Validity + Not Before: Jan 1 00:00:00 2020 GMT + Not After : Jan 1 00:00:00 2030 GMT + Subject: CN = PSD2 NCAName Test Leaf + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (256 bit) + pub: + 04:1e:69:4a:94:58:0e:54:56:38:53:42:25:65:ac: + e7:10:3a:e3:45:92:0b:25:88:74:86:fb:eb:7c:1b: + 90:42:95:2f:ab:0b:c0:67:f9:eb:0a:db:33:a4:f9: + 33:79:a5:ec:37:4a:25:4b:57:d2:a2:78:e9:ac:4f: + 3e:6d:16:83:5e + ASN1 OID: prime256v1 + NIST CURVE: P-256 + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:FALSE + qcStatements: + 0)0'......'.0.0.0.......'....PSP_PA..NA..NA + Signature Algorithm: ecdsa-with-SHA256 + Signature Value: + 30:44:02:20:78:08:3a:82:3f:76:7c:ff:97:84:cb:c5:43:65: + bd:5f:d1:02:4b:a4:42:d1:d8:98:c5:30:1b:12:4e:bc:33:e8: + 02:20:70:77:2f:7c:ad:c0:e9:d8:b6:87:64:2b:32:79:99:36: + c7:0f:ba:17:81:c5:0b:0a:dc:f1:49:26:0c:56:2b:8c +-----BEGIN CERTIFICATE----- +MIIBVzCB/6ADAgECAgEFMAoGCCqGSM49BAMCMAAwHhcNMjAwMTAxMDAwMDAwWhcN +MzAwMTAxMDAwMDAwWjAhMR8wHQYDVQQDExZQU0QyIE5DQU5hbWUgVGVzdCBMZWFm +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHmlKlFgOVFY4U0IlZaznEDrjRZIL +JYh0hvvrfBuQQpUvqwvAZ/nrCtszpPkzeaXsN0olS1fSonjprE8+bRaDXqNJMEcw +DAYDVR0TAQH/BAIwADA3BggrBgEFBQcBAwQrMCkwJwYGBACBmCcCMB0wEzARBgcE +AIGYJwEGDAZQU1BfUEEMAk5BDAJOQTAKBggqhkjOPQQDAgNHADBEAiB4CDqCP3Z8 +/5eEy8VDZb1f0QJLpELR2JjFMBsSTrwz6AIgcHcvfK3A6di2h2QrMnmZNscPuheB +xQsK3PFJJgxWK4w= +-----END CERTIFICATE----- diff --git a/v3/testdata/QcStmtEtsiPsd2NcaNameTooLongCert01.pem b/v3/testdata/QcStmtEtsiPsd2NcaNameTooLongCert01.pem new file mode 100644 index 000000000..3cd3801f8 --- /dev/null +++ b/v3/testdata/QcStmtEtsiPsd2NcaNameTooLongCert01.pem @@ -0,0 +1,47 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3 (0x3) + Signature Algorithm: ecdsa-with-SHA256 + Issuer: + Validity + Not Before: Jan 1 00:00:00 2020 GMT + Not After : Jan 1 00:00:00 2030 GMT + Subject: CN = PSD2 NCAName Test Leaf + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (256 bit) + pub: + 04:4e:3a:ec:ed:68:f3:e5:1f:a1:38:41:67:00:6e: + e3:1c:d3:be:cf:fc:6e:b3:fe:56:2d:2d:db:04:a7: + 78:d9:99:71:a2:f3:6a:fb:78:6d:19:6d:2a:cc:f4: + 6f:1a:e1:8f:da:59:e8:d5:c7:f8:08:93:26:5b:30: + f4:d1:2c:7a:70 + ASN1 OID: prime256v1 + NIST CURVE: P-256 + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:FALSE + qcStatements: + 0..20.........'.0.."0.0.......'....PSP_AS....AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA..ES-BDE + Signature Algorithm: ecdsa-with-SHA256 + Signature Value: + 30:45:02:21:00:86:49:31:16:fb:56:52:da:54:6c:9a:a1:ac: + 93:fb:fe:05:d2:3b:42:50:98:2e:a6:a2:14:6e:a2:23:3c:b2: + 4c:02:20:1d:96:4c:15:7a:97:c1:8d:a4:57:d5:f5:ea:d6:3c: + a2:2d:5b:bc:9d:ac:58:e7:99:cc:39:ce:3d:f0:0c:0d:69 +-----BEGIN CERTIFICATE----- +MIICbDCCAhKgAwIBAgIBAzAKBggqhkjOPQQDAjAAMB4XDTIwMDEwMTAwMDAwMFoX +DTMwMDEwMTAwMDAwMFowITEfMB0GA1UEAxMWUFNEMiBOQ0FOYW1lIFRlc3QgTGVh +ZjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABE467O1o8+UfoThBZwBu4xzTvs/8 +brP+Vi0t2wSneNmZcaLzavt4bRltKsz0bxrhj9pZ6NXH+AiTJlsw9NEsenCjggFa +MIIBVjAMBgNVHRMBAf8EAjAAMIIBRAYIKwYBBQUHAQMEggE2MIIBMjCCAS4GBgQA +gZgnAjCCASIwEzARBgcEAIGYJwEBDAZQU1BfQVMMggEBQUFBQUFBQUFBQUFBQUFB +QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB +QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB +QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB +QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB +QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB +QUEMBkVTLUJERTAKBggqhkjOPQQDAgNIADBFAiEAhkkxFvtWUtpUbJqhrJP7/gXS +O0JQmC6mohRuoiM8skwCIB2WTBV6l8GNpFfV9erWPKItW7ydrFjnmcw5zj3wDA1p +-----END CERTIFICATE----- diff --git a/v3/util/oid.go b/v3/util/oid.go index 95d4c0da7..51a2f5996 100644 --- a/v3/util/oid.go +++ b/v3/util/oid.go @@ -127,7 +127,9 @@ var ( QEVCPwPolicyOID = asn1.ObjectIdentifier{0, 4, 0, 194112, 1, 4} QNCPwPolicyOID = asn1.ObjectIdentifier{0, 4, 0, 194112, 1, 5} QNCPwgenPolicyOID = asn1.ObjectIdentifier{0, 4, 0, 194112, 1, 6} - IdEtsiPsd2Statem = asn1.ObjectIdentifier{0, 4, 0, 19495, 2} // ETSI TS 119 495 V1.1.2, Annex A: id-etsi-psd2-qcStatement + IdEtsiPsd2Statem = asn1.ObjectIdentifier{0, 4, 0, 19495, 2} // ETSI TS 119 495 V1.1.2, Annex A: id-etsi-psd2-qcStatement + IdEtsiPsd2RolePspCb = asn1.ObjectIdentifier{0, 4, 0, 19495, 1, 5} // ETSI TS 119 495, Annex A: id-psd2-role-psp-cb (Central Bank) + IdEtsiPsd2RolePspPa = asn1.ObjectIdentifier{0, 4, 0, 19495, 1, 6} // ETSI TS 119 495, Annex A: id-psd2-role-psp-pa (Public authority) ) const ( diff --git a/v3/util/qc_stmt.go b/v3/util/qc_stmt.go index 98ff5d18d..ab0022fc4 100644 --- a/v3/util/qc_stmt.go +++ b/v3/util/qc_stmt.go @@ -116,6 +116,19 @@ type EtsiPsd2 struct { Decoded PSD2QcType } +// IsPsd2CentralBankOrPublicAuthority reports whether roles declares a PSP_CB +// (Central Bank) or PSP_PA (Public authority) role, per ETSI TS 119 495 +// Annex A. Certificates declaring either role are subject to the +// GEN-5.2.3-1A/GEN-5.2.3-5 "NA" carve-out for NCAName/NCAId. +func IsPsd2CentralBankOrPublicAuthority(roles []RoleOfPSP) bool { + for _, role := range roles { + if role.RoleOfPspOid.Equal(IdEtsiPsd2RolePspCb) || role.RoleOfPspOid.Equal(IdEtsiPsd2RolePspPa) { + return true + } + } + return false +} + func AppendToStringSemicolonDelim(this *string, s string) { if len(*this) > 0 && len(s) > 0 { (*this) += "; " diff --git a/v3/util/qc_stmt_test.go b/v3/util/qc_stmt_test.go index b3fb0ba1e..211fc6535 100644 --- a/v3/util/qc_stmt_test.go +++ b/v3/util/qc_stmt_test.go @@ -174,3 +174,25 @@ func mustMarshal(t *testing.T, v interface{}) []byte { } return b } + +func TestIsPsd2CentralBankOrPublicAuthority(t *testing.T) { + cb := RoleOfPSP{RoleOfPspOid: IdEtsiPsd2RolePspCb, RoleOfPspName: "PSP_CB"} + pa := RoleOfPSP{RoleOfPspOid: IdEtsiPsd2RolePspPa, RoleOfPspName: "PSP_PA"} + as := RoleOfPSP{RoleOfPspOid: asn1.ObjectIdentifier{0, 4, 0, 19495, 1, 1}, RoleOfPspName: "PSP_AS"} + + if !IsPsd2CentralBankOrPublicAuthority([]RoleOfPSP{cb}) { + t.Errorf("expected true for a role list containing only PSP_CB") + } + if !IsPsd2CentralBankOrPublicAuthority([]RoleOfPSP{pa}) { + t.Errorf("expected true for a role list containing only PSP_PA") + } + if !IsPsd2CentralBankOrPublicAuthority([]RoleOfPSP{as, cb}) { + t.Errorf("expected true for a role list containing PSP_AS and PSP_CB") + } + if IsPsd2CentralBankOrPublicAuthority([]RoleOfPSP{as}) { + t.Errorf("expected false for a role list containing only PSP_AS") + } + if IsPsd2CentralBankOrPublicAuthority(nil) { + t.Errorf("expected false for an empty role list") + } +}