*/
#include "lib/replace/replace.h"
+
#include "librpc/gen_ndr/ndr_keycredlink.h"
#include "gen_ndr/keycredlink.h"
+#include "lib/util/data_blob.h"
+#include "lib/util/debug.h"
#include "libndr.h"
+#include "librpc/gen_ndr/ndr_bcrypt_rsakey_blob.h"
+#include "librpc/gen_ndr/ndr_tpm20_rsakey_blob.h"
+#include "util/asn1.h"
+#include "util/data_blob.h"
#include <assert.h>
/*
ndr->flags = _flags_save_STRUCT;
return NDR_ERR_SUCCESS;
}
+
+/* @brief Check that the AlgorithmIdentifier element is correct
+ *
+ * AlgorithmIdentifier ::= SEQUENCE {
+ * algorithm OBJECT IDENTIFIER,
+ * parameters ANY DEFINED BY algorithm OPTIONAL
+ * -- Should be NULL for RSA
+ * }
+ *
+ * @param[in] ndr ndr pull context
+ * @param[in,out] asn ASN data context
+ *
+ * @return NDR_ERR_SUCCESS if the element is valid.
+ */
+static enum ndr_err_code check_algorithm_identifier(struct ndr_pull *ndr,
+ struct asn1_data *asn)
+{
+ static const char *RSA_ENCRYPTION_OID = "1.2.840.113549.1.1.1";
+ uint8_t asn1_null[2];
+ if (!asn1_start_tag(asn, ASN1_SEQUENCE(0))) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_VALIDATE,
+ "Invalid ASN1 tag, expecting SEQUENCE 0x30");
+ }
+ if (!asn1_check_OID(asn, RSA_ENCRYPTION_OID)) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_VALIDATE,
+ "Invalid ASN1 algorithm OID, expecting %s",
+ RSA_ENCRYPTION_OID);
+ }
+
+ /* For an RSA public key, parameters should be null 0x0500 */
+ if (!asn1_read(asn, asn1_null, 2)) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_VALIDATE,
+ "Unexpected ASN1 element, expecting NULL 0x05");
+ }
+ if (!asn1_end_tag(asn)) { /* AlgorithmIdentifier */
+ return ndr_pull_error(ndr,
+ NDR_ERR_UNREAD_BYTES,
+ "ASN1 element AlgorithmIdentifier");
+ }
+ return NDR_ERR_SUCCESS;
+}
+
+/**
+ * @brief start processing a BIT STRING
+ *
+ * The caller will need to call asn1_end_tag
+ *
+ * @param[in] ndr ndr pull context
+ * @param[in,out] asn ASN data context
+ * @param[out] unused_bits the number of unused bits in the least
+ * significant byte (LSB) of the BIT String
+ *
+ * @return NDR_ERR_SUCCESS if successful
+ * The contents of unused_bits are undefined on an error
+ */
+static enum ndr_err_code start_bit_string(struct ndr_pull *ndr,
+ struct asn1_data *asn,
+ uint8_t *unused_bits)
+{
+ if (!asn1_start_tag(asn, ASN1_BIT_STRING)) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_VALIDATE,
+ "Invalid ASN1 tag, expecting BIT STRING 0x03");
+ }
+
+ /*
+ * The first byte of a BIT STRING contains the number of unused bits
+ * in the final byte.
+ */
+ if (!asn1_read_uint8(asn, unused_bits)) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_VALIDATE,
+ "Invalid ASN1 BIT STRING, unable to read "
+ "number of unused bits");
+ }
+ if (*unused_bits > 8) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_RANGE,
+ "Invalid ASN1 BIT STRING, "
+ "number of unused bits exceeds 9");
+ }
+ return NDR_ERR_SUCCESS;
+}
+
+/**
+ * @brief Read a DER encoded INTEGER into a data_blob
+ *
+ * @param[in] mem_ctx memory context to allocate the data_blob data on
+ * @param[in] ndr ndr pull context
+ * @param[in,out] asn ASN data context
+ * @param[in] name the name of the INTEGER for diagnostic messages
+ * @param[out] blob the data blob to populate
+ * using mem_ctx for allocation
+ *
+ * @return NDR_ERR_SUCCESS if successful
+ * The contents of blob are undefined on an error
+ */
+static enum ndr_err_code read_integer(TALLOC_CTX *mem_ctx,
+ struct ndr_pull *ndr,
+ struct asn1_data *asn,
+ const char *name,
+ DATA_BLOB *blob)
+{
+ static const int MAX_SIZE = 2 * 2048; /* 16384 bits */
+ uint8_t msb = 0;
+ int tag_size = 0;
+
+ if (!asn1_start_tag(asn, ASN1_INTEGER)) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_VALIDATE,
+ "Invalid ASN1 tag, expecting INTEGER 0x02");
+ }
+ if (!asn1_peek_uint8(asn, &msb)) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_VALIDATE,
+ "Invalid ASN1 tag, unable to inspect first byte of %s",
+ name);
+ }
+ /* skip a leading 0 byte if present */
+ if (msb == 0) {
+ if (!asn1_read_uint8(asn, &msb)) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_VALIDATE,
+ "Invalid ASN1 tag, unable to "
+ "read first byte of %s",
+ name);
+ }
+ }
+
+ tag_size = asn1_tag_remaining(asn);
+ if (tag_size > MAX_SIZE) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_LENGTH,
+ "INTEGER %s size of %d "
+ "bytes is too large",
+ name,
+ tag_size);
+ }
+ if (tag_size <= 0) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_LENGTH,
+ "INTEGER %s size of %d "
+ "bytes is too small",
+ name,
+ tag_size);
+ }
+ *blob = data_blob_talloc(mem_ctx, NULL, tag_size);
+ if (blob->data == NULL) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_ALLOC,
+ "Unable to allocate DATA_BLOB for %s",
+ name);
+ }
+
+ if (!asn1_read(asn, blob->data, tag_size)) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_VALIDATE,
+ "Unable to read %s",
+ name);
+ }
+ if (!asn1_end_tag(asn)) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_UNREAD_BYTES,
+ "ASN1 INTEGER element %s",
+ name);
+ }
+ return NDR_ERR_SUCCESS;
+}
+
+/**
+ * @brief Convert a DER encoded X509 PublicKey into the Internal public key
+ * representation
+ *
+ * publicKey BIT STRING -- containing an RSAPublicKey
+ * RSAPublicKey ::= SEQUENCE {
+ * modulus INTEGER,
+ * publicExponent INTEGER
+ * }
+ *
+ * @param[in,out] ndr ndr pull context
+ * @param[in] ndr_flags
+ * @param[out] kmi the KeyMaterialInternal structure to populate
+ * kmi needs to be a talloc context.
+ *
+ * @return NDR_ERR_SUCCESS if successful
+ * The contents of kmi are undefined on an error
+ */
+static enum ndr_err_code read_public_key(struct ndr_pull *ndr,
+ struct asn1_data *asn,
+ struct KeyMaterialInternal *kmi)
+{
+ uint8_t unused_bits = 0;
+
+ /*
+ * publicKey BIT STRING
+ * The RSAPublicKey is encoded in a BIT STRING
+ */
+ NDR_CHECK(start_bit_string(ndr, asn, &unused_bits));
+
+ /* RSAPublicKey ::= SEQUENCE {
+ * modulus INTEGER, -- n
+ * publicExponent INTEGER } -- e
+ */
+ if (!asn1_start_tag(asn, ASN1_SEQUENCE(0))) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_VALIDATE,
+ "Invalid ASN1 tag, expecting SEQUENCE 0x30");
+ }
+
+ /* modulus INTEGER */
+ NDR_CHECK(read_integer(kmi, ndr, asn, "MODULUS", &kmi->modulus));
+ kmi->bit_size = (kmi->modulus.length * 8) - unused_bits;
+
+ /* public exponent INTEGER */
+ NDR_CHECK(read_integer(kmi, ndr, asn, "EXPONENT", &kmi->exponent));
+
+ if (!asn1_end_tag(asn)) { /* RSAPublicKey */
+ return ndr_pull_error(ndr,
+ NDR_ERR_UNREAD_BYTES,
+ "ASN1 element RSAPublicKey");
+ }
+ if (!asn1_end_tag(asn)) { /* PublicKey */
+ return ndr_pull_error(ndr,
+ NDR_ERR_UNREAD_BYTES,
+ "ASN1 element PublicKey");
+ }
+ return NDR_ERR_SUCCESS;
+}
+
+/**
+ * @brief Convert a DER encoded X509 public key into the Internal public key
+ * representation
+ *
+ * @param[in,out] ndr ndr pull context
+ * @param[in] ndr_flags
+ * @param[out] kmi the KeyMaterialInternal structure to populate
+ * kmi needs to be a talloc context.
+ * @param[in] size number of bytes to process from the ndr context
+ *
+ * @return NDR_ERR_SUCCESS if successful
+ * The contents of r are undefined on an error
+ */
+static enum ndr_err_code pull_DER_RSA_KEY(struct ndr_pull *ndr,
+ ndr_flags_type ndr_flags,
+ struct KeyMaterialInternal *kmi,
+ uint32_t size)
+{
+ enum ndr_err_code ret = NDR_ERR_SUCCESS;
+ struct asn1_data *asn = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(ndr->current_mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_ALLOC,
+ "Unable to allocate temporary memory "
+ "context");
+ }
+ asn = asn1_init(tmp_ctx, 5);
+ if (asn == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return ndr_pull_error(ndr,
+ NDR_ERR_ALLOC,
+ "Unable to initialize ASN1 context");
+ }
+ asn1_load_nocopy(asn, ndr->data, size);
+
+ /*
+ * PublicKeyInfo ::= SEQUENCE {
+ * algorithm AlgorithmIdentifier,
+ * publicKey BIT STRING
+ * }
+ */
+ if (!asn1_start_tag(asn, ASN1_SEQUENCE(0))) {
+ ret = ndr_pull_error(
+ ndr,
+ NDR_ERR_VALIDATE,
+ "Invalid ASN1 tag, expecting SEQUENCE 0x30");
+ goto out;
+ }
+
+ ret = check_algorithm_identifier(ndr, asn);
+ if (ret != NDR_ERR_SUCCESS) {
+ goto out;
+ }
+
+ ret = read_public_key(ndr, asn, kmi);
+ if (ret != NDR_ERR_SUCCESS) {
+ goto out;
+ }
+ if (!asn1_end_tag(asn)) { /* PublicKeyInfo */
+ return ndr_pull_error(ndr,
+ NDR_ERR_UNREAD_BYTES,
+ "ASN1 element PublicKeyInfo");
+ goto out;
+ }
+
+ /* Successfully parsed the key data */
+ ret = NDR_ERR_SUCCESS;
+ ndr->offset += size; /* signal to NDR that the data has been consumed */
+
+out:
+ asn1_free(asn);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+/**
+ * @brief Convert a TPM20_RSA_KEY_BLOB into the Internal public key
+ * representation
+ * @param[in,out] ndr ndr pull context
+ * @param[in] ndr_flags
+ * @param[out] kmi the KeyMaterialInternal structure to populate
+ * kmi needs to be a talloc context.
+ *
+ * @return NDR_ERR_SUCCESS if successful
+ * The contents of kmi are undefined on an error
+ */
+static enum ndr_err_code pull_TPM20_RSAKEY_BLOB(struct ndr_pull *ndr,
+ ndr_flags_type ndr_flags,
+ struct KeyMaterialInternal *kmi)
+{
+ enum ndr_err_code ret = NDR_ERR_SUCCESS;
+ struct TPM20_RSAKEY_BLOB *km = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(ndr->current_mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_ALLOC,
+ "Unable to allocate temporary memory context");
+ }
+
+ km = talloc_zero(tmp_ctx, struct TPM20_RSAKEY_BLOB);
+ if (km == NULL) {
+ ret = ndr_pull_error(ndr,
+ NDR_ERR_ALLOC,
+ "Unable to allocate TPM20_RSAKEY_BLOB");
+ goto out;
+ }
+
+ ret = ndr_pull_TPM20_RSAKEY_BLOB(ndr, ndr_flags, km);
+ if (ret != NDR_ERR_SUCCESS) {
+ goto out_km;
+ }
+ kmi->bit_size = km->public_key.rsa_detail.keyBits;
+ kmi->modulus = data_blob_talloc(kmi,
+ km->public_key.rsa.buffer,
+ km->public_key.rsa.size);
+ if (kmi->modulus.data == NULL) {
+ ret = ndr_pull_error(
+ ndr,
+ NDR_ERR_ALLOC,
+ "Unable to allocate TPM20_RSAKEY_BLOB modulus");
+ goto out_km;
+ }
+
+ kmi->exponent = data_blob_talloc(kmi,
+ km->public_key.rsa_detail.exponent,
+ TPM_RSA_EXPONENT_SIZE);
+ if (kmi->exponent.data == NULL) {
+ ret = ndr_pull_error(
+ ndr,
+ NDR_ERR_ALLOC,
+ "Unable to allocate TPM20_RSAKEY_BLOB exponent");
+ goto out_km;
+ }
+ ret = NDR_ERR_SUCCESS;
+
+out_km:
+ TALLOC_FREE(km);
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+
+/**
+ * @brief Convert a BCRYPT_RSAPUBLIC_BLOB public key into the Internal public key
+ * representation
+ *
+ * @param[in,out] ndr ndr pull context
+ * @param[in] ndr_flags
+ * @param[out] kmi the KeyMaterialInternal structure to populate
+ * kmi needs to be a talloc context.
+ *
+ * @return NDR_ERR_SUCCESS if successful
+ * The contents of kmi are undefined on an error
+ */
+static enum ndr_err_code pull_BCRYPT_RSAPUBLIC_BLOB(
+ struct ndr_pull *ndr,
+ ndr_flags_type ndr_flags,
+ struct KeyMaterialInternal *kmi)
+{
+ enum ndr_err_code ret = NDR_ERR_SUCCESS;
+ struct BCRYPT_RSAPUBLIC_BLOB *km = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(ndr->current_mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_ALLOC,
+ "Unable to allocate temporary memory context");
+ }
+
+ km = talloc_zero(tmp_ctx, struct BCRYPT_RSAPUBLIC_BLOB);
+ if (km == NULL) {
+ ret = ndr_pull_error(ndr,
+ NDR_ERR_ALLOC,
+ "Unable to allocate BCRYPT_RSAPUBLIC_BLOB");
+ goto out;
+ }
+
+ ret = ndr_pull_BCRYPT_RSAPUBLIC_BLOB(ndr, ndr_flags, km);
+ if (ret != NDR_ERR_SUCCESS) {
+ goto out_km;
+ }
+
+ kmi->bit_size = km->bit_length;
+
+ kmi->modulus = data_blob_talloc(kmi,
+ km->modulus,
+ km->modulus_len);
+ if (kmi->modulus.data == NULL) {
+ ret = ndr_pull_error(
+ ndr,
+ NDR_ERR_ALLOC,
+ "Unable to allocate BCRYPT_RSAPUBLIC_BLOB modulus");
+ goto out_km;
+ }
+
+ kmi->exponent = data_blob_talloc(kmi,
+ km->public_exponent,
+ km->public_exponent_len);
+ if (kmi->exponent.data == NULL) {
+ ret = ndr_pull_error(
+ ndr,
+ NDR_ERR_ALLOC,
+ "Unable to allocate BCRYPT_RSAPUBLIC_BLOB exponent");
+ goto out_km;
+ }
+
+ ret = NDR_ERR_SUCCESS;
+
+out_km:
+ TALLOC_FREE(km);
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+
+/**
+ * @brief Convert a KeyMaterial blob into the Internal public key
+ * representation KeyMaterialInternal
+ *
+ * @param[in,out] ndr ndr pull context
+ * @param[in] ndr_flags
+ * @param[out] kmi the KeyMaterialInternal structure to populate
+ * kmi needs to be a talloc context.
+ *
+ * @return NDR_ERR_SUCCESS if successful
+ * The contents of kmi are undefined on an error
+ */
+enum ndr_err_code ndr_pull_KeyMaterialInternal(struct ndr_pull *ndr,
+ ndr_flags_type ndr_flags,
+ struct KeyMaterialInternal *kmi)
+{
+ static const uint8_t BCRYPT_HEADER[] = {'R', 'S', 'A', '1'};
+ static const uint8_t TPM20_HEADER[] = {'P', 'C', 'P', 'M'};
+ static const uint32_t MIN_KEY_MATERIAL_SIZE = 5;
+ static const uint32_t MAX_KEY_MATERIAL_SIZE = 64 * 1024;
+
+ uint32_t size = 0;
+
+ if (ndr->offset > ndr->data_size) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_LENGTH,
+ "ndr->offset (%" PRIu32
+ ") is greater than "
+ "ndr->data_size (%" PRIu32 ")",
+ ndr->offset,
+ ndr->data_size);
+ }
+ size = ndr->data_size - ndr->offset;
+ if (size < MIN_KEY_MATERIAL_SIZE) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_LENGTH,
+ "KeyMaterial size of %" PRIu32
+ " bytes is too small",
+ size);
+ }
+ if (size > MAX_KEY_MATERIAL_SIZE) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_LENGTH,
+ "KeyMaterial size of %" PRIu32
+ " bytes is too large",
+ size);
+ }
+
+ if (memcmp(BCRYPT_HEADER, ndr->data, sizeof(BCRYPT_HEADER)) == 0) {
+ return pull_BCRYPT_RSAPUBLIC_BLOB(ndr, ndr_flags, kmi);
+ } else if (memcmp(TPM20_HEADER, ndr->data, sizeof(TPM20_HEADER)) == 0) {
+ return pull_TPM20_RSAKEY_BLOB(ndr, ndr_flags, kmi);
+ } else if (*ndr->data == ASN1_SEQUENCE(0)) {
+ /*
+ * If the first byte is an ASN1 sequence marker assume that
+ * this is an x509 public key
+ */
+ return pull_DER_RSA_KEY(ndr, ndr_flags, kmi, size);
+ } else {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_VALIDATE,
+ "Unknown KeyMaterial type, could not be decoded");
+ }
+}
+
+/**
+ * @brief Push a representation of a KeyMaterialInternal onto the
+ * ndr_push context.
+ *
+ * @param[in,out] ndr ndr push context
+ * @param[in] ndr_flags
+ * @param[out] kmi the KeyMaterialInternal structure to populate
+ * kmi needs to be a talloc context.
+ *
+ * @note This is not currently implemented and will always return
+ * NDR_ERR_VALIDATE
+ *
+ * @return NDR_ERR_VALIDATE
+ *
+ */
+enum ndr_err_code ndr_push_KeyMaterialInternal(
+ struct ndr_push *ndr,
+ ndr_flags_type ndr_flags,
+ const struct KeyMaterialInternal *kmi)
+{
+ return ndr_push_error(
+ ndr,
+ NDR_ERR_VALIDATE,
+ "NDR Push for KeyMaterialInternal not currently supported");
+}
packed = ndr_pack(blob)
self.assertEqual(source, packed)
+ def test_unpack_tpm_key_material(self):
+ """
+ ensure that sample TPM 20 key material can be unpacked
+ into a KeyMaterialInternal structure
+ """
+ key_material = bytes.fromhex(
+ "50 43 50 4D" # Magic value PCPM
+ "2E 00 00 00" # header length
+ "02 00 00 00" # type TPM 2.0
+ "00 00 00 00" # flags
+ "00 00 00 00" # public_length
+ "00 00 00 00" # private length
+ "00 00 00 00" # migration public length
+ "00 00 00 00" # migration private length
+ "00 00 00 00" # policy digest list length
+ "00 00 00 00" # PCR binding length
+ "00 00 00 00" # PCR digest length
+ "00 00 00 00" # Encrypted secret length
+ "00 00 00 00" # TPM 1.2 hostage blob length
+ "00 00" # PCRA Algorithm Id
+ "18 01" # size 280 bytes
+ "00 01" # type
+ "00 0B" # hash algorithm
+ "00 05 24 72" # attributes
+ "00 00" # auth policy"
+ "00 10" # algorithm
+ "00 14" # scheme
+ "00 0B" # hash algorithm
+ "08 00" # key bits
+ "01 02 03 04" # exponent
+ "01 00" # size 256 bytes
+ "9A 9E F6 5D E2 92 D6 D0 E5 B3 C4 35 B1 5B 36 F3"
+ "9E 83 7B A9 34 AB D9 67 E1 1C 75 43 E5 B6 48 9B"
+ "6E CD 8D FC 30 5F 4C B6 8E A0 69 A4 07 21 E7 D7"
+ "A1 74 4A 29 BC C9 5D 78 70 C4 3B E4 20 54 BC D0"
+ "AA FF 21 44 54 FC 09 08 2A CC DE 44 68 ED 9F B2"
+ "3E F7 ED 82 D7 2D 28 74 42 2A 2F 55 A2 E0 DA 45"
+ "F1 08 C0 83 8C 95 81 6D 92 CC A8 5D A4 B8 06 8C"
+ "76 F5 68 94 E7 60 E6 F4 EE 40 50 28 6C 82 47 89"
+ "07 E7 BC 0D 56 5D DA 86 57 E2 CE D3 19 A1 A2 7F"
+ "56 F8 99 8B 4A 71 32 6A 57 3B F9 E5 2D 39 35 6E"
+ "13 3E 84 DC 5C 96 E1 75 38 C3 AA 23 5B 68 BE 41"
+ "52 49 72 7A F6 2A 8F C5 C5 E0 6C DB 99 D1 A8 84"
+ "5F 70 21 87 2E A0 D2 68 D3 76 5C 9E D4 9C B5 E1"
+ "72 9D 17 8B DC 11 55 09 90 8D 96 F3 68 34 DD 50"
+ "63 AC 4A 74 A7 AF 0D DC 15 06 07 D7 5A B3 86 1A"
+ "54 96 E0 FA 66 25 31 F5 B4 C7 97 C7 7C 70 94 E3"
+ )
+
+ exponent = bytes.fromhex("01 02 03 04")
+ modulus = bytes.fromhex(
+ "9A 9E F6 5D E2 92 D6 D0 E5 B3 C4 35 B1 5B 36 F3"
+ "9E 83 7B A9 34 AB D9 67 E1 1C 75 43 E5 B6 48 9B"
+ "6E CD 8D FC 30 5F 4C B6 8E A0 69 A4 07 21 E7 D7"
+ "A1 74 4A 29 BC C9 5D 78 70 C4 3B E4 20 54 BC D0"
+ "AA FF 21 44 54 FC 09 08 2A CC DE 44 68 ED 9F B2"
+ "3E F7 ED 82 D7 2D 28 74 42 2A 2F 55 A2 E0 DA 45"
+ "F1 08 C0 83 8C 95 81 6D 92 CC A8 5D A4 B8 06 8C"
+ "76 F5 68 94 E7 60 E6 F4 EE 40 50 28 6C 82 47 89"
+ "07 E7 BC 0D 56 5D DA 86 57 E2 CE D3 19 A1 A2 7F"
+ "56 F8 99 8B 4A 71 32 6A 57 3B F9 E5 2D 39 35 6E"
+ "13 3E 84 DC 5C 96 E1 75 38 C3 AA 23 5B 68 BE 41"
+ "52 49 72 7A F6 2A 8F C5 C5 E0 6C DB 99 D1 A8 84"
+ "5F 70 21 87 2E A0 D2 68 D3 76 5C 9E D4 9C B5 E1"
+ "72 9D 17 8B DC 11 55 09 90 8D 96 F3 68 34 DD 50"
+ "63 AC 4A 74 A7 AF 0D DC 15 06 07 D7 5A B3 86 1A"
+ "54 96 E0 FA 66 25 31 F5 B4 C7 97 C7 7C 70 94 E3"
+ )
+ kmi = ndr_unpack(keycredlink.KeyMaterialInternal, key_material)
+ self.assertEqual(kmi.bit_size, 2048)
+ self.assertEqual(len(kmi.exponent), 4)
+ self.assertEqual(kmi.exponent, exponent)
+ self.assertEqual(len(kmi.modulus), 256)
+ self.assertEqual(kmi.modulus, modulus)
+
+ def test_unpack_bcrypt_key_material(self):
+ """
+ ensure that sample bcrypt key material can be unpacked
+ into a KeyMaterialInternal structure
+ """
+ key_material = bytes.fromhex(
+ "52 53 41 31" # Magic value RSA1
+ "00 08 00 00" # bit length, 2048
+ "04 00 00 00" # public exponent length
+ "00 01 00 00" # modulus length, 256
+ "00 00 00 00" # prime one length"
+ "00 00 00 00" # prime two length"
+ "01 02 03 04" # public exponent
+ "9A 9E F6 5D E2 92 D6 D0 E5 B3 C4 35 B1 5B 36 F3"
+ "9E 83 7B A9 34 AB D9 67 E1 1C 75 43 E5 B6 48 9B"
+ "6E CD 8D FC 30 5F 4C B6 8E A0 69 A4 07 21 E7 D7"
+ "A1 74 4A 29 BC C9 5D 78 70 C4 3B E4 20 54 BC D0"
+ "AA FF 21 44 54 FC 09 08 2A CC DE 44 68 ED 9F B2"
+ "3E F7 ED 82 D7 2D 28 74 42 2A 2F 55 A2 E0 DA 45"
+ "F1 08 C0 83 8C 95 81 6D 92 CC A8 5D A4 B8 06 8C"
+ "76 F5 68 94 E7 60 E6 F4 EE 40 50 28 6C 82 47 89"
+ "07 E7 BC 0D 56 5D DA 86 57 E2 CE D3 19 A1 A2 7F"
+ "56 F8 99 8B 4A 71 32 6A 57 3B F9 E5 2D 39 35 6E"
+ "13 3E 84 DC 5C 96 E1 75 38 C3 AA 23 5B 68 BE 41"
+ "52 49 72 7A F6 2A 8F C5 C5 E0 6C DB 99 D1 A8 84"
+ "5F 70 21 87 2E A0 D2 68 D3 76 5C 9E D4 9C B5 E1"
+ "72 9D 17 8B DC 11 55 09 90 8D 96 F3 68 34 DD 50"
+ "63 AC 4A 74 A7 AF 0D DC 15 06 07 D7 5A B3 86 1A"
+ "54 96 E0 FA 66 25 31 F5 B4 C7 97 C7 7C 70 94 E3"
+ )
+ exponent = bytes.fromhex("01 02 03 04")
+ modulus = bytes.fromhex(
+ "9A 9E F6 5D E2 92 D6 D0 E5 B3 C4 35 B1 5B 36 F3"
+ "9E 83 7B A9 34 AB D9 67 E1 1C 75 43 E5 B6 48 9B"
+ "6E CD 8D FC 30 5F 4C B6 8E A0 69 A4 07 21 E7 D7"
+ "A1 74 4A 29 BC C9 5D 78 70 C4 3B E4 20 54 BC D0"
+ "AA FF 21 44 54 FC 09 08 2A CC DE 44 68 ED 9F B2"
+ "3E F7 ED 82 D7 2D 28 74 42 2A 2F 55 A2 E0 DA 45"
+ "F1 08 C0 83 8C 95 81 6D 92 CC A8 5D A4 B8 06 8C"
+ "76 F5 68 94 E7 60 E6 F4 EE 40 50 28 6C 82 47 89"
+ "07 E7 BC 0D 56 5D DA 86 57 E2 CE D3 19 A1 A2 7F"
+ "56 F8 99 8B 4A 71 32 6A 57 3B F9 E5 2D 39 35 6E"
+ "13 3E 84 DC 5C 96 E1 75 38 C3 AA 23 5B 68 BE 41"
+ "52 49 72 7A F6 2A 8F C5 C5 E0 6C DB 99 D1 A8 84"
+ "5F 70 21 87 2E A0 D2 68 D3 76 5C 9E D4 9C B5 E1"
+ "72 9D 17 8B DC 11 55 09 90 8D 96 F3 68 34 DD 50"
+ "63 AC 4A 74 A7 AF 0D DC 15 06 07 D7 5A B3 86 1A"
+ "54 96 E0 FA 66 25 31 F5 B4 C7 97 C7 7C 70 94 E3"
+ )
+
+ kmi = ndr_unpack(keycredlink.KeyMaterialInternal, key_material)
+ self.assertEqual(kmi.bit_size, 2048)
+ self.assertEqual(len(kmi.exponent), 4)
+ self.assertEqual(kmi.exponent, exponent)
+ self.assertEqual(len(kmi.modulus), 256)
+ self.assertEqual(kmi.modulus, modulus)
+
+ def test_unpack_der_key_material(self):
+ """
+ ensure that sample X509 public key material can be unpacked
+ into a KeyMaterialInternal structure
+ """
+ key_material = bytes.fromhex(
+ "30 82 01 22" # Sequence 290 bytes, 2 elements
+ "30 0d" # Sequence 13 bytes, 2 elements
+ # OID 9 bytes, 1.2.840.113549.1.1.1
+ "06 09 2a 86 48 86 f7 0d 01 01 01"
+ "05 00" # Null
+ "03 82 01 0f 00" # Bit string, 2160 bits, 0 unused bits
+ "30 82 01 0a" # Sequence 266 bytes, 2 elements
+ "02 82 01 01" # Integer 2048 bit, 257 bytes
+ # MODULUS is 257 bytes as it's most significant byte
+ # is 0xbd 0b10111101 and has bit 8 set,
+ # which DER Integer encoding uses as the sign bit,
+ # so need the leading 00 byte to prevent the value
+ # being interpreted as a negative integer
+ "00 bd ae 45 8b 17 cd 3e 62 71 66 67 7f a2 46 c4"
+ "47 78 79 f2 8c d4 2e 0c a0 90 1c f6 33 e1 94 89"
+ "b9 44 15 e3 29 e7 b6 91 ca ab 7e c6 25 60 e3 7a"
+ "c4 09 97 8a 4e 79 cb a6 1f f8 29 3f 8a 0d 45 58"
+ "9b 0e bf a5 fa 1c a2 5e 31 a1 e7 ba 7e 17 62 03"
+ "79 c0 07 48 11 8b fa 58 17 56 1a a1 62 d2 02 02"
+ "2a 64 8d 8c 53 fa 28 7c 89 18 34 70 64 a7 08 10"
+ "c9 3b 1b 2c 23 88 9c 35 50 78 d1 89 33 ce 82 b2"
+ "84 f4 99 d8 3e 67 11 a1 5c 1a 64 b8 6a 3e e6 95"
+ "2e 47 33 51 7e b7 62 b4 08 2c c4 87 52 00 9e 28"
+ "f2 16 9f 1b c1 3a 93 6d a3 38 9b 34 39 88 85 ea"
+ "38 ad c2 2b c3 7c 15 cb 8f 15 37 ed 88 62 5c 34"
+ "75 6f b0 eb 5c 42 6a cd 03 cc 49 bc b4 78 14 e1"
+ "5e 98 83 6f e7 19 a8 43 cb ca 07 b2 4e a4 36 60"
+ "95 ac 6f e2 1d 3a 33 f6 0e 94 ae fb d2 ac 9f c2"
+ "9f 5b 77 8f 46 3c ee 13 27 19 8e 68 71 27 3f 50"
+ "59"
+ "02 03 01 00 01" # Integer 3 bytes EXPONENT
+ )
+
+ modulus = bytes.fromhex(
+ "bd ae 45 8b 17 cd 3e 62 71 66 67 7f a2 46 c4"
+ "47 78 79 f2 8c d4 2e 0c a0 90 1c f6 33 e1 94 89"
+ "b9 44 15 e3 29 e7 b6 91 ca ab 7e c6 25 60 e3 7a"
+ "c4 09 97 8a 4e 79 cb a6 1f f8 29 3f 8a 0d 45 58"
+ "9b 0e bf a5 fa 1c a2 5e 31 a1 e7 ba 7e 17 62 03"
+ "79 c0 07 48 11 8b fa 58 17 56 1a a1 62 d2 02 02"
+ "2a 64 8d 8c 53 fa 28 7c 89 18 34 70 64 a7 08 10"
+ "c9 3b 1b 2c 23 88 9c 35 50 78 d1 89 33 ce 82 b2"
+ "84 f4 99 d8 3e 67 11 a1 5c 1a 64 b8 6a 3e e6 95"
+ "2e 47 33 51 7e b7 62 b4 08 2c c4 87 52 00 9e 28"
+ "f2 16 9f 1b c1 3a 93 6d a3 38 9b 34 39 88 85 ea"
+ "38 ad c2 2b c3 7c 15 cb 8f 15 37 ed 88 62 5c 34"
+ "75 6f b0 eb 5c 42 6a cd 03 cc 49 bc b4 78 14 e1"
+ "5e 98 83 6f e7 19 a8 43 cb ca 07 b2 4e a4 36 60"
+ "95 ac 6f e2 1d 3a 33 f6 0e 94 ae fb d2 ac 9f c2"
+ "9f 5b 77 8f 46 3c ee 13 27 19 8e 68 71 27 3f 50"
+ "59"
+ )
+ exponent = bytes.fromhex("01 00 01")
+
+ kmi = ndr_unpack(keycredlink.KeyMaterialInternal, key_material)
+ self.assertEqual(kmi.bit_size, 2048)
+ self.assertEqual(len(kmi.exponent), 3)
+ self.assertEqual(kmi.exponent, exponent)
+ self.assertEqual(len(kmi.modulus), 256)
+ self.assertEqual(kmi.modulus, modulus)
+
+ def test_unpack_invalid_key_material(self):
+ """
+ ensure that an unknown key is rejected
+ """
+ key_material = b"NOT REALLY A KEY POSSIBLY A PASSWORD"
+ with self.assertRaises(RuntimeError) as e:
+ ndr_unpack(keycredlink.KeyMaterialInternal, key_material)
+
+ self.assertEqual(e.exception.args[0], 10)
+ self.assertEqual(e.exception.args[1], "Validate Error")
+
+ def test_unpack_too_short_key_material(self):
+ """
+ ensure that key material shorter than 5 bytes is rejected
+ """
+ key_material = b"1234"
+ with self.assertRaises(RuntimeError) as e:
+ ndr_unpack(keycredlink.KeyMaterialInternal, key_material)
+
+ self.assertEqual(e.exception.args[0], 6)
+ self.assertEqual(e.exception.args[1], "Length Error")
+
+ def test_unpack_too_long_key_material(self):
+ """
+ ensure that key material longer than 64KiB is rejected
+ """
+ key_material = b"4" * ((64 * 1024) + 1)
+ with self.assertRaises(RuntimeError) as e:
+ ndr_unpack(keycredlink.KeyMaterialInternal, key_material)
+
+ self.assertEqual(e.exception.args[0], 6)
+ self.assertEqual(e.exception.args[1], "Length Error")
+
if __name__ == "__main__":
import unittest