Created
July 23, 2026 01:03
-
-
Save mohashari/05ba5b92914b3a97272477e3c1033bcf to your computer and use it in GitHub Desktop.
Implementing Cryptographic Attestation for Ephemeral Microservice Identities using AWS Nitro Enclaves and KMS — code snippets
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # build_eif.sh | |
| set -euo pipefail | |
| echo "[*] Building statically linked Go application..." | |
| CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ | |
| -ldflags="-w -s -extldflags '-static'" \ | |
| -o bootstrap_app ./cmd/bootstrap | |
| echo "[*] Generating container definition..." | |
| cat <<EOF > Dockerfile.enclave | |
| FROM alpine:3.19 | |
| RUN apk add --no-cache ca-certificates | |
| COPY bootstrap_app /usr/local/bin/bootstrap_app | |
| ENTRYPOINT ["/usr/local/bin/bootstrap_app"] | |
| EOF | |
| echo "[*] Building target Docker container..." | |
| docker build -t enclave-service:v1.0.0 -f Dockerfile.enclave . | |
| echo "[*] Building Enclave Image File (EIF)..." | |
| # This produces 'enclave-service.eif' and prints PCR measurements to stdout. | |
| nitro-cli build-enclave \ | |
| --docker-uri enclave-service:v1.0.0 \ | |
| --output-file enclave-service.eif > measurements.json | |
| echo "[*] EIF Generation complete. Measurements saved to measurements.json." | |
| cat measurements.json | grep -A 6 "Measurements" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package enclave | |
| import ( | |
| "crypto/rand" | |
| "crypto/rsa" | |
| "crypto/x509" | |
| "fmt" | |
| "os" | |
| "syscall" | |
| "unsafe" | |
| ) | |
| const ( | |
| NsmDevicePath = "/dev/nsm" | |
| NsmIoctlMagic = 0x0A | |
| // IOCTL code to request attestation document from the Nitro Security Module | |
| NsmRequestAttestation = 3224373763 | |
| ) | |
| type NsmAttestationRequest struct { | |
| UserTagRaw uintptr | |
| UserTagLen uint32 | |
| NonceRaw uintptr | |
| NonceLen uint32 | |
| PubKeyRaw uintptr | |
| PubKeyLen uint32 | |
| ResponseRaw uintptr | |
| ResponseLen uint32 | |
| } | |
| // GenerateEphemeralKeyPair generates an RSA-2048 key pair in memory. | |
| // The private key must never write to disk or exit the enclave. | |
| func GenerateEphemeralKeyPair() (*rsa.PrivateKey, []byte, error) { | |
| privKey, err := rsa.GenerateKey(rand.Reader, 2048) | |
| if err != nil { | |
| return nil, nil, fmt.Errorf("failed to generate ephemeral key: %w", err) | |
| } | |
| pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey) | |
| if err != nil { | |
| return nil, nil, fmt.Errorf("failed to marshal public key: %w", err) | |
| } | |
| return privKey, pubKeyBytes, nil | |
| } | |
| // FetchAttestationDocument queries the /dev/nsm device to generate a hardware-signed document. | |
| func FetchAttestationDocument(pubKeyBytes []byte, nonce []byte) ([]byte, error) { | |
| file, err := os.OpenFile(NsmDevicePath, os.O_RDWR, 0) | |
| if err != nil { | |
| return nil, fmt.Errorf("unable to open NSM device: %w", err) | |
| } | |
| defer file.Close() | |
| // Pre-allocate a buffer for the response (typically 4-8KB) | |
| responseBuffer := make([]byte, 16384) | |
| req := NsmAttestationRequest{ | |
| PubKeyRaw: uintptr(unsafe.Pointer(&pubKeyBytes[0])), | |
| PubKeyLen: uint32(len(pubKeyBytes)), | |
| ResponseRaw: uintptr(unsafe.Pointer(&responseBuffer[0])), | |
| ResponseLen: uint32(len(responseBuffer)), | |
| } | |
| if len(nonce) > 0 { | |
| req.NonceRaw = uintptr(unsafe.Pointer(&nonce[0])) | |
| req.NonceLen = uint32(len(nonce)) | |
| } | |
| _, _, errno := syscall.Syscall( | |
| syscall.SYS_IOCTL, | |
| file.Fd(), | |
| uintptr(NsmRequestAttestation), | |
| uintptr(unsafe.Pointer(&req)), | |
| ) | |
| if errno != 0 { | |
| return nil, fmt.Errorf("ioctl NSM_REQUEST_ATT_DOC failed: %w", errno) | |
| } | |
| return responseBuffer[:req.ResponseLen], nil | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "Version": "2012-10-17", | |
| "Id": "enclave-kms-key-policy", | |
| "Statement": [ | |
| { | |
| "Sid": "AllowAdministrationAndManagement", | |
| "Effect": "Allow", | |
| "Principal": { | |
| "AWS": "arn:aws:iam::123456789012:role/DevSecOpsAdmin" | |
| }, | |
| "Action": "kms:*", | |
| "Resource": "*" | |
| }, | |
| { | |
| "Sid": "RestrictDecryptionToSpecificNitroEnclave", | |
| "Effect": "Allow", | |
| "Principal": { | |
| "AWS": "arn:aws:iam::123456789012:role/microservice-parent-role" | |
| }, | |
| "Action": "kms:Decrypt", | |
| "Resource": "*", | |
| "Condition": { | |
| "StringEquals": { | |
| "kms:RecipientEquals:pcr0": "8e3c12f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8", | |
| "kms:RecipientEquals:pcr1": "2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e", | |
| "kms:RecipientEquals:pcr2": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b" | |
| } | |
| } | |
| } | |
| ] | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package enclave | |
| import ( | |
| "context" | |
| "crypto/rand" | |
| "crypto/rsa" | |
| "crypto/sha256" | |
| "fmt" | |
| "net" | |
| "net/http" | |
| "github.com/aws/aws-sdk-go-v2/aws" | |
| "github.com/aws/aws-sdk-go-v2/config" | |
| "github.com/aws/aws-sdk-go-v2/service/kms" | |
| "github.com/aws/aws-sdk-go-v2/service/kms/types" | |
| ) | |
| // DecryptEnclavePayload sends the ciphertext and attestation document to KMS. | |
| // It decrypts the re-encrypted response using the private key. | |
| func DecryptEnclavePayload( | |
| ctx context.Context, | |
| kmsKeyID string, | |
| encryptedSecret []byte, | |
| attestationDoc []byte, | |
| privateKey *rsa.PrivateKey, | |
| ) ([]byte, error) { | |
| // Configure custom HTTP client to route over vsock proxy | |
| dialer := &net.Dialer{} | |
| customTransport := &http.Transport{ | |
| DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { | |
| // Redirect all KMS API requests to the vsock proxy on the parent host | |
| return dialer.DialContext(ctx, "tcp", "127.0.0.1:8000") | |
| }, | |
| } | |
| cfg, err := config.LoadDefaultConfig(ctx, | |
| config.WithHTTPClient(&http.Client{Transport: customTransport}), | |
| config.WithRegion("us-east-1"), | |
| ) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to initialize AWS config: %w", err) | |
| } | |
| kmsClient := kms.NewFromConfig(cfg) | |
| input := &kms.DecryptInput{ | |
| CiphertextBlob: encryptedSecret, | |
| KeyId: aws.String(kmsKeyID), | |
| Recipient: &types.RecipientInfo{ | |
| AttestationDocument: attestationDoc, | |
| KeyEncryptionAlgorithm: types.KeyEncryptionAlgorithmSpecRsaesOaepSha256, | |
| }, | |
| } | |
| output, err := kmsClient.Decrypt(ctx, input) | |
| if err != nil { | |
| return nil, fmt.Errorf("kms.Decrypt execution failed: %w", err) | |
| } | |
| if len(output.CiphertextBlob) == 0 { | |
| return nil, fmt.Errorf("empty ciphertext blob returned from KMS") | |
| } | |
| // Decrypt the re-encrypted ciphertext using the enclave's ephemeral private key | |
| hash := sha256.New() | |
| plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, privateKey, output.CiphertextBlob, nil) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to decrypt envelope payload: %w", err) | |
| } | |
| return plaintext, nil | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "Sid": "AllowSignedEnclaveDecryptionOnly", | |
| "Effect": "Allow", | |
| "Principal": { | |
| "AWS": "arn:aws:iam::123456789012:role/microservice-parent-role" | |
| }, | |
| "Action": "kms:Decrypt", | |
| "Resource": "*", | |
| "Condition": { | |
| "StringEquals": { | |
| "kms:RecipientEquals:pcr4": "ab89ef0123cd45ef678901abcdef2345678901abcdef2345678901abcdef2345" | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package enclave | |
| import ( | |
| "crypto/rand" | |
| "fmt" | |
| "os" | |
| ) | |
| // NSMProvider provides an interface abstraction over hardware-level attestation. | |
| type NSMProvider interface { | |
| GetAttestationDoc(userData, nonce, publicKey []byte) ([]byte, error) | |
| Close() | |
| } | |
| type LiveNSMProvider struct { | |
| fd int | |
| } | |
| func NewLiveNSMProvider() (*LiveNSMProvider, error) { | |
| // Custom implementation or wrapper to open `/dev/nsm` | |
| return &LiveNSMProvider{fd: 1}, nil | |
| } | |
| func (l *LiveNSMProvider) GetAttestationDoc(userData, nonce, publicKey []byte) ([]byte, error) { | |
| return FetchAttestationDocument(publicKey, nonce) | |
| } | |
| func (l *LiveNSMProvider) Close() {} | |
| // MockNSMProvider is instantiated when running on local workstations or testing environments. | |
| type MockNSMProvider struct{} | |
| func (m *MockNSMProvider) GetAttestationDoc(userData, nonce, publicKey []byte) ([]byte, error) { | |
| // Return a dummy base64/cbor payload. Your testing mock in AWS KMS must recognize this pattern. | |
| fmt.Printf("[MOCK] NSM invoked. Generating mock attestation document. UserData size: %d\n", len(userData)) | |
| dummyDoc := make([]byte, 1024) | |
| if _, err := rand.Read(dummyDoc); err != nil { | |
| return nil, err | |
| } | |
| return dummyDoc, nil | |
| } | |
| func (m *MockNSMProvider) Close() {} | |
| // ResolveNSMProvider evaluates environment variables to select live or mock attestation. | |
| func ResolveNSMProvider() NSMProvider { | |
| if _, err := os.Stat(NsmDevicePath); os.IsNotExist(err) { | |
| fmt.Println("[!] /dev/nsm device not found. Initializing Mock NSM Provider.") | |
| return &MockNSMProvider{} | |
| } | |
| prov, err := NewLiveNSMProvider() | |
| if err != nil { | |
| fmt.Printf("[!] Failed to initialize Live NSM: %v. Falling back to Mock.\n", err) | |
| return &MockNSMProvider{} | |
| } | |
| return prov | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment