@@ -113,6 +113,10 @@ static void ListMDfunc(const EVP_MD *m, const char *from, const char *to, void *
113113static BIO * PEMOpenReadSteam (const char * fnOrData )
114114 NS_GNUC_NONNULL (1 );
115115
116+ static void uuid_format (unsigned char * b , char * dst ) NS_GNUC_NONNULL (1 ,2 ) NS_GNUC_PURE ;
117+ static int uuid_v4 (char * dst ) NS_GNUC_NONNULL (1 );
118+ static int uuid_v7 (char * dst ) NS_GNUC_NONNULL (1 );
119+
116120static TCL_OBJCMDPROC_T CryptoHmacAddObjCmd ;
117121static TCL_OBJCMDPROC_T CryptoHmacFreeObjCmd ;
118122static TCL_OBJCMDPROC_T CryptoHmacGetObjCmd ;
@@ -3420,6 +3424,250 @@ NsTclCryptoRandomBytesObjCmd(ClientData UNUSED(clientData), Tcl_Interp *interp,
34203424 return result ;
34213425}
34223426
3427+
3428+
3429+ /*
3430+ *----------------------------------------------------------------------
3431+ *
3432+ * uuid_format --
3433+ *
3434+ * Convert 16 raw bytes into the canonical RFC 4122 UUID string
3435+ * form (8-4-4-4-12 hex digits, lowercase, with hyphens). The
3436+ * caller must provide a destination buffer of at least 37 bytes
3437+ * to hold the 36-character UUID plus the trailing NUL.
3438+ *
3439+ * Results:
3440+ * None. The formatted UUID string is written into 'dst'.
3441+ *
3442+ *----------------------------------------------------------------------
3443+ */
3444+
3445+ static inline void
3446+ uuid_format (unsigned char * b , char * dst )
3447+ {
3448+ /*
3449+ * Format into canonical string 8-4-4-4-12
3450+ */
3451+ Ns_HexString (& b [0 ], & dst [0 ], 4 , NS_FALSE ); /* bytes 0..3 -> dst[0..7] */
3452+ dst [8 ] = '-' ;
3453+
3454+ Ns_HexString (& b [4 ], & dst [9 ], 2 , NS_FALSE ); /* bytes 4..5 -> dst[9..12] */
3455+ dst [13 ] = '-' ;
3456+
3457+ Ns_HexString (& b [6 ], & dst [14 ], 2 , NS_FALSE ); /* bytes 6..7 -> dst[14..17] */
3458+ dst [18 ] = '-' ;
3459+
3460+ Ns_HexString (& b [8 ], & dst [19 ], 2 , NS_FALSE ); /* bytes 8..9 -> dst[19..22] */
3461+ dst [23 ] = '-' ;
3462+
3463+ Ns_HexString (& b [10 ], & dst [24 ], 6 , NS_FALSE ); /* bytes 10..15 -> dst[24..35] */
3464+ dst [36 ] = '\0' ;
3465+ }
3466+
3467+ /*
3468+ *----------------------------------------------------------------------
3469+ *
3470+ * uuid_v4 --
3471+ *
3472+ * Generate a Version 4 (random) RFC 4122 UUID string. The
3473+ * function fills a 16-byte buffer with cryptographically strong
3474+ * random bytes via RAND_bytes() and Sets the UUID version and
3475+ * variant bits:
3476+ * * byte[6] high nibble -> 0b0100 (version 4)
3477+ * * byte[8] high bits -> 0b10 (RFC 4122 variant)
3478+ *
3479+ * Finally, it formats the result into canonical text using
3480+ * uuid_format().
3481+ *
3482+ * Results:
3483+ * Returns TCL_OK on success (UUID written into 'dst'),
3484+ * or TCL_ERROR if RAND_bytes() failed and no valid UUID was produced.
3485+ *
3486+ * Side effects:
3487+ * None.
3488+ *
3489+ *----------------------------------------------------------------------
3490+ */
3491+ static inline int
3492+ uuid_v4 (char * dst )
3493+ {
3494+ unsigned char b [16 ];
3495+
3496+ if (RAND_bytes (b , sizeof (b )) != 1 ) {
3497+ return TCL_ERROR ;
3498+ }
3499+
3500+ /*
3501+ * RFC 4122 version 4:
3502+ * - Set the 4 most significant bits of byte 6 (index 6) to 0100 (0x4).
3503+ * - Set the 2 most significant bits of byte 8 (index 8) to 10.
3504+ *
3505+ * Bytes are 0..15.
3506+ */
3507+ b [6 ] = (unsigned char )((b [6 ] & 0x0F ) | 0x40 ); /* version 4 */
3508+ b [8 ] = (unsigned char )((b [8 ] & 0x3F ) | 0x80 ); /* variant RFC4122 */
3509+
3510+ uuid_format (b , dst );
3511+ return TCL_OK ;
3512+ }
3513+
3514+
3515+ /*
3516+ *----------------------------------------------------------------------
3517+ *
3518+ * uuid_v7 --
3519+ *
3520+ * Summary: Generate a Version 7 (time-ordered) UUID per RFC 9562 and
3521+ * write its canonical textual form ("8-4-4-4-12") into 'dst'.
3522+ *
3523+ * Parameters:
3524+ * dst - Pointer to a character buffer that will receive the textual
3525+ * UUID; must not be NULL and must hold at least 37 bytes
3526+ * (36 printable characters plus terminating NUL).
3527+ *
3528+ * Returns:
3529+ * TCL_OK on success (a valid UUIDv7 string is written to 'dst' and NUL-terminated).
3530+ * TCL_ERROR on failure when RAND_bytes() reports an error; in this case the function
3531+ * does not guarantee a valid UUID in 'dst'.
3532+ *
3533+ * Side effects:
3534+ * None. The function calls Ns_GetTime(), Ns_TimeToMilliseconds(),
3535+ * RAND_bytes(), memcpy(), and uuid_format(); writes into 'dst'.
3536+ *
3537+ *----------------------------------------------------------------------
3538+ */
3539+ static inline int
3540+ uuid_v7 (char * dst )
3541+ {
3542+ unsigned char out [16 ];
3543+ unsigned char rnd [10 ];
3544+ time_t ms ;
3545+ unsigned long b0 , b1 , b2 ;
3546+ Ns_Time now ;
3547+
3548+ /*
3549+ * We need 10 random bytes:
3550+ * - parts of them go into the rand_a / rand_b fields.
3551+ */
3552+ if (RAND_bytes (rnd , sizeof (rnd )) != 1 ) {
3553+ return TCL_ERROR ;
3554+ }
3555+
3556+ /*
3557+ * We'll compose the 16 output bytes:
3558+ *
3559+ * Bytes 0..5 : timestamp ms (big-endian 48-bit)
3560+ * Byte 6 : high nibble = version(0x7), low nibble = top 4 bits of random
3561+ * Byte 7 : next 8 random bits
3562+ * Byte 8 : variant '10' in the top bits, then next 6 random bits
3563+ * Byte 9..15 : remaining random bytes
3564+ */
3565+
3566+ Ns_GetTime (& now );
3567+ ms = Ns_TimeToMilliseconds (& now );
3568+
3569+ /* timestamp big-endian into out[0..5] */
3570+ out [0 ] = (unsigned char )((ms >> 40 ) & 0xFF );
3571+ out [1 ] = (unsigned char )((ms >> 32 ) & 0xFF );
3572+ out [2 ] = (unsigned char )((ms >> 24 ) & 0xFF );
3573+ out [3 ] = (unsigned char )((ms >> 16 ) & 0xFF );
3574+ out [4 ] = (unsigned char )((ms >> 8 ) & 0xFF );
3575+ out [5 ] = (unsigned char )( ms & 0xFF );
3576+
3577+ /*
3578+ * Pull first 3 random bytes for the structured fields.
3579+ */
3580+ b0 = rnd [0 ];
3581+ b1 = rnd [1 ];
3582+ b2 = rnd [2 ];
3583+
3584+ /*
3585+ * Byte 6:
3586+ * high nibble = version (0111 -> 0x7)
3587+ * low nibble = low 4 bits of b0
3588+ */
3589+ out [6 ] = (unsigned char )(0x70 | (b0 & 0x0F ));
3590+
3591+ /*
3592+ * Byte 7:
3593+ * full 8 bits from b1
3594+ */
3595+ out [7 ] = (unsigned char )b1 ;
3596+
3597+ /*
3598+ * Byte 8:
3599+ * variant '10' in the two MSBs -> 0b10xxxxxx
3600+ * keep lower 6 bits from b2
3601+ *
3602+ * mask lower 6 bits: b2 & 0x3F
3603+ * set top bits to 10: | 0x80
3604+ */
3605+ out [8 ] = (unsigned char )((b2 & 0x3F ) | 0x80 );
3606+
3607+ /*
3608+ * The rest (bytes 9..15) are just rnd[3]..rnd[9]
3609+ */
3610+ memcpy (& out [9 ], & rnd [3 ], 7 );
3611+ uuid_format (out , dst );
3612+
3613+ return TCL_OK ;
3614+ }
3615+
3616+ /*
3617+ *----------------------------------------------------------------------
3618+ *
3619+ * NsTclCryptoUUIDCmd --
3620+ *
3621+ * Implements "ns_crypto::uuid". Returns UUID in version v4
3622+ *
3623+ * Example: ns_crypto::randombytes 20
3624+ *
3625+ * Results:
3626+ * Tcl Result Code.
3627+ *
3628+ * Side effects:
3629+ * None
3630+ *
3631+ *----------------------------------------------------------------------
3632+ */
3633+ int
3634+ NsTclCryptoUUIDObjCmd (ClientData UNUSED (clientData ), Tcl_Interp * interp , TCL_SIZE_T objc , Tcl_Obj * const * objv )
3635+ {
3636+ int result , versionInt = 4 ;
3637+ static Ns_ObjvTable uuidVersions [] = {
3638+ {"v4" , 4u },
3639+ {"v7" , 7u },
3640+ {NULL , 0u }
3641+ };
3642+ Ns_ObjvSpec lopts [] = {
3643+ {"-version" , Ns_ObjvIndex , & versionInt , uuidVersions },
3644+ {NULL , NULL , NULL , NULL }
3645+ };
3646+
3647+ if (Ns_ParseObjv (lopts , NULL , interp , 1 , objc , objv ) != NS_OK ) {
3648+ result = TCL_ERROR ;
3649+
3650+ } else {
3651+ Tcl_DString ds ;
3652+
3653+ Tcl_DStringInit (& ds );
3654+ Tcl_DStringSetLength (& ds , (TCL_SIZE_T )36 );
3655+
3656+ if (versionInt == 4 ) {
3657+ result = uuid_v4 (ds .string );
3658+ } else {
3659+ result = uuid_v7 (ds .string );
3660+ }
3661+ if (result == TCL_OK ) {
3662+ Tcl_DStringResult (interp , & ds );
3663+ } else {
3664+ Ns_TclPrintfResult (interp , "UUID conversion failed" );
3665+ }
3666+ }
3667+
3668+ return result ;
3669+ }
3670+
34233671# ifdef OPENSSL_NO_EC
34243672int
34253673NsTclCryptoEckeyObjCmd (ClientData UNUSED (clientData ), Tcl_Interp * interp , TCL_SIZE_T UNUSED (objc ), Tcl_Obj * const * UNUSED (objv ))
@@ -3468,6 +3716,13 @@ NsTclCryptoRandomBytesObjCmd(ClientData UNUSED(clientData), Tcl_Interp *interp,
34683716 return TCL_ERROR ;
34693717}
34703718
3719+ int
3720+ NsTclCryptoUUIDCmd (ClientData UNUSED (clientData ), Tcl_Interp * interp , TCL_SIZE_T UNUSED (objc ), Tcl_Obj * const * UNUSED (objv ))
3721+ {
3722+ Ns_TclPrintfResult (interp , "Command requires support for OpenSSL built into NaviServer" );
3723+ return TCL_ERROR ;
3724+ }
3725+
34713726int
34723727NsTclCryptoEckeyObjCmd (ClientData UNUSED (clientData ), Tcl_Interp * interp , TCL_SIZE_T UNUSED (objc ), Tcl_Obj * const * UNUSED (objv ))
34733728{
0 commit comments