Skip to content

Commit 82f0604

Browse files
authored
Merge pull request #13 from aws-samples/atalla_migration
atalla migration scripts - initial changes
2 parents 7345ff0 + f6065c3 commit 82f0604

File tree

11 files changed

+1058
-1
lines changed

11 files changed

+1058
-1
lines changed

LICENSE

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
1111
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
1212
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
1313
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
14-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15+
16+
Using this sample code will require access to Atalla payment hsm hardware that is not provided as part of this sample code and that needs to be provisioned separately by the relevant manufacturers or their resellers. This sample code does not imply an endorsement by any third parties mentioned.
263 KB
Loading

key-import-export/hsm/atalla/atalla_to_apc_tr34.py

Lines changed: 433 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import boto3
2+
import base64
3+
from botocore.exceptions import ClientError
4+
from cryptography.hazmat.primitives import serialization
5+
from cryptography import x509
6+
import logging
7+
8+
apc_client = boto3.client('payment-cryptography')
9+
publicKeyCertificateAliasName = 'alias/tr34-key-import-kdh-ca'
10+
#un-comment to see debug logs for AWS SDK
11+
#boto3.set_stream_logger('', logging.DEBUG)
12+
#boto3.set_stream_logger('boto3.resources', logging.INFO)
13+
14+
def verify_certificate_state(key_arn):
15+
"""
16+
Verifies the state of the imported certificate
17+
18+
Args:
19+
key_arn (str): ARN of the imported key
20+
21+
Returns:
22+
bool: True if certificate is enabled and complete, False otherwise
23+
"""
24+
try:
25+
response = apc_client.describe_key(
26+
KeyIdentifier=key_arn
27+
)
28+
29+
key_state = response['Key']['KeyState']
30+
is_enabled = response['Key']['Enabled']
31+
32+
if key_state == 'CREATE_COMPLETE' and is_enabled:
33+
print(f"Certificate status: {key_state}, Enabled: {is_enabled}")
34+
return True
35+
else:
36+
print(f"Certificate not ready. Status: {key_state}, Enabled: {is_enabled}")
37+
return False
38+
39+
except ClientError as e:
40+
print(f"Error verifying certificate state: {e}")
41+
return False
42+
43+
def importPublicCACertificate(kdhPublicKeyCertificate):
44+
cert_data = kdhPublicKeyCertificate.encode('ascii')
45+
""" with open(publicKeyCertificatePath, 'rb') as cert_file:
46+
cert_data = cert_file.read() """
47+
# Load the certificate
48+
cert = x509.load_pem_x509_certificate(cert_data)
49+
50+
#keyAlias = apc_client.get_alias(AliasName=publicKeyCertificateAliasName)
51+
52+
kdh_ca_key_arn = apc_client.import_key(Enabled=True, KeyMaterial={
53+
'RootCertificatePublicKey': {
54+
'KeyAttributes': {
55+
'KeyAlgorithm': 'RSA_2048',
56+
'KeyClass': 'PUBLIC_KEY',
57+
'KeyModesOfUse': {
58+
'Verify': True,
59+
},
60+
'KeyUsage': 'TR31_S0_ASYMMETRIC_KEY_FOR_DIGITAL_SIGNATURE',
61+
},
62+
'PublicKeyCertificate': base64.b64encode(cert.public_bytes(encoding=serialization.Encoding.PEM)).decode('UTF-8')
63+
}
64+
}, KeyCheckValueAlgorithm='ANSI_X9_24', Tags=[])['Key']['KeyArn']
65+
66+
tagResponse = apc_client.tag_resource(
67+
ResourceArn= kdh_ca_key_arn,
68+
Tags=[
69+
{
70+
'Key': 'project',
71+
'Value': 'sample-atalla-tr34-exchange'
72+
},
73+
]
74+
)
75+
""" aliasResponse = apc_client.create_alias(
76+
AliasName=publicKeyCertificateAliasName,
77+
KeyArn=kdh_ca_key_arn
78+
)
79+
print(f"aliasResponse: {aliasResponse}")
80+
"""
81+
print(f"Imported KDH CA certificate into APC with key ARN: {kdh_ca_key_arn}")
82+
return kdh_ca_key_arn
83+
84+
def getKeyIfExixts(alias_name):
85+
try:
86+
keyAlias = apc_client.get_alias(AliasName=alias_name)
87+
88+
# Check if response has the expected structure
89+
if (keyAlias and
90+
isinstance(keyAlias, dict) and
91+
'Alias' in keyAlias and
92+
isinstance(keyAlias['Alias'], dict) and
93+
'AliasName' in keyAlias['Alias']):
94+
95+
return keyAlias['Alias']['AliasName']
96+
return None
97+
98+
except apc_client.exceptions.NotFoundException:
99+
return None
100+
101+
def deleteKeyIfExixts(key_arn):
102+
try:
103+
payment_crypto = boto3.client('payment-cryptography')
104+
105+
# Schedule the key for deletion
106+
response = payment_crypto.delete_key(
107+
KeyIdentifier=key_arn,
108+
DeleteKeyInDays=3
109+
)
110+
111+
print(f"Key {key_arn} scheduled for deletion in 3 days")
112+
return True
113+
114+
except payment_crypto.exceptions.NotFoundException:
115+
print(f"Key {key_arn} not found")
116+
return False
117+
except payment_crypto.exceptions.InvalidStateException:
118+
print(f"Key {key_arn} is in an invalid state for deletion")
119+
return False
120+
except Exception as e:
121+
print(f"Error deleting key: {str(e)}")
122+
return False
123+
124+
def importTR34Payload(tr34Payload,nonce,kdh_ca_key_arn,kdh_ca_certificate,importToken):
125+
print("Importing TR34 payload into APC ...")
126+
# Load the certificate
127+
cert = x509.load_pem_x509_certificate(kdh_ca_certificate.encode('ascii'))
128+
129+
trt34_import_res = apc_client.import_key(
130+
Enabled=True,
131+
KeyMaterial={
132+
"Tr34KeyBlock": {
133+
'CertificateAuthorityPublicKeyIdentifier': kdh_ca_key_arn,
134+
'ImportToken': importToken,
135+
'KeyBlockFormat': 'X9_TR34_2012',
136+
'SigningKeyCertificate': base64.b64encode(cert.public_bytes(encoding=serialization.Encoding.PEM)).decode('UTF-8'),
137+
'WrappedKeyBlock': tr34Payload,
138+
'RandomNonce': nonce,
139+
}
140+
},
141+
KeyCheckValueAlgorithm='ANSI_X9_24',
142+
Tags= [{"Key": "Type", "Value" : "Atalla-Test"}]
143+
)
144+
KeyArn=trt34_import_res['Key']['KeyArn']
145+
146+
print(f"Imported TR34 payload with key ARN: {KeyArn}")
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import re
2+
import time
3+
import socket
4+
import binascii
5+
from typing import Tuple
6+
7+
def createRsaKey(atalla_address):
8+
print("Generating RSA key pair from Atalla using command 120")
9+
#generate rsa key pair
10+
data = '<120#w#010001#2048#>'
11+
response = send_data(atalla_address, data.encode(), '<220#(.*?)#(.*?)#(.*?)#>', b'>') #<220#Public Key#Private Key#Check Digits#[Key slot#]>
12+
pubKey = response[0]
13+
privKey = response[1]
14+
return pubKey,privKey
15+
16+
def sign139(keyReference, message, atalla_address ):
17+
print("Signing with Atalla using command 139")
18+
#valjue of 5 means sha-256
19+
data = '<139#5#1#%s######%s#>' % (binascii.hexlify(message).decode().upper(), keyReference)
20+
response = send_data(atalla_address, data.encode(), '<239#.*?#(.*?)#.*>', b'>')
21+
return response[0]
22+
23+
def send_data(target: Tuple[str, int], data: bytes, pattern: str, terminator: bytes = b''):
24+
25+
s = socket.create_connection(target, timeout=10)
26+
s.settimeout(30)
27+
s.sendall(data)
28+
29+
end_time = time.time() + 30
30+
31+
output = b''
32+
while end_time > time.time():
33+
try:
34+
output += s.recv(2**16)
35+
if terminator and terminator in output:
36+
break
37+
except socket.timeout:
38+
break
39+
40+
matcher = re.match(pattern, output.decode())
41+
if not matcher:
42+
raise Exception('Failed to parse data ' + output.decode() + ' for command ' + data.decode())
43+
return matcher.groups()
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import boto3
2+
import time
3+
from botocore.exceptions import ClientError
4+
5+
TAG_KEY = "APC_TR34"
6+
controlplane_client = boto3.client("payment-cryptography")
7+
private_ca = boto3.client("acm-pca")
8+
9+
def create_certificate_authority():
10+
# create Certificate Authority for short-lived certificates
11+
ca_arn = private_ca.create_certificate_authority(
12+
CertificateAuthorityConfiguration={
13+
'KeyAlgorithm': 'RSA_2048',
14+
'SigningAlgorithm': 'SHA256WITHRSA',
15+
'Subject': {
16+
'Country': 'US',
17+
'Organization': 'AWS Samples',
18+
'OrganizationalUnit': 'APC',
19+
'State': 'CA',
20+
'CommonName': 'AWS Samples',
21+
}
22+
},
23+
CertificateAuthorityType='ROOT',
24+
UsageMode='SHORT_LIVED_CERTIFICATE'
25+
)['CertificateAuthorityArn']
26+
27+
state = "CREATING"
28+
while state == "CREATING":
29+
time.sleep(1)
30+
state = private_ca.describe_certificate_authority(CertificateAuthorityArn=ca_arn)['CertificateAuthority'][
31+
'Status']
32+
print(state)
33+
return ca_arn
34+
35+
def create_private_ca():
36+
print("Creating AWS Private CA")
37+
cert_authority_arn = create_certificate_authority()
38+
print("Newly created Private CA ARN: %s" % cert_authority_arn)
39+
# add tag to CA
40+
private_ca.tag_certificate_authority(
41+
CertificateAuthorityArn=cert_authority_arn,
42+
Tags=[
43+
{
44+
'Key': TAG_KEY,
45+
'Value': 'Sample'
46+
},
47+
]
48+
)
49+
print("Getting root CA CSR")
50+
csr = private_ca.get_certificate_authority_csr(CertificateAuthorityArn=cert_authority_arn)['Csr']
51+
print("self-signing root CA CSR")
52+
certificate, chain = sign_with_private_ca(cert_authority_arn, csr, {
53+
'Value': 10,
54+
'Type': 'YEARS'
55+
}, template='arn:aws:acm-pca:::template/RootCACertificate/V1')
56+
print("Importing signed certificate as ROOT")
57+
private_ca.import_certificate_authority_certificate(CertificateAuthorityArn=cert_authority_arn,
58+
Certificate=certificate)
59+
print("CA Setup complete")
60+
return cert_authority_arn
61+
62+
def find_or_create_private_ca():
63+
# find existing CA
64+
for ca in private_ca.list_certificate_authorities()['CertificateAuthorities']:
65+
if ca['Status'] == 'ACTIVE':
66+
# get ca tags
67+
tags = private_ca.list_tags(CertificateAuthorityArn=ca['Arn'])
68+
for tag in tags['Tags']:
69+
if tag['Key'] == TAG_KEY:
70+
# if tag is present, use this CA
71+
return ca['Arn']
72+
return create_private_ca()
73+
74+
def issue_certificate(csr_content):
75+
"""
76+
Issues a certificate using AWS Private CA
77+
78+
Args:
79+
ca_arn (str): ARN of the private CA
80+
csr_file_path (str): Path to the CSR file
81+
82+
Returns:
83+
str: Certificate ARN if successful, None otherwise
84+
"""
85+
try:
86+
# Create ACM PCA client
87+
acmpca_client = boto3.client('acm-pca')
88+
ca_arn = find_or_create_private_ca()
89+
# Request to issue certificate
90+
response = acmpca_client.issue_certificate(
91+
CertificateAuthorityArn=ca_arn,
92+
Csr=csr_content,
93+
SigningAlgorithm='SHA256WITHRSA',
94+
Validity={
95+
'Value': 7,
96+
'Type': 'DAYS'
97+
},
98+
TemplateArn='arn:aws:acm-pca:::template/EndEntityCertificate/V1'
99+
)
100+
101+
certificate_arn = response['CertificateArn']
102+
103+
# Wait for certificate to be issued
104+
waiter = acmpca_client.get_waiter('certificate_issued')
105+
waiter.wait(
106+
CertificateAuthorityArn=ca_arn,
107+
CertificateArn=certificate_arn
108+
)
109+
110+
# Get the issued certificate
111+
response = acmpca_client.get_certificate(
112+
CertificateAuthorityArn=ca_arn,
113+
CertificateArn=certificate_arn
114+
)
115+
116+
certificate = response['Certificate']
117+
certificate_chain = response['CertificateChain']
118+
119+
""" # Save the certificate and chain to files
120+
with open('certificate.pem', 'w') as f:
121+
f.write(certificate)
122+
123+
with open('certificate_chain.pem', 'w') as f:
124+
f.write(certificate_chain) """
125+
126+
return certificate, certificate_chain
127+
128+
except ClientError as e:
129+
print(f"Error issuing certificate: {e}")
130+
return None
131+
132+
def sign_with_private_ca(ca_arn, csr, validity, template="arn:aws:acm-pca:::template/EndEntityCertificate/V1"):
133+
"""
134+
Signs the client-side Key with AWS Private CA and returns the Certificate and Certificate Chain
135+
:param validity:
136+
:param ca_arn:
137+
:param csr: Certificate Signing Request
138+
:param template: Template ARN to use for the certificate
139+
:return:
140+
"""
141+
client = boto3.client('acm-pca')
142+
response = client.issue_certificate(
143+
CertificateAuthorityArn=ca_arn,
144+
Csr=csr,
145+
TemplateArn=template,
146+
SigningAlgorithm='SHA256WITHRSA',
147+
Validity=validity
148+
)
149+
certificate_arn = response['CertificateArn']
150+
time.sleep(0.5)
151+
152+
while 1:
153+
try:
154+
certificate_response = client.get_certificate(CertificateArn=certificate_arn,
155+
CertificateAuthorityArn=ca_arn)
156+
if 'CertificateChain' in certificate_response:
157+
chain = certificate_response['CertificateChain']
158+
else:
159+
chain = None
160+
return certificate_response['Certificate'], chain
161+
except client.exceptions.RequestInProgressException:
162+
time.sleep(0.1)
163+
164+
""" def setup():
165+
ca_arn = find_or_create_private_ca()
166+
#ca_certificate = private_ca.get_certificate_authority_certificate(CertificateAuthorityArn=ca_arn)['Certificate']
167+
print("CA Certificate: %s" % ca_arn)
168+
return ca_arn, ca_arn
169+
170+
setup() """

0 commit comments

Comments
 (0)