Files
cloudpods/pkg/cloudcommon/validators/validators_cert.go
2024-02-05 20:06:40 +08:00

446 lines
11 KiB
Go

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validators
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
"reflect"
"strings"
"yunion.io/x/jsonutils"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/httperrors"
)
type ValidatorPEM struct {
Validator
Blocks []*pem.Block
}
func NewPEMValidator(key string) *ValidatorPEM {
v := &ValidatorPEM{
Validator: Validator{Key: key},
}
v.SetParent(v)
return v
}
func (v *ValidatorPEM) getValue() interface{} {
return v.Blocks
}
func (v *ValidatorPEM) parseFromString(s string) []*pem.Block {
blocks := []*pem.Block{}
for d := []byte(s); ; {
block, rest := pem.Decode(d)
if block == nil {
if len(rest) > 0 {
return nil
}
break
}
blocks = append(blocks, block)
d = rest
}
return blocks
}
func (v *ValidatorPEM) setDefault(data *jsonutils.JSONDict) bool {
if v.defaultVal == nil {
return false
}
s, ok := v.defaultVal.(string)
if !ok {
return false
}
blocks := v.parseFromString(s)
if blocks != nil {
value := jsonutils.NewString(s)
v.value = value
data.Set(v.Key, value)
v.Blocks = blocks
return true
}
return false
}
func (v *ValidatorPEM) Validate(ctx context.Context, data *jsonutils.JSONDict) error {
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
return err
}
s, err := v.value.GetString()
if err != nil {
return newInvalidTypeError(v.Key, "pem", err)
}
v.Blocks = v.parseFromString(s)
return nil
}
type ValidatorCertificate struct {
ValidatorPEM
Certificates []*x509.Certificate
}
func NewCertificateValidator(key string) *ValidatorCertificate {
v := &ValidatorCertificate{
ValidatorPEM: *NewPEMValidator(key),
}
v.SetParent(v)
return v
}
func (v *ValidatorCertificate) getValue() interface{} {
return v.Certificates
}
func (v *ValidatorCertificate) parseFromBlocks(blocks []*pem.Block) []*x509.Certificate {
certs := []*x509.Certificate{}
for i, block := range blocks {
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil
}
if i > 0 {
certSub := certs[i-1]
if err := certSub.CheckSignatureFrom(cert); err != nil {
return nil
}
}
certs = append(certs, cert)
}
return certs
}
func (v *ValidatorCertificate) parseFromString(s string) []*x509.Certificate {
blocks := v.ValidatorPEM.parseFromString(s)
if blocks == nil {
return nil
}
v.Blocks = blocks
certs := v.parseFromBlocks(blocks)
return certs
}
func (v *ValidatorCertificate) setDefault(data *jsonutils.JSONDict) bool {
if v.defaultVal == nil {
return false
}
s, ok := v.defaultVal.(string)
if !ok {
return false
}
certs := v.parseFromString(s)
if certs != nil {
v.setCertificates(certs, data)
return true
}
return false
}
func (v *ValidatorCertificate) setCertificates(certs []*x509.Certificate, data *jsonutils.JSONDict) {
pems := []byte{}
for _, block := range v.Blocks {
d := pem.EncodeToMemory(block)
pems = append(pems, d...)
}
value := jsonutils.NewString(string(pems))
v.value = value
data.Set(v.Key, value)
v.Certificates = certs
}
func (v *ValidatorCertificate) Validate(ctx context.Context, data *jsonutils.JSONDict) error {
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
return err
}
s, err := v.value.GetString()
if err != nil {
return newInvalidTypeError(v.Key, "certificate", err)
}
certs := v.parseFromString(s)
if !v.optional && len(certs) == 0 {
return newInvalidValueError(v.Key, "empty certificate chain")
}
if len(certs) > 0 {
for _, block := range v.Blocks {
if block.Type != "CERTIFICATE" {
err := fmt.Errorf("wrong PEM type: %s", block.Type)
return newInvalidTypeError(v.Key, "certificate", err)
}
}
for i := 0; i < len(certs)-1; i++ {
cert := certs[i]
certP := certs[i+1]
err := cert.CheckSignatureFrom(certP)
if err != nil {
msg := fmt.Sprintf("cannot verify signature of certificate %d in the chain", i+1)
return newInvalidValueError(v.Key, msg)
}
}
v.setCertificates(certs, data)
return nil
}
return nil
}
func (v *ValidatorCertificate) FingerprintSha256() []byte {
csum := sha256.Sum256(v.Certificates[0].Raw)
return csum[:]
}
func (v *ValidatorCertificate) FingerprintSha256String() string {
fp := v.FingerprintSha256()
s := hex.EncodeToString(fp)
return s
}
func (v *ValidatorCertificate) PublicKeyBitLen() int {
cert := v.Certificates[0]
pubkey := cert.PublicKey
switch pub := pubkey.(type) {
case *rsa.PublicKey:
return pub.N.BitLen()
case *ecdsa.PublicKey:
return pub.X.BitLen()
default:
return 0
}
}
type ValidatorPrivateKey struct {
ValidatorPEM
PrivateKey crypto.PrivateKey
}
func NewPrivateKeyValidator(key string) *ValidatorPrivateKey {
v := &ValidatorPrivateKey{
ValidatorPEM: *NewPEMValidator(key),
}
v.SetParent(v)
return v
}
func (v *ValidatorPrivateKey) getValue() interface{} {
return v.PrivateKey
}
func (v *ValidatorPrivateKey) parseFromBlock(block *pem.Block) (crypto.PrivateKey, error) {
const RSA_PRIVATE_KEY = "RSA PRIVATE KEY"
const EC_PRIVATE_KEY = "EC PRIVATE KEY"
const PKCS8_PRIVATE_KEY = "PRIVATE KEY"
var fuzz bool
switch block.Type {
case PKCS8_PRIVATE_KEY:
goto parsePkcs8
case RSA_PRIVATE_KEY:
goto parseRsa
case EC_PRIVATE_KEY:
goto parseEc
default:
}
fuzz = true
parsePkcs8:
{
pkey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err == nil {
switch pkey1 := pkey.(type) {
case *rsa.PrivateKey:
block.Type = RSA_PRIVATE_KEY
block.Bytes = x509.MarshalPKCS1PrivateKey(pkey1)
return pkey1, nil
case *ecdsa.PrivateKey:
block.Type = EC_PRIVATE_KEY
block.Bytes, _ = x509.MarshalECPrivateKey(pkey1)
return pkey1, nil
default:
return nil, newInvalidValueError(v.Key, "unknown private key type")
}
}
if !fuzz {
return nil, newInvalidValueError(v.Key, fmt.Sprintf("invalid pkcs8 private key: %s", err))
}
}
parseRsa:
{
pkey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err == nil {
block.Type = RSA_PRIVATE_KEY
return pkey, nil
}
if !fuzz {
return nil, newInvalidValueError(v.Key, fmt.Sprintf("invalid rsa private key: %s", err))
}
}
parseEc:
{
pkey, err := x509.ParseECPrivateKey(block.Bytes)
if err == nil {
block.Type = EC_PRIVATE_KEY
return pkey, nil
}
if !fuzz {
return nil, newInvalidValueError(v.Key, fmt.Sprintf("invalid ec private key: %s", err))
}
}
return nil, newInvalidValueError(v.Key, "invalid private key")
}
func (v *ValidatorPrivateKey) parseFromString(s string) (crypto.PrivateKey, error) {
blocks := v.ValidatorPEM.parseFromString(s)
if len(blocks) != 1 {
return nil, newInvalidValueError(v.Key, fmt.Sprintf("found %d pem blocks, expecting 1", len(blocks)))
}
v.Blocks = blocks
pkey, err := v.parseFromBlock(blocks[0])
return pkey, err
}
func (v *ValidatorPrivateKey) setDefault(data *jsonutils.JSONDict) bool {
if v.defaultVal == nil {
return false
}
s, ok := v.defaultVal.(string)
if !ok {
return false
}
pkey, err := v.parseFromString(s)
if err != nil {
return false
}
v.setPrivateKey(pkey, data)
return true
}
func (v *ValidatorPrivateKey) setPrivateKey(pkey crypto.PrivateKey, data *jsonutils.JSONDict) {
d := pem.EncodeToMemory(v.Blocks[0])
value := jsonutils.NewString(string(d))
v.value = value
data.Set(v.Key, value)
v.PrivateKey = pkey
}
func (v *ValidatorPrivateKey) Validate(ctx context.Context, data *jsonutils.JSONDict) error {
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
return err
}
s, err := v.value.GetString()
if err != nil {
return newInvalidTypeError(v.Key, "privateKey", err)
}
pkey, err := v.parseFromString(s)
if err != nil {
return err
}
v.setPrivateKey(pkey, data)
return nil
}
func (v *ValidatorPrivateKey) MatchCertificate(cert *x509.Certificate) error {
switch pkey := v.PrivateKey.(type) {
case *rsa.PrivateKey:
pubkey0 := pkey.Public()
pubkey1, _ := cert.PublicKey.(crypto.PublicKey) // safe conversion
if !reflect.DeepEqual(pubkey0, pubkey1) {
return newInvalidValueError(v.Key, "certificate and rsa key do not match")
}
case *ecdsa.PrivateKey:
pubkey0 := pkey.Public()
pubkey1, _ := cert.PublicKey.(crypto.PublicKey)
if !reflect.DeepEqual(pubkey0, pubkey1) {
return newInvalidValueError(v.Key, "certificate and ec key do not match")
}
default:
// should never happen
return newInvalidValueError(v.Key, "unknown private key type")
}
return nil
}
type ValidatorCertKey struct {
*ValidatorCertificate
*ValidatorPrivateKey
certPubKeyAlgo string
}
func NewCertKeyValidator(cert, key string) *ValidatorCertKey {
return &ValidatorCertKey{
ValidatorCertificate: NewCertificateValidator(cert),
ValidatorPrivateKey: NewPrivateKeyValidator(key),
}
}
func (v *ValidatorCertKey) Validate(ctx context.Context, data *jsonutils.JSONDict) error {
keyV := map[string]IValidator{
"certificate": v.ValidatorCertificate,
"private_key": v.ValidatorPrivateKey,
}
for _, v := range keyV {
if err := v.Validate(ctx, data); err != nil {
return err
}
}
cert := v.ValidatorCertificate.Certificates[0]
var certPubKeyAlgo string
{
// x509.PublicKeyAlgorithm.String() is only available since go1.10
switch cert.PublicKeyAlgorithm {
case x509.RSA:
certPubKeyAlgo = api.LB_TLS_CERT_PUBKEY_ALGO_RSA
case x509.ECDSA:
certPubKeyAlgo = api.LB_TLS_CERT_PUBKEY_ALGO_ECDSA
default:
certPubKeyAlgo = fmt.Sprintf("algo %#v", cert.PublicKeyAlgorithm)
}
if !api.LB_TLS_CERT_PUBKEY_ALGOS.Has(certPubKeyAlgo) {
return httperrors.NewInputParameterError("invalid cert pubkey algorithm: %s, want %s",
certPubKeyAlgo, api.LB_TLS_CERT_PUBKEY_ALGOS.String())
}
}
v.certPubKeyAlgo = certPubKeyAlgo
if err := v.ValidatorPrivateKey.MatchCertificate(cert); err != nil {
return err
}
return nil
}
func (v *ValidatorCertKey) UpdateCertKeyInfo(ctx context.Context, data *jsonutils.JSONDict) *jsonutils.JSONDict {
cert := v.ValidatorCertificate.Certificates[0]
// NOTE subject alternative names also includes email, url, ip addresses,
// but we ignore them here.
//
// NOTE we use white space to separate names
data.Set("common_name", jsonutils.NewString(cert.Subject.CommonName))
data.Set("subject_alternative_names", jsonutils.NewString(strings.Join(cert.DNSNames, " ")))
data.Set("not_before", jsonutils.NewTimeString(cert.NotBefore))
data.Set("not_after", jsonutils.NewTimeString(cert.NotAfter))
data.Set("public_key_algorithm", jsonutils.NewString(v.certPubKeyAlgo))
data.Set("public_key_bit_len", jsonutils.NewInt(int64(v.ValidatorCertificate.PublicKeyBitLen())))
data.Set("signature_algorithm", jsonutils.NewString(cert.SignatureAlgorithm.String()))
data.Set("fingerprint", jsonutils.NewString(api.LB_TLS_CERT_FINGERPRINT_ALGO_SHA256+":"+v.ValidatorCertificate.FingerprintSha256String()))
return data
}