diff --git a/handler/handler.go b/handler/handler.go index 5184a9d1190891328b552d9b1342ae907834e724..99000df4702e059355e5444d5c76d8022edccf13 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -18,7 +18,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. package handler import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "io" + "log" + "math/big" "strings" + "time" "code.vereign.com/code/viam-apis/versions" "github.com/golang/protobuf/proto" @@ -53,90 +62,252 @@ func (s *KeyStorageServerImpl) CreateAuthentication(ctx context.Context) *authen return nil } -func (s *KeyStorageServerImpl) GetPrivateKey(ctx context.Context, in *api.GetPrivateKeyRequest) (*api.GetPrivateKeyResponse, error) { +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.CertPath) defer client.CloseClient() - data, _ := client.DoGetDataCall("privatekeys", in.Name) + getKeyResponse := &api.GetKeyResponse{} - getPrivateKeyResponse := &api.GetPrivateKeyResponse{} + 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 != "" { - getPrivateKeyResponse.Key = nil - getPrivateKeyResponse.StatusList = utils.AddStatus(getPrivateKeyResponse.StatusList, "500", "Internal server error", data.Errors) + getKeyResponse.Key = nil + getKeyResponse.StatusList = utils.AddStatus(getKeyResponse.StatusList, "500", api.StatusType_ERROR, data.Errors) } else { - key := &api.PrivateKey{} + key := &api.Key{} proto.Unmarshal(data.Data.Data, key) - getPrivateKeyResponse.Key = key + getKeyResponse.Key = key } - return getPrivateKeyResponse, nil + return getKeyResponse, nil } -func (s *KeyStorageServerImpl) GetPublicKey(ctx context.Context, in *api.GetPublicKeyRequest) (*api.GetPublicKeyResponse, error) { +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.CertPath) defer client.CloseClient() - data, _ := client.DoGetDataCall("publickeys", in.Name) + setKeyResponse := &api.SetKeyResponse{} + + if in.KeyType == api.KeyType_KT_EMPTY { + setKeyResponse.StatusList = utils.AddStatus(setKeyResponse.StatusList, "400", api.StatusType_ERROR, "KeyType cannot be empty") + } - getPublicKeyResponse := &api.GetPublicKeyResponse{} + data, _ := client.DoGetDataCall("keys", in.Uuid+"/"+api.KeyType.String(in.KeyType)) if data.Errors != "" { - getPublicKeyResponse.Key = nil - getPublicKeyResponse.StatusList = utils.AddStatus(getPublicKeyResponse.StatusList, "500", "Internal server error", data.Errors) - } else { - key := &api.PublicKey{} - proto.Unmarshal(data.Data.Data, key) - getPublicKeyResponse.Key = key + setKeyResponse.StatusList = utils.AddStatus(setKeyResponse.StatusList, "400", api.StatusType_ERROR, data.Errors) + return setKeyResponse, nil } - return getPublicKeyResponse, 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) SetPrivateKey(ctx context.Context, in *api.SetPrivateKeyRequest) (*api.SetPrivateKeyResponse, error) { +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.CertPath) defer client.CloseClient() - result, errors, err := client.DoPutDataCall("privatekeys", in.Key.Name, in.Key, versions.EntitiesManagementAgentApiVersion) + uuid, err := newUUID() + privateKeyBytes, publicKeyBytes, _ := generateKeyPair(int(in.KeySize * 8)) - setPrivateKeyResponse := &api.SetPrivateKeyResponse{} - if err != nil { - setPrivateKeyResponse.StatusList = utils.AddStatus(setPrivateKeyResponse.StatusList, "500", "error", err.Error()) - } else if errors != "" { - setPrivateKeyResponse.StatusList = utils.AddStatus(setPrivateKeyResponse.StatusList, "500", "error", errors) + privateKey := &api.Key{Content: privateKeyBytes} + publicKey := &api.Key{Content: publicKeyBytes} + + generateKeyPairResponse := &api.GenerateKeyPairResponse{ + Uuid: uuid, + } + + result, errors, err := client.DoPutDataCall("keys", uuid+"/"+api.KeyType.String(api.KeyType_PRIVATE), privateKey, versions.EntitiesManagementAgentApiVersion) + generateKeyPairResponse.StatusList = handlePutDataErrors(generateKeyPairResponse.StatusList, errors, err) + + if generateKeyPairResponse.StatusList == nil || len(generateKeyPairResponse.StatusList) == 0 { + result, errors, err = client.DoPutDataCall("keys", uuid+"/"+api.KeyType.String(api.KeyType_PUBLIC), publicKey, versions.EntitiesManagementAgentApiVersion) + generateKeyPairResponse.StatusList = handlePutDataErrors(generateKeyPairResponse.StatusList, errors, err) + } + + if generateKeyPairResponse.StatusList == nil || len(generateKeyPairResponse.StatusList) == 0 { + generateKeyPairResponse.StatusList = utils.AddStatus(generateKeyPairResponse.StatusList, "200", api.StatusType_INFO, result) + } + + return generateKeyPairResponse, nil +} + +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.CertPath) + defer client.CloseClient() + + generateCertificateResponse := &api.GenerateCertificateResponse{} + + privateKeyMessage := &api.Key{} + publicKeyMessage := &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) + } else { + proto.Unmarshal(data.Data.Data, privateKeyMessage) + } + + 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) } else { - setPrivateKeyResponse.StatusList = utils.AddStatus(setPrivateKeyResponse.StatusList, "200", "info", result) + proto.Unmarshal(data.Data.Data, publicKeyMessage) + } + + certificateBytes, err := generateCertificate(privateKeyMessage.Content, publicKeyMessage.Content, in.CertificateData) + if err != nil { + generateCertificateResponse.StatusList = utils.AddStatus(generateCertificateResponse.StatusList, "400", api.StatusType_ERROR, err.Error()) + } + + 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 setPrivateKeyResponse, nil + return generateCertificateResponse, nil } -func (s *KeyStorageServerImpl) SetPublicKey(ctx context.Context, in *api.SetPublicKeyRequest) (*api.SetPublicKeyResponse, error) { +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.CertPath) defer client.CloseClient() - result, errors, err := client.DoPutDataCall("publickeys", in.Key.Name, in.Key, versions.EntitiesManagementAgentApiVersion) + uuid, err := newUUID() + emptyKey := &api.Key{ + Content: []byte{}, + } + + reserveKeyUUIDResponse := &api.ReserveKeyUUIDResponse{ + Uuid: uuid, + } + + 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.StatusList = utils.AddStatus(reserveKeyUUIDResponse.StatusList, "200", api.StatusType_INFO, result) + } - setPublicKeyResponse := &api.SetPublicKeyResponse{} + return reserveKeyUUIDResponse, nil +} + +func generateKeyPair(bitSize int) ([]byte, []byte, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, bitSize) + if err != nil { + return nil, nil, err + } + + err = privateKey.Validate() if err != nil { - setPublicKeyResponse.StatusList = utils.AddStatus(setPublicKeyResponse.StatusList, "500", "error", err.Error()) + return nil, nil, err + } + + publicKey := &privateKey.PublicKey + + privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + publicKeyBytes := x509.MarshalPKCS1PublicKey(publicKey) + + return privateKeyBytes, publicKeyBytes, nil +} + +func generateCertificate(privateKeyBytes []byte, publicKeyBytes []byte, certificateData *api.GenerateCertificateRequest_CertificateData) ([]byte, error) { + + privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBytes) + if err != nil { + return nil, err + } + publicKey, err := x509.ParsePKCS1PublicKey(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: true, + DNSNames: []string{certificateData.Host}, + } + + certificateBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey, privateKey) + if err != nil { + log.Fatalf("Failed to create certificate: %s", err) + } + + return certificateBytes, nil +} +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 != "" { - setPublicKeyResponse.StatusList = utils.AddStatus(setPublicKeyResponse.StatusList, "500", "error", errors) - } else { - setPublicKeyResponse.StatusList = utils.AddStatus(setPublicKeyResponse.StatusList, "200", "info", result) + statusList = utils.AddStatus(statusList, "400", api.StatusType_ERROR, errors) } - return setPublicKeyResponse, nil + return statusList } diff --git a/server/server_test.go b/server/server_test.go index 1c0fda7aef278d34358541a5655ee47faaf6c305..42c383b5e849f108af2c4b727ebd2a83f845c517 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -28,6 +28,7 @@ import ( "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 ( @@ -51,56 +52,147 @@ func TestSetAndGetKeys(t *testing.T) { keyStorageClient.SetUpClient(keyStorageAuth, keyStorageGrpcAddress, certFile) defer keyStorageClient.CloseClient() - privateKey := &ksapi.PrivateKey{ - Name: "privkey1", - PublicKeyReference: "pubkey1", - Content: []byte{0, 1, 4}, + 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.DoSetPrivateKey(privateKey) + statusList, _ = keyStorageClient.DoSetKey(uuid, ksapi.KeyType_PRIVATE, privateKey) for _, status := range statusList { - if status.StatusType == "error" { - t.Errorf("DoSetPrivateKey, returned error: %s.", status.Code+":"+status.Description) + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoSetKey, returned error: %s.", status.Code+":"+status.Description) + return } } - privateKeyResult, statusList, _ := keyStorageClient.DoGetPrivateKey("privkey1") + privateKeyResult, statusList, _ := keyStorageClient.DoGetKey(uuid, ksapi.KeyType_PRIVATE) for _, status := range statusList { - if status.StatusType == "error" { - t.Errorf("DoGetPrivateKey, returned error: %s.", status.Code+":"+status.Description) + 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("DoGetPrivateKey, incorrect keyResult.Content, expected: %v, but was: %v", + t.Errorf("DoGetKey, incorrect keyResult.Content, expected: %v, but was: %v", []byte{0, 1, 2}, privateKeyResult.Content) } - publicKey := &ksapi.PublicKey{ - Name: "pubkey1", - Content: []byte{3, 4, 2}, - Certificate: []byte{5, 1, 6}, + publicKey := &ksapi.Key{ + Content: []byte{3, 4, 2}, } - statusList, _ = keyStorageClient.DoSetPublicKey(publicKey) + statusList, _ = keyStorageClient.DoSetKey(uuid, ksapi.KeyType_PUBLIC, publicKey) for _, status := range statusList { - if status.StatusType == "error" { - t.Errorf("DoSetPublicKey, returned error: %s.", status.Code+":"+status.Description) + if status.StatusType == ksapi.StatusType_ERROR { + t.Errorf("DoSetKey, returned error: %s.", status.Code+":"+status.Description) } } - publicKeyResult, statusList, _ := keyStorageClient.DoGetPublicKey("pubkey1") + publicKeyResult, statusList, _ := keyStorageClient.DoGetKey(uuid, ksapi.KeyType_PUBLIC) for _, status := range statusList { - if status.StatusType == "error" { - t.Errorf("DoGetPublicKey, returned error: %s.", status.Code+":"+status.Description) + 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("DoGetPublicKey, incorrect publicKeyResult.Content, expected: %v, but was: %v", + t.Errorf("DoGetKey, incorrect publicKeyResult.Content, expected: %v, but was: %v", []byte{3, 4, 2}, publicKeyResult.Content) } - if publicKeyResult.Certificate == nil || !utils.CompareByteArrays(publicKeyResult.Certificate, []byte{5, 1, 6}) { - t.Errorf("DoGetPublicKey, incorrect publicKeyResult.Certificate, expected: %v, but was: %v", - []byte{5, 1, 6}, publicKeyResult.Certificate) +} +func TestGenerateKeyPairAndCertificate(t *testing.T) { + dataStorageClient := utils.CreateClientFromUuidAndSession("viam-system", "viam-session", dataStorageGrpcAddress, certFile) + + keyStorageAuth := &authentication.Authentication{ + Uuid: "some-uuid", + Session: "some-session", + } + + _, _, _ = dataStorageClient.RenewSession(keyStorageAuth.Uuid, keyStorageAuth.Session) + + keyStorageClient := &ksclient.KeyStorageClientImpl{} + keyStorageClient.SetUpClient(keyStorageAuth, keyStorageGrpcAddress, certFile) + defer keyStorageClient.CloseClient() + + uuid, statusList, _ := keyStorageClient.DoGenerateKeyPair(256) + 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) + 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.Content == nil { + t.Errorf("DoGetKey, certificate.Content is nil") } }