Skip to content

Commit a4f5e04

Browse files
committed
lang/funcs: Add support for OpenSSH RSA key format
Previously this function only supported the x509 RSA private key format. More recent versions of OpenSSH default to generating a new PEM key format, which this commit now supports using the x/crypto/ssh package. Also improve the returned error messages for various invalid ciphertext or invalid private key errors.
1 parent 6fbd394 commit a4f5e04

File tree

2 files changed

+99
-29
lines changed

2 files changed

+99
-29
lines changed

lang/funcs/crypto.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@ import (
66
"crypto/sha1"
77
"crypto/sha256"
88
"crypto/sha512"
9-
"crypto/x509"
9+
"encoding/asn1"
1010
"encoding/base64"
1111
"encoding/hex"
12-
"encoding/pem"
1312
"fmt"
1413
"hash"
14+
"strings"
1515

1616
uuidv5 "github.com/google/uuid"
1717
uuid "github.com/hashicorp/go-uuid"
1818
"github.com/zclconf/go-cty/cty"
1919
"github.com/zclconf/go-cty/cty/function"
2020
"github.com/zclconf/go-cty/cty/gocty"
2121
"golang.org/x/crypto/bcrypt"
22+
"golang.org/x/crypto/ssh"
2223
)
2324

2425
var UUIDFunc = function.New(&function.Spec{
@@ -152,27 +153,30 @@ var RsaDecryptFunc = function.New(&function.Spec{
152153

153154
b, err := base64.StdEncoding.DecodeString(s)
154155
if err != nil {
155-
return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode input %q: cipher text must be base64-encoded", s)
156+
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "failed to decode input %q: cipher text must be base64-encoded", s)
156157
}
157158

158-
block, _ := pem.Decode([]byte(key))
159-
if block == nil {
160-
return cty.UnknownVal(cty.String), fmt.Errorf("failed to parse key: no key found")
161-
}
162-
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
163-
return cty.UnknownVal(cty.String), fmt.Errorf(
164-
"failed to parse key: password protected keys are not supported. Please decrypt the key prior to use",
165-
)
166-
}
167-
168-
x509Key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
159+
rawKey, err := ssh.ParseRawPrivateKey([]byte(key))
169160
if err != nil {
170-
return cty.UnknownVal(cty.String), err
161+
var errStr string
162+
switch e := err.(type) {
163+
case asn1.SyntaxError:
164+
errStr = strings.ReplaceAll(e.Error(), "asn1: syntax error", "invalid ASN1 data in the given private key")
165+
case asn1.StructuralError:
166+
errStr = strings.ReplaceAll(e.Error(), "asn1: struture error", "invalid ASN1 data in the given private key")
167+
default:
168+
errStr = fmt.Sprintf("invalid private key: %s", e)
169+
}
170+
return cty.UnknownVal(cty.String), function.NewArgErrorf(1, errStr)
171+
}
172+
privateKey, ok := rawKey.(*rsa.PrivateKey)
173+
if !ok {
174+
return cty.UnknownVal(cty.String), function.NewArgErrorf(1, "invalid private key type %t", rawKey)
171175
}
172176

173-
out, err := rsa.DecryptPKCS1v15(nil, x509Key, b)
177+
out, err := rsa.DecryptPKCS1v15(nil, privateKey, b)
174178
if err != nil {
175-
return cty.UnknownVal(cty.String), err
179+
return cty.UnknownVal(cty.String), fmt.Errorf("failed to decrypt: %s", err)
176180
}
177181

178182
return cty.StringVal(string(out)), nil

lang/funcs/crypto_test.go

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -370,58 +370,67 @@ func TestRsaDecrypt(t *testing.T) {
370370
Ciphertext cty.Value
371371
Privatekey cty.Value
372372
Want cty.Value
373-
Err bool
373+
Err string
374374
}{
375375
// Base-64 encoded cipher decrypts correctly
376376
{
377377
cty.StringVal(CipherBase64),
378378
cty.StringVal(PrivateKey),
379379
cty.StringVal("message"),
380-
false,
380+
"",
381+
},
382+
// OpenSSH key format
383+
{
384+
cty.StringVal(CipherBase64),
385+
cty.StringVal(OpenSSHPrivateKey),
386+
cty.StringVal("message"),
387+
"",
381388
},
382389
// Wrong key
383390
{
384391
cty.StringVal(CipherBase64),
385392
cty.StringVal(WrongPrivateKey),
386393
cty.UnknownVal(cty.String),
387-
true,
394+
"failed to decrypt: crypto/rsa: decryption error",
388395
},
389396
// Bad key
390397
{
391398
cty.StringVal(CipherBase64),
392-
cty.StringVal("bad key"),
399+
cty.StringVal(BadPrivateKey),
393400
cty.UnknownVal(cty.String),
394-
true,
401+
"invalid ASN1 data in the given private key: data truncated",
395402
},
396403
// Empty key
397404
{
398405
cty.StringVal(CipherBase64),
399406
cty.StringVal(""),
400407
cty.UnknownVal(cty.String),
401-
true,
408+
"invalid private key: ssh: no key found",
402409
},
403-
// Bad cipher
410+
// Bad ciphertext
404411
{
405-
cty.StringVal("bad cipher"),
412+
cty.StringVal("bad"),
406413
cty.StringVal(PrivateKey),
407414
cty.UnknownVal(cty.String),
408-
true,
415+
`failed to decode input "bad": cipher text must be base64-encoded`,
409416
},
410-
// Empty cipher
417+
// Empty ciphertext
411418
{
412419
cty.StringVal(""),
413420
cty.StringVal(PrivateKey),
414421
cty.UnknownVal(cty.String),
415-
true,
422+
"failed to decrypt: crypto/rsa: decryption error",
416423
},
417424
}
418425
for _, test := range tests {
419426
t.Run(fmt.Sprintf("RsaDecrypt(%#v, %#v)", test.Ciphertext, test.Privatekey), func(t *testing.T) {
420427
got, err := RsaDecrypt(test.Ciphertext, test.Privatekey)
421428

422-
if test.Err {
429+
if test.Err != "" {
423430
if err == nil {
424431
t.Fatal("succeeded; want error")
432+
} else if err.Error() != test.Err {
433+
t.Fatalf("wrong error\ngot: %s\nwant: %s", err.Error(), test.Err)
425434
}
426435
return
427436
} else if err != nil {
@@ -699,6 +708,35 @@ OLlRAoGBAIZ5Uv4Z3s8O7WKXXUe/lq6j7vfiVkR1NW/Z/WLKXZpnmvJ7FgxN4e56
699708
RXT7GwNQHIY8eDjDnsHxzrxd+raOxOZeKcMHj3XyjCX3NHfTscnsBPAGYpY/Wxzh
700709
T8UYnFu6RzkixElTf2rseEav7rkdKkI3LAeIZy7B0HulKKsmqVQ7
701710
-----END RSA PRIVATE KEY-----
711+
`
712+
OpenSSHPrivateKey = `
713+
-----BEGIN OPENSSH PRIVATE KEY-----
714+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
715+
NhAAAAAwEAAQAAAQEAgUElV5mwqkloIrM8ZNZ72gSCcnSJt7+/Usa5G+D15YQUAdf9c1zE
716+
ekTfHgDP+04nw/uFNFaE5v1RbHaPxhZYVg5ZErNCa/hzn+x10xzcepeS3KPVXcxae4MR0B
717+
EegvqZqJzN9loXsNL/c3H/B+2Gle3hTxjlWFb3F5qLgR+4Mf4ruhER1v6eHQa/nchi03MB
718+
pT4UeJ7MrL92hTJYLdpSyCqmr8yjxkKJDVC2uRrr+sTSxfh7r6v24u/vp/QTmBIAlNPgad
719+
VAZw17iNNb7vjV7Gwl/5gHXonCUKURaV++dBNLrHIZpqcAM8wHRph8mD1EfL9hsz77pHew
720+
xolBATV+7QAAA7jbhEFk24RBZAAAAAdzc2gtcnNhAAABAQCBQSVXmbCqSWgiszxk1nvaBI
721+
JydIm3v79Sxrkb4PXlhBQB1/1zXMR6RN8eAM/7TifD+4U0VoTm/VFsdo/GFlhWDlkSs0Jr
722+
+HOf7HXTHNx6l5Lco9VdzFp7gxHQER6C+pmonM32Whew0v9zcf8H7YaV7eFPGOVYVvcXmo
723+
uBH7gx/iu6ERHW/p4dBr+dyGLTcwGlPhR4nsysv3aFMlgt2lLIKqavzKPGQokNULa5Guv6
724+
xNLF+Huvq/bi7++n9BOYEgCU0+Bp1UBnDXuI01vu+NXsbCX/mAdeicJQpRFpX750E0usch
725+
mmpwAzzAdGmHyYPUR8v2GzPvukd7DGiUEBNX7tAAAAAwEAAQAAAQAtayvpBVt76wGJt/vP
726+
30J0EMOZ3nOKOvnK54OiVUFy3h99ql0oTX/JCyxvyY9L2mHEzzw2cPSQipEzENJio/V0f+
727+
Qy2wTLFenjV17rySd8eIiluXg/VpCw+BSpTWqwUcju4/LHz06l1u7mrTcVnRR+2LEkbzYf
728+
/ackBy1gOTorbonTK2G3NxFMfAdRjzcifVvEPM5zWC38GDo1OFr9UixOqhkEB/UNFswNll
729+
H/I5JQmMjGEyMsAIxm/JGwCZSoZo9rdiII5qrcLdT2HKRpam7UAQ1Ill7eUuGF/9ZmiEP+
730+
PcnjVGo46WyYh9w24SWx8BU8z96WfT/Rhzs5RpGEfsEhAAAAgQCGeVL+Gd7PDu1il11Hv5
731+
auo+734lZEdTVv2f1iyl2aZ5ryexYMTeHuekV0+xsDUByGPHg4w57B8c68Xfq2jsTmXinD
732+
B4918owl9zR307HJ7ATwBmKWP1sc4U/FGJxbukc5IsRJU39q7HhGr+65HSpCNywHiGcuwd
733+
B7pSirJqlUOwAAAIEAw/Xida8kSv8n86V3qSY/I+fYQ5V+jDtXIE+JhRnS8xzbOzz3v0WS
734+
Oo5H+o4nJx5eL3Ghb3Gcm0Jn46dHrxinHbm+3RjXv/X6tlbxIYjRSQfHOTSMCTvdqcliF5
735+
vC6RCLXuc7R+IWR1Ky6eDEZGtrvt3DyeYABsp9fRUFR/6NluUAAACBAKjbMNWkpe1iWtux
736+
dA6CXZVPS1nq6V3Gs5SXCHTs/0vUA5kit0Q0E3an08UZq8YmCPSxLJpDpL85Z5zgTKZ2d2
737+
TlOaiEX6a3nESIt+ygwDh1hp5QtBFhoJeOmC2+/414ln9ABmPg3ySTXfYuk2yA1rvNueP3
738+
qwEumyjIVv96u39pAAAAAAEC
739+
-----END OPENSSH PRIVATE KEY-----
702740
`
703741
WrongPrivateKey = `
704742
-----BEGIN RSA PRIVATE KEY-----
@@ -728,5 +766,33 @@ CYhwNQKBgGPcLXmjpGtkZvggl0aZr9LsvCTckllSCFSI861kivL/rijdNoCHGxZv
728766
dfDkLTLcz9Gk41rD9Gxn/3sqodnTAc3Z2PxFnzg1Q/u3+x6YAgBwI/g/jE2xutGW
729767
H7CurtMwALQ/n/6LUKFmjRZjqbKX9SO2QSaC3grd6sY9Tu+bZjLe
730768
-----END RSA PRIVATE KEY-----
769+
`
770+
BadPrivateKey = `
771+
-----BEGIN RSA PRIVATE KEY-----
772+
MIIEowIBAAKCAQEAgUElV5mwqkloIrM8ZNZ72gSCcnSJt7+/Usa5G+D15YQUAdf9
773+
c1zEekTfHgDP+04nw/uFNFaE5v1RbHaPxhZYVg5ZErNCa/hzn+x10xzcepeS3KPV
774+
Xcxae4MR0BEegvqZqJzN9loXsNL/c3H/B+2Gle3hTxjlWFb3F5qLgR+4Mf4ruhER
775+
1v6eHQa/nchi03MBpT4UeJ7MrL92hTJYLdpSyCqmr8yjxkKJDVC2uRrr+sTSxfh7
776+
r6v24u/vp/QTmBIAlNPgadVAZw17iNNb7vjV7Gwl/5gHXonCUKURaV++dBNLrHIZ
777+
pqcAM8wHRph8mD1EfL9hsz77pHewxolBATV+7QIDAQABAoIBAC1rK+kFW3vrAYm3
778+
+8/fQnQQw5nec4o6+crng6JVQXLeH32qXShNf8kLLG/Jj0vaYcTPPDZw9JCKkTMQ
779+
0mKj9XR/5DLbBMsV6eNXXuvJJ3x4iKW5eD9WkLD4FKlNarBRyO7j8sfPTqXW7uat
780+
NxWdFH7YsSRvNh/9pyQHLWA5OituidMrYbc3EUx8B1GPNyJ9W8Q8znNYLfwYOjU4
781+
Wv1SLE6qGQQH9Q0WzA2WUf8jklCYyMYTIywAjGb8kbAJlKhmj2t2Igjmqtwt1PYc
782+
pGlqbtQBDUiWXt5S4YX/1maIQ/49yeNUajjpbJiH3DbhJbHwFTzP3pZ9P9GHOzlG
783+
kYR+wSECgYEAw/Xida8kSv8n86V3qSY/I+fYQ5V+jDtXIE+JhRnS8xzbOzz3v0WS
784+
Oo5H+o4nJx5eL3Ghb3Gcm0Jn46dHrxinHbm+3RjXv/X6tlbxIYjRSQfHOTSMCTvd
785+
qcliF5vC6RCLXuc7R+IWR1Ky6eDEZGtrvt3DyeYABsp9fRUFR/6NluUCgYEAqNsw
786+
1aSl7WJa27F0DoJdlU9LWerpXcazlJcIdOz/S9QDmSK3RDQTdqfTxRmrxiYI9LEs
787+
mkOkvzlnnOBMpnZ3ZOU5qIRfprecRIi37KDAOHWGnlC0EWGgl46YLb7/jXiWf0AG
788+
BhXoKvjI2HjYP21z/EyZ+PFPzur/lNaZhIUlMnUfibbwE9pFggQzzf8scM7c7Sf+
789+
mLoVSdoQ/Rujz7CqvQzi2nKSsM7t0curUIb3lJWee5/UeEaxZcmIufoNUrzohAWH
790+
BJOIPDM4ssUTLRq7wYM9uQKBgHCBau5OP8gE6mjKuXsZXWUoahpFLKwwwmJUp2vQ
791+
pOFPJ/6WZOlqkTVT6QPAcPUbTohKrF80hsZqZyDdSfT3peFx4ZLocBrS56m6NmHR
792+
UYHMvJ8rQm76T1fryHVidz85g3zRmfBeWg8yqT5oFg4LYgfLsPm1gRjOhs8LfPvI
793+
OLlRAoGBAIZ5Uv4Z3s8O7WKXXUe/lq6j7vfiVkR1NW/Z/WLKXZpnmvJ7FgxN4e56
794+
RXT7GwNQHIY8eDjDnsHxzrxd+raOxOZeKcMHj3XyjCX3NHfTscnsBPAGYpY/Wxzh
795+
T8UYnFu6RzkixElTf2rseEav7rkdKkI3LAeIZy7B0HulKKsmqVQ7
796+
-----END RSA PRIVATE KEY-----
731797
`
732798
)

0 commit comments

Comments
 (0)