/* * Copyright (c) 2005 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * EncodedSourceID.c * SourceIDValidation * * (Uses FindPrimaryEthernetInterfaces() and GetPrimaryMACAddress() from developer.apple.com.) * Copyright (c) 2005 Apple. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include "EncodedSourceID.h" #define ACCOUNT_NAME_MAX_LEN 255 #define ENCODED_ID_MAX_LEN 31 #define SUCCESS 1 #define FAILURE 0 static int GenerateIDString(char *idBuffer, size_t idBufferLen); static int PrimaryMACAddressFromSystem(char *addressBuffer, size_t addressBufferLen); static kern_return_t GetPrimaryMACAddress(UInt8 *MACAddress); static kern_return_t FindPrimaryEthernetInterfaces(io_iterator_t *matchingServices); int base64Encode(const void *inSourceData, size_t inSourceSize, void *inEncodedDataBuffer, size_t inEncodedDataBufferSize, size_t *outEncodedSize); unsigned char base64EncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /* Returns a Base-64 encoded MD5 hash of 'username:primary-mac-address' */ int GetEncodedSourceID(char encodedIdBuffer[32]) { char idStr[ACCOUNT_NAME_MAX_LEN + (kIOEthernetAddressSize * 2) + 2]; int status = FAILURE; CC_MD5_CTX md5_ctx; size_t b64Len = 0; int result; unsigned char MD5Value[CC_MD2_DIGEST_LENGTH]; if (encodedIdBuffer == NULL) return FAILURE; encodedIdBuffer[0] = '\0'; if (GenerateIDString(idStr, sizeof(idStr))) { /* Convert idStr to MD5 */ CC_MD5_Init(&md5_ctx); CC_MD5_Update(&md5_ctx, idStr, (CC_LONG)strlen(idStr)); CC_MD5_Final(MD5Value, &md5_ctx); /* Convert MD5 to Base-64 */ result = base64Encode(MD5Value, CC_MD2_DIGEST_LENGTH, encodedIdBuffer, 32, &b64Len); if (result != 0) goto out; if (b64Len > 0) { if (b64Len > ENCODED_ID_MAX_LEN) encodedIdBuffer[ENCODED_ID_MAX_LEN] = '\0'; else encodedIdBuffer[b64Len] = '\0'; /* Overwrites the newline char */ status = SUCCESS; } } out: return status; } static int GenerateIDString(char *idBuffer, size_t idBufferLen) { char addressString[(kIOEthernetAddressSize * 2) + 1]; const char *accountName = NULL; if (idBuffer == NULL) return FAILURE; idBuffer[0] = '\0'; accountName = getlogin(); if ( (accountName==NULL) || (strlen(accountName)<=0) ) { return FAILURE; } if (PrimaryMACAddressFromSystem(addressString, sizeof(addressString)) == SUCCESS) { snprintf(idBuffer, idBufferLen, "%s:%s", accountName, addressString); return SUCCESS; } else { return FAILURE; } } /* Writes the MAC address of the primary interface to the addressBuffer argument as a * human-readable hex string. */ static int PrimaryMACAddressFromSystem(char *addressBuffer, size_t addressBufferLen) { int status = FAILURE, len = kIOEthernetAddressSize; UInt8 MACAddress[ kIOEthernetAddressSize ]; kern_return_t kernResult = KERN_FAILURE; if (addressBuffer == NULL) return status; kernResult = GetPrimaryMACAddress(MACAddress); if (KERN_SUCCESS == kernResult) { char *bufPtr = addressBuffer; const UInt8 *addrPtr = &MACAddress[0]; while ( len-- ) { bufPtr += snprintf( bufPtr, addressBufferLen, "%2.2x", *addrPtr++ ); } if (strlen(addressBuffer) > 0) { status = SUCCESS; } } return status; } /* ----------------------------------------------------------------------------- Get the mac address of the primary interface ----------------------------------------------------------------------------- */ static kern_return_t GetPrimaryMACAddress(UInt8 *MACAddress) { io_object_t intfService; io_object_t controllerService; kern_return_t kernResult = KERN_FAILURE; io_iterator_t intfIterator; kernResult = FindPrimaryEthernetInterfaces(&intfIterator); if (kernResult != KERN_SUCCESS) return kernResult; // Initialize the returned address bzero(MACAddress, kIOEthernetAddressSize); // IOIteratorNext retains the returned object, so release it when we're done with it. while ((intfService = IOIteratorNext(intfIterator))) { CFTypeRef MACAddressAsCFData; // IONetworkControllers can't be found directly by the IOServiceGetMatchingServices call, // since they are hardware nubs and do not participate in driver matching. In other words, // registerService() is never called on them. So we've found the IONetworkInterface and will // get its parent controller by asking for it specifically. // IORegistryEntryGetParentEntry retains the returned object, so release it when we're done with it. kernResult = IORegistryEntryGetParentEntry( intfService, kIOServicePlane, &controllerService ); if (kernResult == KERN_SUCCESS) { // Retrieve the MAC address property from the I/O Registry in the form of a CFData MACAddressAsCFData = IORegistryEntryCreateCFProperty( controllerService, CFSTR(kIOMACAddress), kCFAllocatorDefault, 0); if (MACAddressAsCFData) { // Get the raw bytes of the MAC address from the CFData CFDataGetBytes(MACAddressAsCFData, CFRangeMake(0, kIOEthernetAddressSize), MACAddress); CFRelease(MACAddressAsCFData); } // Done with the parent Ethernet controller object so we release it. IOObjectRelease(controllerService); } // Done with the Ethernet interface object so we release it. IOObjectRelease(intfService); } IOObjectRelease(intfIterator); return kernResult; } /* ----------------------------------------------------------------------------- Returns an iterator containing the primary (built-in) Ethernet interface. The caller is responsible for releasing the iterator after the caller is done with it. ----------------------------------------------------------------------------- */ static kern_return_t FindPrimaryEthernetInterfaces(io_iterator_t *matchingServices) { kern_return_t kernResult; mach_port_t masterPort; CFMutableDictionaryRef matchingDict; CFMutableDictionaryRef propertyMatchDict; // Retrieve the Mach port used to initiate communication with I/O Kit kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort); if (kernResult != KERN_SUCCESS) { //error("Can't create IOKit MasterPort (%d)\n", kernResult); return kernResult; } // Ethernet interfaces are instances of class kIOEthernetInterfaceClass. // IOServiceMatching is a convenience function to create a dictionary with the key kIOProviderClassKey and // the specified value. matchingDict = IOServiceMatching(kIOEthernetInterfaceClass); if (matchingDict) { // Each IONetworkInterface object has a Boolean property with the key kIOPrimaryInterface. Only the // primary (built-in) interface has this property set to TRUE. // IOServiceGetMatchingServices uses the default matching criteria defined by IOService. This considers // only the following properties plus any family-specific matching in this order of precedence // (see IOService::passiveMatch): // // kIOProviderClassKey (IOServiceMatching) // kIONameMatchKey (IOServiceNameMatching) // kIOPropertyMatchKey // kIOPathMatchKey // kIOMatchedServiceCountKey // family-specific matching // kIOBSDNameKey (IOBSDNameMatching) // kIOLocationMatchKey // The IONetworkingFamily does not define any family-specific matching. This means that in // order to have IOServiceGetMatchingServices consider the kIOPrimaryInterface property, we must // add that property to a separate dictionary and then add that to our matching dictionary // specifying kIOPropertyMatchKey. propertyMatchDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (propertyMatchDict) { // Set the value in the dictionary of the property with the given key, or add the key // to the dictionary if it doesn't exist. This call retains the value object passed in. CFDictionarySetValue(propertyMatchDict, CFSTR(kIOPrimaryInterface), kCFBooleanTrue); // Now add the dictionary containing the matching value for kIOPrimaryInterface to our main // matching dictionary. This call will retain propertyMatchDict, so we can release our reference // on propertyMatchDict after adding it to matchingDict. CFDictionarySetValue(matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict); CFRelease(propertyMatchDict); } } // IOServiceGetMatchingServices retains the returned iterator, so release the iterator when we're done with it. // IOServiceGetMatchingServices also consumes a reference on the matching dictionary so we don't need to release // the dictionary explicitly. kernResult = IOServiceGetMatchingServices(masterPort, matchingDict, matchingServices); return kernResult; } // // Base64 encoder based documented in RFC 1521 // int base64Encode(const void *inSourceData, size_t inSourceSize, void *inEncodedDataBuffer, size_t inEncodedDataBufferSize, size_t *outEncodedSize) { int err; const unsigned char *src; const unsigned char *end; unsigned char *dst; size_t encodedSize; src = (const unsigned char *) inSourceData; end = src + inSourceSize; dst = (unsigned char *) inEncodedDataBuffer; err = 0; // every 3 bytes are turned into 4 bytes and + 2 for padding. encodedSize = ( ( inSourceSize + 2 ) / 3 ) * 4; require_action_quiet( encodedSize <= inEncodedDataBufferSize, exit, err = EINVAL ); // Process all 3 byte chunks into 4 byte encoded chunks. while( ( end - src ) >= 3 ) { dst[ 0 ] = base64EncodeTable[ src[ 0 ] >> 2 ]; dst[ 1 ] = base64EncodeTable[ ( ( src[ 0 ] & 0x03 ) << 4 ) + ( src[ 1 ] >> 4 ) ]; dst[ 2 ] = base64EncodeTable[ ( ( src[ 1 ] & 0x0F ) << 2 ) + ( src[ 2 ] >> 6 ) ]; dst[ 3 ] = base64EncodeTable[ src[ 2 ] & 0x3F ]; src += 3; dst += 4; } // Process any remaining 1 or 2 bytes into a 4 byte chunk, padding with 1 or 2 '=' characters as needed. switch( end - src ) { case 1: dst[ 0 ] = base64EncodeTable[ src[ 0 ] >> 2 ]; dst[ 1 ] = base64EncodeTable[ ( src[ 0 ] & 0x03 ) << 4 ]; dst[ 2 ] = '='; dst[ 3 ] = '='; dst += 4; break; case 2: dst[ 0 ] = base64EncodeTable[ src[ 0 ] >> 2 ]; dst[ 1 ] = base64EncodeTable[ ( ( src[ 0 ] & 0x03 ) << 4 ) + ( src[ 1 ] >> 4 ) ]; dst[ 2 ] = base64EncodeTable[ ( src[ 1 ] & 0x0F ) << 2 ]; dst[ 3 ] = '='; dst += 4; break; default: break; } exit: if( outEncodedSize ) *outEncodedSize = (size_t)( dst - ( (unsigned char *) inEncodedDataBuffer ) ); return( err ); }