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 }