Skip to content

Commit

Permalink
implement Parse/Load RSA private key
Browse files Browse the repository at this point in the history
We use PKCS1 for parsing/loading RSA private keys.
This means we do not support ECDSA yet.
  • Loading branch information
shibumi committed Jun 26, 2020
1 parent d868be7 commit f54d2e5
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 1 deletion.
104 changes: 104 additions & 0 deletions in_toto/keylib.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,28 @@ func ParseRSAPublicKeyFromPEM(pemBytes []byte) (*rsa.PublicKey, error) {
return rsaPub, nil
}

/*
ParseRSAPrivateKeyFromPEM parses the passed pemBytes as e.g. read from a PEM
formatted file, and instantiates and returns the corresponding RSA Private key.
If the no RSA Private key can be parsed, the first return value is nil and the
second return value is the error.
*/
func ParseRSAPrivateKeyFromPEM(pemBytes []byte) (*rsa.PrivateKey, error) {
// TODO: There could be more key data in _, which we silently ignore here.
// Should we handle it / fail / say something about it?
data, _ := pem.Decode(pemBytes)
if data == nil {
return nil, fmt.Errorf("Could not find a Private key PEM block")
}

priv, err := x509.ParsePKCS1PrivateKey(data.Bytes)
if err != nil {
return nil, err
}

return priv, nil
}

/*
LoadRSAPublicKey parses an RSA public key from a PEM formatted file at the passed
path into the Key object on which it was called. It returns an error if the
Expand Down Expand Up @@ -128,6 +150,88 @@ func (k *Key) LoadRSAPublicKey(path string) (err error) {
return nil
}

/*
LoadRSAPrivateKey parses an RSA Private key from a PEM formatted file at the passed
path into the Key object on which it was called. It returns an error if the
file at path does not exist or is not a PEM formatted RSA Private key.
*/
func (k *Key) LoadRSAPrivateKey(path string) (err error) {
keyFile, err := os.Open(path)
if err != nil {
return err
}
defer func() {
if closeErr := keyFile.Close(); closeErr != nil {
err = closeErr
}
}()

// Read key bytes and decode PEM
keyBytes, err := ioutil.ReadAll(keyFile)
if err != nil {
return err
}

// We only parse to see if this is indeed a pem formatted rsa Private key,
// but don't use the returned *rsa.PrivateKey. Instead, we continue with
// the original keyBytes from above.
_, err = ParseRSAPrivateKeyFromPEM(keyBytes)
if err != nil {
return err
}

// Strip leading and trailing data from PEM file like securesystemslib does
// TODO: Should we instead use the parsed Private key to reconstruct the PEM?
keyHeader := "-----BEGIN RSA PRIVATE KEY-----"
keyFooter := "-----END RSA PRIVATE KEY-----"
keyStart := strings.Index(string(keyBytes), keyHeader)
keyEnd := strings.Index(string(keyBytes), keyFooter) + len(keyFooter)
// Successful call to ParseRSAPrivateKeyFromPEM already guarantees that
// header and footer are present, i.e. `!(keyStart == -1 || keyEnd == -1)`
keyBytesStripped := keyBytes[keyStart:keyEnd]

// Declare values for key
// TODO: Do not hardcode here, but define defaults elsewhere and add support
// for parametrization
keyType := "rsa"
scheme := "rsassa-pss-sha256"
keyIdHashAlgorithms := []string{"sha256", "sha512"}

// Create partial key map used to create the keyid
// Unfortunately, we can't use the Key object because this also carries
// yet unwanted fields, such as KeyId and KeyVal.Private and therefore
// produces a different hash
var keyToBeHashed = map[string]interface{}{
"keytype": keyType,
"scheme": scheme,
"keyid_hash_algorithms": keyIdHashAlgorithms,
"keyval": map[string]string{
"Private": string(keyBytesStripped),
},
}

// Canonicalize key and get hex representation of hash
keyCanonical, err := EncodeCanonical(keyToBeHashed)
if err != nil {
return err
}
keyHashed := sha256.Sum256(keyCanonical)

// Unmarshalling the canonicalized key into the Key object would seem natural
// Unfortunately, our mandated canonicalization function produces a byte
// slice that cannot be unmarshalled by Golang's json decoder, hence we have
// to manually assign the values
k.KeyType = keyType
k.KeyVal = KeyVal{
Private: string(keyBytesStripped),
}
k.Scheme = scheme
k.KeyIdHashAlgorithms = keyIdHashAlgorithms
k.KeyId = fmt.Sprintf("%x", keyHashed)

return nil
}

/*
VerifyRSASignature uses the passed Key to verify the passed Signature over the
passed data. It returns an error if the key is not a valid RSA public key or
Expand Down
31 changes: 30 additions & 1 deletion in_toto/keylib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ yMxdI/24LUOOQ71cHW3ITIDImm6I8KmrXFM2NewTARKfAgMBAAE=
}
}

func TestLoadPublicKey(t *testing.T) {
func TestLoadRSAPublicKey(t *testing.T) {
// Test loading valid public rsa key from pem-formatted file
var key Key
err := key.LoadRSAPublicKey("alice.pub")
Expand Down Expand Up @@ -86,6 +86,35 @@ func TestLoadPublicKey(t *testing.T) {
}
}

func TestLoadRSAPrivateKey(t *testing.T) {
// Test loading valid Private rsa key from pem-formatted file
var key Key
err := key.LoadRSAPrivateKey("dan")
if err != nil {
t.Errorf("LoadRSAPrivateKey returned %s, expected no error", err)
}
expectedKeyID := "f29cb6877d14ebcf28b136a96a4d64935522afaddcc84e6b70ff6b9eaefb8fcf"
if key.KeyId != expectedKeyID {
t.Errorf("LoadRSAPrivateKey parsed KeyId '%s', expected '%s'",
key.KeyId, expectedKeyID)
}

// Test loading error:
// - Not a pem formatted rsa Private key
expectedError := "Could not find a Private key PEM block"
err = key.LoadRSAPrivateKey("demo.layout.template")
if err == nil || !strings.Contains(err.Error(), expectedError) {
t.Errorf("LoadRSAPrivateKey returned (%s), expected '%s' error", err,
expectedError)
}

// Test not existing file
err = key.LoadRSAPrivateKey("inToToRocks")
if !errors.Is(err, os.ErrNotExist) {
t.Errorf("Invalid file load returned (%s), expected '%s' error", err, os.ErrNotExist)
}
}

func TestVerifySignature(t *testing.T) {
validSig := Signature{
KeyId: "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498",
Expand Down
39 changes: 39 additions & 0 deletions test/data/dan
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
11 changes: 11 additions & 0 deletions test/data/dan.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCTik98953hKl6+B6n5l
8DVIDwDnvrJfpasbJ3+Rw66YcawOZinRpMxPTqWBKs7sRop7jqsQNcslUoIZLrXP
r3foPHF455TlrqPVfCZiFQ+O4CafxWOB4mL1NddvpFXTEjmUiwFrrL7PcvQKMbYz
eUHH4tH9MNzqKWbbJoekBsDpCDIxp1NbgivGBKwjRGa281sClKgpd0Q0ebl+RTcT
vpfZVDbXazQ7VqZkidt7geWq2BidOXZp/cjoXyVneKx/gYiOUv8x94svQMzSEhw2
LFMQ04A1KnGn1jxO35/fd6/OW32njyWs96RKu9UQVacYHsQfsACPWwmVqgnX/sp5
ujlvSDjyfZu7c5yUQ2asYfQPLvnjG+u7QcBukGf8hAfVgsezzX9QPiK35BKDgBU/
Vk43riJs165TJGYGVuLUhIEhHgiQtwo8pUTJS5npEe5XMDuZoighNdzoWY2nfsBf
p8348k6vJtDMB093/t6V9sTGYQcSbgKPyEQo5Pk6Wd4ZAgMBAAE=
-----END PUBLIC KEY-----

0 comments on commit f54d2e5

Please sign in to comment.