endesive icon indicating copy to clipboard operation
endesive copied to clipboard

Use Aws KMS

Open mirhmousavi opened this issue 4 years ago • 5 comments

Hi

Firstly, I appreciate you for creating this fantastic library.

Unfortunately, we couldn't find an example of how to sign/verify pdf documents with Aws KMS.

We created a key on Aws KMS, an "RSA_2048" key for "Sign and verify"

Then we use the cert.py script to generate a certificate for it by using its public key.

But when we sign a document, and open it with a document viewer, it says the signature is invalid

Signature is INVALID: - The document has not been modified since the signature was applied - There are INVALID certificates in the certificate chain. - Signature timestamp is not from a secure server.
Signer:
test CA
Hash Algorithm:
SHA256
Date:
Aug 22, 2021 8:49:56 AM CEST
Reason:
document validation
Location:
World
Contact info:
<Not Available>

These are the scripts

# cert.py
import boto3
from asn1crypto import x509
from cryptography import x509 as cx
from oscrypto import asymmetric
import datetime
from asn1crypto.util import timezone
import hashlib
from certbuilder import pem_armor_certificate

keyid = 'a3b2d70e-3a11-4ebe-81a2-XXX'

client = boto3.client(
    'kms',
    region_name='us-east-2',
    aws_access_key_id='AKIATMRHGNQFKXXXX',
    aws_secret_access_key='BXVTnahV9uUAbVloW4sAaZnv9g9XXXX',
)

response = client.get_public_key(
    KeyId=keyid
)

pkey = response['PublicKey']


def create_x509TBS_certificate(public_key):
    pubkey = asymmetric.load_public_key(public_key)
    one_day = datetime.timedelta(1, 0, 0)
    self_dict = {
        'country_name': 'World',
        'state_or_province_name': 'World',
        'locality_name': 'World',
        'organization_name': 'test',
        'common_name': 'test CA',
    }
    tbs_cert = x509.TbsCertificate({
        'version': 'v3',
        'serial_number': cx.random_serial_number(),
        'signature': {
            'algorithm': 'sha256_rsa'
        },
        'issuer': x509.Name.build(self_dict),
        'validity': {
            'not_before': x509.Time(name='utc_time', value=datetime.datetime.now(timezone.utc) - one_day),
            'not_after': x509.Time(name='utc_time', value=datetime.datetime.now(timezone.utc) + (one_day * 365)),
        },
        'subject': x509.Name.build(self_dict),
        'subject_public_key_info': pubkey.asn1,
        'extensions': []
    })
    return tbs_cert


def sign_asymmetric(key_version_name, message_bytes):
    # Calculate the hash.
    hash_ = hashlib.sha256(message_bytes).digest()

    # Build the digest.
    # digest = {'sha256': hash_}

    # Call the API
    # sign_response = client.asymmetric_sign(request={'name': key_version_name, 'digest': digest})

    sign_response = client.sign(
        KeyId=keyid,
        Message=hash_,
        MessageType='DIGEST',
        SigningAlgorithm='RSASSA_PSS_SHA_256'
    )

    return sign_response['Signature']


def get_cert_pem(tbs_cert, signature):
    cert = x509.Certificate({
        'tbs_certificate': tbs_cert,
        'signature_algorithm': {
            'algorithm': 'sha256_rsa'
        },
        'signature_value': signature
    })
    return pem_armor_certificate(cert)


tbs = create_x509TBS_certificate(pkey)
sig = sign_asymmetric(keyid, tbs.dump())
HSM_cert = get_cert_pem(tbs, sig)


filename = "certificate.crt"

with open(filename, 'wb') as f:
    f.write(HSM_cert)
# main script

import os
import datetime
import hashlib
import boto3
from endesive import hsm, pdf
from cobro.sign.config import Config
from endesive import pdf



ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
CERT_PATH = os.path.join(ROOT_PATH, 'certificate.crt')


class Config:
    SIGN_AWS_ACCESS_KEY_ID = os.getenv('SIGN_AWS_ACCESS_KEY_ID')
    SIGN_AWS_SECRET_ACCESS_KEY = os.getenv('SIGN_AWS_SECRET_ACCESS_KEY')
    SIGN_AWS_REGION = os.getenv('SIGN_AWS_REGION')
    SIGN_AWS_KEY_ID = os.getenv('SIGN_AWS_KEY_ID')
    CERT_PATH = CERT_PATH
    


def sign(document):
    date = datetime.datetime.utcnow() - datetime.timedelta(hours=12)
    date = date.strftime('D:%Y%m%d%H%M%S+00\'00\'')

    parameters = {
        'sigflags': 1,  # locks the document
        'sigfield': 'Signature',
        'contact': '[email protected]',
        'location': 'World',
        'signingdate': date.encode(),
        'reason': 'Document validation',
    }

    data_signature = pdf.cms.sign(
        document,
        parameters,
        key=None,
        cert=None,
        othercerts=[],
        algomd='sha256',
        hsm=Aws(),
    )
    return document + data_signature


def verify(document):
    fileu = open(Config.CERT_PATH, 'rb')  # TODO real cert
    cert = fileu.read()

    try:
        (hash_ok, signature_ok, cert_ok) = pdf.verify(document, [cert])
        success = hash_ok and signature_ok and cert_ok
        result = 'valid' if success else 'invalid'
        return {
            'result': result,
            'hash_ok': hash_ok,
            'signature_ok': signature_ok,
            'cert_ok': cert_ok
        }
    except AssertionError:
        return {
            'result': 'error'
        }


class Aws(hsm.BaseHSM):
    def certificate(self):
        fileu = open(Config.CERT_PATH, 'rb')  # TODO real cert
        cert = fileu.read()
        return Config.SIGN_AWS_KEY_ID, cert

    def sign(self, keyid, data, mech):
        doc_hash = getattr(hashlib, mech.lower())(data).digest()

        client = boto3.client(
            'kms',
            region_name=Config.SIGN_AWS_REGION,
            aws_access_key_id=Config.SIGN_AWS_ACCESS_KEY_ID,
            aws_secret_access_key=Config.SIGN_AWS_SECRET_ACCESS_KEY,
        )

        response = client.sign(
            KeyId=Config.SIGN_AWS_KEY_ID,
            Message=doc_hash,
            MessageType='DIGEST',
            SigningAlgorithm='RSASSA_PKCS1_V1_5_SHA_256'
        )
        client.verify
        return response['Signature']

We're suspicious of generating the certificate incorrectly.

mirhmousavi avatar Aug 25 '21 11:08 mirhmousavi

If you have access to the key, it is not a KMS. Having a key, you cannot use it in a signed certificate, CA signs your data with its certificate (key) - and you create it (certificate) yourself, as a result your certificate chain is broken. HSM has the option of signing with your certificate using its own infrastructure = HSM API, check the example using HSM from google.

m32 avatar Aug 26 '21 09:08 m32

You should actually download your certificate from AWS, not create one.

m32 avatar Aug 26 '21 09:08 m32

Thanks for the response.

As I understood the private key doesn't leave Amazon servers in KMS. I only have access to the public key.

So how do I get a certificate from that public key? I don't get this part.

Should I generate a certificate with the Aws certificate manager?

mirhmousavi avatar Aug 26 '21 11:08 mirhmousavi

I don't know how to download a certificate from amazon's servers, this service is paid and I didn't need to use it, but there should be an option (in amazon api or on custom pages) to download your own certificate and intermediate certificates (certificate chain), but never a private key.

see pdf-sign-cms-hsm-google.py from endesive examples

m32 avatar Aug 26 '21 12:08 m32

Thanks for the response.

As I understood the private key doesn't leave Amazon servers in KMS. I only have access to the public key.

So how do I get a certificate from that public key? I don't get this part.

Should I generate a certificate with the Aws certificate manager?

Export public Key from a Certificate openssl x509 -pubkey -noout -in cert.pem > pubkey.pem

shyjuk avatar Dec 22 '21 12:12 shyjuk

There are INVALID certificates in the certificate chain is the error source.

The source of the problem is not the signed document, but the lack of PKI infrastructure. AWS KMS only gives you a key, not a certificate with a key. Trust consists in trusting the issuer of the certificate for which only you have the private key. There is no trust in the signature, which is made with a key that does not match the certificate, and this is what you got when you tried to view the signed document.

m32 avatar Sep 22 '22 23:09 m32