Correctly decode PEM files in Go.
I had a misunderstanding of what a PEM encoding of a public or private
key was. This bug was not detected until I started working on the
interaction between the C++ code and the Go code. Previously
I had a unit test in which I was incorrectly creating a PEM encoding
and then incorrectly parsing the PEM encoding that I created.
Now I have a unit test in which I use a literal PEM string that was
created by the C++ code and so I can test that the new implementation
of PEM decoding in GO is correct. I don't implement PEM encoding in GO as
we have no need for it.
Change-Id: I21d60d55049d49b639381840285b6011a120ded3
diff --git a/shuffler/src/util/elliptic_util.go b/shuffler/src/util/elliptic_util.go
index 1f95c3e..e4cfbff 100644
--- a/shuffler/src/util/elliptic_util.go
+++ b/shuffler/src/util/elliptic_util.go
@@ -15,7 +15,10 @@
package util
import (
+ "crypto/ecdsa"
"crypto/elliptic"
+ "crypto/x509"
+ "encoding/pem"
"fmt"
"math/big"
)
@@ -160,3 +163,54 @@
copy(fieldElementPadded[ecFieldElementSize-len(fieldElement):], fieldElement)
return fieldElementPadded, nil
}
+
+// ParseECPrivateKeyPem parses a PEM encoded private EC key. The PEM should contain
+// an unencrypted PKCS#8 (see RFC 5208) private key of type ECDSA (see RFC 5480).
+func ParseECPrivateKeyPem(privateKeyPem string) (privateKey []byte, err error) {
+ block, _ := pem.Decode([]byte(privateKeyPem))
+ if block == nil {
+ err = fmt.Errorf("Private key PEM could not be parsed.")
+ return
+ }
+ result, err2 := x509.ParsePKCS8PrivateKey(block.Bytes)
+ if err2 != nil {
+ err = err2
+ return
+ }
+ ecPrivateKey, ok := result.(*ecdsa.PrivateKey)
+ if !ok {
+ err = fmt.Errorf("The private key PEM file did not contain an EC private key but rather a %T.", result)
+ return
+ }
+ privateKey, err = padFieldElement(ecPrivateKey.D.Bytes())
+ if err != nil {
+ err = fmt.Errorf("Cannot parse hybrid cipher private key PEM: %d.", err)
+ return
+ }
+ return
+}
+
+// ParseECPublicKeyPem parses a PEM encoded public EC key. The PEM should contain a
+// PKCS#8 public key of type ECDSA.
+func ParseECPublicKeyPem(publicKeyPEM string) (publicKey []byte, err error) {
+ block, _ := pem.Decode([]byte(publicKeyPEM))
+ if block == nil {
+ err = fmt.Errorf("Public key PEM could not be parsed.")
+ return
+ }
+ result, err2 := x509.ParsePKIXPublicKey(block.Bytes)
+ if err2 != nil {
+ err = err2
+ return
+ }
+ ecPublicKey, ok := result.(*ecdsa.PublicKey)
+ if !ok {
+ err = fmt.Errorf("The public key PEM file did not contain an EC public key but rather a %T.", result)
+ return
+ }
+ if ecPublicKey.Curve != ellipticCurve {
+ err = fmt.Errorf("The public key PEM contained an EC public key for the wrong curve: %v", ecPublicKey.Curve)
+ return
+ }
+ return elliptic.Marshal(ecPublicKey.Curve, ecPublicKey.X, ecPublicKey.Y), nil
+}
diff --git a/shuffler/src/util/encrypted_message_util.go b/shuffler/src/util/encrypted_message_util.go
index d9dfeab..66cb0ca 100644
--- a/shuffler/src/util/encrypted_message_util.go
+++ b/shuffler/src/util/encrypted_message_util.go
@@ -15,8 +15,6 @@
package util
import (
- "encoding/pem"
-
"github.com/golang/glog"
"github.com/golang/protobuf/proto"
"google.golang.org/grpc"
@@ -62,12 +60,11 @@
scheme cobalt.EncryptedMessage_EncryptionScheme) *EncryptedMessageMaker {
var cipher *HybridCipher
if scheme == cobalt.EncryptedMessage_HYBRID_ECDH_V1 {
- block, _ := pem.Decode([]byte(publicKeyPem))
- if block == nil {
- glog.Errorln("Failed to decode publicKeyPem.")
+ publicKey, err := ParseECPublicKeyPem(publicKeyPem)
+ if err != nil {
+ glog.Errorf("Failed to decode public key PEM: %v.", err)
return nil
}
- publicKey := block.Bytes
cipher = NewHybridCipher(nil, publicKey)
if cipher == nil {
glog.Errorln("Failed to construct a HybridCipher.")
@@ -136,11 +133,18 @@
// key.
func NewMessageDecrypter(privateKeyPem string) *MessageDecrypter {
var hybridCipher *HybridCipher
- block, _ := pem.Decode([]byte(privateKeyPem))
- if block == nil {
- glog.V(1).Infoln("Failed to decode privateKeyPem.")
+ 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 {
- hybridCipher = NewHybridCipher(block.Bytes, nil)
+ 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,
@@ -179,6 +183,7 @@
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
}
diff --git a/shuffler/src/util/encrypted_message_util_test.go b/shuffler/src/util/encrypted_message_util_test.go
index d76b960..1af2796 100644
--- a/shuffler/src/util/encrypted_message_util_test.go
+++ b/shuffler/src/util/encrypted_message_util_test.go
@@ -15,13 +15,27 @@
package util
import (
- "encoding/pem"
"reflect"
"testing"
"cobalt"
)
+var privateKeyPem, publicKeyPem string
+
+func init() {
+ privateKeyPem = `-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg1kZxvT81qrRWg2Y8
+g/M7YNtiHaC14/fbevhy/hgXcByhRANCAASkbLO+7iLLaPayYIr3YVmY0jkbwalG
+sOB9Tf3R8TR7Ow43cHlGjX3HALV1z4Lxs1v2K13yeegBJF8lU88cdAqY
+-----END PRIVATE KEY-----`
+
+ publicKeyPem = `-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpGyzvu4iy2j2smCK92FZmNI5G8Gp
+RrDgfU390fE0ezsON3B5Ro19xwC1dc+C8bNb9itd8nnoASRfJVPPHHQKmA==
+-----END PUBLIC KEY-----`
+}
+
// Makes and returns an Envelope with some non-default values so that we can recognized
// when we correctly encrypt and decrypt it.
func MakeTestEnvelope() cobalt.Envelope {
@@ -43,6 +57,9 @@
func TestNoEncryption(t *testing.T) {
// Make an EncryptedMessageMaker
encryptedMessageMaker := NewEncryptedMessageMaker("", cobalt.EncryptedMessage_NONE)
+ if encryptedMessageMaker == nil {
+ t.Fatal("Failed to create EncryptedMessageMaker")
+ }
// Make an Envelope with some non-default values so we can recognize it.
envelope1 := MakeTestEnvelope()
@@ -72,24 +89,11 @@
// Tests that a MessageDecrypter that is constructed with a valid private key
// can decrypt messages that use the HYBRID_ECDH_V1 scheme.
func TestHybridEncryption(t *testing.T) {
- // Generate a key pair.
- privateKey, publicKey, _, _, err := generateECKey()
- if err != nil {
- t.Errorf("%v", err)
- }
-
- // Make privateKeyPem
- block := pem.Block{
- Bytes: privateKey,
- }
- privateKeyPem := string(pem.EncodeToMemory(&block))
-
- // Make publicKeyPem
- block.Bytes = publicKey
- publicKeyPem := string(pem.EncodeToMemory(&block))
-
// Make an EncryptedMessageMaker
encryptedMessageMaker := NewEncryptedMessageMaker(publicKeyPem, cobalt.EncryptedMessage_HYBRID_ECDH_V1)
+ if encryptedMessageMaker == nil {
+ t.Fatal("Failed to create EncryptedMessageMaker")
+ }
// Make an Envelope with some non-default values so we can recognize it.
envelope1 := MakeTestEnvelope()
@@ -119,20 +123,11 @@
// Tests that a MessageDecrypter that is constructed with an invalid private key
// can fail gracefully.
func TestFailedHybridEncryption(t *testing.T) {
- // Generate a key pair.
- _, publicKey, _, _, err := generateECKey()
- if err != nil {
- t.Errorf("%v", err)
- }
-
- // Make publicKeyPem
- block := pem.Block{
- Bytes: publicKey,
- }
- publicKeyPem := string(pem.EncodeToMemory(&block))
-
// Make an EncryptedMessageMaker
encryptedMessageMaker := NewEncryptedMessageMaker(publicKeyPem, cobalt.EncryptedMessage_HYBRID_ECDH_V1)
+ if encryptedMessageMaker == nil {
+ t.Fatal("Failed to create EncryptedMessageMaker")
+ }
// Make an Envelope with some non-default values so we can recognize it.
envelope1 := MakeTestEnvelope()
@@ -157,24 +152,11 @@
// Tests that a MessageDecrypter that is constructed with a valid private key
// and is given a corrupted ciphertext can fail gracefully.
func TestCorruptedHybridEncryption(t *testing.T) {
- // Generate a key pair.
- privateKey, publicKey, _, _, err := generateECKey()
- if err != nil {
- t.Errorf("%v", err)
- }
-
- // Make privateKeyPem
- block := pem.Block{
- Bytes: privateKey,
- }
- privateKeyPem := string(pem.EncodeToMemory(&block))
-
- // Make publicKeyPem
- block.Bytes = publicKey
- publicKeyPem := string(pem.EncodeToMemory(&block))
-
// Make an EncryptedMessageMaker
encryptedMessageMaker := NewEncryptedMessageMaker(publicKeyPem, cobalt.EncryptedMessage_HYBRID_ECDH_V1)
+ if encryptedMessageMaker == nil {
+ t.Fatal("Failed to create EncryptedMessageMaker")
+ }
// Make an Envelope with some non-default values so we can recognize it.
envelope1 := MakeTestEnvelope()