GEA issues (May 2016): *********************** libosmo-crypt-gea12/src/osmocom.c --------------------------------- GEA1_stream() and GEA2_stream() operate with bits and not bytes. This is how they have to be used: static int gea1_run(uint8_t *out, uint16_t len, uint64_t kc, uint32_t iv, enum gprs_cipher_direction direction) { uint8_t dir; uint8_t bits_input[32]; uint8_t bits_kc[64]; int i; // get 32 input bits for(i = 0; i < 32; i++) { bits_input[i] = (BYTE)((iv >> i) & 1); } // get 64 key bits for(i = 0; i < 64; i++) { #if 0 // reverse Kc byte order ("reverse" compared to Calypso test code) bits_kc[i] = (BYTE)((kc >> i) & 1); #else // "normal" Kc byte order (whatever "normal" is) bits_kc[i] = (BYTE)((kc >> (((7 - (i / 8)) * 8) + (i % 8))) & 1); #endif } if (direction == GPRS_CIPH_MS2SGSN) dir = DIRECTION_UPLINK; else dir = DIRECTION_DOWNLINK; GEA1_stream(bits_kc, bits_input, dir, len, out); return 0; } static int gea2_run(uint8_t *out, uint16_t len, uint64_t kc, uint32_t iv, enum gprs_cipher_direction direction) { uint8_t dir; uint8_t bits_input[32]; uint8_t bits_kc[64]; int i; // get 32 input bits for(i = 0; i < 32; i++) { bits_input[i] = (BYTE)((iv >> i) & 1); } // get 64 key bits for(i = 0; i < 64; i++) { #if 0 // reverse Kc byte order ("reverse" compared to Calypso test code) bits_kc[i] = (BYTE)((kc >> i) & 1); #else // "normal" Kc byte order (whatever "normal" is) bits_kc[i] = (BYTE)((kc >> (((7 - (i / 8)) * 8) + (i % 8))) & 1); #endif } if (direction == GPRS_CIPH_MS2SGSN) dir = DIRECTION_UPLINK; else dir = DIRECTION_DOWNLINK; GEA2_stream(bits_kc, bits_input, dir, len, out); return 0; } libosmo-crypt-a53/src/osmocom.c ------------------------------- The length of the key is in bits and not in bytes: static int gea3_run(uint8_t *out, uint16_t len, uint64_t kc, uint32_t iv, enum gprs_cipher_direction direction) { uint8_t dir; if (direction == GPRS_CIPH_MS2SGSN) dir = 0; else dir = 1; GEA3((uint8_t *)&kc, sizeof(kc) * 8, iv, dir, out, len); return 0; } openbsc/openbsc/src/gprs/gprs_llc.c ----------------------------------- Frame encryption in gprs_llc_tx_ui() doesn't work: /* encrypt information field + FCS, if needed! */ if (lle->llme->algo != GPRS_ALGO_GEA0) { uint32_t iov_ui = 0; /* FIXME: randomly select for TLLI */ uint16_t crypt_len = (fcs + 3) - (llch + 3); uint8_t cipher_out[GSM0464_CIPH_MAX_BLOCK]; uint32_t iv; int rc, i; uint64_t kc = *(uint64_t *)&lle->llme->kc; /* Compute the 'Input' Paraemeter */ iv = gprs_cipher_gen_input_ui(iov_ui, sapi, nu, oc); /* Compute the keystream that we need to XOR with the data */ rc = gprs_cipher_run(cipher_out, crypt_len, lle->llme->algo, kc, iv, GPRS_CIPH_SGSN2MS); if (rc < 0) { LOGP(DLLC, LOGL_ERROR, "Error crypting UI frame: %d\n", rc); msgb_free(msg); return rc; } /* Mark frame as encrypted */ #if 1 // Dieter: recalculate checksum after properly setting the flag llch[2] |= 0x02; fcs_calc = gprs_llc_fcs(llch, fcs - llch); fcs[0] = fcs_calc & 0xff; fcs[1] = (fcs_calc >> 8) & 0xff; fcs[2] = (fcs_calc >> 16) & 0xff; #endif /* XOR the cipher output with the information field + FCS */ for (i = 0; i < crypt_len; i++) *(llch + 3 + i) ^= cipher_out[i]; /* Mark frame as encrypted */ #if 0 // Dieter: This won't work ctrl[1] |= 0x02; #endif } Frame decryption in gprs_llc_rcvmsg() doesn't work: /* decrypt information field + FCS, if needed! */ if (llhp.is_encrypted) { uint32_t iov_ui = 0; /* FIXME: randomly select for TLLI */ uint16_t crypt_len = llhp.data_len + 3; uint8_t cipher_out[GSM0464_CIPH_MAX_BLOCK]; uint32_t iv; uint64_t kc = *(uint64_t *)&lle->llme->kc; int rc, i; if (lle->llme->algo == GPRS_ALGO_GEA0) { LOGP(DLLC, LOGL_NOTICE, "encrypted frame for LLC that " "has no KC/Algo! Dropping.\n"); return 0; } iv = gprs_cipher_gen_input_ui(iov_ui, lle->sapi, llhp.seq_tx, lle->oc_ui_recv); rc = gprs_cipher_run(cipher_out, crypt_len, lle->llme->algo, kc, iv, GPRS_CIPH_MS2SGSN); if (rc < 0) { LOGP(DLLC, LOGL_ERROR, "Error decrypting frame: %d\n", rc); return rc; } /* XOR the cipher output with the information field + FCS */ for (i = 0; i < crypt_len; i++) *(llhp.data + i) ^= cipher_out[i]; #if 1 // Dieter: FCS is encrypted, gprs_llc_hdr_parse() should be called again llhp.fcs = *(llhp.data + crypt_len - 3); llhp.fcs |= *(llhp.data + crypt_len - 2) << 8; llhp.fcs |= *(llhp.data + crypt_len - 1) << 16; #endif } else { if (lle->llme->algo != GPRS_ALGO_GEA0) { LOGP(DLLC, LOGL_NOTICE, "unencrypted frame for LLC " "that is supposed to be encrypted. Dropping.\n"); return 0; } } Open issues: ############ openbsc/openbsc/src/gprs/gprs_llc.c ----------------------------------- When creating an LLME "on the fly" in lle_for_rx_by_tlli_sapi() kc and algo of the LLME have to be set to the ones used for the subscriber. In general: ----------- Management of GEA algorithm and KC for a subscriber. The algorithm has to be selected according to the capabilities of the MS. Some affected functions are gprs_llgmm_assign() and gsm48_tx_gmm_auth_ciph_req(). Handling of IOV (initialisation vector), currently most of the time IOV is set to 0, but gprs_llgmm_reset() and gprs_llgmm_reset_oldmsg() use a random IOV and would probably cause problems when called. Using uint64_t as the type for Kc in the GEA libraries is not a good choice, uint8_t[8] would be better and cause less troubles (e.g. endian issues and incompatibility with the test vectors, where the order of Kc has to be reversed). Attention: this change might require to adjust the byte order of Kc in the GEA libraries.