You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

271 lines
7.2 KiB

/*
* Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved.
*
* 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 harmonyos
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"github.com/cristalhq/jwt/v3"
"io/ioutil"
"net/http"
"strings"
)
const (
CA_CERT_FILE_PATH = "manifest/config/RootCaG2Ecdsa.cer" // TODO: Need to replace it with the actual value.
LEAF_CERT_OID = "1.3.6.1.4.1.2011.2.415.1.1"
// HEADER_PARAM_ALG Algorithm type. The value is always ES256.
HEADER_PARAM_ALG = "ES256"
X5C_CHAIN_LENGTH = 3
)
type JWSChecker struct {
}
type Header struct {
// Alg Algorithm type. The value is always ES256.
Alg string `json:"alg"`
// X5c Indicates the X.509 certificate chain. The sequence is leaf certificate, intermediate certificate, and root certificate.
X5c []string `json:"x5c"`
}
func (jwsChecker *JWSChecker) CheckAndDecodeJWS(jwsStr string) (string, error) {
jwsStrArr := strings.Split(jwsStr, ".")
if len(jwsStrArr) != X5C_CHAIN_LENGTH {
// TODO: Need to replace it with the actual business logic.
return "", errors.New("invalid jwsStr length")
}
certChain, err := jwsChecker.extractCertChain(jwsStrArr)
if err != nil {
// TODO: Need to replace it with the actual business logic.
return "", err
}
rootCert, err := jwsChecker.loadRootCert()
if err != nil {
// TODO: Need to replace it with the actual business logic.
return "", err
}
if err = jwsChecker.verifyCertChain(certChain, rootCert); err != nil {
// TODO: Need to replace it with the actual business logic.
return "", err
}
if err = jwsChecker.verifyOID(certChain[0]); err != nil {
// TODO: Need to replace it with the actual business logic.
return "", err
}
if err = jwsChecker.verifyCRLFromCerts(certChain[:2]); err != nil {
// TODO: Determine whether to ignore CRL certificate download exception based on application Security Policy.
return "", err
}
if err = jwsChecker.verifySignature(jwsStr, certChain[0], string(jwt.ES256)); err != nil {
// TODO: Need to replace it with the actual business logic.
return "", err
}
payLoadBytes, err := base64.RawStdEncoding.DecodeString(jwsStrArr[1])
if err != nil {
// TODO: Need to replace it with the actual business logic.
return "", err
}
return string(payLoadBytes), err
}
func (jwsChecker *JWSChecker) loadRootCert() (*x509.Certificate, error) {
rootCABytes, err := ioutil.ReadFile(CA_CERT_FILE_PATH)
if err != nil {
// TODO: Need to replace it with the actual business logic.
return nil, err
}
rootCerBlock, _ := pem.Decode(rootCABytes)
if rootCerBlock == nil {
// TODO: Need to replace it with the actual business logic.
return nil, err
}
rootCer, err := x509.ParseCertificate(rootCerBlock.Bytes)
if err != nil {
// TODO: Need to replace it with the actual business logic.
return nil, err
}
return rootCer, err
}
func (jwsChecker *JWSChecker) extractCertChain(jwsStrArr []string) ([]*x509.Certificate, error) {
headerByte, err := base64.RawStdEncoding.DecodeString(jwsStrArr[0])
if err != nil {
// TODO: Need to replace it with the actual business logic.
return nil, err
}
var header Header
err = json.Unmarshal(headerByte, &header)
if err != nil {
// TODO: Need to replace it with the actual business logic.
return nil, err
}
certChain := make([]*x509.Certificate, len(header.X5c))
for i := range header.X5c {
certByte, err := base64.StdEncoding.DecodeString(header.X5c[i])
if err != nil {
// TODO: Need to replace it with the actual business logic.
return nil, err
}
cert, err := x509.ParseCertificate(certByte)
if err != nil {
// TODO: Need to replace it with the actual business logic.
return nil, err
}
certChain[i] = cert
}
return certChain, err
}
func (jwsChecker *JWSChecker) verifyCertChain(certChain []*x509.Certificate, rootCert *x509.Certificate) error {
roots := x509.NewCertPool()
roots.AddCert(rootCert)
intermedia := x509.NewCertPool()
intermedia.AddCert(certChain[1])
opts := x509.VerifyOptions{
Roots: roots,
Intermediates: intermedia,
}
_, err := rootCert.Verify(opts)
if err != nil {
// TODO: Need to replace it with the actual business logic.
return err
}
_, err = certChain[0].Verify(opts)
if err != nil {
// TODO: Need to replace it with the actual business logic.
return err
}
return err
}
func (jwsChecker *JWSChecker) verifyCRLFromCerts(certChain []*x509.Certificate) error {
for _, cert := range certChain {
err := checkCrl(cert)
if err != nil {
// TODO: Determine whether to ignore CRL certificate download exception based on application Security Policy.
return err
}
}
return nil
}
func checkCrl(cert *x509.Certificate) error {
if len(cert.CRLDistributionPoints) == 0 {
return errors.New("No CRL distribution points found in certificate")
}
resp, err := http.Get(cert.CRLDistributionPoints[0])
if err != nil {
// TODO: Determine whether to ignore CRL certificate download exception based on application Security Policy.
return err
}
defer resp.Body.Close()
crlData, err := ioutil.ReadAll(resp.Body)
if err != nil {
// TODO: Need to replace it with the actual business logic.
return err
}
crl, err := x509.ParseCRL(crlData)
if err != nil {
// TODO: Need to replace it with the actual business logic.
return err
}
if crl.TBSCertList.RevokedCertificates == nil {
return nil
}
for _, revokedCert := range crl.TBSCertList.RevokedCertificates {
if revokedCert.SerialNumber.Cmp(cert.SerialNumber) == 0 {
// TODO: Need to replace it with the actual business logic.
return fmt.Errorf("certificate %s has been revoked", cert.Subject.CommonName)
}
}
return nil
}
func (jwsChecker *JWSChecker) verifyOID(leafCert *x509.Certificate) error {
var isExist = false
for i := range leafCert.Extensions {
if LEAF_CERT_OID == leafCert.Extensions[i].Id.String() && leafCert.Extensions[i].Critical == false {
isExist = true
}
}
if !isExist {
// TODO: Need to replace it with the actual business logic.
return errors.New("oid verification failed")
}
return nil
}
func (jwsChecker *JWSChecker) verifySignature(jwsStr string, leafCert *x509.Certificate, algParam string) error {
if algParam != jwt.ES256.String() {
// TODO: Need to replace it with the actual business logic.
return errors.New("invalid algorithm")
}
switch pubicKey := leafCert.PublicKey.(type) {
case *ecdsa.PublicKey:
verifier, err := jwt.NewVerifierES(jwt.ES256, pubicKey)
if err != nil {
return err
}
token := []byte(jwsStr)
_, err = jwt.ParseAndVerify(token, verifier)
if err != nil {
return err
}
default:
// TODO: Need to replace it with the actual business logic.
return errors.New("public key not supported")
}
return nil
}