From c280fa32cf38a8100f53edcef0670c2bbd98fd38 Mon Sep 17 00:00:00 2001 From: ASHUTOSH KUMAR Date: Mon, 6 Feb 2017 11:34:49 -0500 Subject: [PATCH] Fabric-CA bccsp integration for VerifyToken Implements FAB 2014 https://jira.hyperledger.org/browse/FAB-2014 The implementation removed support of RSA for the time being.Will provide support of RSA later. Client BCCSP integration is more of workaround as client BCCSP is not being instantiated the way it should have been. The reason being : 1) The focus of change set is primarily server side support and 2)This implementation is not aimed to support for Go Client Fixed lint issue Rebased Resolved merge conflict Commented out RSA code Add some more test cases to improve test coverage Change-Id: Ia7f198fe3a4dfbbb60f355e8b063f885e5edc845 Signed-off-by: ASHUTOSH KUMAR Signed-off-by: Keith Smith --- api/net.go | 2 +- lib/identity.go | 24 +++++- lib/serverauth.go | 2 +- lib/serverrevoke.go | 2 +- testdata/ec-key.ski | 3 + util/util.go | 139 +++++++++++++++++++++++------------ util/util_test.go | 173 +++++++++++++++++++++++++++++++++++++++----- 7 files changed, 277 insertions(+), 68 deletions(-) create mode 100644 testdata/ec-key.ski diff --git a/api/net.go b/api/net.go index 632644160..dcb7c9066 100644 --- a/api/net.go +++ b/api/net.go @@ -63,7 +63,7 @@ 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 TCert to an ECert. + // which cryptographically relates the TCerts to an ECert. KeySigs []KeySig `json:"key_sigs,omitempty"` } diff --git a/lib/identity.go b/lib/identity.go index 51c8ad76f..94bb30fb3 100644 --- a/lib/identity.go +++ b/lib/identity.go @@ -25,6 +25,8 @@ import ( "github.com/cloudflare/cfssl/signer" "github.com/hyperledger/fabric-ca/api" "github.com/hyperledger/fabric-ca/util" + "github.com/hyperledger/fabric/bccsp" + "github.com/hyperledger/fabric/bccsp/factory" ) func newIdentity(client *Client, name string, key []byte, cert []byte) *Identity { @@ -40,6 +42,7 @@ type Identity struct { name string ecert *Signer client *Client + CSP bccsp.BCCSP } // GetName returns the identity name @@ -190,10 +193,29 @@ 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(cert, key, body) + if i.CSP == nil { + csp, error := getDefaultBCCSPInstance() + if error != nil { + return fmt.Errorf("Default BCCSP instance failed with error : %s", error) + } + i.CSP = csp + } + 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 } + +func getDefaultBCCSPInstance() (bccsp.BCCSP, error) { + defaultBccsp, bccspError := factory.GetDefault() + if bccspError != nil { + return nil, fmt.Errorf("BCCSP initialiazation failed with error : %s", bccspError) + } + if defaultBccsp == nil { + return nil, errors.New("Cannot get default instance of BCCSP") + } + + return defaultBccsp, nil +} diff --git a/lib/serverauth.go b/lib/serverauth.go index 03ec4cc7f..bdb4372ae 100644 --- a/lib/serverauth.go +++ b/lib/serverauth.go @@ -120,7 +120,7 @@ func (ah *fcaAuthHandler) serveHTTP(w http.ResponseWriter, r *http.Request) erro } r.Body = ioutil.NopCloser(bytes.NewReader(body)) // verify token - cert, err2 := util.VerifyToken(authHdr, body) + cert, err2 := util.VerifyToken(MyCSP, authHdr, body) if err2 != nil { log.Debugf("Failed to verify token: %s", err2) return authError diff --git a/lib/serverrevoke.go b/lib/serverrevoke.go index 2b2d1f0a6..becb9f850 100644 --- a/lib/serverrevoke.go +++ b/lib/serverrevoke.go @@ -59,7 +59,7 @@ func (h *revokeHandler) Handle(w http.ResponseWriter, r *http.Request) error { } r.Body.Close() - cert, err := util.VerifyToken(authHdr, body) + cert, err := util.VerifyToken(MyCSP, authHdr, body) if err != nil { return authErr(w, err) } diff --git a/testdata/ec-key.ski b/testdata/ec-key.ski new file mode 100644 index 000000000..6cc688c1b --- /dev/null +++ b/testdata/ec-key.ski @@ -0,0 +1,3 @@ +-----BEGIN BCCSP SKI----- +sLJGcSFzmXHJlmULJ9Ne8//jZlTKnS8dsZvbQu4i27c= +-----END BCCSP SKI----- diff --git a/util/util.go b/util/util.go index 8232e6811..0bff48813 100644 --- a/util/util.go +++ b/util/util.go @@ -18,13 +18,8 @@ package util import ( "bytes" - "crypto" "crypto/ecdsa" - "crypto/rand" - "crypto/rsa" - "crypto/sha512" "crypto/x509" - "encoding/asn1" "encoding/base64" "encoding/json" "encoding/pem" @@ -42,6 +37,7 @@ import ( "time" "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric/bccsp" "github.com/jmoiron/sqlx" "github.com/spf13/viper" ) @@ -156,7 +152,8 @@ func DERCertToPEM(der []byte) []byte { // @param cert The pem-encoded certificate // @param key The pem-encoded key // @param body The body of an HTTP request -func CreateToken(cert []byte, key []byte, body []byte) (string, error) { +func CreateToken(csp bccsp.BCCSP, cert []byte, key []byte, body []byte) (string, error) { + block, _ := pem.Decode(cert) if block == nil { return "", errors.New("Failed to PEM decode certificate") @@ -169,14 +166,17 @@ func CreateToken(cert []byte, key []byte, body []byte) (string, error) { 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(cert, key, body) - if err != nil { - return "", err - } + /* + case *rsa.PublicKey: + token, err = GenRSAToken(csp, cert, key, body) + if err != nil { + return "", err + } + */ case *ecdsa.PublicKey: - token, err = GenECDSAToken(cert, key, body) + token, err = GenECDSAToken(csp, cert, key, body) if err != nil { return "", err } @@ -185,7 +185,9 @@ func CreateToken(cert []byte, key []byte, body []byte) (string, error) { } //GenRSAToken signs the http body and cert with RSA using RSA private key -func GenRSAToken(cert []byte, key []byte, body []byte) (string, error) { +// @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 @@ -203,30 +205,36 @@ func GenRSAToken(cert []byte, key []byte, body []byte) (string, error) { b64sig := B64Encode(RSAsignature) token := b64cert + "." + b64sig - return token, nil + return token, nil } +*/ //GenECDSAToken signs the http body and cert with ECDSA using EC private key -func GenECDSAToken(cert []byte, key []byte, body []byte) (string, error) { - privKey, err := GetECPrivateKey(key) +func GenECDSAToken(csp bccsp.BCCSP, cert []byte, key []byte, body []byte) (string, error) { + + sk, err := GetKeyFromBytes(csp, 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) - r, s, err := ecdsa.Sign(rand.Reader, privKey, h) - if err != nil { - return "", fmt.Errorf("Failed in ecdsa.Sign: %s", err) + + 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 := asn1.Marshal(ECDSASignature{r, s}) - if err != nil { - return "", fmt.Errorf("Failed in asn1.Marshal: %s", err) + + ecSignature, signatureError := csp.Sign(sk, digest, nil) + if signatureError != 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) + + b64sig := B64Encode(ecSignature) token := b64cert + "." + b64sig return token, nil @@ -235,7 +243,11 @@ func GenECDSAToken(cert []byte, key []byte, body []byte) (string, error) { // VerifyToken verifies token signed by either ECDSA or RSA and // returns the associated user ID -func VerifyToken(token string, body []byte) (*x509.Certificate, error) { +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 @@ -246,27 +258,30 @@ func VerifyToken(token string, body []byte) (*x509.Certificate, error) { } b64Body := B64Encode(body) sigString := b64Body + "." + b64Cert - publicKey := x509Cert.PublicKey - hash := sha512.New384() - hash.Write([]byte(sigString)) - h := hash.Sum(nil) - switch publicKey.(type) { - case *rsa.PublicKey: - err := rsa.VerifyPKCS1v15(publicKey.(*rsa.PublicKey), crypto.SHA384, h[:], sig) - if err != nil { - return nil, err - } - case *ecdsa.PublicKey: - ecdsaSignature := new(ECDSASignature) - _, err := asn1.Unmarshal(sig, ecdsaSignature) - if err != nil { - return nil, fmt.Errorf("Failed to unmarshal EC signature to R and S: %s", err) - } - verified := ecdsa.Verify(publicKey.(*ecdsa.PublicKey), h, ecdsaSignature.R, ecdsaSignature.S) - if !verified { - return nil, errors.New("token verification failed (ecdsa.Verify failed)") - } + + pk2, err := csp.KeyImport(x509Cert, &bccsp.X509PublicKeyImportOpts{Temporary: false}) + 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 } @@ -309,6 +324,9 @@ func GetECPrivateKey(raw []byte) (*ecdsa.PrivateKey, error) { } //GetRSAPrivateKey get *rsa.PrivateKey from key pem +// This function is commented out as there is no +// adequate support for RSA +/* func GetRSAPrivateKey(raw []byte) (*rsa.PrivateKey, error) { decoded, _ := pem.Decode(raw) if decoded == nil { @@ -320,6 +338,7 @@ func GetRSAPrivateKey(raw []byte) (*rsa.PrivateKey, error) { } return RSAprivKey, nil } +*/ // B64Encode base64 encodes bytes func B64Encode(buf []byte) string { @@ -483,3 +502,29 @@ func GetUser() (string, string, error) { return eid, pass, nil } + +// GetKeyFromBytes returns a BCCSP key given a byte buffer. The byte buffer +// should always contain the SKI and not the real private key; however, +// until we have complete BCCSP integration, we tolerate it being the real +// private key. +func GetKeyFromBytes(csp bccsp.BCCSP, key []byte) (bccsp.Key, error) { + + // This should succeed if key is an SKI + sk, err := csp.GetKey(key) + if err == nil { + return sk, nil + } + + // Nope, try handling as a private key itself + pk, err := GetECPrivateKey(key) + if err != nil { + return nil, err + } + + pkb, err := x509.MarshalECPrivateKey(pk) + if err != nil { + return nil, fmt.Errorf("Failed to marshal EC private key: %s", err) + } + + return csp.KeyImport(pkb, &bccsp.ECDSAPrivateKeyImportOpts{Temporary: false}) +} diff --git a/util/util_test.go b/util/util_test.go index 8b71f4349..7a76ff61a 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -17,11 +17,15 @@ limitations under the License. package util import ( + "errors" + "fmt" "io/ioutil" "os" "path/filepath" "testing" + "github.com/hyperledger/fabric/bccsp" + "github.com/hyperledger/fabric/bccsp/factory" _ "github.com/mattn/go-sqlite3" "github.com/spf13/viper" ) @@ -41,62 +45,165 @@ func TestECCreateToken(t *testing.T) { cert, _ := ioutil.ReadFile(getPath("ec.pem")) privKey, _ := ioutil.ReadFile(getPath("ec-key.pem")) body := []byte("request byte array") - ECtoken, err := CreateToken(cert, privKey, body) + + csp, error := getDefaultBCCSPInstance() + if error != nil { + t.Errorf("Default BCCSP instance failed with error %s", error) + } + + ECtoken, err := CreateToken(csp, cert, privKey, body) if err != nil { t.Fatalf("CreatToken failed: %s", err) } - _, err = VerifyToken(ECtoken, body) + + _, err = VerifyToken(csp, ECtoken, body) if err != nil { t.Fatalf("VerifyToken failed: %s", err) } + + _, err = VerifyToken(nil, ECtoken, body) + if err == nil { + t.Fatal("VerifyToken should have failed as no instance of csp is passed") + } + + _, err = VerifyToken(csp, "", body) + if err == nil { + t.Fatal("VerifyToken should have failed as no EC Token is passed") + } + + _, err = VerifyToken(csp, ECtoken, nil) + if err == nil { + t.Fatal("VerifyToken should have failed as no EC Token is passed") + } + + verifiedByte := []byte("TEST") + body = append(body, verifiedByte[0]) + _, err = VerifyToken(csp, ECtoken, body) + if err == nil { + t.Fatal("VerifyToken should have failed as body was tampered") + } + + ski, skierror := ioutil.ReadFile(getPath("ec-key.ski")) + if skierror != nil { + t.Fatalf("SKI File Read failed with error : %s", skierror) + } + ECtoken, err = CreateToken(csp, ski, privKey, body) + if (err == nil) || (ECtoken != "") { + t.Fatal("CreatToken should have failed as certificate passed is not correct") + } +} + +func TestGetX509CertFromPem(t *testing.T) { + + certBuffer, error := ioutil.ReadFile(getPath("ec.pem")) + if error != nil { + t.Fatalf("Certificate File Read from file failed with error : %s", error) + } + certificate, err := GetX509CertificateFromPEM(certBuffer) + if err != nil { + t.Fatalf("GetX509CertificateFromPEM failed with error : %s", err) + } + if certificate == nil { + t.Fatal("Certificate cannot be nil") + } + + skiBuffer, skiError := ioutil.ReadFile(getPath("ec-key.ski")) + if skiError != nil { + t.Fatalf("SKI File read failed with error : %s", skiError) + } + + certificate, err = GetX509CertificateFromPEM(skiBuffer) + if err == nil { + t.Fatal("GetX509CertificateFromPEM should have failed as bytes passed was not in correct format") + } + if certificate != nil { + t.Fatalf("GetX509CertificateFromPEM should have failed as bytes passed was not in correct format") + } + } +// This test case has been removed temporarily +// as BCCSP does not have support for RSA private key import +/* func TestRSACreateToken(t *testing.T) { cert, _ := ioutil.ReadFile(getPath("rsa.pem")) privKey, _ := ioutil.ReadFile(getPath("rsa-key.pem")) body := []byte("request byte array") - RSAtoken, err := CreateToken(cert, privKey, body) + + csp, error := getDefaultBCCSPInstance() + if error != nil { + t.Errorf("Default BCCSP instance failed with error %s", error) + } + + RSAtoken, err := CreateToken(csp, cert, privKey, body) if err != nil { - t.Fatalf("CreatToken failed: %s", err) + t.Fatalf("CreatToken failed with error : %s", err) } - _, err = VerifyToken(RSAtoken, body) + + _, err = VerifyToken(csp, RSAtoken, body) if err != nil { - t.Fatalf("VerifyToken failed: %s", err) + t.Fatalf("VerifyToken failed with error : %s", err) } } +*/ func TestCreateTokenDiffKey(t *testing.T) { cert, _ := ioutil.ReadFile(getPath("ec.pem")) privKey, _ := ioutil.ReadFile(getPath("rsa-key.pem")) body := []byte("request byte array") - _, err := CreateToken(cert, privKey, body) + + csp, error := getDefaultBCCSPInstance() + if error != nil { + t.Errorf("Default BCCSP instance failed with error %s", error) + } + _, err := CreateToken(csp, cert, privKey, body) if err == nil { t.Fatalf("TestCreateTokenDiffKey passed but should have failed") } } +// TestCreateTokenDiffKey2 has been commeted out right now +// As there BCCSP does not have support fot RSA private Key +// import. This will be uncommented when the support is in. +/* func TestCreateTokenDiffKey2(t *testing.T) { cert, _ := ioutil.ReadFile(getPath("rsa.pem")) privKey, _ := ioutil.ReadFile(getPath("ec-key.pem")) body := []byte("request byte array") - _, err := CreateToken(cert, privKey, body) + + csp, error := getDefaultBCCSPInstance() + if error != nil { + t.Errorf("Default BCCSP instance failed with error %s", error) + } + _, err := CreateToken(csp, cert, privKey, body) if err == nil { t.Fatalf("TestCreateTokenDiffKey2 passed but should have failed") } } +*/ func TestEmptyToken(t *testing.T) { body := []byte("request byte array") - _, err := VerifyToken("", body) + + csp, error := getDefaultBCCSPInstance() + if error != nil { + t.Errorf("Default BCCSP instance failed with error %s", error) + } + _, err := VerifyToken(csp, "", body) if err == nil { t.Fatalf("TestEmptyToken passed but should have failed") } } func TestEmptyCert(t *testing.T) { - cert, _ := ioutil.ReadFile(getPath("rsa.pem")) + cert, _ := ioutil.ReadFile(getPath("ec.pem")) body := []byte("request byte array") - _, err := CreateToken(cert, []byte(""), body) + + csp, error := getDefaultBCCSPInstance() + if error != nil { + t.Errorf("Default BCCSP instance failed with error %s", error) + } + _, err := CreateToken(csp, cert, []byte(""), body) if err == nil { t.Fatalf("TestEmptyCert passed but should have failed") } @@ -105,7 +212,13 @@ func TestEmptyCert(t *testing.T) { func TestEmptyKey(t *testing.T) { privKey, _ := ioutil.ReadFile(getPath("ec-key.pem")) body := []byte("request byte array") - _, err := CreateToken([]byte(""), privKey, body) + + csp, error := getDefaultBCCSPInstance() + if error != nil { + t.Errorf("Default BCCSP instance failed with error %s", error) + } + + _, err := CreateToken(csp, []byte(""), privKey, body) if err == nil { t.Fatalf("TestEmptyKey passed but should have failed") } @@ -114,7 +227,13 @@ func TestEmptyKey(t *testing.T) { func TestEmptyBody(t *testing.T) { cert, _ := ioutil.ReadFile(getPath("ec.pem")) privKey, _ := ioutil.ReadFile(getPath("ec-key.pem")) - _, err := CreateToken(cert, privKey, []byte("")) + + csp, error := getDefaultBCCSPInstance() + if error != nil { + t.Errorf("Default BCCSP instance failed with error %s", error) + } + + _, err := CreateToken(csp, cert, privKey, []byte("")) if err != nil { t.Fatalf("CreateToken failed: %s", err) } @@ -170,12 +289,20 @@ func TestGetDefaultConfigFile(t *testing.T) { os.Setenv("HOME", "/tmp") - defConfigFile := filepath.Join("/tmp", ".fabric-ca-client/fabric-ca-client-config.yaml") - defConfig := GetDefaultConfigFile("fabric-ca-client") - if defConfigFile != defConfig { - t.Errorf("Incorrect default config (%s) path retrieved", defConfig) + expected := filepath.Join("/tmp", ".fabric-ca-client/fabric-ca-client-config.yaml") + real := GetDefaultConfigFile("fabric-ca-client") + if real != expected { + t.Errorf("Incorrect default config path retrieved; expected %s but found %s", + expected, real) } + os.Setenv("FABRIC_CA_HOME", "/tmp") + expected = filepath.Join("/tmp", "fabric-ca-server-config.yaml") + real = GetDefaultConfigFile("fabric-ca-server") + if real != expected { + t.Errorf("Incorrect default config path retrieved; expected %s but found %s", + expected, real) + } } func TestUnmarshal(t *testing.T) { @@ -280,3 +407,15 @@ func makeFileAbs(t *testing.T, file, dir, expect string) { t.Errorf("Absolute of file=%s with dir=%s expected %s but was %s", file, dir, expect, path) } } + +func getDefaultBCCSPInstance() (bccsp.BCCSP, error) { + defaultBccsp, bccspError := factory.GetDefault() + if bccspError != nil { + return nil, fmt.Errorf("BCCSP initialiazation failed with error : %s", bccspError) + } + if defaultBccsp == nil { + return nil, errors.New("Cannot get default instance of BCCSP") + } + + return defaultBccsp, nil +}