mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-05-07 06:02:09 +08:00
446 lines
11 KiB
Go
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
|
|
}
|