diff --git a/pkg/server/commits.go b/pkg/server/commits.go index 6694fd4c..bf9b647e 100644 --- a/pkg/server/commits.go +++ b/pkg/server/commits.go @@ -183,7 +183,7 @@ func RunBuild(c *gin.Context) { if repo.Params != nil && len(repo.Params) != 0 { raw = []byte(inject.InjectSafe(string(raw), repo.Params)) } - encrypted, _ := secure.Parse(repo.Hash, string(raw)) + encrypted, _ := secure.Parse(repo.Keys.Private, repo.Hash, string(raw)) if encrypted != nil && len(encrypted) != 0 { raw = []byte(inject.InjectSafe(string(raw), encrypted)) } diff --git a/pkg/server/hooks.go b/pkg/server/hooks.go index a5bb9948..b2d1ab46 100644 --- a/pkg/server/hooks.go +++ b/pkg/server/hooks.go @@ -101,7 +101,7 @@ func PostHook(c *gin.Context) { if repo.Params != nil && len(repo.Params) != 0 { raw = []byte(inject.InjectSafe(string(raw), repo.Params)) } - encrypted, _ := secure.Parse(repo.Hash, string(raw)) + encrypted, _ := secure.Parse(repo.Keys.Private, repo.Hash, string(raw)) if encrypted != nil && len(encrypted) != 0 { raw = []byte(inject.InjectSafe(string(raw), encrypted)) } diff --git a/pkg/server/repos.go b/pkg/server/repos.go index c3931660..8e780916 100644 --- a/pkg/server/repos.go +++ b/pkg/server/repos.go @@ -253,7 +253,8 @@ func Encrypt(c *gin.Context) { in := map[string]string{} json.NewDecoder(c.Request.Body).Decode(&in) - err := secure.EncryptMap(repo.Hash, in) + privKey := sshutil.UnMarshalPrivateKey([]byte(repo.Keys.Private)) + err := secure.EncryptMap(secure.ToHash(repo.Hash), &privKey.PublicKey, in) if err != nil { c.Fail(500, err) return @@ -261,7 +262,7 @@ func Encrypt(c *gin.Context) { c.JSON(200, &in) } -// Unubscribe accapets a request to unsubscribe the +// Unsubscribe accapets a request to unsubscribe the // currently authenticated user to the repository. // // DEL /api/subscribers/:owner/:name diff --git a/pkg/utils/sshutil/sshutil.go b/pkg/utils/sshutil/sshutil.go index 197265f0..4348b7b4 100644 --- a/pkg/utils/sshutil/sshutil.go +++ b/pkg/utils/sshutil/sshutil.go @@ -4,7 +4,9 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" + "encoding/base64" "encoding/pem" + "hash" "github.com/drone/drone/Godeps/_workspace/src/code.google.com/p/go.crypto/ssh" ) @@ -38,15 +40,35 @@ func MarshalPrivateKey(privkey *rsa.PrivateKey) []byte { return privateKeyPEM } -// helper function to encrypt a plain-text string using -// an RSA public key. -func Encrypt(pubkey *rsa.PublicKey, msg string) ([]byte, error) { - return rsa.EncryptPKCS1v15(rand.Reader, pubkey, []byte(msg)) +// UnMarshalPrivateKey is a helper function that unmarshals a PEM +// bytes to an RSA Private Key +func UnMarshalPrivateKey(privateKeyPEM []byte) *rsa.PrivateKey { + derBlock, _ := pem.Decode(privateKeyPEM) + privateKey, err := x509.ParsePKCS1PrivateKey(derBlock.Bytes) + + if err != nil { + return nil + } + return privateKey } -// helper function to encrypt a plain-text string using +// Encrypt is helper function to encrypt a plain-text string using // an RSA public key. -func Decrypt(privkey *rsa.PrivateKey, secret string) (string, error) { - msg, err := rsa.DecryptPKCS1v15(rand.Reader, privkey, []byte(secret)) - return string(msg), err +func Encrypt(hash hash.Hash, pubkey *rsa.PublicKey, msg string) (string, error) { + src, err := rsa.EncryptOAEP(hash, rand.Reader, pubkey, []byte(msg), nil) + + return base64.StdEncoding.EncodeToString(src), err +} + +// Decrypt is helper function to encrypt a plain-text string using +// an RSA public key. +func Decrypt(hash hash.Hash, privkey *rsa.PrivateKey, secret string) (string, error) { + decoded, err := base64.StdEncoding.DecodeString(secret) + if err != nil { + return "", err + } + + out, err := rsa.DecryptOAEP(hash, rand.Reader, privkey, decoded, nil) + + return string(out), err } diff --git a/pkg/utils/sshutil/sshutil_test.go b/pkg/utils/sshutil/sshutil_test.go new file mode 100644 index 00000000..051f4748 --- /dev/null +++ b/pkg/utils/sshutil/sshutil_test.go @@ -0,0 +1,40 @@ +package sshutil + +import ( + "crypto/sha256" + "testing" + + "github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin" +) + +func TestSSHUtil(t *testing.T) { + + g := goblin.Goblin(t) + g.Describe("sshutil", func() { + var encrypted, testMsg string + + privkey, err := GeneratePrivateKey() + g.Assert(err == nil).IsTrue() + pubkey := privkey.PublicKey + sha256 := sha256.New() + testMsg = "foo=bar" + + g.Before(func() { + encrypted, err = Encrypt(sha256, &pubkey, testMsg) + g.Assert(err == nil).IsTrue() + }) + + g.It("Can decrypt encrypted msg", func() { + decrypted, err := Decrypt(sha256, privkey, encrypted) + g.Assert(err == nil).IsTrue() + g.Assert(decrypted == testMsg).IsTrue() + }) + + g.It("Unmarshals private key from PEM block", func() { + privateKeyPEM := MarshalPrivateKey(privkey) + privateKey := UnMarshalPrivateKey(privateKeyPEM) + + g.Assert(privateKey.PublicKey.E == pubkey.E).IsTrue() + }) + }) +} diff --git a/pkg/yaml/secure/secure.go b/pkg/yaml/secure/secure.go index 3ad71c20..09e28264 100644 --- a/pkg/yaml/secure/secure.go +++ b/pkg/yaml/secure/secure.go @@ -1,79 +1,37 @@ package secure import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/base64" - "fmt" - "io" + "crypto/rsa" + "crypto/sha256" + "hash" "github.com/drone/drone/Godeps/_workspace/src/gopkg.in/yaml.v2" + + "github.com/drone/drone/pkg/utils/sshutil" ) // Parse parses and returns the secure section of the // yaml file as plaintext parameters. -func Parse(key, raw string) (map[string]string, error) { +func Parse(privateKeyPEM, repoHash, raw string) (map[string]string, error) { params, err := parseSecure(raw) if err != nil { return nil, err } - err = DecryptMap(key, params) + + hasher := ToHash(repoHash) + privKey := sshutil.UnMarshalPrivateKey([]byte(privateKeyPEM)) + + err = DecryptMap(hasher, privKey, params) return params, err } -// Encrypt encrypts a string to base64 crypto using AES. -func Encrypt(key, text string) (_ string, err error) { - plaintext := []byte(text) - - block, err := aes.NewCipher(trimKey(key)) - if err != nil { - return - } - - ciphertext := make([]byte, aes.BlockSize+len(plaintext)) - iv := ciphertext[:aes.BlockSize] - if _, err = io.ReadFull(rand.Reader, iv); err != nil { - return - } - - stream := cipher.NewCFBEncrypter(block, iv) - stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) - - return base64.URLEncoding.EncodeToString(ciphertext), nil -} - -// Decrypt decrtyps from base64 to decrypted string. -func Decrypt(key, text string) (_ string, err error) { - ciphertext, err := base64.URLEncoding.DecodeString(text) - if err != nil { - return - } - - block, err := aes.NewCipher(trimKey(key)) - if err != nil { - return - } - - if len(ciphertext) < aes.BlockSize { - err = fmt.Errorf("ciphertext too short") - return - } - iv := ciphertext[:aes.BlockSize] - ciphertext = ciphertext[aes.BlockSize:] - - stream := cipher.NewCFBDecrypter(block, iv) - stream.XORKeyStream(ciphertext, ciphertext) - - return fmt.Sprintf("%s", ciphertext), nil -} - -// DecryptMap decrypts a map of named parameters -// from base64 to decrypted string. -func DecryptMap(key string, params map[string]string) error { +// DecryptMap decrypts values of a map of named parameters +// from base64 to decrypted strings. +func DecryptMap(hasher hash.Hash, privKey *rsa.PrivateKey, params map[string]string) error { var err error - for name, value := range params { - params[name], err = Decrypt(key, value) + + for name, encrypted := range params { + params[name], err = sshutil.Decrypt(hasher, privKey, encrypted) if err != nil { return err } @@ -81,12 +39,12 @@ func DecryptMap(key string, params map[string]string) error { return nil } -// EncryptMap encrypts encrypts a map of string parameters -// to base64 crypto using AES. -func EncryptMap(key string, params map[string]string) error { +// EncryptMap encrypts values of a map of named parameters +func EncryptMap(hasher hash.Hash, pubKey *rsa.PublicKey, params map[string]string) error { var err error + for name, value := range params { - params[name], err = Encrypt(key, value) + params[name], err = sshutil.Encrypt(hasher, pubKey, value) if err != nil { return err } @@ -94,22 +52,21 @@ func EncryptMap(key string, params map[string]string) error { return nil } -// helper function that trims a key to a maximum -// of 32 bytes to match the expected AES block size. -func trimKey(key string) []byte { - b := []byte(key) - if len(b) > 32 { - b = b[:32] - } - return b -} - -// helper function to parse the Secure data from +// parseSecure is helper function to parse the Secure data from // the raw yaml file. func parseSecure(raw string) (map[string]string, error) { data := struct { Secure map[string]string }{} err := yaml.Unmarshal([]byte(raw), &data) + return data.Secure, err } + +// ToHash is helper function to generate Hash of given string +func ToHash(key string) hash.Hash { + hasher := sha256.New() + hasher.Write([]byte(key)) + hasher.Reset() + return hasher +} diff --git a/pkg/yaml/secure/secure_test.go b/pkg/yaml/secure/secure_test.go index 8fed79f1..69a9e546 100644 --- a/pkg/yaml/secure/secure_test.go +++ b/pkg/yaml/secure/secure_test.go @@ -4,58 +4,35 @@ import ( "testing" "github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin" + + "github.com/drone/drone/pkg/utils/sshutil" ) func Test_Secure(t *testing.T) { g := goblin.Goblin(t) g.Describe("Encrypt params", func() { + privKey, _ := sshutil.GeneratePrivateKey() + publicKey := &privKey.PublicKey - key := "9T2tH3qZ8FSPr9uxrhzV4mn2VdVgA56xPVtYvCh0" + privateKeyPEM := string(sshutil.MarshalPrivateKey(privKey)) + + repoHash := "9T2tH3qZ8FSPr9uxrhzV4mn2VdVgA56xPVtYvCh0" + hashKey := ToHash(repoHash) text := "super_duper_secret" - long := "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,32495A90F3FF199D\nlrMAsSjjkKiRxGdgR8p5kZJj0AFgdWYa3OT2snIXnN5+/p7j13PSkseUcrAFyokc\nV9pgeDfitAhb9lpdjxjjuxRcuQjBfmNVLPF9MFyNOvhrprGNukUh/12oSKO9dFEt\ns39F/2h6Ld5IQrGt3gZaBB1aGO+tw3ill1VBy2zGPIDeuSz6DS3GG/oQ2gLSSMP4\nOVfQ32Oajo496iHRkdIh/7Hho7BNzMYr1GxrYTcE9/Znr6xgeSdNT37CCeCH8cmP\naEAUgSMTeIMVSpILwkKeNvBURic1EWaqXRgPRIWK0vNyOCs/+jNoFISnV4pu1ROF\n92vayHDNSVw9wHcdSQ75XSE4Msawqv5U1iI7e2lD64uo1qhmJdrPcXDJQCiDbh+F\nhQhF+wAoLRvMNwwhg+LttL8vXqMDQl3olsWSvWPs6b/MZpB0qwd1bklzA6P+PeAU\nsfOvTqi9edIOfKqvXqTXEhBP8qC7ZtOKLGnryZb7W04SSVrNtuJUFRcLiqu+w/F/\nMSxGSGalYpzIZ1B5HLQqISgWMXdbt39uMeeooeZjkuI3VIllFjtybecjPR9ZYQPt\nFFEP1XqNXjLFmGh84TXtvGLWretWM1OZmN8UKKUeATqrr7zuh5AYGAIbXd8BvweL\nPigl9ei0hTculPqohvkoc5x1srPBvzHrirGlxOYjW3fc4kDgZpy+6ik5k5g7JWQD\nlbXCRz3HGazgUPeiwUr06a52vhgT7QuNIUZqdHb4IfCYs2pQTLHzQjAqvVk1mm2D\nkh4myIcTtf69BFcu/Wuptm3NaKd1nwk1squR6psvcTXOWII81pstnxNYkrokx4r2\n7YVllNruOD+cMDNZbIG2CwT6V9ukIS8tl9EJp8eyb0a1uAEc22BNOjYHPF50beWF\nukf3uc0SA+G3zhmXCM5sMf5OxVjKr5jgcir7kySY5KbmG71omYhczgr4H0qgxYo9\nZyj2wMKrTHLfFOpd4OOEun9Gi3srqlKZep7Hj7gNyUwZu1qiBvElmBVmp0HJxT0N\nmktuaVbaFgBsTS0/us1EqWvCA4REh1Ut/NoA9oG3JFt0lGDstTw1j+orDmIHOmSu\n7FKYzr0uCz14AkLMSOixdPD1F0YyED1NMVnRVXw77HiAFGmb0CDi2KEg70pEKpn3\nksa8oe0MQi6oEwlMsAxVTXOB1wblTBuSBeaECzTzWE+/DHF+QQfQi8kAjjSdmmMJ\nyN+shdBWHYRGYnxRkTatONhcDBIY7sZV7wolYHz/rf7dpYUZf37vdQnYV8FpO1um\nYa0GslyRJ5GqMBfDS1cQKne+FvVHxEE2YqEGBcOYhx/JI2soE8aA8W4XffN+DoEy\nZkinJ/+BOwJ/zUI9GZtwB4JXqbNEE+j7r7/fJO9KxfPp4MPK4YWu0H0EUWONpVwe\nTWtbRhQUCOe4PVSC/Vv1pstvMD/D+E/0L4GQNHxr+xyFxuvILty5lvFTxoAVYpqD\nu8gNhk3NWefTrlSkhY4N+tPP6o7E4t3y40nOA/d9qaqiid+lYcIDB0cJTpZvgeeQ\nijohxY3PHruU4vVZa37ITQnco9az6lsy18vbU0bOyK2fEZ2R9XVO8fH11jiV8oGH\n-----END RSA PRIVATE KEY-----" - - g.It("Should encrypt a string", func() { - encrypted, err := Encrypt(key, text) - g.Assert(err == nil).IsTrue() - decrypted, err := Decrypt(key, encrypted) - g.Assert(err == nil).IsTrue() - g.Assert(text).Equal(decrypted) - }) - - g.It("Should encrypt a long string", func() { - encrypted, err := Encrypt(key, long) - g.Assert(err == nil).IsTrue() - decrypted, err := Decrypt(key, encrypted) - g.Assert(err == nil).IsTrue() - g.Assert(long).Equal(decrypted) - }) - - g.It("Should decrypt a map", func() { - params := map[string]string{ - "foo": "2NQPoQfxPERVi42OEYzuVTjQrEQSrcN2-Pwk4kTlIVN5HA==", - } - err := DecryptMap(key, params) - g.Assert(err == nil).IsTrue() - g.Assert(params["foo"]).Equal("super_duper_secret") - }) - - g.It("Should trim a key with blocksize greater than 32 bytes", func() { - trimmed := trimKey("9T2tH3qZ8FSPr9uxrhzV4mn2VdVgA56x") - g.Assert(len(key) > 32).IsTrue() - g.Assert(len(trimmed)).Equal(32) - }) + encryptedValue, _ := sshutil.Encrypt(hashKey, publicKey, text) g.It("Should decrypt a yaml", func() { - yaml := `secure: {"foo": "2NQPoQfxPERVi42OEYzuVTjQrEQSrcN2-Pwk4kTlIVN5HA=="}` - decrypted, err := Parse(key, yaml) + yaml := "secure: {\"foo\": \"" + encryptedValue + "\"}" + decrypted, err := Parse(privateKeyPEM, repoHash, yaml) + g.Assert(err == nil).IsTrue() - g.Assert(decrypted["foo"]).Equal("super_duper_secret") + g.Assert(decrypted["foo"]).Equal(text) }) g.It("Should decrypt a yaml with no secure section", func() { yaml := `foo: bar` - decrypted, err := Parse(key, yaml) + decrypted, err := Parse(privateKeyPEM, repoHash, yaml) g.Assert(err == nil).IsTrue() g.Assert(len(decrypted)).Equal(0) }) @@ -64,10 +41,10 @@ func Test_Secure(t *testing.T) { params := map[string]string{ "foo": text, } - err := EncryptMap(key, params) + err := EncryptMap(hashKey, publicKey, params) g.Assert(err == nil).IsTrue() g.Assert(params["foo"] == "super_duper_secret").IsFalse() - err = DecryptMap(key, params) + err = DecryptMap(hashKey, privKey, params) g.Assert(err == nil).IsTrue() g.Assert(params["foo"] == "super_duper_secret").IsTrue() })