From 591cea8afebc4b74f87a49d531f0a2c04964f9ea Mon Sep 17 00:00:00 2001 From: Troy Ronda Date: Fri, 15 Sep 2017 15:48:12 -0400 Subject: [PATCH] [FAB-6177] Improve Fabric-CA vendoring (populate 1.0.1) This patch uses the third_party pinning script to populate Fabric CA 1.0.1 dependencies into the SDK. Change-Id: I650850257663f1fde211c80de910684b3289e178 Signed-off-by: Troy Ronda --- Gopkg.lock | 62 +- Gopkg.toml | 4 - def/fabapi/pkgfactory.go | 2 +- .../hyperledger/fabric-ca/api/client.go | 153 ++++ .../hyperledger/fabric-ca/api/net.go | 85 +++ .../hyperledger/fabric-ca/lib/client.go | 520 +++++++++++++ .../hyperledger/fabric-ca/lib/clientconfig.go | 72 ++ .../hyperledger/fabric-ca/lib/identity.go | 221 ++++++ .../hyperledger/fabric-ca/lib/serverstruct.go | 30 + .../hyperledger/fabric-ca/lib/signer.go | 66 ++ .../fabric-ca/lib/spi/affiliation.go | 39 + .../fabric-ca/lib/spi/userregistry.go | 58 ++ .../hyperledger/fabric-ca/lib/tcert/api.go | 68 ++ .../fabric-ca/lib/tcert/keytree.go | 94 +++ .../hyperledger/fabric-ca/lib/tcert/tcert.go | 320 ++++++++ .../hyperledger/fabric-ca/lib/tcert/util.go | 368 ++++++++++ .../hyperledger/fabric-ca/lib/tls/tls.go | 186 +++++ .../hyperledger/fabric-ca/lib/util.go | 181 +++++ .../hyperledger/fabric-ca/util/args.go | 92 +++ .../hyperledger/fabric-ca/util/csp.go | 357 +++++++++ .../hyperledger/fabric-ca/util/flag.go | 211 ++++++ .../hyperledger/fabric-ca/util/struct.go | 178 +++++ .../hyperledger/fabric-ca/util/util.go | 690 ++++++++++++++++++ pkg/fabric-ca-client/fabricca.go | 4 +- .../mocks/mockfabriccaserver.go | 4 +- 25 files changed, 3998 insertions(+), 67 deletions(-) create mode 100644 internal/github.com/hyperledger/fabric-ca/api/client.go create mode 100644 internal/github.com/hyperledger/fabric-ca/api/net.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/client.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/clientconfig.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/identity.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/serverstruct.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/signer.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/spi/affiliation.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/spi/userregistry.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/tcert/api.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/tcert/keytree.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/tcert/tcert.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/tcert/util.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/tls/tls.go create mode 100644 internal/github.com/hyperledger/fabric-ca/lib/util.go create mode 100644 internal/github.com/hyperledger/fabric-ca/util/args.go create mode 100644 internal/github.com/hyperledger/fabric-ca/util/csp.go create mode 100644 internal/github.com/hyperledger/fabric-ca/util/flag.go create mode 100644 internal/github.com/hyperledger/fabric-ca/util/struct.go create mode 100644 internal/github.com/hyperledger/fabric-ca/util/util.go diff --git a/Gopkg.lock b/Gopkg.lock index 2202f23337..808b146874 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -34,7 +34,7 @@ [[projects]] branch = "master" name = "github.com/cloudflare/cfssl" - packages = ["api","api/client","auth","certdb","certdb/sql","cli","config","crypto/pkcs7","csr","errors","helpers","helpers/derhelpers","info","initca","log","ocsp","ocsp/config","revoke","signer","signer/local","signer/remote","signer/universal"] + packages = ["api","api/client","auth","certdb","cli","config","crypto/pkcs7","csr","errors","helpers","helpers/derhelpers","info","initca","log","ocsp","ocsp/config","signer","signer/local","signer/remote","signer/universal"] revision = "fb16c3479c6ecbd2211751586b206f71387349e6" [[projects]] @@ -98,12 +98,6 @@ packages = ["."] revision = "75772940379e725b5aae213e570f9dcd751951cb" -[[projects]] - name = "github.com/go-sql-driver/mysql" - packages = ["."] - revision = "a0583e0143b1624142adab07e0e97fe106d99561" - version = "v1.3" - [[projects]] name = "github.com/gogo/protobuf" packages = ["gogoproto","proto","protoc-gen-gogo/descriptor","sortkeys","types"] @@ -167,57 +161,21 @@ [[projects]] branch = "master" name = "github.com/hyperledger/fabric" - packages = ["bccsp","bccsp/factory","bccsp/pkcs11","bccsp/signer","bccsp/sw","bccsp/utils","common/cauthdsl","common/channelconfig","common/configtx","common/configtx/api","common/crypto","common/flogging","common/ledger","common/ledger/util","common/metadata","common/policies","common/util","core/chaincode/platforms","core/chaincode/platforms/car","core/chaincode/platforms/golang","core/chaincode/platforms/java","core/chaincode/platforms/node","core/chaincode/platforms/util","core/chaincode/shim","core/comm","core/config","core/container/util","core/ledger","core/ledger/kvledger/txmgmt/rwsetutil","core/ledger/kvledger/txmgmt/version","core/ledger/util","events/consumer","msp","msp/cache","msp/mgmt","protos/common","protos/ledger/queryresult","protos/ledger/rwset","protos/ledger/rwset/kvrwset","protos/msp","protos/orderer","protos/peer","protos/utils"] + packages = ["bccsp","bccsp/factory","bccsp/pkcs11","bccsp/signer","bccsp/sw","bccsp/utils","common/cauthdsl","common/channelconfig","common/configtx","common/configtx/api","common/crypto","common/flogging","common/ledger","common/ledger/util","common/metadata","common/policies","common/util","core/chaincode/platforms","core/chaincode/platforms/car","core/chaincode/platforms/golang","core/chaincode/platforms/java","core/chaincode/platforms/node","core/chaincode/platforms/util","core/comm","core/config","core/container/util","core/ledger","core/ledger/kvledger/txmgmt/rwsetutil","core/ledger/kvledger/txmgmt/version","core/ledger/util","events/consumer","msp","msp/cache","msp/mgmt","protos/common","protos/ledger/rwset","protos/ledger/rwset/kvrwset","protos/msp","protos/orderer","protos/peer","protos/utils"] revision = "a657db28a0ff53ed512bd6f4ac4786a0f4ca709c" -[[projects]] - name = "github.com/hyperledger/fabric-ca" - packages = ["api","lib","lib/dbutil","lib/ldap","lib/spi","lib/tcert","lib/tls","util"] - revision = "0262ccbeeb09a8ec85e47a7c051e9b7ddd6856b8" - version = "v1.0.1" - [[projects]] name = "github.com/jmhodges/clock" packages = ["."] revision = "880ee4c335489bc78d01e4d0a254ae880734bc15" version = "v1.1" -[[projects]] - branch = "master" - name = "github.com/jmoiron/sqlx" - packages = [".","reflectx"] - revision = "d9bd385d68c068f1fabb5057e3dedcbcbb039d0f" - -[[projects]] - branch = "master" - name = "github.com/kisielk/sqlstruct" - packages = ["."] - revision = "648daed35d49dac24a4bff253b190a80da3ab6a5" - -[[projects]] - branch = "master" - name = "github.com/lib/pq" - packages = [".","oid"] - revision = "e42267488fe361b9dc034be7a6bffef5b195bceb" - -[[projects]] - branch = "master" - name = "github.com/looplab/fsm" - packages = ["."] - revision = "bcc3636384ce80a109a6039432b30ff5b82476ee" - [[projects]] name = "github.com/magiconair/properties" packages = ["."] revision = "be5ece7dd465ab0765a9682137865547526d1dfb" version = "v1.7.3" -[[projects]] - name = "github.com/mattn/go-sqlite3" - packages = ["."] - revision = "ca5e3819723d8eeaf170ad510e7da1d6d2e94a08" - version = "v1.2.0" - [[projects]] name = "github.com/matttproud/golang_protobuf_extensions" packages = ["pbutil"] @@ -342,7 +300,7 @@ [[projects]] branch = "master" name = "golang.org/x/crypto" - packages = ["bcrypt","blowfish","ocsp","pkcs12","pkcs12/internal/rc2","sha3","ssh/terminal"] + packages = ["ocsp","pkcs12","pkcs12/internal/rc2","sha3","ssh/terminal"] revision = "eb71ad9bd329b5ac0fd0148dd99bd62e8be8e035" [[projects]] @@ -381,18 +339,6 @@ revision = "b3ddf786825de56a4178401b7e174ee332173b66" version = "v1.5.2" -[[projects]] - name = "gopkg.in/asn1-ber.v1" - packages = ["."] - revision = "379148ca0225df7a432012b8df0355c2a2063ac0" - version = "v1.2" - -[[projects]] - name = "gopkg.in/ldap.v2" - packages = ["."] - revision = "8168ee085ee43257585e50c6441aadf54ecb2c9f" - version = "v2.5.0" - [[projects]] branch = "v2" name = "gopkg.in/yaml.v2" @@ -402,6 +348,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "6d5dc49c0186edb6b06aa171e68709aade340b57cc3e6cae330c6eebbad81c2a" + inputs-digest = "fe7a9899d1cbbb9cbd256ee66db7421b5207e34241681202cc78e740ff33547a" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 151e056944..4d61846a7b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -8,10 +8,6 @@ name = "github.com/hyperledger/fabric" branch = "master" -[[constraint]] - name = "github.com/hyperledger/fabric-ca" - version = "~1.0.0" - [[constraint]] name = "github.com/golang/mock" version = "^1.0.0" diff --git a/def/fabapi/pkgfactory.go b/def/fabapi/pkgfactory.go index f582471c02..4f9aa4c205 100644 --- a/def/fabapi/pkgfactory.go +++ b/def/fabapi/pkgfactory.go @@ -10,10 +10,10 @@ import ( "fmt" "io/ioutil" - fabricCaUtil "github.com/hyperledger/fabric-ca/util" config "github.com/hyperledger/fabric-sdk-go/api/apiconfig" fabca "github.com/hyperledger/fabric-sdk-go/api/apifabca" fab "github.com/hyperledger/fabric-sdk-go/api/apifabclient" + fabricCaUtil "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/util" configImpl "github.com/hyperledger/fabric-sdk-go/pkg/config" fabricCAClient "github.com/hyperledger/fabric-sdk-go/pkg/fabric-ca-client" clientImpl "github.com/hyperledger/fabric-sdk-go/pkg/fabric-client" diff --git a/internal/github.com/hyperledger/fabric-ca/api/client.go b/internal/github.com/hyperledger/fabric-ca/api/client.go new file mode 100644 index 0000000000..f5e135e991 --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/api/client.go @@ -0,0 +1,153 @@ +/* +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 api + +import ( + "time" + + "github.com/cloudflare/cfssl/csr" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/lib/tcert" +) + +// RegistrationRequest for a new identity +type RegistrationRequest struct { + // Name is the unique name of the identity + Name string `json:"id" help:"Unique name of the identity"` + // Type of identity being registered (e.g. "peer, app, user") + Type string `json:"type" help:"Type of identity being registered (e.g. 'peer, app, user')"` + // Secret is an optional password. If not specified, + // a random secret is generated. In both cases, the secret + // is returned in the RegistrationResponse. + Secret string `json:"secret,omitempty" help:"The enrollment secret for the identity being registered"` + // MaxEnrollments is the maximum number of times the secret can + // be reused to enroll. + MaxEnrollments int `json:"max_enrollments,omitempty" def:"-1" help:"The maximum number of times the secret can be reused to enroll."` + // is returned in the response. + // The identity's affiliation. + // For example, an affiliation of "org1.department1" associates the identity with "department1" in "org1". + Affiliation string `json:"affiliation" help:"The identity's affiliation"` + // Attributes associated with this identity + Attributes []Attribute `json:"attrs,omitempty"` + // CAName is the name of the CA to connect to + CAName string `json:"caname,omitempty" skip:"true"` +} + +// RegistrationResponse is a registration response +type RegistrationResponse struct { + // The secret returned from a successful registration response + Secret string `json:"secret"` +} + +// EnrollmentRequest is a request to enroll an identity +type EnrollmentRequest struct { + // The identity name to enroll + Name string `json:"name" skip:"true"` + // The secret returned via Register + Secret string `json:"secret,omitempty" skip:"true"` + // Profile is the name of the signing profile to use in issuing the certificate + Profile string `json:"profile,omitempty" help:"Name of the signing profile to use in issuing the certificate"` + // Label is the label to use in HSM operations + Label string `json:"label,omitempty" help:"Label to use in HSM operations"` + // CSR is Certificate Signing Request info + CSR *CSRInfo `json:"csr,omitempty" help:"Certificate Signing Request info"` + // CAName is the name of the CA to connect to + CAName string `json:"caname,omitempty" skip:"true"` +} + +// ReenrollmentRequest is a request to reenroll an identity. +// This is useful to renew a certificate before it has expired. +type ReenrollmentRequest struct { + // Profile is the name of the signing profile to use in issuing the certificate + Profile string `json:"profile,omitempty"` + // Label is the label to use in HSM operations + Label string `json:"label,omitempty"` + // CSR is Certificate Signing Request info + CSR *CSRInfo `json:"csr,omitempty"` + // CAName is the name of the CA to connect to + CAName string `json:"caname,omitempty" skip:"true"` +} + +// RevocationRequest is a revocation request for a single certificate or all certificates +// associated with an identity. +// To revoke a single certificate, both the Serial and AKI fields must be set; +// otherwise, to revoke all certificates and the identity associated with an enrollment ID, +// the Name field must be set to an existing enrollment ID. +// A RevocationRequest can only be performed by a user with the "hf.Revoker" attribute. +type RevocationRequest struct { + // Name of the identity whose certificates should be revoked + // If this field is omitted, then Serial and AKI must be specified. + Name string `json:"id,omitempty" opt:"e" help:"Identity whose certificates should be revoked"` + // Serial number of the certificate to be revoked + // If this is omitted, then Name must be specified + Serial string `json:"serial,omitempty" opt:"s" help:"Serial number of the certificate to be revoked"` + // AKI (Authority Key Identifier) of the certificate to be revoked + AKI string `json:"aki,omitempty" opt:"a" help:"AKI (Authority Key Identifier) of the certificate to be revoked"` + // Reason is the reason for revocation. See https://godoc.org/golang.org/x/crypto/ocsp for + // valid values. The default value is 0 (ocsp.Unspecified). + Reason string `json:"reason,omitempty" opt:"r" help:"Reason for revocation"` + // CAName is the name of the CA to connect to + CAName string `json:"caname,omitempty" skip:"true"` +} + +// GetTCertBatchRequest is input provided to identity.GetTCertBatch +type GetTCertBatchRequest struct { + // Number of TCerts in the batch. + Count int `json:"count"` + // The attribute names whose names and values are to be sealed in the issued TCerts. + AttrNames []string `json:"attr_names,omitempty"` + // EncryptAttrs denotes whether to encrypt attribute values or not. + // When set to true, each issued TCert in the batch will contain encrypted attribute values. + EncryptAttrs bool `json:"encrypt_attrs,omitempty"` + // Certificate Validity Period. If specified, the value used + // is the minimum of this value and the configured validity period + // of the TCert manager. + ValidityPeriod time.Duration `json:"validity_period,omitempty"` + // The pre-key to be used for key derivation. + PreKey string `json:"prekey"` + // DisableKeyDerivation if true disables key derivation so that a TCert is not + // cryptographically related to an ECert. This may be necessary when using an + // HSM which does not support the TCert's key derivation function. + DisableKeyDerivation bool `json:"disable_kdf,omitempty"` + // CAName is the name of the CA to connect to + CAName string `json:"caname,omitempty" skip:"true"` +} + +// GetTCertBatchResponse is the return value of identity.GetTCertBatch +type GetTCertBatchResponse struct { + tcert.GetBatchResponse +} + +// GetCAInfoRequest is request to get generic CA information +type GetCAInfoRequest struct { + CAName string `json:"caname,omitempty" skip:"true"` +} + +// CSRInfo is Certificate Signing Request information +type CSRInfo struct { + CN string `json:"CN"` + Names []csr.Name `json:"names,omitempty"` + Hosts []string `json:"hosts,omitempty"` + KeyRequest *csr.BasicKeyRequest `json:"key,omitempty"` + CA *csr.CAConfig `json:"ca,omitempty"` + SerialNumber string `json:"serial_number,omitempty"` +} + +// Attribute is a name and value pair +type Attribute struct { + Name string `json:"name"` + Value string `json:"value"` +} diff --git a/internal/github.com/hyperledger/fabric-ca/api/net.go b/internal/github.com/hyperledger/fabric-ca/api/net.go new file mode 100644 index 0000000000..b26238fee4 --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/api/net.go @@ -0,0 +1,85 @@ +/* +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 api + +import ( + "github.com/cloudflare/cfssl/signer" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/lib/tcert" +) + +/* + * This file contains the structure definitions for the request + * and responses which flow over the network between a fabric-ca client + * and the fabric-ca server. + */ + +// RegistrationRequestNet is the registration request for a new identity +type RegistrationRequestNet struct { + RegistrationRequest +} + +// RegistrationResponseNet is a registration response +type RegistrationResponseNet struct { + RegistrationResponse +} + +// EnrollmentRequestNet is a request to enroll an identity +type EnrollmentRequestNet struct { + signer.SignRequest + CAName string +} + +// ReenrollmentRequestNet is a request to reenroll an identity. +// This is useful to renew a certificate before it has expired. +type ReenrollmentRequestNet struct { + signer.SignRequest + CAName string +} + +// RevocationRequestNet is a revocation request which flows over the network +// to the fabric-ca server. +// To revoke a single certificate, both the Serial and AKI fields must be set; +// otherwise, to revoke all certificates and the identity associated with an enrollment ID, +// the Name field must be set to an existing enrollment ID. +// A RevocationRequest can only be performed by a user with the "hf.Revoker" attribute. +type RevocationRequestNet struct { + RevocationRequest +} + +// GetTCertBatchRequestNet is a network request for a batch of transaction certificates +type GetTCertBatchRequestNet struct { + GetTCertBatchRequest + // KeySigs is an optional array of public keys and corresponding signatures. + // If not set, the server generates it's own keys based on a key derivation function + // which cryptographically relates the TCerts to an ECert. + KeySigs []KeySig `json:"key_sigs,omitempty"` +} + +// GetTCertBatchResponseNet is the network response for a batch of transaction certificates +type GetTCertBatchResponseNet struct { + tcert.GetBatchResponse +} + +// KeySig is a public key, signature, and signature algorithm tuple +type KeySig struct { + // Key is a public key + Key []byte `json:"key"` + // Sig is a signature over the PublicKey + Sig []byte `json:"sig"` + // Alg is the signature algorithm + Alg string `json:"alg"` +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/client.go b/internal/github.com/hyperledger/fabric-ca/lib/client.go new file mode 100644 index 0000000000..76b655f6a6 --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/client.go @@ -0,0 +1,520 @@ +/* +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 lib + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "path" + "strconv" + "strings" + + cfsslapi "github.com/cloudflare/cfssl/api" + "github.com/cloudflare/cfssl/csr" + "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/api" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/lib/tls" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/util" + "github.com/hyperledger/fabric/bccsp" + "github.com/mitchellh/mapstructure" +) + +// Client is the fabric-ca client object +type Client struct { + // The client's home directory + HomeDir string `json:"homeDir,omitempty"` + // The client's configuration + Config *ClientConfig + // Denotes if the client object is already initialized + initialized bool + // File and directory paths + keyFile, certFile, caCertsDir string + // The crypto service provider (BCCSP) + csp bccsp.BCCSP +} + +// Init initializes the client +func (c *Client) Init() error { + if !c.initialized { + cfg := c.Config + log.Debugf("Initializing client with config: %+v", cfg) + if cfg.MSPDir == "" { + cfg.MSPDir = "msp" + } + mspDir, err := util.MakeFileAbs(cfg.MSPDir, c.HomeDir) + if err != nil { + return err + } + cfg.MSPDir = mspDir + // Key directory and file + keyDir := path.Join(mspDir, "keystore") + err = os.MkdirAll(keyDir, 0700) + if err != nil { + return fmt.Errorf("Failed to create keystore directory: %s", err) + } + c.keyFile = path.Join(keyDir, "key.pem") + // Cert directory and file + certDir := path.Join(mspDir, "signcerts") + err = os.MkdirAll(certDir, 0755) + if err != nil { + return fmt.Errorf("Failed to create signcerts directory: %s", err) + } + c.certFile = path.Join(certDir, "cert.pem") + // CA certs directory + c.caCertsDir = path.Join(mspDir, "cacerts") + err = os.MkdirAll(c.caCertsDir, 0755) + if err != nil { + return fmt.Errorf("Failed to create cacerts directory: %s", err) + } + // Initialize BCCSP (the crypto layer) + c.csp, err = util.InitBCCSP(&cfg.CSP, mspDir, c.HomeDir) + if err != nil { + return err + } + // Successfully initialized the client + c.initialized = true + } + return nil +} + +// GetServerInfoResponse is the response from the GetServerInfo call +type GetServerInfoResponse struct { + // CAName is the name of the CA + CAName string + // CAChain is the PEM-encoded bytes of the fabric-ca-server's CA chain. + // The 1st element of the chain is the root CA cert + CAChain []byte +} + +// GetCAInfo returns generic CA information +func (c *Client) GetCAInfo(req *api.GetCAInfoRequest) (*GetServerInfoResponse, error) { + err := c.Init() + if err != nil { + return nil, err + } + body, err := util.Marshal(req, "GetCAInfo") + if err != nil { + return nil, err + } + cainforeq, err := c.newPost("cainfo", body) + if err != nil { + return nil, err + } + netSI := &serverInfoResponseNet{} + err = c.SendReq(cainforeq, netSI) + if err != nil { + return nil, err + } + localSI := &GetServerInfoResponse{} + err = c.net2LocalServerInfo(netSI, localSI) + if err != nil { + return nil, err + } + return localSI, nil +} + +// Convert from network to local server information +func (c *Client) net2LocalServerInfo(net *serverInfoResponseNet, local *GetServerInfoResponse) error { + caChain, err := util.B64Decode(net.CAChain) + if err != nil { + return err + } + local.CAName = net.CAName + local.CAChain = caChain + return nil +} + +// EnrollmentResponse is the response from Client.Enroll and Identity.Reenroll +type EnrollmentResponse struct { + Identity *Identity + ServerInfo GetServerInfoResponse +} + +// Enroll enrolls a new identity +// @param req The enrollment request +func (c *Client) Enroll(req *api.EnrollmentRequest) (*EnrollmentResponse, error) { + log.Debugf("Enrolling %+v", req) + + err := c.Init() + if err != nil { + return nil, err + } + + // Generate the CSR + csrPEM, key, err := c.GenCSR(req.CSR, req.Name) + if err != nil { + return nil, fmt.Errorf("Failure generating CSR: %s", err) + } + + reqNet := &api.EnrollmentRequestNet{ + CAName: req.CAName, + } + + if req.CSR != nil { + reqNet.SignRequest.Hosts = req.CSR.Hosts + } + reqNet.SignRequest.Request = string(csrPEM) + reqNet.SignRequest.Profile = req.Profile + reqNet.SignRequest.Label = req.Label + + body, err := util.Marshal(reqNet, "SignRequest") + if err != nil { + return nil, err + } + + // Send the CSR to the fabric-ca server with basic auth header + post, err := c.newPost("enroll", body) + if err != nil { + return nil, err + } + post.SetBasicAuth(req.Name, req.Secret) + var result enrollmentResponseNet + err = c.SendReq(post, &result) + if err != nil { + return nil, err + } + + // Create the enrollment response + return c.newEnrollmentResponse(&result, req.Name, key) +} + +// newEnrollmentResponse creates a client enrollment response from a network response +// @param result The result from server +// @param id Name of identity being enrolled or reenrolled +// @param key The private key which was used to sign the request +func (c *Client) newEnrollmentResponse(result *enrollmentResponseNet, id string, key bccsp.Key) (*EnrollmentResponse, error) { + log.Debugf("newEnrollmentResponse %s", id) + certByte, err := util.B64Decode(result.Cert) + if err != nil { + return nil, fmt.Errorf("Invalid response format from server: %s", err) + } + resp := &EnrollmentResponse{ + Identity: newIdentity(c, id, key, certByte), + } + err = c.net2LocalServerInfo(&result.ServerInfo, &resp.ServerInfo) + if err != nil { + return nil, err + } + return resp, nil +} + +// GenCSR generates a CSR (Certificate Signing Request) +func (c *Client) GenCSR(req *api.CSRInfo, id string) ([]byte, bccsp.Key, error) { + log.Debugf("GenCSR %+v", req) + + err := c.Init() + if err != nil { + return nil, nil, err + } + + cr := c.newCertificateRequest(req) + cr.CN = id + + if cr.KeyRequest == nil { + cr.KeyRequest = csr.NewBasicKeyRequest() + } + + key, cspSigner, err := util.BCCSPKeyRequestGenerate(cr, c.csp) + if err != nil { + log.Debugf("failed generating BCCSP key: %s", err) + return nil, nil, err + } + + csrPEM, err := csr.Generate(cspSigner, cr) + if err != nil { + log.Debugf("failed generating CSR: %s", err) + return nil, nil, err + } + + return csrPEM, key, nil +} + +// newCertificateRequest creates a certificate request which is used to generate +// a CSR (Certificate Signing Request) +func (c *Client) newCertificateRequest(req *api.CSRInfo) *csr.CertificateRequest { + cr := csr.CertificateRequest{} + if req != nil && req.Names != nil { + cr.Names = req.Names + } + if req != nil && req.Hosts != nil { + cr.Hosts = req.Hosts + } else { + // Default requested hosts are local hostname + hostname, _ := os.Hostname() + if hostname != "" { + cr.Hosts = make([]string, 1) + cr.Hosts[0] = hostname + } + } + if req != nil && req.KeyRequest != nil { + cr.KeyRequest = req.KeyRequest + } + if req != nil { + cr.CA = req.CA + cr.SerialNumber = req.SerialNumber + } + return &cr +} + +// LoadMyIdentity loads the client's identity from disk +func (c *Client) LoadMyIdentity() (*Identity, error) { + err := c.Init() + if err != nil { + return nil, err + } + return c.LoadIdentity(c.keyFile, c.certFile) +} + +// StoreMyIdentity stores my identity to disk +func (c *Client) StoreMyIdentity(cert []byte) error { + err := c.Init() + if err != nil { + return err + } + err = util.WriteFile(c.certFile, cert, 0644) + if err != nil { + return fmt.Errorf("Failed to store my certificate: %s", err) + } + log.Infof("Stored client certificate at %s", c.certFile) + return nil +} + +// LoadIdentity loads an identity from disk +func (c *Client) LoadIdentity(keyFile, certFile string) (*Identity, error) { + log.Debug("Loading identity: keyFile=%s, certFile=%s", keyFile, certFile) + err := c.Init() + if err != nil { + return nil, err + } + cert, err := util.ReadFile(certFile) + if err != nil { + log.Debugf("No cert found at %s", certFile) + return nil, err + } + key, _, _, err := util.GetSignerFromCertFile(certFile, c.csp) + if err != nil { + // Fallback: attempt to read out of keyFile and import + log.Debugf("No key found in BCCSP keystore, attempting fallback") + key, err = util.ImportBCCSPKeyFromPEM(keyFile, c.csp, true) + if err != nil { + return nil, fmt.Errorf("Could not find the private key in BCCSP keystore nor in keyfile %s: %s", keyFile, err) + } + } + return c.NewIdentity(key, cert) +} + +// NewIdentity creates a new identity +func (c *Client) NewIdentity(key bccsp.Key, cert []byte) (*Identity, error) { + name, err := util.GetEnrollmentIDFromPEM(cert) + if err != nil { + return nil, err + } + return newIdentity(c, name, key, cert), nil +} + +// LoadCSRInfo reads CSR (Certificate Signing Request) from a file +// @parameter path The path to the file contains CSR info in JSON format +func (c *Client) LoadCSRInfo(path string) (*api.CSRInfo, error) { + csrJSON, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + var csrInfo api.CSRInfo + err = util.Unmarshal(csrJSON, &csrInfo, "LoadCSRInfo") + if err != nil { + return nil, err + } + return &csrInfo, nil +} + +// GetCertFilePath returns the path to the certificate file for this client +func (c *Client) GetCertFilePath() string { + return c.certFile +} + +// NewGet create a new GET request +func (c *Client) newGet(endpoint string) (*http.Request, error) { + curl, err := c.getURL(endpoint) + if err != nil { + return nil, err + } + req, err := http.NewRequest("GET", curl, bytes.NewReader([]byte{})) + if err != nil { + return nil, fmt.Errorf("Failed creating GET request for %s: %s", curl, err) + } + return req, nil +} + +// NewPost create a new post request +func (c *Client) newPost(endpoint string, reqBody []byte) (*http.Request, error) { + curl, err := c.getURL(endpoint) + if err != nil { + return nil, err + } + req, err := http.NewRequest("POST", curl, bytes.NewReader(reqBody)) + if err != nil { + return nil, fmt.Errorf("Failed posting to %s: %s", curl, err) + } + return req, nil +} + +// SendReq sends a request to the fabric-ca-server and fills in the result +func (c *Client) SendReq(req *http.Request, result interface{}) (err error) { + + reqStr := util.HTTPRequestToString(req) + log.Debugf("Sending request\n%s", reqStr) + + err = c.Init() + if err != nil { + return err + } + + var tr = new(http.Transport) + + if c.Config.TLS.Enabled { + log.Info("TLS Enabled") + + err = tls.AbsTLSClient(&c.Config.TLS, c.HomeDir) + if err != nil { + return err + } + + tlsConfig, err2 := tls.GetClientTLSConfig(&c.Config.TLS, c.csp) + if err2 != nil { + return fmt.Errorf("Failed to get client TLS config: %s", err2) + } + + tr.TLSClientConfig = tlsConfig + } + + httpClient := &http.Client{Transport: tr} + resp, err := httpClient.Do(req) + if err != nil { + return fmt.Errorf("POST failure [%s]; not sending\n%s", err, reqStr) + } + var respBody []byte + if resp.Body != nil { + respBody, err = ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return fmt.Errorf("Failed to read response [%s] of request:\n%s", err, reqStr) + } + log.Debugf("Received response\n%s", util.HTTPResponseToString(resp)) + } + var body *cfsslapi.Response + if respBody != nil && len(respBody) > 0 { + body = new(cfsslapi.Response) + err = json.Unmarshal(respBody, body) + if err != nil { + return fmt.Errorf("Failed to parse response: %s\n%s", err, respBody) + } + if len(body.Errors) > 0 { + msg := body.Errors[0].Message + return fmt.Errorf("Error response from server was: %s", msg) + } + } + scode := resp.StatusCode + if scode >= 400 { + return fmt.Errorf("Failed with server status code %d for request:\n%s", scode, reqStr) + } + if body == nil { + return fmt.Errorf("Empty response body:\n%s", reqStr) + } + if !body.Success { + return fmt.Errorf("Server returned failure for request:\n%s", reqStr) + } + log.Debugf("Response body result: %+v", body.Result) + if result != nil { + return mapstructure.Decode(body.Result, result) + } + return nil +} + +func (c *Client) getURL(endpoint string) (string, error) { + nurl, err := NormalizeURL(c.Config.URL) + if err != nil { + return "", err + } + rtn := fmt.Sprintf("%s/%s", nurl, endpoint) + return rtn, nil +} + +// CheckEnrollment returns an error if this client is not enrolled +func (c *Client) CheckEnrollment() error { + err := c.Init() + if err != nil { + return err + } + keyFileExists := util.FileExists(c.keyFile) + certFileExists := util.FileExists(c.certFile) + if keyFileExists && certFileExists { + return nil + } + // If key file does not exist, but certFile does, key file is probably + // stored by bccsp, so check to see if this is the case + if certFileExists { + _, _, _, err := util.GetSignerFromCertFile(c.certFile, c.csp) + if err == nil { + // Yes, the key is stored by BCCSP + return nil + } + } + return errors.New("Enrollment information does not exist. Please execute enroll command first. Example: fabric-ca-client enroll -u http://user:userpw@serverAddr:serverPort") +} + +// NormalizeURL normalizes a URL (from cfssl) +func NormalizeURL(addr string) (*url.URL, error) { + addr = strings.TrimSpace(addr) + u, err := url.Parse(addr) + if err != nil { + return nil, err + } + if u.Opaque != "" { + u.Host = net.JoinHostPort(u.Scheme, u.Opaque) + u.Opaque = "" + } else if u.Path != "" && !strings.Contains(u.Path, ":") { + u.Host = net.JoinHostPort(u.Path, util.GetServerPort()) + u.Path = "" + } else if u.Scheme == "" { + u.Host = u.Path + u.Path = "" + } + if u.Scheme != "https" { + u.Scheme = "http" + } + _, port, err := net.SplitHostPort(u.Host) + if err != nil { + _, port, err = net.SplitHostPort(u.Host + ":" + util.GetServerPort()) + if err != nil { + return nil, err + } + } + if port != "" { + _, err = strconv.Atoi(port) + if err != nil { + return nil, err + } + } + return u, nil +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/clientconfig.go b/internal/github.com/hyperledger/fabric-ca/lib/clientconfig.go new file mode 100644 index 0000000000..1c07550553 --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/clientconfig.go @@ -0,0 +1,72 @@ +/* +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. +*/ + +package lib + +import ( + "fmt" + "net/url" + + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/api" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/lib/tls" + "github.com/hyperledger/fabric/bccsp/factory" +) + +// ClientConfig is the fabric-ca client's config +type ClientConfig struct { + Debug bool `def:"false" opt:"d" help:"Enable debug level logging"` + URL string `def:"http://localhost:7054" opt:"u" help:"URL of fabric-ca-server"` + MSPDir string `def:"msp" opt:"M" help:"Membership Service Provider directory"` + TLS tls.ClientTLSConfig + Enrollment api.EnrollmentRequest + CSR api.CSRInfo + ID api.RegistrationRequest + Revoke api.RevocationRequest + CAInfo api.GetCAInfoRequest + CAName string `help:"Name of CA"` + CSP *factory.FactoryOpts `mapstructure:"bccsp"` +} + +// Enroll a client given the server's URL and the client's home directory. +// The URL may be of the form: http://user:pass@host:port where user and pass +// are the enrollment ID and secret, respectively. +func (c *ClientConfig) Enroll(rawurl, home string) (*EnrollmentResponse, error) { + purl, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + if purl.User != nil { + name := purl.User.Username() + secret, _ := purl.User.Password() + c.Enrollment.Name = name + c.Enrollment.Secret = secret + purl.User = nil + } + if c.Enrollment.Name == "" { + expecting := fmt.Sprintf( + "%s://:@%s", + purl.Scheme, purl.Host) + return nil, fmt.Errorf( + "The URL of the fabric CA server is missing the enrollment ID and secret;"+ + " found '%s' but expecting '%s'", rawurl, expecting) + } + c.Enrollment.CAName = c.CAName + c.URL = purl.String() + c.TLS.Enabled = purl.Scheme == "https" + c.Enrollment.CSR = &c.CSR + client := &Client{HomeDir: home, Config: c} + return client.Enroll(&c.Enrollment) +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/identity.go b/internal/github.com/hyperledger/fabric-ca/lib/identity.go new file mode 100644 index 0000000000..e8a6e2285c --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/identity.go @@ -0,0 +1,221 @@ +/* +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 lib + +import ( + "errors" + "fmt" + "net/http" + + "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/api" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/util" + "github.com/hyperledger/fabric/bccsp" +) + +func newIdentity(client *Client, name string, key bccsp.Key, cert []byte) *Identity { + id := new(Identity) + id.name = name + id.ecert = newSigner(key, cert, id) + id.client = client + if client != nil { + id.CSP = client.csp + } else { + id.CSP = util.GetDefaultBCCSP() + } + return id +} + +// Identity is fabric-ca's implementation of an identity +type Identity struct { + name string + ecert *Signer + client *Client + CSP bccsp.BCCSP +} + +// GetName returns the identity name +func (i *Identity) GetName() string { + return i.name +} + +// GetClient returns the client associated with this identity +func (i *Identity) GetClient() *Client { + return i.client +} + +// GetECert returns the enrollment certificate signer for this identity +func (i *Identity) GetECert() *Signer { + return i.ecert +} + +// GetTCertBatch returns a batch of TCerts for this identity +func (i *Identity) GetTCertBatch(req *api.GetTCertBatchRequest) ([]*Signer, error) { + reqBody, err := util.Marshal(req, "GetTCertBatchRequest") + if err != nil { + return nil, err + } + err = i.Post("tcert", reqBody, nil) + if err != nil { + return nil, err + } + // Ignore the contents of the response for now. They will be processed in the future when we need to + // support the Go SDK. We currently have Node and Java SDKs which process this and they are the + // priority. + return nil, nil +} + +// Register registers a new identity +// @param req The registration request +func (i *Identity) Register(req *api.RegistrationRequest) (rr *api.RegistrationResponse, err error) { + log.Debugf("Register %+v", req) + if req.Name == "" { + return nil, errors.New("Register was called without a Name set") + } + if req.Affiliation == "" { + return nil, errors.New("Registration request does not have an affiliation") + } + + reqBody, err := util.Marshal(req, "RegistrationRequest") + if err != nil { + return nil, err + } + + // Send a post to the "register" endpoint with req as body + resp := &api.RegistrationResponse{} + err = i.Post("register", reqBody, resp) + if err != nil { + return nil, err + } + + log.Debug("The register request completely successfully") + return resp, nil +} + +// RegisterAndEnroll registers and enrolls an identity and returns the identity +func (i *Identity) RegisterAndEnroll(req *api.RegistrationRequest) (*Identity, error) { + if i.client == nil { + return nil, errors.New("No client is associated with this identity") + } + rresp, err := i.Register(req) + if err != nil { + return nil, fmt.Errorf("Failed to register %s: %s", req.Name, err) + } + eresp, err := i.client.Enroll(&api.EnrollmentRequest{ + Name: req.Name, + Secret: rresp.Secret, + }) + if err != nil { + return nil, fmt.Errorf("Failed to enroll %s: %s", req.Name, err) + } + return eresp.Identity, nil +} + +// Reenroll reenrolls an existing Identity and returns a new Identity +// @param req The reenrollment request +func (i *Identity) Reenroll(req *api.ReenrollmentRequest) (*EnrollmentResponse, error) { + log.Debugf("Reenrolling %s", req) + + csrPEM, key, err := i.client.GenCSR(req.CSR, i.GetName()) + if err != nil { + return nil, err + } + + reqNet := &api.ReenrollmentRequestNet{ + CAName: req.CAName, + } + + // Get the body of the request + if req.CSR != nil { + reqNet.SignRequest.Hosts = req.CSR.Hosts + } + reqNet.SignRequest.Request = string(csrPEM) + reqNet.SignRequest.Profile = req.Profile + reqNet.SignRequest.Label = req.Label + + body, err := util.Marshal(reqNet, "SignRequest") + if err != nil { + return nil, err + } + var result enrollmentResponseNet + err = i.Post("reenroll", body, &result) + if err != nil { + return nil, err + } + return i.client.newEnrollmentResponse(&result, i.GetName(), key) +} + +// Revoke the identity associated with 'id' +func (i *Identity) Revoke(req *api.RevocationRequest) error { + log.Debugf("Entering identity.Revoke %+v", req) + reqBody, err := util.Marshal(req, "RevocationRequest") + if err != nil { + return err + } + err = i.Post("revoke", reqBody, nil) + if err != nil { + return err + } + log.Debugf("Successfully revoked %+v", req) + return nil +} + +// RevokeSelf revokes the current identity and all certificates +func (i *Identity) RevokeSelf() error { + name := i.GetName() + log.Debugf("RevokeSelf %s", name) + req := &api.RevocationRequest{ + Name: name, + } + return i.Revoke(req) +} + +// Store writes my identity info to disk +func (i *Identity) Store() error { + if i.client == nil { + return fmt.Errorf("An identity with no client may not be stored") + } + return i.client.StoreMyIdentity(i.ecert.cert) +} + +// Post sends arbtrary request body (reqBody) to an endpoint. +// This adds an authorization header which contains the signature +// of this identity over the body and non-signature part of the authorization header. +// The return value is the body of the response. +func (i *Identity) Post(endpoint string, reqBody []byte, result interface{}) error { + req, err := i.client.newPost(endpoint, reqBody) + if err != nil { + return err + } + err = i.addTokenAuthHdr(req, reqBody) + if err != nil { + return err + } + return i.client.SendReq(req, result) +} + +func (i *Identity) addTokenAuthHdr(req *http.Request, body []byte) error { + log.Debug("adding token-based authorization header") + cert := i.ecert.cert + key := i.ecert.key + token, err := util.CreateToken(i.CSP, cert, key, body) + if err != nil { + return fmt.Errorf("Failed to add token authorization header: %s", err) + } + req.Header.Set("authorization", token) + return nil +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/serverstruct.go b/internal/github.com/hyperledger/fabric-ca/lib/serverstruct.go new file mode 100644 index 0000000000..3bc2be59b9 --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/serverstruct.go @@ -0,0 +1,30 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package lib + +// CAConfig ... +type CAConfig struct { +} + +// ServerConfig ... +type ServerConfig struct { + CAcfg CAConfig `skip:"true"` +} + +type serverInfoResponseNet struct { + // CAName is a unique name associated with fabric-ca-server's CA + CAName string + // Base64 encoding of PEM-encoded certificate chain + CAChain string +} + +type enrollmentResponseNet struct { + // Base64 encoded PEM-encoded ECert + Cert string + // The server information + ServerInfo serverInfoResponseNet +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/signer.go b/internal/github.com/hyperledger/fabric-ca/lib/signer.go new file mode 100644 index 0000000000..4eb60524de --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/signer.go @@ -0,0 +1,66 @@ +/* +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 lib + +import ( + "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/api" + "github.com/hyperledger/fabric/bccsp" +) + +func newSigner(key bccsp.Key, cert []byte, id *Identity) *Signer { + return &Signer{ + key: key, + cert: cert, + id: id, + client: id.client, + } +} + +// Signer represents a signer +// Each identity may have multiple signers, currently one ecert and multiple tcerts +type Signer struct { + name string + key bccsp.Key + cert []byte + id *Identity + client *Client +} + +// Key returns the key bytes of this signer +func (s *Signer) Key() bccsp.Key { + return s.key +} + +// Cert returns the cert bytes of this signer +func (s *Signer) Cert() []byte { + return s.cert +} + +// RevokeSelf revokes only the certificate associated with this signer +func (s *Signer) RevokeSelf() error { + log.Debugf("RevokeSelf %s", s.name) + serial, aki, err := GetCertID(s.cert) + if err != nil { + return err + } + req := &api.RevocationRequest{ + Serial: serial, + AKI: aki, + } + return s.id.Revoke(req) +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/spi/affiliation.go b/internal/github.com/hyperledger/fabric-ca/lib/spi/affiliation.go new file mode 100644 index 0000000000..ed6bb73233 --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/spi/affiliation.go @@ -0,0 +1,39 @@ +/* +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 spi + +// AffiliationImpl defines a group name and its parent +type AffiliationImpl struct { + Name string `db:"name"` + Prekey string `db:"prekey"` +} + +// Affiliation is the API for a user's affiliation +type Affiliation interface { + GetName() string + GetPrekey() string +} + +// GetName returns the name of the affiliation +func (g *AffiliationImpl) GetName() string { + return g.Name +} + +// GetPrekey returns the prekey of the affiliation +func (g *AffiliationImpl) GetPrekey() string { + return g.Prekey +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/spi/userregistry.go b/internal/github.com/hyperledger/fabric-ca/lib/spi/userregistry.go new file mode 100644 index 0000000000..b082659c0b --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/spi/userregistry.go @@ -0,0 +1,58 @@ +/* +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. +*/ + +/* + * This file defines the user registry interface used by the fabric-ca server. + */ + +package spi + +import "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/api" + +// UserInfo contains information about a user +type UserInfo struct { + Name string + Pass string + Type string + Affiliation string + Attributes []api.Attribute + State int + MaxEnrollments int +} + +// User is the SPI for a user +type User interface { + // Returns the enrollment ID of the user + GetName() string + // Login the user with a password + Login(password string, caMaxEnrollment int) error + // Get the complete path for the user's affiliation. + GetAffiliationPath() []string + // GetAttribute returns the value for an attribute name + GetAttribute(name string) string +} + +// UserRegistry is the API for retreiving users and groups +type UserRegistry interface { + GetUser(id string, attrs []string) (User, error) + GetUserInfo(id string) (UserInfo, error) + InsertUser(user UserInfo) error + UpdateUser(user UserInfo) error + DeleteUser(id string) error + GetAffiliation(name string) (Affiliation, error) + InsertAffiliation(name string, prekey string) error + DeleteAffiliation(name string) error +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/tcert/api.go b/internal/github.com/hyperledger/fabric-ca/lib/tcert/api.go new file mode 100644 index 0000000000..a30069ca64 --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/tcert/api.go @@ -0,0 +1,68 @@ +/* +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 tcert + +import ( + "math/big" + "time" +) + +/* + * This file contains definitions of the input and output to the TCert + * library APIs. + */ + +// GetBatchRequest defines input to the GetBatch API +type GetBatchRequest struct { + // Number of TCerts in the batch. + Count int `json:"count"` + // If PublicKeys is non nil, generates a TCert for each public key; + // in this case, the 'Count' field is ignored and the number of TCerts + // generated matches the number of public keys in the array. + PublicKeys [][]byte `json:"public_keys,omitempty"` + // The attribute name and values that are to be inserted in the issued TCerts. + Attrs []Attribute `json:"attrs,omitempty"` + // EncryptAttrs denotes whether to encrypt attribute values or not. + // When set to true, each issued TCert in the batch will contain encrypted attribute values. + EncryptAttrs bool `json:"encrypt_attrs,omitempty"` + // Certificate Validity Period. If specified, the value used + // is the minimum of this value and the configured validity period + // of the TCert manager. + ValidityPeriod time.Duration `json:"validity_period,omitempty"` + // The pre-key to be used for key derivation. + PreKey string `json:"prekey"` +} + +// GetBatchResponse is the response from the GetBatch API +type GetBatchResponse struct { + ID *big.Int `json:"id"` + TS time.Time `json:"ts"` + Key []byte `json:"key"` + TCerts []TCert `json:"tcerts"` +} + +// TCert encapsulates a signed transaction certificate and optionally a map of keys +type TCert struct { + Cert []byte `json:"cert"` + Keys map[string][]byte `json:"keys,omitempty"` //base64 encoded string as value +} + +// Attribute is a single attribute name and value +type Attribute struct { + Name string `json:"name"` + Value string `json:"value"` +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/tcert/keytree.go b/internal/github.com/hyperledger/fabric-ca/lib/tcert/keytree.go new file mode 100644 index 0000000000..84bf4e935b --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/tcert/keytree.go @@ -0,0 +1,94 @@ +/* +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 tcert + +import ( + "fmt" + "strings" + + "github.com/hyperledger/fabric/bccsp" +) + +/* + * A key tree is a hierarchy of derived keys with a single root key. + * Each node in the tree has a key and a name, where the key is secret + * and the name may be public. If the secret associated with a node + * is known, then the secret of each node in it's sub-tree can be derived + * if the name of the nodes are known; however, it is not possible to + * derive the keys associated with other nodes in the tree which are not + * part of this sub-tree. + * + * This data structure is useful to support releasing a secret associated + * with any node to an auditor without giving the auditor access to all + * nodes in the tree. + */ + +const ( + keyPathSep = "/" +) + +// NewKeyTree is the constructor for a key tree +func NewKeyTree(bccspMgr bccsp.BCCSP, rootKey bccsp.Key) *KeyTree { + tree := new(KeyTree) + tree.bccspMgr = bccspMgr + tree.rootKey = rootKey + tree.keys = make(map[string]bccsp.Key) + return tree +} + +// KeyTree is a tree of derived keys +type KeyTree struct { + bccspMgr bccsp.BCCSP + rootKey bccsp.Key + keys map[string]bccsp.Key +} + +// GetKey returns a key associated with a specific path in the tree. +func (m *KeyTree) GetKey(path []string) (bccsp.Key, error) { + if path == nil || len(path) == 0 { + return m.rootKey, nil + } + pathStr := strings.Join(path, keyPathSep) + key := m.keys[pathStr] + if key != nil { + return key, nil + } + parentKey, err := m.GetKey(path[0 : len(path)-1]) + if err != nil { + return nil, err + } + childName := path[len(path)-1] + key, err = m.deriveChildKey(parentKey, childName, pathStr) + if err != nil { + return nil, err + } + m.keys[pathStr] = key + return key, nil +} + +// Given a parentKey and a childName, derive the child's key +func (m *KeyTree) deriveChildKey(parentKey bccsp.Key, childName, path string) (bccsp.Key, error) { + opts := &bccsp.HMACDeriveKeyOpts{ + Temporary: true, + Arg: []byte(childName), + } + key, err := m.bccspMgr.KeyDeriv(parentKey, opts) + if err != nil { + return nil, fmt.Errorf("Failed to derive key %s: %s", path, err) + } + return key, nil +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/tcert/tcert.go b/internal/github.com/hyperledger/fabric-ca/lib/tcert/tcert.go new file mode 100644 index 0000000000..12d8b1a234 --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/tcert/tcert.go @@ -0,0 +1,320 @@ +/* +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 tcert + +import ( + "crypto/ecdsa" + "crypto/hmac" + "crypto/rand" + "crypto/sha512" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/base64" + "fmt" + "time" + + "math/big" + "strconv" + + "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/util" + "github.com/hyperledger/fabric/bccsp" + cspsigner "github.com/hyperledger/fabric/bccsp/signer" +) + +var ( + // TCertEncTCertIndex is the ASN1 object identifier of the TCert index. + TCertEncTCertIndex = asn1.ObjectIdentifier{1, 2, 3, 4, 5, 6, 7} + + // TCertEncEnrollmentID is the ASN1 object identifier of the enrollment id. + TCertEncEnrollmentID = asn1.ObjectIdentifier{1, 2, 3, 4, 5, 6, 8} + + // TCertAttributesHeaders is the ASN1 object identifier of attributes header. + TCertAttributesHeaders = asn1.ObjectIdentifier{1, 2, 3, 4, 5, 6, 9} + + // Padding for encryption. + Padding = []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255} + + // tcertSubject is the subject name placed in all generated TCerts + tcertSubject = pkix.Name{CommonName: "Fabric Transaction Certificate"} +) + +// LoadMgr is the constructor for a TCert manager given key and certificate file names +// @parameter caKeyFile is the file name for the CA's key +// @parameter caCertFile is the file name for the CA's cert +func LoadMgr(caKeyFile, caCertFile string, myCSP bccsp.BCCSP) (*Mgr, error) { + _, caKey, caCert, err := util.GetSignerFromCertFile(caCertFile, myCSP) + if err != nil && caCert == nil { + return nil, fmt.Errorf("Failed to load cert [%s]", err) + } + if err != nil { + // Fallback: attempt to read out of keyFile and import + log.Debugf("No key found in BCCSP keystore, attempting fallback") + key, err := util.ImportBCCSPKeyFromPEM(caKeyFile, myCSP, true) + if err != nil { + return nil, err + } + signer, err := cspsigner.New(myCSP, key) + if err != nil { + return nil, fmt.Errorf("Failed initializing CryptoSigner [%s]", err) + } + caKey = signer + } + + return NewMgr(caKey, caCert) +} + +// NewMgr is the constructor for a TCert manager given a key and an x509 certificate +// @parameter caKey is used for signing a certificate request +// @parameter caCert is used for extracting CA data to associate with issued certificates +func NewMgr(caKey interface{}, caCert *x509.Certificate) (*Mgr, error) { + mgr := new(Mgr) + mgr.CAKey = caKey + mgr.CACert = caCert + mgr.ValidityPeriod = time.Hour * 24 * 365 // default to 1 year + mgr.MaxAllowedBatchSize = 1000 + return mgr, nil +} + +// Mgr is the manager for the TCert library +type Mgr struct { + // CAKey is used for signing a certificate request + CAKey interface{} + // CACert is used for extracting CA data to associate with issued certificates + CACert *x509.Certificate + // ValidityPeriod is the duration that the issued certificate will be valid + // unless the user requests a shorter validity period. + // The default value is 1 year. + ValidityPeriod time.Duration + // MaxAllowedBatchSize is the maximum number of TCerts which can be requested at a time. + // The default value is 1000. + MaxAllowedBatchSize int +} + +// GetBatch gets a batch of TCerts +// @parameter req Is the TCert batch request +// @parameter ecert Is the enrollment certificate of the caller +func (tm *Mgr) GetBatch(req *GetBatchRequest, ecert *x509.Certificate) (*GetBatchResponse, error) { + + log.Debugf("GetBatch req=%+v", req) + + // Set numTCertsInBatch to the number of TCerts to get. + // If 0 are requested, retrieve the maximum allowable; + // otherwise, retrieve the number requested it not too many. + var numTCertsInBatch int + if req.Count == 0 { + numTCertsInBatch = int(tm.MaxAllowedBatchSize) + } else if req.Count <= tm.MaxAllowedBatchSize { + numTCertsInBatch = int(req.Count) + } else { + return nil, fmt.Errorf("You may not request %d TCerts; the maximum is %d", + req.Count, tm.MaxAllowedBatchSize) + } + + // Certs are valid for the min of requested and configured max + vp := tm.ValidityPeriod + if req.ValidityPeriod > 0 && req.ValidityPeriod < vp { + vp = req.ValidityPeriod + } + + // Create a template from which to create all other TCerts. + // Since a TCert is anonymous and unlinkable, do not include + template := &x509.Certificate{ + Subject: tcertSubject, + } + template.NotBefore = time.Now() + template.NotAfter = template.NotBefore.Add(vp) + template.IsCA = false + template.KeyUsage = x509.KeyUsageDigitalSignature + template.SubjectKeyId = []byte{1, 2, 3, 4} + + // Generate nonce for TCertIndex + nonce := make([]byte, 16) // 8 bytes rand, 8 bytes timestamp + rand.Reader.Read(nonce[:8]) + + pub := ecert.PublicKey.(*ecdsa.PublicKey) + + mac := hmac.New(sha512.New384, []byte(createHMACKey())) + raw, _ := x509.MarshalPKIXPublicKey(pub) + mac.Write(raw) + kdfKey := mac.Sum(nil) + + var set []TCert + + for i := 0; i < numTCertsInBatch; i++ { + tcertid, uuidError := GenerateIntUUID() + if uuidError != nil { + return nil, fmt.Errorf("Failure generating UUID: %s", uuidError) + } + // Compute TCertIndex + tidx := []byte(strconv.Itoa(2*i + 1)) + tidx = append(tidx[:], nonce[:]...) + tidx = append(tidx[:], Padding...) + + mac := hmac.New(sha512.New384, kdfKey) + mac.Write([]byte{1}) + extKey := mac.Sum(nil)[:32] + + mac = hmac.New(sha512.New384, kdfKey) + mac.Write([]byte{2}) + mac = hmac.New(sha512.New384, mac.Sum(nil)) + mac.Write(tidx) + + one := new(big.Int).SetInt64(1) + k := new(big.Int).SetBytes(mac.Sum(nil)) + k.Mod(k, new(big.Int).Sub(pub.Curve.Params().N, one)) + k.Add(k, one) + + tmpX, tmpY := pub.ScalarBaseMult(k.Bytes()) + txX, txY := pub.Curve.Add(pub.X, pub.Y, tmpX, tmpY) + txPub := ecdsa.PublicKey{Curve: pub.Curve, X: txX, Y: txY} + + // Compute encrypted TCertIndex + encryptedTidx, encryptErr := CBCPKCS7Encrypt(extKey, tidx) + if encryptErr != nil { + return nil, encryptErr + } + + extensions, ks, extensionErr := generateExtensions(tcertid, encryptedTidx, ecert, req) + + if extensionErr != nil { + return nil, extensionErr + } + + template.PublicKey = txPub + template.Extensions = extensions + template.ExtraExtensions = extensions + template.SerialNumber = tcertid + + raw, err := x509.CreateCertificate(rand.Reader, template, tm.CACert, &txPub, tm.CAKey) + if err != nil { + return nil, fmt.Errorf("Failed in TCert x509.CreateCertificate: %s", err) + } + + pem := ConvertDERToPEM(raw, "CERTIFICATE") + + set = append(set, TCert{pem, ks}) + } + + tcertID, randNumErr := GenNumber(big.NewInt(20)) + if randNumErr != nil { + return nil, randNumErr + } + + tcertResponse := &GetBatchResponse{tcertID, time.Now(), kdfKey, set} + + return tcertResponse, nil + +} + +/** +* Create HMAC Key +* returns HMAC String + */ +func createHMACKey() string { + key := make([]byte, 49) + rand.Reader.Read(key) + var cooked = base64.StdEncoding.EncodeToString(key) + return cooked +} + +// Generate encrypted extensions to be included into the TCert (TCertIndex, EnrollmentID and attributes). +func generateExtensions(tcertid *big.Int, tidx []byte, enrollmentCert *x509.Certificate, batchRequest *GetBatchRequest) ([]pkix.Extension, map[string][]byte, error) { + // For each TCert we need to store and retrieve to the user the list of Ks used to encrypt the EnrollmentID and the attributes. + ks := make(map[string][]byte) + attrs := batchRequest.Attrs + extensions := make([]pkix.Extension, len(attrs)) + + preK1 := batchRequest.PreKey + mac := hmac.New(sha512.New384, []byte(preK1)) + mac.Write(tcertid.Bytes()) + preK0 := mac.Sum(nil) + + // Compute encrypted EnrollmentID + mac = hmac.New(sha512.New384, preK0) + mac.Write([]byte("enrollmentID")) + enrollmentIDKey := mac.Sum(nil)[:32] + + enrollmentID := []byte(GetEnrollmentIDFromCert(enrollmentCert)) + enrollmentID = append(enrollmentID, Padding...) + + encEnrollmentID, err := CBCPKCS7Encrypt(enrollmentIDKey, enrollmentID) + if err != nil { + return nil, nil, err + } + + // save k used to encrypt EnrollmentID + ks["enrollmentId"] = enrollmentIDKey + + attributeIdentifierIndex := 9 + count := 0 + attributesHeader := make(map[string]int) + + // Append attributes to the extensions slice + for i := 0; i < len(attrs); i++ { + count++ + name := attrs[i].Name + value := []byte(attrs[i].Value) + + // Save the position of the attribute extension on the header. + attributesHeader[name] = count + + // Encrypt attribute if enabled + if batchRequest.EncryptAttrs { + mac = hmac.New(sha512.New384, preK0) + mac.Write([]byte(name)) + attributeKey := mac.Sum(nil)[:32] + + value = append(value, Padding...) + value, err = CBCPKCS7Encrypt(attributeKey, value) + if err != nil { + return nil, nil, err + } + + // Save the key used to encrypt the attribute + ks[name] = attributeKey + } + + // Generate an ObjectIdentifier for the extension holding the attribute + TCertEncAttributes := asn1.ObjectIdentifier{1, 2, 3, 4, 5, 6, attributeIdentifierIndex + count} + + // Add the attribute extension to the extensions array + extensions[count-1] = pkix.Extension{Id: TCertEncAttributes, Critical: false, Value: value} + } + + // Append the TCertIndex to the extensions + extensions = append(extensions, pkix.Extension{Id: TCertEncTCertIndex, Critical: true, Value: tidx}) + + // Append the encrypted EnrollmentID to the extensions + extensions = append(extensions, pkix.Extension{Id: TCertEncEnrollmentID, Critical: false, Value: encEnrollmentID}) + + // Append the attributes header if there was attributes to include in the TCert + if len(attrs) > 0 { + extensions = append(extensions, pkix.Extension{Id: TCertAttributesHeaders, Critical: false, Value: buildAttributesHeader(attributesHeader)}) + } + + return extensions, ks, nil +} + +func buildAttributesHeader(attributesHeader map[string]int) []byte { + var headerString string + for k, v := range attributesHeader { + headerString = headerString + k + "->" + strconv.Itoa(v) + "#" + } + return []byte(headerString) +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/tcert/util.go b/internal/github.com/hyperledger/fabric-ca/lib/tcert/util.go new file mode 100644 index 0000000000..03e081042f --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/tcert/util.go @@ -0,0 +1,368 @@ +/* +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 tcert + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "time" + + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "math/big" + + "github.com/cloudflare/cfssl/log" +) + +const ( + // AESKeyLength is the default AES key length + AESKeyLength = 32 +) + +var ( + //RootPreKeySize is the default value of root key + RootPreKeySize = 48 +) + +// GenerateIntUUID returns a UUID based on RFC 4122 returning a big.Int +func GenerateIntUUID() (*big.Int, error) { + uuid, err := GenerateBytesUUID() + if err != nil { + return nil, err + } + z := big.NewInt(0) + return z.SetBytes(uuid), nil +} + +// GenerateBytesUUID returns a UUID based on RFC 4122 returning the generated bytes +func GenerateBytesUUID() ([]byte, error) { + uuid := make([]byte, 16) + _, err := io.ReadFull(rand.Reader, uuid) + if err != nil { + return nil, err + } + + // variant bits; see section 4.1.1 + uuid[8] = uuid[8]&^0xc0 | 0x80 + + // version 4 (pseudo-random); see section 4.1.3 + uuid[6] = uuid[6]&^0xf0 | 0x40 + + return uuid, nil +} + +// CBCPKCS7Encrypt combines CBC encryption and PKCS7 padding +func CBCPKCS7Encrypt(key, src []byte) ([]byte, error) { + return CBCEncrypt(key, PKCS7Padding(src)) +} + +// CBCEncrypt encrypts using CBC mode +func CBCEncrypt(key, s []byte) ([]byte, error) { + // CBC mode works on blocks so plaintexts may need to be padded to the + // next whole block. For an example of such padding, see + // https://tools.ietf.org/html/rfc5246#section-6.2.3.2. Here we'll + // assume that the plaintext is already of the correct length. + if len(s)%aes.BlockSize != 0 { + return nil, errors.New("CBCEncrypt failure: plaintext is not a multiple of the block size") + } + + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + ciphertext := make([]byte, aes.BlockSize+len(s)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, fmt.Errorf("CBCEncrypt failure in io.ReadFull: %s", err) + } + + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(ciphertext[aes.BlockSize:], s) + + // It's important to remember that ciphertexts must be authenticated + // (i.e. by using crypto/hmac) as well as being encrypted in order to + // be secure. + return ciphertext, nil +} + +// PKCS7Padding pads as prescribed by the PKCS7 standard +func PKCS7Padding(src []byte) []byte { + padding := aes.BlockSize - len(src)%aes.BlockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(src, padtext...) +} + +//ConvertDERToPEM returns data from DER to PEM format +//DERData is DER +func ConvertDERToPEM(der []byte, datatype string) []byte { + pemByte := pem.EncodeToMemory( + &pem.Block{ + Type: datatype, + Bytes: der, + }, + ) + return pemByte +} + +//GenNumber generates random numbers of type *big.Int with fixed length +func GenNumber(numlen *big.Int) (*big.Int, error) { + lowerBound := new(big.Int).Exp(big.NewInt(10), new(big.Int).Sub(numlen, big.NewInt(1)), nil) + upperBound := new(big.Int).Exp(big.NewInt(10), numlen, nil) + randomNum, err := rand.Int(rand.Reader, upperBound) + if err != nil { + return nil, fmt.Errorf("Failed to generate random number: %s", err) + } + val := new(big.Int).Add(randomNum, lowerBound) + valMod := new(big.Int).Mod(val, upperBound) + + if valMod.Cmp(lowerBound) == -1 { + newval := new(big.Int).Add(valMod, lowerBound) + return newval, nil + } + return valMod, nil +} + +// GetEnrollmentIDFromCert retrieves Enrollment Id from certificate +func GetEnrollmentIDFromCert(ecert *x509.Certificate) string { + return ecert.Subject.CommonName +} + +//GetCertificate returns interface containing *rsa.PublicKey or ecdsa.PublicKey +func GetCertificate(certificate []byte) (*x509.Certificate, error) { + + var certificates []*x509.Certificate + var isvalidCert bool + var err error + + block, _ := pem.Decode(certificate) + if block == nil { + certificates, err = x509.ParseCertificates(certificate) + if err != nil { + log.Error("Certificate Parse failed") + return nil, errors.New("DER Certificate Parse failed") + } //else { + isvalidCert = ValidateCert(certificates[0]) + if !isvalidCert { + log.Error("Certificate expired") + return nil, errors.New("Certificate expired") + } + //} + } else { + certificates, err = x509.ParseCertificates(block.Bytes) + if err != nil { + log.Error("PEM Certificatre Parse failed") + return nil, errors.New("PEM Certificate Parse failed") + } //else { + isvalidCert = ValidateCert(certificates[0]) + if !isvalidCert { + log.Error("Certificate expired") + return nil, errors.New("Certificate expired") + } + //} + } + return certificates[0], nil + +} + +//GetCertitificateSerialNumber returns serial number for Certificate byte +//return -1 , if there is problem with the cert +func GetCertitificateSerialNumber(certificatebyte []byte) (*big.Int, error) { + certificate, error := GetCertificate(certificatebyte) + if error != nil { + log.Error("Not a valid Certificate") + return big.NewInt(-1), error + } + return certificate.SerialNumber, nil +} + +//ValidateCert checks for expiry in the certificate cert +//Does not check for revocation +func ValidateCert(cert *x509.Certificate) bool { + notBefore := cert.NotBefore + notAfter := cert.NotAfter + currentTime := time.Now() + diffFromExpiry := notAfter.Sub(currentTime) + diffFromStart := currentTime.Sub(notBefore) + return ((diffFromExpiry > 0) && (diffFromStart > 0)) +} + +// CBCPKCS7Decrypt combines CBC decryption and PKCS7 unpadding +func CBCPKCS7Decrypt(key, src []byte) ([]byte, error) { + pt, err := CBCDecrypt(key, src) + if err != nil { + + return nil, err + } + + original, err := PKCS7UnPadding(pt) + if err != nil { + + return nil, err + } + + return original, nil +} + +// CBCDecrypt decrypts using CBC mode +func CBCDecrypt(key, src []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + + return nil, err + } + + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + if len(src) < aes.BlockSize { + + return nil, errors.New("ciphertext too short") + } + iv := src[:aes.BlockSize] + src = src[aes.BlockSize:] + + // CBC mode always works in whole blocks. + if len(src)%aes.BlockSize != 0 { + + return nil, errors.New("ciphertext is not a multiple of the block size") + } + + mode := cipher.NewCBCDecrypter(block, iv) + + // CryptBlocks can work in-place if the two arguments are the same. + mode.CryptBlocks(src, src) + + // If the original plaintext lengths are not a multiple of the block + // size, padding would have to be added when encrypting, which would be + // removed at this point. For an example, see + // https://tools.ietf.org/html/rfc5246#section-6.2.3.2. However, it's + // critical to note that ciphertexts must be authenticated (i.e. by + // using crypto/hmac) before being decrypted in order to avoid creating + // a padding oracle. + + return src, nil +} + +// PKCS7UnPadding unpads as prescribed by the PKCS7 standard +func PKCS7UnPadding(src []byte) ([]byte, error) { + length := len(src) + unpadding := int(src[length-1]) + + if unpadding > aes.BlockSize || unpadding == 0 { + return nil, fmt.Errorf("invalid padding") + } + + pad := src[len(src)-unpadding:] + for i := 0; i < unpadding; i++ { + if pad[i] != byte(unpadding) { + return nil, fmt.Errorf("invalid padding") + } + } + + return src[:(length - unpadding)], nil +} + +//CreateRootPreKey method generates root key +func CreateRootPreKey() string { + var cooked string + key := make([]byte, RootPreKeySize) + rand.Reader.Read(key) + cooked = base64.StdEncoding.EncodeToString(key) + return cooked +} + +// GetPrivateKey returns ecdsa.PrivateKey or rsa.privateKey object for the private Key Bytes +func GetPrivateKey(buf []byte) (interface{}, error) { + var err error + var privateKey interface{} + + block, _ := pem.Decode(buf) + if block == nil { + privateKey, err = ParsePrivateKey(buf) + if err != nil { + return nil, fmt.Errorf("Failure parsing DER-encoded private key: %s", err) + } + } else { + privateKey, err = ParsePrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("Failure parsing PEM private key: %s", err) + } + } + + switch privateKey := privateKey.(type) { + case *rsa.PrivateKey: + return privateKey, nil + case *ecdsa.PrivateKey: + return privateKey, nil + default: + return nil, errors.New("Key is neither RSA nor ECDSA") + } + +} + +// ParsePrivateKey parses private key +func ParsePrivateKey(der []byte) (interface{}, error) { + if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { + return key, nil + } + if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { + switch key := key.(type) { + case *rsa.PrivateKey, *ecdsa.PrivateKey: + return key, nil + default: + return nil, errors.New("Key is neither RSA nor ECDSA") + } + } + key, err := x509.ParseECPrivateKey(der) + if err != nil { + return nil, fmt.Errorf("Failure parsing private key: %s", err) + } + return key, nil +} + +// LoadCert loads a certificate from a file +func LoadCert(path string) (*x509.Certificate, error) { + certBuf, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + return GetCertificate(certBuf) +} + +// LoadKey loads a private key from a file +func LoadKey(path string) (interface{}, error) { + keyBuf, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + key, err := GetPrivateKey(keyBuf) + if err != nil { + return nil, err + } + return key, nil +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/tls/tls.go b/internal/github.com/hyperledger/fabric-ca/lib/tls/tls.go new file mode 100644 index 0000000000..75dda96012 --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/tls/tls.go @@ -0,0 +1,186 @@ +/* +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 tls + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io/ioutil" + "time" + + "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/util" + "github.com/hyperledger/fabric/bccsp" + "github.com/hyperledger/fabric/bccsp/factory" +) + +// ServerTLSConfig defines key material for a TLS server +type ServerTLSConfig struct { + Enabled bool `help:"Enable TLS on the listening port"` + CertFile string `def:"ca-cert.pem" help:"PEM-encoded TLS certificate file for server's listening port"` + KeyFile string `def:"ca-key.pem" help:"PEM-encoded TLS key for server's listening port"` + ClientAuth ClientAuth +} + +// ClientAuth defines the key material needed to verify client certificates +type ClientAuth struct { + Type string `def:"noclientcert" help:"Policy the server will follow for TLS Client Authentication."` + CertFiles []string `help:"A list of comma-separated PEM-encoded trusted certificate files (e.g. root1.pem,root2.pem)"` +} + +// ClientTLSConfig defines the key material for a TLS client +type ClientTLSConfig struct { + Enabled bool `skip:"true"` + CertFiles []string `help:"A list of comma-separated PEM-encoded trusted certificate files (e.g. root1.pem,root2.pem)"` + Client KeyCertFiles +} + +// KeyCertFiles defines the files need for client on TLS +type KeyCertFiles struct { + KeyFile string `help:"PEM-encoded key file when mutual authentication is enabled"` + CertFile string `help:"PEM-encoded certificate file when mutual authenticate is enabled"` +} + +// GetClientTLSConfig creates a tls.Config object from certs and roots +func GetClientTLSConfig(cfg *ClientTLSConfig, csp bccsp.BCCSP) (*tls.Config, error) { + var certs []tls.Certificate + + if csp == nil { + csp = factory.GetDefault() + } + + log.Debugf("CA Files: %+v\n", cfg.CertFiles) + log.Debugf("Client Cert File: %s\n", cfg.Client.CertFile) + log.Debugf("Client Key File: %s\n", cfg.Client.KeyFile) + + if cfg.Client.CertFile != "" { + err := checkCertDates(cfg.Client.CertFile) + if err != nil { + return nil, err + } + + clientCert, err := util.LoadX509KeyPair(cfg.Client.CertFile, cfg.Client.KeyFile, csp) + if err != nil { + return nil, err + } + + certs = append(certs, *clientCert) + } else { + log.Debug("Client TLS certificate and/or key file not provided") + } + rootCAPool := x509.NewCertPool() + if len(cfg.CertFiles) == 0 { + return nil, errors.New("No TLS certificate files were provided") + } + + for _, cacert := range cfg.CertFiles { + caCert, err := ioutil.ReadFile(cacert) + if err != nil { + return nil, fmt.Errorf("Failed to read '%s': %s", cacert, err) + } + ok := rootCAPool.AppendCertsFromPEM(caCert) + if !ok { + return nil, fmt.Errorf("Failed to process certificate from file %s", cacert) + } + } + + config := &tls.Config{ + Certificates: certs, + RootCAs: rootCAPool, + } + + return config, nil +} + +// AbsTLSClient makes TLS client files absolute +func AbsTLSClient(cfg *ClientTLSConfig, configDir string) error { + var err error + + for i := 0; i < len(cfg.CertFiles); i++ { + cfg.CertFiles[i], err = util.MakeFileAbs(cfg.CertFiles[i], configDir) + if err != nil { + return err + } + + } + + cfg.Client.CertFile, err = util.MakeFileAbs(cfg.Client.CertFile, configDir) + if err != nil { + return err + } + + cfg.Client.KeyFile, err = util.MakeFileAbs(cfg.Client.KeyFile, configDir) + if err != nil { + return err + } + + return nil +} + +// AbsTLSServer makes TLS client files absolute +func AbsTLSServer(cfg *ServerTLSConfig, configDir string) error { + var err error + + for i := 0; i < len(cfg.ClientAuth.CertFiles); i++ { + cfg.ClientAuth.CertFiles[i], err = util.MakeFileAbs(cfg.ClientAuth.CertFiles[i], configDir) + if err != nil { + return err + } + + } + + cfg.CertFile, err = util.MakeFileAbs(cfg.CertFile, configDir) + if err != nil { + return err + } + + cfg.KeyFile, err = util.MakeFileAbs(cfg.KeyFile, configDir) + if err != nil { + return err + } + + return nil +} + +func checkCertDates(certFile string) error { + log.Debug("Check client TLS certificate for valid dates") + certPEM, err := ioutil.ReadFile(certFile) + if err != nil { + return err + } + + cert, err := util.GetX509CertificateFromPEM(certPEM) + if err != nil { + return err + } + + notAfter := cert.NotAfter + currentTime := time.Now().UTC() + + if currentTime.After(notAfter) { + return errors.New("Certificate provided has expired") + } + + notBefore := cert.NotBefore + if currentTime.Before(notBefore) { + return errors.New("Certificate provided not valid until later date") + } + + return nil +} diff --git a/internal/github.com/hyperledger/fabric-ca/lib/util.go b/internal/github.com/hyperledger/fabric-ca/lib/util.go new file mode 100644 index 0000000000..42022bdb73 --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/lib/util.go @@ -0,0 +1,181 @@ +/* +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 lib + +import ( + "crypto/tls" + "crypto/x509" + "encoding/hex" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + + "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/api" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/util" + "github.com/spf13/viper" +) + +var clientAuthTypes = map[string]tls.ClientAuthType{ + "noclientcert": tls.NoClientCert, + "requestclientcert": tls.RequestClientCert, + "requireanyclientcert": tls.RequireAnyClientCert, + "verifyclientcertifgiven": tls.VerifyClientCertIfGiven, + "requireandverifyclientcert": tls.RequireAndVerifyClientCert, +} + +// GetCertID returns both the serial number and AKI (Authority Key ID) for the certificate +func GetCertID(bytes []byte) (string, string, error) { + cert, err := BytesToX509Cert(bytes) + if err != nil { + return "", "", err + } + serial := util.GetSerialAsHex(cert.SerialNumber) + aki := hex.EncodeToString(cert.AuthorityKeyId) + return serial, aki, nil +} + +// BytesToX509Cert converts bytes (PEM or DER) to an X509 certificate +func BytesToX509Cert(bytes []byte) (*x509.Certificate, error) { + dcert, _ := pem.Decode(bytes) + if dcert != nil { + bytes = dcert.Bytes + } + cert, err := x509.ParseCertificate(bytes) + if err != nil { + return nil, fmt.Errorf("buffer was neither PEM nor DER encoding: %s", err) + } + return cert, err +} + +// LoadPEMCertPool loads a pool of PEM certificates from list of files +func LoadPEMCertPool(certFiles []string) (*x509.CertPool, error) { + certPool := x509.NewCertPool() + + if len(certFiles) > 0 { + for _, cert := range certFiles { + log.Debugf("Reading cert file: %s", cert) + pemCerts, err := ioutil.ReadFile(cert) + if err != nil { + return nil, err + } + + log.Debugf("Appending cert %s to pool", cert) + if !certPool.AppendCertsFromPEM(pemCerts) { + return nil, errors.New("Failed to load cert pool") + } + } + } + + return certPool, nil +} + +// UnmarshalConfig will use the viperunmarshal workaround to unmarshal a +// configuration file into a struct +func UnmarshalConfig(config interface{}, vp *viper.Viper, configFile string, server, viperIssue327WorkAround bool) error { + vp.SetConfigFile(configFile) + err := vp.ReadInConfig() + if err != nil { + return fmt.Errorf("Failed to read config file: %s", err) + } + + // Unmarshal the config into 'caConfig' + // When viper bug https://github.com/spf13/viper/issues/327 is fixed + // and vendored, the work around code can be deleted. + if viperIssue327WorkAround { + sliceFields := []string{ + "csr.hosts", + "tls.clientauth.certfiles", + "ldap.tls.certfiles", + "db.tls.certfiles", + "cafiles", + "intermediate.tls.certfiles", + } + err = util.ViperUnmarshal(config, sliceFields, vp) + if err != nil { + return fmt.Errorf("Incorrect format in file '%s': %s", configFile, err) + } + if server { + serverCfg := config.(*ServerConfig) + err = util.ViperUnmarshal(&serverCfg.CAcfg, sliceFields, vp) + if err != nil { + return fmt.Errorf("Incorrect format in file '%s': %s", configFile, err) + } + } + } else { + err = vp.Unmarshal(config) + if err != nil { + return fmt.Errorf("Incorrect format in file '%s': %s", configFile, err) + } + if server { + serverCfg := config.(*ServerConfig) + err = vp.Unmarshal(&serverCfg.CAcfg) + if err != nil { + return fmt.Errorf("Incorrect format in file '%s': %s", configFile, err) + } + } + } + return nil +} + +// GetAttrValue searches 'attrs' for the attribute with name 'name' and returns +// its value, or "" if not found. +func GetAttrValue(attrs []api.Attribute, name string) string { + for _, attr := range attrs { + if attr.Name == name { + return attr.Value + } + } + return "" +} + +func getMaxEnrollments(userMaxEnrollments int, caMaxEnrollments int) (int, error) { + log.Debugf("Max enrollment value verification - User specified max enrollment: %d, CA max enrollment: %d", userMaxEnrollments, caMaxEnrollments) + if userMaxEnrollments < -1 { + return 0, fmt.Errorf("Max enrollment in registration request may not be less than -1, but was %d", userMaxEnrollments) + } + switch caMaxEnrollments { + case -1: + if userMaxEnrollments == 0 { + // The user is requesting the matching limit of the CA, so gets infinite + return caMaxEnrollments, nil + } + // There is no CA max enrollment limit, so simply use the user requested value + return userMaxEnrollments, nil + case 0: + // The CA max enrollment is 0, so registration is disabled. + return 0, errors.New("Registration is disabled") + default: + switch userMaxEnrollments { + case -1: + // User requested infinite enrollments is not allowed + return 0, errors.New("Registration for infinite enrollments is not allowed") + case 0: + // User is requesting the current CA maximum + return caMaxEnrollments, nil + default: + // User is requesting a specific positive value; make sure it doesn't exceed the CA maximum. + if userMaxEnrollments > caMaxEnrollments { + return 0, fmt.Errorf("Requested enrollments (%d) exceeds maximum allowable enrollments (%d)", + userMaxEnrollments, caMaxEnrollments) + } + // otherwise, use the requested maximum + return userMaxEnrollments, nil + } + } +} diff --git a/internal/github.com/hyperledger/fabric-ca/util/args.go b/internal/github.com/hyperledger/fabric-ca/util/args.go new file mode 100644 index 0000000000..5ba702d2bd --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/util/args.go @@ -0,0 +1,92 @@ +/* +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. +*/ + +package util + +import ( + "fmt" + "os" +) + +const ( + defaultServerProtocol = "http" + defaultServerAddr = "localhost" + defaultServerPort = "7054" +) + +// GetCommandLineOptValue searches the command line arguments for the +// specified option and returns the following value if found; otherwise +// it returns "". If **remove** is true and it is found, the option +// and its value are removed from os.Args. +// For example, if command line is: +// fabric-ca client enroll -config myconfig.json +// GetCommandLineOptValue("-config",true) returns "myconfig.json" +// and changes os.Args to +// fabric-ca client enroll +func GetCommandLineOptValue(optName string, remove bool) string { + for i := 0; i < len(os.Args)-1; i++ { + if os.Args[i] == optName { + val := os.Args[i+1] + if remove { + // Splice out the option and its value + os.Args = append(os.Args[:i], os.Args[i+2:]...) + } + return val + } + } + return "" +} + +// GetServerURL returns the server's URL +func GetServerURL() string { + return fmt.Sprintf("%s://%s:%s", GetServerProtocol(), GetServerAddr(), GetServerPort()) +} + +// GetServerProtocol returns the server's protocol +func GetServerProtocol() string { + protocol := GetCommandLineOptValue("-protocol", false) + if protocol != "" { + return protocol + } + return defaultServerProtocol +} + +// GetServerAddr returns the server's address +func GetServerAddr() string { + addr := GetCommandLineOptValue("-address", false) + if addr != "" { + return addr + } + return defaultServerAddr +} + +// GetServerPort returns the server's listening port +func GetServerPort() string { + port := GetCommandLineOptValue("-port", false) + if port != "" { + return port + } + return defaultServerPort +} + +// SetDefaultServerPort overrides the default CFSSL server port +// by adding the "-port" option to the command line if it was not +// already present. +func SetDefaultServerPort() { + if len(os.Args) > 2 && GetCommandLineOptValue("-port", false) == "" { + os.Args = append(os.Args, "-port", defaultServerPort) + } +} diff --git a/internal/github.com/hyperledger/fabric-ca/util/csp.go b/internal/github.com/hyperledger/fabric-ca/util/csp.go new file mode 100644 index 0000000000..1ad6263613 --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/util/csp.go @@ -0,0 +1,357 @@ +/* +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. +*/ + +package util + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "path" + "strings" + _ "time" // for ocspSignerFromConfig + + _ "github.com/cloudflare/cfssl/cli" // for ocspSignerFromConfig + "github.com/cloudflare/cfssl/config" + "github.com/cloudflare/cfssl/csr" + "github.com/cloudflare/cfssl/helpers" + "github.com/cloudflare/cfssl/log" + _ "github.com/cloudflare/cfssl/ocsp" // for ocspSignerFromConfig + "github.com/cloudflare/cfssl/signer" + "github.com/cloudflare/cfssl/signer/local" + "github.com/hyperledger/fabric/bccsp" + "github.com/hyperledger/fabric/bccsp/factory" + cspsigner "github.com/hyperledger/fabric/bccsp/signer" + "github.com/hyperledger/fabric/bccsp/utils" +) + +// GetDefaultBCCSP returns the default BCCSP +func GetDefaultBCCSP() bccsp.BCCSP { + return factory.GetDefault() +} + +// InitBCCSP initializes BCCSP +func InitBCCSP(optsPtr **factory.FactoryOpts, mspDir, homeDir string) (bccsp.BCCSP, error) { + err := ConfigureBCCSP(optsPtr, mspDir, homeDir) + if err != nil { + return nil, err + } + csp, err := GetBCCSP(*optsPtr, homeDir) + if err != nil { + return nil, err + } + return csp, nil +} + +// ConfigureBCCSP configures BCCSP, using +func ConfigureBCCSP(optsPtr **factory.FactoryOpts, mspDir, homeDir string) error { + var err error + if optsPtr == nil { + return errors.New("nil argument not allowed") + } + opts := *optsPtr + if opts == nil { + opts = &factory.FactoryOpts{} + } + if opts.ProviderName == "" { + opts.ProviderName = "SW" + } + if strings.ToUpper(opts.ProviderName) == "SW" { + if opts.SwOpts == nil { + opts.SwOpts = &factory.SwOpts{} + } + if opts.SwOpts.HashFamily == "" { + opts.SwOpts.HashFamily = "SHA2" + } + if opts.SwOpts.SecLevel == 0 { + opts.SwOpts.SecLevel = 256 + } + if opts.SwOpts.FileKeystore == nil { + opts.SwOpts.FileKeystore = &factory.FileKeystoreOpts{} + } + // The mspDir overrides the KeyStorePath; otherwise, if not set, set default + if mspDir != "" { + opts.SwOpts.FileKeystore.KeyStorePath = path.Join(mspDir, "keystore") + } else if opts.SwOpts.FileKeystore.KeyStorePath == "" { + opts.SwOpts.FileKeystore.KeyStorePath = path.Join("msp", "keystore") + } + } + err = makeFileNamesAbsolute(opts, homeDir) + if err != nil { + return fmt.Errorf("Failed to make BCCSP files absolute: %s", err) + } + log.Debugf("Initializing BCCSP: %+v", opts) + if opts.SwOpts != nil { + log.Debugf("Initializing BCCSP with software options %+v", opts.SwOpts) + } + if opts.Pkcs11Opts != nil { + log.Debugf("Initializing BCCSP with PKCS11 options %+v", opts.Pkcs11Opts) + } + // Init the BCCSP factories + err = factory.InitFactories(opts) + if err != nil { + return fmt.Errorf("Failed to initialize BCCSP Factories: %s", err) + } + *optsPtr = opts + return nil +} + +// GetBCCSP returns BCCSP +func GetBCCSP(opts *factory.FactoryOpts, homeDir string) (bccsp.BCCSP, error) { + + // Get BCCSP from the opts + csp, err := factory.GetBCCSPFromOpts(opts) + if err != nil { + return nil, fmt.Errorf("Failed to get BCCSP: %s [opts: %+v]", err, opts) + } + return csp, nil +} + +// makeFileNamesAbsolute makes all relative file names associated with CSP absolute, +// relative to 'homeDir'. +func makeFileNamesAbsolute(opts *factory.FactoryOpts, homeDir string) error { + var err error + if opts != nil && opts.SwOpts != nil && opts.SwOpts.FileKeystore != nil { + fks := opts.SwOpts.FileKeystore + fks.KeyStorePath, err = MakeFileAbs(fks.KeyStorePath, homeDir) + } + return err +} + +// BccspBackedSigner attempts to create a signer using csp bccsp.BCCSP. This csp could be SW (golang crypto) +// PKCS11 or whatever BCCSP-conformant library is configured +func BccspBackedSigner(caFile, keyFile string, policy *config.Signing, csp bccsp.BCCSP) (signer.Signer, error) { + _, cspSigner, parsedCa, err := GetSignerFromCertFile(caFile, csp) + if err != nil { + // Fallback: attempt to read out of keyFile and import + log.Debugf("No key found in BCCSP keystore, attempting fallback") + var key bccsp.Key + var signer crypto.Signer + + key, err = ImportBCCSPKeyFromPEM(keyFile, csp, false) + if err != nil { + return nil, fmt.Errorf("Could not find the private key in BCCSP keystore nor in keyfile %s: %s", keyFile, err) + } + + signer, err = cspsigner.New(csp, key) + if err != nil { + return nil, fmt.Errorf("Failed initializing CryptoSigner: %s", err) + } + cspSigner = signer + } + + signer, err := local.NewSigner(cspSigner, parsedCa, signer.DefaultSigAlgo(cspSigner), policy) + if err != nil { + return nil, fmt.Errorf("Failed to create new signer: %s", err.Error()) + } + return signer, nil +} + +// getBCCSPKeyOpts generates a key as specified in the request. +// This supports ECDSA and RSA. +func getBCCSPKeyOpts(kr csr.KeyRequest, ephemeral bool) (opts bccsp.KeyGenOpts, err error) { + if kr == nil { + return &bccsp.ECDSAKeyGenOpts{Temporary: ephemeral}, nil + } + log.Debugf("generate key from request: algo=%s, size=%d", kr.Algo(), kr.Size()) + switch kr.Algo() { + case "rsa": + switch kr.Size() { + case 2048: + return &bccsp.RSA2048KeyGenOpts{Temporary: ephemeral}, nil + case 3072: + return &bccsp.RSA3072KeyGenOpts{Temporary: ephemeral}, nil + case 4096: + return &bccsp.RSA4096KeyGenOpts{Temporary: ephemeral}, nil + default: + // Need to add a way to specify arbitrary RSA key size to bccsp + return nil, fmt.Errorf("Invalid RSA key size: %d", kr.Size()) + } + case "ecdsa": + switch kr.Size() { + case 256: + return &bccsp.ECDSAP256KeyGenOpts{Temporary: ephemeral}, nil + case 384: + return &bccsp.ECDSAP384KeyGenOpts{Temporary: ephemeral}, nil + case 521: + // Need to add curve P521 to bccsp + // return &bccsp.ECDSAP512KeyGenOpts{Temporary: false}, nil + return nil, errors.New("Unsupported ECDSA key size: 521") + default: + return nil, fmt.Errorf("Invalid ECDSA key size: %d", kr.Size()) + } + default: + return nil, fmt.Errorf("Invalid algorithm: %s", kr.Algo()) + } +} + +// GetSignerFromCert load private key represented by ski and return bccsp signer that conforms to crypto.Signer +func GetSignerFromCert(cert *x509.Certificate, csp bccsp.BCCSP) (bccsp.Key, crypto.Signer, error) { + if csp == nil { + return nil, nil, fmt.Errorf("CSP was not initialized") + } + // get the public key in the right format + certPubK, err := csp.KeyImport(cert, &bccsp.X509PublicKeyImportOpts{Temporary: true}) + if err != nil { + return nil, nil, fmt.Errorf("Failed to import certificate's public key: %s", err.Error()) + } + // Get the key given the SKI value + privateKey, err := csp.GetKey(certPubK.SKI()) + if err != nil { + return nil, nil, fmt.Errorf("Could not find matching private key for SKI: %s", err.Error()) + } + // Construct and initialize the signer + signer, err := cspsigner.New(csp, privateKey) + if err != nil { + return nil, nil, fmt.Errorf("Failed to load ski from bccsp: %s", err.Error()) + } + return privateKey, signer, nil +} + +// GetSignerFromCertFile load skiFile and load private key represented by ski and return bccsp signer that conforms to crypto.Signer +func GetSignerFromCertFile(certFile string, csp bccsp.BCCSP) (bccsp.Key, crypto.Signer, *x509.Certificate, error) { + // Load cert file + certBytes, err := ioutil.ReadFile(certFile) + if err != nil { + return nil, nil, nil, fmt.Errorf("Could not read certFile [%s]: %s", certFile, err.Error()) + } + // Parse certificate + parsedCa, err := helpers.ParseCertificatePEM(certBytes) + if err != nil { + return nil, nil, nil, err + } + // Get the signer from the cert + key, cspSigner, err := GetSignerFromCert(parsedCa, csp) + return key, cspSigner, parsedCa, err +} + +// BCCSPKeyRequestGenerate generates keys through BCCSP +// somewhat mirroring to cfssl/req.KeyRequest.Generate() +func BCCSPKeyRequestGenerate(req *csr.CertificateRequest, myCSP bccsp.BCCSP) (bccsp.Key, crypto.Signer, error) { + log.Infof("generating key: %+v", req.KeyRequest) + keyOpts, err := getBCCSPKeyOpts(req.KeyRequest, false) + if err != nil { + return nil, nil, err + } + key, err := myCSP.KeyGen(keyOpts) + if err != nil { + return nil, nil, err + } + + cspSigner, err := cspsigner.New(myCSP, key) + if err != nil { + return nil, nil, fmt.Errorf("Failed initializing CryptoSigner: %s", err.Error()) + } + return key, cspSigner, nil +} + +// ImportBCCSPKeyFromPEM attempts to create a private BCCSP key from a pem file keyFile +func ImportBCCSPKeyFromPEM(keyFile string, myCSP bccsp.BCCSP, temporary bool) (bccsp.Key, error) { + keyBuff, err := ioutil.ReadFile(keyFile) + if err != nil { + return nil, err + } + key, err := utils.PEMtoPrivateKey(keyBuff, nil) + if err != nil { + return nil, fmt.Errorf("Failed parsing private key from %s: %s", keyFile, err.Error()) + } + switch key.(type) { + case *ecdsa.PrivateKey: + priv, err := utils.PrivateKeyToDER(key.(*ecdsa.PrivateKey)) + if err != nil { + return nil, fmt.Errorf("Failed to convert ECDSA private key from %s: %s", keyFile, err.Error()) + } + sk, err := myCSP.KeyImport(priv, &bccsp.ECDSAPrivateKeyImportOpts{Temporary: temporary}) + if err != nil { + return nil, fmt.Errorf("Failed to import ECDSA private key from %s: %s", keyFile, err.Error()) + } + return sk, nil + case *rsa.PrivateKey: + return nil, fmt.Errorf("Failed to import RSA key from %s; RSA private key import is not supported", keyFile) + default: + return nil, fmt.Errorf("Failed to import key from %s: invalid secret key type", keyFile) + } +} + +// LoadX509KeyPair reads and parses a public/private key pair from a pair +// of files. The files must contain PEM encoded data. The certificate file +// may contain intermediate certificates following the leaf certificate to +// form a certificate chain. On successful return, Certificate.Leaf will +// be nil because the parsed form of the certificate is not retained. +// +// This function originated from crypto/tls/tls.go and was adapted to use a +// BCCSP Signer +func LoadX509KeyPair(certFile, keyFile string, csp bccsp.BCCSP) (*tls.Certificate, error) { + + certPEMBlock, err := ioutil.ReadFile(certFile) + if err != nil { + return nil, err + } + + cert := &tls.Certificate{} + var skippedBlockTypes []string + for { + var certDERBlock *pem.Block + certDERBlock, certPEMBlock = pem.Decode(certPEMBlock) + if certDERBlock == nil { + break + } + if certDERBlock.Type == "CERTIFICATE" { + cert.Certificate = append(cert.Certificate, certDERBlock.Bytes) + } else { + skippedBlockTypes = append(skippedBlockTypes, certDERBlock.Type) + } + } + + if len(cert.Certificate) == 0 { + if len(skippedBlockTypes) == 0 { + return nil, fmt.Errorf("Failed to find PEM block in file %s", certFile) + } + if len(skippedBlockTypes) == 1 && strings.HasSuffix(skippedBlockTypes[0], "PRIVATE KEY") { + return nil, fmt.Errorf("Failed to find certificate PEM data in file %s, but did find a private key; PEM inputs may have been switched", certFile) + } + return nil, fmt.Errorf("Failed to find \"CERTIFICATE\" PEM block in file %s after skipping PEM blocks of the following types: %v", certFile, skippedBlockTypes) + } + + x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + return nil, err + } + + _, cert.PrivateKey, err = GetSignerFromCert(x509Cert, csp) + if err != nil { + if keyFile != "" { + log.Debugf("Could not load TLS certificate with BCCSP: %s", err) + log.Debugf("Attempting fallback with certfile %s and keyfile %s", certFile, keyFile) + fallbackCerts, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, fmt.Errorf("Could not get the private key %s that matches %s: %s", keyFile, certFile, err) + } + cert = &fallbackCerts + } else { + return nil, fmt.Errorf("Could not load TLS certificate with BCCSP: %s", err) + } + + } + + return cert, nil +} diff --git a/internal/github.com/hyperledger/fabric-ca/util/flag.go b/internal/github.com/hyperledger/fabric-ca/util/flag.go new file mode 100644 index 0000000000..289415affb --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/util/flag.go @@ -0,0 +1,211 @@ +/* +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. +*/ + +package util + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/cloudflare/cfssl/log" + "github.com/mitchellh/mapstructure" + "github.com/op/go-logging" + "github.com/spf13/cast" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +const ( + // TagDefault is the tag name for a default value of a field as recognized + // by RegisterFlags. + TagDefault = "def" + // TagHelp is the tag name for a help message of a field as recognized + // by RegisterFlags. + TagHelp = "help" + // TagOpt is the tag name for a one character option of a field as recognized + // by RegisterFlags. For example, a value of "d" reserves "-d" for the + // command line argument. + TagOpt = "opt" + // TagSkip is the tag name which causes the field to be skipped by + // RegisterFlags. + TagSkip = "skip" +) + +// RegisterFlags registers flags for all fields in an arbitrary 'config' object. +// This method recognizes the following field tags: +// "def" - the default value of the field; +// "opt" - the optional one character short name to use on the command line; +// "help" - the help message to display on the command line; +// "skip" - to skip the field. +func RegisterFlags(flags *pflag.FlagSet, config interface{}, tags map[string]string) error { + fr := &flagRegistrar{flags: flags, tags: tags} + return ParseObj(config, fr.Register) +} + +type flagRegistrar struct { + flags *pflag.FlagSet + tags map[string]string +} + +func (fr *flagRegistrar) Register(f *Field) (err error) { + // Don't register non-leaf fields + if !f.Leaf { + return nil + } + // Don't register fields with no address + if f.Addr == nil { + return fmt.Errorf("Field is not addressable: %s", f.Path) + } + skip := fr.getTag(f, TagSkip) + if skip != "" { + return nil + } + help := fr.getTag(f, TagHelp) + opt := fr.getTag(f, TagOpt) + def := fr.getTag(f, TagDefault) + switch f.Kind { + case reflect.String: + if help == "" { + return fmt.Errorf("Field is missing a help tag: %s", f.Path) + } + fr.flags.StringVarP(f.Addr.(*string), f.Path, opt, def, help) + case reflect.Int: + if help == "" { + return fmt.Errorf("Field is missing a help tag: %s", f.Path) + } + var intDef int + if def != "" { + intDef, err = strconv.Atoi(def) + if err != nil { + return fmt.Errorf("Invalid integer value in 'def' tag of %s field", f.Path) + } + } + fr.flags.IntVarP(f.Addr.(*int), f.Path, opt, intDef, help) + case reflect.Bool: + if help == "" { + return fmt.Errorf("Field is missing a help tag: %s", f.Path) + } + var boolDef bool + if def != "" { + boolDef, err = strconv.ParseBool(def) + if err != nil { + return fmt.Errorf("Invalid boolean value in 'def' tag of %s field", f.Path) + } + } + fr.flags.BoolVarP(f.Addr.(*bool), f.Path, opt, boolDef, help) + case reflect.Slice: + if f.Type.Elem().Kind() == reflect.String { + if help == "" { + return fmt.Errorf("Field is missing a help tag: %s", f.Path) + } + fr.flags.StringSliceVarP(f.Addr.(*[]string), f.Path, opt, nil, help) + } else { + return nil + } + default: + log.Debugf("Not registering flag for '%s' because it is a currently unsupported type: %s\n", + f.Path, f.Kind) + return nil + } + bindFlag(fr.flags, f.Path) + return nil +} + +func (fr *flagRegistrar) getTag(f *Field, tagName string) string { + var key, val string + key = fmt.Sprintf("%s.%s", tagName, f.Path) + if fr.tags != nil { + val = fr.tags[key] + } + if val == "" { + val = f.Tag.Get(tagName) + } + return val +} + +// CmdRunBegin is called at the beginning of each cobra run function +func CmdRunBegin() { + // If -d or --debug, set debug logging level + if viper.GetBool("debug") { + log.Level = log.LevelDebug + + logging.SetLevel(logging.INFO, "bccsp") + logging.SetLevel(logging.INFO, "bccsp_p11") + logging.SetLevel(logging.INFO, "bccsp_sw") + } +} + +// FlagString sets up a flag for a string, binding it to its name +func FlagString(flags *pflag.FlagSet, name, short string, def string, desc string) { + flags.StringP(name, short, def, desc) + bindFlag(flags, name) +} + +// common binding function +func bindFlag(flags *pflag.FlagSet, name string) { + flag := flags.Lookup(name) + if flag == nil { + panic(fmt.Errorf("failed to lookup '%s'", name)) + } + viper.BindPFlag(name, flag) +} + +// ViperUnmarshal is a work around for a bug in viper.Unmarshal +// This can be removed once https://github.com/spf13/viper/issues/327 is fixed +// and vendored. +func ViperUnmarshal(cfg interface{}, stringSliceFields []string, vp *viper.Viper) error { + decoderConfig := &mapstructure.DecoderConfig{ + Metadata: nil, + Result: cfg, + WeaklyTypedInput: true, + DecodeHook: mapstructure.StringToTimeDurationHookFunc(), + } + decoder, err := mapstructure.NewDecoder(decoderConfig) + if err != nil { + return fmt.Errorf("Failed to create decoder: %s", err) + } + settings := vp.AllSettings() + for _, field := range stringSliceFields { + var ok bool + path := strings.Split(field, ".") + m := settings + name := path[0] + // If it is a top level option check to see if nil before continuing + if _, ok = m[name]; !ok { + continue + } + + if len(path) > 1 { + for _, field2 := range path[1:] { + m = m[name].(map[string]interface{}) + name = field2 + + // Inspect nested options to see if nil before proceeding with loop + if _, ok = m[name]; !ok { + break + } + } + } + // Only do casting if path was valid + if ok { + m[name] = cast.ToStringSlice(m[name]) + } + } + + return decoder.Decode(settings) +} diff --git a/internal/github.com/hyperledger/fabric-ca/util/struct.go b/internal/github.com/hyperledger/fabric-ca/util/struct.go new file mode 100644 index 0000000000..ccfe987823 --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/util/struct.go @@ -0,0 +1,178 @@ +/* +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. +*/ + +package util + +import ( + "errors" + "fmt" + "reflect" + "strings" + "time" +) + +// Field is a field of an arbitrary struct +type Field struct { + Name string + Path string + Type reflect.Type + Kind reflect.Kind + Leaf bool + Depth int + Tag reflect.StructTag + Value interface{} + Addr interface{} +} + +// ParseObj parses an object structure, calling back with field info +// for each field +func ParseObj(obj interface{}, cb func(*Field) error) error { + if cb == nil { + return errors.New("nil callback") + } + return parse(obj, cb, nil) +} + +func parse(ptr interface{}, cb func(*Field) error, parent *Field) error { + var path string + var depth int + v := reflect.ValueOf(ptr).Elem() + t := v.Type() + for i := 0; i < v.NumField(); i++ { + vf := v.Field(i) + tf := t.Field(i) + name := strings.ToLower(tf.Name) + if tf.Name[0] == name[0] { + continue // skip unexported fields + } + if parent != nil { + path = fmt.Sprintf("%s.%s", parent.Path, name) + depth = parent.Depth + 1 + } else { + path = name + } + kind := vf.Kind() + leaf := kind != reflect.Struct && kind != reflect.Ptr + field := &Field{ + Name: name, + Path: path, + Type: tf.Type, + Kind: kind, + Leaf: leaf, + Depth: depth, + Tag: tf.Tag, + Value: vf.Interface(), + Addr: vf.Addr().Interface(), + } + err := cb(field) + if err != nil { + return err + } + if kind == reflect.Struct { + // Skip parsing the entire struct if "skip" tag is present on a struct field + if tf.Tag.Get(TagSkip) == "true" { + continue + } + err := parse(field.Addr, cb, field) + if err != nil { + return err + } + } + } + return nil +} + +// CopyMissingValues checks the dst interface for missing values and +// replaces them with value from src config struct. +// This does a deep copy of pointers. +func CopyMissingValues(src, dst interface{}) { + s := reflect.ValueOf(src).Elem() + d := reflect.ValueOf(dst).Elem() + copyMissingValues(s, d) +} + +func copyMissingValues(src, dst reflect.Value) { + if !src.IsValid() { + return + } + switch src.Kind() { + case reflect.Ptr: + src = src.Elem() + if !src.IsValid() { + return + } + if dst.IsNil() { + dst.Set(reflect.New(src.Type())) + } + copyMissingValues(src, dst.Elem()) + case reflect.Interface: + if src.IsNil() { + return + } + src = src.Elem() + if dst.IsNil() { + newVal := reflect.New(src.Type()).Elem() + copyMissingValues(src, newVal) + dst.Set(newVal) + } else { + copyMissingValues(src, dst.Elem()) + } + case reflect.Struct: + if !src.IsValid() { + return + } + t, ok := src.Interface().(time.Time) + if ok { + dst.Set(reflect.ValueOf(t)) + } + for i := 0; i < src.NumField(); i++ { + copyMissingValues(src.Field(i), dst.Field(i)) + } + case reflect.Slice: + if !dst.IsNil() { + return + } + dst.Set(reflect.MakeSlice(src.Type(), src.Len(), src.Cap())) + for i := 0; i < src.Len(); i++ { + copyMissingValues(src.Index(i), dst.Index(i)) + } + case reflect.Map: + if dst.IsNil() { + dst.Set(reflect.MakeMap(src.Type())) + } + for _, key := range src.MapKeys() { + sval := src.MapIndex(key) + dval := dst.MapIndex(key) + copy := !dval.IsValid() + if copy { + dval = reflect.New(sval.Type()).Elem() + } + copyMissingValues(sval, dval) + if copy { + dst.SetMapIndex(key, dval) + } + } + default: + if !dst.CanInterface() { + return + } + dval := dst.Interface() + zval := reflect.Zero(dst.Type()).Interface() + if reflect.DeepEqual(dval, zval) { + dst.Set(src) + } + } +} diff --git a/internal/github.com/hyperledger/fabric-ca/util/util.go b/internal/github.com/hyperledger/fabric-ca/util/util.go new file mode 100644 index 0000000000..93797c251d --- /dev/null +++ b/internal/github.com/hyperledger/fabric-ca/util/util.go @@ -0,0 +1,690 @@ +/* +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 util + +import ( + "bytes" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "encoding/asn1" + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io" + "io/ioutil" + "math/big" + mrand "math/rand" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "reflect" + "regexp" + "strings" + "time" + + "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric/bccsp" + "github.com/spf13/viper" + "golang.org/x/crypto/ocsp" +) + +var ( + rnd = mrand.NewSource(time.Now().UnixNano()) + // ErrNotImplemented used to return errors for functions not implemented + ErrNotImplemented = errors.New("NOT YET IMPLEMENTED") +) + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = rnd.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + + return string(b) +} + +// RemoveQuotes removes outer quotes from a string if necessary +func RemoveQuotes(str string) string { + if str == "" { + return str + } + if (strings.HasPrefix(str, "'") && strings.HasSuffix(str, "'")) || + (strings.HasPrefix(str, "\"") && strings.HasSuffix(str, "\"")) { + str = str[1 : len(str)-1] + } + return str +} + +// ReadFile reads a file +func ReadFile(file string) ([]byte, error) { + return ioutil.ReadFile(file) +} + +// WriteFile writes a file +func WriteFile(file string, buf []byte, perm os.FileMode) error { + return ioutil.WriteFile(file, buf, perm) +} + +// FileExists checks to see if a file exists +func FileExists(name string) bool { + if _, err := os.Stat(name); err != nil { + if os.IsNotExist(err) { + return false + } + } + return true +} + +// Marshal to bytes +func Marshal(from interface{}, what string) ([]byte, error) { + buf, err := json.Marshal(from) + if err != nil { + return nil, fmt.Errorf("Failed to marshal %s: %s", what, err) + } + return buf, nil +} + +// Unmarshal from bytes +func Unmarshal(from []byte, to interface{}, what string) error { + err := json.Unmarshal(from, to) + if err != nil { + return fmt.Errorf("Failed to unmarshal %s: %s", what, err) + } + return nil +} + +// CreateToken creates a JWT-like token. +// In a normal JWT token, the format of the token created is: +// +// where each part is base64-encoded string separated by a period. +// In this JWT-like token, there are two differences: +// 1) the claims section is a certificate, so the format is: +// +// 2) the signature uses the private key associated with the certificate, +// and the signature is across both the certificate and the "body" argument, +// which is the body of an HTTP request, though could be any arbitrary bytes. +// @param cert The pem-encoded certificate +// @param key The pem-encoded key +// @param body The body of an HTTP request +func CreateToken(csp bccsp.BCCSP, cert []byte, key bccsp.Key, body []byte) (string, error) { + x509Cert, err := GetX509CertificateFromPEM(cert) + if err != nil { + return "", err + } + publicKey := x509Cert.PublicKey + + var token string + + //The RSA Key Gen is commented right now as there is bccsp does + switch publicKey.(type) { + /* + case *rsa.PublicKey: + token, err = GenRSAToken(csp, cert, key, body) + if err != nil { + return "", err + } + */ + case *ecdsa.PublicKey: + token, err = GenECDSAToken(csp, cert, key, body) + if err != nil { + return "", err + } + } + return token, nil +} + +//GenRSAToken signs the http body and cert with RSA using RSA private key +// @csp : BCCSP instance +/* +func GenRSAToken(csp bccsp.BCCSP, cert []byte, key []byte, body []byte) (string, error) { + privKey, err := GetRSAPrivateKey(key) + if err != nil { + return "", err + } + b64body := B64Encode(body) + b64cert := B64Encode(cert) + bodyAndcert := b64body + "." + b64cert + hash := sha512.New384() + hash.Write([]byte(bodyAndcert)) + h := hash.Sum(nil) + RSAsignature, err := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA384, h[:]) + if err != nil { + return "", fmt.Errorf("Error from rsa.SignPKCS1v15: %s", err) + } + b64sig := B64Encode(RSAsignature) + token := b64cert + "." + b64sig + + return token, nil +} +*/ + +//GenECDSAToken signs the http body and cert with ECDSA using EC private key +func GenECDSAToken(csp bccsp.BCCSP, cert []byte, key bccsp.Key, body []byte) (string, error) { + b64body := B64Encode(body) + b64cert := B64Encode(cert) + bodyAndcert := b64body + "." + b64cert + + digest, digestError := csp.Hash([]byte(bodyAndcert), &bccsp.SHAOpts{}) + if digestError != nil { + return "", fmt.Errorf("Hash operation on %s\t failed with error : %s", bodyAndcert, digestError) + } + + ecSignature, err := csp.Sign(key, digest, nil) + if err != nil { + return "", fmt.Errorf("BCCSP signature generation failed with error :%s", err) + } + if len(ecSignature) == 0 { + return "", errors.New("BCCSP signature creation failed. Signature must be different than nil") + } + + b64sig := B64Encode(ecSignature) + token := b64cert + "." + b64sig + + return token, nil + +} + +// VerifyToken verifies token signed by either ECDSA or RSA and +// returns the associated user ID +func VerifyToken(csp bccsp.BCCSP, token string, body []byte) (*x509.Certificate, error) { + + if csp == nil { + return nil, errors.New("BCCSP instance is not present") + } + x509Cert, b64Cert, b64Sig, err := DecodeToken(token) + if err != nil { + return nil, err + } + sig, err := B64Decode(b64Sig) + if err != nil { + return nil, fmt.Errorf("Invalid base64 encoded signature in token: %s", err) + } + b64Body := B64Encode(body) + sigString := b64Body + "." + b64Cert + + pk2, err := csp.KeyImport(x509Cert, &bccsp.X509PublicKeyImportOpts{Temporary: true}) + if err != nil { + return nil, fmt.Errorf("Public Key import into BCCSP failed with error : %s", err) + } + if pk2 == nil { + return nil, errors.New("Public Key Cannot be imported into BCCSP") + } + //bccsp.X509PublicKeyImportOpts + //Using default hash algo + digest, digestError := csp.Hash([]byte(sigString), &bccsp.SHAOpts{}) + if digestError != nil { + return nil, fmt.Errorf("Message digest failed with error : %s", digestError) + } + + valid, validErr := csp.Verify(pk2, sig, digest, nil) + + if validErr != nil { + return nil, fmt.Errorf("Token Signature validation failed with error : %s ", validErr) + } + if !valid { + return nil, errors.New("Token Signature Validation failed") + } + + return x509Cert, nil +} + +// DecodeToken extracts an X509 certificate and base64 encoded signature from a token +func DecodeToken(token string) (*x509.Certificate, string, string, error) { + if token == "" { + return nil, "", "", errors.New("Invalid token; it is empty") + } + parts := strings.Split(token, ".") + if len(parts) != 2 { + return nil, "", "", errors.New("Invalid token format; expecting 2 parts separated by '.'") + } + b64cert := parts[0] + certDecoded, err := B64Decode(b64cert) + if err != nil { + return nil, "", "", fmt.Errorf("Failed to decode base64 encoded x509 cert: %s", err) + } + x509Cert, err := GetX509CertificateFromPEM(certDecoded) + if err != nil { + return nil, "", "", fmt.Errorf("Error in parsing x509 cert given Block Bytes: %s", err) + } + return x509Cert, b64cert, parts[1], nil +} + +//GetECPrivateKey get *ecdsa.PrivateKey from key pem +func GetECPrivateKey(raw []byte) (*ecdsa.PrivateKey, error) { + decoded, _ := pem.Decode(raw) + if decoded == nil { + return nil, errors.New("Failed to decode the PEM-encoded ECDSA key") + } + ECprivKey, err := x509.ParseECPrivateKey(decoded.Bytes) + if err == nil { + return ECprivKey, nil + } + key, err2 := x509.ParsePKCS8PrivateKey(decoded.Bytes) + if err2 == nil { + switch key.(type) { + case *ecdsa.PrivateKey: + return key.(*ecdsa.PrivateKey), nil + case *rsa.PrivateKey: + return nil, errors.New("Expecting EC private key but found RSA private key") + default: + return nil, errors.New("Invalid private key type in PKCS#8 wrapping") + } + } + return nil, fmt.Errorf("Failed parsing EC private key: %s", err) +} + +//GetRSAPrivateKey get *rsa.PrivateKey from key pem +func GetRSAPrivateKey(raw []byte) (*rsa.PrivateKey, error) { + decoded, _ := pem.Decode(raw) + if decoded == nil { + return nil, errors.New("Failed to decode the PEM-encoded RSA key") + } + RSAprivKey, err := x509.ParsePKCS1PrivateKey(decoded.Bytes) + if err == nil { + return RSAprivKey, nil + } + key, err2 := x509.ParsePKCS8PrivateKey(raw) + if err2 == nil { + switch key.(type) { + case *ecdsa.PrivateKey: + return nil, errors.New("Expecting RSA private key but found EC private key") + case *rsa.PrivateKey: + return key.(*rsa.PrivateKey), nil + default: + return nil, errors.New("Invalid private key type in PKCS#8 wrapping") + } + } + return nil, fmt.Errorf("Failed parsing RSA private key: %s", err) +} + +// B64Encode base64 encodes bytes +func B64Encode(buf []byte) string { + return base64.StdEncoding.EncodeToString(buf) +} + +// B64Decode base64 decodes a string +func B64Decode(str string) (buf []byte, err error) { + return base64.StdEncoding.DecodeString(str) +} + +// StrContained returns true if 'str' is in 'strs'; otherwise return false +func StrContained(str string, strs []string) bool { + for _, s := range strs { + if strings.ToLower(s) == strings.ToLower(str) { + return true + } + } + return false +} + +// IsSubsetOf returns an error if there is something in 'small' that +// is not in 'big'. Both small and big are assumed to be comma-separated +// strings. All string comparisons are case-insensitive. +// Examples: +// 1) IsSubsetOf('a,B', 'A,B,C') returns nil +// 2) IsSubsetOf('A,B,C', 'B,C') returns an error because A is not in the 2nd set. +func IsSubsetOf(small, big string) error { + bigSet := strings.Split(big, ",") + smallSet := strings.Split(small, ",") + for _, s := range smallSet { + if s != "" && !StrContained(s, bigSet) { + return fmt.Errorf("'%s' is not a member of '%s'", s, big) + } + } + return nil +} + +// HTTPRequestToString returns a string for an HTTP request for debuggging +func HTTPRequestToString(req *http.Request) string { + body, _ := ioutil.ReadAll(req.Body) + req.Body = ioutil.NopCloser(bytes.NewReader(body)) + return fmt.Sprintf("%s %s\nAuthorization: %s\n%s", + req.Method, req.URL, req.Header.Get("authorization"), string(body)) +} + +// HTTPResponseToString returns a string for an HTTP response for debuggging +func HTTPResponseToString(resp *http.Response) string { + body, _ := ioutil.ReadAll(resp.Body) + resp.Body = ioutil.NopCloser(bytes.NewReader(body)) + return fmt.Sprintf("statusCode=%d (%s)\n%s", + resp.StatusCode, resp.Status, string(body)) +} + +// CreateClientHome will create a home directory if it does not exist +func CreateClientHome() (string, error) { + log.Debug("CreateHome") + home := filepath.Dir(GetDefaultConfigFile("fabric-ca-client")) + + if _, err := os.Stat(home); err != nil { + if os.IsNotExist(err) { + err := os.MkdirAll(home, 0755) + if err != nil { + return "", err + } + } + } + return home, nil +} + +// GetDefaultConfigFile gets the default path for the config file to display in usage message +func GetDefaultConfigFile(cmdName string) string { + if cmdName == "fabric-ca-server" { + var fname = fmt.Sprintf("%s-config.yaml", cmdName) + // First check home env variables + home := "." + envs := []string{"FABRIC_CA_SERVER_HOME", "FABRIC_CA_HOME", "CA_CFG_PATH"} + for _, env := range envs { + envVal := os.Getenv(env) + if envVal != "" { + home = envVal + break + } + } + return path.Join(home, fname) + } + + var fname = fmt.Sprintf("%s-config.yaml", cmdName) + // First check home env variables + var home string + envs := []string{"FABRIC_CA_CLIENT_HOME", "FABRIC_CA_HOME", "CA_CFG_PATH"} + for _, env := range envs { + envVal := os.Getenv(env) + if envVal != "" { + home = envVal + return path.Join(home, fname) + } + } + + return path.Join(os.Getenv("HOME"), ".fabric-ca-client", fname) +} + +// GetX509CertificateFromPEMFile gets an X509 certificate from a file +func GetX509CertificateFromPEMFile(file string) (*x509.Certificate, error) { + pemBytes, err := ReadFile(file) + if err != nil { + return nil, err + } + x509Cert, err := GetX509CertificateFromPEM(pemBytes) + if err != nil { + return nil, fmt.Errorf("Invalid certificate in %s: %s", file, err) + } + return x509Cert, nil +} + +// GetX509CertificateFromPEM get an X509 certificate from bytes in PEM format +func GetX509CertificateFromPEM(cert []byte) (*x509.Certificate, error) { + block, _ := pem.Decode(cert) + if block == nil { + return nil, errors.New("Failed to PEM decode certificate") + } + x509Cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("Error parsing certificate: %s", err) + } + return x509Cert, nil +} + +// GetCertificateDurationFromFile returns the validity duration for a certificate +// in a file. +func GetCertificateDurationFromFile(file string) (time.Duration, error) { + cert, err := GetX509CertificateFromPEMFile(file) + if err != nil { + return 0, err + } + return GetCertificateDuration(cert), nil +} + +// GetCertificateDuration returns the validity duration for a certificate +func GetCertificateDuration(cert *x509.Certificate) time.Duration { + return cert.NotAfter.Sub(cert.NotBefore) +} + +// GetEnrollmentIDFromPEM returns the EnrollmentID from a PEM buffer +func GetEnrollmentIDFromPEM(cert []byte) (string, error) { + x509Cert, err := GetX509CertificateFromPEM(cert) + if err != nil { + return "", err + } + return GetEnrollmentIDFromX509Certificate(x509Cert), nil +} + +// GetEnrollmentIDFromX509Certificate returns the EnrollmentID from the X509 certificate +func GetEnrollmentIDFromX509Certificate(cert *x509.Certificate) string { + return cert.Subject.CommonName +} + +// MakeFileAbs makes 'file' absolute relative to 'dir' if not already absolute +func MakeFileAbs(file, dir string) (string, error) { + if file == "" { + return "", nil + } + if filepath.IsAbs(file) { + return file, nil + } + path, err := filepath.Abs(filepath.Join(dir, file)) + if err != nil { + return "", fmt.Errorf("Failed making '%s' absolute based on '%s'", file, dir) + } + return path, nil +} + +// MakeFileNamesAbsolute makes all file names in the list absolute, relative to home +func MakeFileNamesAbsolute(files []*string, home string) error { + for _, filePtr := range files { + abs, err := MakeFileAbs(*filePtr, home) + if err != nil { + return err + } + *filePtr = abs + } + return nil +} + +// Fatal logs a fatal message and exits +func Fatal(format string, v ...interface{}) { + log.Fatalf(format, v...) + os.Exit(1) +} + +// GetUser returns username and password from CLI input +func GetUser() (string, string, error) { + fabricCAServerURL := viper.GetString("url") + + URL, err := url.Parse(fabricCAServerURL) + if err != nil { + return "", "", err + } + + user := URL.User + if user == nil { + return "", "", errors.New("No username and password provided as part of URL") + } + + eid := user.Username() + if eid == "" { + return "", "", errors.New("No username provided as part of URL") + } + + pass, _ := user.Password() + if pass == "" { + return "", "", errors.New("No password provided as part of URL") + } + + return eid, pass, nil +} + +// GetSerialAsHex returns the serial number from certificate as hex format +func GetSerialAsHex(serial *big.Int) string { + hex := fmt.Sprintf("%x", serial) + return hex +} + +// StructToString converts a struct to a string. If a field +// has a 'secret' tag, it is masked in the returned string +func StructToString(si interface{}) string { + rval := reflect.ValueOf(si).Elem() + tipe := rval.Type() + var buffer bytes.Buffer + buffer.WriteString("{ ") + for i := 0; i < rval.NumField(); i++ { + tf := tipe.Field(i) + if !rval.FieldByName(tf.Name).CanSet() { + continue // skip unexported fields + } + var fStr string + tagv := tf.Tag.Get(SecretTag) + if PassExpr.MatchString(tagv) { + fStr = fmt.Sprintf("%s:**** ", tf.Name) + } else { + fStr = fmt.Sprintf("%s:%v ", tf.Name, rval.Field(i).Interface()) + } + buffer.WriteString(fStr) + } + buffer.WriteString(" }") + return buffer.String() +} + +// NormalizeStringSlice checks for seperators +func NormalizeStringSlice(slice []string) []string { + var normalizedSlice []string + + if len(slice) > 0 { + for _, item := range slice { + if strings.Contains(item, ",") { + normalizedSlice = append(normalizedSlice, strings.Split(item, ",")...) + } else { + normalizedSlice = append(normalizedSlice, item) + } + } + } + + return normalizedSlice +} + +// NormalizeFileList provides absolute pathing for the list of files +func NormalizeFileList(files []string, homeDir string) ([]string, error) { + var err error + + files = NormalizeStringSlice(files) + + for i, file := range files { + files[i], err = MakeFileAbs(file, homeDir) + if err != nil { + return nil, err + } + } + + return files, nil +} + +// CheckHostsInCert checks to see if host correctly inserted into certificate +func CheckHostsInCert(certFile string, host string) error { + containsHost := false + certBytes, err := ioutil.ReadFile(certFile) + if err != nil { + return fmt.Errorf("Failed to read file: %s", err) + } + + cert, err := GetX509CertificateFromPEM(certBytes) + if err != nil { + return fmt.Errorf("Failed to get certificate: %s", err) + } + // Run through the extensions for the certificates + for _, ext := range cert.Extensions { + // asn1 identifier for 'Subject Alternative Name' + if ext.Id.Equal(asn1.ObjectIdentifier{2, 5, 29, 17}) { + if !strings.Contains(string(ext.Value), host) { + return fmt.Errorf("Host '%s' was not found in the certificate in file '%s'", host, certFile) + } + containsHost = true + } + } + + if !containsHost { + return errors.New("Certificate contains no hosts") + } + + return nil +} + +// Read reads from Reader into a byte array +func Read(r io.Reader, data []byte) ([]byte, error) { + j := 0 + for { + n, err := r.Read(data[j:]) + j = j + n + if err != nil { + if err == io.EOF { + break + } + return nil, fmt.Errorf("Read failure: %s", err) + } + + if (n == 0 && j == len(data)) || j > len(data) { + return nil, errors.New("Size of requested data is too large") + } + } + + return data[:j], nil +} diff --git a/pkg/fabric-ca-client/fabricca.go b/pkg/fabric-ca-client/fabricca.go index d5c92d2869..6b191c4b8d 100644 --- a/pkg/fabric-ca-client/fabricca.go +++ b/pkg/fabric-ca-client/fabricca.go @@ -9,10 +9,10 @@ package fabricca import ( "fmt" - api "github.com/hyperledger/fabric-ca/api" - fabric_ca "github.com/hyperledger/fabric-ca/lib" config "github.com/hyperledger/fabric-sdk-go/api/apiconfig" sdkApi "github.com/hyperledger/fabric-sdk-go/api/apifabca" + api "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/api" + fabric_ca "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/lib" "github.com/hyperledger/fabric/bccsp" "github.com/op/go-logging" diff --git a/pkg/fabric-ca-client/mocks/mockfabriccaserver.go b/pkg/fabric-ca-client/mocks/mockfabriccaserver.go index 6f158817e5..4e266c1a41 100644 --- a/pkg/fabric-ca-client/mocks/mockfabriccaserver.go +++ b/pkg/fabric-ca-client/mocks/mockfabriccaserver.go @@ -12,8 +12,8 @@ import ( cfapi "github.com/cloudflare/cfssl/api" cfsslapi "github.com/cloudflare/cfssl/api" - "github.com/hyperledger/fabric-ca/api" - "github.com/hyperledger/fabric-ca/util" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/api" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/util" ) var ecert = `-----BEGIN CERTIFICATE-----