-
Notifications
You must be signed in to change notification settings - Fork 8.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This change set is simply moving a small package from fabric-ca to fabric to avoid vendoring fabric-ca from fabric. This package was at fabric-ca/attrmgr and moving to fabric/common/attrmgr. Change-Id: Id24b83e8fb7f0f2b6687088809f91fe4264f889e Signed-off-by: Keith Smith <bksmith@us.ibm.com>
- Loading branch information
Keith Smith
committed
Sep 19, 2017
1 parent
30e6f49
commit 3a7f893
Showing
2 changed files
with
326 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
/* | ||
Copyright IBM Corp. 2017 All Rights Reserved. | ||
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. | ||
*/ | ||
|
||
/* | ||
* The attrmgr package contains utilities for managing attributes. | ||
* Attributes are added to an X509 certificate as an extension. | ||
*/ | ||
|
||
package attrmgr | ||
|
||
import ( | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/asn1" | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
var ( | ||
// AttrOID is the ASN.1 object identifier for an attribute extension in an | ||
// X509 certificate | ||
AttrOID = asn1.ObjectIdentifier{1, 2, 3, 4, 5, 6, 7, 8, 1} | ||
// AttrOIDString is the string version of AttrOID | ||
AttrOIDString = "1.2.3.4.5.6.7.8.1" | ||
) | ||
|
||
// Attribute is a name/value pair | ||
type Attribute interface { | ||
// GetName returns the name of the attribute | ||
GetName() string | ||
// GetValue returns the value of the attribute | ||
GetValue() string | ||
} | ||
|
||
// AttributeRequest is a request for an attribute | ||
type AttributeRequest interface { | ||
// GetName returns the name of an attribute | ||
GetName() string | ||
// IsRequired returns true if the attribute is required | ||
IsRequired() bool | ||
} | ||
|
||
// New constructs an attribute manager | ||
func New() *Mgr { return &Mgr{} } | ||
|
||
// Mgr is the attribute manager and is the main object for this package | ||
type Mgr struct{} | ||
|
||
// ProcessAttributeRequestsForCert add attributes to an X509 certificate, given | ||
// attribute requests and attributes. | ||
func (mgr *Mgr) ProcessAttributeRequestsForCert(requests []AttributeRequest, attributes []Attribute, cert *x509.Certificate) error { | ||
attrs, err := mgr.ProcessAttributeRequests(requests, attributes) | ||
if err != nil { | ||
return err | ||
} | ||
return mgr.AddAttributesToCert(attrs, cert) | ||
} | ||
|
||
// ProcessAttributeRequests takes an array of attribute requests and an identity's attributes | ||
// and returns an Attributes object containing the requested attributes. | ||
func (mgr *Mgr) ProcessAttributeRequests(requests []AttributeRequest, attributes []Attribute) (*Attributes, error) { | ||
attrsMap := map[string]string{} | ||
attrs := &Attributes{Attrs: attrsMap} | ||
missingRequiredAttrs := []string{} | ||
// For each of the attribute requests | ||
for _, req := range requests { | ||
// Get the attribute | ||
name := req.GetName() | ||
attr := getAttrByName(name, attributes) | ||
if attr == nil { | ||
if req.IsRequired() { | ||
// Didn't find attribute and it was required; return error below | ||
missingRequiredAttrs = append(missingRequiredAttrs, name) | ||
} | ||
// Skip attribute requests which aren't required | ||
continue | ||
} | ||
attrsMap[name] = attr.GetValue() | ||
} | ||
if len(missingRequiredAttrs) > 0 { | ||
return nil, errors.Errorf("The following required attributes are missing: %+v", | ||
missingRequiredAttrs) | ||
} | ||
return attrs, nil | ||
} | ||
|
||
// AddAttributesToCert adds public attribute info to an X509 certificate. | ||
func (mgr *Mgr) AddAttributesToCert(attrs *Attributes, cert *x509.Certificate) error { | ||
buf, err := json.Marshal(attrs) | ||
if err != nil { | ||
return errors.Wrap(err, "Failed to marshal attributes") | ||
} | ||
ext := pkix.Extension{ | ||
Id: AttrOID, | ||
Critical: false, | ||
Value: buf, | ||
} | ||
cert.Extensions = append(cert.Extensions, ext) | ||
return nil | ||
} | ||
|
||
// GetAttributesFromCert gets the attributes from a certificate. | ||
func (mgr *Mgr) GetAttributesFromCert(cert *x509.Certificate) (*Attributes, error) { | ||
// Get certificate attributes from the certificate if it exists | ||
buf, err := getAttributesFromCert(cert) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// Unmarshal into attributes object | ||
attrs := &Attributes{} | ||
if buf != nil { | ||
err := json.Unmarshal(buf, attrs) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "Failed to unmarshal attributes from certificate") | ||
} | ||
} | ||
return attrs, nil | ||
} | ||
|
||
// Attributes contains attribute names and values | ||
type Attributes struct { | ||
Attrs map[string]string `json:"attrs"` | ||
} | ||
|
||
// Names returns the names of the attributes | ||
func (a *Attributes) Names() []string { | ||
i := 0 | ||
names := make([]string, len(a.Attrs)) | ||
for name := range a.Attrs { | ||
names[i] = name | ||
i++ | ||
} | ||
return names | ||
} | ||
|
||
// Contains returns true if the named attribute is found | ||
func (a *Attributes) Contains(name string) bool { | ||
_, ok := a.Attrs[name] | ||
return ok | ||
} | ||
|
||
// Value returns an attribute's value | ||
func (a *Attributes) Value(name string) (string, bool, error) { | ||
attr, ok := a.Attrs[name] | ||
return attr, ok, nil | ||
} | ||
|
||
// True returns nil if the value of attribute 'name' is true; | ||
// otherwise, an appropriate error is returned. | ||
func (a *Attributes) True(name string) error { | ||
val, ok, err := a.Value(name) | ||
if err != nil { | ||
return err | ||
} | ||
if !ok { | ||
return fmt.Errorf("Attribute '%s' was not found", name) | ||
} | ||
if val != "true" { | ||
return fmt.Errorf("Attribute '%s' is not true", name) | ||
} | ||
return nil | ||
} | ||
|
||
// Get the attribute info from a certificate extension, or return nil if not found | ||
func getAttributesFromCert(cert *x509.Certificate) ([]byte, error) { | ||
for _, ext := range cert.Extensions { | ||
if isAttrOID(ext.Id) { | ||
return ext.Value, nil | ||
} | ||
} | ||
return nil, nil | ||
} | ||
|
||
// Is the object ID equal to the attribute info object ID? | ||
func isAttrOID(oid asn1.ObjectIdentifier) bool { | ||
if len(oid) != len(AttrOID) { | ||
return false | ||
} | ||
for idx, val := range oid { | ||
if val != AttrOID[idx] { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
// Get an attribute from 'attrs' by its name, or nil if not found | ||
func getAttrByName(name string, attrs []Attribute) Attribute { | ||
for _, attr := range attrs { | ||
if attr.GetName() == name { | ||
return attr | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
Copyright IBM Corp. 2016 All Rights Reserved. | ||
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 attrmgr_test | ||
|
||
import ( | ||
"crypto/x509" | ||
"testing" | ||
|
||
"github.com/hyperledger/fabric/common/attrmgr" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
// TestAttrs tests attributes | ||
func TestAttrs(t *testing.T) { | ||
mgr := attrmgr.New() | ||
attrs := []attrmgr.Attribute{ | ||
&Attribute{Name: "attr1", Value: "val1"}, | ||
&Attribute{Name: "attr2", Value: "val2"}, | ||
&Attribute{Name: "attr3", Value: "val3"}, | ||
&Attribute{Name: "boolAttr", Value: "true"}, | ||
} | ||
reqs := []attrmgr.AttributeRequest{ | ||
&AttributeRequest{Name: "attr1", Require: false}, | ||
&AttributeRequest{Name: "attr2", Require: true}, | ||
&AttributeRequest{Name: "boolAttr", Require: true}, | ||
&AttributeRequest{Name: "noattr1", Require: false}, | ||
} | ||
cert := &x509.Certificate{} | ||
|
||
// Verify that the certificate has no attributes | ||
at, err := mgr.GetAttributesFromCert(cert) | ||
if err != nil { | ||
t.Fatalf("Failed to GetAttributesFromCert: %s", err) | ||
} | ||
numAttrs := len(at.Names()) | ||
assert.True(t, numAttrs == 0, "expecting 0 attributes but found %d", numAttrs) | ||
|
||
// Add attributes to certificate | ||
err = mgr.ProcessAttributeRequestsForCert(reqs, attrs, cert) | ||
if err != nil { | ||
t.Fatalf("Failed to ProcessAttributeRequestsForCert: %s", err) | ||
} | ||
|
||
// Get attributes from the certificate and verify the count is correct | ||
at, err = mgr.GetAttributesFromCert(cert) | ||
if err != nil { | ||
t.Fatalf("Failed to GetAttributesFromCert: %s", err) | ||
} | ||
numAttrs = len(at.Names()) | ||
assert.True(t, numAttrs == 3, "expecting 3 attributes but found %d", numAttrs) | ||
|
||
// Check individual attributes | ||
checkAttr(t, "attr1", "val1", at) | ||
checkAttr(t, "attr2", "val2", at) | ||
checkAttr(t, "attr3", "", at) | ||
checkAttr(t, "noattr1", "", at) | ||
assert.NoError(t, at.True("boolAttr")) | ||
|
||
// Negative test case: add required attributes which don't exist | ||
reqs = []attrmgr.AttributeRequest{ | ||
&AttributeRequest{Name: "noattr1", Require: true}, | ||
} | ||
err = mgr.ProcessAttributeRequestsForCert(reqs, attrs, cert) | ||
assert.Error(t, err) | ||
} | ||
|
||
func checkAttr(t *testing.T, name, val string, attrs *attrmgr.Attributes) { | ||
v, ok, err := attrs.Value(name) | ||
assert.NoError(t, err) | ||
if val == "" { | ||
assert.False(t, attrs.Contains(name), "contains attribute '%s'", name) | ||
assert.False(t, ok, "attribute '%s' was found", name) | ||
} else { | ||
assert.True(t, attrs.Contains(name), "does not contain attribute '%s'", name) | ||
assert.True(t, ok, "attribute '%s' was not found", name) | ||
assert.True(t, v == val, "incorrect value for '%s'; expected '%s' but found '%s'", name, val, v) | ||
} | ||
} | ||
|
||
type Attribute struct { | ||
Name, Value string | ||
} | ||
|
||
func (a *Attribute) GetName() string { | ||
return a.Name | ||
} | ||
|
||
func (a *Attribute) GetValue() string { | ||
return a.Value | ||
} | ||
|
||
type AttributeRequest struct { | ||
Name string | ||
Require bool | ||
} | ||
|
||
func (ar *AttributeRequest) GetName() string { | ||
return ar.Name | ||
} | ||
|
||
func (ar *AttributeRequest) IsRequired() bool { | ||
return ar.Require | ||
} |