/* The Newcamd Cardserver Protocol This document is intented for developers, who'd like to program their own clients for the Newcamd cardserver. I got several requests in the past to make this information available and since it's no "state" secret as with many other software of this type, I hope, you'll find this document useful. There is some example code about the encryption used and about TCP connections in general at the bottom of this document. Document conventions In this document, when talking about a particular bit, big endianess is always assumed, so the bit order within one byte is 76543210. Also when talking about server and client, cardserver is always the server, newcamd is a client. Changes newcamd-cardserver-tng5: add 16 bit packet length (byte 1 and 2, unencrypted) and a 16 bit sequence number (byte 3 and 4, encrypted) newcamd-cardserver-tng12: add 4 bytes for general data in the packet encryption process (bytes 5-8, encrypted) In ECM request messages, this general data field has to be set to the service id of the channel, which needs to be decrypted. It is mandatory that the client sets this correctly. I MEAN IT PEOPLE!! Failure to comply with this requirement will result in the change of the newcamd<->cardserver protocol to a non-public variant. newcamd v5.22: keepalive packet added newcamd v5.25: changed the customData field from 4 to 8 bytes, the sid occupies the first two bytes and has to be set correctly by the client (see above), the remaining 6 bytes are for future enhancements and have to be set to zero for now Encryption Newcamd uses encrypted tcp connections to communicate with its cardservers, for every card a dedicated tcp connection is used. The encryption used is Triple-DES in outer cipher block chaining mode (CBC). See a good book about cryptography for details. Remember, DES keys are 7 bytes (56 bits), Triple-DES keys are 14 bytes (112 bits). There are three encryption keys: The key from the config file, which is never actually used as is, the login key (configfile key ^ 14 random bytes) and the session key, which is used after the login (configfile key ^ userpassword). Since newcamd-v5.25 12 bytes are added before the actual package in the encryption process (see above: Changes). Packet description (before encryption) Messages sent back and forth between newcamd and a cardserver always consist of a three byte header and (optional) data bytes. The header always starts with a command tag byte. This is always the first byte (byte 1) of a message. In case of an ECM or EMM this is simply the table id of the ECM (0x80, 0x81) or EMM (0x82 - 0x8f). Other commands use cmd tags starting from 0xe0 like this: #define CWS_FIRSTCMDNO 0xe0 typedef enum { MSG_CLIENT_2_SERVER_LOGIN = CWS_FIRSTCMDNO, MSG_CLIENT_2_SERVER_LOGIN_ACK, MSG_CLIENT_2_SERVER_LOGIN_NAK, MSG_CARD_DATA_REQ, MSG_CARD_DATA, MSG_SERVER_2_CLIENT_NAME, MSG_SERVER_2_CLIENT_NAME_ACK, MSG_SERVER_2_CLIENT_NAME_NAK, MSG_SERVER_2_CLIENT_LOGIN, MSG_SERVER_2_CLIENT_LOGIN_ACK, MSG_SERVER_2_CLIENT_LOGIN_NAK, MSG_ADMIN, MSG_ADMIN_ACK, MSG_ADMIN_LOGIN, MSG_ADMIN_LOGIN_ACK, MSG_ADMIN_LOGIN_NAK, MSG_ADMIN_COMMAND, MSG_ADMIN_COMMAND_ACK, MSG_ADMIN_COMMAND_NAK, MSG_KEEPALIVE = CWS_FIRSTCMDNO + 0x1d, } net_msg_type_t; The upper 4 bits of byte 2 have no effect on messages from newcamd to cardserver. On ECM responses from the cardserver, the upper 4 bits of byte 2 indicate the card provider that was used to operate on the ECM. On EMM responses from the card server, bit 4 indicates to the client, if the card accepted the EMM (signature correct, bit4 = 1) or if it didn't (bit4 = 0). Bit 5 indicates if any if the card's provider id's might have been changed by the EMM. If the clients reads bit 5 of byte 2 = 1, it should send a new card data request MSG_CARD_DATA_REQ and the server will answer with MSG_CARD_DATA. This is currently implemented only for Seca (emm return code 90 19 = PPUA changed) and Irdeto (all c3 updates). The lower 4 bits of byte 2 and all bits of byte 3 form a 12-bit length field. This indicates the total number of bytes to follow byte 3. So the total length of any packet is always len = (((msg[1] & 0x0f) << 8) | msg[2]) + 3; This means, in case you hadn't noticed, that ECMs can be sent to the cardserver just as they were received from the demuxer, starting with 80/81 <4 bit> <12 bit section length>. EMMs usually can be sent the same way, however, there are a few exceptions in CA systems where a "card" EMM has to be assembled with nano commands in ascending order from several "demux" EMMs (Viaccess, Cryptoworks). ECMs and EMMs responses from the cardserver always start with the same command tag used to send the ECM/EMM to the card server. An EMM response never includes any additional data, so it will always look like 82 00 00 (wrong signature) or 82 10 00 (correct signature), with 82 being the original table id of the EMM (this could be any value betwwen 0x82 and 0x8f). An ECM response always begins with the original ECM, followed by either 16 or no additional data bytes. No additional data bytes mean, the card wasn't able to decrypt the channel. If 16 additional data bytes are received by the client, the card was able to decrypt the channel, the 16 data bytes are the two control words, first even, then odd. The redundancy within a control word (bytes 4 and 8) is eliminated by the cardserver, so you can be sure, no information about your particular card is send to the clients. Client to Server Login This describes how to login into a particular card. Remember each card has its own dedicated TCP port, this is how you choose, which card you want. Client <- Server 1/5 - 090f - Thu Jan 8 17:20:17 CET 2004 encryption: none ---------------------------------------------------------- 00: 77 9d cc 5d d2 0d 59 2e dc ed b8 17 c1 ab w ] Y. After opening a TCP connection to the server, the client first receives 14 random bytes. These bytes are to be XORed to the Triple-DES key from the config file. (newcamd: CWS = ..., cardserver: DESKEY = ...). The result forms the Triple DES key to be used to send Username and Password to the cardserver, I call it the login key. Client -> Server 1/5 - 090f - Thu Jan 8 17:20:18 CET 2004 encryption: login ---------------------------------------------------------- 00: e0 00 29 64 75 6d 6d 79 00 24 31 24 61 62 63 64 )dummy $1$abcd 10: 65 66 67 68 24 6e 70 53 45 54 51 73 72 49 6d 33 efgh$npSETQsrIm3 20: 35 4d 51 66 69 55 49 41 64 6e 2e 00 5MQfiUIAdn. Next the client has to send a packet with cmd = MSG_CLIENT_2_SERVER_LOGIN including username and password in the data field. The username is sent as a C-String (NULL terminated), the password follows directly after the zero termination byte of the username. The password has to be put through the glibc crypt() function, using salt $1$abcdefgh$. The password in the data field has to be NULL terminated and the packet encrypted with the login key. cryptPw = crypt(plainPw, "$1$abcdefgh$"); Client <- Server 1/5 - 090f - Thu Jan 8 17:20:18 CET 2004 encryption: login ---------------------------------------------------------- 00: e1 00 00 The cardserver now checks username and password and answers with a MSG_CLIENT_2_SERVER_LOGIN_ACK or MSG_CLIENT_2_SERVER_LOGIN_NAK packet. If instead it just closes the TCP connection, you're DES key was probably incorrect and the cardserver didn't understand a word you were saying :) At this point, you have to change to the session DES key. The session key will be the key from the config file (newcamd: CWS = ..., cardserver: DESKEY = ...) xored with the password of the connection just established. for (i = 0; i < strlen(cryptPw); i++) deskey[i%14] ^= cryptPw[i]; Client -> Server 1/5 - 090f - Thu Jan 8 17:20:18 CET 2004 encryption: session ---------------------------------------------------------- 00: e3 00 00 The clients sends the next command, MSG_CARD_DATA_REQ, no additional data, encrypted with the new DES key. Client <- Server 1/5 - 090f - Thu Jan 8 17:20:18 CET 2004 encryption: session ---------------------------------------------------------- 00: e4 00 17 01 09 0f 00 00 00 00 XX XX XX XX 01 00 a 10: 00 00 00 00 00 00 XX XX XX 00 It receives a packet MSG_CARD_DATA from the cardserver which looks like this: byte 1: MSG_CARD_DATA byte 2/3: data length byte 4: userid of the user byte 5/6: caid of the card byte 7-14: card number (only for userid == 1, all 00 otherwise) byte 15: number of providers on the card (noProv) for (i = 0; i < noProv; i++) { byte 16+11*i - 18+11*i: provider ident byte 19+11*i - 26+11*i: provider id (only for userid == 1, all 00 otherwise) } Receiving the card data concludes the login procedure. Now ECM/EMMs can be sent to this card. Server to Client Login If the cardserver wants to open a connection to a client (because a card was just inserted into a card reader for example) it opens a TCP connection to CWS_INCOMING_PORT of newcamd. Client -> Server 1/5 - 090f - Thu Jan 8 17:02:24 CET 2004 encryption: none ---------------------------------------------------------- 00: 77 9d cc 5d d2 0d 59 2e dc ed b8 17 c1 ab w ] Y. The client has to send 14 random bytes to form the login DES key later on (see Client to Server Login). Client <- Server 1/5 - 090f - Thu Jan 8 17:02:24 CET 2004 encryption: none ---------------------------------------------------------- 00: e5 00 08 68 64 62 6f 78 00 07 df hdbox Now the cardserver sends an UNENCRYPTED identification packet MSG_SERVER_2_CLIENT_NAME. The data field of this packet consists of the cardserver's name (SERVER_NAME from cardserv.cfg), again NULL terminated, and the TCP port it is listening on for incoming connections. The client has to use this information to choose the appropriate DES key for the cardserver attempting to make the connection, that's why this packet is sent without any encryption. Client -> Server 1/5 - 090f - Thu Jan 8 17:02:24 CET 2004 encryption: none ---------------------------------------------------------- 00: e6 00 00 If the client recognizes the cardserver it answers with MSG_SERVER_2_CLIENT_NAME_ACK, otherwise with MSG_SERVER_2_CLIENT_NAME_NAK. This packet is also sent unencrypted. Client <- Server 1/5 - 090f - Thu Jan 8 17:02:24 CET 2004 encryption: login ---------------------------------------------------------- 00: e8 00 29 64 75 6d 6d 79 00 24 31 24 61 62 63 64 )dummy $1$abcd 10: 65 66 67 68 24 6e 70 53 45 54 51 73 72 49 6d 33 efgh$npSETQsrIm3 20: 35 4d 51 66 69 55 49 41 64 6e 2e 00 5MQfiUIAdn. Now the cardserver sends username and password in the same way as in Client to Server Login, but with different command tag MSG_SERVER_2_CLIENT_LOGIN, encrypted with the login key. Client -> Server 1/5 - 090f - Thu Jan 8 17:02:24 CET 2004 encryption: login ---------------------------------------------------------- 00: e9 00 00 Client answers either MSG_SERVER_2_CLIENT_LOGIN_ACK or MSG_SERVER_2_CLIENT_LOGIN_NAK, Client -> Server 1/5 - 090f - Thu Jan 8 17:02:25 CET 2004 encryption: session ---------------------------------------------------------- 00: e3 00 00 Then it switches from the login to the session key and sends packet MSG_CARD_DATA_REQ. Client <- Server 1/5 - 090f - Thu Jan 8 17:02:25 CET 2004 encryption: session ---------------------------------------------------------- 00: e4 00 17 01 09 0f 00 00 00 00 XX XX XX XX 01 00 a 10: 00 00 00 00 00 00 XX XX XX 00 Then waits for MSG_CARD_DATA, just as in client 2 server login. This concludes the server 2 client login procedure. Viaccess EMM-S Viaccess EMM-S have to be assembled by the client from an EMM-GH (general (for all cards) header) with table id 0x8c or 0x8d and an EMM-S (shared (for a group of 256 cards)) with table id 0x8e. A pseudo EMM with table id 0x8e has to be build by the client including all nano commands from both the original EMM-GH and EMM-S in ascending order. If the EMM-S is a "fixed-length" EMM, containing only the data fields of the 9e 20 and f0 08 nanos, 9e 20 and f0 08 have to be added to the pseudo EMM as well, right before their respective data fields. Use cardserver's DEBUG_NET logging facility if you need any examples on how these pseudo EMM-S look like. Cryptoworks EMM-S Cryptoworks EMM-S have to be assembled by the client from an EMM-SH with table id 0x84 and a corresponding EMM-SB (body) with table id 0x86. A pseudo EMM-S with table id 0x84 has to be build containing all nano commands from both the original EMM-SH and EMM-SB in ascending order. Keepalive Your client may send a keepalive packet on occasion to prevent crappy NAT routers from timing out your tcp connection to the server. The server will echo the packet as is. The command tag for this packet is MSG_KEEPALIVE. */ #include #include #include #include #include #include #include #include #define CWS_NETMSGSIZE 240 typedef unsigned char uint8; typedef unsigned short uint16; typedef unsigned int uint32; typedef unsigned long long uint64; #define CWS_FIRSTCMDNO 0xe0 typedef enum { MSG_CLIENT_2_SERVER_LOGIN = CWS_FIRSTCMDNO, MSG_CLIENT_2_SERVER_LOGIN_ACK, MSG_CLIENT_2_SERVER_LOGIN_NAK, MSG_CARD_DATA_REQ, MSG_CARD_DATA, MSG_SERVER_2_CLIENT_NAME, MSG_SERVER_2_CLIENT_NAME_ACK, MSG_SERVER_2_CLIENT_NAME_NAK, MSG_SERVER_2_CLIENT_LOGIN, MSG_SERVER_2_CLIENT_LOGIN_ACK, MSG_SERVER_2_CLIENT_LOGIN_NAK, MSG_ADMIN, MSG_ADMIN_ACK, MSG_ADMIN_LOGIN, MSG_ADMIN_LOGIN_ACK, MSG_ADMIN_LOGIN_NAK, MSG_ADMIN_COMMAND, MSG_ADMIN_COMMAND_ACK, MSG_ADMIN_COMMAND_NAK, MSG_KEEPALIVE = CWS_FIRSTCMDNO + 0x1d } net_msg_type_t; typedef enum { COMMTYPE_CLIENT, COMMTYPE_SERVER } comm_type_t; typedef struct customData_struct { uint16 sid; } customData_t; void des_key_parity_adjust(uint8 *key, uint8 len) { uint8 i, j, parity; for (i = 0; i < len; i++) { parity = 1; for (j = 1; j < 8; j++) if ((key[i] >> j) & 0x1) parity = ~parity & 0x01; key[i] |= parity; } } uint8 *des_key_spread(uint8 *normal) { static uint8 spread[16]; spread[ 0] = normal[ 0] & 0xfe; spread[ 1] = ((normal[ 0] << 7) | (normal[ 1] >> 1)) & 0xfe; spread[ 2] = ((normal[ 1] << 6) | (normal[ 2] >> 2)) & 0xfe; spread[ 3] = ((normal[ 2] << 5) | (normal[ 3] >> 3)) & 0xfe; spread[ 4] = ((normal[ 3] << 4) | (normal[ 4] >> 4)) & 0xfe; spread[ 5] = ((normal[ 4] << 3) | (normal[ 5] >> 5)) & 0xfe; spread[ 6] = ((normal[ 5] << 2) | (normal[ 6] >> 6)) & 0xfe; spread[ 7] = normal[ 6] << 1; spread[ 8] = normal[ 7] & 0xfe; spread[ 9] = ((normal[ 7] << 7) | (normal[ 8] >> 1)) & 0xfe; spread[10] = ((normal[ 8] << 6) | (normal[ 9] >> 2)) & 0xfe; spread[11] = ((normal[ 9] << 5) | (normal[10] >> 3)) & 0xfe; spread[12] = ((normal[10] << 4) | (normal[11] >> 4)) & 0xfe; spread[13] = ((normal[11] << 3) | (normal[12] >> 5)) & 0xfe; spread[14] = ((normal[12] << 2) | (normal[13] >> 6)) & 0xfe; spread[15] = normal[13] << 1; des_key_parity_adjust(spread, 16); return spread; } void des_random_get(uint8 *buffer, uint8 len) { uint8 idx = 0; int randomNo = 0; for (idx = 0; idx < len; idx++) { if (!(idx % 3)) randomNo = rand(); buffer[idx] = (randomNo >> ((idx % 3) << 3)) & 0xff; } } int des_encrypt(uint8 *buffer, int len, uint8 *deskey) { uint8 checksum = 0; uint8 noPadBytes; uint8 padBytes[7]; char ivec[8]; uint16 i; if (!deskey) return len; noPadBytes = (8 - ((len - 1) % 8)) % 8; if (len + noPadBytes + 1 >= CWS_NETMSGSIZE-8) return -1; des_random_get(padBytes, noPadBytes); for (i = 0; i < noPadBytes; i++) buffer[len++] = padBytes[i]; for (i = 2; i < len; i++) checksum ^= buffer[i]; buffer[len++] = checksum; des_random_get((uint8 *)ivec, 8); memcpy(buffer+len, ivec, 8); for (i = 2; i < len; i += 8) { cbc_crypt(deskey , (char *) buffer+i, 8, DES_ENCRYPT, ivec); ecb_crypt(deskey+8, (char *) buffer+i, 8, DES_DECRYPT); ecb_crypt(deskey , (char *) buffer+i, 8, DES_ENCRYPT); memcpy(ivec, buffer+i, 8); } len += 8; return len; } int des_decrypt(uint8 *buffer, int len, uint8 *deskey) { char ivec[8]; char nextIvec[8]; int i; uint8 checksum = 0; if (!deskey) return len; if ((len-2) % 8 || (len-2) < 16) return -1; len -= 8; memcpy(nextIvec, buffer+len, 8); for (i = 2; i < len; i += 8) { memcpy(ivec, nextIvec, 8); memcpy(nextIvec, buffer+i, 8); ecb_crypt(deskey , (char *) buffer+i, 8, DES_DECRYPT); ecb_crypt(deskey+8, (char *) buffer+i, 8, DES_ENCRYPT); cbc_crypt(deskey , (char *) buffer+i, 8, DES_DECRYPT, ivec); } for (i = 2; i < len; i++) checksum ^= buffer[i]; if (checksum) return -1; return len; } uint8 *des_login_key_get(uint8 *key1, uint8 *key2) { uint8 des14[14]; static uint8 des16[16]; int i; for (i = 0; i < 14; i++) des14[i] = key1[i] ^ key2[i]; memcpy(des16, des_key_spread(des14), 16); return des16; } int network_message_send(int handle, uint16 *netMsgId, customData_t *customData, uint8 *buffer, int len, uint8 *deskey, comm_type_t commType) { uint8 netbuf[CWS_NETMSGSIZE]; if (len < 3 || len + 12 > CWS_NETMSGSIZE || handle < 0) return -1; buffer[1] = (buffer[1] & 0xf0) | (((len - 3) >> 8) & 0x0f); buffer[2] = (len - 3) & 0xff; memcpy(netbuf+12, buffer, len); len += 12; if (netMsgId) { if (commType == COMMTYPE_CLIENT) (*netMsgId)++; netbuf[2] = (*netMsgId) >> 8; netbuf[3] = (*netMsgId) & 0xff; } else netbuf[2] = netbuf[3] = 0; if (customData) { netbuf[4] = customData->sid >> 8; netbuf[5] = customData->sid & 0xff; memset(netbuf+6, 0, 6); } else memset(netbuf+4, 0, 8); if ((len = des_encrypt(netbuf, len, deskey)) < 0) return -1; netbuf[0] = (len - 2) >> 8; netbuf[1] = (len - 2) & 0xff; write(handle, netbuf, len); return 0; } int network_message_receive(int handle, uint16 *netMsgId, customData_t *customData, uint8 *buffer, uint8 *deskey, comm_type_t commType) { int len; uint8 netbuf[CWS_NETMSGSIZE]; int returnLen; if (customData) memset(customData, 0, sizeof(customData_t)); if (!buffer || handle < 0) return -1; len = read(handle, netbuf, 2); if (!len) return 0; if (len != 2) return -1; if (((netbuf[0] << 8) | netbuf[1]) > CWS_NETMSGSIZE - 2) return -1; len = read(handle, netbuf+2, (netbuf[0] << 8) | netbuf[1]); if (!len) return 0; if (len != ((netbuf[0] << 8) | netbuf[1])) return -1; len += 2; if ((len = des_decrypt(netbuf, len, deskey)) < 15) return -1; if ((returnLen = (((netbuf[13] & 0x0f) << 8) | netbuf[14]) + 3) > len-12) return -1; if (netMsgId) { switch (commType) { case COMMTYPE_SERVER: *netMsgId = (netbuf[2] << 8) | netbuf[3]; break; case COMMTYPE_CLIENT: if (*netMsgId != ((netbuf[2] << 8) | netbuf[3])) return -1; break; default: return -1; break; } } if (customData) { customData->sid = (netbuf[4] << 8) | netbuf[5]; } memcpy(buffer, netbuf+12, returnLen); return returnLen; } void network_cmd_no_data_send(int handle, uint16 *netMsgId, customData_t *customData, net_msg_type_t cmd, uint8 *deskey, comm_type_t commType) { uint8 buffer[CWS_NETMSGSIZE]; buffer[0] = cmd; buffer[1] = 0; network_message_send(handle, netMsgId, customData, buffer, 3, deskey, commType); } int network_cmd_no_data_receive(int handle, uint16 *netMsgId, customData_t *customData, uint8 *deskey, comm_type_t commType) { uint8 buffer[CWS_NETMSGSIZE]; if (network_message_receive(handle, netMsgId, customData, buffer, deskey, commType) != 3) return -1; return buffer[0]; } int network_tcp_incoming_port_open(uint16 port) { struct sockaddr_in socketAddr; int socketOptActive = 1; int handle; if (!port) return -1; if ((handle = socket(PF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "network port %u open: ", port); perror("socket"); return -1; } if (setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, &socketOptActive, sizeof(int)) < 0) { fprintf(stderr, "network port %u open: error setsockopt\n", port); close(handle); return -1; } socketAddr.sin_family = AF_INET; socketAddr.sin_port = htons(port); socketAddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(handle, (struct sockaddr *) &socketAddr, sizeof (socketAddr)) < 0) { fprintf(stderr, "network port %u open: ", port); perror("bind"); close(handle); return -1; } if (listen(handle, 5) < 0) { fprintf(stderr, "network port %u open: ", port); perror("listen"); close(handle); return -1; } return handle; } int network_tcp_connection_accept(int socketHandle) { int connHandle; struct sockaddr_in peerAddr; struct sockaddr_in myAddr; socklen_t peerAddrLen; socklen_t myAddrLen; uint16 peerPort, myPort; uint32 peerIp, myIp; if (socketHandle < 0) return -1; peerAddrLen = sizeof(peerAddr); myAddrLen = sizeof(myAddr); if ((connHandle = accept(socketHandle, (struct sockaddr *) &peerAddr, &peerAddrLen)) < 0) { fprintf(stderr, "error network accept connection\n"); return -1; } peerPort = ntohs(peerAddr.sin_port); peerIp = ntohl(peerAddr.sin_addr.s_addr); myPort = ntohs(myAddr.sin_port); myIp = ntohl(myAddr.sin_addr.s_addr); /* optional: do checks on or log IP of incoming connections */ return connHandle; } int network_tcp_connection_open(uint8 *hostname, uint16 port) { int handle; struct hostent *hostaddr; struct sockaddr_in socketAddr; if (!(hostaddr = gethostbyname(hostname))) { fprintf(stderr, "Host lookup of %s failed\n", hostname); return -1; } if ((handle = socket(PF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "network make connection: couldn't create socket\n"); return -1; } socketAddr.sin_family = AF_INET; socketAddr.sin_port = htons(port); socketAddr.sin_addr.s_addr = ((struct in_addr *)hostaddr->h_addr)->s_addr; if (connect(handle, (struct sockaddr *)&socketAddr, sizeof(socketAddr)) < 0) { fprintf(stderr, "network make connection: error connect\n"); close(handle); return -1; } return handle; }