refactor: private key decoding and generators (#4116)

pull/4118/head^2
James Elliott 2022-10-03 11:52:29 +11:00 committed by GitHub
parent 32bd2eba60
commit 3f39914c8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 179 additions and 69 deletions

View File

@ -1,6 +1,8 @@
package main package main
import ( import (
"crypto/ecdsa"
"crypto/rsa"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@ -181,6 +183,8 @@ var decodedTypes = []reflect.Type{
reflect.TypeOf(url.URL{}), reflect.TypeOf(url.URL{}),
reflect.TypeOf(time.Duration(0)), reflect.TypeOf(time.Duration(0)),
reflect.TypeOf(schema.Address{}), reflect.TypeOf(schema.Address{}),
reflect.TypeOf(rsa.PrivateKey{}),
reflect.TypeOf(ecdsa.PrivateKey{}),
} }
func containsType(needle reflect.Type, haystack []reflect.Type) (contains bool) { func containsType(needle reflect.Type, haystack []reflect.Type) (contains bool) {

View File

@ -1,5 +1,5 @@
--- ---
title: "Commit Message Guidelines" title: "Commit Message"
description: "Authelia Development Commit Message Guidelines" description: "Authelia Development Commit Message Guidelines"
lead: "This section covers the git commit message guidelines we use for development." lead: "This section covers the git commit message guidelines we use for development."
date: 2021-01-30T19:29:07+11:00 date: 2021-01-30T19:29:07+11:00
@ -7,8 +7,8 @@ draft: false
images: [] images: []
menu: menu:
contributing: contributing:
parent: "development" parent: "guidelines"
weight: 231 weight: 320
toc: true toc: true
aliases: aliases:
- /docs/contributing/commitmsg-guidelines.html - /docs/contributing/commitmsg-guidelines.html

View File

@ -2,7 +2,7 @@
title: "Documentation" title: "Documentation"
description: "Authelia Development Documentation Guidelines" description: "Authelia Development Documentation Guidelines"
lead: "This section covers the guidelines we use when writing documentation." lead: "This section covers the guidelines we use when writing documentation."
date: 2021-01-30T19:29:07+11:00 date: 2022-10-02T14:32:16+11:00
draft: false draft: false
images: [] images: []
menu: menu:

View File

@ -2,7 +2,7 @@
title: "Guidelines" title: "Guidelines"
description: "An introduction into guidelines for contributing to the Authelia project." description: "An introduction into guidelines for contributing to the Authelia project."
lead: "An introduction into guidelines for contributing to the Authelia project." lead: "An introduction into guidelines for contributing to the Authelia project."
date: 2022-06-15T17:51:47+10:00 date: 2022-10-02T14:32:16+11:00
draft: false draft: false
images: [] images: []
menu: menu:

View File

@ -2,7 +2,7 @@
title: "Istio" title: "Istio"
description: "A guide to integrating Authelia with the Istio Kubernetes Ingress." description: "A guide to integrating Authelia with the Istio Kubernetes Ingress."
lead: "A guide to integrating Authelia with the Istio Kubernetes Ingress." lead: "A guide to integrating Authelia with the Istio Kubernetes Ingress."
date: 2022-06-15T17:51:47+10:00 date: 2022-10-02T13:59:09+11:00
draft: false draft: false
images: [] images: []
menu: menu:

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
package configuration package configuration
import ( import (
"crypto/ecdsa"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
@ -343,8 +344,8 @@ func StringToX509CertificateChainHookFunc() mapstructure.DecodeHookFuncType {
} }
} }
// StringToRSAPrivateKeyHookFunc decodes strings to rsa.PrivateKey's. // StringToPrivateKeyHookFunc decodes strings to rsa.PrivateKey's.
func StringToRSAPrivateKeyHookFunc() mapstructure.DecodeHookFuncType { func StringToPrivateKeyHookFunc() mapstructure.DecodeHookFuncType {
return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) { return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
if f.Kind() != reflect.String { if f.Kind() != reflect.String {
return data, nil return data, nil
@ -354,21 +355,36 @@ func StringToRSAPrivateKeyHookFunc() mapstructure.DecodeHookFuncType {
return data, nil return data, nil
} }
expectedType := reflect.TypeOf(rsa.PrivateKey{}) expectedTypeRSA := reflect.TypeOf(rsa.PrivateKey{})
expectedTypeECDSA := reflect.TypeOf(ecdsa.PrivateKey{})
if t.Elem() != expectedType { var (
return data, nil i any
} expectedType reflect.Type
)
dataStr := data.(string) dataStr := data.(string)
switch t.Elem() {
case expectedTypeRSA:
var result *rsa.PrivateKey var result *rsa.PrivateKey
if dataStr == "" { if dataStr == "" {
return result, nil return result, nil
} }
var i interface{} expectedType = expectedTypeRSA
case expectedTypeECDSA:
var result *ecdsa.PrivateKey
if dataStr == "" {
return result, nil
}
expectedType = expectedTypeECDSA
default:
return data, nil
}
if i, err = utils.ParseX509FromPEM([]byte(dataStr)); err != nil { if i, err = utils.ParseX509FromPEM([]byte(dataStr)); err != nil {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, err) return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, err)
@ -376,6 +392,16 @@ func StringToRSAPrivateKeyHookFunc() mapstructure.DecodeHookFuncType {
switch r := i.(type) { switch r := i.(type) {
case *rsa.PrivateKey: case *rsa.PrivateKey:
if expectedType != expectedTypeRSA {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, fmt.Errorf("the data is for a %T not a *%s", r, expectedType))
}
return r, nil
case *ecdsa.PrivateKey:
if expectedType != expectedTypeECDSA {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, fmt.Errorf("the data is for a %T not a *%s", r, expectedType))
}
return r, nil return r, nil
default: default:
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, fmt.Errorf("the data is for a %T not a *%s", r, expectedType)) return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, fmt.Errorf("the data is for a %T not a *%s", r, expectedType))

View File

@ -23,8 +23,8 @@ import (
func TestStringToMailAddressHookFunc(t *testing.T) { func TestStringToMailAddressHookFunc(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
have interface{} have any
want interface{} want any
err string err string
decode bool decode bool
}{ }{
@ -78,8 +78,8 @@ func TestStringToMailAddressHookFunc(t *testing.T) {
func TestStringToMailAddressHookFuncPointer(t *testing.T) { func TestStringToMailAddressHookFuncPointer(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
have interface{} have any
want interface{} want any
err string err string
decode bool decode bool
}{ }{
@ -139,8 +139,8 @@ func TestStringToMailAddressHookFuncPointer(t *testing.T) {
func TestStringToURLHookFunc(t *testing.T) { func TestStringToURLHookFunc(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
have interface{} have any
want interface{} want any
err string err string
decode bool decode bool
}{ }{
@ -212,8 +212,8 @@ func TestStringToURLHookFunc(t *testing.T) {
func TestStringToURLHookFuncPointer(t *testing.T) { func TestStringToURLHookFuncPointer(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
have interface{} have any
want interface{} want any
err string err string
decode bool decode bool
}{ }{
@ -285,8 +285,8 @@ func TestStringToURLHookFuncPointer(t *testing.T) {
func TestToTimeDurationHookFunc(t *testing.T) { func TestToTimeDurationHookFunc(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
have interface{} have any
want interface{} want any
err string err string
decode bool decode bool
}{ }{
@ -405,8 +405,8 @@ func TestToTimeDurationHookFunc(t *testing.T) {
func TestToTimeDurationHookFuncPointer(t *testing.T) { func TestToTimeDurationHookFuncPointer(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
have interface{} have any
want interface{} want any
err string err string
decode bool decode bool
}{ }{
@ -526,8 +526,8 @@ func TestToTimeDurationHookFuncPointer(t *testing.T) {
func TestStringToRegexpFunc(t *testing.T) { func TestStringToRegexpFunc(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
have interface{} have any
want interface{} want any
err string err string
decode bool decode bool
wantGrps []string wantGrps []string
@ -640,8 +640,8 @@ func TestStringToRegexpFunc(t *testing.T) {
func TestStringToRegexpFuncPointers(t *testing.T) { func TestStringToRegexpFuncPointers(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
have interface{} have any
want interface{} want any
err string err string
decode bool decode bool
wantGrps []string wantGrps []string
@ -775,8 +775,8 @@ func TestStringToAddressHookFunc(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
have interface{} have any
expected interface{} expected any
err string err string
decode bool decode bool
}{ }{
@ -867,51 +867,108 @@ func TestStringToAddressHookFunc(t *testing.T) {
} }
} }
func TestStringToRSAPrivateKeyHookFunc(t *testing.T) { func TestStringToPrivateKeyHookFunc(t *testing.T) {
var nilkey *rsa.PrivateKey var (
nilRSA *rsa.PrivateKey
nilECDSA *ecdsa.PrivateKey
nilCert *x509.Certificate
)
testCases := []struct { testCases := []struct {
desc string desc string
have interface{} have any
want interface{} want any
err string err string
decode bool decode bool
}{ }{
{ {
desc: "ShouldDecodeRSAPrivateKey", desc: "ShouldDecodeRSAPrivateKey",
have: x509PrivateKeyRSA1, have: x509PrivateKeyRSA1,
want: mustParseRSAPrivateKey(x509PrivateKeyRSA1), want: MustParseRSAPrivateKey(x509PrivateKeyRSA1),
decode: true,
},
{
desc: "ShouldDecodeECDSAPrivateKey",
have: x509PrivateKeyEC1,
want: MustParseECDSAPrivateKey(x509PrivateKeyEC1),
decode: true, decode: true,
}, },
{ {
desc: "ShouldNotDecodeToECDSAPrivateKey", desc: "ShouldNotDecodeToECDSAPrivateKey",
have: x509PrivateKeyRSA1, have: x509PrivateKeyRSA1,
want: &ecdsa.PrivateKey{}, want: &ecdsa.PrivateKey{},
decode: false, decode: true,
err: "could not decode to a *ecdsa.PrivateKey: the data is for a *rsa.PrivateKey not a *ecdsa.PrivateKey",
}, },
{ {
desc: "ShouldNotDecodeEmptyKey", desc: "ShouldNotDecodeEmptyRSAKey",
have: "", have: "",
want: nilkey, want: nilRSA,
decode: true,
},
{
desc: "ShouldNotDecodeEmptyECDSAKey",
have: "",
want: nilECDSA,
decode: true, decode: true,
}, },
{ {
desc: "ShouldNotDecodeECDSAKeyToRSAKey", desc: "ShouldNotDecodeECDSAKeyToRSAKey",
have: x509PrivateKeyEC1, have: x509PrivateKeyEC1,
want: nilkey, want: nilRSA,
decode: true, decode: true,
err: "could not decode to a *rsa.PrivateKey: the data is for a *ecdsa.PrivateKey not a *rsa.PrivateKey", err: "could not decode to a *rsa.PrivateKey: the data is for a *ecdsa.PrivateKey not a *rsa.PrivateKey",
}, },
{
desc: "ShouldNotDecodeRSAKeyToECDSAKey",
have: x509PrivateKeyRSA1,
want: nilECDSA,
decode: true,
err: "could not decode to a *ecdsa.PrivateKey: the data is for a *rsa.PrivateKey not a *ecdsa.PrivateKey",
},
{ {
desc: "ShouldNotDecodeBadRSAPrivateKey", desc: "ShouldNotDecodeBadRSAPrivateKey",
have: x509PrivateKeyRSA2, have: x509PrivateKeyRSA2,
want: nilkey, want: nilRSA,
decode: true, decode: true,
err: "could not decode to a *rsa.PrivateKey: failed to parse PEM block containing the key", err: "could not decode to a *rsa.PrivateKey: failed to parse PEM block containing the key",
}, },
{
desc: "ShouldNotDecodeBadECDSAPrivateKey",
have: x509PrivateKeyEC2,
want: nilECDSA,
decode: true,
err: "could not decode to a *ecdsa.PrivateKey: failed to parse PEM block containing the key",
},
{
desc: "ShouldNotDecodeCertificateToRSAPrivateKey",
have: x509CertificateRSA1,
want: nilRSA,
decode: true,
err: "could not decode to a *rsa.PrivateKey: the data is for a *x509.Certificate not a *rsa.PrivateKey",
},
{
desc: "ShouldNotDecodeCertificateToECDSAPrivateKey",
have: x509CertificateRSA1,
want: nilECDSA,
decode: true,
err: "could not decode to a *ecdsa.PrivateKey: the data is for a *x509.Certificate not a *ecdsa.PrivateKey",
},
{
desc: "ShouldNotDecodeRSAKeyToCertificate",
have: x509PrivateKeyRSA1,
want: nilCert,
decode: false,
},
{
desc: "ShouldNotDecodeECDSAKeyToCertificate",
have: x509PrivateKeyEC1,
want: nilCert,
decode: false,
},
} }
hook := configuration.StringToRSAPrivateKeyHookFunc() hook := configuration.StringToPrivateKeyHookFunc()
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
@ -944,25 +1001,25 @@ func TestStringToX509CertificateHookFunc(t *testing.T) {
{ {
desc: "ShouldDecodeRSACertificate", desc: "ShouldDecodeRSACertificate",
have: x509CertificateRSA1, have: x509CertificateRSA1,
want: mustParseX509Certificate(x509CertificateRSA1), want: MustParseX509Certificate(x509CertificateRSA1),
decode: true, decode: true,
}, },
{ {
desc: "ShouldDecodeECDSACertificate", desc: "ShouldDecodeECDSACertificate",
have: x509CACertificateECDSA, have: x509CACertificateECDSA,
want: mustParseX509Certificate(x509CACertificateECDSA), want: MustParseX509Certificate(x509CACertificateECDSA),
decode: true, decode: true,
}, },
{ {
desc: "ShouldDecodeRSACACertificate", desc: "ShouldDecodeRSACACertificate",
have: x509CACertificateRSA, have: x509CACertificateRSA,
want: mustParseX509Certificate(x509CACertificateRSA), want: MustParseX509Certificate(x509CACertificateRSA),
decode: true, decode: true,
}, },
{ {
desc: "ShouldDecodeECDSACACertificate", desc: "ShouldDecodeECDSACACertificate",
have: x509CACertificateECDSA, have: x509CACertificateECDSA,
want: mustParseX509Certificate(x509CACertificateECDSA), want: MustParseX509Certificate(x509CACertificateECDSA),
decode: true, decode: true,
}, },
{ {
@ -1020,50 +1077,50 @@ func TestStringToX509CertificateChainHookFunc(t *testing.T) {
{ {
desc: "ShouldDecodeRSACertificate", desc: "ShouldDecodeRSACertificate",
have: x509CertificateRSA1, have: x509CertificateRSA1,
expected: mustParseX509CertificateChain(x509CertificateRSA1), expected: MustParseX509CertificateChain(x509CertificateRSA1),
decode: true, decode: true,
}, },
{ {
desc: "ShouldDecodeRSACertificateNoPtr", desc: "ShouldDecodeRSACertificateNoPtr",
have: x509CertificateRSA1, have: x509CertificateRSA1,
expected: *mustParseX509CertificateChain(x509CertificateRSA1), expected: *MustParseX509CertificateChain(x509CertificateRSA1),
decode: true, decode: true,
}, },
{ {
desc: "ShouldDecodeRSACertificateChain", desc: "ShouldDecodeRSACertificateChain",
have: buildChain(x509CertificateRSA1, x509CACertificateRSA), have: BuildChain(x509CertificateRSA1, x509CACertificateRSA),
expected: mustParseX509CertificateChain(x509CertificateRSA1, x509CACertificateRSA), expected: MustParseX509CertificateChain(x509CertificateRSA1, x509CACertificateRSA),
decode: true, decode: true,
}, },
{ {
desc: "ShouldDecodeRSACertificateChainNoPtr", desc: "ShouldDecodeRSACertificateChainNoPtr",
have: buildChain(x509CertificateRSA1, x509CACertificateRSA), have: BuildChain(x509CertificateRSA1, x509CACertificateRSA),
expected: *mustParseX509CertificateChain(x509CertificateRSA1, x509CACertificateRSA), expected: *MustParseX509CertificateChain(x509CertificateRSA1, x509CACertificateRSA),
decode: true, decode: true,
}, },
{ {
desc: "ShouldNotDecodeBadRSACertificateChain", desc: "ShouldNotDecodeBadRSACertificateChain",
have: buildChain(x509CertificateRSA1, x509CACertificateECDSA), have: BuildChain(x509CertificateRSA1, x509CACertificateECDSA),
expected: mustParseX509CertificateChain(x509CertificateRSA1, x509CACertificateECDSA), expected: MustParseX509CertificateChain(x509CertificateRSA1, x509CACertificateECDSA),
verr: "certificate #1 in chain is not signed properly by certificate #2 in chain: x509: signature algorithm specifies an RSA public key, but have public key of type *ecdsa.PublicKey", verr: "certificate #1 in chain is not signed properly by certificate #2 in chain: x509: signature algorithm specifies an RSA public key, but have public key of type *ecdsa.PublicKey",
decode: true, decode: true,
}, },
{ {
desc: "ShouldDecodeECDSACertificate", desc: "ShouldDecodeECDSACertificate",
have: x509CACertificateECDSA, have: x509CACertificateECDSA,
expected: mustParseX509CertificateChain(x509CACertificateECDSA), expected: MustParseX509CertificateChain(x509CACertificateECDSA),
decode: true, decode: true,
}, },
{ {
desc: "ShouldDecodeRSACACertificate", desc: "ShouldDecodeRSACACertificate",
have: x509CACertificateRSA, have: x509CACertificateRSA,
expected: mustParseX509CertificateChain(x509CACertificateRSA), expected: MustParseX509CertificateChain(x509CACertificateRSA),
decode: true, decode: true,
}, },
{ {
desc: "ShouldDecodeECDSACACertificate", desc: "ShouldDecodeECDSACACertificate",
have: x509CACertificateECDSA, have: x509CACertificateECDSA,
expected: mustParseX509CertificateChain(x509CACertificateECDSA), expected: MustParseX509CertificateChain(x509CACertificateECDSA),
decode: true, decode: true,
}, },
{ {
@ -1175,6 +1232,11 @@ bad key
MHcCAQEEIMn970LSn8aKVhBM4vyUmpZyEdCT4riN+Lp4QU04zUhYoAoGCCqGSM49 MHcCAQEEIMn970LSn8aKVhBM4vyUmpZyEdCT4riN+Lp4QU04zUhYoAoGCCqGSM49
AwEHoUQDQgAEMD69n22nd78GmaRDzy/s7muqhbc/OEnFS2mNtiRAA5FaX+kbkCB5 AwEHoUQDQgAEMD69n22nd78GmaRDzy/s7muqhbc/OEnFS2mNtiRAA5FaX+kbkCB5
8pu/k2jkaSVNZtBYKPVAibHkhvakjVb66A== 8pu/k2jkaSVNZtBYKPVAibHkhvakjVb66A==
-----END EC PRIVATE KEY-----`
x509PrivateKeyEC2 = `
-----BEGIN EC PRIVATE KEY-----
bad key
-----END EC PRIVATE KEY-----` -----END EC PRIVATE KEY-----`
x509CertificateRSA1 = ` x509CertificateRSA1 = `
@ -1232,14 +1294,14 @@ uDv6M2spMi0CIQC8uOSMcv11vp1ylsGg38N6XYA+GQa1BHRd79+91hC+7w==
-----END CERTIFICATE-----` -----END CERTIFICATE-----`
) )
func mustParseRSAPrivateKey(data string) *rsa.PrivateKey { func MustParseRSAPrivateKey(data string) *rsa.PrivateKey {
block, _ := pem.Decode([]byte(data)) block, _ := pem.Decode([]byte(data))
if block == nil || block.Bytes == nil || len(block.Bytes) == 0 { if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
panic("not pem encoded") panic("not pem encoded")
} }
if block.Type != "RSA PRIVATE KEY" { if block.Type != "RSA PRIVATE KEY" {
panic("not private key") panic("not rsa private key")
} }
key, err := x509.ParsePKCS1PrivateKey(block.Bytes) key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
@ -1250,7 +1312,25 @@ func mustParseRSAPrivateKey(data string) *rsa.PrivateKey {
return key return key
} }
func mustParseX509Certificate(data string) *x509.Certificate { func MustParseECDSAPrivateKey(data string) *ecdsa.PrivateKey {
block, _ := pem.Decode([]byte(data))
if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
panic("not pem encoded")
}
if block.Type != "EC PRIVATE KEY" {
panic("not ecdsa private key")
}
key, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
panic(err)
}
return key
}
func MustParseX509Certificate(data string) *x509.Certificate {
block, _ := pem.Decode([]byte(data)) block, _ := pem.Decode([]byte(data))
if block == nil || len(block.Bytes) == 0 { if block == nil || len(block.Bytes) == 0 {
panic("not a PEM") panic("not a PEM")
@ -1264,7 +1344,7 @@ func mustParseX509Certificate(data string) *x509.Certificate {
return cert return cert
} }
func buildChain(pems ...string) string { func BuildChain(pems ...string) string {
buf := bytes.Buffer{} buf := bytes.Buffer{}
for i, data := range pems { for i, data := range pems {
@ -1278,8 +1358,8 @@ func buildChain(pems ...string) string {
return buf.String() return buf.String()
} }
func mustParseX509CertificateChain(datas ...string) *schema.X509CertificateChain { func MustParseX509CertificateChain(datas ...string) *schema.X509CertificateChain {
chain, err := schema.NewX509CertificateChain(buildChain(datas...)) chain, err := schema.NewX509CertificateChain(BuildChain(datas...))
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -63,7 +63,7 @@ func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o inte
StringToAddressHookFunc(), StringToAddressHookFunc(),
StringToX509CertificateHookFunc(), StringToX509CertificateHookFunc(),
StringToX509CertificateChainHookFunc(), StringToX509CertificateChainHookFunc(),
StringToRSAPrivateKeyHookFunc(), StringToPrivateKeyHookFunc(),
ToTimeDurationHookFunc(), ToTimeDurationHookFunc(),
), ),
Metadata: nil, Metadata: nil,