Skip to content

Commit

Permalink
feat: support distinguished name state(S) (notaryproject#432)
Browse files Browse the repository at this point in the history
Resolves notaryproject#431 
Signed-off-by: Junjie Gao <[email protected]>

---------

Signed-off-by: Junjie Gao <[email protected]>
  • Loading branch information
JeyJeyGao authored Aug 7, 2024
1 parent 09e32d7 commit c3b2f51
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 5 deletions.
11 changes: 8 additions & 3 deletions internal/pkix/pkix.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ func ParseDistinguishedName(name string) (map[string]string, error) {
return nil, fmt.Errorf("unsupported distinguished name (DN) %q: notation does not support x509.subject identities containing \"=#\"", name)
}

mandatoryFields := []string{"C", "ST", "O"}
attrKeyValue := make(map[string]string)
dn, err := ldapv3.ParseDN(name)
if err != nil {
return nil, fmt.Errorf("parsing distinguished name (DN) %q failed with err: %v. A valid DN must contain 'C', 'ST', and 'O' RDN attributes at a minimum, and follow RFC 4514 standard", name, err)
return nil, fmt.Errorf("parsing distinguished name (DN) %q failed with err: %v. A valid DN must contain 'C', 'ST' or 'S', and 'O' RDN attributes at a minimum, and follow RFC 4514 standard", name, err)
}

for _, rdn := range dn.RDNs {
Expand All @@ -39,6 +38,10 @@ func ParseDistinguishedName(name string) (map[string]string, error) {
return nil, fmt.Errorf("distinguished name (DN) %q has multi-valued RDN attributes, remove multi-valued RDN attributes as they are not supported", name)
}
for _, attribute := range rdn.Attributes {
// stateOrProvince name 'S' is an alias for 'ST'
if attribute.Type == "S" {
attribute.Type = "ST"
}
if attrKeyValue[attribute.Type] == "" {
attrKeyValue[attribute.Type] = attribute.Value
} else {
Expand All @@ -48,11 +51,13 @@ func ParseDistinguishedName(name string) (map[string]string, error) {
}

// Verify mandatory fields are present
mandatoryFields := []string{"C", "ST", "O"}
for _, field := range mandatoryFields {
if attrKeyValue[field] == "" {
return nil, fmt.Errorf("distinguished name (DN) %q has no mandatory RDN attribute for %q, it must contain 'C', 'ST', and 'O' RDN attributes at a minimum", name, field)
return nil, fmt.Errorf("distinguished name (DN) %q has no mandatory RDN attribute for %q, it must contain 'C', 'ST' or 'S', and 'O' RDN attributes at a minimum", name, field)
}
}

// No errors
return attrKeyValue, nil
}
Expand Down
143 changes: 143 additions & 0 deletions internal/pkix/pkix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright The Notary Project Authors.
// 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 pkix

import "testing"

func TestParseDistinguishedName(t *testing.T) {
// Test cases
tests := []struct {
name string
input string
wantErr bool
}{
{
name: "valid DN",
input: "C=US,ST=California,O=Notary Project",
wantErr: false,
},
{
name: "valid DN with State alias",
input: "C=US,S=California,O=Notary Project",
wantErr: false,
},
{
name: "invalid DN",
input: "C=US,ST=California",
wantErr: true,
},
{
name: "invalid DN without State",
input: "C=US,O=Notary Project",
wantErr: true,
},
{
name: "invalid DN without State",
input: "invalid",
wantErr: true,
},
{
name: "duplicate RDN attribute",
input: "C=US,ST=California,O=Notary Project,S=California",
wantErr: true,
},
{
name: "unsupported DN =#",
input: "C=US,ST=California,O=Notary Project=#",
wantErr: true,
},
{
name: "multi-valued RDN attributes",
input: "OU=Sales+CN=J. Smith,DC=example,DC=net",
wantErr: true,
},
}

// Run tests
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := ParseDistinguishedName(tt.input)
if tt.wantErr != (err != nil) {
t.Errorf("ParseDistinguishedName() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestIsSubsetDN(t *testing.T) {
// Test cases
tests := []struct {
name string
dn1 map[string]string
dn2 map[string]string
want bool
}{
{
name: "subset DN",
dn1: map[string]string{
"C": "US",
"ST": "California",
"O": "Notary Project",
},
dn2: map[string]string{
"C": "US",
"ST": "California",
"O": "Notary Project",
"L": "Los Angeles",
},
want: true,
},
{
name: "not subset DN",
dn1: map[string]string{
"C": "US",
"ST": "California",
"O": "Notary Project",
},
dn2: map[string]string{
"C": "US",
"ST": "California",
"O": "Notary Project 2",
"L": "Los Angeles",
"CN": "Notary",
},
want: false,
},
{
name: "not subset DN 2",
dn1: map[string]string{
"C": "US",
"ST": "California",
"O": "Notary Project",
"CN": "Notary",
},
dn2: map[string]string{
"C": "US",
"ST": "California",
"O": "Notary Project",
"L": "Los Angeles",
},
want: false,
},
}

// Run tests
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsSubsetDN(tt.dn1, tt.dn2); got != tt.want {
t.Errorf("IsSubsetDN() = %v, want %v", got, tt.want)
}
})
}
}
4 changes: 2 additions & 2 deletions verifier/trustpolicy/trustpolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func TestValidateTrustedIdentities(t *testing.T) {
// Validate x509.subject identities
invalidDN := "x509.subject:,,,"
err = validateTrustedIdentities("test-statement-name", []string{invalidDN})
if err == nil || err.Error() != "trust policy statement \"test-statement-name\" has trusted identity \"x509.subject:,,,\" with invalid identity value: parsing distinguished name (DN) \",,,\" failed with err: incomplete type, value pair. A valid DN must contain 'C', 'ST', and 'O' RDN attributes at a minimum, and follow RFC 4514 standard" {
if err == nil || err.Error() != "trust policy statement \"test-statement-name\" has trusted identity \"x509.subject:,,,\" with invalid identity value: parsing distinguished name (DN) \",,,\" failed with err: incomplete type, value pair. A valid DN must contain 'C', 'ST' or 'S', and 'O' RDN attributes at a minimum, and follow RFC 4514 standard" {
t.Fatalf("invalid x509.subject identity should return error. Error : %q", err)
}

Expand All @@ -185,7 +185,7 @@ func TestValidateTrustedIdentities(t *testing.T) {
// Validate mandatory RDNs
invalidDN = "x509.subject:C=US,ST=WA"
err = validateTrustedIdentities("test-statement-name", []string{invalidDN})
if err == nil || err.Error() != "trust policy statement \"test-statement-name\" has trusted identity \"x509.subject:C=US,ST=WA\" with invalid identity value: distinguished name (DN) \"C=US,ST=WA\" has no mandatory RDN attribute for \"O\", it must contain 'C', 'ST', and 'O' RDN attributes at a minimum" {
if err == nil || err.Error() != "trust policy statement \"test-statement-name\" has trusted identity \"x509.subject:C=US,ST=WA\" with invalid identity value: distinguished name (DN) \"C=US,ST=WA\" has no mandatory RDN attribute for \"O\", it must contain 'C', 'ST' or 'S', and 'O' RDN attributes at a minimum" {
t.Fatalf("invalid x509.subject identity should return error. Error : %q", err)
}

Expand Down

0 comments on commit c3b2f51

Please sign in to comment.