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.
This commit is contained in:
parent
6fbd3942ea
commit
a4f5e04066
|
@ -6,12 +6,12 @@ import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"crypto/x509"
|
"encoding/asn1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
|
"strings"
|
||||||
|
|
||||||
uuidv5 "github.com/google/uuid"
|
uuidv5 "github.com/google/uuid"
|
||||||
uuid "github.com/hashicorp/go-uuid"
|
uuid "github.com/hashicorp/go-uuid"
|
||||||
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/zclconf/go-cty/cty/function"
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
"github.com/zclconf/go-cty/cty/gocty"
|
"github.com/zclconf/go-cty/cty/gocty"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
var UUIDFunc = function.New(&function.Spec{
|
var UUIDFunc = function.New(&function.Spec{
|
||||||
|
@ -152,27 +153,30 @@ var RsaDecryptFunc = function.New(&function.Spec{
|
||||||
|
|
||||||
b, err := base64.StdEncoding.DecodeString(s)
|
b, err := base64.StdEncoding.DecodeString(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode input %q: cipher text must be base64-encoded", s)
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "failed to decode input %q: cipher text must be base64-encoded", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
block, _ := pem.Decode([]byte(key))
|
rawKey, err := ssh.ParseRawPrivateKey([]byte(key))
|
||||||
if block == nil {
|
|
||||||
return cty.UnknownVal(cty.String), fmt.Errorf("failed to parse key: no key found")
|
|
||||||
}
|
|
||||||
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
|
|
||||||
return cty.UnknownVal(cty.String), fmt.Errorf(
|
|
||||||
"failed to parse key: password protected keys are not supported. Please decrypt the key prior to use",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
x509Key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cty.UnknownVal(cty.String), err
|
var errStr string
|
||||||
|
switch e := err.(type) {
|
||||||
|
case asn1.SyntaxError:
|
||||||
|
errStr = strings.ReplaceAll(e.Error(), "asn1: syntax error", "invalid ASN1 data in the given private key")
|
||||||
|
case asn1.StructuralError:
|
||||||
|
errStr = strings.ReplaceAll(e.Error(), "asn1: struture error", "invalid ASN1 data in the given private key")
|
||||||
|
default:
|
||||||
|
errStr = fmt.Sprintf("invalid private key: %s", e)
|
||||||
|
}
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(1, errStr)
|
||||||
|
}
|
||||||
|
privateKey, ok := rawKey.(*rsa.PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(1, "invalid private key type %t", rawKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := rsa.DecryptPKCS1v15(nil, x509Key, b)
|
out, err := rsa.DecryptPKCS1v15(nil, privateKey, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cty.UnknownVal(cty.String), err
|
return cty.UnknownVal(cty.String), fmt.Errorf("failed to decrypt: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cty.StringVal(string(out)), nil
|
return cty.StringVal(string(out)), nil
|
||||||
|
|
|
@ -370,58 +370,67 @@ func TestRsaDecrypt(t *testing.T) {
|
||||||
Ciphertext cty.Value
|
Ciphertext cty.Value
|
||||||
Privatekey cty.Value
|
Privatekey cty.Value
|
||||||
Want cty.Value
|
Want cty.Value
|
||||||
Err bool
|
Err string
|
||||||
}{
|
}{
|
||||||
// Base-64 encoded cipher decrypts correctly
|
// Base-64 encoded cipher decrypts correctly
|
||||||
{
|
{
|
||||||
cty.StringVal(CipherBase64),
|
cty.StringVal(CipherBase64),
|
||||||
cty.StringVal(PrivateKey),
|
cty.StringVal(PrivateKey),
|
||||||
cty.StringVal("message"),
|
cty.StringVal("message"),
|
||||||
false,
|
"",
|
||||||
|
},
|
||||||
|
// OpenSSH key format
|
||||||
|
{
|
||||||
|
cty.StringVal(CipherBase64),
|
||||||
|
cty.StringVal(OpenSSHPrivateKey),
|
||||||
|
cty.StringVal("message"),
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
// Wrong key
|
// Wrong key
|
||||||
{
|
{
|
||||||
cty.StringVal(CipherBase64),
|
cty.StringVal(CipherBase64),
|
||||||
cty.StringVal(WrongPrivateKey),
|
cty.StringVal(WrongPrivateKey),
|
||||||
cty.UnknownVal(cty.String),
|
cty.UnknownVal(cty.String),
|
||||||
true,
|
"failed to decrypt: crypto/rsa: decryption error",
|
||||||
},
|
},
|
||||||
// Bad key
|
// Bad key
|
||||||
{
|
{
|
||||||
cty.StringVal(CipherBase64),
|
cty.StringVal(CipherBase64),
|
||||||
cty.StringVal("bad key"),
|
cty.StringVal(BadPrivateKey),
|
||||||
cty.UnknownVal(cty.String),
|
cty.UnknownVal(cty.String),
|
||||||
true,
|
"invalid ASN1 data in the given private key: data truncated",
|
||||||
},
|
},
|
||||||
// Empty key
|
// Empty key
|
||||||
{
|
{
|
||||||
cty.StringVal(CipherBase64),
|
cty.StringVal(CipherBase64),
|
||||||
cty.StringVal(""),
|
cty.StringVal(""),
|
||||||
cty.UnknownVal(cty.String),
|
cty.UnknownVal(cty.String),
|
||||||
true,
|
"invalid private key: ssh: no key found",
|
||||||
},
|
},
|
||||||
// Bad cipher
|
// Bad ciphertext
|
||||||
{
|
{
|
||||||
cty.StringVal("bad cipher"),
|
cty.StringVal("bad"),
|
||||||
cty.StringVal(PrivateKey),
|
cty.StringVal(PrivateKey),
|
||||||
cty.UnknownVal(cty.String),
|
cty.UnknownVal(cty.String),
|
||||||
true,
|
`failed to decode input "bad": cipher text must be base64-encoded`,
|
||||||
},
|
},
|
||||||
// Empty cipher
|
// Empty ciphertext
|
||||||
{
|
{
|
||||||
cty.StringVal(""),
|
cty.StringVal(""),
|
||||||
cty.StringVal(PrivateKey),
|
cty.StringVal(PrivateKey),
|
||||||
cty.UnknownVal(cty.String),
|
cty.UnknownVal(cty.String),
|
||||||
true,
|
"failed to decrypt: crypto/rsa: decryption error",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(fmt.Sprintf("RsaDecrypt(%#v, %#v)", test.Ciphertext, test.Privatekey), func(t *testing.T) {
|
t.Run(fmt.Sprintf("RsaDecrypt(%#v, %#v)", test.Ciphertext, test.Privatekey), func(t *testing.T) {
|
||||||
got, err := RsaDecrypt(test.Ciphertext, test.Privatekey)
|
got, err := RsaDecrypt(test.Ciphertext, test.Privatekey)
|
||||||
|
|
||||||
if test.Err {
|
if test.Err != "" {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("succeeded; want error")
|
t.Fatal("succeeded; want error")
|
||||||
|
} else if err.Error() != test.Err {
|
||||||
|
t.Fatalf("wrong error\ngot: %s\nwant: %s", err.Error(), test.Err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
@ -699,6 +708,35 @@ OLlRAoGBAIZ5Uv4Z3s8O7WKXXUe/lq6j7vfiVkR1NW/Z/WLKXZpnmvJ7FgxN4e56
|
||||||
RXT7GwNQHIY8eDjDnsHxzrxd+raOxOZeKcMHj3XyjCX3NHfTscnsBPAGYpY/Wxzh
|
RXT7GwNQHIY8eDjDnsHxzrxd+raOxOZeKcMHj3XyjCX3NHfTscnsBPAGYpY/Wxzh
|
||||||
T8UYnFu6RzkixElTf2rseEav7rkdKkI3LAeIZy7B0HulKKsmqVQ7
|
T8UYnFu6RzkixElTf2rseEav7rkdKkI3LAeIZy7B0HulKKsmqVQ7
|
||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`
|
||||||
|
OpenSSHPrivateKey = `
|
||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
|
||||||
|
NhAAAAAwEAAQAAAQEAgUElV5mwqkloIrM8ZNZ72gSCcnSJt7+/Usa5G+D15YQUAdf9c1zE
|
||||||
|
ekTfHgDP+04nw/uFNFaE5v1RbHaPxhZYVg5ZErNCa/hzn+x10xzcepeS3KPVXcxae4MR0B
|
||||||
|
EegvqZqJzN9loXsNL/c3H/B+2Gle3hTxjlWFb3F5qLgR+4Mf4ruhER1v6eHQa/nchi03MB
|
||||||
|
pT4UeJ7MrL92hTJYLdpSyCqmr8yjxkKJDVC2uRrr+sTSxfh7r6v24u/vp/QTmBIAlNPgad
|
||||||
|
VAZw17iNNb7vjV7Gwl/5gHXonCUKURaV++dBNLrHIZpqcAM8wHRph8mD1EfL9hsz77pHew
|
||||||
|
xolBATV+7QAAA7jbhEFk24RBZAAAAAdzc2gtcnNhAAABAQCBQSVXmbCqSWgiszxk1nvaBI
|
||||||
|
JydIm3v79Sxrkb4PXlhBQB1/1zXMR6RN8eAM/7TifD+4U0VoTm/VFsdo/GFlhWDlkSs0Jr
|
||||||
|
+HOf7HXTHNx6l5Lco9VdzFp7gxHQER6C+pmonM32Whew0v9zcf8H7YaV7eFPGOVYVvcXmo
|
||||||
|
uBH7gx/iu6ERHW/p4dBr+dyGLTcwGlPhR4nsysv3aFMlgt2lLIKqavzKPGQokNULa5Guv6
|
||||||
|
xNLF+Huvq/bi7++n9BOYEgCU0+Bp1UBnDXuI01vu+NXsbCX/mAdeicJQpRFpX750E0usch
|
||||||
|
mmpwAzzAdGmHyYPUR8v2GzPvukd7DGiUEBNX7tAAAAAwEAAQAAAQAtayvpBVt76wGJt/vP
|
||||||
|
30J0EMOZ3nOKOvnK54OiVUFy3h99ql0oTX/JCyxvyY9L2mHEzzw2cPSQipEzENJio/V0f+
|
||||||
|
Qy2wTLFenjV17rySd8eIiluXg/VpCw+BSpTWqwUcju4/LHz06l1u7mrTcVnRR+2LEkbzYf
|
||||||
|
/ackBy1gOTorbonTK2G3NxFMfAdRjzcifVvEPM5zWC38GDo1OFr9UixOqhkEB/UNFswNll
|
||||||
|
H/I5JQmMjGEyMsAIxm/JGwCZSoZo9rdiII5qrcLdT2HKRpam7UAQ1Ill7eUuGF/9ZmiEP+
|
||||||
|
PcnjVGo46WyYh9w24SWx8BU8z96WfT/Rhzs5RpGEfsEhAAAAgQCGeVL+Gd7PDu1il11Hv5
|
||||||
|
auo+734lZEdTVv2f1iyl2aZ5ryexYMTeHuekV0+xsDUByGPHg4w57B8c68Xfq2jsTmXinD
|
||||||
|
B4918owl9zR307HJ7ATwBmKWP1sc4U/FGJxbukc5IsRJU39q7HhGr+65HSpCNywHiGcuwd
|
||||||
|
B7pSirJqlUOwAAAIEAw/Xida8kSv8n86V3qSY/I+fYQ5V+jDtXIE+JhRnS8xzbOzz3v0WS
|
||||||
|
Oo5H+o4nJx5eL3Ghb3Gcm0Jn46dHrxinHbm+3RjXv/X6tlbxIYjRSQfHOTSMCTvdqcliF5
|
||||||
|
vC6RCLXuc7R+IWR1Ky6eDEZGtrvt3DyeYABsp9fRUFR/6NluUAAACBAKjbMNWkpe1iWtux
|
||||||
|
dA6CXZVPS1nq6V3Gs5SXCHTs/0vUA5kit0Q0E3an08UZq8YmCPSxLJpDpL85Z5zgTKZ2d2
|
||||||
|
TlOaiEX6a3nESIt+ygwDh1hp5QtBFhoJeOmC2+/414ln9ABmPg3ySTXfYuk2yA1rvNueP3
|
||||||
|
qwEumyjIVv96u39pAAAAAAEC
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
`
|
`
|
||||||
WrongPrivateKey = `
|
WrongPrivateKey = `
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
@ -728,5 +766,33 @@ CYhwNQKBgGPcLXmjpGtkZvggl0aZr9LsvCTckllSCFSI861kivL/rijdNoCHGxZv
|
||||||
dfDkLTLcz9Gk41rD9Gxn/3sqodnTAc3Z2PxFnzg1Q/u3+x6YAgBwI/g/jE2xutGW
|
dfDkLTLcz9Gk41rD9Gxn/3sqodnTAc3Z2PxFnzg1Q/u3+x6YAgBwI/g/jE2xutGW
|
||||||
H7CurtMwALQ/n/6LUKFmjRZjqbKX9SO2QSaC3grd6sY9Tu+bZjLe
|
H7CurtMwALQ/n/6LUKFmjRZjqbKX9SO2QSaC3grd6sY9Tu+bZjLe
|
||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`
|
||||||
|
BadPrivateKey = `
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAgUElV5mwqkloIrM8ZNZ72gSCcnSJt7+/Usa5G+D15YQUAdf9
|
||||||
|
c1zEekTfHgDP+04nw/uFNFaE5v1RbHaPxhZYVg5ZErNCa/hzn+x10xzcepeS3KPV
|
||||||
|
Xcxae4MR0BEegvqZqJzN9loXsNL/c3H/B+2Gle3hTxjlWFb3F5qLgR+4Mf4ruhER
|
||||||
|
1v6eHQa/nchi03MBpT4UeJ7MrL92hTJYLdpSyCqmr8yjxkKJDVC2uRrr+sTSxfh7
|
||||||
|
r6v24u/vp/QTmBIAlNPgadVAZw17iNNb7vjV7Gwl/5gHXonCUKURaV++dBNLrHIZ
|
||||||
|
pqcAM8wHRph8mD1EfL9hsz77pHewxolBATV+7QIDAQABAoIBAC1rK+kFW3vrAYm3
|
||||||
|
+8/fQnQQw5nec4o6+crng6JVQXLeH32qXShNf8kLLG/Jj0vaYcTPPDZw9JCKkTMQ
|
||||||
|
0mKj9XR/5DLbBMsV6eNXXuvJJ3x4iKW5eD9WkLD4FKlNarBRyO7j8sfPTqXW7uat
|
||||||
|
NxWdFH7YsSRvNh/9pyQHLWA5OituidMrYbc3EUx8B1GPNyJ9W8Q8znNYLfwYOjU4
|
||||||
|
Wv1SLE6qGQQH9Q0WzA2WUf8jklCYyMYTIywAjGb8kbAJlKhmj2t2Igjmqtwt1PYc
|
||||||
|
pGlqbtQBDUiWXt5S4YX/1maIQ/49yeNUajjpbJiH3DbhJbHwFTzP3pZ9P9GHOzlG
|
||||||
|
kYR+wSECgYEAw/Xida8kSv8n86V3qSY/I+fYQ5V+jDtXIE+JhRnS8xzbOzz3v0WS
|
||||||
|
Oo5H+o4nJx5eL3Ghb3Gcm0Jn46dHrxinHbm+3RjXv/X6tlbxIYjRSQfHOTSMCTvd
|
||||||
|
qcliF5vC6RCLXuc7R+IWR1Ky6eDEZGtrvt3DyeYABsp9fRUFR/6NluUCgYEAqNsw
|
||||||
|
1aSl7WJa27F0DoJdlU9LWerpXcazlJcIdOz/S9QDmSK3RDQTdqfTxRmrxiYI9LEs
|
||||||
|
mkOkvzlnnOBMpnZ3ZOU5qIRfprecRIi37KDAOHWGnlC0EWGgl46YLb7/jXiWf0AG
|
||||||
|
BhXoKvjI2HjYP21z/EyZ+PFPzur/lNaZhIUlMnUfibbwE9pFggQzzf8scM7c7Sf+
|
||||||
|
mLoVSdoQ/Rujz7CqvQzi2nKSsM7t0curUIb3lJWee5/UeEaxZcmIufoNUrzohAWH
|
||||||
|
BJOIPDM4ssUTLRq7wYM9uQKBgHCBau5OP8gE6mjKuXsZXWUoahpFLKwwwmJUp2vQ
|
||||||
|
pOFPJ/6WZOlqkTVT6QPAcPUbTohKrF80hsZqZyDdSfT3peFx4ZLocBrS56m6NmHR
|
||||||
|
UYHMvJ8rQm76T1fryHVidz85g3zRmfBeWg8yqT5oFg4LYgfLsPm1gRjOhs8LfPvI
|
||||||
|
OLlRAoGBAIZ5Uv4Z3s8O7WKXXUe/lq6j7vfiVkR1NW/Z/WLKXZpnmvJ7FgxN4e56
|
||||||
|
RXT7GwNQHIY8eDjDnsHxzrxd+raOxOZeKcMHj3XyjCX3NHfTscnsBPAGYpY/Wxzh
|
||||||
|
T8UYnFu6RzkixElTf2rseEav7rkdKkI3LAeIZy7B0HulKKsmqVQ7
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue