github.com/zmap/zcrypto@v0.0.0-20240512203510-0fef58d9a9db/x509/qc_statements.go (about) 1 package x509 2 3 import ( 4 "encoding/json" 5 "errors" 6 7 "github.com/zmap/zcrypto/encoding/asn1" 8 ) 9 10 type QCStatementASN struct { 11 StatementID asn1.ObjectIdentifier 12 StatementInfo asn1.RawValue `asn1:"optional"` 13 } 14 15 func (s *QCStatementASN) MarshalJSON() ([]byte, error) { 16 aux := struct { 17 ID string `json:"id,omitempty"` 18 Value []byte `json:"value,omitempty"` 19 }{ 20 ID: s.StatementID.String(), 21 Value: s.StatementInfo.Bytes, 22 } 23 return json.Marshal(&aux) 24 } 25 26 type QCStatementsASN struct { 27 QCStatements []QCStatementASN 28 } 29 30 // ETSI OIDS from https://www.etsi.org/deliver/etsi_en/319400_319499/31941205/02.02.03_20/en_31941205v020203a.pdf 31 var ( 32 oidEtsiQcsQcCompliance = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 1} 33 oidEtsiQcsQcLimitValue = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 2} 34 oidEtsiQcsQcRetentionPeriod = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 3} 35 oidEtsiQcsQcSSCD = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 4} 36 oidEtsiQcsQcEuPDS = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 5} 37 oidEtsiQcsQcType = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 6} 38 oidEtsiQcsQcCCLegislation = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 7} 39 oidEtsiQcsQctEsign = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 6, 1} 40 oidEtsiQcsQctEseal = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 6, 2} 41 oidEtsiQcsQctWeb = asn1.ObjectIdentifier{0, 4, 0, 1862, 1, 6, 3} 42 ) 43 44 type QCStatements struct { 45 StatementIDs []string `json:"ids,omitempty"` 46 ParsedStatements *ParsedQCStatements `json:"parsed,omitempty"` 47 } 48 49 type ParsedQCStatements struct { 50 ETSICompliance []bool `json:"etsi_compliance,omitempty"` 51 SSCD []bool `json:"sscd,omitempty"` 52 Types []QCType `json:"types,omitempty"` 53 Limit []MonetaryValue `json:"limit,omitempty"` 54 PDSLocations []PDSLocations `json:"pds_locations,omitempty"` 55 RetentionPeriod []int `json:"retention_period,omitempty"` 56 Legislation []QCLegistation `json:"legislation,omitempty"` 57 } 58 59 type MonetaryValue struct { 60 Currency string `json:"currency,omitempty"` 61 CurrencyNumber int `json:"currency_number,omitempty"` 62 Amount int `json:"amount,omitempty"` 63 Exponent int `json:"exponent,omitempty"` 64 } 65 66 type monetaryValueASNString struct { 67 Currency string `asn1:"printable"` 68 Amount int 69 Exponent int 70 } 71 72 type monetaryValueASNNumber struct { 73 Currency int 74 Amount int 75 Exponent int 76 } 77 78 type PDSLocations struct { 79 Locations []PDSLocation `json:"locations,omitempty"` 80 } 81 82 type PDSLocation struct { 83 URL string `json:"url,omitempty" asn1:"ia5"` 84 Language string `json:"language,omitempty" asn1:"printable"` 85 } 86 87 type QCType struct { 88 TypeIdentifiers []asn1.ObjectIdentifier 89 } 90 91 type QCLegistation struct { 92 CountryCodes []string `json:"country_codes,omitempty"` 93 } 94 95 func (qt *QCType) MarshalJSON() ([]byte, error) { 96 aux := struct { 97 Types []string `json:"ids,omitempty"` 98 }{ 99 Types: make([]string, len(qt.TypeIdentifiers)), 100 } 101 for idx := range qt.TypeIdentifiers { 102 aux.Types[idx] = qt.TypeIdentifiers[idx].String() 103 } 104 return json.Marshal(&aux) 105 } 106 107 func (q *QCStatements) Parse(in *QCStatementsASN) error { 108 q.StatementIDs = make([]string, len(in.QCStatements)) 109 known := ParsedQCStatements{} 110 for i, s := range in.QCStatements { 111 val := in.QCStatements[i].StatementInfo.FullBytes 112 q.StatementIDs[i] = s.StatementID.String() 113 if s.StatementID.Equal(oidEtsiQcsQcCompliance) { 114 known.ETSICompliance = append(known.ETSICompliance, true) 115 if val != nil { 116 return errors.New("EtsiQcsQcCompliance QCStatement must not contain a statementInfo") 117 } 118 } else if s.StatementID.Equal(oidEtsiQcsQcLimitValue) { 119 // TODO 120 mvs := monetaryValueASNString{} 121 mvn := monetaryValueASNNumber{} 122 out := MonetaryValue{} 123 if _, err := asn1.Unmarshal(val, &mvs); err == nil { 124 out.Currency = mvs.Currency 125 out.Amount = mvs.Amount 126 out.Exponent = mvs.Exponent 127 } else if _, err := asn1.Unmarshal(val, &mvn); err == nil { 128 out.CurrencyNumber = mvn.Currency 129 out.Amount = mvn.Amount 130 out.Exponent = mvn.Exponent 131 } else { 132 return err 133 } 134 known.Limit = append(known.Limit, out) 135 } else if s.StatementID.Equal(oidEtsiQcsQcRetentionPeriod) { 136 var retentionPeriod int 137 if _, err := asn1.Unmarshal(val, &retentionPeriod); err != nil { 138 return err 139 } 140 known.RetentionPeriod = append(known.RetentionPeriod, retentionPeriod) 141 } else if s.StatementID.Equal(oidEtsiQcsQcSSCD) { 142 known.SSCD = append(known.SSCD, true) 143 if val != nil { 144 return errors.New("EtsiQcsQcSSCD QCStatement must not contain a statementInfo") 145 } 146 } else if s.StatementID.Equal(oidEtsiQcsQcEuPDS) { 147 locations := make([]PDSLocation, 0) 148 if _, err := asn1.Unmarshal(val, &locations); err != nil { 149 return err 150 } 151 known.PDSLocations = append(known.PDSLocations, PDSLocations{ 152 Locations: locations, 153 }) 154 } else if s.StatementID.Equal(oidEtsiQcsQcType) { 155 typeIds := make([]asn1.ObjectIdentifier, 0) 156 if _, err := asn1.Unmarshal(val, &typeIds); err != nil { 157 return err 158 } 159 known.Types = append(known.Types, QCType{ 160 TypeIdentifiers: typeIds, 161 }) 162 } else if s.StatementID.Equal(oidEtsiQcsQcCCLegislation) { 163 countryCodes := make([]string, 0) 164 if _, err := asn1.Unmarshal(val, &countryCodes); err != nil { 165 return err 166 } 167 known.Legislation = append(known.Legislation, QCLegistation{ 168 CountryCodes: countryCodes, 169 }) 170 } 171 } 172 q.ParsedStatements = &known 173 return nil 174 }