Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added POC for CycloneDX v1.6 (snapshot) standard support #1676

Merged
merged 5 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 4.0/generate-all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ for lang in ${LANGS}; do
verslong="./docs_$lang/OWASP Application Security Verification Standard $vers-$lang"

python3 tools/export.py --format json --language $lang > "$verslong.json"
python3 tools/export.py --format cdx_json --language $lang > "$verslong.cdx.json"
python3 tools/export.py --format json --language $lang --verify-only true

python3 tools/export.py --format json_flat --language $lang > "$verslong.flat.json"
Expand Down
142 changes: 142 additions & 0 deletions 4.0/tools/cyclonedx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
''' CycloneDX converter class

Converts the ASVS JSON into CycloneDX Standards format
Copyright (c) 2023 OWASP Foundation

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

'''

import json
from dicttoxml2 import dicttoxml
import datetime
import uuid
try:
from StringIO import StringIO
except ImportError:
from io import StringIO


class CycloneDX:
bom = {}
bom['bomFormat'] = "CycloneDX"
bom['specVersion'] = "1.6"
bom['serialNumber'] = "urn:uuid:" + str(uuid.uuid4())
bom['version'] = 1
bom['metadata'] = {}
bom['metadata']['timestamp'] = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat()
bom['metadata']['licenses'] = []
bom['metadata']['licenses'].append({})
bom['metadata']['licenses'][0]['license'] = {}
bom['metadata']['licenses'][0]['license']['id'] = "CC-BY-SA-4.0"
bom['metadata']['licenses'][0]['license']['url'] = "https://creativecommons.org/licenses/by-sa/4.0/legalcode.txt"
bom['metadata']['supplier'] = {}
bom['metadata']['supplier']['name'] = "OWASP Foundation"
bom['metadata']['supplier']['url'] = [ "https://owasp.org" ]
bom['definitions'] = {}
bom['definitions']['standards'] = []
bom['definitions']['standards'].append({})

def __init__(self, asvs_json_in):
self.asvs = asvs_json_in
asvs = json.loads(asvs_json_in)
bom_ref = asvs["ShortName"] + "-" + asvs["Version"]
self.bom['definitions']['standards'][0]['bom-ref'] = bom_ref
self.bom['definitions']['standards'][0]['name'] = \
asvs["Name"].replace('Project', '') + "(" + asvs["ShortName"] + ")"
self.bom['definitions']['standards'][0]['version'] = asvs["Version"]
self.bom['definitions']['standards'][0]['description'] = asvs["Description"]
self.bom['definitions']['standards'][0]['owner'] = asvs["Name"]

requirements = []
l1_requirements = []
l2_requirements = []
l3_requirements = []
for asvs_chapter in asvs['Requirements']:
chapter_req = self.convert_requirement(asvs_chapter, None)
requirements.append(chapter_req)
if 'Items' in asvs_chapter:
for asvs_section in asvs_chapter['Items']:
section_req = self.convert_requirement(asvs_section, chapter_req['bom-ref'])
requirements.append(section_req)
for asvs_requirement in asvs_section['Items']:
requirement = self.convert_requirement(asvs_requirement, section_req['bom-ref'])
requirements.append(requirement)
if 'L1' in asvs_requirement and 'Required' in asvs_requirement['L1'] and asvs_requirement['L1']['Required'] is True:
l1_requirements.append(requirement['bom-ref'])
if 'L2' in asvs_requirement and 'Required' in asvs_requirement['L2'] and asvs_requirement['L2']['Required'] is True:
l2_requirements.append(requirement['bom-ref'])
if 'L3' in asvs_requirement and 'Required' in asvs_requirement['L3'] and asvs_requirement['L3']['Required'] is True:
l3_requirements.append(requirement['bom-ref'])

self.bom['definitions']['standards'][0]['requirements'] = requirements

self.bom['definitions']['standards'][0]['levels'] = []
self.bom['definitions']['standards'][0]['levels'].append({})
self.bom['definitions']['standards'][0]['levels'][0] = {}
self.bom['definitions']['standards'][0]['levels'][0]['bom-ref'] = "level-1"
self.bom['definitions']['standards'][0]['levels'][0]['identifier'] = "Level 1"
self.bom['definitions']['standards'][0]['levels'][0]['description'] = "ASVS Level 1 is for low assurance levels, and is completely penetration testable."
self.bom['definitions']['standards'][0]['levels'][0]['requirements'] = l1_requirements
self.bom['definitions']['standards'][0]['levels'].append({})
self.bom['definitions']['standards'][0]['levels'][1] = {}
self.bom['definitions']['standards'][0]['levels'][1]['bom-ref'] = "level-2"
self.bom['definitions']['standards'][0]['levels'][1]['identifier'] = "Level 2"
self.bom['definitions']['standards'][0]['levels'][1]['description'] = "ASVS Level 2 is for applications that contain sensitive data, which requires protection and is the recommended level for most apps."
self.bom['definitions']['standards'][0]['levels'][1]['requirements'] = l2_requirements
self.bom['definitions']['standards'][0]['levels'].append({})
self.bom['definitions']['standards'][0]['levels'][2] = {}
self.bom['definitions']['standards'][0]['levels'][2]['bom-ref'] = "level-3"
self.bom['definitions']['standards'][0]['levels'][2]['identifier'] = "Level 3"
self.bom['definitions']['standards'][0]['levels'][2]['description'] = "ASVS Level 3 is for the most critical applications - applications that perform high value transactions, contain sensitive medical data, or any application that requires the highest level of trust."
self.bom['definitions']['standards'][0]['levels'][2]['requirements'] = l3_requirements

self.bom['definitions']['standards'][0]['externalReferences'] = []
self.bom['definitions']['standards'][0]['externalReferences'].append({})
self.bom['definitions']['standards'][0]['externalReferences'][0]['type'] = 'website'
self.bom['definitions']['standards'][0]['externalReferences'][0]['url'] = 'https://owasp.org/asvs'
self.bom['definitions']['standards'][0]['externalReferences'].append({})
self.bom['definitions']['standards'][0]['externalReferences'][1]['type'] = 'vcs'
self.bom['definitions']['standards'][0]['externalReferences'][1]['url'] = 'https://github.com/OWASP/ASVS'
self.bom['definitions']['standards'][0]['externalReferences'].append({})
self.bom['definitions']['standards'][0]['externalReferences'][2]['type'] = 'issue-tracker'
self.bom['definitions']['standards'][0]['externalReferences'][2]['url'] = 'https://github.com/OWASP/ASVS/issues'
self.bom['definitions']['standards'][0]['externalReferences'].append({})
self.bom['definitions']['standards'][0]['externalReferences'][3]['type'] = 'social'
self.bom['definitions']['standards'][0]['externalReferences'][3]['url'] = 'https://twitter.com/OWASP_ASVS'

def convert_requirement(self, asvs_requirement, parent):
requirement = {}
requirement['bom-ref'] = asvs_requirement['Shortcode']
requirement['identifier'] = asvs_requirement['Shortcode']
if 'ShortName' in asvs_requirement and asvs_requirement['ShortName'] != '':
requirement['title'] = asvs_requirement['ShortName']
if 'Name' in asvs_requirement and asvs_requirement['Name'] != '':
requirement['title'] = asvs_requirement['Name']
if 'Description' in asvs_requirement and asvs_requirement['Description'] != '':
requirement['text'] = asvs_requirement['Description']
if parent:
requirement['parent'] = parent
return requirement

def to_json(self):
''' Returns a JSON-formatted string '''
return json.dumps(self.bom, indent = 2, sort_keys = False, ensure_ascii=False).strip()
8 changes: 6 additions & 2 deletions 4.0/tools/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@

import argparse
from asvs import ASVS
from cyclonedx import CycloneDX

parser = argparse.ArgumentParser(description='Export the ASVS requirements.')
parser.add_argument('--format', choices=['json', 'json_flat', 'xml', 'csv'], default='json')
parser.add_argument('--format', choices=['json', 'json_flat', 'xml', 'csv', 'cdx_json'], default='json')
parser.add_argument('--language', default='en')
parser.add_argument('--verify-only', default=False)

Expand All @@ -55,6 +56,9 @@
elif args.format == "xml":
print(m.to_xml())
elif args.format == "json_flat":
print(m.to_json_flat())
print(m.to_json_flat())
elif args.format == "cdx_json":
cdx = CycloneDX(m.to_json())
print(cdx.to_json())
else:
print(m.to_json())
9 changes: 7 additions & 2 deletions 5.0/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ TEX_FILES := $(foreach lang, $(LANGDIRS), $(DISTDIR)/$(lang)/$(TARGETNAME)$(lang
DOCX_FILES := $(foreach lang, $(LANGDIRS), $(DISTDIR)/$(lang)/$(TARGETNAME)$(lang).docx)
ODT_FILES := $(foreach lang, $(LANGDIRS), $(DISTDIR)/$(lang)/$(TARGETNAME)$(lang).odt)
JSON_FILES := $(foreach lang, $(LANGDIRS), $(DISTDIR)/$(lang)/$(TARGETNAME)$(lang).json)
JSON_CDX_FILES := $(foreach lang, $(LANGDIRS), $(DISTDIR)/$(lang)/$(TARGETNAME)$(lang).cdx.json)
JSON_FLAT_FILES := $(foreach lang, $(LANGDIRS), $(DISTDIR)/$(lang)/$(TARGETNAME)$(lang).flat.json)
CSV_FILES := $(foreach lang, $(LANGDIRS), $(DISTDIR)/$(lang)/$(TARGETNAME)$(lang).csv)
XML_FILES := $(foreach lang, $(LANGDIRS), $(DISTDIR)/$(lang)/$(TARGETNAME)$(lang).xml)
Expand Down Expand Up @@ -53,9 +54,9 @@ PANDOC_ODT_FLAGS= -s \
--columns 10000 \
--reference-doc=./templates/reference.odt

.PHONY: md pdf docx json json_flat csv xml odt tex clean rm-build rm-dist
.PHONY: md pdf docx json json_flat cdx_json csv xml odt tex clean rm-build rm-dist

all: $(TARGETS) pdf docx json json_flat csv xml rm-build
all: $(TARGETS) pdf docx json json_flat cdx_json csv xml rm-build

$(BUILDDIR):
mkdir -p $@
Expand Down Expand Up @@ -106,6 +107,10 @@ $(JSON_FLAT_FILES): $(SOURCE_FOLDERS) $(DIST_FOLDERS)
python3 $(EXPORT_TOOL) --format json_flat --language "$(subst dist,,$(@D))" > $@
json_flat: $(JSON_FLAT_FILES)

$(JSON_CDX_FILES): $(SOURCE_FOLDERS) $(DIST_FOLDERS)
python3 $(EXPORT_TOOL) --format cdx_json --language "$(subst dist,,$(@D))" > $@
cdx_json: $(JSON_CDX_FILES)

$(CSV_FILES): $(SOURCE_FOLDERS) $(DIST_FOLDERS)
python3 $(EXPORT_TOOL) --format csv --language "$(subst dist,,$(@D))" > $@
csv: $(CSV_FILES)
Expand Down
142 changes: 142 additions & 0 deletions 5.0/tools/cyclonedx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
''' CycloneDX converter class

Converts the ASVS JSON into CycloneDX Standards format
Copyright (c) 2023 OWASP Foundation

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

'''

import json
from dicttoxml2 import dicttoxml
import datetime
import uuid
try:
from StringIO import StringIO
except ImportError:
from io import StringIO


class CycloneDX:
bom = {}
bom['bomFormat'] = "CycloneDX"
bom['specVersion'] = "1.6"
bom['serialNumber'] = "urn:uuid:" + str(uuid.uuid4())
bom['version'] = 1
bom['metadata'] = {}
bom['metadata']['timestamp'] = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat()
bom['metadata']['licenses'] = []
bom['metadata']['licenses'].append({})
bom['metadata']['licenses'][0]['license'] = {}
bom['metadata']['licenses'][0]['license']['id'] = "CC-BY-SA-4.0"
bom['metadata']['licenses'][0]['license']['url'] = "https://creativecommons.org/licenses/by-sa/4.0/legalcode.txt"
bom['metadata']['supplier'] = {}
bom['metadata']['supplier']['name'] = "OWASP Foundation"
bom['metadata']['supplier']['url'] = [ "https://owasp.org" ]
bom['declarations'] = {}
bom['declarations']['standards'] = []
bom['declarations']['standards'].append({})

def __init__(self, asvs_json_in):
self.asvs = asvs_json_in
asvs = json.loads(asvs_json_in)
bom_ref = asvs["ShortName"] + "-" + asvs["Version"]
self.bom['declarations']['standards'][0]['bom-ref'] = bom_ref
self.bom['declarations']['standards'][0]['name'] = \
asvs["Name"].replace('Project', '') + "(" + asvs["ShortName"] + ")"
self.bom['declarations']['standards'][0]['version'] = asvs["Version"]
self.bom['declarations']['standards'][0]['description'] = asvs["Description"]
self.bom['declarations']['standards'][0]['owner'] = asvs["Name"]

requirements = []
l1_requirements = []
l2_requirements = []
l3_requirements = []
for asvs_chapter in asvs['Requirements']:
chapter_req = self.convert_requirement(asvs_chapter, None)
requirements.append(chapter_req)
if 'Items' in asvs_chapter:
for asvs_section in asvs_chapter['Items']:
section_req = self.convert_requirement(asvs_section, chapter_req['bom-ref'])
requirements.append(section_req)
for asvs_requirement in asvs_section['Items']:
requirement = self.convert_requirement(asvs_requirement, section_req['bom-ref'])
requirements.append(requirement)
if 'L1' in asvs_requirement and 'Required' in asvs_requirement['L1'] and asvs_requirement['L1']['Required'] is True:
l1_requirements.append(requirement['bom-ref'])
if 'L2' in asvs_requirement and 'Required' in asvs_requirement['L2'] and asvs_requirement['L2']['Required'] is True:
l2_requirements.append(requirement['bom-ref'])
if 'L3' in asvs_requirement and 'Required' in asvs_requirement['L3'] and asvs_requirement['L3']['Required'] is True:
l3_requirements.append(requirement['bom-ref'])

self.bom['declarations']['standards'][0]['requirements'] = requirements

self.bom['declarations']['standards'][0]['levels'] = []
self.bom['declarations']['standards'][0]['levels'].append({})
self.bom['declarations']['standards'][0]['levels'][0] = {}
self.bom['declarations']['standards'][0]['levels'][0]['bom-ref'] = "level-1"
self.bom['declarations']['standards'][0]['levels'][0]['identifier'] = "Level 1"
self.bom['declarations']['standards'][0]['levels'][0]['description'] = "ASVS Level 1 is for low assurance levels, and is completely penetration testable."
self.bom['declarations']['standards'][0]['levels'][0]['requirements'] = l1_requirements
self.bom['declarations']['standards'][0]['levels'].append({})
self.bom['declarations']['standards'][0]['levels'][1] = {}
self.bom['declarations']['standards'][0]['levels'][1]['bom-ref'] = "level-2"
self.bom['declarations']['standards'][0]['levels'][1]['identifier'] = "Level 2"
self.bom['declarations']['standards'][0]['levels'][1]['description'] = "ASVS Level 2 is for applications that contain sensitive data, which requires protection and is the recommended level for most apps."
self.bom['declarations']['standards'][0]['levels'][1]['requirements'] = l2_requirements
self.bom['declarations']['standards'][0]['levels'].append({})
self.bom['declarations']['standards'][0]['levels'][2] = {}
self.bom['declarations']['standards'][0]['levels'][2]['bom-ref'] = "level-3"
self.bom['declarations']['standards'][0]['levels'][2]['identifier'] = "Level 3"
self.bom['declarations']['standards'][0]['levels'][2]['description'] = "ASVS Level 3 is for the most critical applications - applications that perform high value transactions, contain sensitive medical data, or any application that requires the highest level of trust."
self.bom['declarations']['standards'][0]['levels'][2]['requirements'] = l3_requirements

self.bom['declarations']['standards'][0]['externalReferences'] = []
self.bom['declarations']['standards'][0]['externalReferences'].append({})
self.bom['declarations']['standards'][0]['externalReferences'][0]['type'] = 'website'
self.bom['declarations']['standards'][0]['externalReferences'][0]['url'] = 'https://owasp.org/asvs'
self.bom['declarations']['standards'][0]['externalReferences'].append({})
self.bom['declarations']['standards'][0]['externalReferences'][1]['type'] = 'vcs'
self.bom['declarations']['standards'][0]['externalReferences'][1]['url'] = 'https://github.com/OWASP/ASVS'
self.bom['declarations']['standards'][0]['externalReferences'].append({})
self.bom['declarations']['standards'][0]['externalReferences'][2]['type'] = 'issue-tracker'
self.bom['declarations']['standards'][0]['externalReferences'][2]['url'] = 'https://github.com/OWASP/ASVS/issues'
self.bom['declarations']['standards'][0]['externalReferences'].append({})
self.bom['declarations']['standards'][0]['externalReferences'][3]['type'] = 'social'
self.bom['declarations']['standards'][0]['externalReferences'][3]['url'] = 'https://twitter.com/OWASP_ASVS'

def convert_requirement(self, asvs_requirement, parent):
requirement = {}
requirement['bom-ref'] = asvs_requirement['Shortcode']
requirement['identifier'] = asvs_requirement['Shortcode']
if 'ShortName' in asvs_requirement and asvs_requirement['ShortName'] != '':
requirement['title'] = asvs_requirement['ShortName']
if 'Name' in asvs_requirement and asvs_requirement['Name'] != '':
requirement['title'] = asvs_requirement['Name']
if 'Description' in asvs_requirement and asvs_requirement['Description'] != '':
requirement['text'] = asvs_requirement['Description']
if parent:
requirement['parent'] = parent
return requirement

def to_json(self):
''' Returns a JSON-formatted string '''
return json.dumps(self.bom, indent = 2, sort_keys = False, ensure_ascii=False).strip()
Loading