Skip to content

Commit

Permalink
Merge pull request #1 from datatrails/steve/hash-signing
Browse files Browse the repository at this point in the history
Change to Payload Hash Signing
  • Loading branch information
SteveLasker authored Jul 10, 2024
2 parents 940b34e + faa2833 commit e765d9c
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 9 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:
- name: Register as a SCITT Signed Statement
# Register the Signed Statement wit DataTrails SCITT APIs
id: register-compliance-scitt-signed-statement
uses: datatrails/scitt-action@v0.4
uses: datatrails/scitt-action@v0.5
with:
datatrails-client_id: ${{ env.DATATRAILS_CLIENT_ID }}
datatrails-secret: ${{ env.DATATRAILS_SECRET }}
Expand All @@ -112,3 +112,17 @@ jobs:
run: |
rm ./signingkey.pem
```
## Testing Action Updates
To test incremental changes to this github action:
1. Fork https://github.com/datatrails/scitt-action/ into an org you own
1. Make the changes to your fork of the scitt-action
1. For the repo you wish to include this action:
- Change the `uses` to reference a branch and commit on your org/repo:

```yaml
uses: <your-org>/scitt-action@<full-commit>
uses: synsation-corp/scitt-action@5b861ed4722787835cdd5e9d86efc698974f1131
```
214 changes: 214 additions & 0 deletions scitt-scripts/create_hashed_signed_statement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
""" Module for creating a SCITT signed statement with a detached payload"""

import hashlib
import argparse

from typing import Optional

from hashlib import sha256

from pycose.messages import Sign1Message
from pycose.headers import Algorithm, KID, ContentType
from pycose.algorithms import Es256
from pycose.keys.curves import P256
from pycose.keys.keyparam import KpKty, EC2KpD, EC2KpX, EC2KpY, KpKeyOps, EC2KpCurve
from pycose.keys.keytype import KtyEC2
from pycose.keys.keyops import SignOp, VerifyOp
from pycose.keys import CoseKey

from ecdsa import SigningKey, VerifyingKey


# CWT header label comes from version 4 of the scitt architecture document
# https://www.ietf.org/archive/id/draft-ietf-scitt-architecture-04.html#name-issuer-identity
HEADER_LABEL_CWT = 13

# Various CWT header labels come from:
# https://www.rfc-editor.org/rfc/rfc8392.html#section-3.1
HEADER_LABEL_CWT_ISSUER = 1
HEADER_LABEL_CWT_SUBJECT = 2

# CWT CNF header labels come from:
# https://datatracker.ietf.org/doc/html/rfc8747#name-confirmation-claim
HEADER_LABEL_CWT_CNF = 8
HEADER_LABEL_CNF_COSE_KEY = 1


# Signed Hash envelope header labels from:
# https://github.com/OR13/draft-steele-cose-hash-envelope/blob/main/draft-steele-cose-hash-envelope.md
HEADER_LABEL_PAYLOAD_HASH_ALGORITHM = 998
HEADER_LABEL_LOCATION = 999


def open_signing_key(key_file: str) -> SigningKey:
"""
opens the signing key from the key file.
NOTE: the signing key is expected to be a P-256 ecdsa key in PEM format.
While this sample script uses P-256 ecdsa, DataTrails supports any format
supported through [go-cose](https://github.com/veraison/go-cose/blob/main/algorithm.go)
"""
with open(key_file, encoding="UTF-8") as file:
signing_key = SigningKey.from_pem(file.read(), hashlib.sha256)
return signing_key


def open_payload(payload_file: str) -> str:
"""
opens the payload from the payload file.
"""
with open(payload_file, encoding="UTF-8") as file:
return file.read()


def create_hashed_signed_statement(
signing_key: SigningKey,
payload: str,
subject: str,
issuer: str,
content_type: str,
location: str,
) -> bytes:
"""
creates a hashed signed statement, given the signing_key, payload, subject and issuer
the payload will be hashed and the hash added to the payload field.
"""

# NOTE: for the sample an ecdsa P256 key is used
verifying_key: Optional[VerifyingKey] = signing_key.verifying_key
assert verifying_key is not None

# pub key is the x and y parts concatenated
xy_parts = verifying_key.to_string()

# ecdsa P256 is 64 bytes
x_part = xy_parts[0:32]
y_part = xy_parts[32:64]

# create a protected header where
# the verification key is attached to the cwt claims
protected_header = {
Algorithm: Es256,
KID: b"testkey",
ContentType: content_type,
HEADER_LABEL_CWT: {
HEADER_LABEL_CWT_ISSUER: issuer,
HEADER_LABEL_CWT_SUBJECT: subject,
HEADER_LABEL_CWT_CNF: {
HEADER_LABEL_CNF_COSE_KEY: {
KpKty: KtyEC2,
EC2KpCurve: P256,
EC2KpX: x_part,
EC2KpY: y_part,
},
},
},
HEADER_LABEL_PAYLOAD_HASH_ALGORITHM: -16, # for sha256
HEADER_LABEL_LOCATION: location,
}

# now create a sha256 hash of the payload
#
# NOTE: any hashing algorithm can be used.
payload_hash = sha256(payload.encode("utf-8")).digest()

# create the statement as a sign1 message using the protected header and payload
statement = Sign1Message(phdr=protected_header, payload=payload_hash)

# create the cose_key to sign the statement using the signing key
cose_key = {
KpKty: KtyEC2,
EC2KpCurve: P256,
KpKeyOps: [SignOp, VerifyOp],
EC2KpD: signing_key.to_string(),
EC2KpX: x_part,
EC2KpY: y_part,
}

cose_key = CoseKey.from_dict(cose_key)
statement.key = cose_key

# sign and cbor encode the statement.
# NOTE: the encode() function performs the signing automatically
signed_statement = statement.encode([None])

return signed_statement


def main():
"""Creates a signed statement"""

parser = argparse.ArgumentParser(description="Create a signed statement.")

# signing key file
parser.add_argument(
"--signing-key-file",
type=str,
help="filepath to the stored ecdsa P-256 signing key, in pem format.",
default="scitt-signing-key.pem",
)

# payload-file (a reference to the file that will become the payload of the SCITT Statement)
parser.add_argument(
"--payload-file",
type=str,
help="filepath to the content that will be hashed into the payload of the SCITT Statement.",
default="scitt-payload.json",
)

# content-type
parser.add_argument(
"--content-type",
type=str,
help="The iana.org media type for the payload",
default="application/json",
)

# subject
parser.add_argument(
"--subject",
type=str,
help="subject to correlate statements made about an artifact.",
)

# issuer
parser.add_argument(
"--issuer",
type=str,
help="issuer who owns the signing key.",
)

# location hint
parser.add_argument(
"--location-hint",
type=str,
help="location hint for the original statement that was hashed.",
)

# output file
parser.add_argument(
"--output-file",
type=str,
help="name of the output file to store the signed statement.",
default="signed-statement.cbor",
)

args = parser.parse_args()

signing_key = open_signing_key(args.signing_key_file)
payload = open_payload(args.payload_file)

signed_statement = create_hashed_signed_statement(
signing_key,
payload,
args.subject,
args.issuer,
args.content_type,
args.location_hint,
)

with open(args.output_file, "wb") as output_file:
output_file.write(signed_statement)


if __name__ == "__main__":
main()
16 changes: 9 additions & 7 deletions scitt-scripts/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/bin/bash -l

# Uncomment for debugging
# echo "datatrails-client_id: " ${1}
# echo "datatrails-secret: " ${2}
# echo "subject: " ${3}
Expand All @@ -14,23 +15,24 @@ SIGNED_STATEMENT_FILE=./${6}
TOKEN_FILE="./bearer-token.txt"
SUBJECT=${3}

# echo "Create an access token"
echo "Create an access token"
/scripts/create-token.sh ${1} ${2} $TOKEN_FILE

ls -a
echo "PWD: $PWD"
# ls -a
# echo "PWD: $PWD"

ls -la $TOKEN_FILE
# ls -la $TOKEN_FILE

python /scripts/create_signed_statement.py \
echo "Create a Signed Statement, hashing the payload"
python /scripts/create_hashed_signed_statement.py \
--subject ${3} \
--payload ${4} \
--content-type ${5} \
--output-file $SIGNED_STATEMENT_FILE \
--signing-key-file ${8} \
--issuer ${9}

echo "SCITT Register to https://app.datatrails.ai/archivist/v1/publicscitt/entries"
echo "Register the SCITT SIgned Statement to https://app.datatrails.ai/archivist/v1/publicscitt/entries"

RESPONSE=$(curl -X POST -H @$TOKEN_FILE \
--data-binary @$SIGNED_STATEMENT_FILE \
Expand All @@ -47,4 +49,4 @@ echo "OPERATION_ID: $OPERATION_ID"
# RESPONSE=$(python /scripts/check_operation_status.py --operation-id $OPERATION_ID --token-file-name $TOKEN_FILE)
# ENTRY_ID=$(echo $RESPONSE | jq -r .entryID)

# curl https://app.datatrails.ai/archivist/v2/publicassets/-/events?event_attributes.feed_id=$SUBJECT | jq
# curl https://app.datatrails.ai/archivist/v2/publicassets/-/events?event_attributes.subject=$SUBJECT | jq
2 changes: 1 addition & 1 deletion scitt-scripts/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
pycose~=1.0.1
ecdsa~=0.18.0
jwcrypto~=1.5.0
requests~=2.31.0
requests>=2.32.0

0 comments on commit e765d9c

Please sign in to comment.