anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

validation_CZ_BN.c (3764B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2020, 2021, 2026 Anastasis SARL
      4 
      5   Anastasis is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   Anastasis; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file reducer/validation_CZ_BN.c
     18  * @brief validation of Czech birth numbers
     19  * @author Christian Grothoff
     20  */
     21 #include <string.h>
     22 #include <stdbool.h>
     23 #include <stdio.h>
     24 
     25 
     26 /**
     27  * Check the YYMMDD part of a birth number.
     28  *
     29  * The month carries the sex and, since 2004, an overflow marker: women have
     30  * 50 added, and an exhausted daily serial adds another 20.
     31  *
     32  * @param d the first six characters of the birth number
     33  * @param allow_overflow true if the +20/+70 month variants are acceptable
     34  * @param[out] yy set to the two-digit year
     35  * @return true if the date is well-formed
     36  */
     37 static bool
     38 check_date (const char *d,
     39             bool allow_overflow,
     40             unsigned int *yy)
     41 {
     42   unsigned int mm;
     43   unsigned int dd;
     44 
     45   for (unsigned int i = 0; i<6; i++)
     46     if ( ('0' > d[i]) || ('9' < d[i]) )
     47       return false;
     48   *yy = (d[0] - '0') * 10 + (d[1] - '0');
     49   mm = (d[2] - '0') * 10 + (d[3] - '0');
     50   dd = (d[4] - '0') * 10 + (d[5] - '0');
     51   if ( (mm >= 71) && (mm <= 82) )
     52   {
     53     if (! allow_overflow)
     54       return false;
     55     mm -= 70;
     56   }
     57   else if ( (mm >= 51) && (mm <= 62) )
     58   {
     59     mm -= 50;
     60   }
     61   else if ( (mm >= 21) && (mm <= 32) )
     62   {
     63     if (! allow_overflow)
     64       return false;
     65     mm -= 20;
     66   }
     67   if ( (mm < 1) || (mm > 12) )
     68     return false;
     69   if ( (dd < 1) || (dd > 31) )
     70     return false;
     71   return true;
     72 }
     73 
     74 
     75 /**
     76  * Function to validate a Czech birth number (rodné číslo).
     77  *
     78  * The modern form is YYMMDD/XXXC, where the ten digits must be divisible by
     79  * 11; as a documented exception, a first-nine-digits remainder of 10 is
     80  * written with a check digit of 0.  Numbers issued before 1954 have only nine
     81  * digits (YYMMDD/XXX) and carry no check digit at all, so nothing beyond the
     82  * date can be verified for them.  Those are accepted only when the year reads
     83  * as 1900-1953; a nine-digit number claiming a later year is a truncated
     84  * modern one.  (People born before 1900 also hold nine-digit numbers, which
     85  * this therefore rejects.)
     86  *
     87  * @param b_number birth number to validate (input)
     88  * @return true if b_number is valid
     89  */
     90 bool
     91 CZ_BN_check (const char *b_number);
     92 
     93 /* declaration to fix compiler warning */
     94 bool
     95 CZ_BN_check (const char *b_number)
     96 {
     97   size_t len = strlen (b_number);
     98   unsigned int yy;
     99 
    100   if ( (10 != len) && (11 != len) )
    101     return false;
    102   if ('/' != b_number[6])
    103     return false;
    104   for (size_t i = 7; i<len; i++)
    105     if ( ('0' > b_number[i]) || ('9' < b_number[i]) )
    106       return false;
    107   if (! check_date (b_number,
    108                     11 == len,
    109                     &yy))
    110     return false;
    111   if (10 == len)
    112   {
    113     /* pre-1954 form: no check digit exists */
    114     return (yy < 54);
    115   }
    116   {
    117     unsigned long long n = 0;
    118     unsigned long long base;
    119     unsigned int check;
    120 
    121     for (unsigned int i = 0; i<6; i++)
    122       n = n * 10 + (b_number[i] - '0');
    123     for (unsigned int i = 7; i<11; i++)
    124       n = n * 10 + (b_number[i] - '0');
    125     base = n / 10;
    126     check = (unsigned int) (n % 10);
    127     if (10 == base % 11)
    128       return (0 == check);
    129     return (0 == n % 11);
    130   }
    131 }