github.com/zmap/zlint@v1.1.0/util/qc_stmt.go (about)

     1  /*
     2   * ZLint Copyright 2017 Regents of the University of Michigan
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License"); you may not
     5   * use this file except in compliance with the License. You may obtain a copy
     6   * of the License at http://www.apache.org/licenses/LICENSE-2.0
     7   *
     8   * Unless required by applicable law or agreed to in writing, software
     9   * distributed under the License is distributed on an "AS IS" BASIS,
    10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    11   * implied. See the License for the specific language governing
    12   * permissions and limitations under the License.
    13   */
    14  
    15  package util
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/asn1"
    20  	"fmt"
    21  	"reflect"
    22  )
    23  
    24  func etsiOidToDescString(oid asn1.ObjectIdentifier) string {
    25  	switch {
    26  	case oid.Equal(IdEtsiQcsQcCompliance):
    27  		{
    28  			return "IdEtsiQcsQcCompliance"
    29  		}
    30  	case oid.Equal(IdEtsiQcsQcLimitValue):
    31  		{
    32  			return "IdEtsiQcsQcLimitValue"
    33  		}
    34  	case oid.Equal(IdEtsiQcsQcRetentionPeriod):
    35  		{
    36  			return "IdEtsiQcsQcRetentionPeriod"
    37  		}
    38  	case oid.Equal(IdEtsiQcsQcSSCD):
    39  		{
    40  			return "IdEtsiQcsQcSSCSD"
    41  		}
    42  	case oid.Equal(IdEtsiQcsQcEuPDS):
    43  		{
    44  			return "IdEtsiQcsQcEuPDS"
    45  		}
    46  	case oid.Equal(IdEtsiQcsQcType):
    47  		{
    48  			return "IdEtsiQcsQcType"
    49  		}
    50  	default:
    51  		{
    52  			panic("unresolved ETSI QC Statement OID")
    53  		}
    54  	}
    55  }
    56  
    57  type anyContent struct {
    58  	Raw asn1.RawContent
    59  }
    60  
    61  type qcStatementWithInfoField struct {
    62  	Oid asn1.ObjectIdentifier
    63  	Any asn1.RawValue
    64  }
    65  type qcStatementWithoutInfoField struct {
    66  	Oid asn1.ObjectIdentifier
    67  }
    68  
    69  type etsiBase struct {
    70  	errorInfo string
    71  	isPresent bool
    72  }
    73  
    74  func (this etsiBase) GetErrorInfo() string {
    75  	return this.errorInfo
    76  }
    77  
    78  func (this etsiBase) IsPresent() bool {
    79  	return this.isPresent
    80  }
    81  
    82  type EtsiQcStmtIf interface {
    83  	GetErrorInfo() string
    84  	IsPresent() bool
    85  }
    86  
    87  type Etsi421QualEuCert struct {
    88  	etsiBase
    89  }
    90  
    91  type Etsi423QcType struct {
    92  	etsiBase
    93  	TypeOids []asn1.ObjectIdentifier
    94  }
    95  
    96  type EtsiQcSscd struct {
    97  	etsiBase
    98  }
    99  
   100  type EtsiMonetaryValueAlph struct {
   101  	Iso4217CurrencyCodeAlph string `asn1:"printable"`
   102  	Amount                  int
   103  	Exponent                int
   104  }
   105  type EtsiMonetaryValueNum struct {
   106  	Iso4217CurrencyCodeNum int
   107  	Amount                 int
   108  	Exponent               int
   109  }
   110  
   111  type EtsiQcLimitValue struct {
   112  	etsiBase
   113  	Amount       int
   114  	Exponent     int
   115  	IsNum        bool
   116  	CurrencyAlph string
   117  	CurrencyNum  int
   118  }
   119  
   120  type EtsiQcRetentionPeriod struct {
   121  	etsiBase
   122  	Period int
   123  }
   124  type PdsLocation struct {
   125  	Url      string `asn1:"ia5"`
   126  	Language string `asn1:"printable"`
   127  }
   128  type EtsiQcPds struct {
   129  	etsiBase
   130  	PdsLocations []PdsLocation
   131  }
   132  
   133  func AppendToStringSemicolonDelim(this *string, s string) {
   134  	if len(*this) > 0 && len(s) > 0 {
   135  		(*this) += "; "
   136  	}
   137  	(*this) += s
   138  }
   139  
   140  func checkAsn1Reencoding(i interface{}, originalEncoding []byte, appendIfComparisonFails string) string {
   141  	result := ""
   142  	reencoded, marshErr := asn1.Marshal(i)
   143  	if marshErr != nil {
   144  		AppendToStringSemicolonDelim(&result, fmt.Sprintf("error reencoding ASN1 value of statementInfo field: %s",
   145  			marshErr))
   146  	}
   147  	if !bytes.Equal(reencoded, originalEncoding) {
   148  		AppendToStringSemicolonDelim(&result, appendIfComparisonFails)
   149  	}
   150  	return result
   151  }
   152  
   153  func IsAnyEtsiQcStatementPresent(extVal []byte) bool {
   154  	oidList := make([]*asn1.ObjectIdentifier, 6)
   155  	oidList[0] = &IdEtsiQcsQcCompliance
   156  	oidList[1] = &IdEtsiQcsQcLimitValue
   157  	oidList[2] = &IdEtsiQcsQcRetentionPeriod
   158  	oidList[3] = &IdEtsiQcsQcSSCD
   159  	oidList[4] = &IdEtsiQcsQcEuPDS
   160  	oidList[5] = &IdEtsiQcsQcType
   161  	for _, oid := range oidList {
   162  		r := ParseQcStatem(extVal, *oid)
   163  		if r.IsPresent() {
   164  			return true
   165  		}
   166  	}
   167  	return false
   168  }
   169  
   170  func ParseQcStatem(extVal []byte, sought asn1.ObjectIdentifier) EtsiQcStmtIf {
   171  	sl := make([]anyContent, 0)
   172  	rest, err := asn1.Unmarshal(extVal, &sl)
   173  	if err != nil {
   174  		return etsiBase{errorInfo: "error parsing outer SEQ", isPresent: true}
   175  	}
   176  	if len(rest) != 0 {
   177  		return etsiBase{errorInfo: "rest len of outer seq != 0", isPresent: true}
   178  	}
   179  
   180  	for _, raw := range sl {
   181  		parseErrorString := "format error in at least one QC statement within the QC statements extension." +
   182  			" this message may appear multiple times for the same error cause."
   183  		var statem qcStatementWithInfoField
   184  		rest, err = asn1.Unmarshal(raw.Raw, &statem)
   185  		if err != nil {
   186  			var statemWithoutInfo qcStatementWithoutInfoField
   187  
   188  			rest, err = asn1.Unmarshal(raw.Raw, &statemWithoutInfo)
   189  			if err != nil || len(rest) != 0 {
   190  				return etsiBase{errorInfo: parseErrorString, isPresent: false}
   191  			}
   192  			copy(statem.Oid, statemWithoutInfo.Oid)
   193  			if len(statem.Any.FullBytes) != 0 {
   194  				return etsiBase{errorInfo: "internal error, default optional content len is not zero"}
   195  			}
   196  		} else if 0 != len(rest) {
   197  			return etsiBase{errorInfo: parseErrorString, isPresent: false}
   198  		}
   199  
   200  		if !statem.Oid.Equal(sought) {
   201  			continue
   202  		}
   203  		if statem.Oid.Equal(IdEtsiQcsQcCompliance) {
   204  			etsiObj := Etsi421QualEuCert{etsiBase: etsiBase{isPresent: true}}
   205  			statemWithoutInfo := qcStatementWithoutInfoField{Oid: statem.Oid}
   206  			AppendToStringSemicolonDelim(&etsiObj.errorInfo, checkAsn1Reencoding(reflect.ValueOf(statemWithoutInfo).Interface(), raw.Raw,
   207  				"invalid format of ETSI Complicance statement"))
   208  			return etsiObj
   209  		} else if statem.Oid.Equal(IdEtsiQcsQcLimitValue) {
   210  			etsiObj := EtsiQcLimitValue{etsiBase: etsiBase{isPresent: true}}
   211  			numErr := false
   212  			alphErr := false
   213  			var numeric EtsiMonetaryValueNum
   214  			var alphabetic EtsiMonetaryValueAlph
   215  			restNum, errNum := asn1.Unmarshal(statem.Any.FullBytes, &numeric)
   216  			if len(restNum) != 0 || errNum != nil {
   217  				numErr = true
   218  			} else {
   219  				etsiObj.IsNum = true
   220  				etsiObj.Amount = numeric.Amount
   221  				etsiObj.Exponent = numeric.Exponent
   222  				etsiObj.CurrencyNum = numeric.Iso4217CurrencyCodeNum
   223  
   224  			}
   225  			if numErr {
   226  				restAlph, errAlph := asn1.Unmarshal(statem.Any.FullBytes, &alphabetic)
   227  				if len(restAlph) != 0 || errAlph != nil {
   228  					alphErr = true
   229  				} else {
   230  					etsiObj.IsNum = false
   231  					etsiObj.Amount = alphabetic.Amount
   232  					etsiObj.Exponent = alphabetic.Exponent
   233  					etsiObj.CurrencyAlph = alphabetic.Iso4217CurrencyCodeAlph
   234  					AppendToStringSemicolonDelim(&etsiObj.errorInfo,
   235  						checkAsn1Reencoding(reflect.ValueOf(alphabetic).Interface(),
   236  							statem.Any.FullBytes, "error with ASN.1 encoding, possibly a wrong ASN.1 string type was used"))
   237  				}
   238  			}
   239  			if numErr && alphErr {
   240  				etsiObj.errorInfo = "error parsing the ETSI Qc Statement statementInfo field"
   241  			}
   242  			return etsiObj
   243  
   244  		} else if statem.Oid.Equal(IdEtsiQcsQcRetentionPeriod) {
   245  			etsiObj := EtsiQcRetentionPeriod{etsiBase: etsiBase{isPresent: true}}
   246  			rest, err := asn1.Unmarshal(statem.Any.FullBytes, &etsiObj.Period)
   247  
   248  			if len(rest) != 0 || err != nil {
   249  				etsiObj.errorInfo = "error parsing the statementInfo field"
   250  			}
   251  			return etsiObj
   252  		} else if statem.Oid.Equal(IdEtsiQcsQcSSCD) {
   253  			etsiObj := EtsiQcSscd{etsiBase: etsiBase{isPresent: true}}
   254  			statemWithoutInfo := qcStatementWithoutInfoField{Oid: statem.Oid}
   255  			AppendToStringSemicolonDelim(&etsiObj.errorInfo, checkAsn1Reencoding(reflect.ValueOf(statemWithoutInfo).Interface(), raw.Raw,
   256  				"invalid format of ETSI SCSD statement"))
   257  			return etsiObj
   258  		} else if statem.Oid.Equal(IdEtsiQcsQcEuPDS) {
   259  			etsiObj := EtsiQcPds{etsiBase: etsiBase{isPresent: true}}
   260  			rest, err := asn1.Unmarshal(statem.Any.FullBytes, &etsiObj.PdsLocations)
   261  			if len(rest) != 0 || err != nil {
   262  				etsiObj.errorInfo = "error parsing the statementInfo field"
   263  			} else {
   264  				AppendToStringSemicolonDelim(&etsiObj.errorInfo,
   265  					checkAsn1Reencoding(reflect.ValueOf(etsiObj.PdsLocations).Interface(), statem.Any.FullBytes,
   266  						"error with ASN.1 encoding, possibly a wrong ASN.1 string type was used"))
   267  			}
   268  			return etsiObj
   269  		} else if statem.Oid.Equal(IdEtsiQcsQcType) {
   270  			var qcType Etsi423QcType
   271  			qcType.isPresent = true
   272  			rest, err := asn1.Unmarshal(statem.Any.FullBytes, &qcType.TypeOids)
   273  			if len(rest) != 0 || err != nil {
   274  				return etsiBase{errorInfo: "error parsing IdEtsiQcsQcType extension statementInfo field", isPresent: true}
   275  			}
   276  			return qcType
   277  		} else {
   278  			return etsiBase{errorInfo: "", isPresent: true}
   279  		}
   280  
   281  	}
   282  
   283  	return etsiBase{errorInfo: "", isPresent: false}
   284  
   285  }