blob: 66cb0ca7555925f8b87de277a4b532e20543124e [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"github.com/golang/glog"
"github.com/golang/protobuf/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"cobalt"
)
// This file contains two types for working with EncryptedMessages.
//
// EncryptedMessageMaker is not currently used on the Shuffler. It is included for
// completeness and for use in tests.
//
// MessageDecrypter is used by the Shuffler to decrypt EncryptedMessages
// containing Envelopes.
// EncryptedMessageMaker is used only in tests. The C++ version of EncryptedMessageMaker
// is used by the Encoder client to build EncryptedMessages to send to the Shuffler
// and the Analyzer.
type EncryptedMessageMaker struct {
hybridCipher *HybridCipher
encryptionScheme cobalt.EncryptedMessage_EncryptionScheme
}
// Constructs and returns a new EncryptedMessageMaker or nil if |publicKeyPem| cannot be
// parsed.
//
// |scheme| specifies which encryption scheme should be used. As of this
// writing there are two schemes:
// (i) EncryptedMessage_NONE means that messages will not be
// encrypted: they will be sent in plain text. This scheme must
// never be used in production Cobalt.
//
// (ii) EncryptedMessage_HYBRID_ECDH_V1 indicates that version 1 of
// Cobalt's Elliptic-Curve Diffie-Hellman-based hybrid
// public-key/private-key encryption scheme should be used.
//
// |publicKeyPem| must be appropriate to |scheme|. If |scheme| is
// EncryptedMessage_NONE then |publicKeyPem| is ignored. If |scheme| is
// EncryptedMessage_HYBRID_ECDH_V1 then |publicKeyPem| must be a PEM
// encoding of a public key appropriate for that scheme.
func NewEncryptedMessageMaker(publicKeyPem string,
scheme cobalt.EncryptedMessage_EncryptionScheme) *EncryptedMessageMaker {
var cipher *HybridCipher
if scheme == cobalt.EncryptedMessage_HYBRID_ECDH_V1 {
publicKey, err := ParseECPublicKeyPem(publicKeyPem)
if err != nil {
glog.Errorf("Failed to decode public key PEM: %v.", err)
return nil
}
cipher = NewHybridCipher(nil, publicKey)
if cipher == nil {
glog.Errorln("Failed to construct a HybridCipher.")
return nil
}
}
return &EncryptedMessageMaker{
hybridCipher: cipher,
encryptionScheme: scheme,
}
}
// Encrypts a protocol buffer |message|. Returns an EncryptedMessage and nil on success
// or nil and an error on failure.
func (m *EncryptedMessageMaker) Encrypt(message proto.Message) (*cobalt.EncryptedMessage, error) {
if m == nil {
return nil, grpc.Errorf(codes.InvalidArgument, "EncryptedMessageMaker is nil")
}
if message == nil {
return nil, grpc.Errorf(codes.InvalidArgument, "message is nil")
}
serializedMessage, err := proto.Marshal(message)
if err != nil {
return nil, grpc.Errorf(codes.InvalidArgument, "message could not be serialized: %v", err)
}
var encryptedMessage cobalt.EncryptedMessage
encryptedMessage.Scheme = m.encryptionScheme
if m.encryptionScheme == cobalt.EncryptedMessage_NONE {
encryptedMessage.Ciphertext = serializedMessage
return &encryptedMessage, nil
}
if m.encryptionScheme != cobalt.EncryptedMessage_HYBRID_ECDH_V1 {
// HYBRID_ECDH_V1 is the only other scheme we know about.
return nil, grpc.Errorf(codes.Internal, "Unexpected encryption scheme: %v", m.encryptionScheme)
}
if m.hybridCipher == nil {
return nil, grpc.Errorf(codes.Internal, "m.hybridCipher is nil")
}
ciphertext, err := m.hybridCipher.Encrypt(serializedMessage)
if err != nil {
return nil, err
}
encryptedMessage.Ciphertext = ciphertext
// TODO(rudominer) Set encryptedMessage.PublicKeyFingerprint
return &encryptedMessage, nil
}
type MessageDecrypter struct {
hybridCipher *HybridCipher
}
// Constructs a new MessageDecrypter. If |privateKeyPem| is a valid PEM
// encoding of a private key for Cobalt's hybrid encryption scheme, then the
// resulting MessageDecrypter will be able to decrypt messages that use the
// HYBRID_ECDH_V1 scheme. Otherwise the resulting MessageDecrypter will only
// be able to decrypt EncryptedMessages that use the NONE scheme.
//
// TODO(rudominer) For key-rotation support this constructor
// should accept multiple (public, private) key pairs and use the
// fingerprint field of EncryptedMessage to select the appropriate private
// key.
func NewMessageDecrypter(privateKeyPem string) *MessageDecrypter {
var hybridCipher *HybridCipher
if privateKeyPem == "" {
// We use glog.V() here becuase we don't want to print an error message if the
// Shuffler is being used in a test without encryption.
glog.V(3).Infoln("No privateKeyPem provided. Shuffler will not be able to decrypt EncryptedMessages.")
} else {
privateKey, err := ParseECPrivateKeyPem(privateKeyPem)
if err != nil {
glog.Errorf("Failed to decode private key PEM: %v, Shuffler will not be able to decrypt EncryptedMessages.", err)
} else {
hybridCipher = NewHybridCipher(privateKey, nil)
glog.Infoln("Successfully parsed the private key PEM file.")
}
}
return &MessageDecrypter{
hybridCipher: hybridCipher,
}
}
// Decrypts |encryptedMessage| and deserializes the result into the provided |outMessage|. Return a non-nil error if and only if this fails.
func (m *MessageDecrypter) DecryptMessage(encryptedMessage *cobalt.EncryptedMessage, outMessage proto.Message) error {
if m == nil {
return grpc.Errorf(codes.InvalidArgument, "MessageDecrypter is nil")
}
if encryptedMessage == nil {
return grpc.Errorf(codes.InvalidArgument, "encrypedMessage is nil")
}
if outMessage == nil {
return grpc.Errorf(codes.InvalidArgument, "outMessage is nil")
}
if encryptedMessage.Scheme == cobalt.EncryptedMessage_NONE {
err := proto.Unmarshal(encryptedMessage.Ciphertext, outMessage)
if err == nil {
return nil
}
return grpc.Errorf(codes.InvalidArgument, "Unable to unmarshal encryptedMessage. Ciphertext: %v", err)
}
if encryptedMessage.Scheme != cobalt.EncryptedMessage_HYBRID_ECDH_V1 {
// HYBRID_ECDH_V1 is the only other scheme we know about.
return grpc.Errorf(codes.InvalidArgument, "Unrecognized encryption scheme specified in EncryptedMessage: %v", encryptedMessage.Scheme)
}
if m.hybridCipher == nil {
return grpc.Errorf(codes.Internal, "m.hybridCipher is nil")
}
recoveredText, err := m.hybridCipher.Decrypt(encryptedMessage.Ciphertext)
if err != nil {
return grpc.Errorf(codes.InvalidArgument, "Decryption error: %v", err)
}
if err = proto.Unmarshal(recoveredText, outMessage); err != nil {
return grpc.Errorf(codes.InvalidArgument, "Unable to unmarshal decrypted text: %v", err)
}
glog.V(4).Infoln("Decryption of Envelope succeeded.")
return nil
}