diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..cfa4370189676c011d73d45d4111e383ea8f3b27 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +bin/ +vendor/ +Gopkg.lock + diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000000000000000000000000000000000000..6e4954b45287512568d441b89e2ef45c00efcee1 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,27 @@ +[[constraint]] + branch = "master" + name = "code.vereign.com/code/viam-apis" + +[[constraint]] + branch = "master" + name = "code.vereign.com/code/data-storage-agent" + +[[constraint]] + name = "github.com/golang/protobuf" + version = "1.1.0" + +[[constraint]] + name = "github.com/grpc-ecosystem/grpc-gateway" + version = "1.4.1" + +[[constraint]] + branch = "master" + name = "golang.org/x/net" + +[[constraint]] + name = "google.golang.org/grpc" + version = "1.13.0" + +[prune] + go-tests = true + unused-packages = true diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e84137fd9f75576cddc61728006ea28dc499862f --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ + +Copyright (c) 2018 Vereign AG [https://www.vereign.com] + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d95e1e5cec40a31c8f4fee3b276f5e0c0d439673 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +SERVER_OUT := "bin/server" +PKG := "code.vereign.com/code/key-storage-agent" +SERVER_PKG_BUILD := "${PKG}" +PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/) + +.PHONY: all api server + +all: server + +dep: ## Get the dependencies + dep ensure -update +server: dep ## Build the binary file for server + @go build -i -v -o $(SERVER_OUT) $(SERVER_PKG_BUILD) + +clean: ## Remove previous builds + @rm $(SERVER_OUT) + +help: ## Display this help screen + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/cert/server.crt b/cert/server.crt new file mode 100644 index 0000000000000000000000000000000000000000..ffc52d260e9fac51405c58e02317690c9e79cfa2 --- /dev/null +++ b/cert/server.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPjCCAiYCCQDpx954xyvbgjANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJG +UjEMMAoGA1UECAwDaWRmMQ4wDAYDVQQHDAVQYXJpczESMBAGA1UECgwJUGFudG9t +YXRoMQwwCgYDVQQLDANub2MxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNzEwMDUx +NTI5MzZaFw0yNzEwMDMxNTI5MzZaMGExCzAJBgNVBAYTAkZSMQwwCgYDVQQIDANp +ZGYxDjAMBgNVBAcMBVBhcmlzMRIwEAYDVQQKDAlQYW50b21hdGgxDDAKBgNVBAsM +A25vYzESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA9UFeeiJ5Gyi5MZGEI0ME8v4IikVByiBgwqn6PH/bYuJwRoR3acZg +tiiMS1pyfUBSQ2iTLRzrkvFd5rXByXWK4+6MeqYdAzAyQzgk6/1U58oPzGrZCRYe +b3Bm7QvS9rl00keO37gE8ETpatL8rCQt9Qsl88ah1BfCVuDdFtBdOW2Qz1i6qGUv +pkTSJDZBmE3gjWGHIp4UjcdshFlTEjmFfcKtNJtMuhnKZIgo6KZcN1Trvyf4aUUM +zQbPFm2jGd5lUFZJQvSQ00k+TF4YrbuDVfhozoxBrbsoaRXkVWVYC1fYey89FY1n +9zFyxB6OF32EIry4Kn5Tu6AG9+9z/CU3gwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB +AQDUeByNiVS/XZgc4BXO5JPXY98orVZKfXEEWKzMfzPDxW925k2IpnnCpT4WkAe4 +sUR7C5efGPyv0TMTzNeXGrkB7lK/9WGWRrlR/bI0kdad7/p7Qx+5hC/nE2HWZYQo +5JYj8tEfetY3aV64rFllcq2hfI71dMML05GwoVaKaMc9Q1ccwIZAbkXR2Sifwsn9 ++UNNsP5hR+7kQh+Dqd/+qEySp1+0ZJ1LmRmRes37MlJI9KSoC1uANwcB5+4ZFrba +LHrkszk9nxk09Y/tLGYlvvf23y1BdhcqT2EbUZX7jD/jEDC0kZ5yxDE1UDk+pnZp +UaEcVsgg/b9dMESt38f6ICK/ +-----END CERTIFICATE----- diff --git a/cert/server.csr b/cert/server.csr new file mode 100644 index 0000000000000000000000000000000000000000..5662bf115417fa8cc05a5c987aa46e4bfbb229dd --- /dev/null +++ b/cert/server.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICpjCCAY4CAQAwYTELMAkGA1UEBhMCRlIxDDAKBgNVBAgMA2lkZjEOMAwGA1UE +BwwFUGFyaXMxEjAQBgNVBAoMCVBhbnRvbWF0aDEMMAoGA1UECwwDbm9jMRIwEAYD +VQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD1 +QV56InkbKLkxkYQjQwTy/giKRUHKIGDCqfo8f9ti4nBGhHdpxmC2KIxLWnJ9QFJD +aJMtHOuS8V3mtcHJdYrj7ox6ph0DMDJDOCTr/VTnyg/MatkJFh5vcGbtC9L2uXTS +R47fuATwROlq0vysJC31CyXzxqHUF8JW4N0W0F05bZDPWLqoZS+mRNIkNkGYTeCN +YYcinhSNx2yEWVMSOYV9wq00m0y6GcpkiCjoplw3VOu/J/hpRQzNBs8WbaMZ3mVQ +VklC9JDTST5MXhitu4NV+GjOjEGtuyhpFeRVZVgLV9h7Lz0VjWf3MXLEHo4XfYQi +vLgqflO7oAb373P8JTeDAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAES8c7EOs +rU0eu3whtUumllhotNvvqBP4x46arQQE2ud0GkLkxvxqbUuGQxLWi5KCcdads+Cx +EramC/UAUgAFFj8Ll1EuSZxy1+sb5GL00uJMxatpDBHwr78fhllAWkM7jiZbh2ad +FHOg9kQcDtdfFB6XP7JM7uiXluEQRyxIutoOzIkhZZva6zg/7iWE+u6DGz3r8dWQ +WNH8gsWA3D85ZyTDVrCvp6omGx35pzwuQOoWH6nO3dCsR2smf58ShnMtE+c6uLbF +qoZRTXGAiCaH3/Cn3TXkcrclZdneCCOWidHG2ICTsTqfujDYz/CCYM23AGkqQb1Y +QMQl0LGZ+k+HAQ== +-----END CERTIFICATE REQUEST----- diff --git a/cert/server.key b/cert/server.key new file mode 100644 index 0000000000000000000000000000000000000000..e4633619d4877c9d199a723e3334cf887dece536 --- /dev/null +++ b/cert/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEA9UFeeiJ5Gyi5MZGEI0ME8v4IikVByiBgwqn6PH/bYuJwRoR3 +acZgtiiMS1pyfUBSQ2iTLRzrkvFd5rXByXWK4+6MeqYdAzAyQzgk6/1U58oPzGrZ +CRYeb3Bm7QvS9rl00keO37gE8ETpatL8rCQt9Qsl88ah1BfCVuDdFtBdOW2Qz1i6 +qGUvpkTSJDZBmE3gjWGHIp4UjcdshFlTEjmFfcKtNJtMuhnKZIgo6KZcN1Trvyf4 +aUUMzQbPFm2jGd5lUFZJQvSQ00k+TF4YrbuDVfhozoxBrbsoaRXkVWVYC1fYey89 +FY1n9zFyxB6OF32EIry4Kn5Tu6AG9+9z/CU3gwIDAQABAoIBAQDcQdXAaD9NRdh0 +DNSX+nNyavRugV5hUYy0poTmWolDmEru+b5oj1GBpo7Aib0ygVaf1UYACO4D7KLB +NNCRxe9zXmRpLc/2cg1h0wVNrxjWheCEXB0IjQXOXSsCjlDrZYjl5IAKqTA+PBVI +660iR+fCHz35XZUubhwJfC7yczSWAe8nlnFHrcUzSeqLctI18JGsq3hDf5Fy+97+ +4uuJFPwQ0mLsgOdzKvCB79ecSZdQ18hkidgpnARaM1sI501b1Tp/uUQOLUmdVnvh +/MFEWndm1th3i+gMotsBwrBCyPPeWVrcvKI2sf0LJmgaMl4/sBB0UsSKLhcbBLsj +jvABikmJAoGBAP1z/WeNDaEaypXBSOKZlMu9YuPOemS2DY5GEc204Fi0dYNDlMEh +CAu7mEsEvH66hwKn+xMnpb5hIuxQv3/2hUFTME9au2htj2MX0+qk1bqMQMlDEdda +WWybxy+jW1mYJFMVHFwAAE7hXS5peH9ZCihAu/szYt8zPdZ4X3EEEqqfAoGBAPe4 +Slp2GVd742ZA3VBpfv6802C/DA98gIdHqWNF5vJzi1HU29m+2zY6mMGl9CP3ICeX +gLL6a3bqIc8aPgJ8ULAhegKOcFqruhGFIjmd+FPRtwjiYC9jJRXbMUSmTrR0XsxG +GkK2UmBNxVNGUj++1hwji39OfsMnf6OvfVhnmOydAoGBAJk5OgUUHR08WSTXyPxU +5MOXJuWZuhyQgvl0GudFZiu6TSCiBpgLJBYTvyn7HwluMpjEfOFDosvJZZd/6YWu +vziS1i3jKFEliv3ZNeAw7pTsnW4PAgYzNMSYGH8QPvWXKL6hkJd92LHXRMH+OT6j +0aQsHnjqw+czzzqNYwWr9Kz5AoGBAKvxBeLuUD6x5jAGW9dBsn08MXfYg5WINGox +qngWf+vPmWdOWN81o1BrsbXP67q/AFmaxiD0wnzCnH701w/Am/z074wws/mrcrZQ +c2YMqN39FY+cGWkq5wXZo8Pjr4N/toERM48Un+7qbEmV6OcIHfNgFKZjpIbutqC1 +4UnodnPdAoGBANzcLKz99NXBBuAiYHgmOckh6I2RCcORgE6WDbj57qrdMco0NToU +wU9ute0SBEm6VEnBf9i3jlrPZr69f3VT4OFWeo9RoHkVmVDD+LEY5P7xqftj/XZy +2r+uvgkqZWBC3rpAORGq3F7MgD4nFcyssFKwylEGyCVCfyGIsv5+ZKMr +-----END RSA PRIVATE KEY----- diff --git a/cert/vereign_ca.cer b/cert/vereign_ca.cer new file mode 100644 index 0000000000000000000000000000000000000000..6aacfac5ee9a0c352d35ebc86d9e3a4ee848841d --- /dev/null +++ b/cert/vereign_ca.cer @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEnjCCA4agAwIBAgIEW2lRSjANBgkqhkiG9w0BAQsFADCBoTETMBEGCgmSJomT +8ixkARkWA2NvbTEXMBUGCgmSJomT8ixkARkWB3ZlcmVpZ24xCzAJBgNVBAYTAkJH +MRAwDgYDVQQIDAdQbG92ZGl2MRAwDgYDVQQHDAdQbG92ZGl2MRMwEQYDVQQKDApW +ZXJlaWduIEFHMRUwEwYDVQQLDAxWZXJlaWduIExhYnMxFDASBgNVBAMMC3ZlcmVp +Z24uY29tMB4XDTE4MDgwNzA3NTkwNloXDTI4MDgwNzA3NTkwNlowgaExEzARBgoJ +kiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgd2ZXJlaWduMQswCQYDVQQG +EwJCRzEQMA4GA1UECAwHUGxvdmRpdjEQMA4GA1UEBwwHUGxvdmRpdjETMBEGA1UE +CgwKVmVyZWlnbiBBRzEVMBMGA1UECwwMVmVyZWlnbiBMYWJzMRQwEgYDVQQDDAt2 +ZXJlaWduLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALYs0VOg +HyXuNbJhYjZBLKoR2EH95VYQ5BStX+KD7ppxlT+H4sxbuj11xVVcDj95aJvlc9Nc +yBdWktjkIi+MrqoiMM1ljuCzwSsvfnVidWGhZmqSNvEjXgAkX57NoG0U+C9AK16i +otLApBQ79sXSONfxq2fbdXlaGm98ahCLreomFqKXizkTsOmnirbouJvTX0eFXeaA ++qXPhGpsQxu7vOlWoWqgCAqPAn2krpIBKPvX8n9rL29tFs8JXft0Z5MKykCweP8D +Sn3e4nmMUDRl8F/0+r+1HIK5FWw2vKUrinMAN/YkpsfEE/flIlZCKg3D8GEYnMFQ +w9Gaz7NVH87qI8sCAwEAAaOB2zCB2DAeBgNVHREEFzAVgRNjb250YWN0QHZlcmVp +Z24uY29tMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgH+MIGaBgNVHSUEgZIwgY8G +CCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCisGAQQBgjcKAwwGCSqGSIb3 +LwEBBQYIKwYBBQUHAwQGCisGAQQBgjcKAwQGCCsGAQUFBwMFBggrBgEFBQcDBgYI +KwYBBQUHAwcGCCsGAQUFBwMIBggrBgEFBQcDCQYKKwYBBAGCNxQCAgYEVR0lADAN +BgkqhkiG9w0BAQsFAAOCAQEAMotIWeKzq8T2CZR6hzUMVjBzLfJRnKKXLwoaa3R0 +mXwOcyloQmIQkYqQ7M4ELDEmAtN/f7cE/uBhUdD8UEWn0I4Im6Al8Q7eUFPOAvW3 +uvoqEx5+0sMjsBDUxVW1xkcwYIP9Vpp7occ4Uu0dbj6WR6jsTWQFor+Fc+J4wkRq +5a4i7pnI78r41rJAHpIUqToLmcoxBOqVrjDys/scCQcxlDGgZcy7T4OFGgJGUdY0 +Zs2W/7GdZUKctb4d+2kWwvA+34w8V9lOBTwM2ddSYS/EkUYMkP93Cb0/LH9Go43u +U2lEx/41yecR4bipRcwHamTOlvpsFoC9+NjZ0m+l9EM0vA== +-----END CERTIFICATE----- diff --git a/cert/vereign_ca.key b/cert/vereign_ca.key new file mode 100644 index 0000000000000000000000000000000000000000..2bdd81dff19a2917475fa8845923b4021ce475f3 --- /dev/null +++ b/cert/vereign_ca.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2LNFToB8l7jWy +YWI2QSyqEdhB/eVWEOQUrV/ig+6acZU/h+LMW7o9dcVVXA4/eWib5XPTXMgXVpLY +5CIvjK6qIjDNZY7gs8ErL351YnVhoWZqkjbxI14AJF+ezaBtFPgvQCteoqLSwKQU +O/bF0jjX8atn23V5WhpvfGoQi63qJhail4s5E7Dpp4q26Lib019HhV3mgPqlz4Rq +bEMbu7zpVqFqoAgKjwJ9pK6SASj71/J/ay9vbRbPCV37dGeTCspAsHj/A0p93uJ5 +jFA0ZfBf9Pq/tRyCuRVsNrylK4pzADf2JKbHxBP35SJWQioNw/BhGJzBUMPRms+z +VR/O6iPLAgMBAAECggEAG9HMRZD2MLynxo3IibB6OZ5v/+Pd2b7Klb3EHrs2/K7L +s9/0anC3iBsr/1UHd/n6V5Q6k9RfWfEGi8iKz+gT5DdEbJlNsFLC9O1Tymk2s6oK +EcwyR7C28h6b24xbK01AeTa7aMA9TSHN7KkbjioENDXbjwicb+OqlcvSBqTN8iuM +aZrpLHG1ddd7qceUsJuZWkrtC8/RIv/OOPbjdCx2US24T1AgpSANgReHWqk2C8aK +Bo/Np2NhKKr0lmWN7mhXKBV/OVtM8QPFd8JfsI4vQByd3aV0R5GJpBgX/sP2rQi5 +CnZSV45f6OrYNbzK3DgNdVLFqFUdvAdyroyClTDQgQKBgQDzm6WnmjKg20wUi3tb +Wy7syU8Ss4WBFeCjt5aVkqeILgdeMwy+FBnXZNfU5enNBTjPMRcAfPuVm0jet79H +NLgHrTU80sb5fQ0fENlS031OOdEG0PjeoCCGfGsf/YFsmS2WYSHlkYlbibItCmzn +WTVmu0KIQ3H82YDqz7vu/NQ0WQKBgQC/cSsdwPrMVT1XcSANB77/81v2t3ZVrckx +gp1KNWKEmaVgWfFsNQ780d4HQAdhKAa9eKRLAL3R7hIDOGXSTPkxEZ95LwkzbNlf +qSnlxTwhRY41+OHYdjHMzCTu8qoumwPLCrFkKBdU/s5z84HA8KU1nAIAJzz1mNAN +qbT+Bv/kwwKBgCTSV/23bwOlYiCQ3Lp4U+VyoEMhY4KZffUBIP/GxQ/udSql6L0q +aKWIFp+ViPt2WJnov6NRQO3iJOeVOpJWw6JVagChk3XOkxcpAtBkK0KRtqijGZr8 +9S2ezMpvFQsHND7Qu8DpeKufapEoTEHD2DCJCYtzNl2TusrDT5LWIHUxAoGADCwe +6LJnf+x/jPrFZe6zJ0UK+OHrZUE6hKpgY+KHFBVM3ZZ6cj3haRPTATUCAxxvaUat +c5Nlfl6byJaiar+4LHWJZUQnWpy2KY3w+woSa68nfqkHeyLwwavNQWAuj+4NTLCu +XMbrzNyytc6q1mC2sHTt76KPDrKbr/K1bl11kc8CgYEAodKJD6bP4w1vWDR08/HU +T4I0XaKq5X150BbwQZO0axEsuiVwqsosioXYSVAW85NOWSTNDSgpVPCjyWRyHyRL +cVVrWMQda3Hc/s6EzsiqC0BGw9D0r3TZN8n7Wm+BoNkek//xPhoiLfNTKtwRZIcr +CikzGE28ynzRwrDK70wV7Q4= +-----END PRIVATE KEY----- diff --git a/handler/generate_certificate.go b/handler/generate_certificate.go new file mode 100644 index 0000000000000000000000000000000000000000..07caca54c709261075ae497db352e848c7c74f95 --- /dev/null +++ b/handler/generate_certificate.go @@ -0,0 +1,215 @@ +/* +Copyright (c) 2018 Vereign AG [https://www.vereign.com] + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package handler + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "io/ioutil" + "math/big" + "time" + + "code.vereign.com/code/viam-apis/data-storage-agent/client" + "code.vereign.com/code/viam-apis/key-storage-agent/api" + "code.vereign.com/code/viam-apis/utils" + "code.vereign.com/code/viam-apis/versions" + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" +) + +func (s *KeyStorageServerImpl) GenerateCertificate(ctx context.Context, in *api.GenerateCertificateRequest) (*api.GenerateCertificateResponse, error) { + auth := s.CreateAuthentication(ctx) + + client := &client.DataStorageClientImpl{} + client.SetUpClient(auth, s.DataStorageUrl, s.CertFilePath) + defer client.CloseClient() + + generateCertificateResponse := &api.GenerateCertificateResponse{} + + aesKeyBytes, err := rsaDecryptWithServerKey(s.VereignPrivateKeyFilePath, in.EncryptedAesKey, []byte("aeskeys")) + if err != nil { + generateCertificateResponse.StatusList = utils.AddStatus(generateCertificateResponse.StatusList, + "400", api.StatusType_ERROR, err.Error()) + return generateCertificateResponse, nil + } + + // Get and decrypt rsa private key + encryptedPrivateKeyMessage := &api.Key{} + data, _ := client.DoGetDataCall("keys", in.Uuid+"/"+api.KeyType.String(api.KeyType_PRIVATE)) + if data.Errors != "" { + generateCertificateResponse.StatusList = utils.AddStatus(generateCertificateResponse.StatusList, + "400", api.StatusType_ERROR, data.Errors) + return generateCertificateResponse, nil + } + proto.Unmarshal(data.Data.Data, encryptedPrivateKeyMessage) + + privateKeyBytes, err := aesDecrypt(aesKeyBytes, in.PrivateKeyNonce, encryptedPrivateKeyMessage.Content) + if err != nil { + generateCertificateResponse.StatusList = utils.AddStatus(generateCertificateResponse.StatusList, + "400", api.StatusType_ERROR, err.Error()) + return generateCertificateResponse, nil + } + + // Get and decrypt rsa public key + encryptedPublicKeyMessage := &api.Key{} + data, _ = client.DoGetDataCall("keys", in.Uuid+"/"+api.KeyType.String(api.KeyType_PUBLIC)) + if data.Errors != "" { + generateCertificateResponse.StatusList = utils.AddStatus(generateCertificateResponse.StatusList, + "400", api.StatusType_ERROR, data.Errors) + return generateCertificateResponse, nil + } + proto.Unmarshal(data.Data.Data, encryptedPublicKeyMessage) + + publicKeyBytes, err := aesDecrypt(aesKeyBytes, in.PublicKeyNonce, encryptedPublicKeyMessage.Content) + if err != nil { + generateCertificateResponse.StatusList = utils.AddStatus(generateCertificateResponse.StatusList, + "400", api.StatusType_ERROR, err.Error()) + return generateCertificateResponse, nil + } + + certificateBytes, err := generateCertificate(privateKeyBytes, publicKeyBytes, s.VereignCertFilePath, in.CertificateData) + if err != nil { + generateCertificateResponse.StatusList = utils.AddStatus(generateCertificateResponse.StatusList, + "400", api.StatusType_ERROR, err.Error()) + return generateCertificateResponse, nil + } + + certificateMessage := &api.Key{ + Content: certificateBytes, + } + + result, errors, err := client.DoPutDataCall("keys", in.Uuid+"/"+api.KeyType.String(api.KeyType_CERTIFICATE), certificateMessage, versions.EntitiesManagementAgentApiVersion) + generateCertificateResponse.StatusList = handlePutDataErrors(generateCertificateResponse.StatusList, errors, err) + + if generateCertificateResponse.StatusList == nil || len(generateCertificateResponse.StatusList) == 0 { + generateCertificateResponse.StatusList = utils.AddStatus(generateCertificateResponse.StatusList, + "200", api.StatusType_INFO, result) + } + + return generateCertificateResponse, nil +} + +func generateCertificate(privateKeyBytes []byte, publicKeyBytes []byte, caCertFilePath string, + certificateData *api.GenerateCertificateRequest_CertificateData) ([]byte, error) { + + privateKey, err := x509.ParsePKCS8PrivateKey(privateKeyBytes) + if err != nil { + return nil, err + } + publicKey, err := x509.ParsePKIXPublicKey(publicKeyBytes) + if err != nil { + return nil, err + } + + notBeforeTime := time.Unix(certificateData.NotBefore.Seconds, int64(certificateData.NotBefore.Nanos)).UTC() + notAfterTime := time.Unix(certificateData.NotAfter.Seconds, int64(certificateData.NotAfter.Nanos)).UTC() + + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Country: []string{certificateData.Country}, + Organization: []string{certificateData.Organization}, + OrganizationalUnit: []string{certificateData.OrganizationalUnit}, + CommonName: certificateData.CommonName, + }, + NotBefore: notBeforeTime, + NotAfter: notAfterTime, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: false, + DNSNames: []string{certificateData.Host}, + } + + caCertificate, err := readCertificateFromFile(caCertFilePath) + if err != nil { + return nil, err + } + + certificateBytes, err := x509.CreateCertificate(rand.Reader, &template, caCertificate, publicKey, privateKey) + if err != nil { + return nil, err + } + + return certificateBytes, nil +} + +func readPrivateKeyFromFile(fileName string) (*rsa.PrivateKey, error) { + privateKeyPemBlock, err := readPemBlockFromFile(fileName) + if err != nil { + return nil, err + } + + parseResult, err := x509.ParsePKCS8PrivateKey(privateKeyPemBlock.Bytes) + if err != nil { + return nil, err + } + privateKey := parseResult.(*rsa.PrivateKey) + + return privateKey, nil +} + +func readPemBlockFromFile(fileName string) (*pem.Block, error) { + fileBytes, err := ioutil.ReadFile(fileName) + if err != nil { + return nil, err + } + + certificatePemBlock, _ := pem.Decode(fileBytes) + + return certificatePemBlock, nil +} + +func rsaDecryptWithServerKey(privateKeyFilePath string, encryptedMessage []byte, label []byte) ([]byte, error) { + serverPrivateKey, err := readPrivateKeyFromFile(privateKeyFilePath) + if err != nil { + return nil, err + } + + message, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, serverPrivateKey, encryptedMessage, label) + if err != nil { + return nil, err + } + + return message, nil +} + +func aesDecrypt(aesKey []byte, nonce []byte, encryptedMessage []byte) ([]byte, error) { + block, err := aes.NewCipher(aesKey) + if err != nil { + return nil, err + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + message, err := aesgcm.Open(nil, nonce, encryptedMessage, nil) + if err != nil { + return nil, err + } + + return message, nil +} diff --git a/handler/generate_keypair.go b/handler/generate_keypair.go new file mode 100644 index 0000000000000000000000000000000000000000..0b106cd0e7c58d9b59b82a9b8fcb1709b26ddf5e --- /dev/null +++ b/handler/generate_keypair.go @@ -0,0 +1,175 @@ +/* +Copyright (c) 2018 Vereign AG [https://www.vereign.com] + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package handler + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + + "code.vereign.com/code/viam-apis/data-storage-agent/client" + "code.vereign.com/code/viam-apis/key-storage-agent/api" + "code.vereign.com/code/viam-apis/utils" + "code.vereign.com/code/viam-apis/versions" + "golang.org/x/net/context" +) + +func (s *KeyStorageServerImpl) GenerateKeyPair(ctx context.Context, in *api.GenerateKeyPairRequest) (*api.GenerateKeyPairResponse, error) { + auth := s.CreateAuthentication(ctx) + + client := &client.DataStorageClientImpl{} + client.SetUpClient(auth, s.DataStorageUrl, s.CertFilePath) + defer client.CloseClient() + + generateKeyPairResponse := &api.GenerateKeyPairResponse{} + + uuid, err := generateUnusedUUID(client) + if err != nil { + generateKeyPairResponse.StatusList = utils.AddStatus(generateKeyPairResponse.StatusList, + "500", api.StatusType_ERROR, err.Error()) + } + + privateKeyBytes, publicKeyBytes, err := generateKeyPair(int(in.KeySize)) + if err != nil { + generateKeyPairResponse.StatusList = utils.AddStatus(generateKeyPairResponse.StatusList, + "500", api.StatusType_ERROR, err.Error()) + } + + aesKeyBytes, err := generateRandomSequence(256) + if err != nil { + generateKeyPairResponse.StatusList = utils.AddStatus(generateKeyPairResponse.StatusList, + "500", api.StatusType_ERROR, err.Error()) + } + + encryptedPrivateKeyBytes, privateKeyNonce, err := aesEncrypt(aesKeyBytes, privateKeyBytes) + if err != nil { + generateKeyPairResponse.StatusList = utils.AddStatus(generateKeyPairResponse.StatusList, + "500", api.StatusType_ERROR, err.Error()) + return generateKeyPairResponse, nil + } + encryptedPrivateKey := &api.Key{Content: encryptedPrivateKeyBytes} + result, errors, err := client.DoPutDataCall("keys", uuid+"/"+api.KeyType.String(api.KeyType_PRIVATE), encryptedPrivateKey, versions.EntitiesManagementAgentApiVersion) + generateKeyPairResponse.StatusList = handlePutDataErrors(generateKeyPairResponse.StatusList, errors, err) + + publicKeyNonce := []byte{} + if generateKeyPairResponse.StatusList == nil || len(generateKeyPairResponse.StatusList) == 0 { + encryptedPublicKeyBytes, publicKeyNonceLocal, err := aesEncrypt(aesKeyBytes, publicKeyBytes) + publicKeyNonce = publicKeyNonceLocal + if err != nil { + generateKeyPairResponse.StatusList = utils.AddStatus(generateKeyPairResponse.StatusList, + "500", api.StatusType_ERROR, err.Error()) + return generateKeyPairResponse, nil + } + encryptedPublicKey := &api.Key{Content: encryptedPublicKeyBytes} + result, errors, err = client.DoPutDataCall("keys", uuid+"/"+api.KeyType.String(api.KeyType_PUBLIC), encryptedPublicKey, versions.EntitiesManagementAgentApiVersion) + generateKeyPairResponse.StatusList = handlePutDataErrors(generateKeyPairResponse.StatusList, errors, err) + } + + encryptedAesKeyBytes, err := rsaEncryptWithServerKey(s.VereignCertFilePath, aesKeyBytes, []byte("aeskeys")) + if err != nil { + generateKeyPairResponse.StatusList = utils.AddStatus(generateKeyPairResponse.StatusList, + "500", api.StatusType_ERROR, err.Error()) + return generateKeyPairResponse, nil + } + + if generateKeyPairResponse.StatusList == nil || len(generateKeyPairResponse.StatusList) == 0 { + generateKeyPairResponse.Uuid = uuid + generateKeyPairResponse.EncryptedAesKey = encryptedAesKeyBytes + generateKeyPairResponse.PrivateKeyNonce = privateKeyNonce + generateKeyPairResponse.PublicKeyNonce = publicKeyNonce + generateKeyPairResponse.StatusList = utils.AddStatus(generateKeyPairResponse.StatusList, + "200", api.StatusType_INFO, result) + } + + return generateKeyPairResponse, nil +} + +func generateKeyPair(keySize int) ([]byte, []byte, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, keySize) + if err != nil { + return nil, nil, err + } + + err = privateKey.Validate() + if err != nil { + return nil, nil, err + } + + publicKey := &privateKey.PublicKey + + privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return nil, nil, err + } + publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return nil, nil, err + } + + return privateKeyBytes, publicKeyBytes, nil +} + +func rsaEncryptWithServerKey(certFilePath string, message []byte, label []byte) ([]byte, error) { + serverCertificate, err := readCertificateFromFile(certFilePath) + if err != nil { + return nil, err + } + serverPublicKey := serverCertificate.PublicKey.(*rsa.PublicKey) + + encryptedMessageBytes, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, serverPublicKey, message, label) + if err != nil { + return nil, err + } + + return encryptedMessageBytes, nil +} + +func aesEncrypt(aesKey []byte, message []byte) ([]byte, []byte, error) { + block, err := aes.NewCipher(aesKey) + if err != nil { + return nil, nil, err + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, nil, err + } + + nonce, err := generateRandomSequence(aesgcm.NonceSize() * 8) + if err != nil { + return nil, nil, err + } + + encryptedMessage := aesgcm.Seal(nil, nonce, message, nil) + + return encryptedMessage, nonce, nil +} + +func generateRandomSequence(keySize int) ([]byte, error) { + key := make([]byte, keySize/8) + + _, err := rand.Read(key) + if err != nil { + return nil, err + } + + return key, nil +} diff --git a/handler/handler.go b/handler/handler.go new file mode 100644 index 0000000000000000000000000000000000000000..293a3408d8ed1346e9a112df4d0d895f0cce61aa --- /dev/null +++ b/handler/handler.go @@ -0,0 +1,167 @@ +/* +Copyright (c) 2018 Vereign AG [https://www.vereign.com] + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package handler + +import ( + "strings" + + "code.vereign.com/code/viam-apis/versions" + "github.com/golang/protobuf/proto" + + "code.vereign.com/code/viam-apis/authentication" + "code.vereign.com/code/viam-apis/data-storage-agent/client" + "code.vereign.com/code/viam-apis/key-storage-agent/api" + "code.vereign.com/code/viam-apis/utils" + "golang.org/x/net/context" + "google.golang.org/grpc/metadata" +) + +// Server represents the gRPC server +type KeyStorageServerImpl struct { + DataStorageUrl string + CertFilePath string + VereignCertFilePath string + VereignPrivateKeyFilePath string +} + +func (s *KeyStorageServerImpl) CreateAuthentication(ctx context.Context) *authentication.Authentication { + if md, ok := metadata.FromIncomingContext(ctx); ok { + uuid := strings.Join(md["uuid"], "") + session := strings.Join(md["session"], "") + + auth := &authentication.Authentication{ + Uuid: uuid, + Session: session, + } + + return auth + } + + return nil +} + +func (s *KeyStorageServerImpl) GetKey(ctx context.Context, in *api.GetKeyRequest) (*api.GetKeyResponse, error) { + auth := s.CreateAuthentication(ctx) + + client := &client.DataStorageClientImpl{} + client.SetUpClient(auth, s.DataStorageUrl, s.CertFilePath) + defer client.CloseClient() + + getKeyResponse := &api.GetKeyResponse{} + + if in.KeyType == api.KeyType_KT_EMPTY { + getKeyResponse.StatusList = utils.AddStatus(getKeyResponse.StatusList, + "400", api.StatusType_ERROR, "KeyType cannot be empty") + } + + data, _ := client.DoGetDataCall("keys", in.Uuid+"/"+api.KeyType.String(in.KeyType)) + + if data.Errors != "" { + getKeyResponse.Key = nil + getKeyResponse.StatusList = utils.AddStatus(getKeyResponse.StatusList, + "500", api.StatusType_ERROR, data.Errors) + } else { + key := &api.Key{} + proto.Unmarshal(data.Data.Data, key) + getKeyResponse.Key = key + } + + return getKeyResponse, nil +} + +func (s *KeyStorageServerImpl) SetKey(ctx context.Context, in *api.SetKeyRequest) (*api.SetKeyResponse, error) { + auth := s.CreateAuthentication(ctx) + + client := &client.DataStorageClientImpl{} + client.SetUpClient(auth, s.DataStorageUrl, s.CertFilePath) + defer client.CloseClient() + + setKeyResponse := &api.SetKeyResponse{} + + if in.KeyType == api.KeyType_KT_EMPTY { + setKeyResponse.StatusList = utils.AddStatus(setKeyResponse.StatusList, + "400", api.StatusType_ERROR, "KeyType cannot be empty") + } + + data, _ := client.DoGetDataCall("keys", in.Uuid+"/"+api.KeyType.String(in.KeyType)) + + if data.Errors != "" { + setKeyResponse.StatusList = utils.AddStatus(setKeyResponse.StatusList, + "400", api.StatusType_ERROR, data.Errors) + return setKeyResponse, nil + } + + key := &api.Key{} + proto.Unmarshal(data.Data.Data, key) + if key != nil && key.Content != nil && len(key.Content) > 0 { + setKeyResponse.StatusList = utils.AddStatus(setKeyResponse.StatusList, + "400", api.StatusType_ERROR, "Key is already set") + return setKeyResponse, nil + } + + result, errors, err := client.DoPutDataCall("keys", in.Uuid+"/"+api.KeyType.String(in.KeyType), in.Key, versions.EntitiesManagementAgentApiVersion) + setKeyResponse.StatusList = handlePutDataErrors(setKeyResponse.StatusList, errors, err) + + if setKeyResponse.StatusList == nil || len(setKeyResponse.StatusList) == 0 { + setKeyResponse.StatusList = utils.AddStatus(setKeyResponse.StatusList, + "200", api.StatusType_INFO, result) + } + + return setKeyResponse, nil +} + +func (s *KeyStorageServerImpl) ReserveKeyUUID(ctx context.Context, in *api.ReserveKeyUUIDRequest) (*api.ReserveKeyUUIDResponse, error) { + auth := s.CreateAuthentication(ctx) + + client := &client.DataStorageClientImpl{} + client.SetUpClient(auth, s.DataStorageUrl, s.CertFilePath) + defer client.CloseClient() + + reserveKeyUUIDResponse := &api.ReserveKeyUUIDResponse{} + + uuid, err := generateUnusedUUID(client) + if err != nil { + reserveKeyUUIDResponse.StatusList = utils.AddStatus(reserveKeyUUIDResponse.StatusList, + "500", api.StatusType_INFO, err.Error()) + } + + emptyKey := &api.Key{ + Content: []byte{}, + } + + result, errors, err := client.DoPutDataCall("keys", uuid+"/"+api.KeyType.String(api.KeyType_PRIVATE), emptyKey, versions.EntitiesManagementAgentApiVersion) + reserveKeyUUIDResponse.StatusList = handlePutDataErrors(reserveKeyUUIDResponse.StatusList, errors, err) + + if reserveKeyUUIDResponse.StatusList == nil || len(reserveKeyUUIDResponse.StatusList) == 0 { + result, errors, err = client.DoPutDataCall("keys", uuid+"/"+api.KeyType.String(api.KeyType_PUBLIC), emptyKey, versions.EntitiesManagementAgentApiVersion) + reserveKeyUUIDResponse.StatusList = handlePutDataErrors(reserveKeyUUIDResponse.StatusList, errors, err) + } + + if reserveKeyUUIDResponse.StatusList == nil || len(reserveKeyUUIDResponse.StatusList) == 0 { + result, errors, err = client.DoPutDataCall("keys", uuid+"/"+api.KeyType.String(api.KeyType_CERTIFICATE), emptyKey, versions.EntitiesManagementAgentApiVersion) + reserveKeyUUIDResponse.StatusList = handlePutDataErrors(reserveKeyUUIDResponse.StatusList, errors, err) + } + + if reserveKeyUUIDResponse.StatusList == nil || len(reserveKeyUUIDResponse.StatusList) == 0 { + reserveKeyUUIDResponse.Uuid = uuid + reserveKeyUUIDResponse.StatusList = utils.AddStatus(reserveKeyUUIDResponse.StatusList, + "200", api.StatusType_INFO, result) + } + + return reserveKeyUUIDResponse, nil +} diff --git a/handler/revoke.go b/handler/revoke.go new file mode 100644 index 0000000000000000000000000000000000000000..2b779ed3372b1ef99fa2ba7d12823a8596386d84 --- /dev/null +++ b/handler/revoke.go @@ -0,0 +1,79 @@ +/* +Copyright (c) 2018 Vereign AG [https://www.vereign.com] + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package handler + +import ( + "code.vereign.com/code/viam-apis/data-storage-agent/client" + "code.vereign.com/code/viam-apis/key-storage-agent/api" + "code.vereign.com/code/viam-apis/utils" + "code.vereign.com/code/viam-apis/versions" + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" +) + +func (s *KeyStorageServerImpl) Revoke(ctx context.Context, in *api.RevokeRequest) (*api.RevokeResponse, error) { + auth := s.CreateAuthentication(ctx) + + client := &client.DataStorageClientImpl{} + client.SetUpClient(auth, s.DataStorageUrl, s.CertFilePath) + defer client.CloseClient() + + revokeResponse := &api.RevokeResponse{} + + revokeResponse.StatusList = revokeKey(client, in.Uuid, api.KeyType_PRIVATE) + if revokeResponse.StatusList != nil { + return revokeResponse, nil + } + + revokeResponse.StatusList = revokeKey(client, in.Uuid, api.KeyType_PUBLIC) + if revokeResponse.StatusList != nil { + return revokeResponse, nil + } + + revokeResponse.StatusList = revokeKey(client, in.Uuid, api.KeyType_CERTIFICATE) + if revokeResponse.StatusList != nil { + return revokeResponse, nil + } + + revokeResponse.StatusList = utils.AddStatus(revokeResponse.StatusList, "200", api.StatusType_INFO, "Keys revoked") + return revokeResponse, nil +} + +func revokeKey(client *client.DataStorageClientImpl, uuid string, keyType api.KeyType) []*api.Status { + + statusList := []*api.Status{} + + data, _ := client.DoGetDataCall("keys", uuid+"/"+api.KeyType.String(keyType)) + if data.Errors != "" { + statusList = utils.AddStatus(statusList, "400", api.StatusType_ERROR, data.Errors) + return statusList + } + + key := &api.Key{} + proto.Unmarshal(data.Data.Data, key) + + key.Revoked = true + + _, errors, err := client.DoPutDataCall("keys", uuid+"/"+api.KeyType.String(keyType), key, versions.EntitiesManagementAgentApiVersion) + statusList = handlePutDataErrors(statusList, errors, err) + if statusList != nil && len(statusList) > 0 { + return statusList + } + + return nil +} diff --git a/handler/utils.go b/handler/utils.go new file mode 100644 index 0000000000000000000000000000000000000000..a489edca30ce359b8bf3431d6faced0b2797c520 --- /dev/null +++ b/handler/utils.go @@ -0,0 +1,85 @@ +/* +Copyright (c) 2018 Vereign AG [https://www.vereign.com] + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package handler + +import ( + "crypto/rand" + "crypto/x509" + "errors" + "fmt" + "io" + + "code.vereign.com/code/viam-apis/data-storage-agent/client" + "code.vereign.com/code/viam-apis/key-storage-agent/api" + "code.vereign.com/code/viam-apis/utils" +) + +func generateUnusedUUID(client *client.DataStorageClientImpl) (string, error) { + count := 0 + for { + uuid, err := newUUID() + + // check that uuid is not used + data, _ := client.DoGetDataCall("keys", uuid+"/"+api.KeyType.String(api.KeyType_PRIVATE)) + + if data.Errors != "" || err != nil { + return uuid, nil + } + if count >= 10 { + return "", errors.New("Could not generate unused UUID in 10 tries") + } + count++ + } +} + +func newUUID() (string, error) { + uuid := make([]byte, 16) + n, err := io.ReadFull(rand.Reader, uuid) + if n != len(uuid) || err != nil { + return "", err + } + + uuid[8] = uuid[8]&^0xc0 | 0x80 + uuid[6] = uuid[6]&^0xf0 | 0x40 + + return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil +} + +func handlePutDataErrors(statusList []*api.Status, errors string, err error) []*api.Status { + if err != nil { + statusList = utils.AddStatus(statusList, "500", api.StatusType_ERROR, err.Error()) + } else if errors != "" { + statusList = utils.AddStatus(statusList, "400", api.StatusType_ERROR, errors) + } + + return statusList +} + +func readCertificateFromFile(fileName string) (*x509.Certificate, error) { + certificatePemBlock, err := readPemBlockFromFile(fileName) + if err != nil { + return nil, err + } + + certificate, err := x509.ParseCertificate(certificatePemBlock.Bytes) + if err != nil { + return nil, err + } + + return certificate, nil +} diff --git a/kill.sh b/kill.sh new file mode 100755 index 0000000000000000000000000000000000000000..40d73ddc80314f22e053c5dd8353dc5c8b233579 --- /dev/null +++ b/kill.sh @@ -0,0 +1,3 @@ +#!/bin/bash +PIDFILE="$HOME/tmp/key-storage-agent.pid" +kill -9 `cat $PIDFILE` diff --git a/main.go b/main.go new file mode 100644 index 0000000000000000000000000000000000000000..0ed788dfa721c3818628fccaee0d525e01a7c1b3 --- /dev/null +++ b/main.go @@ -0,0 +1,67 @@ +/* +Copyright (c) 2018 Vereign AG [https://www.vereign.com] + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package main + +import ( + "fmt" + "log" + + "code.vereign.com/code/key-storage-agent/server" + "code.vereign.com/code/viam-apis/utils" +) + +// main start a gRPC server and waits for connection +func main() { + + // TODO this should be done via configuration or even a certificate repository + certDir := utils.GetCertDirFromFlags() + if certDir == "" { + log.Printf("cert-dir cannot be empty") + return + } + + grpcAddress := fmt.Sprintf("%s:%d", "localhost", 7877) + restAddress := fmt.Sprintf("%s:%d", "localhost", 7878) + dataStorageAddress := fmt.Sprintf("%s:%d", "localhost", 7777) + + certFilePath := certDir + "/server.crt" + privateKeyFilePath := certDir + "/server.key" + vereignCertFilePath := certDir + "/vereign_ca.cer" + vereignPrivateKeyFilePath := certDir + "/vereign_ca.key" + + // fire the gRPC server in a goroutine + go func() { + err := server.StartGRPCServer(grpcAddress, certFilePath, privateKeyFilePath, vereignCertFilePath, + vereignPrivateKeyFilePath, dataStorageAddress) + if err != nil { + log.Fatalf("failed to start gRPC server: %s", err) + } + }() + + // fire the REST server in a goroutine + go func() { + err := server.StartRESTServer(restAddress, grpcAddress, certFilePath) + if err != nil { + log.Fatalf("failed to start gRPC server: %s", err) + } + }() + + // infinite loop + log.Printf("Entering infinite loop") + select {} +} diff --git a/run.sh b/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..ddfaeae8a956962a9c11ff73a110f88f18893a07 --- /dev/null +++ b/run.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +mkdir -p "$HOME/tmp" +PIDFILE="$HOME/tmp/key-storage-agent.pid" + +if [ -e "${PIDFILE}" ] && (ps -u $(whoami) -opid= | + grep -P "^\s*$(cat ${PIDFILE})$" &> /dev/null); then + echo "Already running." + exit 99 +fi + +PATH=$PATH:/usr/local/bin + +nohup $GOPATH/src/code.vereign.com/code/key-storage-agent/bin/server --cert-dir $GOPATH/src/code.vereign.com/code/key-storage-agent/cert > $HOME/key-storage-agent.log 2>&1 & + +echo $! > "${PIDFILE}" +chmod 644 "${PIDFILE}" diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000000000000000000000000000000000000..40e6a427ae6e7f8f8a88598daba917a20cf12029 --- /dev/null +++ b/server/server.go @@ -0,0 +1,176 @@ +package server + +/* +Copyright (c) 2018 Vereign AG [https://www.vereign.com] + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +import ( + "fmt" + "log" + "net" + "net/http" + "strings" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + + "golang.org/x/net/context" + + "code.vereign.com/code/key-storage-agent/handler" + "code.vereign.com/code/key-storage-agent/session" + "code.vereign.com/code/viam-apis/authentication" + "code.vereign.com/code/viam-apis/data-storage-agent/client" + api "code.vereign.com/code/viam-apis/key-storage-agent/api" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" +) + +// private type for Context keys +type contextKey int + +const ( + clientIDKey contextKey = iota +) + +var pkgCertFile string + +func credMatcher(headerName string) (mdName string, ok bool) { + if headerName == "Session" { + return headerName, true + } + return "", false +} + +// authenticateAgent check the client credentials +func authenticateClient(ctx context.Context, s *handler.KeyStorageServerImpl, invokedMethod string) (string, error) { + if md, ok := metadata.FromIncomingContext(ctx); ok { + + clientAuth := &authentication.Authentication{ + Uuid: strings.Join(md["uuid"], ""), + Session: strings.Join(md["session"], ""), + } + + viamAuth := &authentication.Authentication{ + Uuid: "viam-system", + Session: "viam-session", + } + + sessionClient := &client.DataStorageClientImpl{} + sessionClient.SetUpClient(viamAuth, "localhost:7777", pkgCertFile) + defer sessionClient.CloseClient() + + if clientAuth.Uuid == viamAuth.Uuid { + if clientAuth.Session != viamAuth.Session { + return "", fmt.Errorf("bad session %s", clientAuth.Session) + } + } else { + if session.CheckSession(clientAuth.Uuid, clientAuth.Session, sessionClient) == false { + return "", fmt.Errorf("bad session %s", clientAuth.Session) + } + } + + log.Printf("authenticated uuid: %s", clientAuth.Uuid) + + return clientAuth.Uuid, nil + } + return "", fmt.Errorf("missing credentials") +} + +// unaryInterceptor call authenticateClient with current context +func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler1 grpc.UnaryHandler) (interface{}, error) { + s, ok := info.Server.(*handler.KeyStorageServerImpl) + fmt.Println("Invoked method: " + info.FullMethod) + if !ok { + return nil, fmt.Errorf("unable to cast server") + } + clientID, err := authenticateClient(ctx, s, info.FullMethod) + if err != nil { + return nil, err + } + + ctx = context.WithValue(ctx, clientIDKey, clientID) + + return handler1(ctx, req) +} + +func StartGRPCServer(address, certFilePath, privateKeyFilePath, vereignCertFilePath, vereignPrivateKeyFilePath, dataStorageAddress string) error { + pkgCertFile = certFilePath + + // create a listener on TCP port + lis, err := net.Listen("tcp", address) + if err != nil { + return fmt.Errorf("failed to listen: %v", err) + } + + // create a server instance + s := handler.KeyStorageServerImpl{ + DataStorageUrl: dataStorageAddress, + CertFilePath: certFilePath, + VereignCertFilePath: vereignCertFilePath, + VereignPrivateKeyFilePath: vereignPrivateKeyFilePath, + } + + // Create the TLS credentials + creds, err := credentials.NewServerTLSFromFile(certFilePath, privateKeyFilePath) + if err != nil { + return fmt.Errorf("could not load TLS keys: %s", err) + } + + // Create an array of gRPC options with the credentials + opts := []grpc.ServerOption{grpc.Creds(creds), + grpc.UnaryInterceptor(unaryInterceptor)} + + // create a gRPC server object + grpcServer := grpc.NewServer(opts...) + + // attach the CalcMinimumDistance service to the server + api.RegisterKeyStorageServer(grpcServer, &s) + + // start the server + log.Printf("starting HTTP/2 gRPC server on %s", address) + if err := grpcServer.Serve(lis); err != nil { + return fmt.Errorf("failed to serve: %s", err) + } + + return nil +} + +func StartRESTServer(address, grpcAddress, certFile string) error { + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + mux := runtime.NewServeMux(runtime.WithIncomingHeaderMatcher(credMatcher)) + + creds, err := credentials.NewClientTLSFromFile(certFile, "") + if err != nil { + return fmt.Errorf("could not load TLS certificate: %s", err) + } + + // Setup the client gRPC options + opts := []grpc.DialOption{grpc.WithTransportCredentials(creds)} + + // Register RedisStorageServer + err = api.RegisterKeyStorageHandlerFromEndpoint(ctx, mux, grpcAddress, opts) + if err != nil { + return fmt.Errorf("could not register service RedisStorageServer: %s", err) + } + + log.Printf("starting HTTP/1.1 REST server on %s", address) + http.ListenAndServe(address, mux) + + return nil +} diff --git a/server/server_test.go b/server/server_test.go new file mode 100644 index 0000000000000000000000000000000000000000..aded86e9ddb5eecf3eda86d5b808103e96c093fc --- /dev/null +++ b/server/server_test.go @@ -0,0 +1,261 @@ +/* +Copyright (c) 2018 Vereign AG [https://www.vereign.com] + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package server + +import ( + "log" + "os" + "testing" + "time" + + dataStorageServer "code.vereign.com/code/data-storage-agent/server" + "code.vereign.com/code/data-storage-agent/utils" + "code.vereign.com/code/viam-apis/authentication" + ksapi "code.vereign.com/code/viam-apis/key-storage-agent/api" + ksclient "code.vereign.com/code/viam-apis/key-storage-agent/client" + timestamp "github.com/golang/protobuf/ptypes/timestamp" +) + +const ( + dataStorageGrpcAddress = "localhost:7777" + keyStorageGrpcAddress = "localhost:7877" + certFilePath = "../cert/server.crt" + privateKeyFilePath = "../cert/server.key" + vereignCertFilePath = "../cert/vereign_ca.cer" + vereignPrivateKeyFilePath = "../cert/vereign_ca.key" +) + +func TestSetAndGetKeys(t *testing.T) { + dataStorageClient := utils.CreateClientFromUuidAndSession("viam-system", "viam-session", dataStorageGrpcAddress, certFilePath) + + keyStorageAuth := &authentication.Authentication{ + Uuid: "some-uuid", + Session: "some-session", + } + + _, _, _ = dataStorageClient.RenewSession(keyStorageAuth.Uuid, keyStorageAuth.Session) + + keyStorageClient := &ksclient.KeyStorageClientImpl{} + keyStorageClient.SetUpClient(keyStorageAuth, keyStorageGrpcAddress, certFilePath) + defer keyStorageClient.CloseClient() + + uuid, statusList, _ := keyStorageClient.DoReserveKeyUUID() + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoReserveKeyUUID, returned error: %s.", status.Code+":"+status.Description) + } + } + + privateKey := &ksapi.Key{ + Content: []byte{0, 1, 4}, + } + + statusList, _ = keyStorageClient.DoSetKey(uuid, ksapi.KeyType_PRIVATE, privateKey) + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoSetKey, returned error: %s.", status.Code+":"+status.Description) + return + } + } + + privateKeyResult, statusList, _ := keyStorageClient.DoGetKey(uuid, ksapi.KeyType_PRIVATE) + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoGetKey, returned error: %s.", status.Code+":"+status.Description) + return + } + } + if privateKeyResult.Content == nil || !utils.CompareByteArrays(privateKeyResult.Content, []byte{0, 1, 4}) { + t.Errorf("DoGetKey, incorrect keyResult.Content, expected: %v, but was: %v", + []byte{0, 1, 2}, privateKeyResult.Content) + } + + // Test setting the same key twice + statusList, _ = keyStorageClient.DoSetKey(uuid, ksapi.KeyType_PRIVATE, privateKey) + for _, status := range statusList { + if status.StatusType != ksapi.StatusType_ERROR { + t.Errorf("DoSetKey, expected error, but got success") + return + } + } + + publicKey := &ksapi.Key{ + Content: []byte{3, 4, 2}, + } + + statusList, _ = keyStorageClient.DoSetKey(uuid, ksapi.KeyType_PUBLIC, publicKey) + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoSetKey, returned error: %s.", status.Code+":"+status.Description) + } + } + + publicKeyResult, statusList, _ := keyStorageClient.DoGetKey(uuid, ksapi.KeyType_PUBLIC) + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoGetKey, returned error: %s.", status.Code+":"+status.Description) + } + } + if publicKeyResult.Content == nil || !utils.CompareByteArrays(publicKeyResult.Content, []byte{3, 4, 2}) { + t.Errorf("DoGetKey, incorrect publicKeyResult.Content, expected: %v, but was: %v", + []byte{3, 4, 2}, publicKeyResult.Content) + } + + statusList, _ = keyStorageClient.DoRevoke(uuid) + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoRevoke, returned error: %s.", status.Code+":"+status.Description) + } + } + revokedPrivateKeyResult, statusList, _ := keyStorageClient.DoGetKey(uuid, ksapi.KeyType_PRIVATE) + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoGetKey, returned error: %s.", status.Code+":"+status.Description) + } + } + if revokedPrivateKeyResult.Revoked == false { + t.Errorf("DoRevoke, key was not revoked") + } + revokedPublicKeyResult, statusList, _ := keyStorageClient.DoGetKey(uuid, ksapi.KeyType_PUBLIC) + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoGetKey, returned error: %s.", status.Code+":"+status.Description) + } + } + if revokedPublicKeyResult.Revoked == false { + t.Errorf("DoRevoke, key was not revoked") + } +} +func TestGenerateKeyPairAndCertificate(t *testing.T) { + dataStorageClient := utils.CreateClientFromUuidAndSession("viam-system", "viam-session", dataStorageGrpcAddress, certFilePath) + + keyStorageAuth := &authentication.Authentication{ + Uuid: "some-uuid", + Session: "some-session", + } + + _, _, _ = dataStorageClient.RenewSession(keyStorageAuth.Uuid, keyStorageAuth.Session) + + keyStorageClient := &ksclient.KeyStorageClientImpl{} + keyStorageClient.SetUpClient(keyStorageAuth, keyStorageGrpcAddress, certFilePath) + defer keyStorageClient.CloseClient() + + uuid, encryptedAesKey, privateKeyNonce, publicKeyNonce, statusList, _ := keyStorageClient.DoGenerateKeyPair(2048) + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoGenerateKeyPair, returned error: %s.", status.Code+":"+status.Description) + return + } + } + if uuid == "" { + t.Errorf("DoGenerateKeyPair, uuid is empty") + return + } + + privateKeyResult, statusList, _ := keyStorageClient.DoGetKey(uuid, ksapi.KeyType_PRIVATE) + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoGetKey, returned error: %s.", status.Code+":"+status.Description) + return + } + } + if privateKeyResult.Content == nil { + t.Errorf("DoGetKey, privateKeyResult.Content is nil") + } + + publicKeyResult, statusList, _ := keyStorageClient.DoGetKey(uuid, ksapi.KeyType_PUBLIC) + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoGetKey, returned error: %s.", status.Code+":"+status.Description) + return + } + } + if publicKeyResult.Content == nil { + t.Errorf("DoGetKey, publicKeyResult.Content is nil") + } + + nowTime := time.Now() + nowTimeSeconds := time.Now().Unix() + nowTimeNanoSeconds := int32(nowTime.Sub(time.Unix(nowTimeSeconds, 0))) + notBefore := ×tamp.Timestamp{ + Seconds: nowTimeSeconds, + Nanos: nowTimeNanoSeconds, + } + timeAfterOneDay := nowTime.Add(1000000 * 3600 * 24) + secondsAfterOneDay := time.Now().Unix() + nanoSecondsAfterOneDay := int32(timeAfterOneDay.Sub(time.Unix(secondsAfterOneDay, 0))) + notAfter := ×tamp.Timestamp{ + Seconds: secondsAfterOneDay, + Nanos: nanoSecondsAfterOneDay, + } + + certificateData := &ksapi.GenerateCertificateRequest_CertificateData{ + Country: "BG", + Organization: "company", + OrganizationalUnit: "DEV", + CommonName: "CN", + NotBefore: notBefore, + NotAfter: notAfter, + Host: "abcde.com", + } + + statusList, _ = keyStorageClient.DoGenerateCertificate(uuid, certificateData, encryptedAesKey, privateKeyNonce, publicKeyNonce) + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoGenerateCertificate, returned error: %s.", status.Code+":"+status.Description) + } + } + + certificate, statusList, _ := keyStorageClient.DoGetKey(uuid, ksapi.KeyType_CERTIFICATE) + for _, status := range statusList { + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoGetKey, returned error: %s.", status.Code+":"+status.Description) + } + } + if certificate == nil || certificate.Content == nil { + t.Errorf("DoGetKey, certificate is nil") + } +} + +func TestMain(m *testing.M) { + // fire the gRPC server in a goroutine + go func() { + err := dataStorageServer.StartGRPCServer(dataStorageGrpcAddress, certFilePath, privateKeyFilePath) + if err != nil { + log.Fatalf("failed to start gRPC server: %s", err) + } + }() + + // wait a second for the server to start + time.Sleep(time.Duration(1) * time.Second) + + // fire the gRPC server in a goroutine + go func() { + err := StartGRPCServer(keyStorageGrpcAddress, certFilePath, privateKeyFilePath, + vereignCertFilePath, vereignPrivateKeyFilePath, dataStorageGrpcAddress) + if err != nil { + log.Fatalf("failed to start gRPC server: %s", err) + } + }() + + // wait a second for the server to start + time.Sleep(time.Duration(1) * time.Second) + + retCode := m.Run() + os.Exit(retCode) +} diff --git a/session/session.go b/session/session.go new file mode 100644 index 0000000000000000000000000000000000000000..14793a89303df97561b0e64975fe40ab0a22ee2a --- /dev/null +++ b/session/session.go @@ -0,0 +1,31 @@ +/* +Copyright (c) 2018 Vereign AG [https://www.vereign.com] + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package session + +import ( + client "code.vereign.com/code/viam-apis/data-storage-agent/client" +) + +func CheckSession(uuid string, session string, sessionClient *client.DataStorageClientImpl) bool { + hasSession, _, err := sessionClient.HasSession(uuid, session) + if err != nil { + return false + } + + return hasSession +}