| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- // 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
- }
|