Last active
August 14, 2024 02:03
-
-
Save salrashid123/c1de41bf380c1f9a3602675276977e48 to your computer and use it in GitHub Desktop.
GCE mTLS Cert Extractor
This file contains 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 main | |
/* | |
script extracts the tpm encryptedblob (which is the client public/private key | |
curl -v -H 'Metadata-Flavor: Google' http://metadata/computeMetadata/v1/instance/credentials/certs | |
{ | |
"encrypted_credentials": "qmiCsZSSGxU8w7MnZoKe0XriLAPYmNGh6t9BqwtI5MqEJS7sv3pGFaHWi+U0oT8zQpuVsVVaEHP2qLwjE11v7xGR8ZCGr2Y5XnvlyEao8rcD8th6hCswLdJiMUATSZviThwziGIGAAzuHOofzEHbp8vDMe4hFsinz1sPvmOrd1aN1HAYPcXH5qpMdo0kgklXhc3O8p+cgVKO2UA7uNqM+/mOHzgxdLZ9f+X8gxAA1WWgr+X2G4W1BUUTPxFTchnzImoandoUo/SMinSVSrvVSkBCEm6GttxBjjgjc0Y391bOsCcEZH2xUx/Ev7+TfI2zqz0nMYQgB8Vly4/07WAPwUX3D8mFAgoTnokAOgHz1mcenH6C7XhKQRKd84wst2e5L35WFAsXY2Dtj7NgAS08T1e1N81VzDIBS7L8B1y9Y9ACu4n2gcKr/uRo36zunKEOd9rBRvNOdpkzaU3RQC97FUJrAUu6At2q9RLE2brphkq5bn11/CXWskNuzApS3MwzG9jeHJ1AAmDYbKicr1jzgMXhSBB63FXMs87kNtqj3X8XiCFJ89Ckewv1r3G9kzHisOQP1JjSfzD/AFOabkg/eFI6WfnriADuNPwMJHCKMsjDik+f6w4rLuOuLjMFm5OCAQkqAIppiWiBIfoT+4P7Z8Eg31s35wBvd82bIuTx8jG5ODYR7DjXa2kKlnytSI6KKPzlu3Q4z2gzkn3vuP5H5qjpWgDripKr2uqGMsYCRTEC0q0AutLtEY4ayU/Xo07kPyC/fBfQgXJuK/4Eq4UGHmbUP3YWazSph4/2gl4YFZfEzVIpeqzDKLaLWq0BhdJ3nCEFi4NFF0f31bF4QOdqsAzaEZJRYqAAflMDbsJA00Xluz8vQnQs29M8gKEk/600qzvCls81MWlqOetmxWdzkOZk5V2/rVms8QBiKUWh0+WIcPf4krcA9XQ/0I1ppFqX87h0dNH6tPk0vuSrUhSrKTSl25+B+6lZ174S319ObSjkaDjjoeFbCboj2mDvst6R5m/0YpYEfMzhI2VnJWUFrL2JH3VSsiMoBUpu/ZjmZ+CHsFaNkqxmjlZ8qLSMf4SvYO5Cl5Yhq9oXeLuajFiteHaSUluU5rEIDo1YbohFFj1YIE+9kSHk5j+asERsXuiKFSUx4g/plk1E/PXVtIbnK54DjR3mLxl+HQ48vrzRLOSxDoFmCKkY3k1qsFNnlzxDn/g0u0XTp7iV/y5T04bPV1dYs3VwudPQgNZxmPX3yIjw8125FQVyzDy5+J9LhpsoyzlU68tXzuA9g4r1P4isav71nveXPtVY", | |
"key_import_blob": { | |
"duplicate": "ACCM8+q96Kw/opc27hp2eLDjRMtPdhhsVEKL93DTnRgXv4vuLM9X7ESCgen6UR85aFEKuQvmUxGy9dMNSSAGXplQ6XExv/Sm6tZGQvLZiRSuVYXS+qd2SBVD6SQ=", | |
"encrypted_seed": "ACA1qmyysQSllFL7JCwI+83e5OVBy5u7UHHUqTQuxnER2AAgRkQm0TWX8S0sSEWkn0wk5+uncbjcFgU1rwr1XYc9hJs=", | |
"public_area": "AAgACwAAAEAAAAAQACC+l/8i4DIV3sFwI9W0QJj2uSbnj3mrYYaLa1F+YqWGBg==" | |
} | |
} | |
then extracts the root ca from uefi | |
then you can use both to call the server | |
$ curl -vvv --cacert root.crt --cert client.crt --key client.key -H "Metadata-Flavor: Google" https://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token | |
root.crt: | |
```bash | |
-----BEGIN CERTIFICATE----- | |
MIIB8jCCAZmgAwIBAgIQMJ4M/U4XlcHHijn6Z/yc1zAKBggqhkjOPQQDAjBJMQsw | |
CQYDVQQGEwJVUzEgMB4GA1UECgwXR29vZ2xlIENvbXB1dGUgSW50ZXJuYWwxGDAW | |
BgNVBAMMD2dvb2dsZS5pbnRlcm5hbDAgFw0yNDA4MTIxMTUyMjFaGA8yMDc0MDcz | |
MTExNTcyMVowSTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF0dvb2dsZSBDb21wdXRl | |
IEludGVybmFsMRgwFgYDVQQDDA9nb29nbGUuaW50ZXJuYWwwWTATBgcqhkjOPQIB | |
BggqhkjOPQMBBwNCAARGAmdkrV2T+bHYEeDIBNDgyE9H9cLDxBTZo7iufvfDQPb1 | |
adjWWLnlk3rtHgXBwbqr3mTyxuF/uKaajTYI7T9so2EwXzASBgNVHRMBAf8ECDAG | |
AQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU2jmj7l5rSw0yVb/vlWAY | |
kK/YBwkwGgYDVR0RBBMwEYIPZ29vZ2xlLmludGVybmFsMAoGCCqGSM49BAMCA0cA | |
MEQCIFGFNVvHj8Zdo4N5jdoh57HUYGvG8cdfuvyKXvek6YpdAiAnHF2ZqGsMiyHj | |
8ZzzB7BhZDi4JnNKOJvkggBohD9EdA== | |
-----END CERTIFICATE----- | |
Certificate: | |
Data: | |
Version: 3 (0x2) | |
Serial Number: | |
30:9e:0c:fd:4e:17:95:c1:c7:8a:39:fa:67:fc:9c:d7 | |
Signature Algorithm: ecdsa-with-SHA256 | |
Issuer: C=US, O=Google Compute Internal, CN=google.internal | |
Validity | |
Not Before: Aug 12 11:52:21 2024 GMT | |
Not After : Jul 31 11:57:21 2074 GMT | |
Subject: C=US, O=Google Compute Internal, CN=google.internal | |
Subject Public Key Info: | |
Public Key Algorithm: id-ecPublicKey | |
Public-Key: (256 bit) | |
pub: | |
04:46:02:67:64:ad:5d:93:f9:b1:d8:11:e0:c8:04: | |
d0:e0:c8:4f:47:f5:c2:c3:c4:14:d9:a3:b8:ae:7e: | |
f7:c3:40:f6:f5:69:d8:d6:58:b9:e5:93:7a:ed:1e: | |
05:c1:c1:ba:ab:de:64:f2:c6:e1:7f:b8:a6:9a:8d: | |
36:08:ed:3f:6c | |
ASN1 OID: prime256v1 | |
NIST CURVE: P-256 | |
X509v3 extensions: | |
X509v3 Basic Constraints: critical | |
CA:TRUE, pathlen:0 | |
X509v3 Key Usage: critical | |
Certificate Sign, CRL Sign | |
X509v3 Subject Key Identifier: | |
DA:39:A3:EE:5E:6B:4B:0D:32:55:BF:EF:95:60:18:90:AF:D8:07:09 | |
X509v3 Subject Alternative Name: | |
DNS:google.internal | |
Signature Algorithm: ecdsa-with-SHA256 | |
Signature Value: | |
30:44:02:20:51:85:35:5b:c7:8f:c6:5d:a3:83:79:8d:da:21: | |
e7:b1:d4:60:6b:c6:f1:c7:5f:ba:fc:8a:5e:f7:a4:e9:8a:5d: | |
02:20:27:1c:5d:99:a8:6b:0c:8b:21:e3:f1:9c:f3:07:b0:61: | |
64:38:b8:26:73:4a:38:9b:e4:82:00:68:84:3f:44:74 | |
``` | |
============== | |
server.crt | |
```bash | |
$ cat server.crt | |
-----BEGIN CERTIFICATE----- | |
MIIB7zCCAZWgAwIBAgIQVpSIxheNTu0UQYNxSE112TAKBggqhkjOPQQDAjBJMQsw | |
CQYDVQQGEwJVUzEgMB4GA1UECgwXR29vZ2xlIENvbXB1dGUgSW50ZXJuYWwxGDAW | |
BgNVBAMMD2dvb2dsZS5pbnRlcm5hbDAgFw0yNDA4MTIxMTUyMjJaGA8yMDc0MDcz | |
MTExNTcyMlowQzELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF0dvb2dsZSBDb21wdXRl | |
IEludGVybmFsMRIwEAYDVQQDDAlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjO | |
PQMBBwNCAASCqUSirOi0uyhsYmgzYk2Kms/CZW2wXjxjqTIflKA5W3rgm04vU5ic | |
0pU1tLK7CszMUDJbQ5eVSXspIJg1JBG1o2MwYTAMBgNVHRMBAf8EAjAAMA4GA1Ud | |
DwEB/wQEAwIHgDApBgNVHREEIjAgghhtZXRhZGF0YS5nb29nbGUuaW50ZXJuYWyH | |
BKn+qf4wFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwEwCgYIKoZIzj0EAwIDSAAwRQIh | |
ALYb5iqMlAFuyopdbiW36h55USM3Srng0TfHSnvD6fbSAiADZe/f6OF6mnqbP3Ob | |
f4TIrwMr14GU6M+08ZSbD6iYWg== | |
-----END CERTIFICATE----- | |
Certificate: | |
Data: | |
Version: 3 (0x2) | |
Serial Number: | |
56:94:88:c6:17:8d:4e:ed:14:41:83:71:48:4d:75:d9 | |
Signature Algorithm: ecdsa-with-SHA256 | |
Issuer: C=US, O=Google Compute Internal, CN=google.internal | |
Validity | |
Not Before: Aug 12 11:52:22 2024 GMT | |
Not After : Jul 31 11:57:22 2074 GMT | |
Subject: C=US, O=Google Compute Internal, CN=localhost | |
Subject Public Key Info: | |
Public Key Algorithm: id-ecPublicKey | |
Public-Key: (256 bit) | |
pub: | |
04:82:a9:44:a2:ac:e8:b4:bb:28:6c:62:68:33:62: | |
4d:8a:9a:cf:c2:65:6d:b0:5e:3c:63:a9:32:1f:94: | |
a0:39:5b:7a:e0:9b:4e:2f:53:98:9c:d2:95:35:b4: | |
b2:bb:0a:cc:cc:50:32:5b:43:97:95:49:7b:29:20: | |
98:35:24:11:b5 | |
ASN1 OID: prime256v1 | |
NIST CURVE: P-256 | |
X509v3 extensions: | |
X509v3 Basic Constraints: critical | |
CA:FALSE | |
X509v3 Key Usage: critical | |
Digital Signature | |
X509v3 Subject Alternative Name: | |
DNS:metadata.google.internal, IP Address:169.254.169.254 | |
X509v3 Extended Key Usage: critical | |
TLS Web Server Authentication | |
Signature Algorithm: ecdsa-with-SHA256 | |
Signature Value: | |
30:45:02:21:00:b6:1b:e6:2a:8c:94:01:6e:ca:8a:5d:6e:25: | |
b7:ea:1e:79:51:23:37:4a:b9:e0:d1:37:c7:4a:7b:c3:e9:f6: | |
d2:02:20:03:65:ef:df:e8:e1:7a:9a:7a:9b:3f:73:9b:7f:84: | |
c8:af:03:2b:d7:81:94:e8:cf:b4:f1:94:9b:0f:a8:98:5a | |
``` | |
client.crt | |
```bash | |
$ cat client.crt | |
-----BEGIN CERTIFICATE----- | |
MIIB7jCCAZSgAwIBAgIRAIL2RGietbLMgTX/KWEdv54wCgYIKoZIzj0EAwIwSTEL | |
MAkGA1UEBhMCVVMxIDAeBgNVBAoMF0dvb2dsZSBDb21wdXRlIEludGVybmFsMRgw | |
FgYDVQQDDA9nb29nbGUuaW50ZXJuYWwwHhcNMjQwODEzMjMwNTI2WhcNMjQwODIw | |
MjMxMDI2WjBAMQswCQYDVQQGEwJVUzEeMBwGA1UECgwVR29vZ2xlIENvbXB1dGUg | |
RW5naW5lMREwDwYDVQQDDAhtdGxzdGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEH | |
A0IABEsWIDi7KGTc+MBQ82/C4r5fxXPrZ+TBfqj4zQ1zizRopOw5u6iLfTbBtORT | |
1+ROR1tlwpYDEkMJ4QghS7EkH4+jZjBkMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/ | |
BAQDAgeAMCwGA1UdEQQlMCOCIW10bHN0ZXN0LmMuc3Jhc2hpZC10ZXN0Mi5pbnRl | |
cm5hbDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAjAKBggqhkjOPQQDAgNIADBFAiBH | |
9KD/218EYr9NTNx0VpTiOMShYEhy9rTcude8FbH4vQIhAM6KFN4NbC0fVToWS++S | |
I0lgIH0Qnlg8l4UsxnQUVuwr | |
-----END CERTIFICATE----- | |
Certificate: | |
Data: | |
Version: 3 (0x2) | |
Serial Number: | |
82:f6:44:68:9e:b5:b2:cc:81:35:ff:29:61:1d:bf:9e | |
Signature Algorithm: ecdsa-with-SHA256 | |
Issuer: C=US, O=Google Compute Internal, CN=google.internal | |
Validity | |
Not Before: Aug 13 23:05:26 2024 GMT | |
Not After : Aug 20 23:10:26 2024 GMT | |
Subject: C=US, O=Google Compute Engine, CN=mtlstest | |
Subject Public Key Info: | |
Public Key Algorithm: id-ecPublicKey | |
Public-Key: (256 bit) | |
pub: | |
04:4b:16:20:38:bb:28:64:dc:f8:c0:50:f3:6f:c2: | |
e2:be:5f:c5:73:eb:67:e4:c1:7e:a8:f8:cd:0d:73: | |
8b:34:68:a4:ec:39:bb:a8:8b:7d:36:c1:b4:e4:53: | |
d7:e4:4e:47:5b:65:c2:96:03:12:43:09:e1:08:21: | |
4b:b1:24:1f:8f | |
ASN1 OID: prime256v1 | |
NIST CURVE: P-256 | |
X509v3 extensions: | |
X509v3 Basic Constraints: critical | |
CA:FALSE | |
X509v3 Key Usage: critical | |
Digital Signature | |
X509v3 Subject Alternative Name: | |
DNS:mtlstest.c.srashid-test2.internal | |
X509v3 Extended Key Usage: critical | |
TLS Web Client Authentication | |
Signature Algorithm: ecdsa-with-SHA256 | |
Signature Value: | |
30:45:02:20:47:f4:a0:ff:db:5f:04:62:bf:4d:4c:dc:74:56: | |
94:e2:38:c4:a1:60:48:72:f6:b4:dc:b9:d7:bc:15:b1:f8:bd: | |
02:21:00:ce:8a:14:de:0d:6c:2d:1f:55:3a:16:4b:ef:92:23: | |
49:60:20:7d:10:9e:58:3c:97:85:2c:c6:74:14:56:ec:2b | |
``` | |
*/ | |
import ( | |
"context" | |
"crypto/x509" | |
"encoding/hex" | |
"encoding/pem" | |
"fmt" | |
"io" | |
"net" | |
"slices" | |
"google.golang.org/protobuf/encoding/protojson" | |
"github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/uefi" | |
"cloud.google.com/go/compute/metadata" | |
pb "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/agentcrypto/credentials" | |
"github.com/google/go-tpm-tools/client" | |
pbt "github.com/google/go-tpm-tools/proto/tpm" | |
"github.com/google/go-tpm-tools/simulator" | |
"github.com/google/go-tpm/tpmutil" | |
"github.com/google/tink/go/aead/subtle" | |
) | |
var TPMDEVICES = []string{"/dev/tpm0", "/dev/tpmrm0"} | |
func openTPM(path string) (io.ReadWriteCloser, error) { | |
if slices.Contains(TPMDEVICES, path) { | |
return tpmutil.OpenTPM(path) | |
} else if path == "simulator" { | |
return simulator.GetWithFixedSeedInsecure(1073741825) | |
} else { | |
return net.Dial("tcp", path) | |
} | |
} | |
const ( | |
googleGUID = "a2858e46-a37f-456a-8c79-0c1fe48b65ff" | |
// googleRootCACertEFIVarName is predefined string part of the UEFI variable name that holds Root CA cert. | |
googleRootCACertEFIVarName = "InstanceRootCACertificate" | |
) | |
func main() { | |
// f, err := os.ReadFile("certs.json") | |
// if err != nil { | |
// panic(err) | |
// } | |
c, err := metadata.GetWithContext(context.Background(), "instance/credentials/certs") | |
if err != nil { | |
panic(err) | |
} | |
f := []byte(c) | |
res := &pb.GuestCredentialsResponse{} | |
if err := protojson.Unmarshal(f, res); err != nil { | |
panic(err) | |
} | |
dek, err := extractKey(res.KeyImportBlob) | |
if err != nil { | |
panic(err) | |
} | |
err = protojson.Unmarshal(f, res) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Printf("DEK %s\n", hex.EncodeToString(dek)) | |
plaintext, err := decrypt(dek, res.GetEncryptedCredentials(), nil) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Printf("plaintext \n%s\n", string(plaintext)) | |
googleRootCACertUEFIVar := uefi.VariableName{Name: googleRootCACertEFIVarName, GUID: googleGUID} | |
v, err := readRootCACert(googleRootCACertUEFIVar) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Printf("RootCA: \n%s\n", v.Content) | |
} | |
func readRootCACert(name uefi.VariableName) (*uefi.Variable, error) { | |
rootCACert, err := uefi.ReadVariable(name) | |
if err != nil { | |
return nil, fmt.Errorf("unable to read root CA cert file contents: %w", err) | |
} | |
if _, err := parseCertificate(rootCACert.Content); err != nil { | |
return nil, fmt.Errorf("unable to verify Root CA cert: %w", err) | |
} | |
fmt.Printf("Successfully read root CA Cert from %+v", name) | |
return rootCACert, nil | |
} | |
// parseCertificate validates certificate is in valid PEM format. | |
func parseCertificate(cert []byte) (*x509.Certificate, error) { | |
block, _ := pem.Decode(cert) | |
if block == nil { | |
return nil, fmt.Errorf("failed to parse PEM certificate") | |
} | |
x509Cert, err := x509.ParseCertificate(block.Bytes) | |
if err != nil { | |
return nil, fmt.Errorf("failed to parse certificate: %w", err) | |
} | |
return x509Cert, nil | |
} | |
func extractKey(importBlob *pbt.ImportBlob) ([]byte, error) { | |
rwc, err := openTPM("/dev/tpm0") | |
defer rwc.Close() | |
ek, err := client.EndorsementKeyECC(rwc) | |
if err != nil { | |
return nil, fmt.Errorf("failed to load a key from TPM: %w", err) | |
} | |
defer ek.Close() | |
dek, err := ek.Import(importBlob) | |
if err != nil { | |
return nil, fmt.Errorf("failed to decrypt import blob: %w", err) | |
} | |
return dek, nil | |
} | |
// encrypt encrypts plain text using AES GCM algorithm. | |
func encrypt(aesKey []byte, plainText []byte, associatedData []byte) ([]byte, error) { | |
cipher, err := subtle.NewAESGCM(aesKey) | |
if err != nil { | |
return nil, fmt.Errorf("failed to initialize cipher: %v", err) | |
} | |
return cipher.Encrypt(plainText, associatedData) | |
} | |
// decrypt decrypts AES GCM encrypted cipher text. | |
func decrypt(aesKey []byte, cipherText []byte, associatedData []byte) ([]byte, error) { | |
cipher, err := subtle.NewAESGCM(aesKey) | |
if err != nil { | |
return nil, fmt.Errorf("failed to initialize cipher: %v", err) | |
} | |
return cipher.Decrypt(cipherText, associatedData) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment