diff --git a/crypto/pemutil/pem.go b/crypto/pemutil/pem.go index cdb69666..ac0b38ea 100644 --- a/crypto/pemutil/pem.go +++ b/crypto/pemutil/pem.go @@ -96,6 +96,18 @@ func WithPasswordFile(filename string) Options { } } +// WithPasswordPrompt ask the user for a password and adds it to the context. +func WithPasswordPrompt(prompt string) Options { + return func(ctx *context) error { + b, err := ui.PromptPassword(prompt) + if err != nil { + return err + } + ctx.password = b + return nil + } +} + // WithPKCS8 with v set to true returns an option used in the Serialize method // to use the PKCS#8 encoding form on the private keys. With v set to false // default form will be used. @@ -366,3 +378,26 @@ func Serialize(in interface{}, opts ...Options) (p *pem.Block, err error) { return p, nil } + +// ParseDER parses the given DER-encoded bytes and results the public or private +// key encoded. +func ParseDER(b []byte) (interface{}, error) { + // Try private keys + key, err := ParsePKCS8PrivateKey(b) + if err != nil { + if key, err = x509.ParseECPrivateKey(b); err != nil { + key, err = x509.ParsePKCS1PrivateKey(b) + } + } + + // Try public key + if err != nil { + if key, err = x509.ParsePKIXPublicKey(b); err != nil { + if key, err = x509.ParsePKCS1PublicKey(b); err != nil { + return nil, errors.New("error decoding DER; bad format") + } + } + } + + return key, nil +} diff --git a/crypto/pemutil/pem_test.go b/crypto/pemutil/pem_test.go index bada9ff0..4825f2f4 100644 --- a/crypto/pemutil/pem_test.go +++ b/crypto/pemutil/pem_test.go @@ -10,6 +10,7 @@ import ( "encoding/pem" "io/ioutil" "os" + "reflect" "strings" "testing" @@ -488,66 +489,67 @@ func TestSerialize(t *testing.T) { } } -/* -func TestWriteKey(t *testing.T) { - keyOut := "./test.key" - pass := "pass" +func TestParseDER(t *testing.T) { + k1, err := Read("testdata/openssl.rsa2048.pem") + assert.FatalError(t, err) + k2, err := Read("testdata/openssl.p256.pem") + assert.FatalError(t, err) + k3, err := Read("testdata/pkcs8/openssl.ed25519.pem") + assert.FatalError(t, err) + rsaKey := k1.(*rsa.PrivateKey) + ecdsaKey := k2.(*ecdsa.PrivateKey) + edKey := k3.(ed25519.PrivateKey) + // Ed25519 der files + edPubDer, err := ioutil.ReadFile("testdata/pkcs8/openssl.ed25519.pub.der") + assert.FatalError(t, err) + edPrivDer, err := ioutil.ReadFile("testdata/pkcs8/openssl.ed25519.der") + assert.FatalError(t, err) - tests := map[string]struct { - key func() (interface{}, error) - keyOut string - pass string - err error - }{ - "success": { - key: func() (interface{}, error) { - return GenerateKey(DefaultKeyType, DefaultKeyCurve, 0) - }, - keyOut: keyOut, - pass: pass, - err: errors.New("failed to convert private key to PEM block: encryption passphrase cannot be empty"), - }, - } - - for name, test := range tests { - t.Logf("Running test case: %s", name) - - key, err := test.key() - assert.FatalError(t, err) - - err = WritePrivateKey(key, test.pass, test.keyOut) - if err != nil { - if assert.NotNil(t, test.err) { - assert.HasPrefix(t, err.Error(), test.err.Error()) - } - } else { - // Check key permissions - fileInfo, err := os.Stat(test.keyOut) + toDER := func(k interface{}) []byte { + switch k := k.(type) { + case *rsa.PublicKey, *ecdsa.PublicKey: + b, err := x509.MarshalPKIXPublicKey(k) assert.FatalError(t, err) - fileMode := fileInfo.Mode() - if fileMode != 0600 { - t.Errorf("FileMode mismatch for file %s -- expected: `%d`, but got: `%d`", - test.keyOut, fileMode, 0600) - } - - switch k := key.(type) { - case *ecdsa.PrivateKey: - // Verify that key written to file is correct - plain, err := x509.MarshalECPrivateKey(k) - assert.FatalError(t, err) - keyFileBytes, err := ioutil.ReadFile(test.keyOut) - assert.FatalError(t, err) - pemKey, _ := pem.Decode(keyFileBytes) - assert.True(t, x509.IsEncryptedPEMBlock(pemKey)) - assert.Equals(t, pemKey.Type, "EC PRIVATE KEY") - assert.Equals(t, pemKey.Headers["Proc-Type"], "4,ENCRYPTED") - der, err := x509.DecryptPEMBlock(pemKey, []byte(pass)) - assert.FatalError(t, err) - assert.Equals(t, der, plain) - default: - t.Errorf("unexpected key type %T", k) - } + return b + case *rsa.PrivateKey: + return x509.MarshalPKCS1PrivateKey(k) + case *ecdsa.PrivateKey: + b, err := x509.MarshalECPrivateKey(k) + assert.FatalError(t, err) + return b + default: + t.Fatalf("unsupported key type %T", k) + return nil } } + + type args struct { + b []byte + } + tests := []struct { + name string + args args + want interface{} + wantErr bool + }{ + {"rsa public key", args{toDER(rsaKey.Public())}, rsaKey.Public(), false}, + {"rsa private key", args{toDER(rsaKey)}, rsaKey, false}, + {"ecdsa public key", args{toDER(ecdsaKey.Public())}, ecdsaKey.Public(), false}, + {"ecdsa private key", args{toDER(ecdsaKey)}, ecdsaKey, false}, + {"ed25519 public key", args{edPubDer}, edKey.Public(), false}, + {"ed25519 private key", args{edPrivDer}, edKey, false}, + {"fail", args{[]byte("fooo")}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseDER(tt.args.b) + if (err != nil) != tt.wantErr { + t.Errorf("ParseDER() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseDER() = %v, want %v", got, tt.want) + } + }) + } } -*/